18 Commits

Author SHA1 Message Date
鲁树人
c1e17992e9 0.2.8 2024-09-12 22:59:19 +01:00
鲁树人
f478ca8818 fix: upgrade libprarkeet to v0.4.5 2024-09-12 22:56:13 +01:00
鲁树人
8e4367fbf9 build: minify final mjs 2024-01-18 00:59:11 +00:00
鲁树人
1ae2f93e99 chore: make win64 build to its own dir 2024-01-18 00:38:01 +00:00
鲁树人
741e302ea7 ci: publish site deployment to netlify as well 2024-01-18 00:29:37 +00:00
鲁树人
f09aa84984 0.2.7 2023-12-28 23:31:28 +00:00
鲁树人
1d2296a02a docs: add wry project to docs and app 2023-12-28 23:31:23 +00:00
鲁树人
f6703160e7 docs: update note on WebView2 2023-12-28 23:14:48 +00:00
鲁树人
bea2f4b7d4 build: make zip archive of final zip 2023-12-28 23:09:56 +00:00
鲁树人
0a3dac9d3d docs: add note about win64 build 2023-12-28 23:00:41 +00:00
鲁树人
602d6865f5 0.2.6 2023-12-28 20:39:50 +00:00
鲁树人
8d4194772e build: include commit hash in version.txt 2023-12-28 20:38:45 +00:00
鲁树人
1ef1db30ab build: include version.txt in dist 2023-12-28 20:36:29 +00:00
鲁树人
80fc595833 chore: add related projects 2023-12-28 20:30:10 +00:00
鲁树人
be9a1b6724 docs: make note about metadata editor 2023-12-28 14:18:11 +00:00
鲁树人
6cccb722ce docs: update warning about android emu 2023-12-25 20:02:55 +01:00
鲁树人
2f9cfaa763 0.2.5 2023-12-25 18:15:56 +01:00
鲁树人
5d7f5b76ef fix: qmcv2 db name matching when musicex was not found 2023-12-25 18:15:51 +01:00
22 changed files with 5322 additions and 4229 deletions

View File

@@ -27,7 +27,9 @@ steps:
NETLIFY_API_KEY: NETLIFY_API_KEY:
from_secret: NETLIFY_API_KEY from_secret: NETLIFY_API_KEY
commands: commands:
# - git config --global --add safe.directory "/drone/src" - |
- python3 -m zipfile -c um-react.zip dist/. python3 -m zipfile -c um-react.zip dist/.
cp um-react.zip dist/release-"${DRONE_COMMIT_SHA}".zip
python3 -m zipfile -c um-react-site.zip dist/.
# - ./scripts/publish.sh # - ./scripts/publish.sh
- ./scripts/deploy.sh - ./scripts/deploy.sh

5
.gitignore vendored
View File

