Compare commits

...

11 Commits

Author SHA1 Message Date
pk5ls20
479b971b0c refactor: automatically select the optimal packet backend 2024-11-04 23:52:52 +08:00
手瓜一十雪
347ba5f354 feat: 初步封装 NativePacketClient 2024-11-04 21:09:11 +08:00
pk5ls20
81dbb9d980 perf: use cache in NapProto 2024-11-04 14:42:16 +08:00
pk5ls20
c4e1a3ab04 fix: SendGroupAiRecord wrong return id 2024-11-04 14:41:16 +08:00
pk5ls20
90ec774a21 feat: better mface toPreview 2024-11-03 17:29:50 +08:00
手瓜一十雪
db7a27e624 style: lint 2024-11-03 12:13:56 +08:00
Mlikiowa
f7d965eda2 release: v3.4.7 2024-11-03 01:50:09 +00:00
手瓜一十雪
74ca2e2e16 Merge pull request #485 from clansty/revert/get_forward_msg
revert: 还原 ob11 风格 get_forward_msg
2024-11-03 09:48:07 +08:00
Clansty
8ab550f2f5 revert: 还原 ob11 风格 get_forward_msg 2024-11-03 09:44:35 +08:00
pk5ls20
018aca4db2 fix: type hint 2024-11-03 02:45:58 +08:00
Mlikiowa
d4327166c1 release: v3.4.6 2024-11-02 05:20:34 +00:00
20 changed files with 401 additions and 251 deletions

View File

@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
"version": "3.4.5",
"version": "3.4.7",
"icon": "./logo.png",
"authors": [
{

View File

@@ -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",

View File

@@ -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;
}

View File

@@ -1 +1 @@
export const napCatVersion = '3.4.5';
export const napCatVersion = '3.4.7';

View File

@@ -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];
}

View File

