feat: MapInstance 功能,在 tg 群里面发消息,如果 tg 账号在同一进程中有一个个人模式的实例,在 QQ 群里消息会用发送者个人模式实例对应的 QQ 发出去

This commit is contained in:
Clansty 2023-02-23 14:08:53 +08:00
parent 59204eb89a
commit ee57624df3
No known key found for this signature in database
7 changed files with 122 additions and 30 deletions

View File

@ -92,7 +92,7 @@ export default class ForwardController {
await db.message.create({
data: {
qqRoomId: pair.qqRoomId,
qqSenderId: this.oicq.uin,
qqSenderId: qqMessageSent.senderId,
time: qqMessageSent.time,
brief: qqMessageSent.brief,
seq: qqMessageSent.seq,

View File

@ -16,12 +16,21 @@ import db from './models/db';
log.error('UnhandledException: ', error);
});
const instanceEntries = await db.instance.findMany();
const instances = [] as Instance[];
if (!instanceEntries.length) {
await Instance.start(0);
instances.push(await Instance.start(0));
}
else {
for (const instanceEntry of instanceEntries) {
await Instance.start(instanceEntry.id);
instances.push(await Instance.start(instanceEntry.id));
}
}
setTimeout(async () => {
log.info('开始加载 MapInstance')
for (const instance of instances.filter(it => it.workMode === 'group')) {
await instance.forwardPairs.initMapInstance(instances.filter(it => it.workMode === 'personal'));
}
}, 15 * 1000);
})();

View File

@ -7,6 +7,7 @@ import { Entity } from 'telegram/define';
import { BigInteger } from 'big-integer';
import { Pair } from './Pair';
import { getLogger, Logger } from 'log4js';
import Instance from './Instance';
export default class ForwardPairs {
private pairs: Pair[] = [];
@ -75,4 +76,21 @@ export default class ForwardPairs {
return this.pairs.find(e => e.tg.id.eq(target.id));
}
}
public async initMapInstance(instances: Instance[]) {
for (const forwardPair of this.pairs) {
for (const instance of instances) {
const instanceTgUserId = instance.userMe.id.toString();
if (forwardPair.instanceMapForTg[instanceTgUserId]) continue;
try {
const group = instance.oicq.getChat(forwardPair.qqRoomId) as Group;
if (!group) continue;
forwardPair.instanceMapForTg[instanceTgUserId] = group;
this.log.info('MapInstance', { group: forwardPair.qqRoomId, tg: instanceTgUserId, qq: instance.qqUin });
}
catch {
}
}
}
}
}

View File

