安卓用户提取密钥需要 root 权限,或注入文件提供器。
diff --git a/src/faq/OtherFAQ.tsx b/src/faq/OtherFAQ.tsx
index 5728440..d6a0331 100644
--- a/src/faq/OtherFAQ.tsx
+++ b/src/faq/OtherFAQ.tsx
@@ -50,8 +50,8 @@ export function OtherFAQ() {
对安卓设备获取 root 特权通常会破坏系统的完整性并导致部分功能无法使用。
例如部分厂商的安卓设备会在解锁后丧失保修资格,或导致无法使用 NFC 移动支付等限制。
-
如果希望不破坏系统完整性,你可以考虑在电脑上使用安卓模拟器。
-
+
如果希望不破坏系统完整性,你可以考虑在电脑上使用安卓模拟器。
+
很多安卓模拟器都提供了 root 特权支持,可以很方便的启用,例如
网易 MuMu 模拟器(安卓 12,推荐)
diff --git a/src/faq/QQMusicFAQ.tsx b/src/faq/QQMusicFAQ.tsx
index b3ce4c9..b1e0391 100644
--- a/src/faq/QQMusicFAQ.tsx
+++ b/src/faq/QQMusicFAQ.tsx
@@ -7,8 +7,8 @@ export function QQMusicFAQ() {
<>
解锁失败
- 重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。
-
+
重复下载同一首的歌曲不重复扣下载配额,但是同一首歌的两个版本会重复扣下载配额,请仔细分辨。
+
部分平台获取的加密文件未包含密钥。选择你下载文件时使用的客户端来查看说明。
diff --git a/src/faq/SegmentKeyImportInstructions.tsx b/src/faq/SegmentKeyImportInstructions.tsx
index 2d5df66..0fb76d6 100644
--- a/src/faq/SegmentKeyImportInstructions.tsx
+++ b/src/faq/SegmentKeyImportInstructions.tsx
@@ -16,7 +16,7 @@ export function SegmentKeyImportInstructions({
}: SegmentKeyImportInstructionsProps) {
return (
<>
- 导入密钥可以参考下面的步骤:
+ 导入密钥可以参考下面的步骤:
-
@@ -28,7 +28,7 @@ export function SegmentKeyImportInstructions({
-
-
{keyInstructionText}
+ {keyInstructionText}
{clientInstructions}
diff --git a/src/faq/SegmentTryOfficialPlayer.tsx b/src/faq/SegmentTryOfficialPlayer.tsx
index 27e5090..f67efee 100644
--- a/src/faq/SegmentTryOfficialPlayer.tsx
+++ b/src/faq/SegmentTryOfficialPlayer.tsx
@@ -1,8 +1,8 @@
import { RiErrorWarningLine } from 'react-icons/ri';
-export function SegmentTryOfficialPlayer() {
+export function SegmentTryOfficialPlayer({ className = '' }: { className?: string }) {
return (
-
+
尝试用下载音乐的设备播放一次看看,如果官方客户端都无法播放,那解锁肯定会失败哦。
diff --git a/src/features/file-listing/FileRow.tsx b/src/features/file-listing/FileRow.tsx
index 72cde1e..6cf8c9f 100644
--- a/src/features/file-listing/FileRow.tsx
+++ b/src/features/file-listing/FileRow.tsx
@@ -11,31 +11,23 @@ interface FileRowProps {
export function FileRow({ id, file }: FileRowProps) {
const dispatch = useAppDispatch();
const isDecrypted = file.state === ProcessState.COMPLETE;
- const metadata = file.metadata;
- const nameWithoutExt = file.fileName.replace(/\.[a-z\d]{3,6}$/, '');
- const decryptedName = nameWithoutExt + '.' + file.ext;
+ const decryptedName = file.cleanName + '.' + file.ext;
return (
-
- {metadata?.name ?? nameWithoutExt}
+
+
+ {file.cleanName}
+
+ {isDecrypted && file.ext &&
{file.ext}
}
{file.state === ProcessState.ERROR &&
}
{isDecrypted && (
-
+
)}
diff --git a/src/features/file-listing/__tests__/FileListing.test.tsx b/src/features/file-listing/__tests__/FileListing.test.tsx
index a694595..eebc45f 100644
--- a/src/features/file-listing/__tests__/FileListing.test.tsx
+++ b/src/features/file-listing/__tests__/FileListing.test.tsx
@@ -14,5 +14,5 @@ test('should be able to render a list of 3 items', () => {
});
expect(screen.getAllByTestId('file-row')).toHaveLength(3);
- expect(screen.getByText('Für Alice')).toBeInTheDocument();
+ expect(screen.getByText('ready')).toBeInTheDocument();
});
diff --git a/src/features/file-listing/__tests__/FileRow.test.tsx b/src/features/file-listing/__tests__/FileRow.test.tsx
index 3d1412d..1e58397 100644
--- a/src/features/file-listing/__tests__/FileRow.test.tsx
+++ b/src/features/file-listing/__tests__/FileRow.test.tsx
@@ -3,17 +3,14 @@ import { untouchedFile } from './__fixture__/file-list';
import { FileRow } from '../FileRow';
import { completedFile } from './__fixture__/file-list';
-test('should render no metadata when unavailable', () => {
+test('should render basic title (ready)', () => {
renderWithProviders();
expect(screen.getAllByTestId('file-row')).toHaveLength(1);
expect(screen.getByTestId('audio-meta-song-name')).toHaveTextContent('ready');
- expect(screen.queryByTestId('audio-meta-album-name')).toBeFalsy();
- expect(screen.queryByTestId('audio-meta-song-artist')).toBeFalsy();
- expect(screen.queryByTestId('audio-meta-album-artist')).toBeFalsy();
});
-test('should render metadata when file has been processed', () => {
+test('should render basic title (done)', () => {
renderWithProviders();
expect(screen.getAllByTestId('file-row')).toHaveLength(1);
diff --git a/src/features/file-listing/__tests__/__fixture__/file-list.ts b/src/features/file-listing/__tests__/__fixture__/file-list.ts
index 4bdfdab..11fd37e 100644
--- a/src/features/file-listing/__tests__/__fixture__/file-list.ts
+++ b/src/features/file-listing/__tests__/__fixture__/file-list.ts
@@ -1,6 +1,7 @@
import { DecryptedAudioFile, ProcessState } from '../../fileListingSlice';
export const untouchedFile: DecryptedAudioFile = {
+ cleanName: 'ready',
fileName: 'ready.bin',
raw: 'blob://localhost/file-a',
decrypted: '',
@@ -13,6 +14,7 @@ export const untouchedFile: DecryptedAudioFile = {
export const completedFile: DecryptedAudioFile = {
fileName: 'hello-b.bin',
+ cleanName: 'hello-b',
raw: 'blob://localhost/file-b',
decrypted: 'blob://localhost/file-b-decrypted',
ext: 'flac',
@@ -30,6 +32,7 @@ export const completedFile: DecryptedAudioFile = {
export const fileWithError: DecryptedAudioFile = {
fileName: 'hello-c.bin',
+ cleanName: 'hello-c',
raw: 'blob://localhost/file-c',
decrypted: 'blob://localhost/file-c-decrypted',
ext: 'flac',
diff --git a/src/features/file-listing/fileListingSlice.ts b/src/features/file-listing/fileListingSlice.ts
index 02a792e..17e43cb 100644
--- a/src/features/file-listing/fileListingSlice.ts
+++ b/src/features/file-listing/fileListingSlice.ts
@@ -5,9 +5,11 @@ import type { RootState } from '~/store';
import { DECRYPTION_WORKER_ACTION_NAME, type DecryptionResult } from '~/decrypt-worker/constants';
import type {
DecryptCommandOptions,
- FetchMusicExNamePayload, ParseKugouHeaderPayload, ParseKugouHeaderResponse,
+ FetchMusicExNamePayload,
+ ParseKugouHeaderPayload,
+ ParseKugouHeaderResponse,
ParseKuwoHeaderPayload,
- ParseKuwoHeaderResponse
+ ParseKuwoHeaderResponse,
} from '~/decrypt-worker/types';
import { decryptionQueue, workerClientBus } from '~/decrypt-worker/client';
import { DecryptErrorType } from '~/decrypt-worker/util/DecryptError';
@@ -15,8 +17,9 @@ import {
selectKugouKey,
selectKWMv2Key,
selectQMCv2KeyByFileName,
- selectQtfmAndroidKey
+ selectQtfmAndroidKey,
} from '../settings/settingsSelector';
+import { cleanFilename } from '~/util/cleanFilename';
export enum ProcessState {
QUEUED = 'QUEUED',
@@ -40,6 +43,7 @@ export interface AudioMetadata {
export interface DecryptedAudioFile {
fileName: string;
+ cleanName: string;
raw: string; // blob uri
ext: string;
decrypted: string; // blob uri
@@ -106,6 +110,7 @@ export const fileListingSlice = createSlice({
addNewFile: (state, { payload }: PayloadAction<{ id: string; fileName: string; blobURI: string }>) => {
state.files[payload.id] = {
fileName: payload.fileName,
+ cleanName: cleanFilename(payload.fileName),
raw: payload.blobURI,
decrypted: '',
ext: '',
diff --git a/src/features/nav/ResponsiveNav.tsx b/src/features/nav/ResponsiveNav.tsx
index ddd196d..0a15ab2 100644
--- a/src/features/nav/ResponsiveNav.tsx
+++ b/src/features/nav/ResponsiveNav.tsx
@@ -20,7 +20,7 @@ export function ResponsiveNav({
className={`@container/nav grow grid grid-cols-1 grid-rows-[auto_1fr] md:grid-rows-1 md:grid-cols-[10rem_1fr] ${className}`}
>
{/* Sidebar */}
-
+
{/* Main content */}
{children}
diff --git a/src/tabs/FaqTab.tsx b/src/tabs/FaqTab.tsx
index d4a2e67..d3352e5 100644
--- a/src/tabs/FaqTab.tsx
+++ b/src/tabs/FaqTab.tsx
@@ -1,4 +1,4 @@
-import { FC, Fragment } from 'react';
+import { ComponentType, Fragment } from 'react';
import { Header3 } from '~/components/HelpText/Headers';
import { KuwoFAQ } from '~/faq/KuwoFAQ';
import { OtherFAQ } from '~/faq/OtherFAQ';
@@ -8,7 +8,7 @@ import { KugouFAQ } from '~/faq/KugouFAQ.tsx';
type FAQEntry = {
id: string;
title: string;
- Help: FC;
+ Help: ComponentType;
};
const faqEntries: FAQEntry[] = [
@@ -20,10 +20,10 @@ const faqEntries: FAQEntry[] = [
export function FaqTab() {
return (
-