diff --git a/package.json b/package.json index 28437d9a..ef6ba03e 100644 --- a/package.json +++ b/package.json @@ -42,10 +42,10 @@ "nodejs-base64": "^2.0.0", "prisma": "5.8.0", "prompts": "^2.4.2", - "quote-api": "https://github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz", + "quote-api": "https://github.com/Clansty/quote-api/archive/014b21138afbbe0e12c91b00561414b1e851fc0f.tar.gz", "sharp": "^0.33.2", "silk-sdk": "^0.2.2", - "telegram": "^2.19.10", + "telegram": "https://github.com/clansty/gramjs/releases/download/2.19.10%2Brevert_media/telegram-2.19.10.tgz", "tmp-promise": "^3.0.3", "undici": "^6.4.0", "zincsearch-node": "^2.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45cb77da..eb5f1417 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,8 +60,8 @@ dependencies: specifier: ^2.4.2 version: 2.4.2 quote-api: - specifier: https://github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz - version: '@github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz' + specifier: https://github.com/Clansty/quote-api/archive/014b21138afbbe0e12c91b00561414b1e851fc0f.tar.gz + version: '@github.com/Clansty/quote-api/archive/014b21138afbbe0e12c91b00561414b1e851fc0f.tar.gz' sharp: specifier: ^0.33.2 version: 0.33.2 @@ -69,8 +69,8 @@ dependencies: specifier: ^0.2.2 version: 0.2.2 telegram: - specifier: ^2.19.10 - version: 2.19.10 + specifier: https://github.com/clansty/gramjs/releases/download/2.19.10%2Brevert_media/telegram-2.19.10.tgz + version: '@github.com/clansty/gramjs/releases/download/2.19.10%25252Brevert_media/telegram-2.19.10.tgz' tmp-promise: specifier: ^3.0.3 version: 3.0.3 @@ -1243,7 +1243,7 @@ packages: engines: {node: '>=6.14.2'} requiresBuild: true dependencies: - node-gyp-build: 4.6.1 + node-gyp-build: 4.8.0 dev: false /buildcheck@0.0.6: @@ -2740,8 +2740,8 @@ packages: whatwg-url: 5.0.0 dev: false - /node-gyp-build@4.6.1: - resolution: {integrity: sha512-24vnklJmyRS8ViBNI8KbtK/r/DmXQMRiOMXTNz2nrTnAYUwjmEEbnnpB/+kt+yWRv73bPsSPRFddrcIbAxSiMQ==} + /node-gyp-build@4.8.0: + resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} hasBin: true requiresBuild: true dev: false @@ -3457,30 +3457,6 @@ packages: - supports-color dev: false - /telegram@2.19.10: - resolution: {integrity: sha512-5Brrn+BcYSjDUmT1/9T4Nq2tYlR1/TreRvpim1UGeuwfnIgFOiTXwDwXBOy6hbn9yssvmtk/dQJU9pBColM7ag==} - dependencies: - '@cryptography/aes': 0.1.1 - async-mutex: 0.3.2 - big-integer: 1.6.52 - buffer: 6.0.3 - htmlparser2: 6.1.0 - mime: 3.0.0 - node-localstorage: 2.2.1 - pako: 2.1.0 - path-browserify: 1.0.1 - real-cancellable-promise: 1.2.0 - socks: 2.7.1 - store2: 2.14.2 - ts-custom-error: 3.3.1 - websocket: 1.0.34 - optionalDependencies: - bufferutil: 4.0.8 - utf-8-validate: 5.0.10 - transitivePeerDependencies: - - supports-color - dev: false - /timm@1.7.1: resolution: {integrity: sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==} dev: false @@ -3563,6 +3539,7 @@ packages: /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + requiresBuild: true dev: false /tsscmp@1.0.6: @@ -3681,7 +3658,7 @@ packages: engines: {node: '>=6.14.2'} requiresBuild: true dependencies: - node-gyp-build: 4.6.1 + node-gyp-build: 4.8.0 dev: false /utif2@4.1.0: @@ -3895,10 +3872,10 @@ packages: resolution: {integrity: sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==} dev: false - '@github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz': - resolution: {tarball: https://github.com/Clansty/quote-api/archive/37a0e48a434b94bb04c04c7d86d9f0d2295df869.tar.gz} + '@github.com/Clansty/quote-api/archive/014b21138afbbe0e12c91b00561414b1e851fc0f.tar.gz': + resolution: {tarball: https://github.com/Clansty/quote-api/archive/014b21138afbbe0e12c91b00561414b1e851fc0f.tar.gz} name: quote-api - version: 0.12.2 + version: 0.14.0 dependencies: canvas: 2.11.2 dotenv: 7.0.0 @@ -3925,3 +3902,29 @@ packages: - supports-color - utf-8-validate dev: false + + '@github.com/clansty/gramjs/releases/download/2.19.10%25252Brevert_media/telegram-2.19.10.tgz': + resolution: {tarball: https://github.com/clansty/gramjs/releases/download/2.19.10%2Brevert_media/telegram-2.19.10.tgz} + name: telegram + version: 2.19.10 + dependencies: + '@cryptography/aes': 0.1.1 + async-mutex: 0.3.2 + big-integer: 1.6.52 + buffer: 6.0.3 + htmlparser2: 6.1.0 + mime: 3.0.0 + node-localstorage: 2.2.1 + pako: 2.1.0 + path-browserify: 1.0.1 + real-cancellable-promise: 1.2.0 + socks: 2.7.1 + store2: 2.14.2 + ts-custom-error: 3.3.1 + websocket: 1.0.34 + optionalDependencies: + bufferutil: 4.0.8 + utf-8-validate: 5.0.10 + transitivePeerDependencies: + - supports-color + dev: false diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 2f189485..64f096d1 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -60,22 +60,23 @@ model QqBot { } model Message { - id Int @id @default(autoincrement()) - qqRoomId BigInt @db.BigInt - qqSenderId BigInt @db.BigInt - time Int - brief String? - seq Int - rand BigInt @db.BigInt - pktnum Int - tgChatId BigInt @db.BigInt - tgMsgId Int - instanceId Int @default(0) - instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) - tgFileId BigInt? @db.BigInt - tgMessageText String? - nick String? // /抱 的时候会用到 - tgSenderId BigInt? @db.BigInt + id Int @id @default(autoincrement()) + qqRoomId BigInt @db.BigInt + qqSenderId BigInt @db.BigInt + time Int + brief String? + seq Int + rand BigInt @db.BigInt + pktnum Int + tgChatId BigInt @db.BigInt + tgMsgId Int + instanceId Int @default(0) + instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) + tgFileId BigInt? @db.BigInt + tgMessageText String? + nick String? // /抱 的时候会用到 + tgSenderId BigInt? @db.BigInt + richHeaderUsed Boolean @default(false) @@index([qqRoomId, qqSenderId, seq, rand, pktnum, time, instanceId]) @@index([tgChatId, tgMsgId, instanceId]) diff --git a/src/constants/emoji.ts b/src/constants/emoji.ts index d3820ed2..2e7e0384 100644 --- a/src/constants/emoji.ts +++ b/src/constants/emoji.ts @@ -7,4 +7,11 @@ export default { index = index % arr.length; return arr[index]; }, + tgColor(index: number) { + // https://github.com/telegramdesktop/tdesktop/blob/7049929a59176a996c4257d5a09df08b04ac3b22/Telegram/SourceFiles/ui/chat/chat_style.cpp#L1043 + // https://github.com/LyoSU/quote-api/blob/master/utils/quote-generate.js#L163 + const arr = [...new Intl.Segmenter().segment('❤️🧡💜💚🩵💙🩷')].map(x => x.segment); + index = index % arr.length; + return arr[index]; + }, }; diff --git a/src/constants/flags.ts b/src/constants/flags.ts index 28676dd4..4d52af4f 100644 --- a/src/constants/flags.ts +++ b/src/constants/flags.ts @@ -6,6 +6,7 @@ enum flags { NO_DELETE_MESSAGE = 1 << 4, NO_AUTO_CREATE_PM = 1 << 5, COLOR_EMOJI_PREFIX = 1 << 6, + RICH_HEADER = 1 << 7, } export default flags; diff --git a/src/controllers/ForwardController.ts b/src/controllers/ForwardController.ts index e0591bc5..c176c6cd 100644 --- a/src/controllers/ForwardController.ts +++ b/src/controllers/ForwardController.ts @@ -60,36 +60,32 @@ export default class ForwardController { }); if (existed) return; // 开始转发过程 - let tgMessages: Api.Message | Api.Message[] = await this.forwardService.forwardFromQq(event, pair); - if (!tgMessages) return; - if (!Array.isArray(tgMessages)) { - tgMessages = [tgMessages]; - } - for (const tgMessage of tgMessages) { - // 更新数据库 - await db.message.create({ - data: { - qqRoomId: pair.qqRoomId, - qqSenderId: event.sender.user_id, - time: event.time, - brief: event.raw_message, - seq: event.seq, - rand: event.rand, - pktnum: event.pktnum, - tgChatId: pair.tgId, - tgMsgId: tgMessage.id, - instanceId: this.instance.id, - tgMessageText: tgMessage.message, - tgFileId: forwardHelper.getMessageDocumentId(tgMessage), - nick: event.nickname, - tgSenderId: BigInt(this.tgBot.me.id.toString()), - }, - }); - await this.forwardService.addToZinc(pair.dbId, tgMessage.id, { - text: event.raw_message, + let { tgMessage, richHeaderUsed } = await this.forwardService.forwardFromQq(event, pair); + if (!tgMessage) return; + // 更新数据库 + await db.message.create({ + data: { + qqRoomId: pair.qqRoomId, + qqSenderId: event.sender.user_id, + time: event.time, + brief: event.raw_message, + seq: event.seq, + rand: event.rand, + pktnum: event.pktnum, + tgChatId: pair.tgId, + tgMsgId: tgMessage.id, + instanceId: this.instance.id, + tgMessageText: tgMessage.message, + tgFileId: forwardHelper.getMessageDocumentId(tgMessage), nick: event.nickname, - }); - } + tgSenderId: BigInt(this.tgBot.me.id.toString()), + richHeaderUsed, + }, + }); + await this.forwardService.addToZinc(pair.dbId, tgMessage.id, { + text: event.raw_message, + nick: event.nickname, + }); } catch (e) { this.log.error('处理 QQ 消息时遇到问题', e); diff --git a/src/controllers/QuotLyController.ts b/src/controllers/QuotLyController.ts index cd371c0e..5a5a8361 100644 --- a/src/controllers/QuotLyController.ts +++ b/src/controllers/QuotLyController.ts @@ -163,7 +163,7 @@ export default class { title: message.nick, photo: { url: getAvatarUrl(message.qqSenderId) }, }; - if (message.qqRoomId > 0) { + if (message.qqRoomId > 0 || message.richHeaderUsed) { quoteMessage.text = message.tgMessageText; } else if (message.tgMessageText.includes('\n')) { @@ -195,7 +195,7 @@ export default class { photo = await convert.cachedBuffer(`${sender.photo.photoId.toString(16)}.jpg`, () => this.tgBot.downloadEntityPhoto(sender)); } messageFrom = { - id: Number(message.tgSenderId), + id: sender.color || Number(message.tgSenderId), name: message.nick, title: message.nick, username: sender.username, diff --git a/src/services/ForwardService.ts b/src/services/ForwardService.ts index 51dfa577..f508c235 100644 --- a/src/services/ForwardService.ts +++ b/src/services/ForwardService.ts @@ -44,6 +44,7 @@ import ReplyKeyboardHide = Api.ReplyKeyboardHide; import env from '../models/env'; import { CustomFile } from 'telegram/client/uploads'; import flags from '../constants/flags'; +import BigInteger from 'big-integer'; const NOT_CHAINABLE_ELEMENTS = ['flash', 'record', 'video', 'location', 'share', 'json', 'xml', 'poke']; @@ -331,7 +332,6 @@ export default class ForwardService { } } message = message.trim(); - message = messageHeader + (message && messageHeader ? '\n' : '') + message; // 处理回复 if (event.source) { @@ -366,28 +366,66 @@ export default class ForwardService { } + let richHeaderUsed = false; // 发送消息 const messageToSend: SendMessageParams = { forceDocument: forceDocument as any, // 恼 }; - message && (messageToSend.message = message); if (files.length === 1) { messageToSend.file = files[0]; } else if (files.length) { messageToSend.file = files; } + else if ((pair.flags | this.instance.flags) & flags.RICH_HEADER) { + // 没有文件时才能显示链接预览 + richHeaderUsed = true; + const url = new URL('https://q2tg-header.clansty.workers.dev'); + url.searchParams.set('name', sender); + url.searchParams.set('title', 'title' in event.sender ? event.sender.title : ''); + url.searchParams.set('role', 'role' in event.sender ? event.sender.role : ''); + url.searchParams.set('id', event.sender.user_id.toString()); + // https://github.com/tdlib/td/blob/437c2d0c6e0ad104022d5ad86ddc8aedc41cb7a8/td/telegram/MessageContent.cpp#L2575 + // https://github.com/tdlib/td/blob/437c2d0c6e0ad104022d5ad86ddc8aedc41cb7a8/td/generate/scheme/telegram_api.tl#L1841 + // https://github.com/gram-js/gramjs/pull/633 + messageToSend.file = new Api.InputMediaWebPage({ + url: url.toString(), + forceSmallMedia: true, + optional: true, + }); + messageToSend.linkPreview = { showAboveText: true }; + } + + if (!richHeaderUsed) { + message = messageHeader + (message && messageHeader ? '\n' : '') + message; + } + message && (messageToSend.message = message); + buttons.length && (messageToSend.buttons = _.chunk(buttons, 3)); replyTo && (messageToSend.replyTo = replyTo); - const tgMessage = await pair.tg.sendMessage(messageToSend); + let tgMessage: Api.Message; + try { + tgMessage = await pair.tg.sendMessage(messageToSend); + } + catch (e) { + if (richHeaderUsed) { + this.log.warn('Rich Header 发送错误', messageToSend.file, e); + delete messageToSend.file; + delete messageToSend.linkPreview; + message = messageHeader + (message && messageHeader ? '\n' : '') + message; + message && (messageToSend.message = message); + tgMessage = await pair.tg.sendMessage(messageToSend); + } + else throw e; + } if (this.instance.workMode === 'personal' && event.message_type === 'group' && event.atall) { await tgMessage.pin({ notify: false }); } tempFiles.forEach(it => it.cleanup()); - return tgMessage; + return { tgMessage, richHeaderUsed }; } catch (e) { this.log.error('从 QQ 到 TG 的消息转发失败', e); @@ -396,11 +434,12 @@ export default class ForwardService { } catch { } - return null; + return {}; } } public async forwardFromTelegram(message: Api.Message, pair: Pair): Promise> { + // console.log(message); try { const tempFiles: FileResult[] = []; let chain: Sendable = []; @@ -415,7 +454,7 @@ export default class ForwardService { '') + ': \n'; if ((pair.flags | this.instance.flags) & flags.COLOR_EMOJI_PREFIX) { - messageHeader = emoji.color(message.senderId.toJSNumber()) + messageHeader; + messageHeader = emoji.tgColor((message.sender as Api.User)?.color || message.senderId.toJSNumber()) + messageHeader; } if (message.photo instanceof Api.Photo || // stickers 和以文件发送的图片都是这个