um-react/src/features/settings/panels/PanelQMCv2Key.tsx
2023-12-24 12:15:56 +01:00

187 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import {
Box,
Button,
ButtonGroup,
Checkbox,
Flex,
HStack,
Heading,
Icon,
IconButton,
List,
Menu,
MenuButton,
MenuDivider,
MenuItem,
MenuList,
Select,
Text,
Tooltip,
useToast,
} from '@chakra-ui/react';
import { useDispatch, useSelector } from 'react-redux';
import { qmc2AddKey, qmc2AllowFuzzyNameSearch, qmc2ClearKeys, qmc2ImportKeys } from '../settingsSlice';
import { selectStagingQMCv2Settings } from '../settingsSelector';
import React, { useState } from 'react';
import { MdAdd, MdDeleteForever, MdExpandMore, MdFileUpload } from 'react-icons/md';
import { QMCv2EKeyItem } from './QMCv2/QMCv2EKeyItem';
import { InfoOutlineIcon } from '@chakra-ui/icons';
import { ImportSecretModal } from '~/components/ImportSecretModal';
import { StagingQMCv2Key } from '../keyFormats';
import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor';
import { parseAndroidQmEKey } from '~/util/mmkv/qm';
import { getFileName } from '~/util/pathHelper';
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());
const handleAllowFuzzyNameSearchCheckbox = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(qmc2AllowFuzzyNameSearch({ enable: e.target.checked }));
};
const handleSecretImport = async (file: File) => {
try {
const fileBuffer = await file.arrayBuffer();
let qmc2Keys: null | Omit<StagingQMCv2Key, 'id'>[] = null;
if (/(player_process[_.]db|music_audio_play)(\.db)?$/i.test(file.name)) {
const extractor = await DatabaseKeyExtractor.getInstance();
qmc2Keys = extractor.extractQmcV2KeysFromSqliteDb(fileBuffer);
if (!qmc2Keys) {
alert(`不是支持的 SQLite 数据库文件。`);
return;
}
} else if (/MMKVStreamEncryptId|filenameEkeyMap|qmpc-mmkv-v1/i.test(file.name)) {
const fileBuffer = await file.arrayBuffer();
const map = parseAndroidQmEKey(new DataView(fileBuffer));
qmc2Keys = Array.from(map.entries(), ([name, ekey]) => ({ name: getFileName(name), ekey }));
}
if (qmc2Keys?.length === 0) {
toast({
title: '未导入密钥',
description: '选择的密钥数据库文件未发现任何可用的密钥。',
isClosable: true,
status: 'warning',
});
} else if (qmc2Keys) {
dispatch(qmc2ImportKeys(qmc2Keys));
setShowImportModal(false);
toast({
title: `导入成功 (${qmc2Keys.length})`,
description: '记得保存更改来应用。',
isClosable: true,
status: 'success',
});
} else {
alert(`不支持的文件:${file.name}`);
}
} catch (e) {
console.error('error during import: ', e);
alert(`导入数据库时发生错误:${e}`);
}
};
return (
<Flex minH={0} flexDir="column" flex={1}>
<Heading as="h2" size="lg">
QMCv2
</Heading>
<Text>
QQ FM QMCv2使QQ Mac iOS 使
FM线
</Text>
<HStack pb={2} pt={2}>
<ButtonGroup isAttached colorScheme="purple" variant="outline">
<Button onClick={addKey} leftIcon={<Icon as={MdAdd} />}>
</Button>
<Menu>
<MenuButton as={IconButton} icon={<MdExpandMore />}></MenuButton>
<MenuList>
<MenuItem onClick={() => setShowImportModal(true)} icon={<Icon as={MdFileUpload} boxSize={5} />}>
</MenuItem>
<MenuDivider />
<MenuItem color="red" onClick={clearAll} icon={<Icon as={MdDeleteForever} boxSize={5} />}>
</MenuItem>
</MenuList>
</Menu>
</ButtonGroup>
<HStack>
<Checkbox isChecked={allowFuzzyNameSearch} onChange={handleAllowFuzzyNameSearchCheckbox}>
<Text></Text>
</Checkbox>
<Tooltip
hasArrow
closeOnClick={false}
label={
<Box>
<Text>使</Text>
<Text>
使
<ruby>
<rp> (</rp>
<rt>Levenshtein distance</rt>
<rp>)</rp>
</ruby>
</Text>
<Text></Text>
<Text></Text>
</Box>
}
>
<InfoOutlineIcon />
</Tooltip>
</HStack>
</HStack>
<Box flex={1} minH={0} overflow="auto" pr="4">
<List spacing={3}>
{qmc2Keys.map(({ id, ekey, name }, i) => (
<QMCv2EKeyItem key={id} id={id} ekey={ekey} name={name} i={i} />
))}
</List>
{qmc2Keys.length === 0 && <Text></Text>}
</Box>
<ImportSecretModal
clientName={
<Select
value={secretType}
onChange={(e) => setSecretType(e.target.value as 'qm' | 'douban')}
variant="flushed"
display="inline"
css={{ paddingLeft: '0.75rem', width: 'auto' }}
>
<option value="qm">QQ </option>
<option value="douban"> FM</option>
</Select>
}
show={showImportModal}
onClose={() => setShowImportModal(false)}
onImport={handleSecretImport}
>
{secretType === 'qm' && <QMCv2QQMusicAllInstructions />}
{secretType === 'douban' && <QMCv2DoubanAllInstructions />}
</ImportSecretModal>
</Flex>
);
}