mirror of https://github.com/Nofated095/Q2TG.git
feat: 从 TG 到 QQ 到消息转发
This commit is contained in:
parent
61c79bb0e7
commit
848d926cc6
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<boolean>;
|
||||
type MessageHandler = (message: Api.Message) => Promise<boolean | void>;
|
||||
|
||||
export default class Telegram {
|
||||
private readonly client: TelegramClient;
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "nodenext",
|
||||
"module": "CommonJS",
|
||||
"target": "ESNext",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": false,
|
||||
|
|
21
yarn.lock
21
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"
|
||||
|
|
Loading…
Reference in New Issue