@ -35,9 +35,9 @@ export default class Instance {
private readonly log: Logger;
private tgBot: Telegram;
private tgUser: Telegram;
private oicq: OicqClient;
public tgBot: Telegram;
public tgUser: Telegram;
public oicq: OicqClient;
private _ownerChat: TelegramChat;

View File

@ -1,14 +1,18 @@
import { getLogger } from "log4js";
import { Friend, Group } from "oicq";
import TelegramChat from "../client/TelegramChat";
import getAboutText from "../utils/getAboutText";
import { md5 } from "../utils/hashing";
import { getAvatar } from "../utils/urls";
import db from "./db";
import { getLogger } from 'log4js';
import { Friend, Group } from 'oicq';
import TelegramChat from '../client/TelegramChat';
import getAboutText from '../utils/getAboutText';
import { md5 } from '../utils/hashing';
import { getAvatar } from '../utils/urls';
import db from './db';
const log = getLogger("ForwardPair");
const log = getLogger('ForwardPair');
export class Pair {
// 群成员的 tg 账号对应它对应的 QQ 账号获取到的 Group 对象
// 只有群组模式有效
public readonly instanceMapForTg = {} as { [tgUserId: string]: Group };
constructor(
public readonly qq: Friend | Group,
private _tg: TelegramChat,
@ -17,8 +21,9 @@ export class Pair {
private _poke: boolean,
private _enable: boolean,
private _disableQ2TG: boolean,
private _disableTG2Q: boolean
) {}
private _disableTG2Q: boolean,
) {
}
// 更新 TG 群组的头像和简介
public async updateInfo() {

View File

@ -1,5 +1,15 @@
import Telegram from '../client/Telegram';
import { Group, GroupMessageEvent, MessageElem, PrivateMessageEvent, PttElem, Quotable, segment, Sendable } from 'oicq';
import {
Group,
GroupMessageEvent,
MessageElem, MessageRet,
MiraiElem,
PrivateMessageEvent,
PttElem,
Quotable,
segment,
Sendable,
} from 'oicq';
import { fetchFile, getBigFaceUrl, getImageUrlByMd5 } from '../utils/urls';
import { ButtonLike, FileLike } from 'telegram/define';
import { getLogger, Logger } from 'log4js';
@ -23,7 +33,7 @@ import lottie from '../constants/lottie';
import _ from 'lodash';
import emoji from '../constants/emoji';
import convert from '../helpers/convert';
import { CustomFile } from 'telegram/client/uploads';
import { QQMessageSent } from '../types/definitions';
const NOT_CHAINABLE_ELEMENTS = ['flash', 'record', 'video', 'location', 'share', 'json', 'xml', 'poke'];
@ -39,6 +49,16 @@ export default class ForwardService {
public async forwardFromQq(event: PrivateMessageEvent | GroupMessageEvent, pair: Pair) {
try {
const messageMirai = event.message.find(it => it.type === 'mirai') as MiraiElem;
if (messageMirai) {
try {
const miraiData = JSON.parse(messageMirai.data);
if (miraiData.q2tgSkip) return;
}
catch {
}
}
const tempFiles: FileResult[] = [];
let message = '', files: FileLike[] = [], buttons: ButtonLike[] = [], replyTo = 0;
let messageHeader = '', sender = '';
@ -295,20 +315,20 @@ export default class ForwardService {
}
}
async forwardFromTelegram(message: Api.Message, pair: Pair) {
async forwardFromTelegram(message: Api.Message, pair: Pair): Promise<Array<QQMessageSent>> {
try {
const tempFiles: FileResult[] = [];
const chain: Sendable = [];
const senderId = Number(message.senderId || message.sender?.id);
// 这条消息在 tg 中被回复的时候显示的
let brief = '';
this.instance.workMode === 'group' && chain.push(helper.getUserDisplayName(message.sender) +
const messageHeader = helper.getUserDisplayName(message.sender) +
(message.forward ? ' 转发自 ' +
// 要是隐私设置了,应该会有这个,然后下面两个都获取不到
(message.fwdFrom?.fromName ||
helper.getUserDisplayName(await message.forward.getChat() || await message.forward.getSender())) :
'') +
': \n');
': \n';
if (message.photo instanceof Api.Photo ||
// stickers 和以文件发送的图片都是这个
message.document?.mimeType?.startsWith('image/')) {
@ -474,24 +494,60 @@ export default class ForwardService {
}
}
// 防止发送空白消息,也就是除了发送者啥都没有的消息
if (this.instance.workMode === 'group' && chain.length === 1) {
// 防止发送空白消息
if (chain.length === 0) {
return [];
}
const notChainableElements = chain.filter(element => typeof element === 'object' && NOT_CHAINABLE_ELEMENTS.includes(element.type));
const chainableElements = chain.filter(element => typeof element !== 'object' || !NOT_CHAINABLE_ELEMENTS.includes(element.type));
const qqMessages = [];
if (chainableElements.length) {
if (this.instance.workMode === 'group') {
chainableElements.push({
type: 'mirai',
data: JSON.stringify({ id: senderId }, undefined, 0),
});
// MapInstance
if (!notChainableElements.length // notChainableElements 无法附加 mirai 信息,要防止被来回转发
&& chainableElements.length
&& this.instance.workMode
&& pair.instanceMapForTg[senderId]
) {
try {
const messageSent = await pair.instanceMapForTg[senderId].sendMsg([
...chainableElements,
{
type: 'mirai',
data: JSON.stringify({
id: senderId,
eqq: { type: 'tg', tgUid: senderId, noSplitSender: true },
q2tgSkip: true,
}, undefined, 0),
},
]);
tempFiles.forEach(it => it.cleanup());
return [{
...messageSent,
senderId: pair.instanceMapForTg[senderId].client.uin,
brief,
}];
}
catch (e) {
this.log.error('使用 MapInstance 发送消息失败', e);
}
}
if (this.instance.workMode === 'group') {
chainableElements.unshift(messageHeader);
}
const qqMessages = [] as Array<QQMessageSent>;
if (chainableElements.length) {
chainableElements.push({
type: 'mirai',
data: JSON.stringify({
id: senderId,
eqq: { type: 'tg', tgUid: senderId, noSplitSender: this.instance.workMode === 'personal' },
}, undefined, 0),
});
qqMessages.push({
...await pair.qq.sendMsg(chainableElements, source),
brief,
senderId: this.oicq.uin,
});
}
if (notChainableElements.length) {
@ -499,6 +555,7 @@ export default class ForwardService {
qqMessages.push({
...await pair.qq.sendMsg(notChainableElement, source),
brief,
senderId: this.oicq.uin,
});
}
}

View File

@ -1 +1,4 @@
import { MessageRet } from 'oicq';
export type WorkMode = 'group' | 'personal';
export type QQMessageSent = MessageRet & { senderId: number, brief: string };