From e678e40b86a398a095cec88283c86aa3e8e2f096 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=B2=81=E6=A0=91=E4=BA=BA?= Date: Thu, 19 Oct 2023 02:58:50 +0100 Subject: [PATCH] feat: experimental support for douban key import --- .../AndroidADBPullInstruction.tsx | 20 +++++++++-- src/components/ImportSecretModal.tsx | 4 ++- src/faq/QQMusicFAQ.tsx | 4 +-- .../settings/panels/PanelQMCv2Key.tsx | 33 ++++++++++++++----- .../QMCv2/QMCv2DoubanAllInstructions.tsx | 17 ++++++++++ ...ns.tsx => QMCv2QQMusicAllInstructions.tsx} | 2 +- src/util/DatabaseKeyExtractor.ts | 15 ++++++--- 7 files changed, 74 insertions(+), 21 deletions(-) create mode 100644 src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx rename src/features/settings/panels/QMCv2/{QMCv2AllInstructions.tsx => QMCv2QQMusicAllInstructions.tsx} (95%) diff --git a/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx b/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx index b489e41..e38e01a 100644 --- a/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx +++ b/src/components/AndroidADBPullInstruction/AndroidADBPullInstruction.tsx @@ -36,10 +36,19 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi return ( <> - 你需要 root 访问权限来访问安卓应用的私有数据。 + 你需要 + + 超级管理员 + ( + + root + + ) + + 访问权限来访问安卓应用的私有数据。 - ⚠️ 请注意,获取 root 通常意味着你的安卓设备 + ⚠️ 请注意,获取管理员权限通常意味着你的安卓设备 将失去保修资格 @@ -92,7 +101,7 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi - 确保 adb 命令可用。 + 确保 adb 命令可用。 💡 如果没有,可以 @@ -134,6 +143,11 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi + + + 确保 adb 命令可用。 + + 将安卓设备连接到电脑,并允许调试。 diff --git a/src/components/ImportSecretModal.tsx b/src/components/ImportSecretModal.tsx index 2dd9870..cc099bc 100644 --- a/src/components/ImportSecretModal.tsx +++ b/src/components/ImportSecretModal.tsx @@ -35,7 +35,9 @@ export function ImportSecretModal({ clientName, children, show, onClose, onImpor 拖放或点我选择含有密钥的数据库文件 - 选择你的{clientName && <>「{clientName}」}客户端平台以查看对应说明: + + 选择你的{clientName && <>「{clientName}」}客户端平台以查看对应说明: + {children} diff --git a/src/faq/QQMusicFAQ.tsx b/src/faq/QQMusicFAQ.tsx index 86b6018..bdadbce 100644 --- a/src/faq/QQMusicFAQ.tsx +++ b/src/faq/QQMusicFAQ.tsx @@ -1,7 +1,7 @@ import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, chakra } from '@chakra-ui/react'; import { Header4 } from '~/components/HelpText/Header4'; import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer'; -import { QMCv2AllInstructions } from '~/features/settings/panels/QMCv2/QMCv2AllInstructions'; +import { QMCv2QQMusicAllInstructions } from '~/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions'; import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions'; export function QQMusicFAQ() { @@ -35,7 +35,7 @@ export function QQMusicFAQ() { - } /> + } /> diff --git a/src/features/settings/panels/PanelQMCv2Key.tsx b/src/features/settings/panels/PanelQMCv2Key.tsx index 802eac7..5f59502 100644 --- a/src/features/settings/panels/PanelQMCv2Key.tsx +++ b/src/features/settings/panels/PanelQMCv2Key.tsx @@ -14,6 +14,7 @@ import { MenuDivider, MenuItem, MenuList, + Select, Text, Tooltip, useToast, @@ -30,13 +31,15 @@ import { StagingQMCv2Key } from '../keyFormats'; import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor'; import { MMKVParser } from '~/util/MMKVParser'; import { getFileName } from '~/util/pathHelper'; -import { QMCv2AllInstructions } from './QMCv2/QMCv2AllInstructions'; +import { QMCv2QQMusicAllInstructions } from './QMCv2/QMCv2QQMusicAllInstructions'; +import { QMCv2DoubanAllInstructions } from './QMCv2/QMCv2DoubanAllInstructions'; export function PanelQMCv2Key() { const toast = useToast(); const dispatch = useDispatch(); const { keys: qmc2Keys, allowFuzzyNameSearch } = useSelector(selectStagingQMCv2Settings); const [showImportModal, setShowImportModal] = useState(false); + const [secretType, setSecretType] = useState<'qm' | 'douban'>('qm'); const addKey = () => dispatch(qmc2AddKey()); const clearAll = () => dispatch(qmc2ClearKeys()); @@ -51,11 +54,11 @@ export function PanelQMCv2Key() { let qmc2Keys: null | Omit[] = null; - if (/[_.]db$/i.test(file.name)) { + if (/(player_process[_.]db|music_audio_play)(\.db)?$/i.test(file.name)) { const extractor = await DatabaseKeyExtractor.getInstance(); - qmc2Keys = extractor.extractQmAndroidDbKeys(fileBuffer); + qmc2Keys = extractor.extractQmcV2KeysFromSqliteDb(fileBuffer); if (!qmc2Keys) { - alert(`不是支持的 SQLite 数据库文件。\n表名:${qmc2Keys}`); + alert(`不是支持的 SQLite 数据库文件。`); return; } } else if (/MMKVStreamEncryptId|filenameEkeyMap/i.test(file.name)) { @@ -96,8 +99,8 @@ export function PanelQMCv2Key() { - QQ 音乐目前采用的加密方案(QMCv2)。在使用「QQ 音乐」安卓、Mac 或 iOS - 客户端的情况下,其「离线加密文件」对应的「密钥」储存在独立的数据库文件内。 + QQ 音乐、豆瓣 FM 目前采用的加密方案(QMCv2)。在使用「QQ 音乐」安卓、Mac 或 iOS 客户端,以及在使用「豆瓣 + FM」安卓客户端的情况下,其「离线加密文件」对应的「密钥」储存在独立的数据库文件内。 @@ -155,16 +158,28 @@ export function PanelQMCv2Key() { ))} - {qmc2Keys.length === 0 && 还没有添加密钥。} + {qmc2Keys.length === 0 && 还没有密钥。} setSecretType(e.target.value as 'qm' | 'douban')} + variant="flushed" + display="inline" + css={{ paddingLeft: '0.75rem', width: 'auto' }} + > + + + + } show={showImportModal} onClose={() => setShowImportModal(false)} onImport={handleSecretImport} > - + {secretType === 'qm' && } + {secretType === 'douban' && } ); diff --git a/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx b/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx new file mode 100644 index 0000000..b3595c5 --- /dev/null +++ b/src/features/settings/panels/QMCv2/QMCv2DoubanAllInstructions.tsx @@ -0,0 +1,17 @@ +import { Tab, TabList, TabPanel, TabPanels } from '@chakra-ui/react'; +import { AndroidADBPullInstruction } from '~/components/AndroidADBPullInstruction/AndroidADBPullInstruction'; + +export function QMCv2DoubanAllInstructions() { + return ( + <> + + 安卓 + + + + + + + + ); +} diff --git a/src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx b/src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx similarity index 95% rename from src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx rename to src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx index 3cfd9f5..239f1b6 100644 --- a/src/features/settings/panels/QMCv2/QMCv2AllInstructions.tsx +++ b/src/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions.tsx @@ -4,7 +4,7 @@ import { InstructionsIOS } from './InstructionsIOS'; import { InstructionsMac } from './InstructionsMac'; import { InstructionsPC } from './InstructionsPC'; -export function QMCv2AllInstructions() { +export function QMCv2QQMusicAllInstructions() { return ( <> diff --git a/src/util/DatabaseKeyExtractor.ts b/src/util/DatabaseKeyExtractor.ts index 0a4b8e9..c749463 100644 --- a/src/util/DatabaseKeyExtractor.ts +++ b/src/util/DatabaseKeyExtractor.ts @@ -23,16 +23,21 @@ export class DatabaseKeyExtractor { return tables.includes(name); } - extractQmAndroidDbKeys(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] { + extractQmcV2KeysFromSqliteDb(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] { let db: SQLDatabase | null = null; try { db = new this.SQL.Database(new Uint8Array(buffer)); - if (!this.hasTable(db, 'audio_file_ekey_table')) { - return null; - } - const result = db.exec('select file_path, ekey from audio_file_ekey_table'); + let sql: undefined | string; + if (this.hasTable(db, 'audio_file_ekey_table')) { + sql = 'select file_path, ekey from audio_file_ekey_table'; + } else if (this.hasTable(db, 'EKeyFileInfo')) { + sql = 'select filePath, eKey from EKeyFileInfo'; + } + if (!sql) return null; + + const result = db.exec(sql); if (result.length === 0) { return []; }