11const path = require ( "node:path" ) ;
2- const https = require ( "node:https" ) ;
32const { app, BrowserWindow, dialog, ipcMain, Menu, net, session, shell } = require ( "electron" ) ;
43
54const {
@@ -43,9 +42,7 @@ let lastCheckResult = null;
4342const LOCAL_SCRIPT_PATH = path . join ( __dirname , ".." , "XMOJ.user.js" ) ;
4443const XMOJ_HOME = "https://www.xmoj.tech" ;
4544const USER_SCRIPT_DEBUG_MODE_KEY = "UserScript-Setting-DebugMode" ;
46- const APP_UPDATE_URL_TEMPLATE = "https://app.xmoj-bbs.me/{system}/{version}.{ext}" ;
47- const APP_UPDATE_BASE_URL = "https://app.xmoj-bbs.me" ;
48- const APP_RELEASES_API_URL = "https://api.github.com/repos/XMOJ-Script-dev/ELXMOJ/releases/latest" ;
45+ const APP_UPDATE_URL_TEMPLATE = "https://github.com/XMOJ-Script-dev/ELXMOJ/releases/download/v{version}/ELXMOJ-{version}.{ext}" ;
4946const PRELOAD_PATH = path . join ( __dirname , "preload.js" ) ;
5047const APP_ICON_PATH = path . join (
5148 __dirname ,
@@ -66,134 +63,36 @@ function getPlatformPackageExtension() {
6663 if ( process . platform === "win32" ) return "exe" ;
6764 if ( process . platform === "darwin" ) return "dmg" ;
6865 if ( process . platform === "linux" ) return "AppImage" ;
69- return "bin " ;
66+ return "zip " ;
7067}
7168
72- function buildAppUpdateUrl ( version ) {
73- const normalizedVersion = String ( version || app . getVersion ( ) ) . trim ( ) ;
74- const ext = getPlatformPackageExtension ( ) ;
75- return APP_UPDATE_URL_TEMPLATE
76- . replace ( "{system}" , getUpdateSystemName ( ) )
77- . replace ( "{version}" , normalizedVersion )
78- . replace ( "{ext}" , ext ) ;
79- }
80-
81- function getAppUpdateUrl ( ) {
82- return buildAppUpdateUrl ( app . getVersion ( ) ) ;
83- }
84-
85- function downloadTextWithHeaders ( url , headers = { } ) {
86- return new Promise ( ( resolve , reject ) => {
87- const req = https . get (
88- url ,
89- {
90- timeout : 15000 ,
91- headers
92- } ,
93- ( res ) => {
94- if ( res . statusCode !== 200 ) {
95- reject ( new Error ( `HTTP ${ res . statusCode } while downloading ${ url } ` ) ) ;
96- res . resume ( ) ;
97- return ;
98- }
99-
100- const chunks = [ ] ;
101- res . on ( "data" , ( chunk ) => chunks . push ( chunk ) ) ;
102- res . on ( "end" , ( ) => resolve ( Buffer . concat ( chunks ) . toString ( "utf8" ) ) ) ;
103- }
104- ) ;
105-
106- req . on ( "timeout" , ( ) => {
107- req . destroy ( new Error ( `Timeout while downloading ${ url } ` ) ) ;
108- } ) ;
109- req . on ( "error" , reject ) ;
110- } ) ;
111- }
112-
113- function parseVersionFromLatestYml ( ymlText ) {
114- const match = String ( ymlText || "" ) . match ( / ^ \s * v e r s i o n \s * : \s * [ " ' ] ? ( [ ^ " ' \s ] + ) [ " ' ] ? / m) ;
115- return match ? String ( match [ 1 ] ) . trim ( ) : "" ;
116- }
117-
118- function normalizeReleaseVersionTag ( value ) {
119- const raw = String ( value || "" ) . trim ( ) ;
120- return raw . replace ( / ^ v / i, "" ) ;
121- }
69+ function getVersionSuffix ( version ) {
70+ const raw = String ( version || "" ) . trim ( ) ;
71+ const semverMatch = raw . match ( / ^ \d + \. \d + \. \d + (?: - ( [ 0 - 9 A - Z a - z . - ] + ) ) ? (?: \+ [ 0 - 9 A - Z a - z . - ] + ) ? $ / ) ;
72+ const prerelease = semverMatch ?. [ 1 ] || "" ;
12273
123- async function detectLatestVersionFromAppSource ( ) {
124- const system = getUpdateSystemName ( ) ;
125- const latestJsonUrl = `${ APP_UPDATE_BASE_URL } /${ system } /latest.json` ;
126- const latestYmlUrl = `${ APP_UPDATE_BASE_URL } /${ system } /latest.yml` ;
127-
128- try {
129- const raw = await downloadText ( latestJsonUrl ) ;
130- const parsed = JSON . parse ( raw ) ;
131- const version = normalizeReleaseVersionTag ( parsed ?. version || parsed ?. latest || parsed ?. tag || "" ) ;
132- if ( version ) {
133- return { ok : true , version, source : latestJsonUrl } ;
134- }
135- } catch {
136- // fallback to yml below
137- }
138-
139- try {
140- const raw = await downloadText ( latestYmlUrl ) ;
141- const version = normalizeReleaseVersionTag ( parseVersionFromLatestYml ( raw ) ) ;
142- if ( version ) {
143- return { ok : true , version, source : latestYmlUrl } ;
144- }
145- } catch {
146- // fallback to GitHub release API below
74+ if ( ! prerelease ) {
75+ return "release" ;
14776 }
14877
149- return { ok : false , version : "" , source : "" } ;
150- }
78+ const normalized = prerelease . toLowerCase ( ) ;
79+ if ( normalized . startsWith ( "alpha" ) ) return "alpha" ;
80+ if ( normalized . startsWith ( "beta" ) ) return "beta" ;
81+ if ( normalized . startsWith ( "rc" ) ) return "rc" ;
82+ if ( normalized . startsWith ( "dev" ) ) return "dev" ;
83+ if ( normalized . startsWith ( "nightly" ) ) return "nightly" ;
84+ if ( normalized . startsWith ( "canary" ) ) return "canary" ;
15185
152- async function detectLatestVersionFromGitHub ( ) {
153- try {
154- const raw = await downloadTextWithHeaders ( APP_RELEASES_API_URL , {
155- "User-Agent" : "ELXMOJ-App-Updater"
156- } ) ;
157- const parsed = JSON . parse ( raw ) ;
158- const version = normalizeReleaseVersionTag ( parsed ?. tag_name || parsed ?. name || "" ) ;
159- if ( ! version ) {
160- return { ok : false , version : "" , source : APP_RELEASES_API_URL } ;
161- }
162- return { ok : true , version, source : APP_RELEASES_API_URL } ;
163- } catch {
164- return { ok : false , version : "" , source : APP_RELEASES_API_URL } ;
165- }
86+ // Fallback to prerelease tag itself when it is a custom identifier.
87+ return normalized . replace ( / [ ^ a - z 0 - 9 . - ] / g, "" ) || "preview" ;
16688}
16789
168- async function getAppUpdateInfo ( ) {
169- const currentVersion = app . getVersion ( ) ;
170- let latest = await detectLatestVersionFromAppSource ( ) ;
171-
172- if ( ! latest . ok ) {
173- latest = await detectLatestVersionFromGitHub ( ) ;
174- }
175-
176- if ( ! latest . ok || ! latest . version ) {
177- return {
178- ok : false ,
179- currentVersion,
180- latestVersion : "" ,
181- hasUpdate : false ,
182- downloadUrl : getAppUpdateUrl ( ) ,
183- source : "" ,
184- message : "未能获取最新版本。可在发布流程里生成并上传 latest.json 或 latest.yml(包含 version 字段),也可继续维护 GitHub Release 的最新 tag。"
185- } ;
186- }
187-
188- return {
189- ok : true ,
190- currentVersion,
191- latestVersion : latest . version ,
192- hasUpdate : isNewerVersion ( currentVersion , latest . version ) ,
193- downloadUrl : buildAppUpdateUrl ( latest . version ) ,
194- source : latest . source ,
195- message : ""
196- } ;
90+ function getAppUpdateUrl ( ) {
91+ const version = app . getVersion ( ) ;
92+ const ext = getPlatformPackageExtension ( ) ;
93+ return APP_UPDATE_URL_TEMPLATE
94+ . replace ( "{version}" , version )
95+ . replace ( "{ext}" , ext ) ;
19796}
19897
19998function getDebugModeFromChannel ( channel ) {
@@ -587,9 +486,8 @@ function createMainMenu() {
587486 submenu : [
588487 {
589488 label : "下载最新版本" ,
590- click : async ( ) => {
591- const info = await getAppUpdateInfo ( ) ;
592- shell . openExternal ( info . downloadUrl || getAppUpdateUrl ( ) ) . catch ( ( ) => {
489+ click : ( ) => {
490+ shell . openExternal ( getAppUpdateUrl ( ) ) . catch ( ( ) => {
593491 // Ignore failures to open update page
594492 } ) ;
595493 }
@@ -953,23 +851,14 @@ function registerIpcHandlers() {
953851 if ( ! isTrustedIpcSender ( event ) ) {
954852 throw new Error ( "Unauthorized IPC sender" ) ;
955853 }
956- const info = await getAppUpdateInfo ( ) ;
957- return info . downloadUrl || getAppUpdateUrl ( ) ;
958- } ) ;
959-
960- ipcMain . handle ( "elxmoj:get-app-update-info" , async ( event ) => {
961- if ( ! isTrustedIpcSender ( event ) ) {
962- throw new Error ( "Unauthorized IPC sender" ) ;
963- }
964- return getAppUpdateInfo ( ) ;
854+ return getAppUpdateUrl ( ) ;
965855 } ) ;
966856
967857 ipcMain . handle ( "elxmoj:open-app-update-page" , async ( event ) => {
968858 if ( ! isTrustedIpcSender ( event ) ) {
969859 throw new Error ( "Unauthorized IPC sender" ) ;
970860 }
971- const info = await getAppUpdateInfo ( ) ;
972- const url = info . downloadUrl || getAppUpdateUrl ( ) ;
861+ const url = getAppUpdateUrl ( ) ;
973862 await shell . openExternal ( url ) ;
974863 return { ok : true , url } ;
975864 } ) ;
0 commit comments