diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 935cfe38..13a9c6c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: strategy: fail-fast: false matrix: - os: [windows-latest, macos-12] + os: [windows-latest, macos-latest, ubuntu-latest] # create steps steps: diff --git a/package.json b/package.json index 89bd6f16..1e7506a0 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,8 @@ "name": "mediago", "version": "0.1.0", "description": "mediago", + "author": "caorushizi", + "homepage": "https://downloader.caorushizi.cn/", "private": true, "type": "module", "scripts": { @@ -24,7 +26,6 @@ "lint-staged": "pnpm --recursive -F \"./packages/*\" run lint-staged" }, "keywords": [], - "author": "", "license": "MIT", "config": { "commitizen": { diff --git a/packages/main/app/package.json b/packages/main/app/package.json index 31bb64e3..9c082ca3 100644 --- a/packages/main/app/package.json +++ b/packages/main/app/package.json @@ -3,11 +3,13 @@ "version": "2.2.0", "description": "在线视频下载器", "main": "main/index.js", - "author": "", + "author": "caorushizi", + "homepage": "https://downloader.caorushizi.cn/", "license": "MIT", "dependencies": { "@cliqz/adblocker-electron": "^1.27.3", "@cliqz/adblocker-electron-preload": "^1.27.3", + "better-sqlite3": "^9.3.0", "node-pty": "^1.0.0" }, "devDependencies": { diff --git a/packages/main/assets/icon.png b/packages/main/assets/icon.png index ded15701..15cb50d7 100644 Binary files a/packages/main/assets/icon.png and b/packages/main/assets/icon.png differ diff --git a/packages/main/bin/win32/NO_UPDATE b/packages/main/bin/linux/.gitignore similarity index 100% rename from packages/main/bin/win32/NO_UPDATE rename to packages/main/bin/linux/.gitignore diff --git a/packages/main/bin/linux/BBDown b/packages/main/bin/linux/BBDown new file mode 100755 index 00000000..b49a249e Binary files /dev/null and b/packages/main/bin/linux/BBDown differ diff --git a/packages/main/bin/linux/N_m3u8DL-RE b/packages/main/bin/linux/N_m3u8DL-RE new file mode 100755 index 00000000..6edcbb26 Binary files /dev/null and b/packages/main/bin/linux/N_m3u8DL-RE differ diff --git a/packages/main/bin/linux/ffmpeg b/packages/main/bin/linux/ffmpeg new file mode 100755 index 00000000..c9829e12 Binary files /dev/null and b/packages/main/bin/linux/ffmpeg differ diff --git a/packages/main/bin/win32/N_m3u8DL-CLI.exe b/packages/main/bin/win32/N_m3u8DL-CLI.exe deleted file mode 100644 index 30acb3b9..00000000 Binary files a/packages/main/bin/win32/N_m3u8DL-CLI.exe and /dev/null differ diff --git a/packages/main/bin/win32/N_m3u8DL-RE.exe b/packages/main/bin/win32/N_m3u8DL-RE.exe new file mode 100644 index 00000000..73c8119d Binary files /dev/null and b/packages/main/bin/win32/N_m3u8DL-RE.exe differ diff --git a/packages/main/gulpfile.ts b/packages/main/gulpfile.ts new file mode 100644 index 00000000..4d9f9213 --- /dev/null +++ b/packages/main/gulpfile.ts @@ -0,0 +1 @@ +export { dev, build, release } from "./scripts/index.ts"; diff --git a/packages/main/package.json b/packages/main/package.json index 83eabf33..557448ab 100644 --- a/packages/main/package.json +++ b/packages/main/package.json @@ -5,12 +5,12 @@ "main": "main/index.js", "scripts": { "postinstall": "electron-rebuild -f -w", - "dev": "cross-env NODE_ENV=development VITE_CJS_IGNORE_WARNING=true tsx scripts/dev.ts", - "build": "cross-env NODE_ENV=production tsx scripts/build.ts", - "pack": "cross-env NODE_ENV=development tsx scripts/release.ts", + "dev": "cross-env NODE_ENV=development gulp dev", + "pack": "cross-env NODE_ENV=development gulp release", + "build": "cross-env NODE_ENV=production gulp build", + "release": "cross-env NODE_ENV=production gulp release", "types": "tsc", "types:watch": "tsc -w", - "release": "cross-env NODE_ENV=production tsx scripts/release.ts", "lint-staged": "lint-staged" }, "lint-staged": { @@ -22,6 +22,7 @@ "@electron/rebuild": "^3.6.0", "@rushstack/eslint-patch": "^1.7.0", "@types/fs-extra": "^11.0.4", + "@types/gulp": "^4.0.17", "@types/lodash": "^4.14.202", "@types/mime-types": "^2.1.4", "@types/node": "^20.11.5", @@ -29,14 +30,15 @@ "@types/semver": "^7.5.6", "@typescript-eslint/eslint-plugin": "6.21.0", "@typescript-eslint/parser": "6.21.0", - "chokidar": "^3.5.3", "consola": "^3.2.3", "cross-env": "^7.0.3", + "del": "^7.1.0", "dotenv": "^16.3.2", "electron": "30.0.6", "electron-builder": "^24.9.1", "electron-updater": "^6.1.7", "esbuild": "^0.19.11", + "esbuild-register": "^3.5.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", "eslint-config-standard-with-typescript": "^43.0.1", @@ -44,12 +46,11 @@ "eslint-plugin-n": "16.6.2", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-promise": "6.1.1", + "gulp": "^5.0.0", "prebuild-install": "^7.1.1", "prettier": "3.2.5", "semver": "^7.5.4", - "tsx": "^4.7.1", - "typescript": "^5.3.3", - "vite": "^5.0.12" + "typescript": "^5.3.3" }, "dependencies": { "@cliqz/adblocker-electron": "^1.27.3", @@ -72,6 +73,6 @@ "node-pty": "^1.0.0", "reflect-metadata": "^0.2.1", "strip-ansi": "^7.1.0", - "typeorm": "^0.3.19" + "typeorm": "0.3.17" } } diff --git a/packages/main/scripts/build.ts b/packages/main/scripts/build.ts deleted file mode 100644 index 8af7a13f..00000000 --- a/packages/main/scripts/build.ts +++ /dev/null @@ -1,59 +0,0 @@ -import * as esbuild from "esbuild"; -import { - mainResolve, - loadDotEnvDefined, - copyResource, - removeResource, -} from "./utils"; -import { external } from "./config"; - -const mainDefined = loadDotEnvDefined(); - -removeResource([mainResolve("app/build")]); -removeResource([mainResolve("app/bin")]); - -const path = "build/Release/better_sqlite3.node"; - -copyResource([ - { - from: mainResolve("node_modules/better-sqlite3", path), - to: mainResolve("app", path), - }, - { - from: mainResolve("bin"), - to: mainResolve("app/bin"), - }, -]); - -esbuild.build({ - entryPoints: [mainResolve("src/index.ts")], - bundle: true, - platform: "node", - sourcemap: false, - target: ["node16.13"], - external, - define: { - "process.env.NODE_ENV": '"production"', - ...mainDefined, - }, - outdir: mainResolve("app/build/main"), - loader: { ".png": "file" }, - minify: true, - plugins: [], -}); - -esbuild.build({ - entryPoints: [mainResolve("src/preload.ts")], - bundle: true, - platform: "browser", - target: ["chrome89"], - sourcemap: false, - external, - define: { - "process.env.NODE_ENV": '"production"', - ...mainDefined, - }, - outdir: mainResolve("app/build/main"), - loader: { ".png": "file" }, - minify: true, -}); diff --git a/packages/main/scripts/config.ts b/packages/main/scripts/config.ts index dfcefe02..d98606ec 100644 --- a/packages/main/scripts/config.ts +++ b/packages/main/scripts/config.ts @@ -1,8 +1,145 @@ -export const external = [ +import { Configuration } from "electron-builder"; +import { Env, mainResolve } from "./utils"; +import esbuild from "esbuild"; + +const external = [ "electron", "nock", "aws-sdk", "mock-aws-s3", "@cliqz/adblocker-electron-preload", "node-pty", + "better-sqlite3", ]; + +function getConfig(): esbuild.BuildOptions { + const env = Env.getInstance().env; + return { + bundle: true, + sourcemap: process.env.NODE_ENV === "development", + external, + define: + process.env.NODE_ENV === "development" + ? { + // 开发环境中二进制可执行文件的路径 + __bin__: `"${mainResolve("app/bin").replace(/\\/g, "\\\\")}"`, + } + : { + ...env, + "process.env.NODE_ENV": '"production"', + }, + outdir: mainResolve("app/build/main"), + loader: { ".png": "file" }, + minify: process.env.NODE_ENV === "production", + }; +} + +function buildOptions( + entry: string, + platform: esbuild.Platform, + target: string, +): esbuild.BuildOptions { + return { + ...getConfig(), + entryPoints: [mainResolve(entry)], + platform: platform, + target: [target], + }; +} + +export function browserOptions(entry: string): esbuild.BuildOptions { + return buildOptions(entry, "browser", "chrome89"); +} + +export function nodeOptions(entry: string): esbuild.BuildOptions { + return buildOptions(entry, "node", "node16.13"); +} + +export function getReleaseConfig(): Configuration { + return { + productName: process.env.APP_NAME, + buildVersion: process.env.APP_VERSION, + appId: process.env.APP_ID, + copyright: process.env.APP_COPYRIGHT, + artifactName: "${productName}-setup-${arch}-${buildVersion}.${ext}", + // FIXME: 这里屏蔽 node-pty 自动重构,因为会导致打包失败 + npmRebuild: false, + directories: { + output: "./release", + }, + asarUnpack: [ + "**/better-sqlite3/build/Release/*.node", + "**/node-pty/build/Release/**", + ], + files: [ + { + from: "./build", + to: "./", + }, + "./package.json", + ], + extraResources: [ + { + from: "./app/plugin", + to: "plugin", + }, + { + from: "./app/bin/", + to: "bin", + }, + ], + win: { + icon: "../assets/icon.ico", + target: [ + { + target: "nsis", + arch: ["x64"], + }, + ], + }, + dmg: { + contents: [ + { + x: 410, + y: 150, + type: "link", + path: "/Applications", + }, + { + x: 130, + y: 150, + type: "file", + }, + ], + }, + mac: { + icon: "../assets/icon.icns", + target: { + target: "dmg", + arch: ["x64"], + }, + }, + linux: { + category: "Utility", + icon: "../assets/icon.icns", + maintainer: "caorushizi <84996057@qq.com>", + target: { + target: "deb", + arch: ["x64"], + }, + }, + nsis: { + oneClick: true, + allowElevation: true, + allowToChangeInstallationDirectory: false, + installerIcon: "", + uninstallerIcon: "", + installerHeaderIcon: "", + createDesktopShortcut: true, + createStartMenuShortcut: true, + shortcutName: "", + include: "", + script: "", + }, + }; +} diff --git a/packages/main/scripts/dev.ts b/packages/main/scripts/dev.ts deleted file mode 100644 index 8e112289..00000000 --- a/packages/main/scripts/dev.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { ChildProcessWithoutNullStreams, spawn } from "child_process"; -import electron from "electron"; -import * as esbuild from "esbuild"; -import chokidar from "chokidar"; -import { loadDotEnvRuntime, mainResolve, copyResource } from "./utils"; -import { external } from "./config"; -import consola from "consola"; - -let electronProcess: ChildProcessWithoutNullStreams | null = null; - -process.env.NODE_ENV = "development"; -loadDotEnvRuntime(); - -async function copySource() { - const path = "build/Release/better_sqlite3.node"; - - copyResource([ - { - from: mainResolve("node_modules/better-sqlite3", path), - to: mainResolve("app", path), - }, - { - from: mainResolve("bin"), - to: mainResolve("app/bin"), - }, - ]); -} - -const buildConfig: esbuild.BuildOptions = { - bundle: true, - platform: "node", - sourcemap: true, - target: ["node20.9"], - external, - define: { - // 开发环境中二进制可执行文件的路径 - __bin__: `"${mainResolve("bin", process.platform).replace(/\\/g, "\\\\")}"`, - }, - plugins: [], - outdir: mainResolve("app/build/main"), - loader: { ".png": "file" }, -}; - -function startElectron() { - const args = ["--inspect=5858", mainResolve("app/build/main/index.js")]; - - electronProcess = spawn(String(electron), args); - - electronProcess.stdout.on("data", (data) => { - consola.log(String(data)); - }); - - electronProcess.stderr.on("data", (data) => { - consola.log(String(data)); - }); -} - -function restartElectron() { - if (electronProcess && electronProcess.pid) { - if (process.platform === "darwin") { - spawn("kill", ["-9", String(electronProcess.pid)]); - } else { - process.kill(electronProcess.pid); - } - electronProcess = null; - startElectron(); - } -} - -async function start() { - const mainContext = await esbuild.context({ - ...buildConfig, - entryPoints: [mainResolve("src/index.ts")], - }); - - const preloadContext = await esbuild.context({ - ...buildConfig, - entryPoints: [mainResolve("src/preload.ts")], - platform: "browser", - }); - - const watcher = chokidar.watch("./src"); - watcher.on("change", async () => { - await mainContext.rebuild(); - await preloadContext.rebuild(); - consola.log("watch build succeed."); - restartElectron(); - }); - - try { - await mainContext.rebuild(); - await preloadContext.rebuild(); - await copySource(); - await startElectron(); - } catch (e) { - console.error(e); - process.exit(); - } -} - -start(); diff --git a/packages/main/scripts/index.ts b/packages/main/scripts/index.ts new file mode 100644 index 00000000..3c3d4866 --- /dev/null +++ b/packages/main/scripts/index.ts @@ -0,0 +1,115 @@ +import { deleteSync } from "del"; +import { ElectronApp, mainResolve, Env, isWin } from "./utils"; +import gulp from "gulp"; +import * as esbuild from "esbuild"; +import consola from "consola"; +import { browserOptions, nodeOptions, getReleaseConfig } from "./config"; +import semver from "semver"; +import pkg from "../app/package.json"; +import * as builder from "electron-builder"; +import glob from "glob"; +import path from "path"; +import fs from "fs"; + +process.env.NODE_ENV = "development"; + +const env = Env.getInstance(); +env.loadDotEnvRuntime(); + +async function clean() { + return deleteSync([ + mainResolve("app/build"), + mainResolve("app/bin"), + mainResolve("release"), + ]); +} + +async function copyBin() { + const sourceDir = mainResolve("bin", process.platform); + const targetDir = mainResolve("app/bin"); + + // 获取源目录下的所有文件 + const files = glob.sync(path.join(sourceDir, "*")); + + // 遍历每个文件 + for (const file of files) { + // 忽略 .gitignore 文件和 Logs 文件夹 + if (file.endsWith(".gitignore") || file.includes("Logs")) { + continue; + } + + // 计算目标文件的路径 + const targetFile = path.join(targetDir, path.basename(file)); + // 如果没有文件夹则创建文件夹 + if (!fs.existsSync(targetDir)) { + fs.mkdirSync(targetDir); + } + + // 复制文件 + fs.copyFileSync(file, targetFile); + } +} + +async function chmodBin() { + // 遍历文件夹下的所有文件,将文件的权限设置为 777 + if (isWin) return; + + const files = glob.sync(mainResolve("app/bin/*")); + for (const file of files) { + fs.chmodSync(file, 0o777); + } +} + +const copy = gulp.parallel(copyBin); + +async function watchTask() { + const app = new ElectronApp(); + const main = await esbuild.context(nodeOptions("src/index.ts")); + const preload = await esbuild.context(browserOptions("src/preload.ts")); + + const watcher = gulp.watch(["./src"]); + watcher + .on("ready", async () => { + await main.rebuild(); + await preload.rebuild(); + app.start(); + }) + .on("change", async () => { + await main.rebuild(); + await preload.rebuild(); + app.restart(); + }) + .on("error", (error) => { + consola.error(error); + process.exit(); + }); + return Promise.resolve(); +} + +async function buildTask() { + await esbuild.build(nodeOptions("src/index.ts")); + await esbuild.build(browserOptions("src/preload.ts")); +} + +async function pack() { + if (semver.neq(process.env.APP_VERSION || "", pkg.version)) { + throw new Error("请先同步构建版本和发布版本"); + } + const config = getReleaseConfig(); + if (process.env.GH_TOKEN) { + config.publish = { + provider: "github", + repo: "mediago", + owner: "caorushizi", + releaseType: "release", + }; + } + await builder.build({ config }); +} + +// 开发环境 +export const dev = gulp.series(clean, copy, chmodBin, watchTask); +// 构建打包 +export const build = gulp.series(clean, copy, chmodBin, buildTask); +// release +export const release = gulp.series(pack); diff --git a/packages/main/scripts/release.ts b/packages/main/scripts/release.ts deleted file mode 100644 index 0d781728..00000000 --- a/packages/main/scripts/release.ts +++ /dev/null @@ -1,115 +0,0 @@ -import { loadDotEnvRuntime, mainResolve, removeResource } from "./utils"; -import * as builder from "electron-builder"; -import semver from "semver"; -import pkg from "../app/package.json"; -import consola from "consola"; - -removeResource([mainResolve("release")]); - -loadDotEnvRuntime(); - -if (semver.neq(process.env.APP_VERSION || "", pkg.version)) { - consola.log("请先同步构建版本和发布版本"); - process.exit(0); -} - -const options: builder.Configuration = { - productName: process.env.APP_NAME, - buildVersion: process.env.APP_VERSION, - appId: process.env.APP_ID, - copyright: process.env.APP_COPYRIGHT, - artifactName: "${productName}-setup-${buildVersion}.${ext}", - // FIXME: 这里屏蔽 node-pty 自动重构,因为会导致打包失败 - npmRebuild: false, - directories: { - output: "./release", - }, - files: [ - { - from: "./build", - to: "./", - }, - "./package.json", - ], - extraResources: [ - { - from: "./app/plugin", - to: "plugin", - }, - { - from: "./app/bin/${platform}/", - to: "bin", - }, - ], - win: { - icon: "../assets/icon.ico", - target: [ - { - target: "nsis", - }, - ], - }, - dmg: { - contents: [ - { - x: 410, - y: 150, - type: "link", - path: "/Applications", - }, - { - x: 130, - y: 150, - type: "file", - }, - ], - }, - mac: { - icon: "../assets/icon.icns", - target: { - target: "default", - arch: ["x64", "arm64"], - }, - }, - linux: {}, - nsis: { - oneClick: true, - allowElevation: true, - allowToChangeInstallationDirectory: false, - installerIcon: "", - uninstallerIcon: "", - installerHeaderIcon: "", - createDesktopShortcut: true, - createStartMenuShortcut: true, - shortcutName: "", - include: "", - script: "", - }, - publish: { - provider: "github", - repo: "mediago", - owner: "caorushizi", - releaseType: "release", - }, -}; - -async function start() { - const target = - process.env.NODE_ENV === "development" - ? builder.DIR_TARGET - : builder.DEFAULT_TARGET; - try { - const targets = - process.platform === "win32" - ? builder.Platform.WINDOWS.createTarget(target) - : builder.Platform.MAC.createTarget(target); - await builder.build({ - targets, - config: options, - }); - } catch (e) { - consola.log(e); - } -} - -start(); diff --git a/packages/main/scripts/utils.ts b/packages/main/scripts/utils.ts index 8532f4de..6d4ac18d 100644 --- a/packages/main/scripts/utils.ts +++ b/packages/main/scripts/utils.ts @@ -1,72 +1,106 @@ -import { existsSync, cpSync, rmSync } from "fs"; +import { ChildProcessWithoutNullStreams, spawn } from "child_process"; import dotenv from "dotenv"; import { resolve } from "path"; import consola from "consola"; import fs from "fs-extra"; +import electron from "electron"; -export const mainResolve = (...r: string[]) => resolve(__dirname, "..", ...r); -export const rootResolve = (...r: string[]) => - resolve(__dirname, "../../..", ...r); -const nodeEnv = process.env.NODE_ENV; -consola.log("当前的环境是: ", nodeEnv); +const baseResolve = (...r) => resolve(__dirname, ...r); +export const mainResolve = (...r: string[]) => baseResolve("..", ...r); +export const rootResolve = (...r: string[]) => baseResolve("../../..", ...r); -function loadEnv(path: string) { - const result: Record = {}; +export const isLinux = process.platform === "linux"; +export const isMac = process.platform === "darwin"; +export const isWin = process.platform === "win32"; - if (!existsSync(path)) { - return null; - } +export class ElectronApp { + process: ChildProcessWithoutNullStreams | null = null; + + start() { + const args = ["--inspect=5858", mainResolve("app/build/main/index.js")]; + + this.process = spawn(String(electron), args); - const parsed = dotenv.parse(fs.readFileSync(path)); - if (!parsed) { - return null; + this.process.stdout.on("data", (data) => { + consola.log(String(data)); + }); + + this.process.stderr.on("data", (data) => { + consola.log(String(data)); + }); } - Object.keys(parsed).forEach((key) => { - result[key] = parsed[key]; - }); + restart() { + this.kill(); + this.start(); + } - return result; + kill() { + if (this.process && this.process.pid) { + if (isMac) { + spawn("kill", ["-9", String(this.process.pid)]); + } else { + process.kill(this.process.pid); + } + this.process = null; + } + } } -function loadDotEnv() { - const env = loadEnv(rootResolve(".env")); - const envMode = loadEnv(rootResolve(`.env.${nodeEnv}`)); - const envModeLocal = loadEnv(rootResolve(`.env.${nodeEnv}.local`)); +export class Env { + env: Record = {}; + nodeEnv = ""; + static instance: Env; - return { ...env, ...envMode, ...envModeLocal }; -} + private constructor(nodeEnv = process.env.NODE_ENV) { + const env = this.parseEnv(rootResolve(".env")); + const modeEnv = this.parseEnv(rootResolve(`.env.${nodeEnv}`)); + const localEnv = this.parseEnv(rootResolve(`.env.${nodeEnv}.local`)); -export function loadDotEnvRuntime() { - const env = loadDotEnv(); + this.nodeEnv = nodeEnv || "development"; + this.env = { ...env, ...modeEnv, ...localEnv }; + } - Object.keys(env).forEach((key) => { - if (process.env[key] != null || !env[key]) return; - process.env[key] = env[key]; - }); -} + static getInstance() { + if (!Env.instance) { + Env.instance = new Env(); + } -export function loadDotEnvDefined() { - const env = loadDotEnv(); + return Env.instance; + } - return Object.keys(env).reduce>((prev, cur) => { - if (!cur.startsWith("APP_")) return prev; - prev[`process.env.${[cur]}`] = JSON.stringify(env[cur]); - return prev; - }, {}); -} + private parseEnv(path: string) { + if (!fs.existsSync(path)) { + return null; + } + + const parsed = dotenv.parse(fs.readFileSync(path)); + if (!parsed) { + return null; + } + + return Object.keys(parsed).reduce((prev, curr) => { + prev[curr] = parsed[curr]; + return prev; + }, {}); + } -export function copyResource(resource: { from: string; to: string }[]) { - resource.forEach((r) => { - const { from, to } = r; - cpSync(from, to, { - recursive: true, + loadDotEnvRuntime() { + Object.keys(this.env).forEach((key) => { + if (process.env[key] != null || !this.env[key]) return; + process.env[key] = this.env[key]; }); - }); -} + } -export function removeResource(resource: string[]) { - resource.forEach((r) => { - rmSync(r, { recursive: true, force: true }); - }); + loadDotEnvDefined() { + return Object.keys(this.env).reduce>((prev, cur) => { + if (!cur.startsWith("APP_")) return prev; + prev[`process.env.${[cur]}`] = JSON.stringify(this.env[cur]); + return prev; + }, {}); + } + + get isDev() { + return this.nodeEnv === "development"; + } } diff --git a/packages/main/src/helper/variables.ts b/packages/main/src/helper/variables.ts index 69aaaea9..534596d2 100644 --- a/packages/main/src/helper/variables.ts +++ b/packages/main/src/helper/variables.ts @@ -6,8 +6,15 @@ const appPath = app.getAppPath(); export const appData = app.getPath("appData"); export const download = app.getPath("downloads"); -export const isMac = process.platform === "darwin"; -export const isWin = process.platform === "win32"; +export enum Platform { + Windows = "win32", + MacOS = "darwin", + Linux = "linux", +} + +export const isMac = process.platform === Platform.MacOS; +export const isWin = process.platform === Platform.Windows; +export const isLinux = process.platform === Platform.Linux; if (!isDev) { global.__bin__ = resolve(appPath, "../bin"); @@ -32,11 +39,9 @@ export const PERSIST_WEBVIEW = "persist:webview"; export const db = resolve(workspace, "app.db"); // bin path -const downloaderBinName = isWin ? "N_m3u8DL-CLI" : "N_m3u8DL-RE"; export const ffmpegPath = resolveBin("ffmpeg"); export const biliDownloaderBin = resolveBin("BBDown"); -export const m3u8DownloaderBin = resolveBin(downloaderBinName); -export const videoServerBin = resolveBin("server"); +export const m3u8DownloaderBin = resolveBin("N_m3u8DL-RE"); // plugin path export const pluginPath = resolveStatic("plugin/index.js"); diff --git a/packages/main/src/repository/VideoRepository.ts b/packages/main/src/repository/VideoRepository.ts index a337c445..9f6c2a6a 100644 --- a/packages/main/src/repository/VideoRepository.ts +++ b/packages/main/src/repository/VideoRepository.ts @@ -114,12 +114,12 @@ export default class VideoRepository { async changeVideoStatus(id: number | number[], status: DownloadStatus) { const ids = !Array.isArray(id) ? [id] : id; - return this.db.appDataSource - .createQueryBuilder() - .update(Video) - .set({ status }) - .where({ id: In(ids) }) - .execute(); + const videoRepository = this.db.appDataSource.getRepository(Video); + const videos = await videoRepository.findBy({ id: In(ids) }); + for (const video of videos) { + video.status = status; + } + await videoRepository.save(videos); } async changeVideoIsLive(id: number) { diff --git a/packages/main/src/services/DownloadService.ts b/packages/main/src/services/DownloadService.ts index 1929c523..31d49af5 100644 --- a/packages/main/src/services/DownloadService.ts +++ b/packages/main/src/services/DownloadService.ts @@ -10,7 +10,7 @@ import { TYPES } from "../types"; import ElectronLogger from "../vendor/ElectronLogger"; import ElectronStore from "../vendor/ElectronStore"; import VideoRepository from "../repository/VideoRepository"; -import { biliDownloaderBin, m3u8DownloaderBin } from "../helper"; +import { Platform, biliDownloaderBin, m3u8DownloaderBin } from "../helper"; import * as pty from "node-pty"; import stripAnsi from "strip-ansi"; @@ -49,39 +49,7 @@ interface Schema { const processList: Schema[] = [ { type: "m3u8", - platform: ["win32"], - bin: m3u8DownloaderBin, - args: { - url: { - argsName: null, - }, - localDir: { - argsName: ["--workDir"], - }, - name: { - argsName: ["--saveName"], - }, - // headers: { - // argsName: ["--headers"], - // }, - deleteSegments: { - argsName: ["--enableDelAfterDone"], - }, - proxy: { - argsName: ["--proxyAddress"], - }, - }, - consoleReg: { - percent: "([\\d.]+)%", - speed: "([\\d.]+\\s[GMK]B/s)", - error: "ERROR", - start: "开始下载文件|开始录制", - isLive: "识别为直播流, 开始录制", - }, - }, - { - type: "m3u8", - platform: ["darwin"], + platform: [Platform.MacOS, Platform.Linux, Platform.Windows], bin: m3u8DownloaderBin, args: { url: { @@ -113,7 +81,7 @@ const processList: Schema[] = [ }, { type: "bilibili", - platform: ["win32", "darwin"], + platform: [Platform.Linux, Platform.MacOS, Platform.Windows], bin: biliDownloaderBin, args: { url: { @@ -406,7 +374,7 @@ export default class DownloadService extends EventEmitter { } }; - this.logger.debug("download params: ", spawnParams); + this.logger.debug("download params: ", spawnParams.join(" ")); await this._execa(schema.bin, spawnParams, { id, abortSignal, diff --git a/packages/main/src/services/SniffingHelperService.ts b/packages/main/src/services/SniffingHelperService.ts index 5a8b1ba9..f36004e4 100644 --- a/packages/main/src/services/SniffingHelperService.ts +++ b/packages/main/src/services/SniffingHelperService.ts @@ -5,10 +5,7 @@ import ElectronLogger from "../vendor/ElectronLogger"; import EventEmitter from "events"; import { session } from "electron"; import { PERSIST_WEBVIEW } from "../helper"; -import { - CallbackResponse, - OnBeforeRequestListenerDetails, -} from "electron/main"; +import { OnCompletedListenerDetails } from "electron/main"; export interface SourceParams { url: string; @@ -90,7 +87,7 @@ export class SniffingHelper extends EventEmitter { start() { const viewSession = session.fromPartition(PERSIST_WEBVIEW); - viewSession.webRequest.onBeforeRequest(this.requestWillBeSent.bind(this)); + viewSession.webRequest.onCompleted(this.onCompleted); } send = (item: SourceParams) => { @@ -103,10 +100,7 @@ export class SniffingHelper extends EventEmitter { } }; - private requestWillBeSent( - details: OnBeforeRequestListenerDetails, - callback: (response: CallbackResponse) => void, - ): void { + private onCompleted = (details: OnCompletedListenerDetails): void => { const { url } = details; const { title, url: documentURL } = this.pageInfo; @@ -128,7 +122,5 @@ export class SniffingHelper extends EventEmitter { } } } - - callback({}); - } + }; } diff --git a/packages/renderer/package.json b/packages/renderer/package.json index fc6add72..2d7e3fb6 100644 --- a/packages/renderer/package.json +++ b/packages/renderer/package.json @@ -8,6 +8,7 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", + "report": "vite-bundle-visualizer", "lint-staged": "lint-staged" }, "dependencies": { @@ -15,6 +16,7 @@ "@ant-design/pro-components": "^2.6.48", "@reduxjs/toolkit": "^2.0.1", "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", "ahooks": "^3.7.8", "antd": "^5.13.2", "antd-style": "^3.6.1", @@ -32,8 +34,7 @@ "react-i18next": "^14.0.1", "react-redux": "^9.1.0", "react-router-dom": "^6.21.3", - "sort-by": "^1.2.0", - "@xterm/xterm": "^5.5.0" + "sort-by": "^1.2.0" }, "devDependencies": { "@types/node": "^20.11.5", @@ -58,7 +59,8 @@ "stylelint-config-standard": "^36.0.0", "stylelint-config-standard-scss": "^13.0.0", "typescript": "~5.3.3", - "vite": "^5.0.12" + "vite": "^5.0.12", + "vite-bundle-visualizer": "^1.2.1" }, "license": "MIT", "lint-staged": { diff --git a/packages/renderer/src/App.tsx b/packages/renderer/src/App.tsx index 10a1cdfd..6d44fc4b 100644 --- a/packages/renderer/src/App.tsx +++ b/packages/renderer/src/App.tsx @@ -1,17 +1,19 @@ import { ConfigProvider, theme } from "antd"; -import "antd/dist/reset.css"; -import React, { FC, useEffect } from "react"; +import React, { FC, Suspense, lazy, useEffect } from "react"; import { useDispatch } from "react-redux"; import { BrowserRouter, Route, Routes } from "react-router-dom"; -import AppLayout from "./layout/App"; -import HomePage, { DownloadFilter } from "./nodes/HomePage"; -import SettingPage from "./nodes/SettingPage"; -import SourceExtract from "./nodes/SourceExtract"; +import { DownloadFilter } from "./nodes/HomePage"; import { setAppStore, increase } from "./store"; import "dayjs/locale/zh-cn"; import zhCN from "antd/locale/zh_CN"; import "./App.scss"; import useElectron from "./hooks/electron"; +import Loading from "./components/Loading"; + +const AppLayout = lazy(() => import("./layout/App")); +const HomePage = lazy(() => import("./nodes/HomePage")); +const SourceExtract = lazy(() => import("./nodes/SourceExtract")); +const SettingPage = lazy(() => import("./nodes/SettingPage")); function getAlgorithm(appTheme: "dark" | "light") { return appTheme === "dark" ? theme.darkAlgorithm : theme.defaultAlgorithm; @@ -72,17 +74,56 @@ const App: FC = () => { > - }> - } /> + }> + + + } + > + }> + + + } + /> } + element={ + }> + + + } + /> + }> + + + } + /> + }> + + + } /> - } /> - } /> 404} /> - } /> + }> + + + } + /> diff --git a/packages/renderer/src/components/Loading/index.tsx b/packages/renderer/src/components/Loading/index.tsx new file mode 100644 index 00000000..138947d0 --- /dev/null +++ b/packages/renderer/src/components/Loading/index.tsx @@ -0,0 +1,14 @@ +import React, { FC } from "react"; +import { useStyles } from "./styles"; +import { Spin } from "antd"; + +const Loading: FC = () => { + const { styles } = useStyles(); + return ( +
+ +
+ ); +}; + +export default Loading; diff --git a/packages/renderer/src/components/Loading/styles.ts b/packages/renderer/src/components/Loading/styles.ts new file mode 100644 index 00000000..09dceb49 --- /dev/null +++ b/packages/renderer/src/components/Loading/styles.ts @@ -0,0 +1,12 @@ +import { createStyles } from "antd-style"; + +export const useStyles = createStyles(({ css, token }) => ({ + container: css` + height: 100%; + width: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: ${token.colorBgContainer}; + `, +})); diff --git a/packages/renderer/src/components/Terminal/index.tsx b/packages/renderer/src/components/Terminal/index.tsx index 31d0ac85..511831bb 100644 --- a/packages/renderer/src/components/Terminal/index.tsx +++ b/packages/renderer/src/components/Terminal/index.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect, useRef } from "react"; -import { useStyles } from "./style"; +import { useStyles } from "./styles"; import "@xterm/xterm/css/xterm.css"; import { Terminal as XTerminal } from "@xterm/xterm"; import { FitAddon } from "@xterm/addon-fit"; diff --git a/packages/renderer/src/components/Terminal/style.ts b/packages/renderer/src/components/Terminal/styles.ts similarity index 100% rename from packages/renderer/src/components/Terminal/style.ts rename to packages/renderer/src/components/Terminal/styles.ts diff --git a/packages/renderer/src/nodes/HomePage/index.tsx b/packages/renderer/src/nodes/HomePage/index.tsx index 650b06d8..e1774708 100644 --- a/packages/renderer/src/nodes/HomePage/index.tsx +++ b/packages/renderer/src/nodes/HomePage/index.tsx @@ -26,9 +26,9 @@ import { import { useSelector } from "react-redux"; import { selectAppStore } from "../../store"; import DownloadFrom from "../../components/DownloadForm"; -import dayjs from "dayjs"; import { Trans, useTranslation } from "react-i18next"; import Terminal from "../../components/Terminal"; +import { moment } from "../../utils"; const { Text } = Typography; @@ -60,7 +60,12 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { } = useElectron(); const appStore = useSelector(selectAppStore); const { t } = useTranslation(); - const { data, loading, pagination, refresh } = usePagination( + const { + data = { total: 0, list: [] }, + loading, + pagination, + refresh, + } = usePagination( ({ current, pageSize }) => { return getDownloadItems({ current, @@ -84,7 +89,7 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { log: "", }); - const onDownloadProgress = (e: any, progress: DownloadProgress) => { + const onDownloadProgress = (e: unknown, progress: DownloadProgress) => { setProgress((curProgress) => ({ ...curProgress, [progress.id]: progress, @@ -411,9 +416,7 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { const items = batchList.split("\n").map((item: any) => { let [url, name] = item.split(" "); url = url ? url.trim() : ""; - name = name - ? name.trim() - : dayjs().format("YYYY-MM-DDTHH:mm:ssZ"); + name = name ? name.trim() : moment(); return { url, name, @@ -423,7 +426,7 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { await addDownloadItems(items); } else { await addDownloadItem({ - name: values.name || dayjs().format("YYYY-MM-DDTHH:mm:ssZ"), + name: values.name || moment(), url: values.url, headers: values.headers, type: DownloadType.m3u8, @@ -464,7 +467,7 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { }} rowKey="id" rowSelection={rowSelection} - dataSource={data?.list || []} + dataSource={data.list || []} tableAlertOptionRender={({ selectedRowKeys, onCleanSelected }) => { if (selectedRowKeys.length === 0) { return null; @@ -496,11 +499,11 @@ const HomePage: FC = ({ filter = DownloadFilter.list }) => { tableAlertRender={({ selectedRows }) => { return ( <> - {data?.list.length !== 0 && ( + {data.list.length !== 0 && (