From 848d926cc6738125c4273ad8e11b40b556123f6d Mon Sep 17 00:00:00 2001 From: Clansty Date: Sat, 26 Feb 2022 18:15:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=8E=20TG=20=E5=88=B0=20QQ=20?= =?UTF-8?q?=E5=88=B0=E6=B6=88=E6=81=AF=E8=BD=AC=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 3 +- src/client/OicqClient.ts | 4 +- src/client/Telegram.ts | 3 +- src/controllers/ForwardController.ts | 36 ++++++++-- src/helpers/forwardHelper.ts | 17 +++++ src/providers/forwardPairs.ts | 4 +- src/services/ForwardService.ts | 103 ++++++++++++++++++++++++++- tsconfig.json | 2 +- yarn.lock | 21 +++++- 9 files changed, 178 insertions(+), 15 deletions(-) diff --git a/package.json b/package.json index a93d600..e31cc4d 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "log4js": "^6.4.1", "nodejs-base64": "^2.0.0", "oicq": "^2.2.0", - "telegram": "^2.5.0" + "telegram": "^2.5.0", + "tmp-promise": "^3.0.3" }, "engines": { "node": "^14.13.1 || >=16.0.0" diff --git a/src/client/OicqClient.ts b/src/client/OicqClient.ts index 0084ff3..a79bacd 100644 --- a/src/client/OicqClient.ts +++ b/src/client/OicqClient.ts @@ -1,4 +1,4 @@ -import { Client, createClient, Platform } from 'oicq'; +import { Client, LogLevel, Platform } from 'oicq'; import * as Buffer from 'buffer'; import { execSync } from 'child_process'; import random from '../utils/random'; @@ -7,6 +7,8 @@ import fsP from 'fs/promises'; import path from 'path'; import { Config } from 'oicq/lib/client'; +const LOG_LEVEL: LogLevel = 'warn'; + interface CreateOicqParams { uin: number; password: string; diff --git a/src/client/Telegram.ts b/src/client/Telegram.ts index 100aa90..f81acea 100644 --- a/src/client/Telegram.ts +++ b/src/client/Telegram.ts @@ -1,5 +1,4 @@ import { Api, TelegramClient } from 'telegram'; -import { StringSession } from 'telegram/sessions'; import { BotAuthParams, UserAuthParams } from 'telegram/client/auth'; import { NewMessage, NewMessageEvent } from 'telegram/events'; import { EditedMessage, EditedMessageEvent } from 'telegram/events/EditedMessage'; @@ -12,7 +11,7 @@ import os from 'os'; import TelegramChat from './TelegramChat'; import TelegramSession from './TelegramSession'; -type MessageHandler = (message: Api.Message) => Promise; +type MessageHandler = (message: Api.Message) => Promise; export default class Telegram { private readonly client: TelegramClient; diff --git a/src/controllers/ForwardController.ts b/src/controllers/ForwardController.ts index b44dcbc..c9dd68d 100644 --- a/src/controllers/ForwardController.ts +++ b/src/controllers/ForwardController.ts @@ -5,6 +5,7 @@ import forwardPairs from '../providers/forwardPairs'; import { DiscussMessageEvent, Friend, Group, GroupMessageEvent, PrivateMessageEvent } from 'oicq'; import db from '../providers/db'; import helper from '../helpers/forwardHelper'; +import { Api } from 'telegram'; export default class ForwardController { private readonly forwardService: ForwardService; @@ -14,10 +15,11 @@ export default class ForwardController { private readonly oicq: OicqClient) { this.forwardService = new ForwardService(tgBot, oicq); forwardPairs.init(oicq, tgBot) - .then(() => oicq.on('message', this.onQqMsg)); + .then(() => oicq.on('message', this.onQqMessage)) + .then(() => tgBot.addNewMessageEventHandler(this.onTelegramMessage)); } - private onQqMsg = async (event: PrivateMessageEvent | GroupMessageEvent | DiscussMessageEvent) => { + private onQqMessage = async (event: PrivateMessageEvent | GroupMessageEvent | DiscussMessageEvent) => { let target: Friend | Group; if (event.message_type === 'private') { target = event.friend; @@ -28,8 +30,8 @@ export default class ForwardController { else return; const pair = forwardPairs.find(target); if (!pair) return; - const tgMsg = await this.forwardService.forwardFromQq(event, pair); - if (tgMsg) { + const tgMessage = await this.forwardService.forwardFromQq(event, pair); + if (tgMessage) { // 更新数据库 await db.message.create({ data: { @@ -41,7 +43,31 @@ export default class ForwardController { rand: event.rand, pktnum: event.pktnum, tgChatId: Number(pair.tg.id), - tgMsgId: tgMsg.id, + tgMsgId: tgMessage.id, + }, + }); + } + }; + + private onTelegramMessage = async (message: Api.Message) => { + const pair = forwardPairs.find(message.chat); + if (!pair) return; + const qqMessageSent = await this.forwardService.forwardFromTelegram(message, pair); + // 返回的信息不太够 + const qqMessage = await this.oicq.getMsg(qqMessageSent.message_id); + if (qqMessage) { + // 更新数据库 + await db.message.create({ + data: { + qqRoomId: helper.getRoomId(pair.qq), + qqSenderId: qqMessage.sender.user_id, + time: qqMessage.time, + brief: qqMessage.raw_message, + seq: qqMessage.seq, + rand: qqMessage.rand, + pktnum: qqMessage.pktnum, + tgChatId: Number(pair.tg.id), + tgMsgId: message.id, }, }); } diff --git a/src/helpers/forwardHelper.ts b/src/helpers/forwardHelper.ts index 685a2ed..31c5326 100644 --- a/src/helpers/forwardHelper.ts +++ b/src/helpers/forwardHelper.ts @@ -3,6 +3,10 @@ import { CustomFile } from 'telegram/client/uploads'; import { Friend, Group } from 'oicq'; import { base64decode } from 'nodejs-base64'; import { getLogger } from 'log4js'; +import { Entity } from 'telegram/define'; +import { Api } from 'telegram'; +import ChatForbidden = Api.ChatForbidden; +import ChatEmpty = Api.ChatEmpty; const log = getLogger('ForwardHelper'); @@ -119,4 +123,17 @@ export default { }; } }, + + getUserDisplayName(user: Entity) { + if ('firstName' in user) { + return user.firstName + + (user.lastName ? ' ' + user.lastName : ''); + } + else if('title' in user){ + return user.title + } + else if('id' in user){ + return user.id.toString() + } + }, }; diff --git a/src/providers/forwardPairs.ts b/src/providers/forwardPairs.ts index 36bf08d..59472d0 100644 --- a/src/providers/forwardPairs.ts +++ b/src/providers/forwardPairs.ts @@ -1,9 +1,9 @@ import { Friend, Group } from 'oicq'; import TelegramChat from '../client/TelegramChat'; -import { Api } from 'telegram'; import OicqClient from '../client/OicqClient'; import Telegram from '../client/Telegram'; import db from './db'; +import { Entity } from 'telegram/define'; export type Pair = { qq: Friend | Group; @@ -34,7 +34,7 @@ class ForwardPairsInternal { }); } - public find(target: Friend | Group | TelegramChat | Api.Chat | number) { + public find(target: Friend | Group | TelegramChat | Entity | number) { if (target instanceof Friend) { return this.pairs.find(e => e.qq instanceof Friend && e.qq.user_id === target.user_id); } diff --git a/src/services/ForwardService.ts b/src/services/ForwardService.ts index 09c0c47..be29965 100644 --- a/src/services/ForwardService.ts +++ b/src/services/ForwardService.ts @@ -1,6 +1,6 @@ import Telegram from '../client/Telegram'; import OicqClient from '../client/OicqClient'; -import { GroupMessageEvent, PrivateMessageEvent } from 'oicq'; +import { GroupMessageEvent, PrivateMessageEvent, Quotable, segment, Sendable } from 'oicq'; import { Pair } from '../providers/forwardPairs'; import { fetchFile, getBigFaceUrl, getImageUrlByMd5 } from '../utils/urls'; import { FileLike, MarkupLike } from 'telegram/define'; @@ -12,6 +12,11 @@ import helper from '../helpers/forwardHelper'; import db from '../providers/db'; import { Button } from 'telegram/tl/custom/button'; import { SendMessageParams } from 'telegram/client/messages'; +import { Api } from 'telegram'; +import { config } from '../providers/userConfig'; +import { file as createTempFile, FileResult } from 'tmp-promise'; +import fsP from 'fs/promises'; +import GeoPoint = Api.GeoPoint; // noinspection FallThroughInSwitchStatementJS export default class ForwardService { @@ -177,7 +182,101 @@ export default class ForwardService { return await pair.tg.sendMessage(messageToSend); } catch (e) { - this.log.error('从 QQ 到 TG 到消息转发失败', e); + this.log.error('从 QQ 到 TG 的消息转发失败', e); + } + } + + async forwardFromTelegram(message: Api.Message, pair: Pair) { + try { + const tempFiles: FileResult[] = []; + const chain: Sendable = []; + config.workMode === 'group' && chain.push(helper.getUserDisplayName(message.sender) + + (message.forward ? ' Forwarded from ' + helper.getUserDisplayName(message.forward.chat || message.forward.sender) : '') + + ': \n'); + console.log(message.document); + if (message.photo instanceof Api.Photo || + // stickers 和以文件发送的图片都是这个 + message.document?.mimeType?.startsWith('image/')) { + chain.push(segment.image(await message.downloadMedia({}))); + } + else if (message.video || message.videoNote || message.gif) { + const file = message.video || message.videoNote || message.gif; + if (file.size > 20 * 1024 * 1024) { + chain.push('[视频大于 20MB]'); + } + else { + const temp = await createTempFile(); + tempFiles.push(temp); + await fsP.writeFile(temp.path, await message.downloadMedia({})); + chain.push(segment.video(temp.path)); + } + } + else if (message.voice) { + // TODO + chain.push('语音'); + } + else if (message.poll) { + const poll = message.poll.poll; + chain.push(`${poll.multipleChoice ? '多' : '单'}选投票:\n${poll.question}`); + chain.push(...poll.answers.map(answer => `\n - ${answer.text}`)); + } + else if (message.contact) { + const contact = message.contact; + chain.push(`名片:\n` + + contact.firstName + (contact.lastName ? ' ' + contact.lastName : '') + + (contact.phoneNumber ? `\n电话:${contact.phoneNumber}` : '')); + } + else if (message.venue && message.venue.geo instanceof GeoPoint) { + // 地标 + chain.push(segment.location(message.venue.geo.lat, message.venue.geo.long, `${message.venue.title} (${message.venue.address})`)); + } + else if (message.geo instanceof GeoPoint) { + // 普通的位置,没有名字 + chain.push(segment.location(message.geo.lat, message.geo.long, '选中的位置')); + } + else if (message.media instanceof Api.MessageMediaDocument && message.media.document instanceof Api.Document) { + // TODO 转发比较小的群文件 + const file = message.media.document; + const fileNameAttribute = + file.attributes.find(attribute => attribute instanceof Api.DocumentAttributeFilename) as Api.DocumentAttributeFilename; + chain.push(`文件:${fileNameAttribute ? fileNameAttribute.fileName : ''}\n` + + `类型:${file.mimeType}\n` + + `大小:${file.size}`); + } + + message.message && chain.push(message.message); + + // 处理回复 + let source: Quotable; + if (message.replyToMsgId) { + try { + const quote = await db.message.findFirst({ + where: { + tgChatId: Number(pair.tg.id), + tgMsgId: message.replyToMsgId, + }, + }); + if (quote) { + source = { + message: quote.brief, + seq: quote.seq, + rand: quote.rand, + user_id: quote.qqSenderId, + time: quote.time, + }; + } + } + catch (e) { + this.log.error('查找回复消息失败', e); + } + } + + const qqMessage = await pair.qq.sendMsg(chain); + tempFiles.forEach(it => it.cleanup()); + return qqMessage; + } + catch (e) { + this.log.error('从 TG 到 QQ 的消息转发失败', e); } } } diff --git a/tsconfig.json b/tsconfig.json index 67e88d9..1d18425 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "compilerOptions": { - "module": "nodenext", + "module": "CommonJS", "target": "ESNext", "esModuleInterop": true, "sourceMap": false, diff --git a/yarn.lock b/yarn.lock index 1d456f7..6cd4819 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1552,6 +1552,7 @@ __metadata: oicq: ^2.2.0 prisma: latest telegram: ^2.5.0 + tmp-promise: ^3.0.3 ts-node: ^10.5.0 tsc: ^2.0.4 typescript: ^4.5.5 @@ -1601,7 +1602,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -1845,6 +1846,24 @@ __metadata: languageName: node linkType: hard +"tmp-promise@npm:^3.0.3": + version: 3.0.3 + resolution: "tmp-promise@npm:3.0.3" + dependencies: + tmp: ^0.2.0 + checksum: f854f5307dcee6455927ec3da9398f139897faf715c5c6dcee6d9471ae85136983ea06662eba2edf2533bdcb0fca66d16648e79e14381e30c7fb20be9c1aa62c + languageName: node + linkType: hard + +"tmp@npm:^0.2.0": + version: 0.2.1 + resolution: "tmp@npm:0.2.1" + dependencies: + rimraf: ^3.0.0 + checksum: 8b1214654182575124498c87ca986ac53dc76ff36e8f0e0b67139a8d221eaecfdec108c0e6ec54d76f49f1f72ab9325500b246f562b926f85bcdfca8bf35df9e + languageName: node + linkType: hard + "token-types@npm:^5.0.0-alpha.2": version: 5.0.0-alpha.2 resolution: "token-types@npm:5.0.0-alpha.2"