17 Commits

Author SHA1 Message Date
鲁树人
acb7a634b1 docs: update readme on where to find artifact 2024-12-15 21:39:52 +09:00
鲁树人
ce969af57f CI: 替换 Drone CI 为 Gitea 内建的 Actions (#80)
自动构建相关。

Reviewed-on: https://git.unlock-music.dev/um/um-react/pulls/80
Co-authored-by: 鲁树人 <lu.shuren@um-react.app>
Co-committed-by: 鲁树人 <lu.shuren@um-react.app>
2024-12-15 12:37:40 +00:00
鲁树人
ec4bd16b03 test: make vite happy 2024-12-15 04:52:36 +09:00
鲁树人
531930a6ec chore: bump version to 0.3.3 2024-12-15 04:51:12 +09:00
鲁树人
3862f2d38e fix: update packages 2024-12-15 03:57:14 +09:00
鲁树人
ddc073fbcc chore: move husky to sgh 2024-12-15 03:57:08 +09:00
鲁树人
82dbfc2d1f chore: bump node version 2024-12-15 03:57:00 +09:00
鲁树人
87d2d71193 chore: add packageManager field 2024-10-14 11:03:53 +09:00
鲁树人
759252cec5 docs: improve faq page
- add toc for faq page
- fix #79: add note about oem build
- add plat form specific instructions for qqmusic faq
2024-10-14 11:03:01 +09:00
鲁树人
afc65fd5d0 docs: added notes about mac client version requirements (close #70) 2024-10-13 05:54:12 +09:00
鲁树人
9f587212bc docs: remove todo list, add link to crypto issues 2024-10-13 05:51:31 +09:00
鲁树人
9ede00037e docs: fix link to um-crypto docs 2024-10-13 05:48:35 +09:00
鲁树人
0951963f46 docs: format with prettier 2024-10-13 05:47:07 +09:00
鲁树人
c57bc9cfbb docs: add do not fork notice 2024-10-13 05:46:38 +09:00
鲁树人
b16e3bf3ea chore: update readme 2024-09-26 21:15:03 +01:00
鲁树人
e9a95d1bd6 0.3.2 2024-09-26 21:06:00 +01:00
鲁树人
00813957d6 feat: make unsaved settings obvious 2024-09-26 21:02:42 +01:00
25 changed files with 2565 additions and 3363 deletions

View File

@@ -1,35 +0,0 @@
---
kind: pipeline
type: docker
name: default
steps:
- name: test & build
image: node:20.10.0-bookworm
commands:
# - git config --global --add safe.directory "/drone/src"
- corepack enable
- corepack prepare pnpm@latest --activate
- pnpm i --frozen-lockfile
- pnpm build
environment:
# 让 npm 使用淘宝源
npm_config_registry: https://registry.npmmirror.com
- name: publish
image: node:20.10.0-bookworm
environment:
DRONE_GITEA_SERVER: https://git.unlock-music.dev
GITEA_API_KEY:
from_secret: GITEA_API_KEY
NETLIFY_SITE_ID:
from_secret: NETLIFY_SITE_ID
NETLIFY_API_KEY:
from_secret: NETLIFY_API_KEY
commands:
- |
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/deploy.sh

View File

@@ -11,5 +11,5 @@ charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
insert_final_newline = true insert_final_newline = true
[*.{{c,m,}js{x,on,},ts{x,}}] [*.{{c,m,}js{x,on,},ts{x,},y{,a}ml}]
indent_size = 2 indent_size = 2

View File

@@ -0,0 +1,34 @@
name: Build and Deploy
on: [push]
jobs:
build:
runs-on: ubuntu-latest
env:
npm_config_registry: https://registry.npmmirror.com
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: Setup pnpm
uses: pnpm/action-setup@v4.0.0
with:
standalone: true
run_install: |
- args: [--frozen-lockfile, --strict-peer-dependencies]
- name: Build
run: pnpm build
- name: Prepare for deployment
run: |
python3 -m zipfile -c um-react.zip dist/.
cp um-react.zip dist/"release-${GITHUB_SHA}.zip"
python3 -m zipfile -c um-react-site.zip dist/.
- name: Publish Artifact
uses: christopherhx/gitea-upload-artifact@v4
with:
name: site
path: dist/
- name: Deploy
env:
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
NETLIFY_API_KEY: ${{ secrets.NETLIFY_API_KEY }}
run: ./scripts/deploy.sh

View File

@@ -1 +0,0 @@
pnpm exec lint-staged

View File

@@ -1 +0,0 @@
pnpm test

4
.npmrc
View File

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

View File

@@ -7,14 +7,17 @@
- Unlock Music 项目是以学习和技术研究的初衷创建的,修改、再分发时请遵循[授权协议]。 - Unlock Music 项目是以学习和技术研究的初衷创建的,修改、再分发时请遵循[授权协议]。
- Unlock Music 的 CLI 版本可以在 [unlock-music/cli] 找到,大批量转换建议使用 CLI 版本。 - Unlock Music 的 CLI 版本可以在 [unlock-music/cli] 找到,大批量转换建议使用 CLI 版本。
- 我们新建了 Telegram 群组 [`@unlock_music_chat`] ,欢迎加入! - 我们新建了 Telegram 群组 [`@unlock_music_chat`] ,欢迎加入!
- CI 自动构建已经部署,可以在 [Packages][um-react-packages] 下载。 - CI 自动构建已经部署,可以在 [Actions][um-react-actions] 寻找对应的<ruby>构建产物<rp>(</rp><rt>Artifact</rt><rp>)</rp> </ruby>下载。
- [常见问题参考](./docs/faq_zh-hans.md) - [常见问题参考](./docs/faq_zh-hans.md)
> **WARNING**
> 在本站 fork 不会起到备份的作用,只会浪费服务器储存空间。如无必要请勿 fork 该仓库。
[授权协议]: https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE [授权协议]: https://git.unlock-music.dev/um/um-react/src/branch/main/LICENSE
[um-vue]: https://git.unlock-music.dev/um/web [um-vue]: https://git.unlock-music.dev/um/web
[unlock-music/cli]: https://git.unlock-music.dev/um/cli [unlock-music/cli]: https://git.unlock-music.dev/um/cli
[`@unlock_music_chat`]: https://t.me/unlock_music_chat [`@unlock_music_chat`]: https://t.me/unlock_music_chat
[um-react-packages]: https://git.unlock-music.dev/um/-/packages/generic/um-react/ [um-react-actions]: https://git.unlock-music.dev/um/um-react/actions
⚠️ 手机端浏览器支持有限,请使用最新版本的 Chrome 或 Firefox 官方浏览器。 ⚠️ 手机端浏览器支持有限,请使用最新版本的 Chrome 或 Firefox 官方浏览器。
@@ -30,20 +33,28 @@
- [x] 虾米音乐 (`.xm`) - [x] 虾米音乐 (`.xm`)
- [x] 酷我音乐 (`.kwm`) - [x] 酷我音乐 (`.kwm`)
- [x] 酷狗音乐 (`.kgm` / `.vpr`) - [x] 酷狗音乐 (`.kgm` / `.vpr`)
- [x] 喜马拉雅 Android 端 (`.x2m` / `.x3m`) - [x] 喜马拉雅 (`.x2m` / `.x3m` / `.xm`)
- [x] 咪咕音乐格式 (`.mg3d`) - [x] 咪咕音乐格式 (`.mg3d`)
- [x] 蜻蜓 FM (`.qta`) - [x] 蜻蜓 FM (`.qta`)
- [ ] ~~<ruby>QQ 音乐海外版<rt>JOOX Music</rt></ruby> (`.ofl_en`)~~ - [ ] ~~<ruby>QQ 音乐海外版<rt>JOOX Music</rt></ruby> (`.ofl_en`)~~
[^qm-key-pc]: PC 客户端仅支持 v19.43 或更低版本。 [^qm-key-pc]: PC 客户端仅支持 v19.43 或更低版本。
[^qm-key-android]: 需要获取超级管理员权限后提取密钥数据库,并导入后使用。 [^qm-key-android]: 需要获取超级管理员权限后提取密钥数据库,并导入后使用。
[^qm-key-ios]: 需要越狱获取密钥数据库,或对设备进行完整备份后提取密钥数据库,并导入后使用。 [^qm-key-ios]: 需要越狱获取密钥数据库,或对设备进行完整备份后提取密钥数据库,并导入后使用。
[^qm-key-mac]: 需要导入密钥数据库。 [^qm-key-mac]: 需要导入密钥数据库。
不支持的格式?请提交样本(加密文件)与客户端信息(或一并上传其安装包)到[仓库的问题追踪区][project-issues] ## 错误报告
。如果文件太大,请上传到不需要登入下载的网盘,如 [mega.nz](https://mega.nz)、[OneDrive](https://www.onedrive.com/) 等。
如果遇到解密出错的情况,请一并携带错误信息并简单描述错误的重现过程 有不支持的格式?请提交样本(加密文件)与客户端信息版本信息(如系统版本、下载渠道),或一并上传其安装包到[仓库的问题追踪区][project-issues]
⚠️ 如果文件太大,请上传到不需要登入下载的网盘,如 [mega.nz](https://mega.nz)、[OneDrive](https://www.onedrive.com/) 等。
遇到解密出错的情况,请一并携带错误信息(诊断信息)并简单描述错误的重现过程。
待实现的算法支持可[追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67)。
[project-issues]: https://git.unlock-music.dev/um/um-react/issues/new [project-issues]: https://git.unlock-music.dev/um/um-react/issues/new
@@ -55,7 +66,7 @@
⚠️ 如果只是进行前端方面的更改,你可以跳过该节。 ⚠️ 如果只是进行前端方面的更改,你可以跳过该节。
请参考文档「[面向 `@unlock-music/crypto` 开发](./docs/develop-with-um_crypto.zh)」。 请参考文档「[面向 `@unlock-music/crypto` 开发](./docs/develop-with-um_crypto.zh.md)」。
### 架构 ### 架构
@@ -87,13 +98,3 @@
[webview2_redist]: https://go.microsoft.com/fwlink/p/?LinkId=2124703 [webview2_redist]: https://go.microsoft.com/fwlink/p/?LinkId=2124703
有新的项目提交?欢迎[提交 issue][project-issues],请带上项目名称和链接。 有新的项目提交?欢迎[提交 issue][project-issues],请带上项目名称和链接。
## TODO
- 待定
- [ ] 各类算法 [追踪 `crypto` 标签](https://git.unlock-music.dev/um/um-react/issues?labels=67)
- 完成
- [x] #7 ~~简易元数据编辑器~~ 放弃
- [x] #8 ~~添加单元测试~~ 框架加上了,以后慢慢添加更多测试即可。
- [x] #2 解密内容探测 (解密过程)
- [x] #6 文件拖放 (利用 `react-dropzone`?)

View File

@@ -1,7 +1,7 @@
{ {
"name": "um-react", "name": "um-react",
"private": true, "private": true,
"version": "0.3.1", "version": "0.3.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"start": "vite", "start": "vite",
@@ -14,66 +14,71 @@
"test:coverage": "vitest run --coverage", "test:coverage": "vitest run --coverage",
"preview": "vite preview", "preview": "vite preview",
"preview:coverage": "vite preview --outDir coverage --port 5175", "preview:coverage": "vite preview --outDir coverage --port 5175",
"prepare": "husky install" "prepare": "simple-git-hooks"
}, },
"dependencies": { "dependencies": {
"@chakra-ui/anatomy": "^2.2.2", "@chakra-ui/anatomy": "^2.3.4",
"@chakra-ui/icons": "^2.1.1", "@chakra-ui/icons": "^2.2.4",
"@chakra-ui/react": "^2.8.2", "@chakra-ui/react": "^2.10.4",
"@emotion/react": "^11.11.1", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.11.0", "@emotion/styled": "^11.14.0",
"@reduxjs/toolkit": "^2.0.1", "@reduxjs/toolkit": "^2.5.0",
"@unlock-music/crypto": "0.1.0", "@unlock-music/crypto": "0.1.2",
"framer-motion": "^11.5.6", "framer-motion": "^11.14.4",
"nanoid": "^5.0.7", "nanoid": "^5.0.9",
"radash": "^12.1.0", "radash": "^12.1.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.3.5",
"react-icons": "^5.3.0", "react-icons": "^5.4.0",
"react-promise-suspense": "^0.3.4", "react-promise-suspense": "^0.3.4",
"react-redux": "^9.1.2", "react-redux": "^9.2.0",
"react-syntax-highlighter": "^15.5.0", "react-syntax-highlighter": "^15.6.1",
"sass": "^1.79.3", "sass": "^1.83.0",
"sql.js": "^1.11.0" "sql.js": "^1.12.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-replace": "^6.0.1", "@rollup/plugin-replace": "^6.0.1",
"@testing-library/jest-dom": "^6.5.0", "@testing-library/jest-dom": "^6.6.3",
"@testing-library/react": "^16.0.1", "@testing-library/react": "^16.1.0",
"@testing-library/user-event": "^14.5.2", "@testing-library/user-event": "^14.5.2",
"@types/node": "^22.6.1", "@types/node": "^22.10.2",
"@types/react": "^18.3.9", "@types/react": "^18.3.16",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.5",
"@types/react-syntax-highlighter": "^15.5.13", "@types/react-syntax-highlighter": "^15.5.13",
"@types/sql.js": "^1.4.9", "@types/sql.js": "^1.4.9",
"@typescript-eslint/eslint-plugin": "^8.7.0", "@typescript-eslint/eslint-plugin": "^8.18.0",
"@typescript-eslint/parser": "^8.7.0", "@typescript-eslint/parser": "^8.18.0",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.4",
"@vitest/coverage-v8": "^2.1.1", "@vitest/coverage-v8": "^2.1.8",
"@vitest/ui": "^2.1.1", "@vitest/ui": "^2.1.8",
"eslint": "^8.57.1", "eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0", "eslint-config-prettier": "^9.1.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.12", "eslint-plugin-react-refresh": "^0.4.16",
"husky": "^9.1.6", "husky": "^9.1.7",
"jsdom": "^25.0.1", "jsdom": "^25.0.1",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.11",
"prettier": "^3.3.3", "prettier": "^3.4.2",
"rollup": "^4.22.4", "rollup": "^4.28.1",
"terser": "^5.33.0", "simple-git-hooks": "^2.11.1",
"typescript": "^5.6.2", "terser": "^5.37.0",
"vite": "^5.4.7", "typescript": "^5.7.2",
"vite": "^5.4.11",
"vite-plugin-pwa": "^0.20.5", "vite-plugin-pwa": "^0.20.5",
"vite-plugin-top-level-await": "^1.4.4", "vite-plugin-top-level-await": "^1.4.4",
"vite-plugin-wasm": "^3.3.0", "vite-plugin-wasm": "^3.3.0",
"vitest": "^2.1.1", "vitest": "^2.1.8",
"workbox-window": "^7.1.0" "workbox-window": "^7.3.0"
}, },
"lint-staged": { "lint-staged": {
"*": "prettier --write --ignore-unknown", "*": "prettier --write --ignore-unknown",
"*.{js,jsx,ts,tsx}": "eslint --fix --report-unused-disable-directives --max-warnings 0" "*.{js,jsx,ts,tsx}": "eslint --fix --report-unused-disable-directives --max-warnings 0"
}, },
"simple-git-hooks": {
"pre-commit": "pnpm exec lint-staged",
"pre-push": "pnpm test"
},
"prettier": { "prettier": {
"singleQuote": true, "singleQuote": true,
"printWidth": 120, "printWidth": 120,
@@ -88,5 +93,6 @@
"rollup-plugin-terser": "npm:@rollup/plugin-terser@0.4.3", "rollup-plugin-terser": "npm:@rollup/plugin-terser@0.4.3",
"sourcemap-codec": "npm:@jridgewell/sourcemap-codec@1.4.15" "sourcemap-codec": "npm:@jridgewell/sourcemap-codec@1.4.15"
} }
} },
"packageManager": "pnpm@9.12.1+sha512.e5a7e52a4183a02d5931057f7a0dbff9d5e9ce3161e33fa68ae392125b79282a8a8a470a51dfc8a0ed86221442eb2fb57019b0990ed24fab519bf0e1bc5ccfc4"
} }

5337
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -103,7 +103,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 (branch: ${BRANCH_NAME})..."
deploy_netlify um-react-site.zip deploy_netlify um-react-site.zip
else else
echo "skip netlify deployment." echo "skip netlify deployment."

View File

@@ -1,26 +0,0 @@
import { Heading } from '@chakra-ui/react';
import React from 'react';
export interface Header3Props {
children: React.ReactNode;
id?: string;
className?: string;
}
export function Header3({ children, className, id }: Header3Props) {
return (
<Heading
as="h3"
id={id}
className={className}
pt={3}
pb={1}
borderBottom={'1px solid'}
borderColor="gray.300"
color="gray.800"
size="lg"
>
{children}
</Heading>
);
}

View File

@@ -1,16 +0,0 @@
import { Heading } from '@chakra-ui/react';
import React from 'react';
export interface Header4Props {
children: React.ReactNode;
id?: string;
className?: string;
}
export function Header4({ children, className, id }: Header4Props) {
return (
<Heading as="h4" id={id} className={className} pt={3} pb={1} color="gray.700" size="md">
{children}
</Heading>
);
}

View File

@@ -0,0 +1,42 @@
import { Heading } from '@chakra-ui/react';
import React from 'react';
export interface HeaderProps {
children: React.ReactNode;
id?: string;
className?: string;
}
export function Header3({ children, className, id }: HeaderProps) {
return (
<Heading
as="h3"
id={id}
className={className}
pt={3}
pb={1}
borderBottom={'1px solid'}
borderColor="gray.300"
color="gray.800"
size="lg"
>
{children}
</Heading>
);
}
export function Header4({ children, className, id }: HeaderProps) {
return (
<Heading as="h4" id={id} className={className} pt={3} pb={1} color="gray.700" size="md">
{children}
</Heading>
);
}
export function Header5({ children, className, id }: HeaderProps) {
return (
<Heading as="h5" id={id} className={className} pt={3} pb={1} color="gray.700" size="sm">
{children}
</Heading>
);
}

View File

@@ -1,5 +1,7 @@
export const toArrayBuffer = async (src: Blob | ArrayBuffer) => (src instanceof Blob ? await src.arrayBuffer() : src); export const toArrayBuffer = async (src: Blob | ArrayBuffer | Uint8Array<ArrayBufferLike>) =>
export const toBlob = (src: Blob | ArrayBuffer) => (src instanceof Blob ? src : new Blob([src])); src instanceof Blob ? await src.arrayBuffer() : src;
export const toBlob = (src: Blob | ArrayBuffer | Uint8Array<ArrayBufferLike>) =>
src instanceof Blob ? src : new Blob([src]);
export function* chunkBuffer(buffer: Uint8Array, blockLen = 4096): Generator<[Uint8Array, number], void> { export function* chunkBuffer(buffer: Uint8Array, blockLen = 4096): Generator<[Uint8Array, number], void> {
const len = buffer.byteLength; const len = buffer.byteLength;

View File

@@ -1,5 +1,5 @@
import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, chakra } from '@chakra-ui/react'; import { Alert, AlertIcon, Container, Flex, List, ListItem, Text } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Header4'; import { Header4 } from '~/components/HelpText/Headers';
import { VQuote } from '~/components/HelpText/VQuote'; import { VQuote } from '~/components/HelpText/VQuote';
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer'; import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
import { HiWord } from '~/components/HelpText/HiWord'; import { HiWord } from '~/components/HelpText/HiWord';
@@ -15,9 +15,6 @@ export function KuwoFAQ() {
<SegmentTryOfficialPlayer /> <SegmentTryOfficialPlayer />
</ListItem> </ListItem>
<ListItem> <ListItem>
<Text>
<chakra.strong>2</chakra.strong>
</Text>
<Text> <Text>
<HiWord></HiWord> <HiWord></HiWord>
<VQuote> <VQuote>
@@ -38,10 +35,10 @@ export function KuwoFAQ() {
<Flex flexDir="column"> <Flex flexDir="column">
<Text> root </Text> <Text> root </Text>
<Text> <Text>
<strong></strong> <strong></strong>
</Text> </Text>
<Text> <Text>
<strong></strong>使使 <strong></strong>使使
</Text> </Text>
</Flex> </Flex>
</Alert> </Alert>

View File

@@ -1,6 +1,6 @@
import { Alert, AlertIcon, Code, Container, Flex, 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/Headers';
import { VQuote } from '~/components/HelpText/VQuote'; import { VQuote } from '~/components/HelpText/VQuote';
import { ProjectIssue } from '~/components/ProjectIssue'; import { ProjectIssue } from '~/components/ProjectIssue';
import LdPlayerSettingsScreen from './assets/ld_settings_misc.webp'; import LdPlayerSettingsScreen from './assets/ld_settings_misc.webp';
@@ -63,7 +63,7 @@ export function OtherFAQ() {
<AlertIcon /> <AlertIcon />
<Flex flexDir="column"> <Flex flexDir="column">
<Text> <Text>
<strong></strong>使<strong></strong> <strong></strong>使<strong></strong>
{';使用前请自行评估风险。'} {';使用前请自行评估风险。'}
</Text> </Text>
</Flex> </Flex>

View File

@@ -1,63 +1,159 @@
import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, UnorderedList, chakra } from '@chakra-ui/react'; import { Accordion, AccordionButton, AccordionIcon, AccordionItem, AccordionPanel, Box } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Header4'; import { Alert, AlertIcon, Container, Flex, ListItem, Text, UnorderedList } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Headers';
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer'; import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
import { QMCv2QQMusicAllInstructions } from '~/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions';
import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions'; import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions';
import { ExtLink } from '~/components/ExtLink'; import { ExtLink } from '~/components/ExtLink';
import { AndroidADBPullInstruction } from '~/components/AndroidADBPullInstruction/AndroidADBPullInstruction';
import { InstructionsIOS } from '~/features/settings/panels/QMCv2/InstructionsIOS';
import { InstructionsMac } from '~/features/settings/panels/QMCv2/InstructionsMac';
export function QQMusicFAQ() { export function QQMusicFAQ() {
return ( return (
<> <>
<Header4></Header4> <Header4></Header4>
<List spacing={2}> <SegmentTryOfficialPlayer />
<ListItem> <Text></Text>
<SegmentTryOfficialPlayer /> <Text>
</ListItem> <strong></strong>使
<ListItem> </Text>
<Text> <Accordion allowToggle my={2}>
<chakra.strong>2</chakra.strong> <AccordionItem>
</Text> <h2>
<Text> <AccordionButton>
Windows 19.43 <Box as="span" flex="1" textAlign="left">
QQ Windows v19.43 Windows
</Text> </Box>
<UnorderedList pl={3}> <AccordionIcon />
<ListItem> </AccordionButton>
<Text> </h2>
<ExtLink href="https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1943.exe"> <AccordionPanel pb={4}>
<code>qq.com</code> <Text>
</ExtLink> Windows 19.51
</Text> </Text>
</ListItem> <Text> QQ Windows v19.51 </Text>
<ListItem> <UnorderedList pl={3}>
<Text> <ListItem>
<ExtLink href="https://web.archive.org/web/2023/https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1943.exe"> <Text>
<code>Archive.org</code> <ExtLink href="https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1951.exe">
</ExtLink> <code>qq.com</code>
</Text> </ExtLink>
</ListItem> </Text>
</UnorderedList> </ListItem>
<ListItem>
<Text>
<ExtLink href="https://web.archive.org/web/2023/https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1951.exe">
<code>Archive.org</code>
</ExtLink>
</Text>
</ListItem>
</UnorderedList>
</AccordionPanel>
</AccordionItem>
<Container p={2}> <AccordionItem>
<Alert status="warning" borderRadius={5}> <h2>
<AlertIcon /> <AccordionButton>
<Flex flexDir="column"> <Box as="span" flex="1" textAlign="left">
<Text>iOS </Text> Mac
<Text>root</Text> </Box>
</Flex> <AccordionIcon />
</Alert> </AccordionButton>
</Container> </h2>
<AccordionPanel pb={4}>
<Container p={2}>
<Alert status="warning" borderRadius={5}>
<AlertIcon />
<Flex flexDir="column">
<Text>Mac 8.8.0 </Text>
<Text>
<ExtLink href="https://web.archive.org/web/20230903/https://dldir1.qq.com/music/clntupate/mac/QQMusicMac_Mgr.dmg">
<code>Archive.org</code>
</ExtLink>
</Text>
</Flex>
</Alert>
</Container>
<Container p={2} pt={0}> <SegmentKeyImportInstructions
<Alert status="info" borderRadius={5}> tab="QMCv2 密钥"
<AlertIcon /> keyInstructionText="查看密钥提取说明:"
clientInstructions={
</Alert> <Box p={2}>
</Container> <InstructionsMac />
</Box>
}
/>
</AccordionPanel>
</AccordionItem>
<SegmentKeyImportInstructions tab="QMCv2 密钥" clientInstructions={<QMCv2QQMusicAllInstructions />} /> <AccordionItem>
</ListItem> <h2>
</List> <AccordionButton>
<Box as="span" flex="1" textAlign="left">
(Android)
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4}>
<Container p={2}>
<Alert status="warning" borderRadius={5}>
<AlertIcon />
<Flex flexDir="column">
<Text> root </Text>
</Flex>
</Alert>
</Container>
<Text>QQ </Text>
<Text>
使QQ OEM
</Text>
<SegmentKeyImportInstructions
tab="QMCv2 密钥"
keyInstructionText="查看密钥提取说明:"
clientInstructions={
<Box p={2}>
<AndroidADBPullInstruction dir="/data/data/com.tencent.qqmusic/databases" file="player_process_db" />
</Box>
}
/>
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<h2>
<AccordionButton>
<Box as="span" flex="1" textAlign="left">
iOS (iPhone, iPad)
</Box>
<AccordionIcon />
</AccordionButton>
</h2>
<AccordionPanel pb={4}>
<Container p={2}>
<Alert status="warning" borderRadius={5}>
<AlertIcon />
<Flex flexDir="column">
<Text>iOS </Text>
</Flex>
</Alert>
</Container>
<SegmentKeyImportInstructions
tab="QMCv2 密钥"
keyInstructionText="查看密钥提取说明:"
clientInstructions={
<Box p={2}>
<InstructionsIOS />
</Box>
}
/>
</AccordionPanel>
</AccordionItem>
</Accordion>
</> </>
); );
} }

View File

@@ -8,9 +8,14 @@ import { MdFileUpload } from 'react-icons/md';
export interface SegmentKeyImportInstructionsProps { export interface SegmentKeyImportInstructionsProps {
clientInstructions: React.ReactNode; clientInstructions: React.ReactNode;
tab: string; tab: string;
keyInstructionText?: React.ReactNode;
} }
export function SegmentKeyImportInstructions({ clientInstructions, tab }: SegmentKeyImportInstructionsProps) { export function SegmentKeyImportInstructions({
clientInstructions,
tab,
keyInstructionText = '选择你的客户端平台来查看密钥提取说明:',
}: SegmentKeyImportInstructionsProps) {
return ( return (
<> <>
<Text></Text> <Text></Text>
@@ -33,7 +38,7 @@ export function SegmentKeyImportInstructions({ clientInstructions, tab }: Segmen
</Flex> </Flex>
</ListItem> </ListItem>
<ListItem> <ListItem>
<Text></Text> <Text>{keyInstructionText}</Text>
<Tabs display="flex" flexDir="column" border="1px solid" borderColor="gray.300" borderRadius={5}> <Tabs display="flex" flexDir="column" border="1px solid" borderColor="gray.300" borderRadius={5}>
{clientInstructions} {clientInstructions}
</Tabs> </Tabs>

View File

@@ -1,12 +1,12 @@
import { Text, chakra } from '@chakra-ui/react'; import { Alert, AlertIcon, Container } from '@chakra-ui/react';
export function SegmentTryOfficialPlayer() { export function SegmentTryOfficialPlayer() {
return ( return (
<> <Container p={2} my={2} pt={0}>
<Text> <Alert status="info" borderRadius={5}>
<chakra.strong>1</chakra.strong> <AlertIcon />
</Text>
<Text></Text> </Alert>
</> </Container>
); );
} }

View File

@@ -1,8 +1,8 @@
import { import {
chakra,
Box, Box,
Button, Button,
Center, Center,
chakra,
Flex, Flex,
HStack, HStack,
Icon, Icon,
@@ -19,9 +19,9 @@ import {
TabPanels, TabPanels,
Tabs, Tabs,
Text, Text,
VStack,
useBreakpointValue, useBreakpointValue,
useToast, useToast,
VStack,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { PanelQMCv2Key } from './panels/PanelQMCv2Key'; import { PanelQMCv2Key } from './panels/PanelQMCv2Key';
import { useState } from 'react'; import { useState } from 'react';
@@ -145,7 +145,7 @@ export function Settings() {
onClick={handleResetSettings} onClick={handleResetSettings}
colorScheme="red" colorScheme="red"
variant="ghost" variant="ghost"
title="放弃未储存的更改,将设定还原储存前的状态。" title="放弃未储存的更改,将设定还原储存前的状态。"
aria-label="放弃未储存的更改" aria-label="放弃未储存的更改"
/> />
<Button onClick={handleApplySettings}></Button> <Button onClick={handleApplySettings}></Button>

View File

@@ -1,12 +1,23 @@
import { Heading, Text, Code, Kbd, OrderedList, ListItem } from '@chakra-ui/react'; import { Heading, Text, Code, Kbd, OrderedList, ListItem, Link } from '@chakra-ui/react';
import { FilePathBlock } from '~/components/FilePathBlock'; import { FilePathBlock } from '~/components/FilePathBlock';
import { MacCommandKey } from '~/components/Key/MacCommandKey'; import { MacCommandKey } from '~/components/Key/MacCommandKey';
import { ShiftKey } from '~/components/Key/ShiftKey'; import { ShiftKey } from '~/components/Key/ShiftKey';
const MAC_CLIENT_URL =
'https://web.archive.org/web/20230903/https://dldir1.qq.com/music/clntupate/mac/QQMusicMac_Mgr.dmg';
export function InstructionsMac() { export function InstructionsMac() {
return ( return (
<> <>
<Text>Mac 使 mmkv </Text> <Text>Mac 使 mmkv </Text>
<Text>
{'此外,你需要降级到 '}
<Link isExternal href={MAC_CLIENT_URL}>
2023.09.03
</Link>
{'。'}
mmkv
</Text>
<Text></Text> <Text></Text>
<FilePathBlock> <FilePathBlock>
~/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/mmkv/MMKVStreamEncryptId ~/Library/Containers/com.tencent.QQMusicMac/Data/Library/Application Support/QQMusicMac/mmkv/MMKVStreamEncryptId

View File

@@ -3,8 +3,8 @@ import { Text } from '@chakra-ui/react';
export function InstructionsPC() { export function InstructionsPC() {
return ( return (
<> <>
<Text>使 Windows 19.43 </Text> <Text>使 Windows 19.51 </Text>
<Text>使 Windows 19.51 </Text> <Text>使 Windows 19.57 </Text>
</> </>
); );
} }

View File

@@ -1,5 +1,5 @@
import { Center, Container, Heading } from '@chakra-ui/react'; import { Center, Container, Heading, Link, ListItem, UnorderedList } from '@chakra-ui/react';
import { Header3 } from '~/components/HelpText/Header3'; import { Header3 } from '~/components/HelpText/Headers';
import { KuwoFAQ } from '~/faq/KuwoFAQ'; import { KuwoFAQ } from '~/faq/KuwoFAQ';
import { OtherFAQ } from '~/faq/OtherFAQ'; import { OtherFAQ } from '~/faq/OtherFAQ';
import { QQMusicFAQ } from '~/faq/QQMusicFAQ'; import { QQMusicFAQ } from '~/faq/QQMusicFAQ';
@@ -10,11 +10,23 @@ export function FaqTab() {
<Center> <Center>
<Heading as="h2"></Heading> <Heading as="h2"></Heading>
</Center> </Center>
<Header3>QQ </Header3> <Header3></Header3>
<UnorderedList>
<ListItem>
<Link href="#faq-qqmusic">QQ </Link>
</ListItem>
<ListItem>
<Link href="#faq-kuwo"></Link>
</ListItem>
<ListItem>
<Link href="#faq-other"></Link>
</ListItem>
</UnorderedList>
<Header3 id="faq-qqmusic">QQ </Header3>
<QQMusicFAQ /> <QQMusicFAQ />
<Header3></Header3> <Header3 id="faq-kuwo"></Header3>
<KuwoFAQ /> <KuwoFAQ />
<Header3></Header3> <Header3 id="faq-other"></Header3>
<OtherFAQ /> <OtherFAQ />
</Container> </Container>
); );

View File

@@ -1,12 +1,36 @@
import { Box, VStack } from '@chakra-ui/react'; import { Alert, AlertIcon, Box, Button, Flex, Text, VStack } from '@chakra-ui/react';
import { SelectFile } from '../components/SelectFile'; import { SelectFile } from '../components/SelectFile';
import { FileListing } from '~/features/file-listing/FileListing'; import { FileListing } from '~/features/file-listing/FileListing';
import { useAppDispatch, useAppSelector } from '~/hooks.ts';
import { selectIsSettingsNotSaved } from '~/features/settings/settingsSelector.ts';
import { commitStagingChange } from '~/features/settings/settingsSlice.ts';
export function MainTab() { export function MainTab() {
const dispatch = useAppDispatch();
const isSettingsNotSaved = useAppSelector(selectIsSettingsNotSaved);
const onClickSaveSettings = () => {
dispatch(commitStagingChange());
};
return ( return (
<Box h="full" w="full" pt="4"> <Box h="full" w="full" pt="4">
<VStack gap="3"> <VStack gap="3">
{isSettingsNotSaved && (
<Alert borderRadius={7} maxW={400} status="warning">
<AlertIcon />
<Flex flexDir="row" alignItems="center" flexGrow={1} justifyContent="space-between">
<Text m={0}>
<br />
</Text>
<Button type="button" ml={3} size="md" onClick={onClickSaveSettings}>
</Button>
</Flex>
</Alert>
)}
<SelectFile /> <SelectFile />
<Box w="full"> <Box w="full">

View File

@@ -101,7 +101,7 @@ test('it should move on to the next item in the queue once failed', async () =>
await expect(badPromise).rejects.toThrowError('dummy error'); await expect(badPromise).rejects.toThrowError('dummy error');
queuedResolver.forEach((resolve) => resolve()); queuedResolver.forEach((resolve) => resolve());
expect(Promise.all(promises)).resolves.toEqual([1, 2, 4, 5]); await expect(Promise.all(promises)).resolves.toEqual([1, 2, 4, 5]);
} finally { } finally {
vi.spyOn(queue, 'handler').mockRejectedValue(new Error('handler ran too late')); vi.spyOn(queue, 'handler').mockRejectedValue(new Error('handler ran too late'));
queuedResolver.forEach((resolve) => resolve()); queuedResolver.forEach((resolve) => resolve());