From 31bb1e5dee1b873319b296139affc085d0d31454 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Sat, 15 Nov 2025 10:45:02 +0800 Subject: [PATCH] Add emoji like event handling to core and onebot Introduces a typed event emitter for app events in napcat-core, specifically for emoji like events in groups. OlPushService now emits 'event:emoji_like' when a group reaction is detected. napcat-onebot listens for this event and emits corresponding OneBot events. Refactors and adds missing type definitions and improves method formatting for consistency. --- packages/napcat-core/index.ts | 2 + .../napcat-core/packet/handler/eventList.ts | 6 +++ .../napcat-core/packet/handler/typeEvent.ts | 22 +++++++++ .../napcat-core/protocol/OlpushSerivce.ts | 42 +++++++++++++--- packages/napcat-onebot/api/group.ts | 44 +++++++++++------ packages/napcat-onebot/index.ts | 49 +++++++++++-------- 6 files changed, 124 insertions(+), 41 deletions(-) create mode 100644 packages/napcat-core/packet/handler/eventList.ts create mode 100644 packages/napcat-core/packet/handler/typeEvent.ts diff --git a/packages/napcat-core/index.ts b/packages/napcat-core/index.ts index bf4c0c6c..e232864f 100644 --- a/packages/napcat-core/index.ts +++ b/packages/napcat-core/index.ts @@ -32,6 +32,7 @@ import { proxiedListenerOf } from 'napcat-common/src/proxy-handler'; import { NTQQPacketApi } from './apis/packet'; import { NativePacketHandler } from './packet/handler/client'; import { container, ReceiverServiceRegistry } from './packet/handler/serviceRegister'; +import { appEvent } from './packet/handler/eventList'; export * from './wrapper'; export * from './types/index'; export * from './services/index'; @@ -93,6 +94,7 @@ export function getMajorPath (QQVersion: string): string { export class NapCatCore { readonly context: InstanceContext; readonly eventWrapper: NTEventWrapper; + event = appEvent; NapCatDataPath: string = ''; NapCatTempPath: string = ''; apis: StableNTApiWrapper; diff --git a/packages/napcat-core/packet/handler/eventList.ts b/packages/napcat-core/packet/handler/eventList.ts new file mode 100644 index 00000000..265d7c66 --- /dev/null +++ b/packages/napcat-core/packet/handler/eventList.ts @@ -0,0 +1,6 @@ +import { TypedEventEmitter } from "./typeEvent"; + +export interface AppEvents { + 'event:emoji_like': { groupId: string; senderUin: string; emojiId: string, msgSeq: string, isAdd: boolean,count:number }; +} +export const appEvent = new TypedEventEmitter(); \ No newline at end of file diff --git a/packages/napcat-core/packet/handler/typeEvent.ts b/packages/napcat-core/packet/handler/typeEvent.ts new file mode 100644 index 00000000..be6efbad --- /dev/null +++ b/packages/napcat-core/packet/handler/typeEvent.ts @@ -0,0 +1,22 @@ +import { EventEmitter } from 'node:events'; + +export class TypedEventEmitter> { + private emitter = new EventEmitter(); + + on(event: K, listener: (payload: E[K]) => void) { + this.emitter.on(event as string, listener); + return () => this.off(event, listener); + } + + once(event: K, listener: (payload: E[K]) => void) { + this.emitter.once(event as string, listener); + } + + off(event: K, listener: (payload: E[K]) => void) { + this.emitter.off(event as string, listener); + } + + emit(event: K, payload: E[K]) { + this.emitter.emit(event as string, payload); + } +} diff --git a/packages/napcat-core/protocol/OlpushSerivce.ts b/packages/napcat-core/protocol/OlpushSerivce.ts index 1d38b509..2b6df938 100644 --- a/packages/napcat-core/protocol/OlpushSerivce.ts +++ b/packages/napcat-core/protocol/OlpushSerivce.ts @@ -1,8 +1,38 @@ +import { NapProtoMsg } from "napcat-protobuf"; +import { appEvent } from "../packet/handler/eventList"; import { ReceiveService, ServiceBase } from "../packet/handler/serviceRegister"; +import { GroupReactNotify, PushMsg } from "../packet/transformer/proto"; -// @ReceiveService('trpc.msg.olpush.OlPushService.MsgPush') -// export class OlPushService extends ServiceBase { -// async handler(seq: number, hex_data: string) { -// console.log(`OlPushService handler called with seq: ${seq} and data: ${hex_data}`); -// } -// } \ No newline at end of file +@ReceiveService('trpc.msg.olpush.OlPushService.MsgPush') +export class OlPushService extends ServiceBase { + async handler(_seq: number, hex_data: string) { + const data = new NapProtoMsg(PushMsg).decode(Buffer.from(hex_data, 'hex')); + if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) { + const pbNotify = data.message.body?.msgContent?.slice(7); + if (!pbNotify) { + return; + } + // 开始解析Notify + const notify = new NapProtoMsg(GroupReactNotify).decode(pbNotify); + if ((notify.field13 ?? 0) === 35) { + // Group React Notify + const groupCode = notify.groupUin?.toString() ?? ''; + const operatorUid = notify.groupReactionData?.data?.data?.groupReactionDataContent?.operatorUid ?? ''; + const type = notify.groupReactionData?.data?.data?.groupReactionDataContent?.type ?? 0; + const seq = notify.groupReactionData?.data?.data?.groupReactionTarget?.seq?.toString() ?? ''; + const code = notify.groupReactionData?.data?.data?.groupReactionDataContent?.code ?? ''; + const count = notify.groupReactionData?.data?.data?.groupReactionDataContent?.count ?? 0; + const senderUin = await this.core.apis.UserApi.getUinByUidV2(operatorUid); + appEvent.emit('event:emoji_like', { + groupId: groupCode, + senderUin: senderUin, + emojiId: code, + msgSeq: seq, + isAdd: type === 1, + count: count + }); + } + } + + } +} \ No newline at end of file diff --git a/packages/napcat-onebot/api/group.ts b/packages/napcat-onebot/api/group.ts index 8ad255b6..4a6f36c5 100644 --- a/packages/napcat-onebot/api/group.ts +++ b/packages/napcat-onebot/api/group.ts @@ -30,12 +30,12 @@ import { GroupReactNotify, PushMsg } from 'napcat-core/packet/transformer/proto' export class OneBotGroupApi { obContext: NapCatOneBot11Adapter; core: NapCatCore; - constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) { + constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) { this.obContext = obContext; this.core = core; } - async parseGroupBanEvent (GroupCode: string, grayTipElement: GrayTipElement) { + async parseGroupBanEvent(GroupCode: string, grayTipElement: GrayTipElement) { const groupElement = grayTipElement?.groupElement; if (!groupElement?.shutUp) return undefined; const memberUid = groupElement.shutUp.member.uid; @@ -66,7 +66,7 @@ export class OneBotGroupApi { return undefined; } - async parseGroupEmojiLikeEventByGrayTip ( + async parseGroupEmojiLikeEventByGrayTip( groupCode: string, grayTipElement: GrayTipElement ) { @@ -85,7 +85,7 @@ export class OneBotGroupApi { return await this.createGroupEmojiLikeEvent(groupCode, senderUin, msgSeq, emojiId, true, 1); } - async createGroupEmojiLikeEvent ( + async createGroupEmojiLikeEvent( groupCode: string, senderUin: string, msgSeq: string, @@ -121,7 +121,7 @@ export class OneBotGroupApi { ); } - async parseCardChangedEvent (msg: RawMessage) { + async parseCardChangedEvent(msg: RawMessage) { if (msg.senderUin && msg.senderUin !== '0') { const member = await this.core.apis.GroupApi.getGroupMember(msg.peerUid, msg.senderUin); if (member && member.cardName !== msg.sendMemberName) { @@ -137,7 +137,7 @@ export class OneBotGroupApi { return undefined; } - async registerParseGroupReactEvent () { + async registerParseGroupReactEvent() { this.obContext.core.context.packetHandler.onCmd('trpc.msg.olpush.OlPushService.MsgPush', async (packet) => { const data = new NapProtoMsg(PushMsg).decode(Buffer.from(packet.hex_data, 'hex')); if (data.message.contentHead.type === 732 && data.message.contentHead.subType === 16) { @@ -172,7 +172,23 @@ export class OneBotGroupApi { }); } - async parsePaiYiPai (msg: RawMessage, jsonStr: string) { + async registerParseGroupReactEventByCore() { + this.core.event.on('event:emoji_like', async (data) => { + console.log('Received emoji_like event from core:', data); + const event = await this.createGroupEmojiLikeEvent( + data.groupId, + data.senderUin, + data.msgSeq, + data.emojiId, + data.isAdd, + data.count + ); + if (event) { + this.obContext.networkManager.emitEvent(event); + } + }); + } + async parsePaiYiPai(msg: RawMessage, jsonStr: string) { const json = JSON.parse(jsonStr); // 判断业务类型 // Poke事件 @@ -191,7 +207,7 @@ export class OneBotGroupApi { return undefined; } - async parseOtherJsonEvent (msg: RawMessage, jsonStr: string, context: InstanceContext) { + async parseOtherJsonEvent(msg: RawMessage, jsonStr: string, context: InstanceContext) { const json = JSON.parse(jsonStr); const type = json.items[json.items.length - 1]?.txt; await this.core.apis.GroupApi.refreshGroupMemberCachePartial(msg.peerUid, msg.senderUid); @@ -213,7 +229,7 @@ export class OneBotGroupApi { return undefined; } - async parseEssenceMsg (msg: RawMessage, jsonStr: string) { + async parseEssenceMsg(msg: RawMessage, jsonStr: string) { const json = JSON.parse(jsonStr); const searchParams = new URL(json.items[0].jp).searchParams; const msgSeq = searchParams.get('msgSeq')!; @@ -241,7 +257,7 @@ export class OneBotGroupApi { // 获取MsgSeq+Peer可获取具体消息 } - async parseGroupUploadFileEvene (msg: RawMessage, element: FileElement, elementWrapper: MessageElement) { + async parseGroupUploadFileEvene(msg: RawMessage, element: FileElement, elementWrapper: MessageElement) { return new OB11GroupUploadNoticeEvent( this.core, parseInt(msg.peerUid), parseInt(msg.senderUin || ''), @@ -257,7 +273,7 @@ export class OneBotGroupApi { ); } - async parseGroupElement (msg: RawMessage, element: TipGroupElement, elementWrapper: GrayTipElement) { + async parseGroupElement(msg: RawMessage, element: TipGroupElement, elementWrapper: GrayTipElement) { if (element.type === TipGroupElementType.KGROUPNAMEMODIFIED) { this.core.context.logger.logDebug('收到群名称变更事件', element); return new OB11GroupNameEvent( @@ -285,7 +301,7 @@ export class OneBotGroupApi { return undefined; } - async parseSelfInviteEvent (msg: RawMessage, inviterUin: string, inviteeUin: string) { + async parseSelfInviteEvent(msg: RawMessage, inviterUin: string, inviteeUin: string) { return new OB11GroupIncreaseEvent( this.core, parseInt(msg.peerUid), @@ -295,7 +311,7 @@ export class OneBotGroupApi { ); } - async parse51TypeEvent (msg: RawMessage, grayTipElement: GrayTipElement) { + async parse51TypeEvent(msg: RawMessage, grayTipElement: GrayTipElement) { // 神经腾讯 没了妈妈想出来的 // Warn 下面存在高并发危险 if (grayTipElement.jsonGrayTipElement.jsonStr) { @@ -324,7 +340,7 @@ export class OneBotGroupApi { return undefined; } - async parseGrayTipElement (msg: RawMessage, grayTipElement: GrayTipElement) { + async parseGrayTipElement(msg: RawMessage, grayTipElement: GrayTipElement) { if (grayTipElement.subElementType === NTGrayTipElementSubTypeV2.GRAYTIP_ELEMENT_SUBTYPE_GROUP) { // 解析群组事件 由sysmsg解析 return await this.parseGroupElement(msg, grayTipElement.groupElement, grayTipElement); diff --git a/packages/napcat-onebot/index.ts b/packages/napcat-onebot/index.ts index 2523ce82..99641e10 100644 --- a/packages/napcat-onebot/index.ts +++ b/packages/napcat-onebot/index.ts @@ -55,18 +55,25 @@ import { OB11HttpSSEServerAdapter } from './network/http-server-sse'; import { OB11PluginMangerAdapter } from './network/plugin-manger'; import { existsSync } from 'node:fs'; +interface ApiListType { + GroupApi: OneBotGroupApi; + UserApi: OneBotUserApi; + FriendApi: OneBotFriendApi; + MsgApi: OneBotMsgApi; + QuickActionApi: OneBotQuickActionApi; +} // OneBot实现类 export class NapCatOneBot11Adapter { readonly core: NapCatCore; readonly context: InstanceContext; configLoader: OB11ConfigLoader; - public readonly apis; + public apis: ApiListType; networkManager: OB11NetworkManager; actions: ActionMap; private readonly bootTime = Date.now() / 1000; recallEventCache = new Map(); - constructor (core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { + constructor(core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { this.core = core; this.context = context; this.configLoader = new OB11ConfigLoader(core, pathWrapper.configPath, OneBotConfigSchema); @@ -81,7 +88,7 @@ export class NapCatOneBot11Adapter { this.networkManager = new OB11NetworkManager(); } - async creatOneBotLog (ob11Config: OneBotConfig) { + async creatOneBotLog(ob11Config: OneBotConfig) { let log = '[network] 配置加载\n'; for (const key of ob11Config.network.httpServers) { log += `HTTP服务: ${key.host}:${key.port}, : ${key.enable ? '已启动' : '未启动'}\n`; @@ -101,7 +108,7 @@ export class NapCatOneBot11Adapter { return log; } - async InitOneBot () { + async InitOneBot() { const selfInfo = this.core.selfInfo; const ob11Config = this.configLoader.configData; this.core.apis.UserApi.getUserDetailInfo(selfInfo.uid, false) @@ -218,12 +225,12 @@ export class NapCatOneBot11Adapter { // this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`); await this.reloadNetwork(prev, newConfig); }); - this.apis.GroupApi.registerParseGroupReactEvent().catch(e => + this.apis.GroupApi.registerParseGroupReactEventByCore().catch(e => this.context.logger.logError('注册群消息反应表情失败', e) ); } - private async reloadNetwork (prev: OneBotConfig, now: OneBotConfig): Promise { + private async reloadNetwork(prev: OneBotConfig, now: OneBotConfig): Promise { const prevLog = await this.creatOneBotLog(prev); const newLog = await this.creatOneBotLog(now); this.context.logger.log(`[Notice] [OneBot11] 配置变更前:\n${prevLog}`); @@ -236,7 +243,7 @@ export class NapCatOneBot11Adapter { await this.handleConfigChange(prev.network.websocketClients, now.network.websocketClients, OB11WebSocketClientAdapter); } - private async handleConfigChange ( + private async handleConfigChange( prevConfig: NetworkAdapterConfig[], nowConfig: NetworkAdapterConfig[], adapterClass: new ( @@ -269,7 +276,7 @@ export class NapCatOneBot11Adapter { } } - private initMsgListener () { + private initMsgListener() { const msgListener = new NodeIKernelMsgListener(); msgListener.onRecvSysMsg = (msg) => { this.apis.MsgApi.parseSysMessage(msg) @@ -382,7 +389,7 @@ export class NapCatOneBot11Adapter { this.context.session.getMsgService().addKernelMsgListener(proxiedListenerOf(msgListener, this.context.logger)); } - private initBuddyListener () { + private initBuddyListener() { const buddyListener = new NodeIKernelBuddyListener(); buddyListener.onBuddyReqChange = async (reqs) => { @@ -413,7 +420,7 @@ export class NapCatOneBot11Adapter { .addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger)); } - private initGroupListener () { + private initGroupListener() { const groupListener = new NodeIKernelGroupListener(); groupListener.onGroupNotifiesUpdated = async (_, notifies) => { @@ -506,7 +513,7 @@ export class NapCatOneBot11Adapter { .addKernelGroupListener(proxiedListenerOf(groupListener, this.context.logger)); } - private async emitMsg (message: RawMessage) { + private async emitMsg(message: RawMessage) { const network = await this.networkManager.getAllConfig(); this.context.logger.logDebug('收到新消息 RawMessage', message); await Promise.allSettled([ @@ -515,7 +522,7 @@ export class NapCatOneBot11Adapter { ]); } - private async handleMsg (message: RawMessage, network: Array) { + private async handleMsg(message: RawMessage, network: Array) { // 过滤无效消息 if (message.msgType === NTMsgType.KMSGTYPENULL) { return; @@ -535,7 +542,7 @@ export class NapCatOneBot11Adapter { } } - private isSelfMessage (ob11Msg: { + private isSelfMessage(ob11Msg: { stringMsg: OB11Message; arrayMsg: OB11Message; }): boolean { @@ -543,7 +550,7 @@ export class NapCatOneBot11Adapter { ob11Msg.arrayMsg.user_id.toString() === this.core.selfInfo.uin; } - private createMsgMap (network: Array, ob11Msg: { + private createMsgMap(network: Array, ob11Msg: { stringMsg: OB11Message; arrayMsg: OB11Message; }, isSelfMsg: boolean, message: RawMessage): Map { @@ -562,7 +569,7 @@ export class NapCatOneBot11Adapter { return msgMap; } - private handleDebugNetwork (network: Array, msgMap: Map, message: RawMessage) { + private handleDebugNetwork(network: Array, msgMap: Map, message: RawMessage) { const debugNetwork = network.filter(e => e.enable && e.debug); if (debugNetwork.length > 0) { debugNetwork.forEach(adapter => { @@ -576,7 +583,7 @@ export class NapCatOneBot11Adapter { } } - private handleNotReportSelfNetwork (network: Array, msgMap: Map, isSelfMsg: boolean) { + private handleNotReportSelfNetwork(network: Array, msgMap: Map, isSelfMsg: boolean) { if (isSelfMsg) { const notReportSelfNetwork = network.filter(e => e.enable && (('reportSelfMessage' in e && !e.reportSelfMessage) || !('reportSelfMessage' in e))); notReportSelfNetwork.forEach(adapter => { @@ -585,7 +592,7 @@ export class NapCatOneBot11Adapter { } } - private async handleGroupEvent (message: RawMessage) { + private async handleGroupEvent(message: RawMessage) { try { // 群名片修改事件解析 任何都该判断 if (message.senderUin && message.senderUin !== '0') { @@ -618,7 +625,7 @@ export class NapCatOneBot11Adapter { } } - private async handlePrivateMsgEvent (message: RawMessage) { + private async handlePrivateMsgEvent(message: RawMessage) { try { if (message.msgType === NTMsgType.KMSGTYPEGRAYTIPS) { // 灰条为单元素消息 @@ -635,7 +642,7 @@ export class NapCatOneBot11Adapter { } } - private async emitRecallMsg (message: RawMessage, element: MessageElement) { + private async emitRecallMsg(message: RawMessage, element: MessageElement) { const peer: Peer = { chatType: message.chatType, peerUid: message.peerUid, guildId: '' }; const oriMessageId = MessageUnique.getShortIdByMsgId(message.msgId) ?? MessageUnique.createUniqueMsgId(peer, message.msgId); if (message.chatType === ChatType.KCHATTYPEC2C) { @@ -646,7 +653,7 @@ export class NapCatOneBot11Adapter { return undefined; } - private async emitFriendRecallMsg (message: RawMessage, oriMessageId: number, element: MessageElement) { + private async emitFriendRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) { const operatorUid = element.grayTipElement?.revokeElement.operatorUid; if (!operatorUid) return undefined; return new OB11FriendRecallNoticeEvent( @@ -656,7 +663,7 @@ export class NapCatOneBot11Adapter { ); } - private async emitGroupRecallMsg (message: RawMessage, oriMessageId: number, element: MessageElement) { + private async emitGroupRecallMsg(message: RawMessage, oriMessageId: number, element: MessageElement) { const operatorUid = element.grayTipElement?.revokeElement.operatorUid; if (!operatorUid) return undefined; const operatorId = await this.core.apis.UserApi.getUinByUidV2(operatorUid);