5 Commits

Author SHA1 Message Date
TianMiao
e9622c28ad featUpdate QQ group link in README 2026-04-29 17:35:19 +08:00
CreeperAWA
8a44af078a [ Feat] Update QQ group link in README 2026-02-16 23:36:37 +08:00
Minoricew
c5c63c6639 [🛠️ Fix] Direct unlock not show when !showTypeList.includes("direct") 2025-12-30 20:11:21 +08:00
Minoricew
420f35a5e4 [ Feat] Impl file-based IPC for Aikari installation 2025-12-16 02:04:17 +08:00
Minoricew
1031bf04bb [🔄 Chore] Add telegram links into README 2025-12-14 13:40:04 +08:00
4 changed files with 255 additions and 132 deletions

View File

@@ -9,11 +9,12 @@
<br />
<center>
<a href="https://campus.seewo.com/iot-public/file/?key=iot_doc_seewoServiceUpdateLog">
<a href="https://forum.smart-teach.cn/d/898-xi-wo-guan-jia-zhe-kuai-zhen-de-shi-zhao-xiao-si-liao">
<img src="https://docs.aurax.cc/static/img/emg_announcement_banner.png" />
</a>
</center>
<br />
<br />
> [!TIP]
@@ -29,8 +30,9 @@
> [!NOTE]
> **社群信息**
>
> - [QQ 群 (用户自治)](https://qm.qq.com/q/buo7m9oHBK)
> - [Telegram 群组](https://www.bilibili.com/video/BV1GJ411x7h7/) (暂时还没有...)
> - [QQ 群 (用户自治)](https://c.colchicum.moe)
> - [Telegram 公告频道](https://t.me/HugoAura)
> - [Telegram 群组](https://t.me/HugoAura_Chat)
> - [STCN 论坛讨论版块](https://forum.smart-teach.cn/t/hugoaura)
![Repobeats](https://repobeats.axiom.co/api/embed/69b5be5daacef624b8f5e4b8966a0b5898439a22.svg "Repobeats analytics image")

View File

@@ -68,149 +68,166 @@ const functions = {
* @param {"stable" | "alpha"} channel
* @param {(arg: DownloadTask) => any} callbackFn
* @param {string} binPath
* @param {string} logPath
* @param {string} progressFilePath
*/
handleAikariDlAndInstall: async (channel, callbackFn, binPath) => {
// TODO: Channel selection
const apiInfo = global.__HUGO_AURA_API__;
handleAikariDlAndInstall: async (
channel,
callbackFn,
binPath,
logPath,
progressFilePath
) => {
let dlResult = false;
if (fs.existsSync(path.join(path.dirname(binPath), ".force"))) {
dlResult = true;
} else {
// TODO: Channel selection
const apiInfo = global.__HUGO_AURA_API__;
const getVerPromise = new Promise(async (resolveGetVerReq) => {
// ↓ 目前 channel param 没有什么用处
for (const apiDomain of apiInfo.domains) {
const reqPromise = new Promise((resolveHttpRequest) => {
nodeHttps
.get(
`${apiDomain}${apiInfo.aikariUpdate}?channel=${channel}`,
(rep) => {
let dataChunk = "";
rep.on("data", (chunk) => {
dataChunk += chunk;
});
const getVerPromise = new Promise(async (resolveGetVerReq) => {
// ↓ 目前 channel param 没有什么用处
for (const apiDomain of apiInfo.domains) {
const reqPromise = new Promise((resolveHttpRequest) => {
nodeHttps
.get(
`${apiDomain}${apiInfo.aikariUpdate}?channel=${channel}`,
(rep) => {
let dataChunk = "";
rep.on("data", (chunk) => {
dataChunk += chunk;
});
rep.on("end", () => {
let parsedData = {};
try {
parsedData = JSON.parse(dataChunk);
} catch (e) {
callbackFn({
id: "",
progress: 0,
status: "struggling",
dlUrl: null,
savePath: null,
message: `数据解析失败, 正在尝试 API 域名 ${
apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1]
} ...`,
errorObj: e,
});
setTimeout(() => {
resolveHttpRequest({
success: false,
rep.on("end", () => {
let parsedData = {};
try {
parsedData = JSON.parse(dataChunk);
} catch (e) {
callbackFn({
id: "",
progress: 0,
status: "struggling",
dlUrl: null,
savePath: null,
message: `数据解析失败, 正在尝试 API 域名 ${
apiInfo.domains[
apiInfo.domains.indexOf(apiDomain) + 1
]
} ...`,
errorObj: e,
});
}, 1000);
setTimeout(() => {
resolveHttpRequest({
success: false,
errorObj: e,
});
}, 1000);
return;
}
resolveHttpRequest({
success: true,
data: parsedData,
});
return;
}
resolveHttpRequest({
success: true,
data: parsedData,
});
return;
});
}
)
.on("error", (e) => {
callbackFn({
id: "",
progress: 0,
status: "struggling",
dlUrl: null,
savePath: null,
message: `连接失败, 正在尝试 API 域名 ${
apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1]
} ...`,
errorObj: e,
});
setTimeout(() => {
resolveHttpRequest({
success: false,
}
)
.on("error", (e) => {
callbackFn({
id: "",
progress: 0,
status: "struggling",
dlUrl: null,
savePath: null,
message: `连接失败, 正在尝试 API 域名 ${
apiInfo.domains[apiInfo.domains.indexOf(apiDomain) + 1]
} ...`,
errorObj: e,
});
}, 1000);
});
});
const requestResult = await reqPromise;
if (requestResult.success) {
resolveGetVerReq({
success: true,
data: requestResult.data,
setTimeout(() => {
resolveHttpRequest({
success: false,
errorObj: e,
});
}, 1000);
});
});
break;
} else {
continue;
const requestResult = await reqPromise;
if (requestResult.success) {
resolveGetVerReq({
success: true,
data: requestResult.data,
});
break;
} else {
continue;
}
}
resolveGetVerReq({
success: false,
data: null,
});
});
const rawResInfo = await getVerPromise;
if (!rawResInfo.success) {
callbackFn({
id: "",
progress: 0,
status: "failed",
dlUrl: null,
savePath: null,
message:
"未能获取 Aikari 版本信息, 所有 API 域名均无法连接, 建议前往 GitHub 下载安装包并自行安装",
});
return false;
}
resolveGetVerReq({
success: false,
data: null,
});
});
const aikariVersionInfo = rawResInfo.data;
const rawResInfo = await getVerPromise;
if (!rawResInfo.success) {
callbackFn({
id: "",
progress: 0,
status: "failed",
dlUrl: null,
savePath: null,
message:
"未能获取 Aikari 版本信息, 所有 API 域名均无法连接, 建议前往 GitHub 下载安装包并自行安装",
});
return false;
}
let deviceArch = process.env.PROCESSOR_ARCHITEW6432
? process.env.PROCESSOR_ARCHITEW6432
: process.env.PROCESSOR_ARCHITECTURE;
// @ts-expect-error
deviceArch = deviceArch.toLowerCase();
const aikariVersionInfo = rawResInfo.data;
if (
!Object.keys(aikariVersionInfo.data.downloadUrl).includes(deviceArch)
) {
callbackFn({
id: "",
progress: 0,
status: "failed",
dlUrl: null,
savePath: null,
message: `不支持的处理器架构, 检测到的架构: "${deviceArch}"`,
});
return false;
}
let deviceArch = process.env.PROCESSOR_ARCHITEW6432
? process.env.PROCESSOR_ARCHITEW6432
: process.env.PROCESSOR_ARCHITECTURE;
// @ts-expect-error
deviceArch = deviceArch.toLowerCase();
const downloadFilePromise = new Promise((resolve) => {
fsComposables.downloadFile(
aikariVersionInfo.data.downloadUrl[deviceArch],
binPath,
(...args) => {
if (args[0].status === "done") {
resolve(true);
} else if (args[0].status === "failed") {
resolve(false);
}
if (!Object.keys(aikariVersionInfo.data.downloadUrl).includes(deviceArch)) {
callbackFn({
id: "",
progress: 0,
status: "failed",
dlUrl: null,
savePath: null,
message: `不支持的处理器架构, 检测到的架构: "${deviceArch}"`,
});
return false;
}
const downloadFilePromise = new Promise((resolve) => {
fsComposables.downloadFile(
aikariVersionInfo.data.downloadUrl[deviceArch],
binPath,
(...args) => {
if (args[0].status === "done") {
resolve(true);
} else if (args[0].status === "failed") {
resolve(false);
callbackFn(...args);
}
);
});
callbackFn(...args);
}
);
});
const dlResult = await downloadFilePromise;
dlResult = await downloadFilePromise;
}
if (dlResult) {
callbackFn({
@@ -224,7 +241,7 @@ const functions = {
const runInstPromise = new Promise((resolve) => {
exec(
`"${binPath}" /VERYSILENT /SUPPRESSMSGBOXES /LOG="${binPath}\\..\\Aikari-Install.log"`,
`"${binPath}" /VERYSILENT /SUPPRESSMSGBOXES /LOG="${logPath}" /PROGRESS_FILE="${progressFilePath}"`,
(err, stdout, stderr) => {
if (err) {
console.error(
@@ -237,6 +254,13 @@ const functions = {
);
});
if (!fs.existsSync(path.dirname(progressFilePath))) {
fs.mkdirSync(path.dirname(progressFilePath));
}
if (!fs.existsSync(progressFilePath)) {
fs.writeFileSync(progressFilePath, "");
}
callbackFn({
id: "INSTALL_STAGE",
progress: 15,
@@ -246,8 +270,87 @@ const functions = {
message: "正在安装 Aikari...",
});
let instFilesStageLastFakeLoadProgressPercent = -1;
const fileProgressWatcher = fs.watch(progressFilePath, (eventType) => {
if (eventType === "change") {
try {
const content = fs.readFileSync(progressFilePath, "utf-8").trim();
if (content === "") {
callbackFn({
id: "INSTALL_STAGE",
progress: 20,
status: "progressing",
dlUrl: null,
savePath: null,
message: "正在准备安装...",
});
} else {
const contentArr = content.split(";");
switch (contentArr[0]) {
case "DL_VC_REDIST":
callbackFn({
id: "INSTALL_STAGE",
progress:
20 +
Math.round((parseFloat(contentArr[1]) / 10) * 2), // 20 + [0, 20] === 20 ~ 40
status: "progressing",
dlUrl: null,
savePath: null,
message: `正在下载 VC++ 运行时... (${contentArr[1]}%)`,
});
break;
case "INST_FILES":
if (instFilesStageLastFakeLoadProgressPercent === -1) {
instFilesStageLastFakeLoadProgressPercent = 60;
} else if (instFilesStageLastFakeLoadProgressPercent < 85) {
instFilesStageLastFakeLoadProgressPercent +=
Math.random() * (5 - 1) + 1;
}
callbackFn({
id: "INSTALL_STAGE",
progress: instFilesStageLastFakeLoadProgressPercent,
status: "progressing",
dlUrl: null,
savePath: null,
message: `正在解压文件...`,
});
break;
case "INST_VC_REDIST":
callbackFn({
id: "INSTALL_STAGE",
progress: 90,
status: "progressing",
dlUrl: null,
savePath: null,
message: `正在安装 VC++ 运行时...`,
});
break;
case "DONE":
callbackFn({
id: "INSTALL_STAGE",
progress: 99,
status: "progressing",
dlUrl: null,
savePath: null,
message: `即将完成`,
});
break;
}
}
} catch (e) {
// Ignore
}
}
});
const instResult = await runInstPromise;
fileProgressWatcher.close();
callbackFn({
id: "INSTALL_STAGE",
progress: 100,
@@ -267,6 +370,8 @@ const functions = {
}
fs.unlinkSync(binPath);
fs.unlinkSync(logPath);
fs.unlinkSync(progressFilePath);
}
},
clearHostFileItem: async () => {
@@ -341,6 +446,14 @@ const applyAikariIpcHandler = (ipcMain) => {
AIKARI_TEMP_DL_DIR,
"Aikari-Installer.exe"
);
const AIKARI_INSTALLER_LOG_FILENAME = path.join(
AIKARI_TEMP_DL_DIR,
"install.log"
);
const AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME = path.join(
AIKARI_TEMP_DL_DIR,
"PROGRESS"
);
// Prev PLS Cfg
const OLD_PLS_INSTALL_DIR = path.join(
@@ -694,6 +807,8 @@ const applyAikariIpcHandler = (ipcMain) => {
async (_evt, arg) => {
if (fs.existsSync(AIKARI_TEMP_DL_DIR)) {
try {
fs.unlinkSync(AIKARI_INSTALLER_LOG_FILENAME);
fs.unlinkSync(AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME);
fs.unlinkSync(AIKARI_TEMP_DL_DIR);
} catch (err) {
console.error(err);
@@ -725,7 +840,9 @@ const applyAikariIpcHandler = (ipcMain) => {
status
);
},
AIKARI_TEMP_INSTALLER_FILENAME
AIKARI_TEMP_INSTALLER_FILENAME,
AIKARI_INSTALLER_LOG_FILENAME,
AIKARI_INSTALLER_PROGRESS_FIPC_FILENAME
);
}
);

View File

@@ -1366,7 +1366,10 @@ const newFunction = function (e, t, n) {
style: { left: o, width: s },
}),
Object.keys(Me).map(function (e, t) {
return r.includes(e)
// ### BOR ### //
const showDirectCondition =
__config.showDirectUnlock && e === "direct";
return r.includes(e) || showDirectCondition
? p.a.createElement(
"div",
{
@@ -1381,6 +1384,7 @@ const newFunction = function (e, t, n) {
m
)
: null;
// ### EOR ### //
})
)
)

View File

@@ -31,8 +31,8 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
getRetryStatusDescByErrId: (errIdString) => {
switch (errIdString) {
case "E_AUTH_TOKEN_GET_FAILED":
return `<p>Aikari 注册表访问失败, 这是一个极为罕见的问题</p>
<p>请检查 HKEY_USERS\\.DEFAULT 是否存在, 并反馈至 GitHub Issues</p>`;
return `<p>Aikari 注册表访问失败, 这可能代表 Aikari 在启动后立即发生了崩溃</p>
<p>请将此情况反馈至 GitHub Issues, 并附上您的系统版本信息</p>`;
case "E_WS_CONN_FAILED_AFT_MULTIPLE_TRIES":
return `<p>在多次尝试连接后仍然失败, 请检查服务是否已启动</p>`;
case "E_IS_LOADING":
@@ -921,7 +921,7 @@ if (!global.__HUGO_AURA_UI_REACTIVES__.subConfig)
} else {
GLOBAL_FUNCTIONS.updatePBarStatus(
info.progress,
`正在安装 Aikari...`,
info.message,
"normal",
false
);