Compare commits

..

No commits in common. "4ff58091883973b130719cd0bc20a302b8d57912" and "57c0a247c0d21b9b59c984d38fdab377bd5f44df" have entirely different histories.

10 changed files with 32 additions and 114 deletions

View File

@ -7,6 +7,7 @@ import {
Box, Box,
Code, Code,
Heading, Heading,
Link,
ListItem, ListItem,
OrderedList, OrderedList,
Text, Text,
@ -18,7 +19,6 @@ import hljsStyleGitHub from 'react-syntax-highlighter/dist/esm/styles/hljs/githu
import PowerShellAdbDumpCommandTemplate from './adb_dump.ps1?raw'; import PowerShellAdbDumpCommandTemplate from './adb_dump.ps1?raw';
import ShellAdbDumpCommandTemplate from './adb_dump.sh?raw'; import ShellAdbDumpCommandTemplate from './adb_dump.sh?raw';
import { ExtLink } from '../ExtLink';
const applyTemplate = (tpl: string, values: Record<string, unknown>) => { const applyTemplate = (tpl: string, values: Record<string, unknown>) => {
return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => (Object.hasOwn(values, key) ? String(values[key]) : '<nil>')); return tpl.replace(/\{\{\s*(\w+)\s*\}\}/g, (_, key) => (Object.hasOwn(values, key) ? String(values[key]) : '<nil>'));
@ -36,19 +36,10 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
return ( return (
<> <>
<Text> <Text>
<code>root</code> 访访
<ruby>
<rp> (</rp>
<rt>
<code>root</code>
</rt>
<rp>)</rp>
</ruby>
访访
</Text> </Text>
<Text> <Text>
<code>root</code>
<chakra.span color="red.400"></chakra.span> <chakra.span color="red.400"></chakra.span>
</Text> </Text>
@ -101,13 +92,13 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
<OrderedList> <OrderedList>
<ListItem> <ListItem>
<Text> <Text>
<Code>adb</Code> <code>adb</code>
</Text> </Text>
<Text> <Text>
💡 💡
<ExtLink href="https://scoop.sh/#/apps?q=adb"> <Link href="https://scoop.sh/#/apps?q=adb" isExternal>
使 Scoop <ExternalLinkIcon /> 使 Scoop <ExternalLinkIcon />
</ExtLink> </Link>
</Text> </Text>
</ListItem> </ListItem>
@ -143,11 +134,6 @@ export function AndroidADBPullInstruction({ dir, file }: AndroidADBPullInstructi
</Heading> </Heading>
<AccordionPanel pb={4}> <AccordionPanel pb={4}>
<OrderedList> <OrderedList>
<ListItem>
<Text>
<Code>adb</Code>
</Text>
</ListItem>
<ListItem> <ListItem>
<Text></Text> <Text></Text>
</ListItem> </ListItem>

View File

@ -1,12 +0,0 @@
import type { AnchorHTMLAttributes } from 'react';
import { ExternalLinkIcon } from '@chakra-ui/icons';
import { Link } from '@chakra-ui/react';
export function ExtLink({ children, ...props }: AnchorHTMLAttributes<HTMLAnchorElement>) {
return (
<Link isExternal {...props} rel="noreferrer noopener nofollow">
{children}
<ExternalLinkIcon />
</Link>
);
}

View File

@ -35,9 +35,7 @@ export function ImportSecretModal({ clientName, children, show, onClose, onImpor
<FileInput onReceiveFiles={handleFileReceived}></FileInput> <FileInput onReceiveFiles={handleFileReceived}></FileInput>
</Center> </Center>
<Text as="div" mt={2}> <Text mt={2}>{clientName && <>{clientName}</>}</Text>
{clientName && <>{clientName}</>}
</Text>
<Flex as={Tabs} variant="enclosed" flexDir="column" flex={1} minH={0}> <Flex as={Tabs} variant="enclosed" flexDir="column" flex={1} minH={0}>
{children} {children}
</Flex> </Flex>

View File

@ -1,5 +1,5 @@
import { Text } from '@chakra-ui/react'; import { ExternalLinkIcon } from '@chakra-ui/icons';
import { ExtLink } from '~/components/ExtLink'; import { Link, Text } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Header4'; import { Header4 } from '~/components/HelpText/Header4';
import { ProjectIssue } from '~/components/ProjectIssue'; import { ProjectIssue } from '~/components/ProjectIssue';
@ -17,7 +17,10 @@ export function OtherFAQ() {
<Header4></Header4> <Header4></Header4>
<Text> <Text>
{'欢迎进入 '} {'欢迎进入 '}
<ExtLink href={'https://t.me/unlock_music_chat'}>Telegram - </ExtLink> <Link href={'https://t.me/unlock_music_chat'} isExternal>
Telegram -
<ExternalLinkIcon />
</Link>
{' 一起探讨。'} {' 一起探讨。'}
</Text> </Text>
</> </>

View File

@ -1,9 +1,8 @@
import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, UnorderedList, chakra } from '@chakra-ui/react'; import { Alert, AlertIcon, Container, Flex, List, ListItem, Text, chakra } from '@chakra-ui/react';
import { Header4 } from '~/components/HelpText/Header4'; import { Header4 } from '~/components/HelpText/Header4';
import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer'; import { SegmentTryOfficialPlayer } from './SegmentTryOfficialPlayer';
import { QMCv2QQMusicAllInstructions } from '~/features/settings/panels/QMCv2/QMCv2QQMusicAllInstructions'; import { QMCv2AllInstructions } from '~/features/settings/panels/QMCv2/QMCv2AllInstructions';
import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions'; import { SegmentKeyImportInstructions } from './SegmentKeyImportInstructions';
import { ExtLink } from '~/components/ExtLink';
export function QQMusicFAQ() { export function QQMusicFAQ() {
return ( return (
@ -17,26 +16,7 @@ export function QQMusicFAQ() {
<Text> <Text>
<chakra.strong>2</chakra.strong> <chakra.strong>2</chakra.strong>
</Text> </Text>
<Text> <Text>Windows客户端下载的歌曲无需密钥</Text>
Windows 19.43
QQ Windows v19.43
</Text>
<UnorderedList pl={3}>
<ListItem>
<Text>
<ExtLink href="https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1943.exe">
<code>qq.com</code>
</ExtLink>
</Text>
</ListItem>
<ListItem>
<Text>
<ExtLink href="https://web.archive.org/web/2023/https://dldir1v6.qq.com/music/clntupate/QQMusic_Setup_1943.exe">
<code>Archive.org</code>
</ExtLink>
</Text>
</ListItem>
</UnorderedList>
<Container p={2}> <Container p={2}>
<Alert status="warning" borderRadius={5}> <Alert status="warning" borderRadius={5}>
@ -55,7 +35,7 @@ export function QQMusicFAQ() {
</Alert> </Alert>
</Container> </Container>
<SegmentKeyImportInstructions tab="QMCv2 密钥" clientInstructions={<QMCv2QQMusicAllInstructions />} /> <SegmentKeyImportInstructions tab="QMCv2 密钥" clientInstructions={<QMCv2AllInstructions />} />
</ListItem> </ListItem>
</List> </List>
</> </>

View File

@ -92,7 +92,7 @@ export function FileRow({ id, file }: FileRowProps) {
</WrapItem> </WrapItem>
<WrapItem> <WrapItem>
{file.decrypted && ( {file.decrypted && (
<Link href={file.decrypted} download={decryptedName}> <Link isExternal href={file.decrypted} download={decryptedName}>
<Button as="span"></Button> <Button as="span"></Button>
</Link> </Link>
)} )}

View File

@ -14,7 +14,6 @@ import {
MenuDivider, MenuDivider,
MenuItem, MenuItem,
MenuList, MenuList,
Select,
Text, Text,
Tooltip, Tooltip,
useToast, useToast,
@ -31,15 +30,13 @@ import { StagingQMCv2Key } from '../keyFormats';
import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor'; import { DatabaseKeyExtractor } from '~/util/DatabaseKeyExtractor';
import { MMKVParser } from '~/util/MMKVParser'; import { MMKVParser } from '~/util/MMKVParser';
import { getFileName } from '~/util/pathHelper'; import { getFileName } from '~/util/pathHelper';
import { QMCv2QQMusicAllInstructions } from './QMCv2/QMCv2QQMusicAllInstructions'; import { QMCv2AllInstructions } from './QMCv2/QMCv2AllInstructions';
import { QMCv2DoubanAllInstructions } from './QMCv2/QMCv2DoubanAllInstructions';
export function PanelQMCv2Key() { export function PanelQMCv2Key() {
const toast = useToast(); const toast = useToast();
const dispatch = useDispatch(); const dispatch = useDispatch();
const { keys: qmc2Keys, allowFuzzyNameSearch } = useSelector(selectStagingQMCv2Settings); const { keys: qmc2Keys, allowFuzzyNameSearch } = useSelector(selectStagingQMCv2Settings);
const [showImportModal, setShowImportModal] = useState(false); const [showImportModal, setShowImportModal] = useState(false);
const [secretType, setSecretType] = useState<'qm' | 'douban'>('qm');
const addKey = () => dispatch(qmc2AddKey()); const addKey = () => dispatch(qmc2AddKey());
const clearAll = () => dispatch(qmc2ClearKeys()); const clearAll = () => dispatch(qmc2ClearKeys());
@ -54,11 +51,11 @@ export function PanelQMCv2Key() {
let qmc2Keys: null | Omit<StagingQMCv2Key, 'id'>[] = null; let qmc2Keys: null | Omit<StagingQMCv2Key, 'id'>[] = null;
if (/(player_process[_.]db|music_audio_play)(\.db)?$/i.test(file.name)) { if (/[_.]db$/i.test(file.name)) {
const extractor = await DatabaseKeyExtractor.getInstance(); const extractor = await DatabaseKeyExtractor.getInstance();
qmc2Keys = extractor.extractQmcV2KeysFromSqliteDb(fileBuffer); qmc2Keys = extractor.extractQmAndroidDbKeys(fileBuffer);
if (!qmc2Keys) { if (!qmc2Keys) {
alert(`不是支持的 SQLite 数据库文件。`); alert(`不是支持的 SQLite 数据库文件。\n表名${qmc2Keys}`);
return; return;
} }
} else if (/MMKVStreamEncryptId|filenameEkeyMap/i.test(file.name)) { } else if (/MMKVStreamEncryptId|filenameEkeyMap/i.test(file.name)) {
@ -99,8 +96,8 @@ export function PanelQMCv2Key() {
</Heading> </Heading>
<Text> <Text>
QQ FM QMCv2使QQ Mac iOS 使 QQ QMCv2使QQ Mac iOS
FM线 线
</Text> </Text>
<HStack pb={2} pt={2}> <HStack pb={2} pt={2}>
@ -158,28 +155,16 @@ export function PanelQMCv2Key() {
<QMCv2EKeyItem key={id} id={id} ekey={ekey} name={name} i={i} /> <QMCv2EKeyItem key={id} id={id} ekey={ekey} name={name} i={i} />
))} ))}
</List> </List>
{qmc2Keys.length === 0 && <Text></Text>} {qmc2Keys.length === 0 && <Text></Text>}
</Box> </Box>
<ImportSecretModal <ImportSecretModal
clientName={ clientName="QQ 音乐"
<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} show={showImportModal}
onClose={() => setShowImportModal(false)} onClose={() => setShowImportModal(false)}
onImport={handleSecretImport} onImport={handleSecretImport}
> >
{secretType === 'qm' && <QMCv2QQMusicAllInstructions />} <QMCv2AllInstructions />
{secretType === 'douban' && <QMCv2DoubanAllInstructions />}
</ImportSecretModal> </ImportSecretModal>
</Flex> </Flex>
); );

View File

@ -4,7 +4,7 @@ import { InstructionsIOS } from './InstructionsIOS';
import { InstructionsMac } from './InstructionsMac'; import { InstructionsMac } from './InstructionsMac';
import { InstructionsPC } from './InstructionsPC'; import { InstructionsPC } from './InstructionsPC';
export function QMCv2QQMusicAllInstructions() { export function QMCv2AllInstructions() {
return ( return (
<> <>
<TabList> <TabList>

View File

@ -1,17 +0,0 @@
import { Tab, TabList, TabPanel, TabPanels } from '@chakra-ui/react';
import { AndroidADBPullInstruction } from '~/components/AndroidADBPullInstruction/AndroidADBPullInstruction';
export function QMCv2DoubanAllInstructions() {
return (
<>
<TabList>
<Tab></Tab>
</TabList>
<TabPanels flex={1} overflow="auto">
<TabPanel>
<AndroidADBPullInstruction dir="/data/data/com.douban.radio/databases" file="music_audio_play" />
</TabPanel>
</TabPanels>
</>
);
}

View File

@ -23,21 +23,16 @@ export class DatabaseKeyExtractor {
return tables.includes(name); return tables.includes(name);
} }
extractQmcV2KeysFromSqliteDb(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] { extractQmAndroidDbKeys(buffer: ArrayBuffer): null | QMAndroidKeyEntry[] {
let db: SQLDatabase | null = null; let db: SQLDatabase | null = null;
try { try {
db = new this.SQL.Database(new Uint8Array(buffer)); db = new this.SQL.Database(new Uint8Array(buffer));
if (!this.hasTable(db, 'audio_file_ekey_table')) {
let sql: undefined | string; return null;
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); const result = db.exec('select file_path, ekey from audio_file_ekey_table');
if (result.length === 0) { if (result.length === 0) {
return []; return [];
} }