@@ -2,10 +2,9 @@ import * as crypto from 'crypto';
import * as os from 'os';
import { ChatType, InstanceContext, NapCatCore } from '..';
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';
@@ -25,6 +24,7 @@ import { AIVoiceChatType, AIVoiceItemList } from "@/core/packet/entities/aiChat"
import { OidbSvcTrpcTcp0X929B_0Resp, OidbSvcTrpcTcp0X929D_0Resp } from "@/core/packet/proto/oidb/Oidb.0x929";
import { IndexNode, MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { NTV2RichMediaResp } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaResp";
import { RecvPacketData } from "@/core/packet/client/client";
interface OffsetType {
@@ -40,7 +40,6 @@ export class NTQQPacketApi {
context: InstanceContext;
core: NapCatCore;
logger: LogWrapper;
serverUrl: string | undefined;
qqVersion: string | undefined;
packetSession: PacketSession | undefined;
@@ -50,9 +49,8 @@ export class NTQQPacketApi {
this.logger = core.context.logger;
this.packetSession = undefined;
const config = this.core.configLoader.configData;
if (config && config.packetServer && config.packetServer.length > 0) {
const serverUrl = this.core.configLoader.configData.packetServer ?? '127.0.0.1:8086';
this.InitSendPacket(serverUrl, this.context.basicInfoWrapper.getFullQQVesion())
if (config) {
this.InitSendPacket(this.context.basicInfoWrapper.getFullQQVesion())
.then()
.catch(this.core.context.logger.logError.bind(this.core.context.logger));
} else {
@@ -64,17 +62,14 @@ export class NTQQPacketApi {
return this.packetSession?.client.available ?? false;
}
async InitSendPacket(serverUrl: string, qqversion: string) {
this.serverUrl = serverUrl;
async InitSendPacket(qqversion: string) {
this.qqVersion = qqversion;
const offsetTable: OffsetType = offset;
const table = offsetTable[qqversion + '-' + os.arch()];
const table = typedOffset[qqversion + '-' + os.arch()];
if (!table) {
this.logger.logError('PacketServer Offset table not found for QQVersion: ', qqversion + '-' + os.arch());
return false;
}
const url = 'ws://' + this.serverUrl + '/ws';
this.packetSession = new PacketSession(this.core.context.logger, new PacketClient(url, this.core));
this.packetSession = new PacketSession(this.core);
const cb = () => {
if (this.packetSession && this.packetSession.client) {
this.packetSession.client.init(process.pid, table.recv, table.send).then().catch(this.logger.logError.bind(this.logger));
@@ -223,7 +218,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);

View File

@@ -1,183 +0,0 @@
import { LogWrapper } from "@/common/log";
import { LRUCache } from "@/common/lru-cache";
import WebSocket, { Data } from "ws";
import crypto, { createHash } from "crypto";
import { NapCatCore } from "@/core";
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
export interface RecvPacket {
type: string, // 仅recv
trace_id_md5?: string,
data: RecvPacketData
}
export interface RecvPacketData {
seq: number
cmd: string
hex_data: string
}
export class PacketClient {
private websocket: WebSocket | undefined;
private isConnected: boolean = false;
private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 60;//现在暂时不可配置
private readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
private readonly clientUrl: string = '';
readonly napCatCore: NapCatCore;
private readonly logger: LogWrapper;
constructor(url: string, core: NapCatCore) {
this.clientUrl = url;
this.napCatCore = core;
this.logger = core.context.logger;
}
get available(): boolean {
return this.isConnected && this.websocket !== undefined;
}
private randText(len: number) {
let text = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < len; i++) {
text += possible.charAt(Math.floor(Math.random() * possible.length));
}
return text;
}
connect(cb: any): Promise<void> {
return new Promise((resolve, reject) => {
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
this.websocket = new WebSocket(this.clientUrl);
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
this.websocket.onopen = () => {
this.isConnected = true;
this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`);
cb();
resolve();
};
this.websocket.onerror = (error) => {
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
reject(new Error(`${error.message}`));
};
this.websocket.onmessage = (event) => {
// const message = JSON.parse(event.data.toString());
// console.log("Received message:", message);
this.handleMessage(event.data).then().catch();
};
this.websocket.onclose = () => {
this.isConnected = false;
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
this.attemptReconnect(cb);
};
});
}
private attemptReconnect(cb: any): void {
try {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
this.connect(cb).catch((error) => {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`);
});
}, 5000 * this.reconnectAttempts);
} else {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`);
}
} catch (error: any) {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
}
}
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> {
if (!this.isConnected || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const initMessage = {
action: 'init',
pid: pid,
recv: recv,
send: send
};
this.websocket.send(JSON.stringify(initMessage));
}
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.isConnected || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const commandMessage = {
action: 'send',
cmd: cmd,
data: data,
trace_id: trace_id
};
this.websocket.send(JSON.stringify(commandMessage));
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);
});
}
private async handleMessage(message: Data): Promise<void> {
try {
const json: RecvPacket = JSON.parse(message.toString());
const trace_id_md5 = json.trace_id_md5;
const action = json?.type ?? 'init';
const event = this.cb.get(trace_id_md5 + action);
if (event) {
await event(json.data);
}
//console.log("Received message:", json);
} catch (error) {
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
}
}
async sendPacket(cmd: string, data: PacketHexStr, rsp = false): Promise<RecvPacketData> {
// wtfk tx
// 校验失败和异常 可能返回undefined
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 sleep(10);
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);
}
}

View File

@@ -0,0 +1,109 @@
import { LRUCache } from "@/common/lru-cache";
import { NapCatCore } from "@/core";
import { LogWrapper } from "@/common/log";
import crypto, { createHash } from "crypto";
import { OidbPacket, PacketHexStr } from "@/core/packet/packer";
import { NapCatConfig } from "@/core/helper/config";
export interface RecvPacket {
type: string, // 仅recv
trace_id_md5?: string,
data: RecvPacketData
}
export interface RecvPacketData {
seq: number
cmd: string
hex_data: string
}
export abstract class PacketClient {
readonly napCatCore: NapCatCore;
protected readonly logger: LogWrapper;
protected readonly cb = new LRUCache<string, (json: RecvPacketData) => Promise<void>>(500); // trace_id-type callback
protected isAvailable: boolean = false;
protected config: NapCatConfig;
protected constructor(core: NapCatCore) {
this.napCatCore = core;
this.logger = core.context.logger;
this.config = core.configLoader.configData;
}
get available(): boolean {
return this.isAvailable;
}
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;
}
static create(core: NapCatCore): PacketClient {
throw new Error("Must be implemented by subclasses");
}
static compatibilityScore(logger: LogWrapper): number {
throw new Error("Must be implemented by subclasses");
}
abstract init(pid: number, recv: string, send: string): Promise<void>;
abstract connect(cb: () => void): Promise<void>;
abstract sendCommandImpl(cmd: string, data: string, trace_id: string): void;
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);
}
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.isAvailable) {
throw new Error("WebSocket is not connected");
}
this.sendCommandImpl(cmd, data, trace_id);
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);
}
}

View File

@@ -0,0 +1,67 @@
import crypto, { createHash } from "crypto";
import { NapCatCore } from "@/core";
import path, { dirname } from "path";
import { fileURLToPath } from "url";
import fs from "fs";
import { console } from "inspector";
import { PacketClient } from "@/core/packet/client/client";
import { constants, platform, type } from "node:os";
import { LogWrapper } from "@/common/log";
export class NativePacketClient extends PacketClient {
static supportedPlatforms = ['win32.x64'];
private MoeHooExport: any = { exports: {} };
protected constructor(core: NapCatCore) {
super(core);
}
get available(): boolean {
return this.isAvailable;
}
static compatibilityScore(logger: LogWrapper): number {
const platform = process.platform + '.' + process.arch;
if (!this.supportedPlatforms.includes(platform)) {
logger.logError(`[NativePacketClient] Unsupported platform: ${platform}`);
return 0;
}
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/moehoo.' + platform + '.node');
if (!fs.existsSync(moehoo_path)) {
logger.logError(`[NativePacketClient] Missing moehoo binary: ${moehoo_path}`);
return 0;
}
return 10;
}
static create(core: NapCatCore): NativePacketClient {
return new NativePacketClient(core);
}
async init(pid: number, recv: string, send: string): Promise<void> {
const platform = process.platform + '.' + process.arch;
const moehoo_path = path.join(dirname(fileURLToPath(import.meta.url)), './moehoo/moehoo.' + platform + '.node');
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}`);
}
// TODO: cannot use console.log here, fxxk tx
// Error [ERR_INSPECTOR_NOT_AVAILABLE]: Inspector is not available
// console.log('type:', type, 'uin:', uin, 'seq:', seq, 'cmd:', cmd, 'hex_data:', hex_data);
});
this.isAvailable = true;
}
sendCommandImpl(cmd: string, data: string, trace_id: string): void {
this.MoeHooExport.exports.SendPacket(cmd, data, crypto.createHash('md5').update(trace_id).digest('hex'));
}
connect(cb: () => void): Promise<void> {
cb();
return Promise.resolve();
}
}

View File

@@ -0,0 +1,113 @@
import { Data, WebSocket } from "ws";
import { NapCatCore } from "@/core";
import { PacketClient, RecvPacket } from "@/core/packet/client/client";
import { LogWrapper } from "@/common/log";
export class wsPacketClient extends PacketClient {
private websocket: WebSocket | undefined;
private reconnectAttempts: number = 0;
private readonly maxReconnectAttempts: number = 60; // 现在暂时不可配置
private readonly clientUrl: string = '';
private clientUrlWrap: (url: string) => string = (url: string) => `ws://${url}/ws`;
protected constructor(core: NapCatCore) {
super(core);
this.clientUrl = this.clientUrlWrap(this.config.packetServer ?? '127.0.0.1:8086');
}
static compatibilityScore(logger: LogWrapper): number {
return 10;
}
static create(core: NapCatCore): wsPacketClient {
return new wsPacketClient(core);
}
connect(cb: () => void): Promise<void> {
return new Promise((resolve, reject) => {
//this.logger.log.bind(this.logger)(`[Core] [Packet Server] Attempting to connect to ${this.clientUrl}`);
this.websocket = new WebSocket(this.clientUrl);
this.websocket.on('error', (err) => { }/*this.logger.logError.bind(this.logger)('[Core] [Packet Server] Error:', err.message)*/);
this.websocket.onopen = () => {
this.isAvailable = true;
this.reconnectAttempts = 0;
this.logger.log.bind(this.logger)(`[Core] [Packet Server] 已连接到 ${this.clientUrl}`);
cb();
resolve();
};
this.websocket.onerror = (error) => {
//this.logger.logError.bind(this.logger)(`WebSocket error: ${error}`);
reject(new Error(`${error.message}`));
};
this.websocket.onmessage = (event) => {
// const message = JSON.parse(event.data.toString());
// console.log("Received message:", message);
this.handleMessage(event.data).then().catch();
};
this.websocket.onclose = () => {
this.isAvailable = false;
//this.logger.logWarn.bind(this.logger)(`[Core] [Packet Server] Disconnected from ${this.clientUrl}`);
this.attemptReconnect(cb);
};
});
}
private attemptReconnect(cb: any): void {
try {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
this.reconnectAttempts++;
setTimeout(() => {
this.connect(cb).catch((error) => {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 尝试重连失败:${error.message}`);
});
}, 5000 * this.reconnectAttempts);
} else {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] ${this.clientUrl} 已达到最大重连次数!`);
}
} catch (error: any) {
this.logger.logError.bind(this.logger)(`[Core] [Packet Server] 重连时出错: ${error.message}`);
}
}
async init(pid: number, recv: string, send: string): Promise<void> {
if (!this.isAvailable || !this.websocket) {
throw new Error("WebSocket is not connected");
}
const initMessage = {
action: 'init',
pid: pid,
recv: recv,
send: send
};
this.websocket.send(JSON.stringify(initMessage));
}
sendCommandImpl(cmd: string, data: string, trace_id: string) : void {
const commandMessage = {
action: 'send',
cmd: cmd,
data: data,
trace_id: trace_id
};
this.websocket!.send(JSON.stringify(commandMessage));
}
private async handleMessage(message: Data): Promise<void> {
try {
const json: RecvPacket = JSON.parse(message.toString());
const trace_id_md5 = json.trace_id_md5;
const action = json?.type ?? 'init';
const event = this.cb.get(trace_id_md5 + action);
if (event) {
await event(json.data);
}
//console.log("Received message:", json);
} catch (error) {
this.logger.logError.bind(this.logger)(`Error parsing message: ${error}`);
}
}
}

View File

@@ -1,7 +1,6 @@
import * as fs from "node:fs";
import { ChatType, Peer } from "@/core";
import { LogWrapper } from "@/common/log";
import { PacketClient } from "@/core/packet/client";
import { PacketPacker } from "@/core/packet/packer";
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
import { HttpConn0x6ff_501Response } from "@/core/packet/proto/action/action";
@@ -19,6 +18,7 @@ import { int32ip2str, oidbIpv4s2HighwayIpv4s } from "@/core/packet/highway/utils
import { calculateSha1, calculateSha1StreamBytes, computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import { OidbSvcTrpcTcp0x6D6Response } from "@/core/packet/proto/oidb/Oidb.0x6D6";
import { OidbSvcTrpcTcp0XE37_800Response, OidbSvcTrpcTcp0XE37Response } from "@/core/packet/proto/oidb/Oidb.0XE37_800";
import { PacketClient } from "@/core/packet/client/client";
export const BlockSize = 1024 * 1024;

View File

@@ -241,7 +241,7 @@ export class PacketMsgMarkFaceElement extends IPacketMsgElement<SendMarketFaceEl
}
toPreview(): string {
return `[${this.emojiName}]`;
return `${this.emojiName}`;
}
}

View File

@@ -22,14 +22,14 @@ import { PacketMsg } from "@/core/packet/message/message";
import { OidbSvcTrpcTcp0x6D6 } from "@/core/packet/proto/oidb/Oidb.0x6D6";
import { OidbSvcTrpcTcp0XE37_1200 } from "@/core/packet/proto/oidb/Oidb.0xE37_1200";
import { PacketMsgConverter } from "@/core/packet/message/converter";
import { PacketClient } from "@/core/packet/client";
import { OidbSvcTrpcTcp0XE37_1700 } from "@/core/packet/proto/oidb/Oidb.0xE37_1700";
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";
import { PacketClient } from "@/core/packet/client/client";
export type PacketHexStr = string & { readonly hexNya: unique symbol };

View File

@@ -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);
}
}

