diff --git a/.gitignore b/.gitignore index b3ea899..c314163 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ release .idea yarn.lock +localVersion diff --git a/electron-builder.json b/electron-builder.json index 486e9d7..9049487 100644 --- a/electron-builder.json +++ b/electron-builder.json @@ -10,9 +10,9 @@ { "from": "./static/engine/aria2.conf", "to": "./engine/aria2.conf"}, { "from": "./static/crx", "to": "./crx"}, { "from": "./public/images/qrcode_1280.jpg", "to": "./images/qrcode_1280.jpg"}, - { "from": "./static/images/icon_24.png", "to": "./images/icon_24.png"}, - { "from": "./static/images/icon_64.png", "to": "./images/icon_64.png"}, - { "from": "./static/images/icon_256.png", "to": "./images/icon_256.png"} + { "from": "./static/images/icon_24x24.png", "to": "./images/icon_24x24.png"}, + { "from": "./static/images/icon_64x64.png", "to": "./images/icon_64x64.png"}, + { "from": "./static/images/icon_256x256.png", "to": "./images/icon_256x256.png"} ], "mac": { "icon": "./static/images/icon.icns", @@ -30,7 +30,7 @@ ] }, "linux": { - "icon": "./static/images/icon_256.png", + "icon": "./static/images/icon_256x256.png", "category": "Network", "artifactName": "XBYDriver-${version}-linux-${arch}.${ext}", "extraResources": [ @@ -42,15 +42,15 @@ ] }, "win": { - "icon": "./static/images/icon_256.ico", + "icon": "./static/images/icon_256x256.ico", "artifactName": "XBYDriver-${version}-win-${arch}.${ext}", "requestedExecutionLevel": "asInvoker", "extraResources": [ { "from": "./static/engine/win32/${arch}", "to": "./engine"}, - { "from": "./static/images/icon_256.ico", "to": "./images/icon_256.ico"} + { "from": "./static/images/icon_256x256.ico", "to": "./images/icon_256x256.ico"} ], "target": [ - { "target": "nsis", "arch": [ "x64", "ia32" ] } + { "target": "nsis", "arch": [ "x64", "ia32", "arm64"] } ] }, "dmg": { diff --git a/electron/main/core/dialog.ts b/electron/main/core/dialog.ts new file mode 100644 index 0000000..146f865 --- /dev/null +++ b/electron/main/core/dialog.ts @@ -0,0 +1,50 @@ +import { app, dialog } from 'electron' + +export function ShowErrorAndRelaunch(title: string, errmsg: string) { + dialog + .showMessageBox({ + type: 'error', + buttons: ['ok'], + title: title + ',小白羊将自动退出', + message: '错误信息:' + errmsg + }) + .then((_) => { + setTimeout(() => { + app.relaunch() + try { + app.exit() + } catch { + } + }, 100) + }) +} + +export function ShowErrorAndExit(title: string, errmsg: string) { + dialog + .showMessageBox({ + type: 'error', + buttons: ['ok'], + title: title + ',小白羊将自动退出', + message: '错误信息:' + errmsg + }) + .then((_) => { + setTimeout(() => { + try { + app.exit() + } catch { + } + }, 100) + }) +} + +export function ShowError(title: string, errmsg: string) { + dialog + .showMessageBox({ + type: 'error', + buttons: ['ok'], + title: title, + message: '错误信息:' + errmsg + }) + .then((_) => { + }) +} \ No newline at end of file diff --git a/electron/main/core/exception.ts b/electron/main/core/exception.ts new file mode 100644 index 0000000..b062a90 --- /dev/null +++ b/electron/main/core/exception.ts @@ -0,0 +1,24 @@ +import is from 'electron-is' +import { ShowErrorAndExit } from './dialog' +import { app } from 'electron' + +export default class exception { + private constructor() { + + } + + static handler() { + if (is.dev()) { + return + } + process.on('unhandledRejection', (reason, p) => { + console.log('Unhandled Rejection at:', p, 'reason:', reason) + }) + process.on('uncaughtException', (err) => { + let { message, stack = '' } = err + if (app.isReady()) { + ShowErrorAndExit('发生未定义的异常', err.message + '\n' + stack) + } + }) + } +} \ No newline at end of file diff --git a/electron/main/core/ipcEvent.ts b/electron/main/core/ipcEvent.ts new file mode 100644 index 0000000..babb399 --- /dev/null +++ b/electron/main/core/ipcEvent.ts @@ -0,0 +1,506 @@ +import { AppWindow, createElectronWindow, Referer, ua } from './window' +import path from 'path' +import is from 'electron-is' +import { app, BrowserWindow, dialog, ipcMain, session, shell } from 'electron' +import { existsSync, writeFileSync } from 'fs' +import { exec, execFile, spawn, SpawnOptions } from 'child_process' +import { ShowError } from './dialog' +// @ts-ignore +import {getResourcesPath, getStaticPath, getUserDataPath} from '../utils/mainfile' +import { portIsOccupied } from '../utils' + +export default class ipcEvent { + private constructor() { + } + + static handleEvents() { + this.handleWebToElectron() + this.handleWebToElectronCB() + this.handleWebShowOpenDialogSync() + this.handleWebShowSaveDialogSync() + this.handleWebShowItemInFolder() + this.handleWebPlatformSync() + this.handleWebSpawnSync() + this.handleWebExecSync() + this.handleWebSaveTheme() + this.handleWebClearCookies() + this.handleWebGetCookies() + this.handleWebSetCookies() + this.handleWebClearCache() + this.handleWebReload() + this.handleWebRelaunch() + this.handleWebRelaunchAria() + this.handleWebRelaunchAlist() + this.handleWebResetAlistPwd() + this.handleWebSetProgressBar() + this.handleWebShutDown() + this.handleWebSetProxy() + this.handleWebOpenWindow() + this.handleWebOpenUrl() + } + + private static handleWebToElectron() { + ipcMain.on('WebToElectron', async (event, data) => { + let mainWindow = AppWindow.mainWindow + if (data.cmd && data.cmd === 'close') { + if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide() + } else if (data.cmd && data.cmd === 'exit') { + if (mainWindow && !mainWindow.isDestroyed()) { + mainWindow.destroy() + mainWindow = undefined + } + try { + app.exit() + } catch { + } + } else if (data.cmd && data.cmd === 'minsize') { + if (mainWindow && !mainWindow.isDestroyed()) mainWindow.minimize() + } else if (data.cmd && data.cmd === 'maxsize') { + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isMaximized()) { + mainWindow.unmaximize() + } else { + mainWindow.maximize() + } + } + } else if (data.cmd && (Object.hasOwn(data.cmd, 'launchStart') + || Object.hasOwn(data.cmd, 'launchStartShow'))) { + const launchStart = data.cmd.launchStart + const launchStartShow = data.cmd.launchStartShow + const appName = path.basename(process.execPath) + // 设置开机自启 + const settings: Electron.Settings = { + openAtLogin: launchStart, + path: process.execPath + } + // 显示主窗口 + if (is.macOS()) { + settings.openAsHidden = !launchStartShow + } else { + settings.args = [ + '--processStart', `${appName}`, + '--process-start-args', `"--hidden"` + ] + !launchStartShow && settings.args.push('--openAsHidden') + } + app.setLoginItemSettings(settings) + } else { + event.sender.send('ElectronToWeb', 'mainsenddata') + } + }) + } + + private static handleWebToElectronCB() { + ipcMain.on('WebToElectronCB', (event, data) => { + const mainWindow = AppWindow.mainWindow + if (data.cmd && data.cmd === 'maxsize') { + if (mainWindow && !mainWindow.isDestroyed()) { + if (mainWindow.isMaximized()) { + mainWindow.unmaximize() + event.returnValue = 'unmaximize' + } else { + mainWindow.maximize() + event.returnValue = 'maximize' + } + } + } else { + event.returnValue = 'backdata' + } + }) + } + + private static handleWebShowOpenDialogSync() { + ipcMain.on('WebShowOpenDialogSync', (event, config) => { + dialog.showOpenDialog(AppWindow.mainWindow!, config).then((result) => { + event.returnValue = result.filePaths + }) + }) + } + + private static handleWebShowSaveDialogSync() { + ipcMain.on('WebShowSaveDialogSync', (event, config) => { + dialog.showSaveDialog(AppWindow.mainWindow!, config).then((result) => { + event.returnValue = result.filePath || '' + }) + }) + } + + private static handleWebShowItemInFolder() { + ipcMain.on('WebShowItemInFolder', (event, fullPath) => { + for (let i = 0; i < 5; i++) { + if (existsSync(fullPath)) break + if (fullPath.lastIndexOf(path.sep) > 0) { + fullPath = fullPath.substring(0, fullPath.lastIndexOf(path.sep)) + } else return + } + if (fullPath.length > 2) shell.showItemInFolder(fullPath) + }) + } + + private static handleWebPlatformSync() { + ipcMain.on('WebPlatformSync', (event) => { + const asarPath = app.getAppPath() + const appPath = app.getPath('userData') + event.returnValue = { + platform: process.platform, + arch: process.arch, + version: process.version, + execPath: process.execPath, + appPath: appPath, + asarPath: asarPath, + argv0: process.argv0 + } + }) + } + + private static handleWebSpawnSync() { + ipcMain.on('WebSpawnSync', (event, data) => { + try { + const options: SpawnOptions = { + shell: true, + stdio: 'ignore', + windowsVerbatimArguments: true, + ...data.options + } + const argsToStr = (args: string) => is.windows() ? `"${args}"` : `'${args}'` + if ((is.windows() || is.macOS()) && !existsSync(data.command)) { + event.returnValue = { error: '找不到文件' + data.command } + ShowError('找不到文件', data.command) + } else { + let command + if (is.macOS()) { + command = `open -a ${argsToStr(data.command)} ${data.command.includes('mpv.app') ? '--args ' : ''}` + } else { + command = `${argsToStr(data.command)}` + } + const subProcess = spawn(command, data.args, options) + const isRunning = process.kill(subProcess.pid, 0) + subProcess.unref() + event.returnValue = { + pid: subProcess.pid, + isRunning: isRunning, + execCmd: data, + options: options, + exitCode: subProcess.exitCode + } + } + } catch (err: any) { + event.returnValue = { error: err } + } + }) + } + + private static handleWebExecSync() { + ipcMain.on('WebExecSync', (event, data) => { + try { + const cmdArguments = [] + cmdArguments.push(data.command) + if (data.args) cmdArguments.push(...data.args) + const finalCmd = cmdArguments.join(' ') + exec(finalCmd, (err: any) => { + event.returnValue = err + }) + event.returnValue = '' + } catch (err: any) { + event.returnValue = { error: err } + } + }) + } + + private static handleWebSaveTheme() { + ipcMain.on('WebSaveTheme', (event, data) => { + try { + const themeJson = getUserDataPath('theme.json') + writeFileSync(themeJson, `{"theme":"${data.theme || ''}"}`, 'utf-8') + } catch { + } + }) + } + + private static handleWebClearCookies() { + ipcMain.on('WebClearCookies', (event, data) => { + session.defaultSession.clearStorageData(data) + }) + } + + private static handleWebGetCookies() { + ipcMain.handle('WebGetCookies', async (event, data) => { + return await session.defaultSession.cookies.get(data) + }) + } + + private static handleWebSetCookies() { + ipcMain.on('WebSetCookies', (event, data) => { + for (let i = 0, maxi = data.length; i < maxi; i++) { + const cookie = { + url: data[i].url, + name: data[i].name, + value: data[i].value, + domain: '.' + data[i].url.substring(data[i].url.lastIndexOf('/') + 1), + secure: data[i].url.indexOf('https://') == 0, + expirationDate: data[i].expirationDate + } + session.defaultSession.cookies.set(cookie).catch((err: any) => console.error(err)) + } + }) + } + + private static handleWebClearCache() { + ipcMain.on('WebClearCache', (event, data) => { + if (data.cache) { + session.defaultSession.clearCache() + session.defaultSession.clearAuthCache() + } else { + session.defaultSession.clearStorageData(data) + } + }) + } + + private static handleWebReload() { + ipcMain.on('WebReload', (event, data) => { + if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) AppWindow.mainWindow.reload() + }) + } + + private static handleWebRelaunch() { + ipcMain.on('WebRelaunch', (event, data) => { + app.relaunch() + try { + app.exit() + } catch { + } + }) + } + + private static handleWebRelaunchAria() { + ipcMain.handle('WebRelaunchAria', async (event, data) => { + try { + const enginePath: string = getStaticPath('engine') + const confPath: string = path.join(enginePath, 'aria2.conf') + const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c' + const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '') + const ariaFilePath: string = path.join(basePath, ariaPath) + if (!existsSync(ariaFilePath)) { + ShowError('找不到Aria程序文件', ariaFilePath) + return 0 + } + const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'` + const listenPort = await portIsOccupied(16800) + const options: SpawnOptions = { + shell: true, + stdio: is.dev() ? 'pipe' : 'ignore', + windowsHide: false, + windowsVerbatimArguments: true + } + const args = [ + `--stop-with-process=${argsToStr(process.pid)}`, + `--conf-path=${argsToStr(confPath)}`, + `--rpc-listen-port=${argsToStr(listenPort)}`, + '-D' + ] + spawn(`${argsToStr(ariaFilePath)}`, args, options) + return listenPort + } catch (e: any) { + console.log(e) + } + return 0 + }) + } + private static handleWebRelaunchAlist() { + ipcMain.handle('WebRelaunchAlist', async (event, data) => { + try { + const enginePath: string = getStaticPath('engine') + const alistPath = is.windows() ? 'alist.exe' : 'alist' + const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '') + const alistFilePath: string = path.join(basePath, alistPath) + const alistDataPath = getUserDataPath('alist-data') + if (!existsSync(alistFilePath)) { + ShowError('找不到alist程序文件', alistFilePath) + return 0 + } + const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'` + + const alistArgs = [ + 'server', + '--data ' + `${argsToStr(alistDataPath)}` + ] + const options = { + shell: true, + windowsVerbatimArguments: true + } + console.log(`${argsToStr(alistFilePath + ' server')}`) + execFile(`${argsToStr(alistFilePath)}`, alistArgs, options, + async (error, stdout, stderr) => { + if (error) { + console.log(`启动AList失败 : ${error}`) + return 0 + } + }) + return 0 + } catch (e: any) { + console.log(e) + } + return 0 + }) + } + + private static handleWebResetAlistPwd() { + ipcMain.handle('WebResetAlistPwd', async (event, data) => { + try { + const enginePath: string = getStaticPath('engine') + const alistPath = is.windows() ? 'alist.exe' : 'alist' + const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '') + const alistFilePath: string = path.join(basePath, alistPath) + const alistDataPath = getUserDataPath('alist-data') + if (!existsSync(alistFilePath)) { + ShowError('找不到alist程序文件', alistFilePath) + return 0 + } + + const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'` + const password = data.cmd + + const alistArgs = [ + 'admin set ' + `${argsToStr(password)}`, + '--data ' + `${argsToStr(alistDataPath)}` + ] + console.log(`修改AList密码 : `, password) + const options = { + shell: true, + windowsVerbatimArguments: true + } + execFile(`${argsToStr(alistFilePath)}`, alistArgs, options, + async (error, stdout, stderr) => { + if (error) { + console.log(`修改AList密码失败 : ${error}`) + } else { + console.log(`修改AList密码成功 : ${stdout}`) + } + }) + } catch (e: any) { + console.log(e) + } + }) + } + + private static handleWebSetProgressBar() { + ipcMain.on('WebSetProgressBar', (event, data) => { + if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) { + if (data.pro) { + + AppWindow.mainWindow.setProgressBar(data.pro, { mode: data.mode || 'normal' }) + } else AppWindow.mainWindow.setProgressBar(-1) + } + }) + } + + private static handleWebShutDown() { + ipcMain.on('WebShutDown', (event, data) => { + if (is.macOS()) { + const shutdownCmd = 'osascript -e \'tell application "System Events" to shut down\'' + exec(shutdownCmd, (err: any) => { + if (data.quitApp) { + try { + app.exit() + } catch { + } + } + if (err) { + // donothing + } + }) + } else { + const cmdArguments = ['shutdown'] + if (is.linux()) { + if (data.sudo) { + cmdArguments.unshift('sudo') + } + cmdArguments.push('-h') + cmdArguments.push('now') + } + if (is.windows()) { + cmdArguments.push('-s') + cmdArguments.push('-f') + cmdArguments.push('-t 0') + } + + const finalcmd = cmdArguments.join(' ') + + exec(finalcmd, (err: any) => { + if (data.quitApp) { + try { + app.exit() + } catch { + } + } + if (err) { + // donothing + } + }) + } + }) + } + + private static handleWebSetProxy() { + ipcMain.on('WebSetProxy', (event, data) => { + // if (data.proxyUrl) app.commandLine.appendSwitch('proxy-server', data.proxyUrl) + // else app.commandLine.removeSwitch('proxy-server') + console.log(JSON.stringify(data)) + if (data.proxyUrl) { + session.defaultSession.setProxy({ proxyRules: data.proxyUrl }) + } else { + session.defaultSession.setProxy({}) + } + }) + } + + private static handleWebOpenWindow() { + ipcMain.on('WebOpenWindow', (event, data) => { + const win = createElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main2', data.theme) + win.on('ready-to-show', function() { + win.webContents.send('setPage', data) + win.setTitle('预览窗口') + win.show() + }) + }) + } + + private static handleWebOpenUrl() { + ipcMain.on('WebOpenUrl', (event, data) => { + const win = new BrowserWindow({ + show: false, + width: AppWindow.winWidth, + height: AppWindow.winHeight, + center: true, + minWidth: 680, + minHeight: 500, + icon: getStaticPath('icon_256x256.ico'), + useContentSize: true, + frame: true, + hasShadow: true, + autoHideMenuBar: true, + backgroundColor: data.theme && data.theme == 'dark' ? '#23232e' : '#ffffff', + webPreferences: { + spellcheck: false, + devTools: is.dev(), + sandbox: false, + webSecurity: false, + allowRunningInsecureContent: true, + backgroundThrottling: false, + enableWebSQL: false, + disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure' + } + }) + + win.on('ready-to-show', function() { + win.setTitle('预览窗口') + win.show() + }) + + win.loadURL(data.PageUrl, { + userAgent: ua, + httpReferrer: Referer + }) + }) + } +} diff --git a/electron/main/window.ts b/electron/main/core/window.ts similarity index 74% rename from electron/main/window.ts rename to electron/main/core/window.ts index 1967a78..ddd5dc0 100644 --- a/electron/main/window.ts +++ b/electron/main/core/window.ts @@ -1,13 +1,13 @@ -import { app, BrowserWindow, dialog, Menu, MessageChannelMain, nativeTheme, Tray, screen } from 'electron' -import { getAsarPath, getStaticPath, getUserDataPath } from './mainfile' -import { existsSync, readFileSync, writeFileSync } from 'fs' -import path from "path"; +import { app, BrowserWindow, Menu, MenuItem, MessageChannelMain, nativeTheme, screen, session, Tray } from 'electron' +// @ts-ignore +import { getAsarPath, getResourcesPath, getStaticPath, getUserDataPath } from '../utils/mainfile' +import fs, { existsSync, readFileSync, writeFileSync } from 'fs' import is from 'electron-is' +import { ShowErrorAndRelaunch } from './dialog' + -const DEBUGGING = !app.isPackaged export const ua = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36 Edg/102.0.1245.33' export const Referer = 'https://www.aliyundrive.com/' - export const AppWindow: { mainWindow: BrowserWindow | undefined uploadWindow: BrowserWindow | undefined @@ -25,10 +25,51 @@ export const AppWindow: { winHeight: 0, winTheme: '' } +export const AppMenu: { + menuEdit: Electron.Menu | undefined + menuCopy: Electron.Menu | undefined +} = { + menuEdit: undefined, + menuCopy: undefined +} + +let timerUpload: NodeJS.Timeout | undefined +const debounceUpload = (fn: any, wait: number) => { + if (timerUpload) { + clearTimeout(timerUpload) + } + timerUpload = setTimeout(() => { + fn() + timerUpload = undefined + }, wait) +} +let timerDownload: NodeJS.Timeout | undefined +const debounceDownload = (fn: any, wait: number) => { + if (timerDownload) { + clearTimeout(timerDownload) + } + timerDownload = setTimeout(() => { + fn() + timerDownload = undefined + }, wait) +} +let timerResize: NodeJS.Timeout | undefined +const debounceResize = (fn: any, wait: number) => { + if (timerResize) clearTimeout(timerResize) + timerResize = setTimeout(() => { + fn() + timerResize = undefined + }, wait) +} +nativeTheme.on('updated', () => { + if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) + AppWindow.mainWindow.webContents.send('setTheme', { + dark: nativeTheme.shouldUseDarkColors + }) +}) export function createMainWindow() { - Menu.setApplicationMenu(null) - + Menu.setApplicationMenu(null) try { const configJson = getUserDataPath('config.json') if (existsSync(configJson)) { @@ -36,22 +77,23 @@ export function createMainWindow() { AppWindow.winWidth = configData.width AppWindow.winHeight = configData.height } - } catch {} + } catch { + } try { const themeJson = getUserDataPath('theme.json') if (existsSync(themeJson)) { const themeData = JSON.parse(readFileSync(themeJson, 'utf-8')) AppWindow.winTheme = themeData.theme } - } catch {} + } catch { + } if (AppWindow.winWidth <= 0) { - try { const size = screen.getPrimaryDisplay().workAreaSize let width = size.width * 0.677 const height = size.height * 0.866 if (width > AppWindow.winWidth) AppWindow.winWidth = width - if (size.width >= 970 && width < 970) width = 970 + if (size.width >= 970 && width < 970) width = 970 if (AppWindow.winWidth > 1080) AppWindow.winWidth = 1080 if (height > AppWindow.winHeight) AppWindow.winHeight = height if (AppWindow.winHeight > 720) AppWindow.winHeight = 720 @@ -60,17 +102,18 @@ export function createMainWindow() { AppWindow.winHeight = 600 } } - AppWindow.mainWindow = creatElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main', AppWindow.winTheme) + AppWindow.mainWindow = createElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main', AppWindow.winTheme) AppWindow.mainWindow.on('resize', () => { - debounceResize(function () { + debounceResize(function() { try { if (AppWindow.mainWindow && AppWindow.mainWindow.isMaximized() == false && AppWindow.mainWindow.isMinimized() == false && AppWindow.mainWindow.isFullScreen() == false) { - const s = AppWindow.mainWindow!.getSize() + const s = AppWindow.mainWindow!.getSize() const configJson = getUserDataPath('config.json') writeFileSync(configJson, `{"width":${s[0].toString()},"height": ${s[1].toString()}}`, 'utf-8') } - } catch {} + } catch { + } }, 3000) }) @@ -87,94 +130,46 @@ export function createMainWindow() { app.quit() }) - AppWindow.mainWindow.on('ready-to-show', function () { + AppWindow.mainWindow.on('ready-to-show', function() { AppWindow.mainWindow!.webContents.send('setPage', { page: 'PageMain' }) AppWindow.mainWindow!.webContents.send('setTheme', { dark: nativeTheme.shouldUseDarkColors }) - AppWindow.mainWindow!.setTitle('小白羊云盘') + AppWindow.mainWindow!.setTitle('阿里云盘小白羊') if (is.windows() && process.argv && process.argv.join(' ').indexOf('--openAsHidden') < 0) { AppWindow.mainWindow!.show() - } else if (is.macOS() && !app.getLoginItemSettings().wasOpenedAsHidden){ + } else if (is.macOS() && !app.getLoginItemSettings().wasOpenedAsHidden) { AppWindow.mainWindow!.show() } - if (is.linux()){ + if (is.linux()) { AppWindow.mainWindow!.show() } creatUploadPort() creatDownloadPort() }) - AppWindow.mainWindow.webContents.on('render-process-gone', function (event, details) { + AppWindow.mainWindow.webContents.on('render-process-gone', function(event, details) { if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed') { - ShowErrorAndRelanch('(⊙o⊙)?小白羊遇到错误崩溃了', details.reason) + ShowErrorAndRelaunch('(⊙o⊙)?小白羊遇到错误崩溃了', details.reason) } }) - creatUpload() - creatDownload() + createUpload() + createDownload() } -nativeTheme.on('updated', () => { - if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) - AppWindow.mainWindow.webContents.send('setTheme', { - dark: nativeTheme.shouldUseDarkColors - }) -}) -function ShowErrorAndRelanch(title: string, errmsg: string) { - dialog - .showMessageBox({ - type: 'error', - buttons: ['ok'], - title: title + ',小白羊将自动退出', - message: '错误信息:' + errmsg - }) - .then((_) => { - setTimeout(() => { - app.relaunch() - try { - app.exit() - } catch {} - }, 100) - }) -} -export function ShowErrorAndExit(title: string, errmsg: string) { - dialog - .showMessageBox({ - type: 'error', - buttons: ['ok'], - title: title + ',小白羊将自动退出', - message: '错误信息:' + errmsg - }) - .then((_) => { - setTimeout(() => { - try { - app.exit() - } catch {} - }, 100) - }) +export function createMenu() { + AppMenu.menuEdit = new Menu() + AppMenu.menuEdit.append(new MenuItem({ label: '剪切', role: 'cut' })) + AppMenu.menuEdit.append(new MenuItem({ label: '复制', role: 'copy' })) + AppMenu.menuEdit.append(new MenuItem({ label: '粘贴', role: 'paste' })) + AppMenu.menuEdit.append(new MenuItem({ label: '删除', role: 'delete' })) + AppMenu.menuEdit.append(new MenuItem({ label: '全选', role: 'selectAll' })) + AppMenu.menuCopy = new Menu() + AppMenu.menuCopy.append(new MenuItem({ label: '复制', role: 'copy' })) + AppMenu.menuCopy.append(new MenuItem({ label: '全选', role: 'selectAll' })) } -export function ShowError(title: string, errmsg: string) { - dialog - .showMessageBox({ - type: 'error', - buttons: ['ok'], - title: title, - message: '错误信息:' + errmsg - }) - .then((_) => {}) -} - -let timerResize: NodeJS.Timeout | undefined -const debounceResize = (fn: any, wait: number) => { - if (timerResize) clearTimeout(timerResize) - timerResize = setTimeout(() => { - fn() - timerResize = undefined - }, wait) -} export function createTray() { - const trayMenuTemplate = [ { label: '显示主界面', @@ -200,16 +195,11 @@ export function createTray() { } ] - - const icon = getStaticPath('icon_256.ico') + const icon = getStaticPath('icon_256x256.ico') AppWindow.appTray = new Tray(icon) - const contextMenu = Menu.buildFromTemplate(trayMenuTemplate) - - AppWindow.appTray.setToolTip('小白羊云盘') - + AppWindow.appTray.setToolTip('阿里云盘小白羊') AppWindow.appTray.setContextMenu(contextMenu) - AppWindow.appTray.on('click', () => { if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) { if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore() @@ -221,53 +211,7 @@ export function createTray() { }) } -export function creatUpload() { - if (AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) return - AppWindow.uploadWindow = creatElectronWindow(10, 10, false, 'main', 'dark', false) - - AppWindow.uploadWindow.on('ready-to-show', function () { - creatUploadPort() - AppWindow.uploadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'upload' } }) - AppWindow.uploadWindow!.setTitle('小白羊云盘上传进程') - }) - - AppWindow.uploadWindow.webContents.on('render-process-gone', function (event, details) { - if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') { - try { - AppWindow.uploadWindow?.destroy() - } catch {} - AppWindow.uploadWindow = undefined - creatUpload() - } - }) - AppWindow.uploadWindow.hide() -} - -export function creatDownload() { - if (AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) return - AppWindow.downloadWindow = creatElectronWindow(10, 10, false, 'main', 'dark', false) - - AppWindow.downloadWindow.on('ready-to-show', function () { - creatDownloadPort() - AppWindow.downloadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'download' } }) - AppWindow.downloadWindow!.setTitle('小白羊云盘下载进程') - }) - - AppWindow.downloadWindow.webContents.on('render-process-gone', function (event, details) { - if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') { - try { - AppWindow.downloadWindow?.destroy() - } catch {} - AppWindow.downloadWindow = undefined - creatDownload() - } - }) - - AppWindow.downloadWindow.webContents.closeDevTools() - AppWindow.downloadWindow.hide() -} - -export function creatElectronWindow(width: number, height: number, center: boolean, page: string, theme: string, devTools: boolean = true) { +export function createElectronWindow(width: number, height: number, center: boolean, page: string, theme: string, devTools: boolean = true) { const win = new BrowserWindow({ show: false, width: width, @@ -275,7 +219,7 @@ export function creatElectronWindow(width: number, height: number, center: boole minWidth: width > 680 ? 680 : width, minHeight: height > 500 ? 500 : height, center: center, - icon: getStaticPath('icon_256.ico'), + icon: getStaticPath('icon_256x256.ico'), useContentSize: true, frame: false, transparent: false, @@ -284,7 +228,7 @@ export function creatElectronWindow(width: number, height: number, center: boole backgroundColor: theme && theme == 'dark' ? '#23232e' : '#ffffff', webPreferences: { spellcheck: false, - devTools: DEBUGGING, + devTools: true, webviewTag: true, nodeIntegration: true, nodeIntegrationInWorker: true, @@ -298,8 +242,10 @@ export function creatElectronWindow(width: number, height: number, center: boole preload: getAsarPath('dist/electron/preload/index.js') } }) + + win.removeMenu() - if (DEBUGGING) { + if (is.dev()) { const url = `http://localhost:${process.env.VITE_DEV_SERVER_PORT}` win.loadURL(url, { userAgent: ua, httpReferrer: Referer }) } else { @@ -309,10 +255,10 @@ export function creatElectronWindow(width: number, height: number, center: boole }) } - if (DEBUGGING && devTools) { + if (is.dev() && devTools) { if (width < 100) win.setSize(800, 600) win.show() - win.webContents.openDevTools() + win.webContents.openDevTools({ mode: 'bottom' }) } else { win.webContents.on('devtools-opened', () => { if (win && win.webContents.getType() === 'webview') { @@ -327,10 +273,9 @@ export function creatElectronWindow(width: number, height: number, center: boole : win.webContents.openDevTools({ mode: 'undocked' }) } }) - win.webContents.on('did-create-window', (childWindow) => { if (is.windows()) { - childWindow.setMenu(null) + childWindow.setMenu(null) } }) return win @@ -345,8 +290,9 @@ function creatUploadPort() { } }, 1000) } + function creatDownloadPort() { - debounceDownload(function () { + debounceDownload(function() { if (AppWindow.mainWindow && AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) { const { port1, port2 } = new MessageChannelMain() AppWindow.mainWindow.webContents.postMessage('setDownloadPort', undefined, [port1]) @@ -354,23 +300,52 @@ function creatDownloadPort() { } }, 1000) } -let timerUpload: NodeJS.Timeout | undefined -const debounceUpload = (fn: any, wait: number) => { - if (timerUpload) { - clearTimeout(timerUpload) - } - timerUpload = setTimeout(() => { - fn() - timerUpload = undefined - }, wait) -} -let timerDownload: NodeJS.Timeout | undefined -const debounceDownload = (fn: any, wait: number) => { - if (timerDownload) { - clearTimeout(timerDownload) - } - timerDownload = setTimeout(() => { - fn() - timerDownload = undefined - }, wait) + + +function createUpload() { + if (AppWindow.uploadWindow && AppWindow.uploadWindow.isDestroyed() == false) return + AppWindow.uploadWindow = createElectronWindow(10, 10, false, 'main', 'dark', false) + + AppWindow.uploadWindow.on('ready-to-show', function() { + creatUploadPort() + AppWindow.uploadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'upload' } }) + AppWindow.uploadWindow!.setTitle('阿里云盘小白羊上传进程') + }) + + AppWindow.uploadWindow.webContents.on('render-process-gone', function(event, details) { + if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') { + try { + AppWindow.uploadWindow?.destroy() + } catch { + } + AppWindow.uploadWindow = undefined + createUpload() + } + }) + AppWindow.uploadWindow.hide() } + +function createDownload() { + if (AppWindow.downloadWindow && AppWindow.downloadWindow.isDestroyed() == false) return + AppWindow.downloadWindow = createElectronWindow(10, 10, false, 'main', 'dark', false) + + AppWindow.downloadWindow.on('ready-to-show', function() { + creatDownloadPort() + AppWindow.downloadWindow!.webContents.send('setPage', { page: 'PageWorker', data: { type: 'download' } }) + AppWindow.downloadWindow!.setTitle('阿里云盘小白羊下载进程') + }) + + AppWindow.downloadWindow.webContents.on('render-process-gone', function(event, details) { + if (details.reason == 'crashed' || details.reason == 'oom' || details.reason == 'killed' || details.reason == 'integrity-failure') { + try { + AppWindow.downloadWindow?.destroy() + } catch { + } + AppWindow.downloadWindow = undefined + createDownload() + } + }) + + AppWindow.downloadWindow.webContents.closeDevTools() + AppWindow.downloadWindow.hide() +} \ No newline at end of file diff --git a/electron/main/index.ts b/electron/main/index.ts index 2ac438f..6f9601b 100644 --- a/electron/main/index.ts +++ b/electron/main/index.ts @@ -1,621 +1,13 @@ -import {getResourcesPath, getStaticPath, getUserDataPath} from './mainfile' -import { release } from 'os' -import { AppWindow, creatElectronWindow, createMainWindow, createTray, Referer, ShowError, ShowErrorAndExit, ua } from './window' -import Electron from 'electron' -import is from 'electron-is' -import { execFile, SpawnOptions } from 'child_process' -import { portIsOccupied } from './utils' -import { app, BrowserWindow, dialog, Menu, MenuItem, ipcMain, shell, session } from 'electron' -import { exec, spawn } from 'child_process' -import { existsSync, readFileSync, writeFileSync } from 'fs' -import path from 'path' -import fixPath from 'fix-path' - -fixPath() -if (release().startsWith('6.1')) { - app.disableHardwareAcceleration() -} -process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' -process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' -process.on('unhandledRejection', (reason, p) => { - console.log('Unhandled Rejection at:', p, 'reason:', reason) -}) -process.on('uncaughtException', (err) => { - const stack = err.stack || '' - if (app.isReady()) ShowErrorAndExit('发生未定义的异常', err.message + '\n' + stack) -}) - - -// app.commandLine.appendSwitch('proxy-server', '192.168.31.74:8888') -app.commandLine.appendSwitch('no-sandbox') -app.commandLine.appendSwitch('disable-web-security') -app.commandLine.appendSwitch('disable-renderer-backgrounding') -app.commandLine.appendSwitch('disable-site-isolation-trials') -app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure') -app.commandLine.appendSwitch('ignore-connections-limit', 'bj29.cn-beijing.data.alicloudccp.com,alicloudccp.com,api.aliyundrive.com,aliyundrive.com') -app.commandLine.appendSwitch('ignore-certificate-errors') -app.commandLine.appendSwitch('proxy-bypass-list', '') -app.commandLine.appendSwitch('wm-window-animations-disabled') - -app.setAppUserModelId('gaozhangmin') -app.name = 'alixby3' -const DEBUGGING = !app.isPackaged - -const userData = getResourcesPath('userdir.config') -try { - if (existsSync(userData)) { - const configData = readFileSync(userData, 'utf-8') - if (configData) app.setPath('userData', configData) - } -} catch { -} - -const gotTheLock = app.requestSingleInstanceLock() -if (DEBUGGING == false) { - if (!gotTheLock) { - app.exit() - } else { - app.on('second-instance', (event, commandLine, workingDirectory) => { - if (commandLine && commandLine.join(' ').indexOf('exit') >= 0) { - app.exit() - } else if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) { - if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore() - AppWindow.mainWindow.show() - AppWindow.mainWindow.focus() - } - }) - } -} - -if (process.argv && process.argv.join(' ').indexOf('exit') >= 0) { - app.exit() -} -app.on('window-all-closed', () => { - if (is.macOS()) { - AppWindow.appTray?.destroy() - } else { - app.quit() // 未测试应该使用哪一个 - } -}) - -app.on('activate', () => { - if (!AppWindow.mainWindow || AppWindow.mainWindow.isDestroyed()) createMainWindow() - else { - if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore() - AppWindow.mainWindow.show() - AppWindow.mainWindow.focus() - } -}) - -app.on('will-quit', () => { - try { - if (AppWindow.appTray) { - AppWindow.appTray.destroy() - AppWindow.appTray = undefined - } - } catch { - - } -}) +import { app } from 'electron' +import { getStaticPath } from './utils/mainfile' +import launch from './launch' app.setAboutPanelOptions({ applicationName: '小白羊云盘', copyright: 'Zhangmin Gao', website: 'https://github.com/gaozhangmin/aliyunpan', - iconPath: getStaticPath('icon_64.png'), + iconPath: getStaticPath('icon_64x64.png'), applicationVersion: '30' }) -let userToken: { access_token: string; access_token_v2:string, user_id: string; refresh: boolean } = { - access_token: '', - access_token_v2: '', - user_id: '', - refresh: false -} -ipcMain.on('WebUserToken', (event, data) => { - if (data.login) { - userToken = data - } else if (userToken.user_id == data.user_id) { - userToken = data - // ShowError('WebUserToken', 'update' + data.name) - } else { - // ShowError('WebUserToken', 'nothing' + data.name) - } -}) - - -// ipcMain.on('CheckUpdate', () => { -// checkForUpdates() -// }); -// -// autoUpdater.setFeedURL({ -// provider: 'github', -// owner: 'gaozhangmin', -// repo: 'aliyunpan', -// }); -// -// function checkForUpdates() { -// autoUpdater.checkForUpdates().then((updateCheckResult) => { -// if (updateCheckResult && updateCheckResult.updateInfo.version !== autoUpdater.currentVersion.version) { -// //有新版本可用,显示提示框 -// dialog.showMessageBox({ -// type: 'question', -// buttons: ['Yes', 'No'], -// title: '应用有新版本可用', -// message: `检测到新版本: ${updateCheckResult.updateInfo.version} , 扫码关注小白羊网盘下载更新`, -// detail: '是否立即安装新版本?', -// icon: qrCodeImage -// }).then((result) => { -// // 根据用户选择的按钮执行相应操作 -// if (result.response === 0) { -// shell.openExternal(downloadPageUrl); -// } -// }); -// } -// }).catch((error) => { -// // 更新检查失败 -// }); -// } - -app - .whenReady() - .then(() => { - try { - const localVersion = getResourcesPath('localVersion') - if (localVersion && existsSync(localVersion)) { - const version = readFileSync(localVersion, 'utf-8') - if (app.getVersion() !== version) { - writeFileSync(localVersion, app.getVersion(), 'utf-8') - } - } else { - writeFileSync(localVersion, app.getVersion(), 'utf-8') - } - } catch (err) {} - session.defaultSession.webRequest.onBeforeSendHeaders((details, cb) => { - const should115Referer = details.url.indexOf('.115.com') > 0 - const shouldGieeReferer = details.url.indexOf('gitee.com') > 0 - const shouldAliOrigin = details.url.indexOf('.aliyundrive.com') > 0 - const shouldAliReferer = !should115Referer && !shouldGieeReferer && (!details.referrer || details.referrer.trim() === '' || /(\/localhost:)|(^file:\/\/)|(\/127.0.0.1:)/.exec(details.referrer) !== null) - const shouldToken = details.url.includes('aliyundrive') && details.url.includes('download') - const shouldOpenApiToken = details.url.includes('adrive/v1.0') - - cb({ - cancel: false, - requestHeaders: { - ...details.requestHeaders, - ...(should115Referer && { - Referer: 'http://115.com/s/swn4bs33z88', - Origin: 'http://115.com' - }), - ...(shouldGieeReferer && { - Referer: 'https://gitee.com/' - }), - ...(shouldAliOrigin && { - Origin: 'https://www.aliyundrive.com' - }), - ...(shouldAliReferer && { - Referer: 'https://www.aliyundrive.com/' - }), - ...(shouldToken && { - Authorization: userToken.access_token - }), - // ...(shouldOpenApiToken && { - // Authorization: userToken.access_token_v2 - // }), - 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.1.0 Chrome/108.0.5359.215 Electron/22.3.1 Safari/537.36', - 'X-Canary': 'client=windows,app=adrive,version=v4.1.0', - 'Accept-Language': 'zh-CN,zh;q=0.9' - } - }) - }) - - session.defaultSession.loadExtension(getStaticPath('crx'), { allowFileAccess: true }) - .then((le) => { - createMenu() - createTray() - createMainWindow() - }) - }) - .catch((err: any) => { - console.log(err) - }) - - -let menuEdit: Electron.Menu | undefined -let menuCopy: Electron.Menu | undefined - -export function createMenu() { - menuEdit = new Menu() - menuEdit.append(new MenuItem({ label: '剪切', role: 'cut' })) - menuEdit.append(new MenuItem({ label: '复制', role: 'copy' })) - menuEdit.append(new MenuItem({ label: '粘贴', role: 'paste' })) - menuEdit.append(new MenuItem({ label: '删除', role: 'delete' })) - menuEdit.append(new MenuItem({ label: '全选', role: 'selectAll' })) - - menuCopy = new Menu() - menuCopy.append(new MenuItem({ label: '复制', role: 'copy' })) - menuCopy.append(new MenuItem({ label: '全选', role: 'selectAll' })) -} - -async function creatAria() { - try { - const enginePath: string = getStaticPath('engine') - const confPath: string = path.join(enginePath, 'aria2.conf') - const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c' - const basePath: string = path.join(enginePath, DEBUGGING ? path.join(process.platform, process.arch) : '') - let ariaFilePath: string = path.join(basePath, ariaPath) - if (!existsSync(ariaFilePath)) { - ShowError('找不到Aria程序文件', ariaFilePath) - return 0 - } - const listenPort = await portIsOccupied(16800) - const options: SpawnOptions = { - stdio: is.dev() ? 'pipe' : 'ignore', - windowsHide: false - } - const args = [ - `--stop-with-process=${process.pid}`, - `--conf-path=${confPath}`, - `--rpc-listen-port=${listenPort}`, - '-D' - ] - execFile(`${ariaFilePath}`, args, options) - return listenPort - } catch (e: any) { - console.log(e) - } - return 0 -} - -ipcMain.on('WebToElectron', async (event, data) => { - let mainWindow = AppWindow.mainWindow - if (data.cmd && data.cmd === 'close') { - if (mainWindow && !mainWindow.isDestroyed()) mainWindow.hide() - } else if (data.cmd && data.cmd === 'exit') { - if (mainWindow && !mainWindow.isDestroyed()) { - mainWindow.destroy() - mainWindow = undefined - } - try { - app.exit() - } catch { - } - } else if (data.cmd && data.cmd === 'minsize') { - if (mainWindow && !mainWindow.isDestroyed()) mainWindow.minimize() - } else if (data.cmd && data.cmd === 'maxsize') { - if (mainWindow && !mainWindow.isDestroyed()) { - if (mainWindow.isMaximized()) { - mainWindow.unmaximize() - } else { - mainWindow.maximize() - } - } - } else if (data.cmd && data.cmd === 'menuedit') { - if (menuEdit) menuEdit.popup() - } else if (data.cmd && data.cmd === 'menucopy') { - if (menuCopy) menuCopy.popup() - } else if (data.cmd && (Object.hasOwn(data.cmd, 'launchStartUp') - || Object.hasOwn(data.cmd, 'launchStartUpShow'))) { - console.log("data.cmd", data.cmd) - const launchStart = data.cmd.launchStartUp - const launchStartShow = data.cmd.launchStartUpShow - const appName = path.basename(process.execPath) - // 设置开机自启 - const settings: Electron.Settings = { - openAtLogin: launchStart, - path: process.execPath - } - // 显示主窗口 - if (is.macOS()) { - settings.openAsHidden = !launchStartShow - } else { - settings.args = [ - '--processStart', `${appName}`, - '--process-start-args', `"--hidden"` - ] - !launchStartShow && settings.args.push('--openAsHidden') - } - app.setLoginItemSettings(settings) - } else if (data.cmd && Object.hasOwn(data.cmd, 'appUserDataPath')) { - const userDataPath = data.cmd.appUserDataPath - const localVersion = getResourcesPath('userdir.config') - writeFileSync(localVersion, userDataPath, 'utf-8') - } else { - event.sender.send('ElectronToWeb', 'mainsenddata') - } -}) - -ipcMain.on('WebToElectronCB', (event, data) => { - const mainWindow = AppWindow.mainWindow - if (data.cmd && data.cmd === 'maxsize') { - if (mainWindow && !mainWindow.isDestroyed()) { - if (mainWindow.isMaximized()) { - mainWindow.unmaximize() - event.returnValue = 'unmaximize' - } else { - mainWindow.maximize() - event.returnValue = 'maximize' - } - } - } else { - event.returnValue = 'backdata' - } -}) - -ipcMain.on('WebShowOpenDialogSync', (event, config) => { - dialog.showOpenDialog(AppWindow.mainWindow!, config).then((result) => { - event.returnValue = result.filePaths - }) -}) - -ipcMain.on('WebShowSaveDialogSync', (event, config) => { - dialog.showSaveDialog(AppWindow.mainWindow!, config).then((result) => { - event.returnValue = result.filePath || '' - }) -}) - -ipcMain.on('WebShowItemInFolder', (event, fullPath) => { - for (let i = 0; i < 5; i++) { - if (existsSync(fullPath)) break - if (fullPath.lastIndexOf(path.sep) > 0) { - fullPath = fullPath.substring(0, fullPath.lastIndexOf(path.sep)) - } else return - } - if (fullPath.length > 2) shell.showItemInFolder(fullPath) -}) - -ipcMain.on('WebPlatformSync', (event) => { - const asarPath = app.getAppPath() - const appPath = app.getPath('userData') - event.returnValue = { - platform: process.platform, - arch: process.arch, - version: process.version, - execPath: process.execPath, - appPath: appPath, - asarPath: asarPath, - argv0: process.argv0 - } -}) - -ipcMain.on('WebSpawnSync', (event, data) => { - try { - const options: SpawnOptions = { - stdio: 'ignore', - shell: true, - ...data.options - } - const argsToStr = (args: string) => is.windows() ? `"${args}"` : `'${args}'` - if ((is.windows() || is.macOS()) && !existsSync(data.command)) { - event.returnValue = { error: '找不到文件' + data.command } - ShowError('找不到文件', data.command) - } else { - let command - if (is.macOS()) { - command = `open -a ${argsToStr(data.command)} ${data.command.includes('mpv.app') ? '--args ' : ''}` - } else { - command = `${argsToStr(data.command)}` - } - const subProcess = spawn(command, data.args, options) - console.log(command, data.args) - const isRunning = process.kill(subProcess.pid, 0) - subProcess.unref() - event.returnValue = { - pid: subProcess.pid, - isRunning: isRunning, - execCmd: data, - options: options, - exitCode: subProcess.exitCode - } - } - } catch (err: any) { - event.returnValue = { error: err } - } -}) -ipcMain.on('WebExecSync', (event, data) => { - try { - const cmdArguments = [] - cmdArguments.push(data.command) - if (data.args) cmdArguments.push(...data.args) - const finalCmd = cmdArguments.join(' ') - exec(finalCmd, (err: any) => { - event.returnValue = err - }) - event.returnValue = '' - } catch (err: any) { - event.returnValue = { error: err } - } -}) - -ipcMain.on('WebSaveTheme', (event, data) => { - try { - const themeJson = getUserDataPath('theme.json') - writeFileSync(themeJson, `{"theme":"${data.theme || ''}"}`, 'utf-8') - } catch { - } -}) - -ipcMain.on('WebClearCookies', (event, data) => { - session.defaultSession.clearStorageData(data) -}) -ipcMain.on('WebSetCookies', (event, data) => { - for (let i = 0, maxi = data.length; i < maxi; i++) { - const cookie = { - url: data[i].url, - name: data[i].name, - value: data[i].value, - domain: '.' + data[i].url.substring(data[i].url.lastIndexOf('/') + 1), - secure: data[i].url.indexOf('https://') == 0, - expirationDate: data[i].expirationDate - } - session.defaultSession.cookies.set(cookie).catch((err: any) => console.error(err)) - } -}) -ipcMain.on('WebClearCache', (event, data) => { - if (data.cache) { - session.defaultSession.clearCache() - session.defaultSession.clearAuthCache() - } else { - session.defaultSession.clearStorageData(data) - } -}) - -ipcMain.on('WebReload', (event, data) => { - if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) AppWindow.mainWindow.reload() -}) -ipcMain.on('WebRelaunch', (event, data) => { - app.relaunch() - try { - app.exit() - } catch { - } -}) - -ipcMain.handle('WebRelaunchAria', async (event, data) => { - return await startAria2c() -}) - -ipcMain.on('WebSetProgressBar', (event, data) => { - if (AppWindow.mainWindow && !AppWindow.mainWindow.isDestroyed()) { - if (data.pro) { - - AppWindow.mainWindow.setProgressBar(data.pro, { mode: data.mode || 'normal' }) - } else AppWindow.mainWindow.setProgressBar(-1) - } -}) - -ipcMain.on('WebShutDown', (event, data) => { - if (is.macOS()) { - const shutdownCmd = 'osascript -e \'tell application "System Events" to shut down\'' - exec(shutdownCmd, (err: any) => { - if (data.quitApp) { - try { - app.exit() - } catch { - } - } - if (err) { - // donothing - } - }) - } else { - const cmdArguments = ['shutdown'] - if (is.linux()) { - if (data.sudo) { - cmdArguments.unshift('sudo') - } - cmdArguments.push('-h') - cmdArguments.push('now') - } - if (is.windows()) { - cmdArguments.push('-s') - cmdArguments.push('-f') - cmdArguments.push('-t 0') - } - - const finalcmd = cmdArguments.join(' ') - - exec(finalcmd, (err: any) => { - if (data.quitApp) { - try { - app.exit() - } catch { - } - } - if (err) { - // donothing - } - }) - } -}) - -ipcMain.on('WebSetProxy', (event, data) => { - // if (data.proxyUrl) app.commandLine.appendSwitch('proxy-server', data.proxyUrl) - // else app.commandLine.removeSwitch('proxy-server') - console.log(JSON.stringify(data)) - if (data.proxyUrl) { - session.defaultSession.setProxy({ proxyRules: data.proxyUrl }) - } else { - session.defaultSession.setProxy({}) - } -}) - -ipcMain.on('WebOpenWindow', (event, data) => { - const win = creatElectronWindow(AppWindow.winWidth, AppWindow.winHeight, true, 'main2', data.theme) - - win.on('ready-to-show', function() { - win.webContents.send('setPage', data) - win.setTitle('预览窗口') - win.show() - }) -}) - -ipcMain.on('WebOpenUrl', (event, data) => { - const win = new BrowserWindow({ - show: false, - width: AppWindow.winWidth, - height: AppWindow.winHeight, - center: true, - minWidth: 680, - minHeight: 500, - icon: getStaticPath('icon_256.ico'), - useContentSize: true, - frame: true, - hasShadow: true, - autoHideMenuBar: true, - backgroundColor: data.theme && data.theme == 'dark' ? '#23232e' : '#ffffff', - webPreferences: { - spellcheck: false, - devTools: DEBUGGING, - sandbox: false, - webSecurity: false, - allowRunningInsecureContent: true, - backgroundThrottling: false, - enableWebSQL: false, - disableBlinkFeatures: 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure' - } - }) - - win.on('ready-to-show', function() { - win.setTitle('预览窗口') - win.show() - }) - - win.loadURL(data.PageUrl, { - userAgent: ua, - httpReferrer: Referer - }) -}) - -async function startAria2c() { - try { - const enginePath: string = getStaticPath('engine') - const confPath: string = path.join(enginePath, 'aria2.conf') - const ariaPath: string = is.windows() ? 'aria2c.exe' : 'aria2c' - const basePath: string = path.join(enginePath, is.dev() ? path.join(process.platform, process.arch) : '') - const ariaFilePath: string = path.join(basePath, ariaPath) - if (!existsSync(ariaFilePath)) { - ShowError('找不到Aria程序文件', ariaFilePath) - return 0 - } - const argsToStr = (args: any) => is.windows() ? `"${args}"` : `'${args}'` - const listenPort = await portIsOccupied(16800) - const options: SpawnOptions = { - shell: true, - stdio: is.dev() ? 'pipe' : 'ignore', - windowsHide: false, - windowsVerbatimArguments: true - } - const args = [ - `--stop-with-process=${argsToStr(process.pid)}`, - `--conf-path=${argsToStr(confPath)}`, - `--rpc-listen-port=${argsToStr(listenPort)}`, - '-D' - ] - spawn(`${argsToStr(ariaFilePath)}`, args, options) - return listenPort - } catch (e: any) { - console.log(e) - } - return 0 -} +const appLaunch = new launch() \ No newline at end of file diff --git a/electron/main/launch.ts b/electron/main/launch.ts new file mode 100644 index 0000000..6141e9f --- /dev/null +++ b/electron/main/launch.ts @@ -0,0 +1,221 @@ +import { AppWindow, createMainWindow, createMenu, createTray } from './core/window' +import { app, ipcMain, session } from 'electron' +import is from 'electron-is' +import fixPath from 'fix-path' +import { release } from 'os' +import { getResourcesPath, getStaticPath } from './utils/mainfile' +import { existsSync, readFileSync, writeFileSync } from 'fs' +import { EventEmitter } from 'node:events' +import exception from './core/exception' +import ipcEvent from './core/ipcEvent' + +type UserToken = { + access_token: string; + access_token_v2: string; + user_id: string; + refresh: boolean +} + +export default class launch extends EventEmitter { + private userToken: UserToken = { + access_token: '', + access_token_v2: '', + user_id: '', + refresh: false + } + + constructor() { + super() + this.init() + } + + init() { + this.start() + if (is.mas()) return + const gotSingleLock = app.requestSingleInstanceLock() + if (!gotSingleLock) { + app.exit() + } else { + app.on('second-instance', (event, commandLine, workingDirectory) => { + if (commandLine && commandLine.join(' ').indexOf('exit') >= 0) { + this.hasExitArgv(commandLine) + } else if (AppWindow.mainWindow && AppWindow.mainWindow.isDestroyed() == false) { + if (AppWindow.mainWindow.isMinimized()) { + AppWindow.mainWindow.restore() + } + AppWindow.mainWindow.show() + AppWindow.mainWindow.focus() + } + }) + } + } + + start() { + exception.handler() + this.setInitArgv() + this.handleEvents() + this.handleAppReady() + } + + setInitArgv() { + fixPath() + if (release().startsWith('6.1')) { + app.disableHardwareAcceleration() + } + process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true' + process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0' + + app.commandLine.appendSwitch('no-sandbox') + app.commandLine.appendSwitch('disable-web-security') + app.commandLine.appendSwitch('disable-renderer-backgrounding') + app.commandLine.appendSwitch('disable-site-isolation-trials') + app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors,SameSiteByDefaultCookies,CookiesWithoutSameSiteMustBeSecure') + app.commandLine.appendSwitch('ignore-connections-limit', 'bj29.cn-beijing.data.alicloudccp.com,alicloudccp.com,api.aliyundrive.com,aliyundrive.com') + app.commandLine.appendSwitch('ignore-certificate-errors') + app.commandLine.appendSwitch('proxy-bypass-list', '') + app.commandLine.appendSwitch('wm-window-animations-disabled') + + app.name = 'alixby3' + if (is.windows()) { + app.setAppUserModelId('gaozhangmin') + } + this.hasExitArgv(process.argv) + } + + hasExitArgv(args) { + if (args && args.join(' ').indexOf('exit') >= 0) { + app.exit() + } + } + + loadUserData() { + const userData = getResourcesPath('userdir.config') + try { + if (existsSync(userData)) { + const configData = readFileSync(userData, 'utf-8') + if (configData) app.setPath('userData', configData) + } + } catch { + } + } + + handleEvents() { + ipcEvent.handleEvents() + this.handleUserToken() + this.handleAppActivate() + this.handleAppWillQuit() + this.handleAppWindowAllClosed() + } + + handleAppReady() { + app + .whenReady() + .then(() => { + try { + const localVersion = getResourcesPath('localVersion') + if (localVersion && existsSync(localVersion)) { + const version = readFileSync(localVersion, 'utf-8') + if (app.getVersion() > version) { + writeFileSync(localVersion, app.getVersion(), 'utf-8') + } + } else { + writeFileSync(localVersion, app.getVersion(), 'utf-8') + } + } catch (err) { + } + session.defaultSession.webRequest.onBeforeSendHeaders((details, cb) => { + const should115Referer = details.url.indexOf('.115.com') > 0 + const shouldGieeReferer = details.url.indexOf('gitee.com') > 0 + const shouldAliOrigin = details.url.indexOf('.aliyundrive.com') > 0 + const shouldAliReferer = !should115Referer && !shouldGieeReferer && (!details.referrer || details.referrer.trim() === '' || /(\/localhost:)|(^file:\/\/)|(\/127.0.0.1:)/.exec(details.referrer) !== null) + const shouldToken = details.url.includes('aliyundrive') && details.url.includes('download') + const shouldOpenApiToken = details.url.includes('adrive/v1.0') + + cb({ + cancel: false, + requestHeaders: { + ...details.requestHeaders, + ...(should115Referer && { + Referer: 'http://115.com/s/swn4bs33z88', + Origin: 'http://115.com' + }), + ...(shouldGieeReferer && { + Referer: 'https://gitee.com/' + }), + ...(shouldAliOrigin && { + Origin: 'https://www.aliyundrive.com' + }), + ...(shouldAliReferer && { + Referer: 'https://www.aliyundrive.com/' + }), + ...(shouldToken && { + Authorization: this.userToken.access_token + }), + // ...(shouldOpenApiToken && { + // Authorization: this.userToken.access_token_v2 + // }), + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) aDrive/4.1.0 Chrome/108.0.5359.215 Electron/22.3.1 Safari/537.36', + 'X-Canary': 'client=windows,app=adrive,version=v4.1.0', + 'Accept-Language': 'zh-CN,zh;q=0.9' + } + }) + }) + session.defaultSession.loadExtension(getStaticPath('crx'), { allowFileAccess: true }).then(() => { + createMainWindow() + createMenu() + createTray() + }) + }) + .catch((err: any) => { + console.log(err) + }) + } + + handleUserToken() { + ipcMain.on('WebUserToken', (event, data) => { + if (data.login) { + this.userToken = data + } else if (this.userToken.user_id == data.user_id) { + this.userToken = data + // ShowError('WebUserToken', 'update' + data.name) + } else { + // ShowError('WebUserToken', 'nothing' + data.name) + } + }) + } + + handleAppActivate() { + app.on('activate', () => { + if (!AppWindow.mainWindow || AppWindow.mainWindow.isDestroyed()){ + createMainWindow() + } else { + if (AppWindow.mainWindow.isMinimized()) AppWindow.mainWindow.restore() + AppWindow.mainWindow.show() + AppWindow.mainWindow.focus() + } + }) + } + + handleAppWillQuit() { + app.on('will-quit', () => { + try { + if (AppWindow.appTray) { + AppWindow.appTray.destroy() + AppWindow.appTray = undefined + } + } catch { + + } + }) + } + + handleAppWindowAllClosed() { + app.on('window-all-closed', () => { + if (is.macOS()) { + AppWindow.appTray?.destroy() + } else { + app.quit() // 未测试应该使用哪一个 + } + }) + } +} \ No newline at end of file diff --git a/electron/main/utils/index.ts b/electron/main/utils/index.ts index e4af927..d7cf920 100644 --- a/electron/main/utils/index.ts +++ b/electron/main/utils/index.ts @@ -1,17 +1,13 @@ import net from 'net' export function portIsOccupied(port: number) { - const server = net.createServer().listen(port, '0.0.0.0') - return new Promise((resolve, reject) => { - server.on('listening', () => { console.log(`the server is runnint on port ${port}`) server.close() resolve(port) // 返回可用端口 }) - server.on('error', (err: any) => { if (err.code === 'EADDRINUSE') { resolve(portIsOccupied(port + 1)) // 如传入端口号被占用则 +1 @@ -22,7 +18,5 @@ export function portIsOccupied(port: number) { resolve(port) } }) - }) - -} +} \ No newline at end of file diff --git a/electron/main/mainfile.ts b/electron/main/utils/mainfile.ts similarity index 83% rename from electron/main/mainfile.ts rename to electron/main/utils/mainfile.ts index 13e39bb..5f5b1ea 100644 --- a/electron/main/mainfile.ts +++ b/electron/main/utils/mainfile.ts @@ -3,13 +3,11 @@ import path from 'path' import { copyFileSync, existsSync, rmSync } from 'fs' import is from 'electron-is' -const DEBUGGING = !app.isPackaged - let NewCopyed = false let NewSaved = false export function getAsarPath(fileName: string) { - if (DEBUGGING) { + if (is.dev()) { const basePath = path.resolve(app.getAppPath()) return path.join(basePath, fileName) } else { @@ -37,16 +35,16 @@ export function getAsarPath(fileName: string) { export function getResourcesPath(fileName: string) { let basePath = path.resolve(app.getAppPath(), '..') - if (DEBUGGING) basePath = path.resolve(app.getAppPath(), '.') + if (is.dev()) basePath = path.resolve(app.getAppPath(), '.') return path.join(basePath, fileName) } export function getStaticPath(fileName: string) { let basePath = path.resolve(app.getAppPath(), '..') - if (DEBUGGING) basePath = path.resolve(app.getAppPath(), './static') + if (is.dev()) basePath = path.resolve(app.getAppPath(), './static') if (fileName.startsWith('icon')) { - if (fileName == 'icon_256.ico' && !is.windows()) { - fileName = path.join('images', 'icon_24.png') + if (fileName == 'icon_256x256.ico' && !is.windows()) { + fileName = path.join('images', 'icon_24x24.png') } else { fileName = path.join('images', fileName) } diff --git a/electron/preload/index.ts b/electron/preload/index.ts index c0f3ff2..ebbd767 100644 --- a/electron/preload/index.ts +++ b/electron/preload/index.ts @@ -102,11 +102,30 @@ window.WebRelaunchAria = async function() { return 0 } } +window.WebRelaunchAlist = async function() { + try { + return await ipcRenderer.invoke('WebRelaunchAlist') + } catch { + return 0 + } +} +window.WebResetAlistPwd = async function(data: any) { + try { + return await ipcRenderer.invoke('WebResetAlistPwd', data) + } catch { + return 0 + } +} window.WebSetProgressBar = function (data: any) { try { ipcRenderer.send('WebSetProgressBar', data) } catch {} } +window.WebGetCookies = async function (data: any) { + try { + return await ipcRenderer.invoke('WebGetCookies', data) + } catch {} +} window.WebSetCookies = function (cookies: any) { try { ipcRenderer.send('WebSetCookies', cookies) diff --git a/electron/preload/preload-env.d.ts b/electron/preload/preload-env.d.ts index 96d542c..e2a2a4c 100644 --- a/electron/preload/preload-env.d.ts +++ b/electron/preload/preload-env.d.ts @@ -25,7 +25,10 @@ declare interface Window { WebReload: any WebRelaunch: any WebRelaunchAria: () => Promise + WebRelaunchAlist: () => Promise WebSetProgressBar: any + WebResetAlistPwd:any + WebGetCookies: any WebSetCookies: any WebOpenWindow: any WebOpenUrl: any diff --git a/index.html b/index.html index 6ffeb85..37786e7 100644 --- a/index.html +++ b/index.html @@ -1,6 +1,15 @@ + + + 小白羊云盘 diff --git a/package.json b/package.json index 5617aef..1f11cf5 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xbyyunpan", "description": "小白羊云盘", - "version": "3.11.15", + "version": "3.11.21", "license": "MIT", "main": "dist/electron/main/index.js", "author": { @@ -15,7 +15,9 @@ "engines": { "node": ">=16.0.0" }, - "dependencies": {}, + "dependencies": { + + }, "devDependencies": { "@arco-design/web-vue": "^2.45.3", "@electron/remote": "^2.0.9", @@ -35,7 +37,8 @@ "consola": "^3.1.0", "crypto-js": "^4.1.1", "dayjs": "^1.11.7", - "dexie": "^3.2.3", + "dexie": "^3.2.4", + "digest-fetch": "^3.0.4", "dom-to-image": "^2.6.0", "electron": "^21.4.4", "electron-builder": "^23.6.0", @@ -43,6 +46,7 @@ "electron-log": "^4.4.8", "fast-levenshtein": "^3.0.0", "fix-path": "^4.0.0", + "ass-html5": "^0.3.5", "fuzzysort": "^2.0.1", "hls.js": "^1.4.3", "howler": "^2.2.3", @@ -58,7 +62,7 @@ "uuid": "^9.0.0", "uuid-by-string": "^4.0.0", "viewerjs": "^1.10.5", - "vite": "^3.1.0", + "vite": "3.2.7", "vite-plugin-electron": "^0.9.2", "vite-plugin-resolve": "^2.1.2", "vue": "^3.2.47", @@ -79,4 +83,4 @@ "vue3", "vue" ] -} \ No newline at end of file +} diff --git a/public/images/alicode.png b/public/images/alicode.png new file mode 100644 index 0000000..f70a7a1 Binary files /dev/null and b/public/images/alicode.png differ diff --git a/public/images/sales.png b/public/images/sales.png new file mode 100644 index 0000000..d4d0322 Binary files /dev/null and b/public/images/sales.png differ diff --git a/public/images/sales1.png b/public/images/sales1.png new file mode 100644 index 0000000..34f0f54 Binary files /dev/null and b/public/images/sales1.png differ diff --git a/src/aliapi/album.ts b/src/aliapi/album.ts index e1e2e60..77ccc7c 100644 --- a/src/aliapi/album.ts +++ b/src/aliapi/album.ts @@ -72,7 +72,7 @@ export default class AliAlbum { static async ApiTotalPhotosNum(): Promise { const driver_id = GetDriveID(useUserStore().user_id, 'pic') const userId = useUserStore().user_id - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' if (!driver_id) { return 0 @@ -95,7 +95,7 @@ export default class AliAlbum { static async ApiLimitedPhotos(marker="", limited=100): Promise { const driver_id = GetDriveID(useUserStore().user_id, 'pic') const userId = useUserStore().user_id - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' let max: number = useSettingStore().debugFileListMax const results:AliAlbumFileInfo[] = [] @@ -132,7 +132,7 @@ export default class AliAlbum { static async ApiAllPhotos(): Promise { const driver_id = GetDriveID(useUserStore().user_id, 'pic') const userId = useUserStore().user_id - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' let marker = ''; let max: number = useSettingStore().debugFileListMax diff --git a/src/aliapi/alihttp.ts b/src/aliapi/alihttp.ts index 9caa92e..15962ea 100644 --- a/src/aliapi/alihttp.ts +++ b/src/aliapi/alihttp.ts @@ -1,11 +1,13 @@ import { ITokenInfo } from '../user/userstore' import UserDAL from '../user/userdal' -import axios, { AxiosResponse } from 'axios' +import { AxiosResponse } from 'axios' +import axios from '../axios' import jschardet from 'jschardet' import AliUser from './user' import message from '../utils/message' import DebugLog from '../utils/debuglog' import { v4 } from 'uuid' +import DigestClient from "digest-fetch" export interface IUrlRespData { code: number @@ -48,9 +50,8 @@ function Sleep(msTime: number): Promise<{ success: true; time: number }> { const IsDebugHttp = false export default class AliHttp { - static LimitMax = 100 - static baseapi = 'https://api.aliyundrive.com/' - static baseOpenApi = 'https://open.aliyundrive.com/' + static baseApi = 'https://api.aliyundrive.com/' + static baseOpenApi = 'https://openapi.aliyundrive.com/' static IsSuccess(code: number): Boolean { return code >= 200 && code <= 300 @@ -60,7 +61,7 @@ export default class AliHttp { if (code >= 200 && code <= 300) return true if (code == 400) return true // if (code == 401) return true - if (code >= 402 && code <= 429) return true + if (code >= 402 && code <= 428) return true if (code == 404) return true if (code == 409) return true return false @@ -92,19 +93,31 @@ export default class AliHttp { 'UserDeviceIllegality', 'UserDeviceOffline', 'DeviceSessionSignatureInvalid', + 'AccessTokenInvalid', + 'AccessTokenExpired', + 'I400JD', ] if (errCode.includes(data.code)) isNeedLog = false // 自动刷新Token if (data.code == 'AccessTokenInvalid' - || data.code == 'AccessTokenExpired') { + || data.code == 'AccessTokenExpired' + || data.code == 'I400JD') { if (token) { - if (window.IsMainPage) { - return await AliUser.ApiTokenRefreshAccount(token, true).then((isLogin: boolean) => { + const isOpenApi = config.url.includes('adrive/v1.0') + if (!isOpenApi) { + return await AliUser.ApiRefreshAccessTokenV1(token, true, true).then((isLogin: boolean) => { if (isLogin) { return { code: 401, header: '', body: '' } as IUrlRespData } return { code: 403, header: '', body: 'NetError 账号需要重新登录' } as IUrlRespData }) + } else { + return await AliUser.ApiRefreshAccessTokenV2(token, true, true).then((flag: boolean) => { + if (flag) { + return { code: 401, header: '', body: '' } as IUrlRespData + } + return { code: 403, header: '', body: '刷新OpenApiToken失败,请检查配置' } as IUrlRespData + }) } } else { return { code: 402, header: '', body: 'NetError 账号需要重新登录' } as IUrlRespData @@ -164,7 +177,7 @@ export default class AliHttp { } static async Get(url: string, user_id: string): Promise { - if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url + if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseApi + url for (let i = 0; i <= 5; i++) { const resp = await AliHttp._Get(url, user_id) if (AliHttp.HttpCodeBreak(resp.code)) return resp @@ -205,7 +218,7 @@ export default class AliHttp { static async GetString(url: string, user_id: string, fileSize: number, maxSize: number): Promise { - if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url + if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseApi + url for (let i = 0; i <= 5; i++) { const resp = await AliHttp._GetString(url, user_id, fileSize, maxSize) if (AliHttp.HttpCodeBreak(resp.code)) return resp @@ -293,7 +306,7 @@ export default class AliHttp { static async GetBlob(url: string, user_id: string): Promise { - if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url + if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseApi + url for (let i = 0; i <= 5; i++) { const resp = await AliHttp._GetBlob(url, user_id) if (AliHttp.HttpCodeBreak(resp.code)) return resp @@ -334,7 +347,7 @@ export default class AliHttp { static async Post(url: string, postData: any, user_id: string, share_token: string): Promise { if (!url.startsWith('http') && !url.startsWith('https')) { - url = (url.includes('adrive/v1.0') ? AliHttp.baseOpenApi : AliHttp.baseapi) + url + url = (url.includes('adrive/v1.0') ? AliHttp.baseOpenApi : AliHttp.baseApi) + url } for (let i = 0; i <= 5; i++) { const resp = await AliHttp._Post(url, postData, user_id, share_token) @@ -342,12 +355,11 @@ export default class AliHttp { (url.includes('/file/search') || url.includes('/file/list') || url.includes('/file/walk') - || url.includes('/file/scan') - || url.includes('openFile')) - && !resp.body?.code) await Sleep(1000) + || url.includes('/file/scan')) + && !resp.body?.code) await Sleep(2000) else if (AliHttp.HttpCodeBreak(resp.code)) return resp - else if (i == 3) return resp - else await Sleep(1000) + else if (i == 5) return resp + else await Sleep(2000) } return { code: 608, header: '', body: 'NetError PostLost' } as IUrlRespData } @@ -395,8 +407,7 @@ export default class AliHttp { if (url.includes('aliyundrive')) { headers['Content-Type'] = 'application/json' } - if (token && (url.startsWith(this.baseOpenApi) - || url.startsWith('https://openapi.aliyundrive.com'))) { + if (token && url.startsWith(this.baseOpenApi)) { headers['Authorization'] = token.token_type + ' ' + token.access_token_v2 headers['x-request-id'] = v4().toString() headers['x-device-id'] = token.device_id @@ -410,6 +421,7 @@ export default class AliHttp { if (share_token) { headers['x-share-token'] = share_token } + if (url.includes('ali')) headers['content-type'] = 'application/json;charset-utf-8' let timeout = 30000 if (url.includes('/batch')) timeout = 60000 return axios @@ -433,7 +445,7 @@ export default class AliHttp { } static async PostString(url: string, postData: any, user_id: string, share_token: string): Promise { - if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseapi + url + if (!url.startsWith('http') && !url.startsWith('https')) url = AliHttp.baseApi + url for (let i = 0; i <= 5; i++) { const resp = await AliHttp._PostString(url, postData, user_id, share_token) if (AliHttp.HttpCodeBreak(resp.code)) return resp diff --git a/src/aliapi/alimodels.ts b/src/aliapi/alimodels.ts index 1857db4..ea9d4fc 100644 --- a/src/aliapi/alimodels.ts +++ b/src/aliapi/alimodels.ts @@ -222,7 +222,8 @@ export interface IAliShareItem { created_at: string creator: string description: string - display_name: string + display_name: string + display_label: string download_count: number drive_id: string expiration: string @@ -235,13 +236,17 @@ export interface IAliShareItem { save_count: number share_id: string - share_msg: string - share_name: string + share_msg: string + full_share_msg: string + share_name: string share_policy: string share_pwd: string share_url: string status: string - updated_at: string + updated_at: string + + is_share_saved: boolean + share_saved: string } export interface IAliShareAnonymous { diff --git a/src/aliapi/dirfilelist.ts b/src/aliapi/dirfilelist.ts index e899fee..aa7a839 100644 --- a/src/aliapi/dirfilelist.ts +++ b/src/aliapi/dirfilelist.ts @@ -1,4 +1,4 @@ -import { usePanFileStore, useSettingStore } from '../store' +import { usePanFileStore, useResPanFileStore, useSettingStore } from '../store' import TreeStore from '../store/treestore' import DebugLog from '../utils/debuglog' import { OrderDir, OrderFile } from '../utils/filenameorder' @@ -126,7 +126,7 @@ export default class AliDirFileList { } - static async ApiDirFileList(user_id: string, drive_id: string, dirID: string, dirName: string, order: string, type: string = ''): Promise { + static async ApiDirFileList(user_id: string, drive_id: string, dirID: string, dirName: string, order: string, type: string = '', openApi = false): Promise { const dir: IAliFileResp = { items: [], itemsKey: new Set(), @@ -196,7 +196,12 @@ export default class AliDirFileList { dir.itemsTotal = total }) } - isGet = await AliDirFileList._ApiDirFileListOnePageOpenApi(orders[0], orders[1], dir, type, pageIndex) + if (openApi) { + isGet = await AliDirFileList._ApiDirFileListOnePageOpenApi(orders[0], orders[1], dir, type, pageIndex) + } else { + isGet = await AliDirFileList._ApiDirFileListOnePage(orders[0], orders[1], dir, type, pageIndex) + } + } if (!isGet) { @@ -265,7 +270,7 @@ export default class AliDirFileList { private static async _ApiDirFileListCount(dir: IAliFileResp, type: string): Promise { - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' const postData = { drive_id: dir.m_drive_id, marker: '', @@ -364,7 +369,7 @@ export default class AliDirFileList { } static async _ApiSearchFileListOnePage(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise { - let url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + let url = 'adrive/v1.0/openFile/search' let query = '' if (dir.dirID.startsWith('color')) { const color = dir.dirID.substring('color'.length).split(' ')[0].replace('#', 'c') @@ -439,7 +444,7 @@ export default class AliDirFileList { } static async _ApiSearchFileListCount(dir: IAliFileResp): Promise { - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' let query = '' if (dir.dirID.startsWith('color')) { @@ -523,7 +528,7 @@ export default class AliDirFileList { } static async _ApiVideoListRecent(orderby: string, order: string, dir: IAliFileResp, pageIndex: number): Promise { - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/video/recentList' + const url = 'adrive/v1.0/openFile/video/recentList' const postData = {} const resp = await AliHttp.Post(url, postData, dir.m_user_id, '') return AliDirFileList._FileListOnePage(orderby, order, dir, resp, pageIndex) @@ -639,7 +644,12 @@ export default class AliDirFileList { if (pageIndex >= 0 && type == '') { const pan = usePanFileStore() - if (pan.DriveID == dir.m_drive_id) pan.mSaveDirFileLoadingPart(pageIndex, dirPart, dir.itemsTotal || 0) + if (pan.DriveID == dir.m_drive_id) { + pan.mSaveDirFileLoadingPart(pageIndex, dirPart, dir.itemsTotal || 0) + } else { + const resPan = useResPanFileStore(); + resPan.mSaveDirFileLoadingPart(pageIndex, dirPart, dir.itemsTotal || 0) + } } if (dirPart.next_marker == 'cancel') dir.next_marker = 'cancel' if (isVideo && dir.items.length >= 500) dir.next_marker = '' @@ -672,6 +682,7 @@ export default class AliDirFileList { for (let i = 0, maxi = file_idList.length; i < maxi; i++) { list.set(file_idList[i], { dirID: file_idList[i], size: 0 }) if (i > 0) postData = postData + ',' + let id = file_idList[i].includes('root') ? 'root' : file_idList[i] const data2 = { body: { drive_id: drive_id, @@ -681,7 +692,7 @@ export default class AliDirFileList { order_by: 'size DESC' }, headers: { 'Content-Type': 'application/json' }, - id: file_idList[i], + id: id, method: 'POST', url: '/file/search' } diff --git a/src/aliapi/dirlist.ts b/src/aliapi/dirlist.ts index 51f03d6..ff96c07 100644 --- a/src/aliapi/dirlist.ts +++ b/src/aliapi/dirlist.ts @@ -264,6 +264,13 @@ export default class AliDirList { for (let i = 0, maxi = dirList.length; i < maxi; i++) { if (i > 0) postData = postData + ',' const query = 'type="folder" and ' + dirList[i].dirID + let id = dirList[i].dirID + .replaceAll('"', '') + .replaceAll(' ', '') + .replaceAll('-', '') + .replaceAll(':', '') + .replaceAll(',', '') + if (id.includes('root')) id = 'root' const data2 = { body: { drive_id: drive_id, @@ -273,7 +280,7 @@ export default class AliDirList { fields: 'thumbnail' }, headers: { 'Content-Type': 'application/json' }, - id: dirList[i].dirID.replaceAll('"', '').replaceAll(' ', '').replaceAll('-', '').replaceAll(':', '').replaceAll(',', ''), + id: id, method: 'POST', url: '/file/search' } @@ -341,7 +348,7 @@ export default class AliDirList { } static async _ApiDirFileListInfo(user_id: string, drive_id: string) { - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' const postData = { drive_id: drive_id, marker: '', diff --git a/src/aliapi/file.ts b/src/aliapi/file.ts index d2b9fa0..145d8ac 100644 --- a/src/aliapi/file.ts +++ b/src/aliapi/file.ts @@ -148,7 +148,7 @@ export default class AliFile { if (!user_id || !drive_id || !file_id) return undefined const url = 'adrive/v1.0/openFile/getVideoPreviewPlayInfo' - const postData = { drive_id: drive_id, file_id: file_id, category: 'live_transcoding', template_id: '', get_subtitle_info: true, url_expire_sec: 14400 } + const postData = { drive_id: drive_id, file_id: file_id, category: 'live_transcoding', template_id: '', get_subtitle_info: true, url_expire_sec: 14400, with_play_cursor:true } const resp = await AliHttp.Post(url, postData, user_id, '') if (resp.body.code == 'VideoPreviewWaitAndRetry') { @@ -168,6 +168,7 @@ export default class AliFile { height: 0, url: '', duration: 0, + play_cursor: 0, urlQHD: '', urlFHD: '', urlHD: '', @@ -201,6 +202,7 @@ export default class AliFile { data.width = resp.body.video_preview_play_info?.meta?.width || 0 data.height = resp.body.video_preview_play_info?.meta?.height || 0 data.expire_sec = GetOssExpires(data.url) + data.play_cursor = Number(resp.body.play_cursor) return data } else { DebugLog.mSaveWarning('ApiVideoPreviewUrl err=' + file_id + ' ' + (resp.code || '')) @@ -482,7 +484,7 @@ export default class AliFile { static async ApiUpdateVideoTimeOpenApi(user_id: string, drive_id: string, file_id: string, play_cursor: number): Promise { if (!useSettingStore().uiAutoPlaycursorVideo) return if (!user_id || !drive_id || !file_id) return undefined - const upateCursorUrl = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/video/updateRecord' + const upateCursorUrl = 'adrive/v1.0/openFile/video/updateRecord' const postData = { "drive_id": drive_id, "file_id": file_id, diff --git a/src/aliapi/filecmd.ts b/src/aliapi/filecmd.ts index e394e5e..ff55e85 100644 --- a/src/aliapi/filecmd.ts +++ b/src/aliapi/filecmd.ts @@ -13,7 +13,7 @@ export default class AliFileCmd { const result = { file_id: '', error: '新建文件夹失败' } if (!user_id || !drive_id || !parent_file_id) return result const url = 'adrive/v1.0/openFile/create' - const pathSplitor = creatDirName.split('/'); + const pathSplitor = creatDirName.split(path.sep); if (pathSplitor.length > 1) { let parentFileId = parent_file_id; for (let i = 0; i < pathSplitor.length; i++) { @@ -22,6 +22,14 @@ export default class AliFileCmd { parentFileId = resp.file_id } return { file_id:parentFileId, error: '' } + } else if (path.sep === '\\' && creatDirName.split('/').length > 1) { + let parentFileId = parent_file_id; + for (let i = 0; i < creatDirName.split('/').length; i++) { + const folderName = creatDirName.split('/')[i]; + const resp = await AliFileCmd.ApiCreatNewForder(user_id, drive_id, parentFileId, folderName); + parentFileId = resp.file_id + } + return { file_id:parentFileId, error: '' } } const postData = JSON.stringify({ diff --git a/src/aliapi/models.ts b/src/aliapi/models.ts index c696c80..ccc776a 100644 --- a/src/aliapi/models.ts +++ b/src/aliapi/models.ts @@ -32,6 +32,7 @@ export interface IVideoPreviewUrl { width: number height: number urlQHD: string + play_cursor: number urlFHD: string urlHD: string @@ -154,11 +155,13 @@ export interface IAliGetAlbumModel { } export interface IAliUserDriveDetails { - drive_used_size: number - drive_total_size: number - default_drive_used_size: number album_drive_used_size: number + backup_drive_used_size: number + default_drive_used_size: number + drive_total_size: number + drive_used_size: number note_drive_used_size: number + resource_drive_used_size: number sbox_drive_used_size: number share_album_drive_used_size: number } diff --git a/src/aliapi/server.tsx b/src/aliapi/server.tsx index 5da0369..fd98ff9 100644 --- a/src/aliapi/server.tsx +++ b/src/aliapi/server.tsx @@ -1,11 +1,11 @@ import { B64decode, b64decode, humanSize } from '../utils/format' +import { getPkgVersion } from '../utils/utils' import axios, { AxiosResponse } from 'axios' -import Config from '../utils/config' import message from '../utils/message' import { IShareSiteModel, useServerStore } from '../store' import { Modal, Button, Space } from '@arco-design/web-vue' import { h } from 'vue' -import { getAppNewPath, getResourcesPath, openExternal, getUserDataPath } from '../utils/electronhelper' +import { getAppNewPath, getResourcesPath, getUserDataPath, openExternal } from '../utils/electronhelper' import ShareDAL from '../share/share/ShareDAL' import DebugLog from '../utils/debuglog' import { writeFile, rmSync, existsSync, readFileSync } from 'fs' @@ -25,7 +25,7 @@ export default class ServerHttp { static baseApi = b64decode('aHR0cDovLzEyMS41LjE0NC44NDo1MjgyLw==') static async PostToServer(postData: any): Promise { - postData.appVersion = Config.appVersion + postData.appVersion = getPkgVersion() const str = JSON.stringify(postData) if (window.postdataFunc) { let enstr = '' @@ -86,7 +86,7 @@ export default class ServerHttp { ) } - static configUrl = b64decode('aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9nYW96aGFuZ21pbi9zdGF0aWNSZXNvdXJjZS9jb250ZW50cy9pbWFnZXMvc2hhcmVfY29uZmlnLmpzb24=') + static configUrl = b64decode('aHR0cHM6Ly9naXRlZS5jb20vemhhbm5hby9yZXNvdXJjZS9yYXcvbWFzdGVyL2ltYWdlcy9zaGFyZV9jb25maWcuanNvbg==') static updateUrl = b64decode('aHR0cHM6Ly9hcGkuZ2l0aHViLmNvbS9yZXBvcy9nYW96aGFuZ21pbi9hbGl5dW5wYW4vcmVsZWFzZXMvbGF0ZXN0') static async CheckConfigUpgrade(): Promise { @@ -117,6 +117,29 @@ export default class ServerHttp { }) } + static compareVersions(version1: string, version2: string): number { + // Split version strings into arrays of numbers + const v1Parts = version1.split('.').map(Number); + const v2Parts = version2.split('.').map(Number); + + // Pad the shorter version with zeros to make their lengths equal + const maxLength = Math.max(v1Parts.length, v2Parts.length); + v1Parts.push(...Array(maxLength - v1Parts.length).fill(0)); + v2Parts.push(...Array(maxLength - v2Parts.length).fill(0)); + + // Compare each part of the version numbers + for (let i = 0; i < maxLength; i++) { + if (v1Parts[i] > v2Parts[i]) { + return 1; + } else if (v1Parts[i] < v2Parts[i]) { + return -1; + } + } + + // Version numbers are equal + return 0; + } + static async CheckUpgrade(showMessage: boolean = true): Promise { axios .get(ServerHttp.updateUrl, { @@ -158,7 +181,7 @@ export default class ServerHttp { } } if (tagName) { - let configVer = Config.appVersion.replaceAll('v', '').trim() + let configVer = getPkgVersion().replaceAll('v', '').trim() if (process.platform !== 'linux') { let localVersion = getResourcesPath('localVersion') if (localVersion && existsSync(localVersion)) { @@ -171,7 +194,7 @@ export default class ServerHttp { if (updateData.url) { verUrl = 'https://ghproxy.com/' + updateData.url } - if (remoteVer !== configVer) { + if (this.compareVersions(remoteVer, configVer) > 0) { Modal.confirm({ mask: true, alignCenter: true, @@ -260,8 +283,8 @@ export default class ServerHttp { }) ]) }) - } else if (showMessage && remoteVer <= configVer) { - message.info('已经是最新版 ' + tagName, 6) + } else if (showMessage ) { + message.info('已经是最新版 ', 6) } } }) diff --git a/src/aliapi/transfershare.ts b/src/aliapi/transfershare.ts new file mode 100644 index 0000000..cbaa0a9 --- /dev/null +++ b/src/aliapi/transfershare.ts @@ -0,0 +1,44 @@ +import DebugLog from '../utils/debuglog' +import { humanExpiration } from '../utils/format' +import AliHttp from './alihttp' +import { IAliShareItem } from './alimodels' + +export default class AliTransferShare { + + static async ApiCreatTransferShare(user_id: string, drive_id: string, file_id_list: string[]): Promise { + if (!user_id || !drive_id || file_id_list.length == 0) return '快传分享链接失败数据错误' + const drive_file_list = [] + for (let i = 0, maxi = file_id_list.length; i < maxi; i++) { + drive_file_list.push({ drive_id, file_id: file_id_list[i] }) + } + const url = 'adrive/v1/share/create' + const postData = JSON.stringify({ drive_file_list }) + const resp = await AliHttp.Post(url, postData, user_id, '') + if (AliHttp.IsSuccess(resp.code)) { + const item = resp.body as IAliShareItem + const add: IAliShareItem = Object.assign({}, item, { first_file: undefined, icon: 'iconwenjian' }) + add.share_msg = humanExpiration(item.expiration) + return add + } else { + DebugLog.mSaveWarning('ApiCreatShare err=' + (resp.code || '')) + } + if (resp.body?.code.startsWith('UserPunished')) return '账号分享行为异常,无法分享' + else if (resp.body?.code == 'InvalidParameter.FileIdList') return '选择文件过多,无法分享' + else if (resp.body?.code == 'CreateShareCountExceed') return '今日快传已达上限,请明天再来' + else if (resp.body?.message && resp.body.message.indexOf('size of file_id_list') >= 0) return '选择文件过多,无法分享' + else if (resp.body?.code) return resp.body.code.toString() + else return '创建快传链接失败' + } + + static async ApiCancelTransferShareBatch(user_id: string, share_idList: string[]): Promise { + const url = 'adrive/v1/share/cancel' + const success: string[] = [] + for (let share_id of share_idList) { + let resp = await AliHttp.Post(url, { share_id }, user_id, '') + if (AliHttp.IsSuccess(resp.code)) { + success.push(share_id) + } + } + return success + } +} diff --git a/src/aliapi/transfersharelist.ts b/src/aliapi/transfersharelist.ts new file mode 100644 index 0000000..f2dec1d --- /dev/null +++ b/src/aliapi/transfersharelist.ts @@ -0,0 +1,119 @@ +import DebugLog from '../utils/debuglog' +import { humanDateTime, humanExpiration } from '../utils/format' +import message from '../utils/message' +import AliHttp, { IUrlRespData } from './alihttp' +import { IAliShareItem } from './alimodels' + +export interface IAliShareResp { + items: IAliShareItem[] + itemsKey: Set + next_marker: string + m_time: number + m_user_id: string +} + +export interface IAliShareResp { + items: IAliShareItem[] + itemsKey: Set + next_marker: string + m_time: number + m_user_id: string +} + +export default class AliTransferShareList { + static async ApiTransferShareListAll(user_id: string): Promise { + const dir: IAliShareResp = { + items: [], + itemsKey: new Set(), + next_marker: '', + m_time: 0, + m_user_id: user_id + } + await AliTransferShareList.ApiShareListOnePage(user_id, dir) + return dir + } + static async ApiShareListOnePage(user_id: string, dir: IAliShareResp): Promise { + const url = 'adrive/v1/share/list' + const postData = { + limit: 20, + order_by: 'created_at', + order_direction: 'DESC' + } + const resp = await AliHttp.Post(url, postData,user_id,'') + return AliTransferShareList._TransferShareListOnePage(user_id, dir, resp) + } + + static _TransferShareListOnePage(user_id: string, dir: IAliShareResp, resp: IUrlRespData): boolean { + try { + if (AliHttp.IsSuccess(resp.code)) { + const timeNow = new Date().getTime() + for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) { + const item = resp.body.items[i] as IAliShareItem + if (dir.itemsKey.has(item.share_id)) continue + let icon = 'iconwenjian' + let first_file: any = item.share_id && AliTransferShareList.ApiTransferShareFileStatus(user_id, item.share_id) + const add = Object.assign({}, item, { first_file, icon }) as IAliShareItem + if (!add.full_share_msg) add.full_share_msg = '' + if (!add.share_msg) add.share_msg = '' + if (!add.share_name) add.share_name = 'share_name' + if (!add.is_share_saved) add.share_saved = '未保存' + else add.share_saved = '已保存' + if (!add.expired) add.expired = false + if (item.created_at) { + add.created_at = humanDateTime(new Date(item.created_at).getTime()) + } else { + add.created_at = '' + } + add.share_msg = humanExpiration(item.expiration, timeNow) + if (item.status == 'forbidden') add.share_msg = '分享违规' + dir.items.push(add) + dir.itemsKey.add(add.share_id) + } + return true + } else if (resp.code == 404) { + dir.items.length = 0 + return true + } else if (resp.body && resp.body.code) { + dir.items.length = 0 + message.warning('列出分享列表出错' + resp.body.code, 2) + return false + } else { + DebugLog.mSaveWarning('_ShareListOnePage err=' + (resp.code || '')) + } + } catch (err: any) { + DebugLog.mSaveDanger('_ShareListOnePage', err) + } + return false + } + + static async ApiTransferShareFileStatus(user_id: string, share_id: string): Promise { + const url = 'adrive/v1/share/get' + const postData = { share_id } + const resp = await AliHttp.Post(url, postData, user_id, '') + if (AliHttp.IsSuccess(resp.code)) { + return resp.body as IAliShareItem + } else { + DebugLog.mSaveWarning('ApiTransferShareFileStatus err=' + (resp.code || '')) + } + return false + } + + static async ApiTransferShareListUntilShareID(user_id: string, share_id: string, limit: number): Promise { + const url = 'adrive/v1/share/list' + const postData = { + limit: limit || 20, + order_by: 'created_at', + order_direction: 'DESC' + } + const resp = await AliHttp.Post(url, postData, user_id, '') + try { + if (AliHttp.IsSuccess(resp.code)) { + for (let i = 0, maxi = resp.body.items.length; i < maxi; i++) { + const item = resp.body.items[i] as IAliShareItem + if (item.share_id == share_id) return true + } + } + } catch {} + return false + } +} diff --git a/src/aliapi/user.ts b/src/aliapi/user.ts index 248f8c8..51c6581 100644 --- a/src/aliapi/user.ts +++ b/src/aliapi/user.ts @@ -11,6 +11,9 @@ import Config from "../utils/config"; export const TokenReTimeMap = new Map() export const TokenLockMap = new Map() + +export const TokenReTimeMapV2 = new Map() +export const TokenLockMapV2 = new Map() export const SessionLockMap = new Map() export const SessionReTimeMap = new Map() export default class AliUser { @@ -55,13 +58,18 @@ export default class AliUser { const postData = { refresh_token: token.refresh_token_v2, grant_type: 'refresh_token', - client_secret: 'a3d3a7036fa9417399eef14891f6084f', - client_id: 'e90a7b360e894c60b7b314579f42827d' + client_secret: '', + client_id: '' } return await AliHttp.Post(Config.accessTokenUrl, postData, '', '') - } +// { +// "token_type": "Bearer", +// "access_token": "", +// "refresh_token": "", +// "expires_in": 7200 +// } static async ApiTokenRefreshAccountV2_TMP(token: ITokenInfo): Promise { const postData = { refresh_token: token.refresh_token_v2, @@ -70,8 +78,63 @@ export default class AliUser { return await AliHttp._Post(Config.tmpUrl, postData, '', '') } - static async ApiTokenRefreshAccount(token: ITokenInfo, showMessage: boolean): Promise { + static async ApiRefreshAccessTokenV2(token: ITokenInfo, showMessage: boolean, force: boolean = false): Promise { + if (!token.refresh_token_v2) return false + if (!force && token.expires_in_v2 && token.expires_in_v2 > Date.now()) { + return true + } + if (force) { + TokenLockMapV2.delete(token.user_id) + TokenReTimeMapV2.delete(token.user_id) + } + while (true) { + const lock = TokenLockMapV2.has(token.user_id) + if (lock) await Sleep(1000) + else break + } + TokenLockMapV2.set(token.user_id, Date.now()) + const time = TokenReTimeMapV2.get(token.user_id) || 0 + if (Date.now() - time < 1000 * 60 * 5) { + TokenLockMapV2.delete(token.user_id) + return true + } + + const resp = await this.ApiTokenRefreshAccountV2(token) + TokenLockMapV2.delete(token.user_id) + if (AliHttp.IsSuccess(resp.code)) { + TokenReTimeMapV2.set(token.user_id, Date.now()) + token.tokenfrom = 'account' + + token.refresh_token_v2 = resp.body.refresh_token + token.access_token_v2 = resp.body.access_token + token.expires_in_v2 = Date.now() + resp.body.expires_in * 1000 + token.token_type_v2 = resp.body.token_type + + UserDAL.SaveUserToken(token) + return true + } else { + if (resp.body?.code != 'InvalidParameter.RefreshToken') { + DebugLog.mSaveWarning('ApiTokenRefreshAccountV2 err=' + (resp.code || '') + ' ' + (resp.body?.code || '')) + } + if (showMessage) { + message.error('刷新账号[' + token.user_name + '] v2 token 失败,需要重新登录') + UserDAL.UserLogOff(token.user_id) + } else { + UserDAL.UserClearFromDB(token.user_id) + } + } + return false + } + + static async ApiRefreshAccessTokenV1(token: ITokenInfo, showMessage: boolean, force: boolean = false): Promise { if (!token.refresh_token) return false + if (!force && token.expires_in && token.expires_in > Date.now()) { + return true + } + if (force) { + TokenLockMap.delete(token.user_id) + TokenReTimeMap.delete(token.user_id) + } while (true) { const lock = TokenLockMap.has(token.user_id) if (lock) await Sleep(1000) @@ -88,26 +151,18 @@ export default class AliUser { const postData = { refresh_token: token.refresh_token, grant_type: 'refresh_token' } const resp = await AliHttp.Post(url, postData, '', '') - const respV2 = await this.ApiTokenRefreshAccountV2_TMP(token) TokenLockMap.delete(token.user_id) - if (AliHttp.IsSuccess(resp.code) && AliHttp.IsSuccess(respV2.code)) { + if (AliHttp.IsSuccess(resp.code)) { TokenReTimeMap.set(resp.body.user_id, Date.now()) token.tokenfrom = 'account' token.access_token = resp.body.access_token token.refresh_token = resp.body.refresh_token - token.expires_in = resp.body.expires_in + token.expires_in = Date.now() + resp.body.expires_in * 1000 token.token_type = resp.body.token_type - - token.refresh_token_v2 = respV2.body.refresh_token - token.access_token_v2 = respV2.body.access_token - token.expires_in_v2 = respV2.body.expires_in - token.token_type_v2 = respV2.body.token_type - token.user_id = resp.body.user_id token.user_name = resp.body.user_name token.avatar = resp.body.avatar token.nick_name = resp.body.nick_name - token.default_drive_id = resp.body.default_drive_id token.default_sbox_drive_id = resp.body.default_sbox_drive_id token.role = resp.body.role token.status = resp.body.status @@ -130,7 +185,7 @@ export default class AliUser { DebugLog.mSaveWarning('ApiTokenRefreshAccount err=' + (resp.code || '') + ' ' + (resp.body?.code || '')) } if (showMessage) { - message.error('刷新账号[' + token.user_name + '] token 失败,需要重新登录') + message.error('刷新账号[' + token.user_name + '] v1 token 失败,需要重新登录') UserDAL.UserLogOff(token.user_id) } else { UserDAL.UserClearFromDB(token.user_id) @@ -156,6 +211,12 @@ export default class AliUser { } if (AliHttp.IsSuccess(resp.code)) { token.spu_id = '' + token.phone = resp.body.phone + token.backup_drive_id = resp.body.backup_drive_id || ''; + token.resource_drive_id = resp.body.resource_drive_id || '' + if (token.backup_drive_id === '') { + token.backup_drive_id = resp.body.default_drive_id + } token.is_expires = resp.body.status === 'enabled' token.name = resp.body.nick_name===''?resp.body.phone:resp.body.nick_name return true @@ -215,15 +276,18 @@ export default class AliUser { static async ApiUserVip(token: ITokenInfo): Promise { if (!token.user_id) return false - const url = 'adrive/v1.0/user/getVipInfo' + const url = 'https://openapi.aliyundrive.com/v1.0/user/getVipInfo' const postData = {} const resp = await AliHttp.Post(url, postData, token.user_id, '') if (AliHttp.IsSuccess(resp.code)) { + token.vipname = resp.body.identity - if (resp.body.expire && new Date(resp.body.expire * 1000) > new Date()) { - token.vipexpire = humanDateTime(resp.body.expire) + token.vipIcon = '' + if (resp.body.identity === 'member') { + token.vipexpire = '' } else { - token.vipexpire = ''; + token.viplevel = resp.body.level + token.vipexpire = humanDateTime(resp.body.expire) } return true } else { @@ -232,6 +296,32 @@ export default class AliUser { return false } + // static async ApiUserVip(token: ITokenInfo): Promise { + // if (!token.user_id) return false + // const url = 'business/v1.0/users/vip/info' + // + // + // const postData = {} + // const resp = await AliHttp.Post(url, postData, token.user_id, '') + // if (AliHttp.IsSuccess(resp.code)) { + // let vipList = resp.body.vipList || [] + // vipList = vipList.sort((a: any, b: any) => b.expire - a.expire) + // if (vipList.length > 0 && new Date(vipList[0].expire * 1000) > new Date()) { + // token.vipname = vipList[0].name + // token.vipIcon = resp.body.mediumIcon + // token.vipexpire = humanDateTime(vipList[0].expire) + // } else { + // token.vipname = '免费用户' + // token.vipIcon = '' + // token.vipexpire = '' + // } + // return true + // } else { + // DebugLog.mSaveWarning('ApiUserPic err=' + (resp.code || '')) + // } + // return false + // } + static async ApiUserPic(token: ITokenInfo): Promise { if (!token.user_id) return false @@ -250,11 +340,13 @@ export default class AliUser { static async ApiUserDriveDetails(user_id: string): Promise { const detail: IAliUserDriveDetails = { - drive_used_size: 0, - drive_total_size: 0, - default_drive_used_size: 0, album_drive_used_size: 0, + backup_drive_used_size: 0, + default_drive_used_size: 0, + drive_total_size: 0, + drive_used_size: 0, note_drive_used_size: 0, + resource_drive_used_size: 0, sbox_drive_used_size: 0, share_album_drive_used_size: 0 } @@ -263,11 +355,13 @@ export default class AliUser { const postData = '{}' const resp = await AliHttp.Post(url, postData, user_id, '') if (AliHttp.IsSuccess(resp.code)) { - detail.drive_used_size = resp.body.drive_used_size || 0 - detail.drive_total_size = resp.body.drive_total_size || 0 - detail.default_drive_used_size = resp.body.default_drive_used_size || 0 detail.album_drive_used_size = resp.body.album_drive_used_size || 0 + detail.backup_drive_used_size = resp.body.backup_drive_used_size || 0 + detail.default_drive_used_size = resp.body.default_drive_used_size || 0 + detail.drive_total_size = resp.body.drive_total_size || 0 + detail.drive_used_size = resp.body.drive_used_size || 0 detail.note_drive_used_size = resp.body.note_drive_used_size || 0 + detail.resource_drive_used_size = resp.body.resource_drive_used_size || 0 detail.sbox_drive_used_size = resp.body.sbox_drive_used_size || 0 detail.share_album_drive_used_size = resp.body.share_album_drive_used_size || 0 } else { @@ -280,9 +374,9 @@ export default class AliUser { if (!user_id) return 0 const token = await UserDAL.GetUserTokenFromDB(user_id) if (!token) return 0 - const url = 'https://openapi.aliyundrive.com/adrive/v1.0/openFile/search' + const url = 'adrive/v1.0/openFile/search' const postData = { - drive_id: token?.default_drive_id, + drive_id: token?.backup_drive_id, marker: '', limit: 1, all: false, diff --git a/src/aliapi/utils.ts b/src/aliapi/utils.ts index de70c69..8480bc7 100644 --- a/src/aliapi/utils.ts +++ b/src/aliapi/utils.ts @@ -18,7 +18,7 @@ export function GetDriveID(user_id: string, drive: Drive): string { if (token) { switch (drive) { case 'pan': - return token.default_drive_id + return token.backup_drive_id case 'pic': return token.pic_drive_id case 'safe': @@ -33,7 +33,7 @@ export function GetDriveID2(token: ITokenInfo, driveName: string): string { if (token) { switch (driveName) { case 'pan': - return token.default_drive_id + return token.backup_drive_id case 'pic': return token.pic_drive_id case 'safe': @@ -43,6 +43,21 @@ export function GetDriveID2(token: ITokenInfo, driveName: string): string { return driveName } +export function GetDriveType(user_id: string, drive_id: string): any { + const token = UserDAL.GetUserToken(user_id) + if (token) { + switch (drive_id) { + case token.backup_drive_id: + return { title: '备份盘', key: 'backup_root' } + case token.resource_drive_id: + return { title: '资源盘', key: 'resource_root' } + case token.default_sbox_drive_id: + return { title: '安全盘', key: 'safe_root' } + } + } + return { title: '', key: '' } +} + export function GetSignature(nonce: number, user_id: string, deviceId: string) { const toHex = (bytes: Uint8Array) => { const hashArray = Array.from(bytes) // convert buffer to byte array diff --git a/src/assets/style.css b/src/assets/style.css index ec5ba87..312b792 100644 --- a/src/assets/style.css +++ b/src/assets/style.css @@ -1,5 +1,5 @@ * { - font-family: "PingFang SC", 'Microsoft YaHei', Arial, serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Lato, Roboto, 'PingFang SC', 'Microsoft YaHei', sans-serif; user-select: none; -moz-user-select: none; -webkit-user-select: none; diff --git a/src/axios.ts b/src/axios.ts new file mode 100644 index 0000000..97dff06 --- /dev/null +++ b/src/axios.ts @@ -0,0 +1,50 @@ +import axios, { AxiosInstance } from 'axios' + +declare module '@vue/runtime-core' { + interface ComponentCustomProperties { + $axios: AxiosInstance; + } +} +let QPS = 30 +let OFFSET = 0 +let INTERVAL = 1000 +const qpsMap = new Map() + +const qpsController = () => async (config: any) => { + if (config.url.indexOf('api.aliyundrive.com') < 0 + && config.url.indexOf('openapi.aliyundrive.com') < 0) return config + if (config.url.indexOf('openapi.aliyundrive.com') < 0) { + QPS = 2 + OFFSET = 2500 + INTERVAL = 1000 + } else { + QPS = 30 + OFFSET = 0 + INTERVAL = 1000 + } + const now = new Date().getTime() + let { count, ts } = qpsMap.get(config.url) || { count: 1, ts: now } + if ((now / INTERVAL) >> 0 <= (ts / INTERVAL) >> 0) { + if (count < QPS) { + count++ + } else { + ts = INTERVAL * Math.ceil(ts / INTERVAL + 1) + count = 1 + } + } else { + ts = now + count = 1 + } + qpsMap.set(config.url, { count, ts }) + let sleep = ts - now + sleep = sleep > 0 ? sleep + OFFSET : 0 + if (sleep > 0) { + await new Promise((resolve) => setTimeout(() => resolve(), sleep)) + } + return config + } + +axios.interceptors.request.use(qpsController()) +axios.defaults.withCredentials = false + +export default axios \ No newline at end of file diff --git a/src/down/DownDAL.ts b/src/down/DownDAL.ts index 5eb1e21..0686e1c 100644 --- a/src/down/DownDAL.ts +++ b/src/down/DownDAL.ts @@ -1,19 +1,22 @@ import { IAliGetFileModel } from '../aliapi/alimodels' import path from 'path' import TreeStore from '../store/treestore' -import { useUserStore, useSettingStore, useDownedStore, useDowningStore, useFootStore } from '../store' +import { useDownedStore, useDowningStore, useFootStore, useSettingStore, useUserStore } from '../store' import { ClearFileName } from '../utils/filehelper' import { AriaAddUrl, - AriaConnect, AriaDeleteList, + AriaConnect, + AriaDeleteList, AriaGetDowningList, - AriaHashFile, AriaStopList, + AriaHashFile, + AriaStopList, FormatAriaError, IsAria2cRemote } from '../utils/aria2c' import { humanSize, humanSizeSpeed } from '../utils/format' import { Howl } from 'howler' import DBDown from '../utils/dbdown' +import fsPromises from 'fs/promises' export interface IStateDownFile { DownID: string @@ -62,7 +65,6 @@ export interface IStateDownInfo { crc64: string } - export interface IAriaDownProgress { gid: string status: string @@ -144,8 +146,9 @@ export default class DownDAL { const userID = useUserStore().user_id const settingStore = useSettingStore() - if (savePath.endsWith('/')) savePath = savePath.substr(0, savePath.length - 1) - if (savePath.endsWith('\\')) savePath = savePath.substr(0, savePath.length - 1) + if (savePath.endsWith('/') || savePath.endsWith('\\')) { + savePath = savePath.substr(0, savePath.length - 1) + } const downlist: IStateDownFile[] = [] const dTime = Date.now() @@ -223,7 +226,6 @@ export default class DownDAL { if (downitem.Info.ariaRemote && !downitem.Info.isDir) downitem.Info.icon = 'iconfont iconcloud-download' downlist.push(downitem) } - useDowningStore().mAddDownload({ downlist }) } @@ -232,109 +234,59 @@ export default class DownDAL { */ static async aSpeedEvent() { const downingStore = useDowningStore() + const downedStore = useDownedStore() const settingStore = useSettingStore() const isOnline = await AriaConnect() - if (isOnline) { - await AriaGetDowningList().catch(() => { - // - }) + if (isOnline && downingStore.ListDataRaw.length) { + await AriaGetDowningList() - const DowningList = useDowningStore().ListDataRaw - let downingCount = 0 const ariaRemote = IsAria2cRemote() - for (let j = 0; j < DowningList.length; j++) { - if (DowningList[j].Info.ariaRemote != ariaRemote) continue - if (DowningList[j].Down.IsDowning) { - downingCount++ - } - if (DowningList[j].Down.IsCompleted && DowningList[j].Down.DownState === '已完成') { - downingStore.mSaveToDowned(DowningList[j].DownID) - j-- - } + const DowningList: IStateDownFile[] = useDowningStore().ListDataRaw + const timeThreshold = Date.now() - 60 * 1000 + const downFileMax = settingStore.downFileMax + const shouldSkipDown = (Down: any) => { + return ( + Down.IsCompleted || + Down.IsStop || + Down.IsDowning || + (Down.IsFailed && timeThreshold <= Down.AutoTry) + ) } - const time = Date.now() - 60 * 1000 - for (let j = 0; j < DowningList.length; j++) { - if (downingCount >= settingStore.downFileMax) break - if (DowningList[j].Info.ariaRemote != ariaRemote) continue - const DownID = DowningList[j].DownID - const down = DowningList[j].Down - if (down.IsCompleted == false && down.IsStop == false && down.IsDowning == false) { - if (down.IsFailed == false || time > down.AutoTry) { - downingCount += 1 - downingStore.mUpdateDownState({ - DownID, - IsDowning: true, - DownSpeedStr: '', - DownState: '解析中', - DownTime: Date.now(), - FailedCode: 0, - FailedMessage: '' - }) - - AriaAddUrl(DowningList[j]).then((ret) => { - if (ret == 'downed') { - downingStore.mUpdateDownState({ - DownID, - IsDowning: true, - IsCompleted: true, - DownProcess: 100, - DownSpeedStr: '', - DownState: '已完成', - AutoTry: 0, - IsFailed: false, - IsStop: false, - FailedCode: 0, - FailedMessage: '' - }) - } else if (ret == 'success') { - downingStore.mUpdateDownState({ - DownID, - IsDowning: true, - IsCompleted: false, - DownSpeedStr: '', - DownState: '下载中', - AutoTry: 0, - IsFailed: false, - IsStop: false, - FailedCode: 0, - FailedMessage: '' - }) - } else if (ret == '已暂停') { - console.log('已暂停') - downingStore.mUpdateDownState({ - DownID, - IsDowning: false, - IsCompleted: false, - DownSpeedStr: '', - DownState: '已暂停', - AutoTry: 0, - IsFailed: false, - IsStop: true, - FailedCode: 0, - FailedMessage: '已暂停' - }) - } else { - downingStore.mUpdateDownState({ - DownID, - IsDowning: false, - IsCompleted: false, - DownSpeedStr: '', - DownState: '已出错', - AutoTry: Date.now(), - IsFailed: true, - IsStop: false, - FailedCode: 504, - FailedMessage: ret - }) - } - }) + let downingCount = DowningList.filter((down: any) => down.Down.IsDowning).length + for (let i = 0; i < DowningList.length; i++) { + const DownItem = DowningList[i] + const { DownID, Info, Down } = DownItem + if (Info.ariaRemote !== ariaRemote) continue + if (Down.IsCompleted && Down.DownState === '已完成') { + // 将下载标记为已完成并添加到列表以供稍后处理 + const completedDownId = `${Date.now()}_${Down.DownTime}` + // 删除已完成的下载并更新数据库 + DowningList.splice(i, 1) + DBDown.deleteDowning(DownID) + // 将已完成的下载添加到下载文件列表中 + const downedData = JSON.parse(JSON.stringify({ DownID: completedDownId, Down, Info })) + downedStore.ListDataRaw.unshift({ DownID: completedDownId, Down, Info }) + downedStore.mRefreshListDataShow(true) + DBDown.saveDowned(completedDownId, downedData) + if (downedStore.ListSelected.has(completedDownId)) { + downedStore.ListSelected.delete(completedDownId) } + // 移除Aria2已完成的任务 + await AriaDeleteList([Info.GID]) + i-- + } else if (downingCount < downFileMax && !shouldSkipDown(Down)) { + downingCount++ + downingStore.mUpdateDownState(DownItem, 'start') + let state = await AriaAddUrl(DownItem) + downingStore.mUpdateDownState(DownItem, state) } } + } else { + useFootStore().mSaveDownTotalSpeedInfo('') } downingStore.mRefreshListDataShow(true) - useDownedStore().mRefreshListDataShow(true) + downedStore.mRefreshListDataShow(true) } /** @@ -343,164 +295,140 @@ export default class DownDAL { static mSpeedEvent(list: IAriaDownProgress[]) { const downingStore = useDowningStore() const settingStore = useSettingStore() - const DowningList = downingStore.ListDataRaw + const DowningList: IStateDownFile[] = downingStore.ListDataRaw + const ariaRemote = !settingStore.AriaIsLocal - if (list == undefined) list = [] const dellist: string[] = [] + const saveList: IStateDownFile[] = [] + let hasSpeed = 0 - for (let n = 0; n < DowningList.length; n++) { - if (DowningList[n].Down.DownSpeedStr != '') { - const gid = DowningList[n].Info.GID - let isFind = false - for (let m = 0; m < list.length; m++) { - if (list[m].gid != gid) continue - if (list[m].gid == gid && list[m].status == 'active') { - isFind = true - break - } - } - if (!isFind) { - if (DowningList[n].Down.DownState != '已暂停') DowningList[n].Down.DownState = '队列中' - DowningList[n].Down.DownSpeed = 0 - DowningList[n].Down.DownSpeedStr = '' - } - } - } - const ariaRemote = !settingStore.AriaIsLocal - const saveList: IStateDownFile[] = [] - for (let i = 0; i < list.length; i++) { + for (const listItem of list) { try { - const gid = list[i].gid - const isComplete = list[i].status === 'complete' - const isDowning = isComplete || list[i].status === 'active' || list[i].status === 'waiting' - const isStop = list[i].status === 'paused' || list[i].status === 'removed' - const isError = list[i].status === 'error' - - for (let j = 0; j < DowningList.length; j++) { - if (DowningList[j].Info.ariaRemote != ariaRemote) continue - if (DowningList[j].Info.GID == gid) { - const downItem = DowningList[j] - const down = downItem.Down - const totalLength = parseInt(list[i].totalLength) || 0 - down.DownSize = parseInt(list[i].completedLength) || 0 - down.DownSpeed = parseInt(list[i].downloadSpeed) || 0 - down.DownSpeedStr = humanSize(down.DownSpeed) + '/s' - down.DownProcess = Math.floor((down.DownSize * 100) / (totalLength + 1)) % 100 - - down.IsCompleted = isComplete - down.IsDowning = isDowning - down.IsFailed = isError - down.IsStop = isStop - - if (list[i].errorCode && list[i].errorCode != '0') { - down.FailedCode = parseInt(list[i].errorCode) || 0 - down.FailedMessage = FormatAriaError(list[i].errorCode, list[i].errorMessage) - } - - if (isComplete) { - down.DownSize = downItem.Info.size - down.DownSpeed = 0 - down.DownSpeedStr = '' - down.DownProcess = 100 - down.FailedCode = 0 - down.FailedMessage = '' - - down.DownState = '校验中' - const check = AriaHashFile(downItem) - if (check.Check) { - if (useSettingStore().downFinishAudio && !sound.playing()) { - sound.play() - } - downingStore.mUpdateDownState({ - DownID: check.DownID, - DownState: '已完成', - IsFailed: false, - IsDowning: true, - IsStop: false, - IsCompleted: true, - FailedMessage: '' - }) - } else { - downingStore.mUpdateDownState({ - DownID: check.DownID, - DownState: '已出错', - IsFailed: true, - IsDowning: false, - IsStop: true, - IsCompleted: false, - FailedMessage: '移动文件失败,请重新下载' - }) - } - } else if (isStop) { - down.DownState = '已暂停' - down.DownSpeed = 0 - down.DownSpeedStr = '' - down.FailedCode = 0 - down.FailedMessage = '' - } else if (isStop || isError) { - down.DownState = '已出错' - down.DownSpeed = 0 - down.DownSpeedStr = '' - down.AutoTry = Date.now() - if (down.FailedMessage == '') down.FailedMessage = '下载失败' - } else if (isDowning) { - hasSpeed += down.DownSpeed - let lasttime = ((totalLength - down.DownSize) / (down.DownSpeed + 1)) % 356400 - if (lasttime < 1) lasttime = 1 - down.DownState = - down.DownProcess.toString() + - '% ' + - (lasttime / 3600).toFixed(0).padStart(2, '0') + - ':' + - ((lasttime % 3600) / 60).toFixed(0).padStart(2, '0') + - ':' + - (lasttime % 60).toFixed(0).padStart(2, '0') - if (SaveTimeWait > 10) saveList.push(downItem) - } else { - //console.log('update', DowningList[j]); - } - if (isStop || isError) { - dellist.push(gid) + const { gid, status, totalLength, completedLength, downloadSpeed, errorCode, errorMessage } = listItem + const isComplete = status === 'complete' + const isDowning = isComplete || status === 'active' || status === 'waiting' + const isStop = status === 'paused' || status === 'removed' + const isError = status === 'error' + const downingItem: IStateDownFile | undefined = DowningList.find((item) => item.Info.ariaRemote === ariaRemote && item.Info.GID === gid) + if (!downingItem) continue + const { DownID, Down, Info } = downingItem + const totalLengthInt = parseInt(totalLength) || 0 + Down.DownSize = parseInt(completedLength) || 0 + Down.DownSpeed = parseInt(downloadSpeed) || 0 + Down.DownSpeedStr = humanSize(Down.DownSpeed) + '/s' + Down.DownProcess = Math.floor((Down.DownSize * 100) / (totalLengthInt + 1)) % 100 + Down.IsCompleted = isComplete + Down.IsDowning = isDowning + Down.IsFailed = isError + Down.IsStop = isStop + if (errorCode && errorCode !== '0') { + Down.FailedCode = parseInt(errorCode) || 0 + Down.FailedMessage = FormatAriaError(errorCode, errorMessage) + } + if (isComplete) { + downingStore.mUpdateDownState(downingItem, 'valid') + const check = AriaHashFile(downingItem) + if (check.Check) { + if (useSettingStore().downFinishAudio && !sound.playing()) { + sound.play() } - downingStore.mRefreshListDataShow(true) - break + downingStore.mUpdateDownState(downingItem, 'downed') + } else { + downingStore.mUpdateDownState(downingItem, 'error', '移动文件失败,请重新下载') + } + } else if (isStop) { + downingStore.mUpdateDownState(downingItem, 'stop') + dellist.push(gid) + } else if (isError) { + if (!Down.FailedMessage) { + Down.FailedMessage = '下载失败' + } + downingStore.mUpdateDownState(downingItem, 'error', Down.FailedMessage) + dellist.push(gid) + } else if (isDowning) { + hasSpeed += Down.DownSpeed + let lastTime = ((totalLengthInt - Down.DownSize) / (Down.DownSpeed + 1)) % 356400 + if (lastTime < 1) lastTime = 1 + // 进度条 + Down.DownState = + `${Down.DownProcess}% ${(lastTime / 3600).toFixed(0).padStart(2, '0')}:${((lastTime % 3600) / 60) + .toFixed(0) + .padStart(2, '0')}:${(lastTime % 60).toFixed(0).padStart(2, '0')}` + if (SaveTimeWait > 10) { + saveList.push(downingItem) } } + downingStore.mRefreshListDataShow(true) } catch { + // Ignore any errors } } - - if (saveList.length > 0) DBDown.saveDownings(JSON.parse(JSON.stringify(saveList))) - if (dellist.length > 0) AriaDeleteList(dellist).then() - if (SaveTimeWait > 10) SaveTimeWait = 0 - else SaveTimeWait++ + // 存盘时间 + SaveTimeWait = (SaveTimeWait + 1) % 11 + if (saveList.length) { + DBDown.saveDownings(JSON.parse(JSON.stringify(saveList))) + } + if (dellist.length) { + AriaDeleteList(dellist).then() + } useFootStore().mSaveDownTotalSpeedInfo(hasSpeed && humanSizeSpeed(hasSpeed) || '') } - static deleteDowning(isAll: boolean, downingList: IStateDownFile[], gidList: string[]) { - // 处理待删除状态 - const downIDList = downingList - .filter(list => list.Down.DownState === '待删除') - .map(item => item.DownID) - console.log('downIDList', downIDList) - DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList))) - AriaStopList(gidList).then(r => {}) - AriaDeleteList(gidList).then(r => {}) + static async deleteDowning(isAll: boolean, deleteList: IStateDownFile[], gidList: string[]) { + // 处理待删除文件 + if (!isAll) { + const downIDList = deleteList.map(item => item.DownID) + console.log('deleteDowning', deleteList) + await DBDown.deleteDownings(JSON.parse(JSON.stringify(downIDList))) + } else { + await DBDown.deleteDowningAll() + } + // 停止aria2下载任务 + await AriaStopList(gidList) + await AriaDeleteList(gidList) + // 删除临时文件 + for (let downFile of deleteList) { + let downInfo = downFile.Info + if (downInfo.ariaRemote) continue + try { + if (!downInfo.isDir) { + let filePath = path.join(downInfo.DownSavePath, downInfo.name) + let tmpFilePath1 = filePath + '.td.aria2' + let tmpFilePath2 = filePath + '.td' + await fsPromises.rm(tmpFilePath1, { recursive: true }) + await fsPromises.rm(tmpFilePath2, { recursive: true }) + } + } catch (e) { + } + } } - static stopDowning(downList: IStateDownFile[], gidList: string[]) { - DBDown.saveDownings(JSON.parse(JSON.stringify(downList))) - AriaStopList(gidList).then(r => {}) + static async deleteDowned(isAll: boolean, deleteList: IStateDownFile[]) { + if (!isAll) { + // 处理待删除状态 + const downIDList = deleteList + .filter(list => list.Down.DownState === '待删除') + .map(item => item.DownID) + console.log('downedList', deleteList) + await DBDown.deleteDowneds(JSON.parse(JSON.stringify(downIDList))) + } else { + await DBDown.deleteDownedAll() + } + } + + static async stopDowning(downList: IStateDownFile[], gidList: string[]) { + await DBDown.saveDownings(JSON.parse(JSON.stringify(downList))) + await AriaStopList(gidList) } static QueryIsDowning() { const downingList = useDowningStore().ListDataRaw for (let i = 0, maxi = downingList.length; i < maxi; i++) { - if(!downingList[i].Down.IsDowning) { + if (!downingList[i].Down.IsDowning) { return true } } return false } -} +} \ No newline at end of file diff --git a/src/down/DownDowned.vue b/src/down/DownDowned.vue index 12f9bf1..6486481 100644 --- a/src/down/DownDowned.vue +++ b/src/down/DownDowned.vue @@ -1,8 +1,16 @@