diff --git a/package.json b/package.json index 7b87eb6..9ea037d 100644 --- a/package.json +++ b/package.json @@ -5,9 +5,11 @@ "dev": "ts-node src/index.ts", "build": "tsc", "start": "prisma db push --accept-data-loss && node build/index.js", - "prisma": "prisma" + "prisma": "prisma", + "convert": "ts-node tools/convert" }, "devDependencies": { + "@types/date-and-time": "^0.13.0", "@types/fluent-ffmpeg": "^2", "@types/node": "^17.0.18", "prisma": "latest", @@ -18,6 +20,7 @@ "dependencies": { "@prisma/client": "latest", "axios": "^0.26.0", + "date-and-time": "^2.2.1", "eviltransform": "^0.2.2", "file-type": "^17.1.1", "fluent-ffmpeg": "^2.1.2", diff --git a/tools/convert/index.ts b/tools/convert/index.ts new file mode 100644 index 0000000..4794286 --- /dev/null +++ b/tools/convert/index.ts @@ -0,0 +1,102 @@ +import fsP from 'fs/promises'; +import fs from 'fs'; +import path from 'path'; +import { Message } from './types'; +import OicqClient from '../../src/client/OicqClient'; +import { Platform } from 'oicq'; +import { fetchFile } from '../../src/utils/urls'; +import { md5Hex } from '../../src/utils/hashing'; +import { format } from 'date-and-time'; +import axios from 'axios'; + +(async () => { + const selfId = Number(process.argv[2]); + const selfName = process.argv[3]; + const filePath = process.argv[4]; + const outputPath = process.argv[5]; + // 可选参数 + const account = Number(process.argv[6]); + const password = process.argv[7]; + const crvApi = process.argv[8]; + const crvKey = process.argv[9]; + + const oicq = account && await OicqClient.create({ + uin: account, + password, + platform: Platform.Android, + onVerifyDevice: () => process.exit(1), + onVerifySlider: () => process.exit(1), + onQrCode: () => process.exit(1), + }); + + const content = JSON.parse(await fsP.readFile(filePath, 'utf-8')) as Message[]; + if (!fs.existsSync(outputPath)) { + await fsP.mkdir(outputPath); + } + const txt = fs.createWriteStream(path.join(outputPath, 'WhatsApp Chat with Cat.txt'), 'utf-8'); + + content.sort((a, b) => a.time - b.time); + + console.log('count:', content.length); + console.log('files:', content.filter(it => it.file?.type?.startsWith('image/')).length); + + for (const message of content) { + let sender = message.senderId === selfId ? selfName : message.username; + if (message.system) sender = '系统'; + const date = new Date(message.time); + if (message.file) { + if (message.file.type.startsWith('image/')) { + try { + let file: Buffer; + if (message.file.url.startsWith('data:image')) { + const base64Data = message.file.url.replace(/^data:image\/\w+;base64,/, ''); + file = Buffer.from(base64Data, 'base64'); + } + else { + file = await fetchFile(message.file.url); + } + const md5 = md5Hex(file); + await fsP.writeFile(path.join(outputPath, `IMG-${md5}.jpg`), file); + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: IMG-${md5}.jpg (file attached)\n`); + process.stdout.write('.'); + } + catch (e) { + process.stdout.write('x'); + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: ${message.file.url}\n`); + } + } + else { + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: 文件: ${message.file.name}\n` + + `${message.file.type}\n${message.file.url}\n`); + } + } + if (message.content) { + const FORWARD_REGEX = /\[Forward: ([A-Za-z0-9\/+=]+)]/; + if (FORWARD_REGEX.test(message.content) && oicq) { + try { + const resId = FORWARD_REGEX.exec(message.content)[1]; + const record = await oicq.getForwardMsg(resId); + const hash = md5Hex(resId); + await axios.post(`${crvApi}/add`, { + auth: crvKey, + key: hash, + data: record, + }); + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: 转发的消息记录 ${crvApi}/?hash=${hash}\n`); + process.stdout.write('w'); + } + catch (e) { + process.stdout.write('v'); + } + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: 转发的消息记录\n`); + } + else { + txt.write(`${format(date, 'DD/MM/YYYY, HH:mm')} - ${sender}: ${message.content}\n`); + } + } + } + + txt.end(); + await oicq.logout(false); + console.log('转换成功'); +})(); diff --git a/tools/convert/types.d.ts b/tools/convert/types.d.ts new file mode 100644 index 0000000..b4cfd4e --- /dev/null +++ b/tools/convert/types.d.ts @@ -0,0 +1,21 @@ +export interface Message { + senderId?: number; + username: string; + content: string; + system: boolean; + file?: { + type: string + url: string + size?: number + name?: string + fid?: string + }; + files: { + type: string + url: string + size?: number + name?: string + fid?: string + }[]; + time: number; +} diff --git a/yarn.lock b/yarn.lock index 2bdecab..6772621 100644 --- a/yarn.lock +++ b/yarn.lock @@ -125,6 +125,13 @@ __metadata: languageName: node linkType: hard +"@types/date-and-time@npm:^0.13.0": + version: 0.13.0 + resolution: "@types/date-and-time@npm:0.13.0" + checksum: a430aaaa77479746b026295ee6dab19497cc838f7a575569e3a2304841396e1aeba431bc528a86ffce037c12e371f0f4847085f6c99117f8c5809a97560f2971 + languageName: node + linkType: hard + "@types/fluent-ffmpeg@npm:^2": version: 2.1.20 resolution: "@types/fluent-ffmpeg@npm:2.1.20" @@ -437,6 +444,13 @@ __metadata: languageName: node linkType: hard +"date-and-time@npm:^2.2.1": + version: 2.2.1 + resolution: "date-and-time@npm:2.2.1" + checksum: 7b789a94b86b551f2777ccefa25a5b9964c1cf0a929044aafbef93316f324002a0454394a2810f1247693c9d8e799adde9e607d4509ff03491e3bdd1321ae636 + languageName: node + linkType: hard + "date-format@npm:^4.0.3": version: 4.0.3 resolution: "date-format@npm:4.0.3" @@ -1607,9 +1621,11 @@ __metadata: resolution: "q2tg@workspace:." dependencies: "@prisma/client": latest + "@types/date-and-time": ^0.13.0 "@types/fluent-ffmpeg": ^2 "@types/node": ^17.0.18 axios: ^0.26.0 + date-and-time: ^2.2.1 eviltransform: ^0.2.2 file-type: ^17.1.1 fluent-ffmpeg: ^2.1.2