Skip to content

Commit c252880

Browse files
v1.1.0
- Add an auto self-update installation func
1 parent f2aa147 commit c252880

File tree

10 files changed

+215
-43
lines changed

10 files changed

+215
-43
lines changed

build-rel.bat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
@echo off&cd /d %~dp0
22

33
set "ISCC_PATH=C:\Program Files (x86)\Inno Setup 6\ISCC.exe"
4-
set VERSION_NUM=1.0.3
4+
set VERSION_NUM=1.1.0
55

66
mkdir build\asmr-dl-ng
77
@rem xcopy bin build\asmr-dl-ng\bin /E /I /Q

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "asmr-dl-ng",
3-
"version": "1.0.3",
3+
"version": "1.1.0",
44
"description": "ASMR Direct Downloader (New Generation)",
55
"author": "daydreamer-json <[email protected]> (https://github.com/daydreamer-json)",
66
"license": "AGPL-3.0-or-later",

setup/main.iss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#define MyAppName "asmr-dl-ng"
2-
#define MyAppVersion "1.0.3"
2+
#define MyAppVersion "1.1.0"
33
#define MyAppPublisher "daydreamer-json"
44
#define MyAppURL "https://github.com/daydreamer-json/asmr-dl-ng"
55
#define MyAppExeName "asmr-dl-ng.exe"