@@ -27,3 +27,8 @@ dist-ssr
# Files created when running "drone exec" locally # Files created when running "drone exec" locally
/.pnpm-store/ /.pnpm-store/
/*.zip /*.zip
/um-react-wry-*
/um-react*.exe
/win64/

1
.npmrc
View File

@@ -1,3 +1,4 @@
use-node-version=20.10.0 use-node-version=20.10.0
node-version=20.10.0 node-version=20.10.0
engine-strict=true engine-strict=true
@um:registry=https://git.unlock-music.dev/api/packages/um/npm/

View File

@@ -73,12 +73,25 @@
满足上述条件后发起 Pull Request仓库管理员审阅后将合并到主分支。 满足上述条件后发起 Pull Request仓库管理员审阅后将合并到主分支。
## 相关项目
- [Unlock Music (Web)](https://git.unlock-music.dev/um/web) - 原始项目
- [Unlock Music (Cli)](https://git.unlock-music.dev/um/cli) - 命令行批量处理版
- [um-react (Electron 前端)](https://github.com/CarlGao4/um-react-electron) - 使用 Electron 框架封装的本地可执行文件。
- [GitHub 下载](https://github.com/CarlGao4/um-react-electron/releases/latest) | [仓库镜像](https://git.unlock-music.dev/CarlGao4/um-react-electron)
- [um-react-wry](https://git.unlock-music.dev/um/um-react-wry) - 使用 WRY 框架封装的 Win64 单文件 (需要[安装 Edge WebView2 运行时][webview2_redist]Win10+ 操作系统自带)
- [本地下载](https://git.unlock-music.dev/um/um-react/releases/latest) | 寻找文件名为 `um-react-win64-` 开头的附件
[webview2_redist]: https://go.microsoft.com/fwlink/p/?LinkId=2124703
有新的项目提交?欢迎[提交 issue][project-issues],请带上项目名称和链接。
## TODO ## TODO
- 待定 - 待定
- [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67) - [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67)
- [ ] #7 简易元数据编辑器
- 完成 - 完成
- [x] #7 ~~简易元数据编辑器~~ 放弃
- [x] #8 ~~添加单元测试~~ 框架加上了,以后慢慢添加更多测试即可。 - [x] #8 ~~添加单元测试~~ 框架加上了,以后慢慢添加更多测试即可。
- [x] #2 解密内容探测 (解密过程) - [x] #2 解密内容探测 (解密过程)
- [x] #6 文件拖放 (利用 `react-dropzone`?) - [x] #6 文件拖放 (利用 `react-dropzone`?)

View File

@@ -63,6 +63,8 @@
如果希望不破坏系统完整性,你可以考虑使用模拟器。 如果希望不破坏系统完整性,你可以考虑使用模拟器。
**注意**:根据应用厂商的风控策略,使用模拟器登录的账号**有可能会被封锁**;使用前请自行评估风险。
目前常见的带有 root 特权支持的的安卓模拟器方案,分别是雷电模拟器(※ 官方版有内置广告)和微软在 Windows 11 开始支援的适用于 Android™ 的 Windows 子系统 (WSA)。 目前常见的带有 root 特权支持的的安卓模拟器方案,分别是雷电模拟器(※ 官方版有内置广告)和微软在 Windows 11 开始支援的适用于 Android™ 的 Windows 子系统 (WSA)。
- WSA 可以参考 [MagiskOnWSALocal](https://github.com/LSPosed/MagiskOnWSALocal) 的说明操作。 - WSA 可以参考 [MagiskOnWSALocal](https://github.com/LSPosed/MagiskOnWSALocal) 的说明操作。

View File

@@ -33,3 +33,31 @@ pnpm build
如果需要预览构建版本,运行 `pnpm preview` 然后打开[项目预览页面][vite-preview-url]即可。 如果需要预览构建版本,运行 `pnpm preview` 然后打开[项目预览页面][vite-preview-url]即可。
[vite-preview-url]: http://localhost:4173/ [vite-preview-url]: http://localhost:4173/
## 打包 `.zip`
建议在 Linux 环境下执行,可参考 `.drone.yml` CI 文件。
1. 确保上述的构建步骤已完成。
2. 确保 `python3` 已安装。
3. 执行下述代码
```sh
python3 -m zipfile -c um-react.zip dist/.
```
## 打包 win64 单文件
利用 Windows 系统自带的 [Edge WebView2 组件](https://learn.microsoft.com/zh-cn/microsoft-edge/webview2/)
和 [wry](https://github.com/tauri-apps/wry) 进行一个单文件的打包。
大部分 Windows 10 或以上版本的操作系统已经集成了 WebView2 运行时。若无法正常启动,请[下载并安装 Edge WebView2 运行时](https://go.microsoft.com/fwlink/p/?LinkId=2124703)。
其它系统兼容性未知。
1. 确保你现在在 `linux-amd64` 环境下。
2. 确保上述的 `um-react.zip` 构建已完成。
3. 执行下述代码
```sh
./scripts/make-win64.sh
```
4. 等待提示 `[Build OK]` 即可。

View File

@@ -1,11 +1,12 @@
{ {
"name": "um-react", "name": "um-react",
"private": true, "private": true,
"version": "0.2.4", "version": "0.2.8",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
"build": "tsc -p tsconfig.prod.json && vite build", "build": "tsc -p tsconfig.prod.json && vite build && pnpm build:finalize",
"build:finalize": "node scripts/write-version.mjs && node scripts/minify-mjs.mjs",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"format": "prettier -w .", "format": "prettier -w .",
"test": "vitest run", "test": "vitest run",
@@ -21,8 +22,8 @@
"@chakra-ui/react": "^2.8.2", "@chakra-ui/react": "^2.8.2",
"@emotion/react": "^11.11.1", "@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.11.0",
"@jixun/libparakeet": "0.4.3",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.0.1",
"@um/libparakeet": "0.4.5",
"framer-motion": "^10.16.16", "framer-motion": "^10.16.16",
"nanoid": "^5.0.4", "nanoid": "^5.0.4",
"radash": "^11.0.0", "radash": "^11.0.0",
@@ -59,6 +60,7 @@
"jsdom": "^23.0.1", "jsdom": "^23.0.1",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.0",
"prettier": "^3.1.1", "prettier": "^3.1.1",
"terser": "^5.27.0",
"typescript": "^5.3.3", "typescript": "^5.3.3",
"vite": "^5.0.10", "vite": "^5.0.10",
"vite-plugin-pwa": "^0.17.4", "vite-plugin-pwa": "^0.17.4",

9082
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -104,7 +104,7 @@ deploy_netlify() {
# For deployment, we care a bit less # For deployment, we care a bit less
if [[ -n "${NETLIFY_API_KEY}" && -n "${NETLIFY_SITE_ID}" ]]; then if [[ -n "${NETLIFY_API_KEY}" && -n "${NETLIFY_SITE_ID}" ]]; then
echo "Deploy to netlify..." echo "Deploy to netlify..."
deploy_netlify um-react.zip deploy_netlify um-react-site.zip
else else
echo "skip netlify deployment." echo "skip netlify deployment."
fi fi

33
scripts/make-win64.sh Executable file
View File

@@ -0,0 +1,33 @@
#!/bin/bash
# sudo apt install -y jq zip
pushd "$(dirname "${BASH_SOURCE[0]}")/../"
WRY_VER="0.1.1"
mkdir -p win64/{deps,dist}
dl_file() {
local FILE="$1"
if [[ ! -f "win64/deps/$FILE" ]]; then
curl -fsL "https://um-react.app/files/${FILE}.gz" | gzip -d >"win64/deps/${FILE}"
fi
}
dl_file "um-react-wry-builder-${WRY_VER}-linux-amd64"
dl_file "um-react-wry-stub-${WRY_VER}-win64.exe"
chmod a+x win64/deps/um-react-wry-builder-${WRY_VER}-linux-amd64
APP_VERSION="$(jq -r '.version' <package.json)"
EXE_NAME="um-react-win64-${APP_VERSION}.exe"
ZIP_NAME="um-react-win64-${APP_VERSION}.zip"
"./win64/deps/um-react-wry-builder-${WRY_VER}-linux-amd64" \
-t "win64/deps/um-react-wry-stub-${WRY_VER}-win64.exe" \
-r um-react.zip \
-o "win64/dist/${EXE_NAME}"
touch -d 1970-01-01T00:00:00Z "win64/dist/${EXE_NAME}"
zip -9oX "win64/dist/${ZIP_NAME}" -- "win64/dist/${EXE_NAME}"
echo "[Build OK] 'win64/dist/${ZIP_NAME}'."
popd

19
scripts/minify-mjs.mjs Normal file
View File

@@ -0,0 +1,19 @@
import { minify } from 'terser';
import { readFileSync, writeFileSync, readdirSync } from 'fs';
for (const file of readdirSync('dist/assets')) {
if (!/\.(mjs|js)$/.test(file)) {
continue;
}
console.log(`minifying ${file}...`);
const isModule = /\.mjs$/.test(file);
const output = await minify(readFileSync(`dist/assets/${file}`, 'utf-8'), {
compress: true,
mangle: true,
module: isModule,
});
writeFileSync(`dist/assets/${file}`, output.code);
}

14
scripts/write-version.mjs Normal file
View File

@@ -0,0 +1,14 @@
/* eslint-env node */
import { readFileSync, writeFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';
import { execSync } from 'node:child_process';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const commitHash = execSync('git rev-parse --short HEAD').toString('utf-8').trim();
const pkgJson = JSON.parse(readFileSync(__dirname + '/../package.json', 'utf-8'));
const pkgVer = `${pkgJson.version ?? 'unknown'}-${commitHash ?? 'unknown'}` + '\n';
writeFileSync(__dirname + '/../dist/version.txt', pkgVer, 'utf-8');

View File

@@ -3,7 +3,7 @@ import type { CryptoBase } from '../CryptoBase';
import { KWM_KEY } from './kwm.key'; import { KWM_KEY } from './kwm.key';
import { DecryptCommandOptions } from '~/decrypt-worker/types'; import { DecryptCommandOptions } from '~/decrypt-worker/types';
import { makeQMCv2KeyCrypto } from '~/decrypt-worker/util/qmc2KeyCrypto'; import { makeQMCv2KeyCrypto } from '~/decrypt-worker/util/qmc2KeyCrypto';
import { fetchParakeet } from '@jixun/libparakeet'; import { fetchParakeet } from '@um/libparakeet';
import { stringToUTF8Bytes } from '~/decrypt-worker/util/utf8Encoder'; import { stringToUTF8Bytes } from '~/decrypt-worker/util/utf8Encoder';
// v1 only // v1 only

View File

@@ -1,7 +1,7 @@
import { transformBlob } from '~/decrypt-worker/util/transformBlob'; import { transformBlob } from '~/decrypt-worker/util/transformBlob';
import type { CryptoBase } from '../CryptoBase'; import type { CryptoBase } from '../CryptoBase';
import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts'; import type { DecryptCommandOptions } from '~/decrypt-worker/types.ts';
import { fetchParakeet } from '@jixun/libparakeet'; import { fetchParakeet } from '@um/libparakeet';
import { stringToUTF8Bytes } from '~/decrypt-worker/util/utf8Encoder.ts'; import { stringToUTF8Bytes } from '~/decrypt-worker/util/utf8Encoder.ts';
import { makeQMCv2FooterParser, makeQMCv2KeyCrypto } from '~/decrypt-worker/util/qmc2KeyCrypto.ts'; import { makeQMCv2FooterParser, makeQMCv2KeyCrypto } from '~/decrypt-worker/util/qmc2KeyCrypto.ts';

View File

@@ -1,4 +1,4 @@
import type { Parakeet } from '@jixun/libparakeet'; import type { Parakeet } from '@um/libparakeet';
import { SEED, ENC_V2_KEY_1, ENC_V2_KEY_2 } from '../crypto/qmc/qmc_v2.key'; import { SEED, ENC_V2_KEY_1, ENC_V2_KEY_2 } from '../crypto/qmc/qmc_v2.key';
export const makeQMCv2KeyCrypto = (p: Parakeet) => p.make.QMCv2KeyCrypto(SEED, ENC_V2_KEY_1, ENC_V2_KEY_2); export const makeQMCv2KeyCrypto = (p: Parakeet) => p.make.QMCv2KeyCrypto(SEED, ENC_V2_KEY_1, ENC_V2_KEY_2);

View File

@@ -1,11 +1,11 @@
import { Transformer, Parakeet, TransformResult, fetchParakeet } from '@jixun/libparakeet'; import { Transformer, Parakeet, TransformResult, fetchParakeet } from '@um/libparakeet';
import { toArrayBuffer } from './buffer'; import { toArrayBuffer } from './buffer';
import { UnsupportedSourceFile } from './DecryptError'; import { UnsupportedSourceFile } from './DecryptError';
export async function transformBlob( export async function transformBlob(
blob: Blob | ArrayBuffer, blob: Blob | ArrayBuffer,
transformerFactory: (p: Parakeet) => Transformer | Promise<Transformer>, transformerFactory: (p: Parakeet) => Transformer | Promise<Transformer>,
{ cleanup, parakeet }: { cleanup?: () => void; parakeet?: Parakeet } = {} { cleanup, parakeet }: { cleanup?: () => void; parakeet?: Parakeet } = {},
) { ) {
const registeredCleanupFns: (() => void)[] = []; const registeredCleanupFns: (() => void)[] = [];
if (cleanup) { if (cleanup) {

View File

@@ -1,7 +1,7 @@
import { WorkerServerBus } from '~/util/WorkerEventBus'; import { WorkerServerBus } from '~/util/WorkerEventBus';
import { DECRYPTION_WORKER_ACTION_NAME } from './constants'; import { DECRYPTION_WORKER_ACTION_NAME } from './constants';
import { getSDKVersion } from '@jixun/libparakeet'; import { getSDKVersion } from '@um/libparakeet';
import { workerDecryptHandler } from './worker/handler/decrypt'; import { workerDecryptHandler } from './worker/handler/decrypt';
import { workerParseMusicExMediaName } from './worker/handler/qmcv2_parser'; import { workerParseMusicExMediaName } from './worker/handler/qmcv2_parser';

View File

@@ -1,4 +1,4 @@
import { Parakeet, fetchParakeet } from '@jixun/libparakeet'; import { Parakeet, fetchParakeet } from '@um/libparakeet';
import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils'; import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils';
import type { DecryptCommandOptions, DecryptCommandPayload } from '~/decrypt-worker/types'; import type { DecryptCommandOptions, DecryptCommandPayload } from '~/decrypt-worker/types';
import { allCryptoFactories } from '../../crypto/CryptoFactory'; import { allCryptoFactories } from '../../crypto/CryptoFactory';

View File

@@ -1,4 +1,4 @@
import { fetchParakeet, FooterParserState } from '@jixun/libparakeet'; import { fetchParakeet, FooterParserState } from '@um/libparakeet';
import type { FetchMusicExNamePayload } from '~/decrypt-worker/types'; import type { FetchMusicExNamePayload } from '~/decrypt-worker/types';
import { makeQMCv2FooterParser } from '~/decrypt-worker/util/qmc2KeyCrypto'; import { makeQMCv2FooterParser } from '~/decrypt-worker/util/qmc2KeyCrypto';
import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils'; import { timedLogger, withGroupedLogs as withTimeGroupedLogs } from '~/util/logUtils';
@@ -10,6 +10,7 @@ export const workerParseMusicExMediaName = async ({ id, blobURI }: FetchMusicExN
const blob = await timedLogger(`${label}/fetch-src`, async () => const blob = await timedLogger(`${label}/fetch-src`, async () =>
fetch(blobURI, { headers: { Range: 'bytes=-1024' } }).then((r) => r.blob()), fetch(blobURI, { headers: { Range: 'bytes=-1024' } }).then((r) => r.blob()),
); );
const buffer = await timedLogger(`${label}/read-src`, async () => { const buffer = await timedLogger(`${label}/read-src`, async () => {
// Firefox: the range header does not work...? // Firefox: the range header does not work...?
const blobBuffer = await blob.arrayBuffer(); const blobBuffer = await blob.arrayBuffer();
@@ -18,10 +19,12 @@ export const workerParseMusicExMediaName = async ({ id, blobURI }: FetchMusicExN
} }
return blobBuffer; return blobBuffer;
}); });
const parsed = makeQMCv2FooterParser(parakeet).parse(buffer); const parsed = makeQMCv2FooterParser(parakeet).parse(buffer);
if (parsed.state === FooterParserState.OK) { if (parsed.state === FooterParserState.OK) {
return parsed.mediaName; return parsed.mediaName;
} }
return '# N/A';
return null;
}); });
}; };

