mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
10 Commits
v3.4.6
...
347ba5f354
Author | SHA1 | Date | |
---|---|---|---|
![]() |
347ba5f354 | ||
![]() |
81dbb9d980 | ||
![]() |
c4e1a3ab04 | ||
![]() |
90ec774a21 | ||
![]() |
db7a27e624 | ||
![]() |
f7d965eda2 | ||
![]() |
74ca2e2e16 | ||
![]() |
8ab550f2f5 | ||
![]() |
018aca4db2 | ||
![]() |
d4327166c1 |
@@ -4,7 +4,7 @@
|
||||
"name": "NapCatQQ",
|
||||
"slug": "NapCat.Framework",
|
||||
"description": "高性能的 OneBot 11 协议实现",
|
||||
"version": "3.4.5",
|
||||
"version": "3.4.7",
|
||||
"icon": "./logo.png",
|
||||
"authors": [
|
||||
{
|
||||
|
@@ -2,7 +2,7 @@
|
||||
"name": "napcat",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"version": "3.4.5",
|
||||
"version": "3.4.7",
|
||||
"scripts": {
|
||||
"build:framework": "vite build --mode framework",
|
||||
"build:shell": "vite build --mode shell",
|
||||
|
@@ -84,7 +84,7 @@ export class QQBasicInfoWrapper {
|
||||
}
|
||||
// 通过Major拉取 性能差
|
||||
try {
|
||||
let majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||
const majorAppid = this.getAppidV2ByMajor(fullVersion);
|
||||
if (majorAppid) {
|
||||
this.context.logger.log(`[QQ版本兼容性检测] 当前版本Appid未内置 通过Major获取 为了更好的性能请尝试更新NapCat`);
|
||||
return { appid: majorAppid, qua: this.getQUAFallback() };
|
||||
@@ -98,8 +98,8 @@ export class QQBasicInfoWrapper {
|
||||
return { appid: this.getAppIdFallback(), qua: this.getQUAFallback() };
|
||||
}
|
||||
getAppidV2ByMajor(QQVersion: string) {
|
||||
let majorPath = getMajorPath(QQVersion);
|
||||
let appid = parseAppidFromMajor(majorPath);
|
||||
const majorPath = getMajorPath(QQVersion);
|
||||
const appid = parseAppidFromMajor(majorPath);
|
||||
return appid;
|
||||
}
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
export const napCatVersion = '3.4.5';
|
||||
export const napCatVersion = '3.4.7';
|
||||
|
@@ -54,7 +54,7 @@ export class NTQQGroupApi {
|
||||
}, pskey);
|
||||
}
|
||||
async getGroupShutUpMemberList(groupCode: string) {
|
||||
let data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
|
||||
const data = this.core.eventWrapper.registerListen('NodeIKernelGroupListener/onShutUpMemberListChanged', 1, 1000, (group_id) => group_id === groupCode);
|
||||
this.context.session.getGroupService().getGroupShutUpMemberList(groupCode);
|
||||
return (await data)[1];
|
||||
}
|
||||
|
@@ -5,7 +5,7 @@ import offset from '@/core/external/offset.json';
|
||||
import { PacketClient, RecvPacketData } from '@/core/packet/client';
|
||||
import { PacketSession } from "@/core/packet/session";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import { NapProtoEncodeStructType, NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||
import { NapProtoEncodeStructType, NapProtoDecodeStructType, NapProtoMsg } from '@/core/packet/proto/NapProto';
|
||||
import { OidbSvcTrpcTcp0X9067_202_Rsp_Body } from '@/core/packet/proto/oidb/Oidb.0x9067_202';
|
||||
import { OidbSvcTrpcTcpBase, OidbSvcTrpcTcpBaseRsp } from '@/core/packet/proto/oidb/OidbBase';
|
||||
import { OidbSvcTrpcTcp0XFE1_2RSP } from '@/core/packet/proto/oidb/Oidb.0XFE1_2';
|
||||
@@ -223,7 +223,7 @@ export class NTQQPacketApi {
|
||||
});
|
||||
}
|
||||
|
||||
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoEncodeStructType<typeof MsgInfo>> {
|
||||
async sendAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType): Promise<NapProtoDecodeStructType<typeof MsgInfo>> {
|
||||
let reqTime = 0;
|
||||
const reqMaxTime = 30;
|
||||
const sessionId = crypto.randomBytes(4).readUInt32BE(0);
|
||||
|
@@ -241,7 +241,7 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
|
||||
}
|
||||
|
||||
toPreview(): string {
|
||||
return `[${this.emojiName}]`;
|
||||
return `${this.emojiName}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
126
src/core/packet/native.ts
Normal file
126
src/core/packet/native.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { LogWrapper } from "@/common/log";
|
||||
import { LRUCache } from "@/common/lru-cache";
|
||||
import crypto, { createHash } from "crypto";
|
||||
import { NapCatCore } from "@/core";
|
||||
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
|
||||
import path, { dirname } from "path";
|
||||
import { fileURLToPath } from "url";
|
||||
import fs from "fs";
|
||||
import { constants } from "os";
|
||||
import { console } from "inspector";
|
||||
|
||||
export interface RecvPacketData {
|
||||
seq: number;
|
||||
cmd: string;
|
||||
hex_data: string;
|
||||
}
|
||||
|
||||
export class NativePacketClient {
|
||||
private isInit: boolean = false;
|
||||
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
|
||||
readonly napCatCore: NapCatCore;
|
||||
private readonly logger: LogWrapper;
|
||||
private supportedPlatforms = ['win32.x64'];
|
||||
private MoeHooExport: any = { exports: {} };
|
||||
|
||||
constructor(core: NapCatCore) {
|
||||
this.napCatCore = core;
|
||||
this.logger = core.context.logger;
|
||||
}
|
||||
|
||||
get available(): boolean {
|
||||
return this.isInit;
|
||||
}
|
||||
|
||||
private randText(len: number): string {
|
||||
let text = '';
|
||||
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
for (let i = 0; i < len; i++) {
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private async registerCallback(trace_id: string, type: string, callback: (json: RecvPacketData) => Promise<void>): Promise<void> {
|
||||
this.cb.put(createHash('md5').update(trace_id).digest('hex') + type, callback);
|
||||
}
|
||||
|
||||
async init(pid: number, recv: string, send: string): Promise<void> {
|
||||
const platform = process.platform + '.' + process.arch;
|
||||
if (!this.supportedPlatforms.includes(platform)) {
|
||||
throw new Error(`Unsupported platform: ${platform}`);
|
||||
}
|
||||
|
||||
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/moehoo.' + platform + '.node');
|
||||
if (!fs.existsSync(moehoo_path)) {
|
||||
throw new Error(`moehoo.${platform}.node not found`);
|
||||
}
|
||||
|
||||
process.dlopen(this.MoeHooExport, moehoo_path, constants.dlopen.RTLD_LAZY);
|
||||
this.MoeHooExport.exports.InitHook(pid, recv, send, (type: number, uin: string, seq: number, cmd: string, hex_data: string) => {
|
||||
const callback = this.cb.get(createHash('md5').update(Buffer.from(hex_data, 'hex')).digest('hex') + (type === 0 ? 'send' : 'recv'));
|
||||
if (callback) {
|
||||
callback({ seq, cmd, hex_data });
|
||||
} else {
|
||||
this.logger.logError(`Callback not found for hex_data: ${hex_data}`);
|
||||
}
|
||||
console.log('type:', type, 'uin:', uin, 'seq:', seq, 'cmd:', cmd, 'hex_data:', hex_data);
|
||||
});
|
||||
}
|
||||
|
||||
private async sendCommand(
|
||||
cmd: string,
|
||||
data: string,
|
||||
trace_id: string,
|
||||
rsp: boolean = false,
|
||||
timeout: number = 20000,
|
||||
sendcb: (json: RecvPacketData) => void = () => { }
|
||||
): Promise<RecvPacketData> {
|
||||
return new Promise<RecvPacketData>((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
throw new Error("MoeHoo is not Init");
|
||||
}
|
||||
|
||||
this.MoeHooExport.exports.SendPacket(cmd, data, crypto.createHash('md5').update(trace_id).digest('hex'));
|
||||
|
||||
if (rsp) {
|
||||
this.registerCallback(trace_id, 'recv', async (json: RecvPacketData) => {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
});
|
||||
}
|
||||
|
||||
this.registerCallback(trace_id, 'send', async (json: RecvPacketData) => {
|
||||
sendcb(json);
|
||||
if (!rsp) {
|
||||
clearTimeout(timeoutHandle);
|
||||
resolve(json);
|
||||
}
|
||||
});
|
||||
|
||||
const timeoutHandle = setTimeout(() => {
|
||||
reject(new Error(`sendCommand timed out after ${timeout} ms for ${cmd} with trace_id ${trace_id}`));
|
||||
}, timeout);
|
||||
});
|
||||
}
|
||||
|
||||
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.available) {
|
||||
this.logger.logError('NapCat.Packet 未初始化!');
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const md5 = crypto.createHash('md5').update(data).digest('hex');
|
||||
const trace_id = (this.randText(4) + md5 + data).slice(0, data.length / 2);
|
||||
|
||||
this.sendCommand(cmd, data, trace_id, rsp, 20000, async () => {
|
||||
await this.napCatCore.context.session.getMsgService().sendSsoCmdReqByContend(cmd, trace_id);
|
||||
}).then((res) => resolve(res)).catch((e: Error) => reject(e));
|
||||
});
|
||||
}
|
||||
|
||||
async sendOidbPacket(pkt: OidbPacket, rsp = false): Promise<RecvPacketData> {
|
||||
return this.sendPacket(pkt.cmd, pkt.data, rsp);
|
||||
}
|
||||
}
|
@@ -28,8 +28,8 @@ import { OidbSvcTrpcTcp0XE37_800 } from "@/core/packet/proto/oidb/Oidb.0XE37_800
|
||||
import { OidbSvcTrpcTcp0XEB7 } from "./proto/oidb/Oidb.0xEB7";
|
||||
import { MiniAppReqParams } from "@/core/packet/entities/miniApp";
|
||||
import { MiniAppAdaptShareInfoReq } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
|
||||
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
|
||||
import {OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0} from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { OidbSvcTrpcTcp0X929B_0, OidbSvcTrpcTcp0X929D_0 } from "@/core/packet/proto/oidb/Oidb.0x929";
|
||||
|
||||
export type PacketHexStr = string & { readonly hexNya: unique symbol };
|
||||
|
||||
|
@@ -91,15 +91,12 @@ export type NapProtoEncodeStructType<T> = NapProtoStructType<T, true>;
|
||||
|
||||
export type NapProtoDecodeStructType<T> = NapProtoStructType<T, false>;
|
||||
|
||||
const NapProtoMsgCache = new Map<ProtoMessageType, MessageType<NapProtoStructType<ProtoMessageType, boolean>>>();
|
||||
|
||||
export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
private readonly _msg: T;
|
||||
class NapProtoRealMsg<T extends ProtoMessageType> {
|
||||
private readonly _field: PartialFieldInfo[];
|
||||
private readonly _proto_msg: MessageType<NapProtoStructType<T, boolean>>;
|
||||
private static cache = new WeakMap<ProtoMessageType, NapProtoRealMsg<any>>();
|
||||
|
||||
constructor(fields: T) {
|
||||
this._msg = fields;
|
||||
private constructor(fields: T) {
|
||||
this._field = Object.keys(fields).map(key => {
|
||||
const field = fields[key];
|
||||
if (field.kind === 'scalar') {
|
||||
@@ -122,13 +119,22 @@ export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
name: key,
|
||||
kind: 'message',
|
||||
repeat: field.repeat ? RepeatType.PACKED : RepeatType.NO,
|
||||
T: () => new NapProtoMsg(field.type())._proto_msg,
|
||||
T: () => NapProtoRealMsg.getInstance(field.type())._proto_msg,
|
||||
};
|
||||
}
|
||||
}) as PartialFieldInfo[];
|
||||
this._proto_msg = new MessageType<NapProtoStructType<T, boolean>>('nya', this._field);
|
||||
}
|
||||
|
||||
static getInstance<T extends ProtoMessageType>(fields: T): NapProtoRealMsg<T> {
|
||||
let instance = this.cache.get(fields);
|
||||
if (!instance) {
|
||||
instance = new NapProtoRealMsg(fields);
|
||||
this.cache.set(fields, instance);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this._proto_msg.toBinary(this._proto_msg.create(data as PartialMessage<NapProtoEncodeStructType<T>>));
|
||||
}
|
||||
@@ -137,3 +143,19 @@ export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
return this._proto_msg.fromBinary(data) as NapProtoDecodeStructType<T>;
|
||||
}
|
||||
}
|
||||
|
||||
export class NapProtoMsg<T extends ProtoMessageType> {
|
||||
private realMsg: NapProtoRealMsg<T>;
|
||||
|
||||
constructor(fields: T) {
|
||||
this.realMsg = NapProtoRealMsg.getInstance(fields);
|
||||
}
|
||||
|
||||
encode(data: NapProtoEncodeStructType<T>): Uint8Array {
|
||||
return this.realMsg.encode(data);
|
||||
}
|
||||
|
||||
decode(data: Uint8Array): NapProtoDecodeStructType<T> {
|
||||
return this.realMsg.decode(data);
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import {ActionName} from '../types';
|
||||
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
|
||||
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
|
||||
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
|
@@ -73,16 +73,14 @@ export class GoCQHTTPGetForwardMsgAction extends BaseAction<Payload, any> {
|
||||
|
||||
const singleMsg = data.msgList[0];
|
||||
const resMsg = await this.obContext.apis.MsgApi.parseMessage(singleMsg, 'array');//强制array 以便处理
|
||||
if (!resMsg) {
|
||||
if (!(resMsg?.message?.[0] as OB11MessageForward)?.data?.content) {
|
||||
throw new Error('找不到相关的聊天记录');
|
||||
}
|
||||
//if (this.obContext.configLoader.configData.messagePostFormat === 'array') {
|
||||
//提取
|
||||
const realmsg = ((await this.parseForward([resMsg]))[0].data.message as OB11MessageNode[])[0].data.message;
|
||||
//里面都是offline消息 id都是0 没得说话
|
||||
return { message: realmsg };
|
||||
return {
|
||||
messages: (resMsg?.message?.[0] as OB11MessageForward)?.data?.content
|
||||
};
|
||||
//}
|
||||
|
||||
// return { message: resMsg };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import {ActionName} from '../types';
|
||||
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
|
||||
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
|
||||
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
|
||||
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
|
||||
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { NapProtoEncodeStructType } from "@/core/packet/proto/NapProto";
|
||||
import { IndexNode } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -23,6 +23,6 @@ export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody![0].index as NapProtoEncodeStructType<typeof IndexNode>);
|
||||
return await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
import {ActionName} from '../types';
|
||||
import {FromSchema, JSONSchema} from 'json-schema-to-ts';
|
||||
import {GetPacketStatusDepends} from "@/onebot/action/packet/GetPacketStatus";
|
||||
import {AIVoiceChatType} from "@/core/packet/entities/aiChat";
|
||||
import {uri2local} from "@/common/file";
|
||||
import {ChatType, Peer} from "@/core";
|
||||
import {NapProtoEncodeStructType} from "@/core/packet/proto/NapProto";
|
||||
import {IndexNode} from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
|
||||
import { ActionName } from '../types';
|
||||
import { FromSchema, JSONSchema } from 'json-schema-to-ts';
|
||||
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
|
||||
import { AIVoiceChatType } from "@/core/packet/entities/aiChat";
|
||||
import { uri2local } from "@/common/file";
|
||||
import { ChatType, Peer } from "@/core";
|
||||
|
||||
const SchemaData = {
|
||||
type: 'object',
|
||||
@@ -20,21 +18,21 @@ const SchemaData = {
|
||||
type Payload = FromSchema<typeof SchemaData>;
|
||||
|
||||
export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
|
||||
message_id: string
|
||||
message_id: number
|
||||
}> {
|
||||
actionName = ActionName.SendGroupAiRecord;
|
||||
payloadSchema = SchemaData;
|
||||
|
||||
async _handle(payload: Payload) {
|
||||
const rawRsp = await this.core.apis.PacketApi.sendAiVoiceChatReq(+payload.group_id, payload.character, payload.text, AIVoiceChatType.Sound);
|
||||
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody![0].index as NapProtoEncodeStructType<typeof IndexNode>);
|
||||
const { path, fileName, errMsg, success} = (await uri2local(this.core.NapCatTempPath, url));
|
||||
const url = await this.core.apis.PacketApi.sendGroupPttFileDownloadReq(+payload.group_id, rawRsp.msgInfoBody[0].index);
|
||||
const { path, fileName, errMsg, success } = (await uri2local(this.core.NapCatTempPath, url));
|
||||
if (!success) {
|
||||
throw new Error(errMsg);
|
||||
}
|
||||
const peer = {chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString()} as Peer;
|
||||
const peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() } as Peer;
|
||||
const element = await this.core.apis.FileApi.createValidSendPttElement(path);
|
||||
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
|
||||
return {message_id: sendRes.msgId};
|
||||
return { message_id: sendRes.id ?? -1 };
|
||||
}
|
||||
}
|
||||
|
@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
|
||||
SettingItem(
|
||||
'<span id="napcat-update-title">Napcat</span>',
|
||||
void 0,
|
||||
SettingButton("V3.4.5", "napcat-update-button", "secondary")
|
||||
SettingButton("V3.4.7", "napcat-update-button", "secondary")
|
||||
)
|
||||
]),
|
||||
SettingList([
|
||||
|
Reference in New Issue
Block a user