src/cmd.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
// import path from 'node:path';
21
import yargs from 'yargs';
32
import { hideBin } from 'yargs/helpers';
43
import cmds from './cmds.js';
54
import * as TypesApi from './types/Api.js';
65
import * as TypesLogLevels from './types/LogLevels.js';
6+
import updaterUtils from './updater/updater.js';
77
import argvUtils from './utils/argv.js';
88
import appConfig from './utils/config.js';
99
import configEmbed from './utils/configEmbed.js';
@@ -17,7 +17,6 @@ function wrapHandler(handler: (argv: any) => Promise<void>) {
1717
return async (argv: any) => {
1818
try {
1919
await handler(argv);
20-
await new Promise((resolve) => setTimeout(resolve, 50)); //! libuv assertion error workaround
2120
await exitUtils.exit(0);
2221
} catch (error) {
2322
logger.error('Error caught:', error);
@@ -161,7 +160,8 @@ async function parseCommand() {
161160
.middleware(async (argv) => {
162161
argvUtils.setArgv(argv);
163162
logger.level = argvUtils.getArgv()['logLevel'];
164-
logger.trace('Process started');
163+
logger.trace('Process started: ' + `${configEmbed.APPLICATION_NAME} v${configEmbed.VERSION_NUMBER}`);
164+
await updaterUtils.checkAppUpdate();
165165
})
166166
.scriptName(configEmbed.APPLICATION_NAME)
167167
.version(String(configEmbed.VERSION_NUMBER))

src/cmds/download.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ async function mainCmdHandler() {
1313
logger.warn('Work ID has not been specified. Requesting ...');
1414
const idRsp: number = (
1515
await prompts(
16-
{ name: 'value', type: 'number', message: 'Enter work ID' },
16+
{
17+
name: 'value',
18+
type: 'number',
19+
message: 'Enter work ID',
20+
validate: (value) => (Boolean(value) ? true : 'Invalid value'),
21+
},
1722
{
1823
onCancel: async () => {
1924
logger.error('Aborted');

src/cmds/test.ts

Lines changed: 2 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,7 @@
1-
import ora from 'ora';
2-
import prompts from 'prompts';
3-
import * as TypesApi from '../types/Api.js';
4-
import apiUtils from '../utils/api.js';
5-
import argvUtils from '../utils/argv.js';
6-
import downloadUtils from '../utils/download.js';
7-
import logger from '../utils/logger.js';
8-
import termPrettyUtils from '../utils/termPretty.js';
1+
import updaterUtils from '../updater/updater.js';
92

103
async function mainCmdHandler() {
11-
if (!('id' in argvUtils.getArgv())) {
12-
logger.warn('Work ID has not been specified. Requesting ...');
13-
const idRsp: number = (await prompts({ name: 'value', type: 'number', message: 'Enter work ID' })).value;
14-
argvUtils.setArgv({ ...argvUtils.getArgv(), id: idRsp });
15-
}
16-
apiUtils.setBaseUri(argvUtils.getArgv()['server'] as TypesApi.ServerName);
17-
18-
await (async () => {
19-
const spinner = !argvUtils.getArgv()['no-show-progress']
20-
? ora({ text: 'Checking API health ...', color: 'cyan', spinner: 'dotsCircle' }).start()
21-
: undefined;
22-
const apiHealthRsp = await apiUtils.api.health();
23-
spinner?.stop();
24-
if (apiHealthRsp.available === true) {
25-
logger.info('API health check succeeded');
26-
} else {
27-
throw new Error('API health check failed');
28-
}
29-
})();
30-
31-
const workApiRsp = await downloadUtils.downloadMeta(argvUtils.getArgv()['id']);
32-
33-
console.log(termPrettyUtils.printWorkInfo(workApiRsp));
34-
35-
const selectedFilesUuid = await downloadUtils.filterFileEntry(workApiRsp.fileEntry.transformed);
36-
37-
await downloadUtils.downloadWork(workApiRsp, selectedFilesUuid);
4+
await updaterUtils.checkAppUpdate();
385
}
396

407
export default mainCmdHandler;

src/types/GitHubApiRel.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
interface SimpleUser {
2+
avatar_url: string;
3+
events_url: string;
4+
followers_url: string;
5+
following_url: string;
6+
gists_url: string;
7+
gravatar_id: string | null;
8+
html_url: string;
9+
id: number;
10+
node_id: string;
11+
login: string;
12+
organizations_url: string;
13+
received_events_url: string;
14+
repos_url: string;
15+
site_admin: boolean;
16+
starred_url: string;
17+
subscriptions_url: string;
18+
type: string;
19+
url: string;
20+
name?: string | null;
21+
email?: string | null;
22+
starred_at?: string;
23+
user_view_type?: string;
24+
}
25+
26+
interface ReleaseAsset {
27+
url: string;
28+
browser_download_url: string;
29+
id: number;
30+
node_id: string;
31+
name: string;
32+
label: string | null;
33+
state: 'uploaded' | 'open';
34+
content_type: string;
35+
size: number;
36+
digest: string | null;
37+
download_count: number;
38+
created_at: string;
39+
updated_at: string;
40+
uploader: SimpleUser | null;
41+
}
42+
43+
interface Release {
44+
url: string;
45+
html_url: string;
46+
assets_url: string;
47+
upload_url: string;
48+
tarball_url: string | null;
49+
zipball_url: string | null;
50+
id: number;
51+
node_id: string;
52+
tag_name: string;
53+
target_commitish: string;
54+
name: string | null;
55+
body: string | null;
56+
draft: boolean;
57+
prerelease: boolean;
58+
immutable: boolean;
59+
created_at: string;
60+
published_at: string | null;
61+
updated_at: string | null;
62+
author: SimpleUser;
63+
assets: ReleaseAsset[];
64+
body_html: string;
65+
body_text: string;
66+
mentions_count: number;
67+
discussion_url: string;
68+
reactions: any;
69+
}
70+
71+
export type { Release };

src/updater/updater.ts

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import { spawn, spawnSync } from 'node:child_process';
2+
import fs from 'node:fs/promises';
3+
import os from 'node:os';
4+
import path from 'node:path';
5+
import chalk from 'chalk';
6+
import ky from 'ky';
7+
import ora from 'ora';
8+
import prompts from 'prompts';
9+
import { rimraf } from 'rimraf';
10+
import semver from 'semver';
11+
import type * as GitHubApiRel from '../types/GitHubApiRel.js';
12+
import apiUtils from '../utils/api.js';
13+
import argvUtils from '../utils/argv.js';
14+
import configEmbed from '../utils/configEmbed.js';
15+
import exitUtils from '../utils/exit.js';
16+
import logger from '../utils/logger.js';
17+
18+
function isInstalledViaInstaller(): boolean {
19+
if (process.platform !== 'win32') return false;
20+
21+
const appId = '{B0B8B114-AE98-4165-BFC7-E029C1DB80D4}_is1';
22+
const regKeys = [
23+
`HKCU\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${appId}`,
24+
`HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${appId}`,
25+
`HKLM\\SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${appId}`,
26+
];
27+
28+
for (const key of regKeys) {
29+
const result = spawnSync('reg', ['query', key], { stdio: 'ignore' });
30+
if (result.status === 0) return true;
31+
}
32+
33+
return false;
34+
}
35+
36+
async function checkAppUpdate(): Promise<void> {
37+
// const testMode: boolean = false;
38+
// if (testMode) logger.warn('Update checker test mode is true!');
39+
if (isInstalledViaInstaller() === false) return;
40+
await cleanupInstaller();
41+
const githubApiUrl: string = 'https://api.github.com/repos/daydreamer-json/asmr-dl-ng/releases/latest';
42+
const githubApiRsp: GitHubApiRel.Release | null = await (async () => {
43+
try {
44+
return await ky.get(githubApiUrl, apiUtils.defaultKySettings).json();
45+
} catch (error) {
46+
logger.error('Failed to check for updates');
47+
return null;
48+
}
49+
})();
50+
if (githubApiRsp === null) return;
51+
const latestVersion = semver.clean(githubApiRsp.tag_name);
52+
const currentVersion = configEmbed.VERSION_NUMBER;
53+
if (latestVersion === null) throw new Error('Failed to get latest update');
54+
if (currentVersion === null) throw new Error('Embed app version number is null');
55+
if (semver.gt(latestVersion, currentVersion) === false) {
56+
logger.info(`App is up to date (local: ${chalk.green(currentVersion)}, remote: ${chalk.green(latestVersion)})`);
57+
return;
58+
}
59+
60+
logger.info(`Update is available (local: ${chalk.red(currentVersion)}, remote: ${chalk.green(latestVersion)})`);
61+
const userSelectRsp: boolean = (
62+
await prompts(
63+
{
64+
name: 'value',
65+
type: 'toggle',
66+
message: 'Download and install updates automatically?',
67+
initial: true,
68+
active: 'yes',
69+
inactive: 'no',
70+
},
71+
{
72+
onCancel: async () => {
73+
logger.error('Aborted');
74+
exitUtils.exit(1, null, false);
75+
},
76+
},
77+
)
78+
).value;
79+
if (userSelectRsp === false) return;
80+
81+
await downloadAndApplyUpdate(githubApiRsp);
82+
}
83+
84+
async function downloadAndApplyUpdate(latestRelInfo: GitHubApiRel.Release): Promise<void> {
85+
if (!(process.platform === 'win32' && process.arch === 'x64')) {
86+
throw new Error(`This environment is not supported: ${process.platform}, ${process.arch}`);
87+
}
88+
89+
const assetNamePattern = /asmr-dl-ng_win_x64_.+?_setup\.exe/g;
90+
const githubAsset = latestRelInfo.assets.find((e) => assetNamePattern.test(e.name));
91+
if (!githubAsset) throw new Error('No update asset found');
92+
93+
const spinner = !argvUtils.getArgv()['no-show-progress']
94+
? ora({ text: 'Downloading installer ...', color: 'cyan', spinner: 'dotsCircle' }).start()
95+
: logger.info('Downloading installer ...');
96+
const assetBuffer = await ky.get(githubAsset.browser_download_url).bytes();
97+
const installerPath = path.join(os.tmpdir(), 'asmr-dl-ng_win_x64_setup.exe');
98+
await fs.writeFile(installerPath, assetBuffer);
99+
spinner?.stop();
100+
101+
logger.info('Starting installer and exiting application ...');
102+
103+
const child = spawn(installerPath, ['/silent', '/norestart'], {
104+
detached: true,
105+
stdio: 'ignore',
106+
shell: true,
107+
});
108+
109+
child.unref();
110+
111+
logger.info('Please restart the app after installation is complete');
112+
113+
await exitUtils.exit(0, null, false);
114+
}
115+
116+
async function cleanupInstaller(): Promise<void> {
117+
const installerPath = path.join(os.tmpdir(), 'asmr-dl-ng_win_x64_setup.exe');
118+
await rimraf(installerPath);
119+
}
120+
121+
export default {
122+
checkAppUpdate,
123+
};

src/utils/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@ type ConfigType = AllRequired<
5858
clearOnComplete: boolean;
5959
};
6060
};
61+
updateChecker: {
62+
useUpdateChecker: boolean;
63+
};
6164
}>
6265
>;
6366

@@ -104,6 +107,9 @@ const initialConfig: ConfigType = {
104107
clearOnComplete: true,
105108
},
106109
},
110+
updateChecker: {
111+
useUpdateChecker: true,
112+
},
107113
};
108114

109115
const deobfuscator = (input: ConfigType): ConfigType => {

src/utils/configEmbed.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ import semver from 'semver';
22

33
export default {
44
APPLICATION_NAME: 'asmr-dl-ng',
5-
VERSION_NUMBER: semver.valid('1.0.3'),
5+
VERSION_NUMBER: semver.valid('1.1.0'),
66
};

0 commit comments

Comments
 (0)