View File

@@ -1,7 +1,19 @@
import { PacketClient } from "@/core/packet/client";
import { PacketHighwaySession } from "@/core/packet/highway/session";
import { LogWrapper } from "@/common/log";
import { PacketPacker } from "@/core/packet/packer";
import { PacketClient } from "@/core/packet/client/client";
import { NativePacketClient } from "@/core/packet/client/nativeClient";
import { wsPacketClient } from "@/core/packet/client/wsClient";
import { NapCatCore } from "@/core";
type clientPriority = {
[key: number]: typeof PacketClient;
}
const clientPriority: clientPriority = {
10: NativePacketClient,
1: wsPacketClient,
};
export class PacketSession {
readonly logger: LogWrapper;
@@ -9,10 +21,29 @@ export class PacketSession {
readonly packer: PacketPacker;
readonly highwaySession: PacketHighwaySession;
constructor(logger: LogWrapper, client: PacketClient) {
this.logger = logger;
this.client = client;
constructor(core: NapCatCore) {
this.logger = core.context.logger;
this.client = this.judgeClient(core);
this.packer = new PacketPacker(this.logger, this.client);
this.highwaySession = new PacketHighwaySession(this.logger, this.client, this.packer);
}
private judgeClient(core: NapCatCore): PacketClient {
let selectedClient: typeof PacketClient | null = null;
let maxScore = -1;
for (const key in clientPriority) {
const priority = parseInt(key);
const ClientClass = clientPriority[priority];
const score = priority * ClientClass.compatibilityScore(core.context.logger);
if (score > maxScore) {
maxScore = score;
selectedClient = ClientClass;
}
}
if (!selectedClient) {
throw new Error("No compatible PacketClient found");
}
this.logger.log(`[Packet] 自动选择了: ${selectedClient.name}`);
return selectedClient.create(core);
}
}

View File

@@ -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',

View File

@@ -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 };
}
}
}

View File

@@ -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);
}
}

View File

@@ -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 };
}
}

View File

@@ -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([