feat: import ekey from Android db (#20)

This commit is contained in:
鲁树人
2023-06-11 16:21:10 +01:00
parent ec27a6f699
commit b78399eddb
16 changed files with 1080 additions and 108 deletions

View File

@ -24,12 +24,14 @@ import {
import { useDispatch, useSelector } from 'react-redux';
import { qmc2AddKey, qmc2ClearKeys, qmc2DeleteKey, qmc2UpdateKey } from '../settingsSlice';
import { selectStagingQMCv2Settings } from '../settingsSelector';
import React from 'react';
import React, { useState } from 'react';
import { MdAdd, MdDelete, MdDeleteForever, MdExpandMore, MdFileUpload, MdVpnKey } from 'react-icons/md';
import { ImportFileModal } from './QMCv2/ImportFileModal';
export function PanelQMCv2Key() {
const dispatch = useDispatch();
const qmc2Keys = useSelector(selectStagingQMCv2Settings).keys;
const [showImportModal, setShowImportModal] = useState(false);
const addKey = () => dispatch(qmc2AddKey());
const updateKey = (prop: 'name' | 'key', id: string, e: React.ChangeEvent<HTMLInputElement>) =>
@ -53,12 +55,10 @@ export function PanelQMCv2Key() {
<Menu>
<MenuButton as={IconButton} icon={<MdExpandMore />}></MenuButton>
<MenuList>
{/* 目前的想法是弹出一个 modal给用户一些信息如期待的格式、如何导出或寻找对应的文件 */}
{/* 但是这样的话就不太方便放在这个分支里面做了,下次一定。 */}
<MenuItem hidden onClick={() => alert('TODO!')} icon={<Icon as={MdFileUpload} boxSize={5} />}>
<MenuItem onClick={() => setShowImportModal(true)} icon={<Icon as={MdFileUpload} boxSize={5} />}>
</MenuItem>
<MenuDivider hidden />
<MenuDivider />
<MenuItem color="red" onClick={clearAll} icon={<Icon as={MdDeleteForever} boxSize={5} />}>
</MenuItem>
@ -111,6 +111,8 @@ export function PanelQMCv2Key() {
</List>
{qmc2Keys.length === 0 && <Text></Text>}
</Box>
<ImportFileModal show={showImportModal} onClose={() => setShowImportModal(false)} />
</Flex>
);
}

View File

@ -0,0 +1,23 @@
### 安卓端获取密钥数据库
你可能需要 root 或类似的访问权限。绝大多数情况下,这会导致你的安卓设备失去保修资格。
#### 提取文件(文件浏览器)
1. 提升到 `root` 权限,访问 `/data/data/com.tencent.qqmusic/databases/` 目录,将文件 `player_process_db`
复制到正常模式下用户可访问的目录(如下载目录)。
2. 如果你需要在电脑上进行解密操作,请将该文件复制到电脑。
3. 选择该文件。
#### 提取文件ADB
※ 目前该指令只支持 Linux & Mac
1. 打开终端并安装好依赖,并复制下述指令:
```sh
adb shell su -c "cat '/data/data/com.tencent.qqmusic/databases/player_process_db' | gzip | base64" \
| base64 -d | gzip -d player_process_db
```
2. 选择提取的这个文件即可。

View File

@ -0,0 +1,95 @@
import {
Button,
Center,
Flex,
Heading,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalFooter,
ModalHeader,
ModalOverlay,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
} from '@chakra-ui/react';
import { FileInput } from '~/components/FileInput';
import mdHelpAndroid from './DocAndroid.md?raw';
import { MarkdownContent } from '~/components/MarkdownContent';
import { qmc2ImportKeys } from '../../settingsSlice';
import { useAppDispatch } from '~/hooks';
import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor';
export interface ImportFileModalProps {
show: boolean;
onClose: () => void;
}
export function ImportFileModal({ onClose, show }: ImportFileModalProps) {
const dispatch = useAppDispatch();
const handleFileReceived = async (files: File[]) => {
try {
const file = files[0];
const fileBuffer = await file.arrayBuffer();
if (/[_.]db$/i.test(file.name)) {
const extractor = await DatabaseKeyExtractor.getInstance();
const qmc2Keys = extractor.extractQmAndroidDbKeys(fileBuffer);
if (qmc2Keys) {
dispatch(qmc2ImportKeys(qmc2Keys));
onClose();
return;
}
alert(`不是支持的 SQLite 数据库文件。\n表名${qmc2Keys}`);
} else {
alert(`不支持的文件:${file.name}`);
}
} catch (e) {
console.error('error during import: ', e);
alert(`导入数据库时发生错误:${e}`);
}
};
return (
<Modal isOpen={show} onClose={onClose} scrollBehavior="inside" size="xl">
<ModalOverlay />
<ModalContent>
<ModalHeader></ModalHeader>
<ModalCloseButton />
<Flex as={ModalBody} gap={2} flexDir="column">
<Center>
<FileInput onReceiveFiles={handleFileReceived}></FileInput>
</Center>
<Heading as="h2" size="md" mt="4">
使
</Heading>
<Tabs variant="enclosed">
<TabList>
<Tab></Tab>
{/* <Tab>Two</Tab> */}
</TabList>
<TabPanels>
<TabPanel>
<MarkdownContent>{mdHelpAndroid}</MarkdownContent>
</TabPanel>
<TabPanel>
<p>two!</p>
</TabPanel>
</TabPanels>
</Tabs>
</Flex>
<ModalFooter>
<Button mr={3} onClick={onClose}>
</Button>
</ModalFooter>
</ModalContent>
</Modal>
);
}

View File

@ -6,9 +6,10 @@ export const selectStagingQMCv2Settings = (state: RootState) => state.settings.s
export const selectFinalQMCv2Settings = (state: RootState) => state.settings.production.qmc2;
export const selectDecryptOptionByFile = (state: RootState, name: string): DecryptCommandOptions => {
const normalizedName = name.normalize();
const qmc2Keys = selectFinalQMCv2Settings(state).keys;
return {
qmc2Key: hasOwn(qmc2Keys, name) ? qmc2Keys[name] : undefined,
qmc2Key: hasOwn(qmc2Keys, normalizedName) ? qmc2Keys[normalizedName] : undefined,
};
};

View File

@ -36,7 +36,7 @@ const stagingToProduction = (staging: StagingSettings): ProductionSettings => ({
qmc2: {
keys: objectify(
staging.qmc2.keys,
(item) => item.name,
(item) => item.name.normalize(),
(item) => item.key
),
},
@ -61,6 +61,10 @@ export const settingsSlice = createSlice({
qmc2AddKey(state) {
state.staging.qmc2.keys.push({ id: nanoid(), name: '', key: '' });
},
qmc2ImportKeys(state, { payload }: PayloadAction<{ name: string; key: string }[]>) {
const newItems = payload.map((item) => ({ id: nanoid(), ...item }));
state.staging.qmc2.keys.push(...newItems);
},
qmc2DeleteKey(state, { payload: { id } }: PayloadAction<{ id: string }>) {
const qmc2 = state.staging.qmc2;
qmc2.keys = qmc2.keys.filter((item) => item.id !== id);
@ -102,6 +106,7 @@ export const {
qmc2UpdateKey,
qmc2DeleteKey,
qmc2ClearKeys,
qmc2ImportKeys,
commitStagingChange,
discardStagingChanges,