├── .eslintignore ├── .gitmodules ├── .prettierrc.yaml ├── resources ├── icon.ico └── icon.png ├── .prettierignore ├── .eslintrc.js ├── .editorconfig ├── src ├── preload.js ├── renderer.js ├── index.html ├── main.js ├── style.css └── icons.svg ├── .gitignore ├── LICENSE ├── package.json ├── electron-builder.json5 └── README.MD /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "um-react"] 2 | path = um-react 3 | url = ./um-react 4 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfvips/um-react-electron/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dfvips/um-react-electron/HEAD/resources/icon.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['eslint:recommended', '@electron-toolkit', '@electron-toolkit/eslint-config-prettier'] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /src/preload.js: -------------------------------------------------------------------------------- 1 | // All of the Node.js APIs are available in the preload process. 2 | // It has the same sandbox as a Chrome extension. 3 | const { contextBridge } = require('electron') 4 | const { electronAPI } = require('@electron-toolkit/preload') 5 | 6 | // Custom APIs for renderer 7 | const api = {} 8 | 9 | // Use `contextBridge` APIs to expose Electron APIs to 10 | // renderer only if context isolation is enabled, otherwise 11 | // just add to the DOM global. 12 | if (process.contextIsolated) { 13 | try { 14 | contextBridge.exposeInMainWorld('electron', electronAPI) 15 | contextBridge.exposeInMainWorld('api', api) 16 | } catch (error) { 17 | console.error(error) 18 | } 19 | } else { 20 | window.electron = electronAPI 21 | window.api = api 22 | } 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs/ 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules/ 11 | coverage/ 12 | dist/ 13 | dist-ssr/ 14 | release/**/* 15 | *.local 16 | src/renderer/**/* 17 | src/default/**/* 18 | 19 | # Editor directories and files 20 | .vscode/ 21 | !.vscode/extensions.json 22 | .idea/ 23 | .DS_Store 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | # Ignore the same patterns in the "um-react" directory 31 | um-react/logs/ 32 | um-react/*.log 33 | um-react/npm-debug.log* 34 | um-react/yarn-debug.log* 35 | um-react/yarn-error.log* 36 | um-react/pnpm-debug.log* 37 | um-react/lerna-debug.log* 38 | 39 | um-react/node_modules/ 40 | um-react/coverage/ 41 | um-react/dist/ 42 | um-react/dist-ssr/ 43 | um-react/*.local 44 | -------------------------------------------------------------------------------- /src/renderer.js: -------------------------------------------------------------------------------- 1 | // This file is required by the index.html file and will 2 | // be executed in the renderer process for that window. 3 | // No Node.js APIs are available in this process because 4 | // `nodeIntegration` is turned off. Use `preload.js` to 5 | // selectively enable features needed in the rendering 6 | // process. 7 | ;(async () => { 8 | window.addEventListener('DOMContentLoaded', () => { 9 | doAThing() 10 | }) 11 | 12 | function doAThing() { 13 | const versions = window.electron.process.versions 14 | replaceText('.electron-version', `Electron v${versions.electron}`) 15 | replaceText('.chrome-version', `Chromium v${versions.chrome}`) 16 | replaceText('.node-version', `Node v${versions.node}`) 17 | replaceText('.v8-version', `V8 v${versions.v8}`) 18 | } 19 | 20 | function replaceText(selector, text) { 21 | const element = document.querySelector(selector) 22 | if (element) { 23 | element.innerText = text 24 | } 25 | } 26 | })() 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 dreamfly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "um-react-electron", 3 | "version": "1.0.0", 4 | "description": "Unlock Music 音乐解锁 (React) Electron App", 5 | "main": "./src/main.js", 6 | "author": "dreamfly", 7 | "homepage": "https://git.unlock-music.dev/um/um-react", 8 | "scripts": { 9 | "format": "prettier --write .", 10 | "lint": "eslint --ext .js .", 11 | "dev": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../ && electron .", 12 | "start": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../ && electron .", 13 | "postinstall": "electron-builder install-app-deps", 14 | "build": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../", 15 | "build:win": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../ && electron-builder --win --config", 16 | "build:mac": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../ && electron-builder --mac --config", 17 | "build:linux": "cd um-react && npm run build && cp -r ./dist ../src/renderer && cd ../ && electron-builder --linux --config" 18 | }, 19 | "dependencies": { 20 | "@electron-toolkit/preload": "^2.0.0", 21 | "@electron-toolkit/utils": "^2.0.0" 22 | }, 23 | "devDependencies": { 24 | "@electron-toolkit/eslint-config": "^1.0.1", 25 | "@electron-toolkit/eslint-config-prettier": "^1.0.1", 26 | "electron": "^25.6.0", 27 | "electron-builder": "^24.6.3", 28 | "eslint": "^8.47.0", 29 | "prettier": "^3.0.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /electron-builder.json5: -------------------------------------------------------------------------------- 1 | /** 2 | * @see https://www.electron.build/configuration/configuration 3 | */ 4 | { 5 | "$schema": "https://github.com/dfvips/iqiyi-parser", 6 | "appId": "com.dreamfly.um", 7 | "productName": "音乐解锁 React", 8 | "asar": true, 9 | "directories": { 10 | "output": "release/${version}" 11 | }, 12 | "asarUnpack":[ 13 | "resources/**" 14 | ], 15 | "mac": { 16 | "icon": "build/icon.icns", 17 | "artifactName": "${name}-${version}-osx64.${ext}", 18 | "target": [ 19 | "dmg" 20 | ], 21 | "files": [ 22 | "!**/.vscode/*", 23 | "!{.eslintignore,.eslintrc.js,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}" 24 | ] 25 | }, 26 | "compression": "maximum", 27 | "win": { 28 | "icon": "build/icon.ico", 29 | "target": [ 30 | { 31 | "target": "nsis", 32 | "arch": [ 33 | "x64" 34 | ] 35 | }, 36 | { 37 | "target": "7z" 38 | } 39 | ], 40 | "artifactName": "${name}-${version}-winx64.${ext}", 41 | "files": [ 42 | "!**/.vscode/*", 43 | "!{.eslintignore,.eslintrc.js,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}" 44 | ] 45 | }, 46 | "nsis": { 47 | "oneClick": false, 48 | "perMachine": false, 49 | "allowToChangeInstallationDirectory": true, 50 | "deleteAppDataOnUninstall": true, 51 | "installerIcon": "build/icon.ico", 52 | "uninstallerIcon": "build/icon.ico", 53 | "uninstallDisplayName": "${name}-${version}", 54 | "artifactName": "${name}-${version}-winx64-setup.${ext}", 55 | "shortcutName": "${productName}" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Electron 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 23 | 24 | 25 |

