Skip to content

Commit

Permalink
feat: qq -> tg 个人模式群组名称变更同步
Browse files Browse the repository at this point in the history
  • Loading branch information
clansty committed Dec 28, 2024
1 parent 845355a commit d6eb2fb
Show file tree
Hide file tree
Showing 10 changed files with 121 additions and 9 deletions.
8 changes: 4 additions & 4 deletions main/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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])
Expand All @@ -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)
Expand Down
10 changes: 9 additions & 1 deletion main/src/client/NapCatClient/client.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -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')
Expand Down Expand Up @@ -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);
Expand Down
12 changes: 10 additions & 2 deletions main/src/client/QQClient/events.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -122,3 +121,12 @@ export class InputStatusChangeEvent {
) {
}
}

export class GroupNameChangeEvent {
constructor(
public readonly group: Group,
public readonly operator: QQUser,
public readonly newName: string,
) {
}
}
14 changes: 13 additions & 1 deletion main/src/client/QQClient/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Friend, Group, SendableElem } from './entity';
import {
FriendIncreaseEvent,
GroupMemberDecreaseEvent,
GroupMemberIncreaseEvent, InputStatusChangeEvent,
GroupMemberIncreaseEvent, GroupNameChangeEvent, InputStatusChangeEvent,
MessageEvent,
MessageRecallEvent, PokeEvent,
} from './events';
Expand Down Expand Up @@ -184,6 +184,18 @@ export abstract class QQClient {
this.onInputStatusChangeHandlers.splice(this.onInputStatusChangeHandlers.indexOf(handler), 1);
}

//
protected readonly onGroupNameChangeHandlers: Array<(e: GroupNameChangeEvent) => Promise<void | boolean>> = [];

public addGroupNameChangeHandler(handler: (e: GroupNameChangeEvent) => Promise<void | boolean>) {
this.onGroupNameChangeHandlers.push(handler);
}

public removeGroupNameChangeHandler(handler: (e: GroupNameChangeEvent) => Promise<void | boolean>) {
this.onGroupNameChangeHandlers.includes(handler) &&
this.onGroupNameChangeHandlers.splice(this.onGroupNameChangeHandlers.indexOf(handler), 1);
}

// End Handlers

public getChat(roomId: number, tempChatFromGroupId?: number): Promise<Group | Friend> {
Expand Down
21 changes: 21 additions & 0 deletions main/src/client/Telegram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import env from '../models/env';

type MessageHandler = (message: Api.Message) => Promise<boolean | void>;
type ServiceMessageHandler = (message: Api.MessageService) => Promise<boolean | void>;
type ChannelUserTypingHandler = (event: Api.UpdateChannelUserTyping) => Promise<boolean | void>;

export default class Telegram {
private readonly client: TelegramClient;
Expand All @@ -27,6 +28,7 @@ export default class Telegram {
private readonly onMessageHandlers: Array<MessageHandler> = [];
private readonly onEditedMessageHandlers: Array<MessageHandler> = [];
private readonly onServiceMessageHandlers: Array<ServiceMessageHandler> = [];
private readonly onChannelUserTypingHandlers: Array<ChannelUserTypingHandler> = [];
public me: Api.User;

private static existedBots = {} as { [id: number]: Telegram };
Expand Down Expand Up @@ -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;
}
Expand All @@ -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 可以阻断下面的处理器
Expand Down Expand Up @@ -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({}));
}
Expand Down
11 changes: 11 additions & 0 deletions main/src/client/TelegramChat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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('不是群组,无法邀请');
Expand Down
1 change: 1 addition & 0 deletions main/src/constants/flags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
44 changes: 44 additions & 0 deletions main/src/controllers/GroupNameRefreshController.ts
Original file line number Diff line number Diff line change
@@ -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 = `<a href="${richHeaderUrl}">${operatorName}</a>`;
}

await pair.tg.sendMessage({
message: `<i>${operatorName} 修改群名为 <b>${event.newName}</b></i>`,
parseMode: 'html',
silent: true,
})
}
}
}
4 changes: 3 additions & 1 deletion main/src/controllers/TypingController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
5 changes: 5 additions & 0 deletions main/src/models/Instance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[] = [];
Expand Down Expand Up @@ -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}`);
Expand Down Expand Up @@ -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();
Expand Down

0 comments on commit d6eb2fb

Please sign in to comment.