diff --git a/launcher/qqnt.json b/launcher/qqnt.json index 3b18a589..974f54b7 100644 --- a/launcher/qqnt.json +++ b/launcher/qqnt.json @@ -1,6 +1,6 @@ { "name": "qq-chat", - "version": "9.9.15-28327", + "version": "9.9.15-28418", "verHash": "512caf78", "linuxVersion": "3.2.12-28327", "linuxVerHash": "f60e8252", @@ -18,7 +18,7 @@ "qd": "externals/devtools/cli/index.js" }, "main": "./loadNapCat.js", - "buildVersion": "28327", + "buildVersion": "28418", "isPureShell": true, "isByteCodeShell": true, "platform": "win32", diff --git a/manifest.json b/manifest.json index fd23717d..f1ea915a 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "2.6.15", + "version": "2.6.16", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index de7a973f..292b74d9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "2.6.15", + "version": "2.6.16", "scripts": { "build:framework": "vite build --mode framework", "build:shell": "vite build --mode shell", diff --git a/src/common/version.ts b/src/common/version.ts index 7c1ae9cc..c59606a4 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '2.6.15'; +export const napCatVersion = '2.6.16'; diff --git a/src/core/external/appid.json b/src/core/external/appid.json index c1bde0c6..e1295504 100644 --- a/src/core/external/appid.json +++ b/src/core/external/appid.json @@ -22,5 +22,13 @@ "3.2.12-28327":{ "appid": 537249393, "qua": "V1_LNX_NQ_3.2.12_28327_GW_B" + }, + "9.9.15-28418":{ + "appid": 537249321, + "qua": "V1_WIN_NQ_9.9.15_28418_GW_B" + }, + "3.2.12-28418":{ + "appid": 537249393, + "qua": "V1_LNX_NQ_3.2.12_28418_GW_B" } } \ No newline at end of file diff --git a/src/core/index.ts b/src/core/index.ts index 20dcad9e..136ce32f 100644 --- a/src/core/index.ts +++ b/src/core/index.ts @@ -20,16 +20,15 @@ import { LogLevel, LogWrapper } from '@/common/log'; import { NodeIKernelLoginService } from '@/core/services'; import { QQBasicInfoWrapper } from '@/common/qq-basic-info'; import { NapCatPathWrapper } from '@/common/path'; -import path from 'node:path'; +import path, { resolve } from 'node:path'; import fs from 'node:fs'; import { hostname, systemName, systemVersion } from '@/common/system'; import { NTEventWrapper } from '@/common/event'; -import { DataSource, GroupMember, KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/core/entities'; +import { ChatType, DataSource, GroupMember, KickedOffLineInfo, Peer, SelfInfo, SelfStatusInfo } from '@/core/entities'; import { NapCatConfigLoader } from '@/core/helper/config'; import os from 'node:os'; import { NodeIKernelGroupListener, NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/core/listeners'; import { proxiedListenerOf } from '@/common/proxy-handler'; - export * from './wrapper'; export * from './entities'; export * from './services'; @@ -99,6 +98,7 @@ export class NapCatCore { if (!fs.existsSync(this.NapCatTempPath)) { fs.mkdirSync(this.NapCatTempPath, { recursive: true }); } + this.initNapCatCoreListeners().then().catch(this.context.logger.logError.bind(this.context.logger)); this.context.logger.setFileLogEnabled( @@ -248,7 +248,7 @@ export class NapCatCore { } export async function genSessionConfig( - guid:string, + guid: string, QQVersionAppid: string, QQVersion: string, selfUin: string, @@ -260,15 +260,15 @@ export async function genSessionConfig( //os.platform() let systemPlatform = PlatformType.KWINDOWS; switch (os.platform()) { - case 'win32': - systemPlatform = PlatformType.KWINDOWS; - break; - case 'darwin': - systemPlatform = PlatformType.KMAC; - break; - case 'linux': - systemPlatform = PlatformType.KLINUX; - break; + case 'win32': + systemPlatform = PlatformType.KWINDOWS; + break; + case 'darwin': + systemPlatform = PlatformType.KMAC; + break; + case 'linux': + systemPlatform = PlatformType.KLINUX; + break; } return { selfUin, diff --git a/src/core/proto/Message.ts b/src/core/proto/Message.ts new file mode 100644 index 00000000..c7d1cbb5 --- /dev/null +++ b/src/core/proto/Message.ts @@ -0,0 +1,37 @@ +import * as pb from 'protobufjs'; + + +export const BodyInner = new pb.Type("BodyInner") + .add(new pb.Field("msgType", 1, "uint32", "optional")) + .add(new pb.Field("subType", 2, "uint32", "optional")) + +export const NoifyData = new pb.Type("NoifyData") + .add(new pb.Field("skip", 1, "bytes", "optional")) + .add(new pb.Field("innerData", 2, "bytes", "optional")) + +export const MsgHead = new pb.Type("MsgHead") + .add(BodyInner) + .add(NoifyData) + .add(new pb.Field("bodyInner", 2, "BodyInner", "optional")) + .add(new pb.Field("noifyData", 3, "NoifyData", "optional")); + +export const Message = new pb.Type("Message") + .add(MsgHead) + .add(new pb.Field("msgHead", 1, "MsgHead")) + +export const SubDetail = new pb.Type("SubDetail") + .add(new pb.Field("msgSeq", 1, "uint32")) + .add(new pb.Field("msgTime", 2, "uint32")) + .add(new pb.Field("senderUid", 6, "string")) + +export const RecallDetails = new pb.Type("RecallDetails") + .add(SubDetail) + .add(new pb.Field("operatorUid", 1, "string")) + .add(new pb.Field("subDetail", 3, "SubDetail")) + +export const RecallGroup = new pb.Type("RecallGroup") + .add(RecallDetails) + .add(new pb.Field("type", 1, "int32")) + .add(new pb.Field("peerUid", 4, "uint32")) + .add(new pb.Field("recallDetails", 11, "RecallDetails")) + .add(new pb.Field("grayTipsSeq", 37, "uint32")) diff --git a/src/native/external/MoeHoo.win32.node b/src/native/external/MoeHoo.win32.node new file mode 100644 index 00000000..89703140 Binary files /dev/null and b/src/native/external/MoeHoo.win32.node differ diff --git a/src/native/index.ts b/src/native/index.ts new file mode 100644 index 00000000..873b0eae --- /dev/null +++ b/src/native/index.ts @@ -0,0 +1,23 @@ +import { constants } from "node:os"; +import path from "path"; +import { dlopen } from "process"; +export class Native { + platform: string; + supportedPlatforms = ['win32']; + MoeHooExport: any = { exports: {} }; + recallHookEnabled: boolean = false; + constructor(nodePath: string, platform: string = process.platform) { + this.platform = platform; + if (!this.supportedPlatforms.includes(this.platform)) { + throw new Error(`Platform ${this.platform} is not supported`); + } + dlopen(this.MoeHooExport, path.join(nodePath, './native/MoeHoo.win32.node'), constants.dlopen.RTLD_LAZY); + } + isSetReCallEnabled(): boolean { + return this.recallHookEnabled; + } + registerRecallCallback(callback: (hex: string) => any): void { + this.recallHookEnabled = true; + return this.MoeHooExport.exports.registMsgPush(callback); + } +} \ No newline at end of file diff --git a/src/onebot/action/msg/GetMsg.ts b/src/onebot/action/msg/GetMsg.ts index 40f0251e..9b183db8 100644 --- a/src/onebot/action/msg/GetMsg.ts +++ b/src/onebot/action/msg/GetMsg.ts @@ -3,6 +3,7 @@ import BaseAction from '../BaseAction'; import { ActionName } from '../types'; import { FromSchema, JSONSchema } from 'json-schema-to-ts'; import { MessageUnique } from '@/common/message-unique'; +import { RawMessage } from '@/core'; export type ReturnDataType = OB11Message @@ -32,13 +33,17 @@ class GetMsg extends BaseAction { throw new Error('消息不存在'); } const peer = { guildId: '', peerUid: msgIdWithPeer?.Peer.peerUid, chatType: msgIdWithPeer.Peer.chatType }; - const msg = await this.core.apis.MsgApi.getMsgsByMsgId( - peer, - [msgIdWithPeer?.MsgId || payload.message_id.toString()]); - const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg.msgList[0]); + let orimsg = this.obContext.recallMsgCache.get(msgIdWithPeer.MsgId); + let msg: RawMessage; + if (orimsg) { + msg = orimsg; + } else { + msg = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgIdWithPeer?.MsgId || payload.message_id.toString()])).msgList[0]; + } + const retMsg = await this.obContext.apis.MsgApi.parseMessage(msg); if (!retMsg) throw Error('消息为空'); try { - retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgList[0].msgId)!; + retMsg.message_id = MessageUnique.createUniqueMsgId(peer, msg.msgId)!; retMsg.message_seq = retMsg.message_id; retMsg.real_id = retMsg.message_id; } catch (e) { diff --git a/src/onebot/index.ts b/src/onebot/index.ts index f1a6c174..b8cb81e9 100644 --- a/src/onebot/index.ts +++ b/src/onebot/index.ts @@ -10,6 +10,7 @@ import { NodeIKernelBuddyListener, NodeIKernelGroupListener, NodeIKernelMsgListener, + Peer, RawMessage, SendStatusType, } from '@/core'; @@ -43,8 +44,9 @@ import { OB11FriendRecallNoticeEvent } from '@/onebot/event/notice/OB11FriendRec import { OB11GroupRecallNoticeEvent } from '@/onebot/event/notice/OB11GroupRecallNoticeEvent'; import { LRUCache } from '@/common/lru-cache'; import { NodeIKernelRecentContactListener } from '@/core/listeners/NodeIKernelRecentContactListener'; -import { OB11ProfileLikeEvent } from './event/notice/OB11ProfileLikeEvent'; -import { profileLikeTip, ProfileLikeTipType, SysMessage, SysMessageType } from '@/core/proto/ProfileLike'; +import { Native } from '@/native'; +import { Message, RecallGroup } from '@/core/proto/Message'; + //OneBot实现类 export class NapCatOneBot11Adapter { readonly core: NapCatCore; @@ -54,8 +56,9 @@ export class NapCatOneBot11Adapter { apis: StableOneBotApiWrapper; networkManager: OB11NetworkManager; actions: ActionMap; - + nativeCore: Native | undefined; private bootTime = Date.now() / 1000; + recallMsgCache = new LRUCache(100); constructor(core: NapCatCore, context: InstanceContext, pathWrapper: NapCatPathWrapper) { this.core = core; @@ -70,10 +73,54 @@ export class NapCatOneBot11Adapter { }; this.actions = createActionMap(this, core); this.networkManager = new OB11NetworkManager(); + this.registerNative(core, context).then().catch(); this.InitOneBot() .catch(e => this.context.logger.logError.bind(this.context.logger)('初始化OneBot失败', e)); - } + } + async registerNative(core: NapCatCore, context: InstanceContext) { + this.nativeCore = new Native(context.pathWrapper.binaryPath); + this.nativeCore.registerRecallCallback(async (hex: string) => { + try { + let data = Message.decode(Buffer.from(hex, 'hex')) as any; + //data.MsgHead.BodyInner.MsgType SubType + let bodyInner = data.msgHead?.bodyInner; + //context.logger.log("[appNative] Parse MsgType:" + bodyInner.msgType + " / SubType:" + bodyInner.subType); + if (bodyInner && bodyInner.msgType == 732 && bodyInner.subType == 17) { + let RecallData = Buffer.from(data.msgHead.noifyData.innerData); + //跳过 4字节 群号 + 不知道的1字节 +2字节 长度 + let uid = RecallData.readUint32BE(); + const buffer = Buffer.from(RecallData.toString('hex').slice(14), 'hex'); + let seq: number = (RecallGroup.decode(buffer) as any).recallDetails.subDetail.msgSeq; + let peer: Peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: uid.toString() }; + context.logger.log("[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq); + let msgs = await core.apis.MsgApi.queryMsgsWithFilterExWithSeq(peer, seq.toString()); + this.recallMsgCache.put(msgs.msgList[0].msgId, msgs.msgList[0]); + // let ob11 = await this.apis.MsgApi.parseMessage(msgs.msgList[0], 'array') + // .catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e)); + // if (ob11) { + // const { sendElements, deleteAfterSentFiles } = await this.apis.MsgApi.createSendElements(ob11.message as OB11MessageData[], peer); + // this.apis.MsgApi.sendMsgWithOb11UniqueId(peer, sendElements, deleteAfterSentFiles); + // } + + + // this.apis.MsgApi.sendMsg(peer, [{ + // elementType: 1, + // elementId: '', + // textElement: { + // content: "[Native] 群消息撤回 Peer: " + uid.toString() + " / MsgSeq:" + seq, + // atType: 0, + // atUid: '', + // atTinyId: '', + // atNtUid: '', + // }, + // }]); + } + } catch (error: any) { + context.logger.logWarn("[Native] Error:", (error as Error).message, ' HEX:', hex); + } + }); + } async InitOneBot() { const selfInfo = this.core.selfInfo; const ob11Config = this.configLoader.configData; @@ -523,7 +570,6 @@ export class NapCatOneBot11Adapter { } }).catch(e => this.context.logger.logError.bind(this.context.logger)('constructPrivateEvent error: ', e)); } - private async emitRecallMsg(msgList: RawMessage[], cache: LRUCache) { for (const message of msgList) { // log("message update", message.sendStatus, message.msgId, message.msgSeq) @@ -555,7 +601,7 @@ export class NapCatOneBot11Adapter { parseInt(message.peerUin), parseInt(message.senderUin), parseInt(operatorId), - oriMessageId, + oriMessageId ); this.networkManager.emitEvent(groupRecallEvent) .catch(e => this.context.logger.logError.bind(this.context.logger)('处理群消息撤回失败', e)); diff --git a/src/shell/napcat.ts b/src/shell/napcat.ts index d48c725c..68b2e053 100644 --- a/src/shell/napcat.ts +++ b/src/shell/napcat.ts @@ -54,7 +54,6 @@ export async function NCoreInitShell() { const loginService = wrapper.NodeIKernelLoginService.get(); const session = wrapper.NodeIQQNTWrapperSession.create(); - // from get dataPath const [dataPath, dataPathGlobal] = (() => { if (os.platform() === 'darwin') { diff --git a/src/webui/ui/NapCat.ts b/src/webui/ui/NapCat.ts index 15e20145..2353e3d1 100644 --- a/src/webui/ui/NapCat.ts +++ b/src/webui/ui/NapCat.ts @@ -30,7 +30,7 @@ async function onSettingWindowCreated(view: Element) { SettingItem( 'Napcat', undefined, - SettingButton('V2.6.15', 'napcat-update-button', 'secondary'), + SettingButton('V2.6.16', 'napcat-update-button', 'secondary'), ), ]), SettingList([ diff --git a/static/assets/renderer.js b/static/assets/renderer.js index 8fe3c37c..779a0e05 100644 --- a/static/assets/renderer.js +++ b/static/assets/renderer.js @@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) { SettingItem( 'Napcat', void 0, - SettingButton("V2.6.15", "napcat-update-button", "secondary") + SettingButton("V2.6.16", "napcat-update-button", "secondary") ) ]), SettingList([ diff --git a/vite.config.ts b/vite.config.ts index f5a4a17e..d52133c2 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -41,6 +41,7 @@ const FrameworkBaseConfigPlugin: PluginOption[] = [ const ShellBaseConfigPlugin: PluginOption[] = [ cp({ targets: [ + { src: './src/native/external', dest: 'dist/native', flatten: false }, { src: './static/', dest: 'dist/static/', flatten: false }, { src: './src/core/external/napcat.json', dest: 'dist/config/' }, { src: './src/onebot/config/onebot11.json', dest: 'dist/config/' },