You've successfully created an Electron project

26 |

Please try pressing F12 to open the devTool

27 |

This is the simplest starter for Electron without any build tools

28 |

29 | You can check out the links to learn more about Electron build tools 30 |

31 | 32 | 46 | 47 |

More templates:

48 |

49 | vanilla, vanilla-ts, vue, vue-ts, 50 | react, react-ts, svelte, svelte-ts 51 |

52 |
53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // Modules to control application life and create native browser window 2 | const { app, shell, BrowserWindow } = require('electron') 3 | const path = require('path') 4 | const { electronApp, optimizer } = require('@electron-toolkit/utils') 5 | 6 | function createWindow() { 7 | // Create the browser window. 8 | const mainWindow = new BrowserWindow({ 9 | width: 900, 10 | height: 670, 11 | show: false, 12 | autoHideMenuBar: true, 13 | // frame: false, // 隐藏标题栏 14 | ...(process.platform === 'linux' || process.platform === 'win32' 15 | ? { 16 | icon: path.join(__dirname, '../resources/icon.png') 17 | } 18 | : {}), 19 | ...(process.platform === 'darwin' 20 | ? { 21 | titleBarStyle: 'hidden' // 隐藏 macOS 的默认标题栏 22 | } 23 | : {}), 24 | webPreferences: { 25 | preload: path.join(__dirname, 'preload.js'), 26 | sandbox: false, 27 | nodeIntegration: true 28 | } 29 | }) 30 | 31 | mainWindow.on('ready-to-show', () => { 32 | mainWindow.show() 33 | }) 34 | 35 | mainWindow.webContents.setWindowOpenHandler((details) => { 36 | shell.openExternal(details.url) 37 | return { action: 'deny' } 38 | }) 39 | 40 | // and load the index.html of the app. 41 | // console.log(app.isPackaged) 42 | // if (!app.isPackaged) { 43 | // mainWindow.loadURL('http://localhost:5173') 44 | // } else { 45 | mainWindow.loadFile(path.join(__dirname, 'renderer/index.html')) 46 | // } 47 | 48 | mainWindow.on('closed', () => { 49 | // 在窗口关闭时触发before-quit事件以结束进程 50 | app.quit() 51 | }) 52 | } 53 | 54 | // This method will be called when Electron has finished 55 | // initialization and is ready to create browser windows. 56 | // Some APIs can only be used after this event occurs. 57 | app.whenReady().then(() => { 58 | // Set app user model id for windows 59 | electronApp.setAppUserModelId('com.dreamfly.um') 60 | 61 | // Default open or close DevTools by F12 in development 62 | // and ignore CommandOrControl + R in production. 63 | // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils 64 | app.on('browser-window-created', (_, window) => { 65 | optimizer.watchWindowShortcuts(window) 66 | }) 67 | 68 | app.on('NSApplicationDelegate.applicationSupportsSecureRestorableState', () => true) 69 | 70 | createWindow() 71 | 72 | app.on('activate', function () { 73 | // On macOS it's common to re-create a window in the app when the 74 | // dock icon is clicked and there are no other windows open. 75 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 76 | }) 77 | 78 | }) 79 | 80 | // Quit when all windows are closed, except on macOS. There, it's common 81 | // for applications and their menu bar to stay active until the user quits 82 | // explicitly with Cmd + Q. 83 | app.on('window-all-closed', function () { 84 | if (process.platform !== 'darwin') { 85 | app.quit() 86 | } 87 | }) 88 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | /* Add styles here to customize the appearance of your app */ 2 | body { 3 | display: flex; 4 | flex-direction: column; 5 | font-family: 6 | Roboto, 7 | -apple-system, 8 | BlinkMacSystemFont, 9 | 'Helvetica Neue', 10 | 'Segoe UI', 11 | 'Oxygen', 12 | 'Ubuntu', 13 | 'Cantarell', 14 | 'Open Sans', 15 | sans-serif; 16 | color: #86a5b1; 17 | background-color: #2f3241; 18 | } 19 | 20 | * { 21 | padding: 0; 22 | margin: 0; 23 | } 24 | 25 | ul { 26 | list-style: none; 27 | } 28 | 29 | code { 30 | font-weight: 600; 31 | padding: 3px 5px; 32 | border-radius: 2px; 33 | background-color: #26282e; 34 | font-family: 35 | ui-monospace, 36 | SFMono-Regular, 37 | SF Mono, 38 | Menlo, 39 | Consolas, 40 | Liberation Mono, 41 | monospace; 42 | font-size: 85%; 43 | } 44 | 45 | a { 46 | color: #9feaf9; 47 | font-weight: 600; 48 | cursor: pointer; 49 | text-decoration: none; 50 | outline: none; 51 | } 52 | 53 | a:hover { 54 | border-bottom: 1px solid; 55 | } 56 | 57 | .container { 58 | flex: 1; 59 | display: flex; 60 | flex-direction: column; 61 | max-width: 900px; 62 | margin: 0 auto; 63 | padding: 15px 30px 0 30px; 64 | box-sizing: border-box; 65 | } 66 | 67 | @media (min-width: 840px) { 68 | .container { 69 | width: 100%; 70 | } 71 | } 72 | 73 | .versions { 74 | margin: 0 auto; 75 | float: none; 76 | clear: both; 77 | overflow: hidden; 78 | font-family: 'Menlo', 'Lucida Console', monospace; 79 | color: #c2f5ff; 80 | line-height: 1; 81 | transition: all 0.3s; 82 | } 83 | 84 | .versions li { 85 | display: block; 86 | float: left; 87 | border-right: 1px solid rgba(194, 245, 255, 0.4); 88 | padding: 0 20px; 89 | font-size: 13px; 90 | opacity: 0.8; 91 | } 92 | 93 | .versions li:last-child { 94 | border: none; 95 | } 96 | 97 | .hero-logo { 98 | margin-top: -0.4rem; 99 | transition: all 0.3s; 100 | } 101 | 102 | @media (max-width: 840px) { 103 | .versions { 104 | display: none; 105 | } 106 | 107 | .hero-logo { 108 | margin-top: -1.5rem; 109 | } 110 | } 111 | 112 | .hero-text { 113 | font-weight: 400; 114 | color: #c2f5ff; 115 | text-align: center; 116 | margin-top: -0.5rem; 117 | margin-bottom: 10px; 118 | } 119 | 120 | @media (max-width: 660px) { 121 | .hero-logo { 122 | display: none; 123 | } 124 | 125 | .hero-text { 126 | margin-top: 20px; 127 | } 128 | } 129 | 130 | .hero-tagline { 131 | text-align: center; 132 | margin-bottom: 14px; 133 | } 134 | 135 | .links { 136 | display: flex; 137 | align-items: center; 138 | justify-content: center; 139 | margin-bottom: 24px; 140 | font-size: 18px; 141 | font-weight: 500; 142 | } 143 | 144 | .links a { 145 | font-weight: 500; 146 | } 147 | 148 | .links .link-item { 149 | padding: 0 4px; 150 | } 151 | 152 | @media (max-width: 480px) { 153 | .links { 154 | flex-direction: column; 155 | line-height: 32px; 156 | } 157 | 158 | .links .link-dot { 159 | display: none; 160 | } 161 | } 162 | 163 | .line-spacing { 164 | line-height: 2; 165 | } 166 | -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # 关于仓库官方 2 | 3 | 本仓库原始地址(已 DMCA):https://github.com/unlock-music/unlock-music 4 | 5 | 本仓库目前官方地址:https://git.unlock-music.dev/um/um-react 6 | 7 | 你所看到的这个仓库是依照 MIT 协议授权的,除electron部分(不限于electron构建)外,代码与[本人](https://github.com/dfvips)无关。 8 | 9 | # Unlock Music 音乐解锁 Electron 应用程序 10 | 11 | [![Build Status](https://ci.unlock-music.dev/api/badges/um/web/status.svg)](https://github.com/dfvips/unlock-music-react-electron) 12 | - 在Electron应用程序中解锁加密的音乐文件。 Unlock encrypted music file in the Electron App. 13 | - 无需依赖浏览器,开箱即可使用。 14 | 15 | ## 自行构建Electron应用程序 16 | 17 | - 环境要求 18 | - Linux、MacOs 19 | - python3、nodejs、npm 20 | 21 | - 开发环境 22 | ```sh 23 | git clone https://git.unlock-music.dev/um/um-react.git && npm install --frozen-lockfile 24 | cd ../ && npm install 25 | npm run start / npm run dev 26 | npm run start / npm run dev 27 | ``` 28 | - 打包 29 | ```sh 30 | npm run build / npm run build:win / npm run build:mac 31 | ``` 32 | - 前端静态页面构建后的产物可以在 `src/renderer` 目录找到。 33 | - Electron构建后的产物可以在 `release` 目录找到。 34 | 35 | ## 开箱即用版本 36 | 37 | https://github.com/dfvips/unlock-music-electron/releases 38 | # Unlock Music 音乐解锁 (React) 39 | 40 | [![Build Status](https://ci.unlock-music.dev/api/badges/um/um-react/status.svg)](https://ci.unlock-music.dev/um/um-react) 41 | 42 | - 在浏览器中解锁加密的音乐文件。 Unlock encrypted music file in the browser. 43 | - Unlock Music 项目是以学习和技术研究的初衷创建的,修改、再分发时请遵循[授权协议]。 44 | - Unlock Music 的 CLI 版本可以在 [unlock-music/cli] 找到,大批量转换建议使用 CLI 版本。 45 | - 我们新建了 Telegram 群组 [`@unlock_music_chat`] ,欢迎加入! 46 | - CI 自动构建已经部署,可以在 [Packages][um-react-packages] 下载。 47 | 48 | [授权协议]: https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE 49 | [unlock-music/cli]: https://git.unlock-music.dev/um/cli 50 | [`@unlock_music_chat`]: https://t.me/unlock_music_chat 51 | [um-react-packages]: https://git.unlock-music.dev/um/-/packages/generic/um-react/ 52 | 53 | ## 支持的格式 54 | 55 | - [x] QQ 音乐 QMCv1 (.qmc0/.qmc2/.qmc3/.qmcflac/.qmcogg/.tkm) 56 | - [x] QQ 音乐 QMCv2 PC 端 (.mflac/.mgg/.mflac0/.mgg1/.mggl) 57 | - [x] 网易云音乐 (.ncm) 58 | - [x] 虾米音乐 (.xm) 59 | - [x] 酷我音乐 (.kwm) 60 | - [x] 酷狗音乐 (.kgm/.vpr) 61 | - [x] 喜马拉雅 Android 端 (.x2m/.x3m) 62 | - [x] 咪咕音乐格式 (.mg3d) 63 | - [ ] ~~QQ 音乐海外版JOOX Music (.ofl_en)~~ 64 | 65 | 不支持的格式?请提交样本(加密文件)与客户端信息(或一并上传其安装包)到[仓库的问题追踪区][project-issues]。如果文件太大,请上传到不需要登入下载的网盘,如 [mega.nz](https://mega.nz)、[OneDrive](https://www.onedrive.com/) 等。 66 | 67 | 如果遇到解密出错的情况,请一并携带错误信息并简单描述错误的重现过程。 68 | 69 | [project-issues]: https://git.unlock-music.dev/um/um-react/issues/new 70 | 71 | ## 开发相关 72 | 73 | 从源码运行或编译生产版本,请参考文档「[新手上路](./docs/getting-started.zh.md)」。 74 | 75 | ### 面向 libparakeet SDK 开发 76 | 77 | ⚠️ 如果只是进行前端方面的更改,你可以跳过该节。 78 | 79 | 请参考文档「[面向 `libparakeet-js` 开发](./docs/develop-with-libparakeet.zh.md)」。 80 | 81 | ### 架构 82 | 83 | - 浏览器主线程: 渲染界面,处理 UI 更新 84 | - Web Worker: 负责计算方面的内容,如内容解密。 85 | 86 | 数据传输: 生成 blob url (`URL.createObjectURL`) 然后透过 `postMessage` 传递给线程,线程利用 `fetch` API 来获取文件信息。 87 | 88 | ### 贡献代码 89 | 90 | 欢迎贡献代码。请确保: 91 | 92 | - 单元测试的覆盖率不低于主分支的覆盖率; 93 | - 提交前请使用 Prettier 对代码进行格式化; 94 | - 提交前请确保 ESLint 不会报告 warning / error; 95 | 96 | 满足上述条件后发起 Pull Request,仓库管理员审阅后将合并到主分支。 97 | 98 | ## TODO 99 | 100 | - 待定 101 | - [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67) 102 | - [ ] #7 简易元数据编辑器 103 | - 完成 104 | - [x] #8 ~~添加单元测试~~ 框架加上了,以后慢慢添加更多测试即可。 105 | - [x] #2 解密内容探测 (解密过程) 106 | - [x] #6 文件拖放 (利用 `react-dropzone`?) 107 | -------------------------------------------------------------------------------- /src/icons.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | --------------------------------------------------------------------------------