View File

@@ -1,4 +1,4 @@
import { Img, ListItem, Text, UnorderedList } from '@chakra-ui/react'; import { Alert, AlertIcon, Code, Container, Flex, Img, ListItem, Text, UnorderedList } from '@chakra-ui/react';
import { ExtLink } from '~/components/ExtLink'; import { ExtLink } from '~/components/ExtLink';
import { Header4 } from '~/components/HelpText/Header4'; import { Header4 } from '~/components/HelpText/Header4';
import { VQuote } from '~/components/HelpText/VQuote'; import { VQuote } from '~/components/HelpText/VQuote';
@@ -57,6 +57,19 @@ export function OtherFAQ() {
</ExtLink> </ExtLink>
</Text> </Text>
<Container p={2}>
<Alert status="warning" borderRadius={5}>
<AlertIcon />
<Flex flexDir="column">
<Text>
<strong></strong>使<strong></strong>
{';使用前请自行评估风险。'}
</Text>
</Flex>
</Alert>
</Container>
<UnorderedList> <UnorderedList>
<ListItem> <ListItem>
<Text> <Text>
@@ -73,6 +86,48 @@ export function OtherFAQ() {
</ListItem> </ListItem>
</UnorderedList> </UnorderedList>
<Header4></Header4>
<UnorderedList>
<ListItem>
<Text>
<ExtLink href="https://github.com/CarlGao4/um-react-electron">
<strong>
<Code>um-react-electron</Code>
</strong>
</ExtLink>
Electron WindowsLinux Mac
</Text>
<UnorderedList>
<ListItem>
<Text>
<ExtLink href="https://github.com/CarlGao4/um-react-electron/releases/latest">GitHub </ExtLink>
</Text>
</ListItem>
</UnorderedList>
</ListItem>
<ListItem>
<Text>
<ExtLink href="https://git.unlock-music.dev/um/um-react-wry">
<strong>
<Code>um-react-wry</Code>
</strong>
</ExtLink>
: 使 WRY Win64
<ExtLink href="https://go.microsoft.com/fwlink/p/?LinkId=2124703"> Edge WebView2 </ExtLink>
{'Win10+ 操作系统自带)'}
</Text>
<UnorderedList>
<ListItem>
<Text>
<ExtLink href="https://git.unlock-music.dev/um/um-react/releases/latest"></ExtLink>
{' | 寻找文件名为 '}
<Code>um-react-win64-</Code>
</Text>
</ListItem>
</UnorderedList>
</ListItem>
</UnorderedList>
<Header4></Header4> <Header4></Header4>
<Text> <Text>
{'欢迎进入 '} {'欢迎进入 '}

View File

@@ -13,7 +13,7 @@ import {
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from '~/hooks'; import { useAppDispatch, useAppSelector } from '~/hooks';
import { fetchParakeet } from '@jixun/libparakeet'; import { fetchParakeet } from '@um/libparakeet';
import { ExtLink } from '~/components/ExtLink'; import { ExtLink } from '~/components/ExtLink';
import { ChangeEvent, ClipboardEvent } from 'react'; import { ChangeEvent, ClipboardEvent } from 'react';
import { VQuote } from '~/components/HelpText/VQuote'; import { VQuote } from '~/components/HelpText/VQuote';

View File

@@ -40,7 +40,7 @@ export default defineConfig({
}, },
base: './', base: './',
optimizeDeps: { optimizeDeps: {
exclude: ['@jixun/libparakeet', 'sql.js'], exclude: ['@um/libparakeet', 'sql.js'],
}, },
plugins: [ plugins: [
replace({ replace({