From d6eb2fb47f2fa91ca3993992ce920fe4cd383b12 Mon Sep 17 00:00:00 2001 From: Clansty Date: Sat, 28 Dec 2024 21:47:19 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20qq=20->=20tg=20=E4=B8=AA=E4=BA=BA?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E7=BE=A4=E7=BB=84=E5=90=8D=E7=A7=B0=E5=8F=98?= =?UTF-8?q?=E6=9B=B4=E5=90=8C=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/prisma/schema.prisma | 8 ++-- main/src/client/NapCatClient/client.ts | 10 ++++- main/src/client/QQClient/events.ts | 12 ++++- main/src/client/QQClient/index.ts | 14 +++++- main/src/client/Telegram.ts | 21 +++++++++ main/src/client/TelegramChat.ts | 11 +++++ main/src/constants/flags.ts | 1 + .../controllers/GroupNameRefreshController.ts | 44 +++++++++++++++++++ main/src/controllers/TypingController.ts | 4 +- main/src/models/Instance.ts | 5 +++ 10 files changed, 121 insertions(+), 9 deletions(-) create mode 100644 main/src/controllers/GroupNameRefreshController.ts diff --git a/main/prisma/schema.prisma b/main/prisma/schema.prisma index 79b403c..eb1c522 100644 --- a/main/prisma/schema.prisma +++ b/main/prisma/schema.prisma @@ -21,7 +21,7 @@ model Session { model Entity { id Int @id @default(autoincrement()) - // 源代码里面大概支持 string 和 BigInteger,不如先全都存 String + /// 源代码里面大概支持 string 和 BigInteger,不如先全都存 String entityId String sessionId Int session Session @relation(fields: [sessionId], references: [id], onDelete: Cascade) @@ -80,10 +80,10 @@ model Message { instance Instance @relation(fields: [instanceId], references: [id], onDelete: Cascade) tgFileId BigInt? @db.BigInt tgMessageText String? - nick String? // /抱 的时候会用到 + nick String? /// /抱 的时候会用到 tgSenderId BigInt? @db.BigInt richHeaderUsed Boolean @default(false) - ignoreDelete Boolean @default(false) // rmq rmt + ignoreDelete Boolean @default(false) /// rmq rmt @@index([qqRoomId, qqSenderId, seq, rand, pktnum, time, instanceId]) @@index([tgChatId, tgMsgId, instanceId]) @@ -92,7 +92,7 @@ model Message { model ForwardPair { id Int @id @default(autoincrement()) qqRoomId BigInt @db.BigInt - qqFromGroupId BigInt? @db.BigInt // 用于群临时 + qqFromGroupId BigInt? @db.BigInt /// 用于群临时 tgChatId BigInt @db.BigInt avatarCache AvatarCache[] instanceId Int @default(0) diff --git a/main/src/client/NapCatClient/client.ts b/main/src/client/NapCatClient/client.ts index 527caf1..98d3f2b 100644 --- a/main/src/client/NapCatClient/client.ts +++ b/main/src/client/NapCatClient/client.ts @@ -1,4 +1,4 @@ -import { CreateQQClientParamsBase, Friend, FriendIncreaseEvent, Group, GroupMemberDecreaseEvent, GroupMemberIncreaseEvent, InputStatusChangeEvent, MessageEvent, MessageRecallEvent, PokeEvent, QQClient, SendableElem } from '../QQClient'; +import { CreateQQClientParamsBase, Friend, FriendIncreaseEvent, Group, GroupMemberDecreaseEvent, GroupMemberIncreaseEvent, GroupNameChangeEvent, InputStatusChangeEvent, MessageEvent, MessageRecallEvent, PokeEvent, QQClient, SendableElem } from '../QQClient'; import random from '../../utils/random'; import { getLogger, Logger } from 'log4js'; import posthog from '../../models/posthog'; @@ -84,6 +84,9 @@ export class NapCatClient extends QQClient { await this.handlePoke(data); else if (data.post_type === 'notice' && data.notice_type === 'notify' && data.sub_type === 'input_status') await this.handleInput(data); + // @ts-ignore + else if (data.post_type === 'notice' && data.notice_type === 'notify' && data.sub_type === 'group_name') + await this.handleGroupNameChange(data); else if (data.post_type === 'request' && data.request_type === 'friend') await this.handleFriendRequest(data); else if (data.post_type === 'request' && data.request_type === 'group' && data.sub_type === 'invite') @@ -168,6 +171,11 @@ export class NapCatClient extends QQClient { await this.callHandlers(this.onInputStatusChangeHandlers, new InputStatusChangeEvent(await this.pickFriend(data.user_id), !!data.status_text)); } + private async handleGroupNameChange(data: { group_id: number; user_id: number; name_new: string }) { + const group = await this.pickGroup(data.group_id); + await this.callHandlers(this.onGroupNameChangeHandlers, new GroupNameChangeEvent(group, group.pickMember(data.user_id), data.name_new)); + } + private async handleFriendRequest(data: WSReceiveHandler['request.friend']) { const event = await NapCatFriendRequestEvent.create(this, data); await this.callHandlers(this.onFriendRequestHandlers, event); diff --git a/main/src/client/QQClient/events.ts b/main/src/client/QQClient/events.ts index bfafcb9..73c19c3 100644 --- a/main/src/client/QQClient/events.ts +++ b/main/src/client/QQClient/events.ts @@ -1,6 +1,5 @@ -import { Friend, Group, Sendable } from './index'; +import { Friend, Group, QQUser, Sendable } from './index'; import type { MessageElem } from '@icqqjs/icqq'; -import type { Receive } from 'node-napcat-ts'; import { NapCatForwardElem } from '../NapCatClient/convert'; export abstract class ChatEvent { @@ -122,3 +121,12 @@ export class InputStatusChangeEvent { ) { } } + +export class GroupNameChangeEvent { + constructor( + public readonly group: Group, + public readonly operator: QQUser, + public readonly newName: string, + ) { + } +} diff --git a/main/src/client/QQClient/index.ts b/main/src/client/QQClient/index.ts index c9f0102..ace1d9e 100644 --- a/main/src/client/QQClient/index.ts +++ b/main/src/client/QQClient/index.ts @@ -3,7 +3,7 @@ import { Friend, Group, SendableElem } from './entity'; import { FriendIncreaseEvent, GroupMemberDecreaseEvent, - GroupMemberIncreaseEvent, InputStatusChangeEvent, + GroupMemberIncreaseEvent, GroupNameChangeEvent, InputStatusChangeEvent, MessageEvent, MessageRecallEvent, PokeEvent, } from './events'; @@ -184,6 +184,18 @@ export abstract class QQClient { this.onInputStatusChangeHandlers.splice(this.onInputStatusChangeHandlers.indexOf(handler), 1); } + // + protected readonly onGroupNameChangeHandlers: Array<(e: GroupNameChangeEvent) => Promise> = []; + + public addGroupNameChangeHandler(handler: (e: GroupNameChangeEvent) => Promise) { + this.onGroupNameChangeHandlers.push(handler); + } + + public removeGroupNameChangeHandler(handler: (e: GroupNameChangeEvent) => Promise) { + this.onGroupNameChangeHandlers.includes(handler) && + this.onGroupNameChangeHandlers.splice(this.onGroupNameChangeHandlers.indexOf(handler), 1); + } + // End Handlers public getChat(roomId: number, tempChatFromGroupId?: number): Promise { diff --git a/main/src/client/Telegram.ts b/main/src/client/Telegram.ts index f9571f4..44cb6ad 100644 --- a/main/src/client/Telegram.ts +++ b/main/src/client/Telegram.ts @@ -19,6 +19,7 @@ import env from '../models/env'; type MessageHandler = (message: Api.Message) => Promise; type ServiceMessageHandler = (message: Api.MessageService) => Promise; +type ChannelUserTypingHandler = (event: Api.UpdateChannelUserTyping) => Promise; export default class Telegram { private readonly client: TelegramClient; @@ -27,6 +28,7 @@ export default class Telegram { private readonly onMessageHandlers: Array = []; private readonly onEditedMessageHandlers: Array = []; private readonly onServiceMessageHandlers: Array = []; + private readonly onChannelUserTypingHandlers: Array = []; public me: Api.User; private static existedBots = {} as { [id: number]: Telegram }; @@ -95,6 +97,9 @@ export default class Telegram { types: [Api.UpdateNewMessage], func: (update: Api.UpdateNewMessage) => update.message instanceof Api.MessageService, })); + this.client.addEventHandler(this.onChannelUserTyping, new Raw({ + types: [Api.UpdateChannelUserTyping], + })); this.client.addEventHandler(this.callbackQueryHelper.onCallbackQuery, new CallbackQuery()); this.me = await this.client.getMe() as Api.User; } @@ -121,6 +126,13 @@ export default class Telegram { } }; + private onChannelUserTyping = async (event: Api.UpdateChannelUserTyping) => { + for (const handler of this.onChannelUserTypingHandlers) { + const res = await handler(event); + if (res) return; + } + }; + /** * 注册消息处理器 * @param handler 此方法返回 true 可以阻断下面的处理器 @@ -152,6 +164,15 @@ export default class Telegram { this.onServiceMessageHandlers.splice(this.onServiceMessageHandlers.indexOf(handler), 1); } + public addChannelUserTypingHandler(handler: ChannelUserTypingHandler) { + this.onChannelUserTypingHandlers.push(handler); + } + + public removeChannelUserTypingHandler(handler: ChannelUserTypingHandler) { + this.onChannelUserTypingHandlers.includes(handler) && + this.onChannelUserTypingHandlers.splice(this.onChannelUserTypingHandlers.indexOf(handler), 1); + } + public addDeletedMessageEventHandler(handler: (event: DeletedMessageEvent) => any) { this.client.addEventHandler(handler, new DeletedMessage({})); } diff --git a/main/src/client/TelegramChat.ts b/main/src/client/TelegramChat.ts index c2a70c5..19e5ced 100644 --- a/main/src/client/TelegramChat.ts +++ b/main/src/client/TelegramChat.ts @@ -142,6 +142,17 @@ export default class TelegramChat { ); } + public async editTitle(title: string) { + if (!(this.entity instanceof Api.Chat || this.entity instanceof Api.Channel)) + throw new Error('不是群组,无法设置描述'); + return await this.client.invoke( + new Api.channels.EditTitle({ + channel: this.entity, + title, + }), + ); + } + public async getInviteLink() { if (!(this.entity instanceof Api.Chat || this.entity instanceof Api.Channel)) throw new Error('不是群组,无法邀请'); diff --git a/main/src/constants/flags.ts b/main/src/constants/flags.ts index 32d82b5..fa71f0d 100644 --- a/main/src/constants/flags.ts +++ b/main/src/constants/flags.ts @@ -16,6 +16,7 @@ enum flags { DISABLE_RICH_HEADER = 1 << 14, DISABLE_OFFLINE_NOTICE = 1 << 15, HIDE_ALL_QQ_NUMBER = 1 << 16, + NAME_LOCKED = 1 << 17, } export default flags; diff --git a/main/src/controllers/GroupNameRefreshController.ts b/main/src/controllers/GroupNameRefreshController.ts new file mode 100644 index 0000000..359eeb3 --- /dev/null +++ b/main/src/controllers/GroupNameRefreshController.ts @@ -0,0 +1,44 @@ +import Instance from '../models/Instance'; +import Telegram from '../client/Telegram'; +import { GroupNameChangeEvent, QQClient } from '../client/QQClient'; +import flags from '../constants/flags'; +import { NapCatGroupMember } from '../client/NapCatClient'; +import env from '../models/env'; +import helper from '../helpers/forwardHelper'; +import { getLogger, Logger } from 'log4js'; + +export default class GroupNameRefreshController { + private readonly log: Logger; + + constructor(private readonly instance: Instance, + private readonly tgBot: Telegram, + private readonly tgUser: Telegram, + private readonly oicq: QQClient) { + oicq.addGroupNameChangeHandler(this.handleGroupNameChange.bind(this)); + this.log = getLogger(`GroupNameRefreshController - ${instance.id}`); + } + + private async handleGroupNameChange(event: GroupNameChangeEvent) { + this.log.debug(event); + const pair = this.instance.forwardPairs.find(event.group); + if (!pair) return; + + if ((pair.flags | this.instance.flags) & flags.NAME_LOCKED) return; + await pair.tg.editTitle(event.newName); + + if(event.operator instanceof NapCatGroupMember) { + const operatorInfo = await event.operator.renew(); + let operatorName = operatorInfo.card || operatorInfo.nickname; + if (!((pair.flags | this.instance.flags) & flags.DISABLE_RICH_HEADER) && env.WEB_ENDPOINT) { + const richHeaderUrl = helper.generateRichHeaderUrl(pair.apiKey, operatorInfo.user_id, operatorName); + operatorName = `${operatorName}`; + } + + await pair.tg.sendMessage({ + message: `${operatorName} 修改群名为 ${event.newName}`, + parseMode: 'html', + silent: true, + }) + } + } +} diff --git a/main/src/controllers/TypingController.ts b/main/src/controllers/TypingController.ts index 5f50021..8389239 100644 --- a/main/src/controllers/TypingController.ts +++ b/main/src/controllers/TypingController.ts @@ -10,12 +10,14 @@ export default class TypingController { private readonly tgUser: Telegram, private readonly oicq: QQClient) { oicq.addInputStatusChangeHandler(this.handleInputStatusChange); + // bot 无法获取输入状态,个人账号无法获取自己在其他设备的输入状态,做不了 + // tgUser.addChannelUserTypingHandler(this.handleChannelUserTyping); } private handleInputStatusChange = async (event: InputStatusChangeEvent) => { const pair = this.instance.forwardPairs.find(event.chat); if (!pair) return; - if (pair.flags & flags.DISABLE_Q2TG) return; + if ((pair.flags | this.instance.flags) & flags.DISABLE_Q2TG) return; if (event.typing) { console.log(await pair.tg.setTyping()) diff --git a/main/src/models/Instance.ts b/main/src/models/Instance.ts index 9fd6784..9b63ac2 100644 --- a/main/src/models/Instance.ts +++ b/main/src/models/Instance.ts @@ -28,6 +28,7 @@ import posthog from './posthog'; import LoadingController from '../controllers/LoadingController'; import { sleep } from 'telegram/Helpers'; import TypingController from '../controllers/TypingController'; +import GroupNameRefreshController from '../controllers/GroupNameRefreshController'; export default class Instance { public static readonly instances: Instance[] = []; @@ -65,6 +66,7 @@ export default class Instance { private aliveCheckController: AliveCheckController; private loadingController: LoadingController; private typingController: TypingController; + private groupNameRefreshController: GroupNameRefreshController; private constructor(public readonly id: number) { this.log = getLogger(`Instance - ${this.id}`); @@ -172,6 +174,9 @@ export default class Instance { if (this.workMode === 'group') { this.hugController = new HugController(this, this.tgBot, this.oicq); } + else { + this.groupNameRefreshController = new GroupNameRefreshController(this, this.tgBot, this.tgUser, this.oicq); + } this.fileAndFlashPhotoController = new FileAndFlashPhotoController(this, this.tgBot, this.oicq); this.isInit = true; this.loadingController.off();