Compare commits

..

22 Commits

Author SHA1 Message Date
手瓜一十雪
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
手瓜一十雪
fa25d2e779 feat: arm64 29271 2024-11-02 13:19:13 +08:00
手瓜一十雪
3ce1c3f0ec feat: support 3.2.13-29271-x64 2024-11-02 10:58:36 +08:00
手瓜一十雪
96dff5141e feat: packet 29271 2024-11-02 10:46:15 +08:00
手瓜一十雪
78d85d9965 feat: 29271 2024-11-02 10:02:39 +08:00
手瓜一十雪
37ec455b02 Merge pull request #484 from NapNeko/feat/ai-voice
feat: ai voice
2024-11-02 08:15:05 +08:00
pk5ls20
6ab82739a6 feat: ai voice 2024-11-02 01:51:57 +08:00
手瓜一十雪
a36917e7c0 Merge branch 'main' of https://github.com/NapNeko/NapCatQQ 2024-11-01 12:13:24 +08:00
手瓜一十雪
21f3428b36 feat: support 6.9.58-28971 2024-11-01 12:13:04 +08:00
Mlikiowa
f8a487db25 release: v3.4.5 2024-10-31 12:30:25 +00:00
手瓜一十雪
73a859be04 fix: report self 2024-10-31 20:30:02 +08:00
手瓜一十雪
63bcee01a1 fix: report self 2024-10-31 20:27:17 +08:00
Mlikiowa
85b4966ba8 release: v3.4.4 2024-10-31 11:32:42 +00:00
23 changed files with 509 additions and 69 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "3.4.3",
"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.3';
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

@@ -1,10 +1,11 @@
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 { 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';
@@ -20,6 +21,10 @@ import {
} from "@/core/packet/message/element";
import { MiniAppReqParams, MiniAppRawData } from "@/core/packet/entities/miniApp";
import { MiniAppAdaptShareInfoResp } from "@/core/packet/proto/action/miniAppAdaptShareInfo";
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";
interface OffsetType {
@@ -188,10 +193,54 @@ export class NTQQPacketApi {
return `https://${resp.download.downloadDns}/ftn_handler/${Buffer.from(resp.download.downloadUrl).toString('hex')}/?fname=`;
}
async sendGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>) {
const data = this.packetSession?.packer.packGroupPttFileDownloadReq(groupUin, node);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(NTV2RichMediaResp).decode(body);
const info = resp.download.info;
return `https://${info.domain}${info.urlPath}${resp.download.rKeyParam}`;
}
async sendMiniAppShareInfoReq(param: MiniAppReqParams) {
const data = this.packetSession?.packer.packMiniAppAdaptShareInfo(param);
const ret = await this.sendPacket("LightAppSvc.mini_app_share.AdaptShareInfo", data!, true);
const body = new NapProtoMsg(MiniAppAdaptShareInfoResp).decode(Buffer.from(ret.hex_data, 'hex'));
return JSON.parse(body.content.jsonContent) as MiniAppRawData;
}
async sendFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType) : Promise<AIVoiceItemList[] | null> {
const data = this.packetSession?.packer.packFetchAiVoiceListReq(groupUin, chatType);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBaseRsp).decode(Buffer.from(ret.hex_data, 'hex')).body;
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929D_0Resp).decode(body);
if (!resp.content) return null;
return resp.content.map((item) => {
return {
category: item.category,
voices: item.voices
};
});
}
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);
while (true) {
if (reqTime >= reqMaxTime) {
throw new Error(`sendAiVoiceChatReq failed after ${reqMaxTime} times`);
}
reqTime++;
const data = this.packetSession?.packer.packAiVoiceChatReq(groupUin, voiceId, text, chatType, sessionId);
const ret = await this.sendOidbPacket(data!, true);
const body = new NapProtoMsg(OidbSvcTrpcTcpBase).decode(Buffer.from(ret.hex_data, 'hex'));
if (body.errorCode) {
throw new Error(`sendAiVoiceChatReq retCode: ${body.errorCode} error: ${body.errorMsg}`);
}
const resp = new NapProtoMsg(OidbSvcTrpcTcp0X929B_0Resp).decode(body.body);
if (!resp.msgInfo) continue;
return resp.msgInfo;
}
}
}

View File

@@ -51,7 +51,7 @@
"appid": 537249739,
"qua": "V1_WIN_NQ_9.9.16_28788_GW_B"
},
"9.9.16-28971":{
"9.9.16-28971": {
"appid": 537249775,
"qua": "V1_WIN_NQ_9.9.16_28971_GW_B"
},
@@ -62,5 +62,17 @@
"6.9.58-28971": {
"appid": 537249826,
"qua": "V1_MAC_NQ_6.9.58_28971_GW_B"
},
"9.9.16-29271": {
"appid": 537249813,
"qua": "V1_WIN_NQ_9.9.16_29271_GW_B"
},
"3.2.13-29271": {
"appid": 537249913,
"qua": "V1_LNX_NQ_3.2.13_29271_GW_B"
},
"6.9.59-29271": {
"appid": 537249863,
"qua": "V1_MAC_NQ_6.9.59_29271_GW_B"
}
}

View File

@@ -1,4 +1,8 @@
{
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
},
"3.2.12-28418-x64": {
"recv": "A0723E0",
"send": "A06EAE0"
@@ -35,8 +39,20 @@
"send": "6E91318",
"recv": "6E94B50"
},
"6.9.56-28418-arm64": {
"send": "4471360",
"recv": "4473BCC"
"6.9.58-28971-arm64": {
"send": "449ACA0",
"recv": "449D50C"
},
"9.9.16-29271-x64": {
"send": "3833510",
"recv": "3837944"
},
"3.2.13-29271-x64": {
"send": "A11E680",
"recv": "A121F80"
},
"3.2.13-29271-arm64": {
"send": "6ECA098",
"recv": "6ECD8D0"
}
}

View File

@@ -0,0 +1,16 @@
export enum AIVoiceChatType {
Unknown = 0,
Sound = 1,
Sing = 2
}
export interface AIVoiceItem {
voiceId: string;
voiceDisplayName: string;
voiceExampleUrl: string;
}
export interface AIVoiceItemList {
category: string;
voices: AIVoiceItem[];
}

View File

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

View File

@@ -1,13 +1,13 @@
import * as zlib from "node:zlib";
import * as crypto from "node:crypto";
import { computeMd5AndLengthWithLimit } from "@/core/packet/utils/crypto/hash";
import { NapProtoMsg } from "@/core/packet/proto/NapProto";
import { NapProtoEncodeStructType, NapProtoMsg } from "@/core/packet/proto/NapProto";
import { OidbSvcTrpcTcpBase } from "@/core/packet/proto/oidb/OidbBase";
import { OidbSvcTrpcTcp0X9067_202 } from "@/core/packet/proto/oidb/Oidb.0x9067_202";
import { OidbSvcTrpcTcp0X8FC_2, OidbSvcTrpcTcp0X8FC_2_Body } from "@/core/packet/proto/oidb/Oidb.0x8FC_2";
import { OidbSvcTrpcTcp0XFE1_2 } from "@/core/packet/proto/oidb/Oidb.0XFE1_2";
import { OidbSvcTrpcTcp0XED3_1 } from "@/core/packet/proto/oidb/Oidb.0xED3_1";
import { NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { IndexNode, NTV2RichMediaReq } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
import { HttpConn0x6ff_501 } from "@/core/packet/proto/action/action";
import { LongMsgResult, SendLongMsgReq } from "@/core/packet/proto/message/action";
import { PacketMsgBuilder } from "@/core/packet/message/builder";
@@ -28,6 +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";
export type PacketHexStr = string & { readonly hexNya: unique symbol };
@@ -696,6 +698,37 @@ export class PacketPacker {
);
}
packGroupPttFileDownloadReq(groupUin: number, node: NapProtoEncodeStructType<typeof IndexNode>): OidbPacket {
return this.packOidbPacket(0x126E, 200, new NapProtoMsg(NTV2RichMediaReq).encode({
reqHead: {
common: {
requestId: 4,
command: 200
},
scene: {
requestType: 1,
businessType: 3,
sceneType: 2,
group: {
groupUin: groupUin
}
},
client: {
agentType: 2
}
},
download: {
node: node,
download: {
video: {
busiType: 0,
sceneType: 0,
}
}
}
}), true, false);
}
packGroupSignReq(uin: string, groupCode: string): OidbPacket {
return this.packOidbPacket(0XEB7, 1, new NapProtoMsg(OidbSvcTrpcTcp0XEB7).encode(
{
@@ -744,4 +777,27 @@ export class PacketPacker {
)
);
}
packFetchAiVoiceListReq(groupUin: number, chatType: AIVoiceChatType): OidbPacket {
return this.packOidbPacket(0x929D, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929D_0).encode({
groupUin: groupUin,
chatType: chatType
})
);
}
packAiVoiceChatReq(groupUin: number, voiceId: string, text: string, chatType: AIVoiceChatType, sessionId: number): OidbPacket {
return this.packOidbPacket(0x929B, 0,
new NapProtoMsg(OidbSvcTrpcTcp0X929B_0).encode({
groupUin: groupUin,
voiceId: voiceId,
text: text,
chatType: chatType,
session: {
sessionId: sessionId
}
})
);
}
}

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

@@ -0,0 +1,42 @@
import { ScalarType } from "@protobuf-ts/runtime";
import { ProtoField } from "../NapProto";
import { MsgInfo } from "@/core/packet/proto/oidb/common/Ntv2.RichMediaReq";
export const OidbSvcTrpcTcp0X929D_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
chatType: ProtoField(2, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929D_0Resp = {
content: ProtoField(1, () => OidbSvcTrpcTcp0X929D_0RespContent, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContent = {
category: ProtoField(1, ScalarType.STRING),
voices: ProtoField(2, () => OidbSvcTrpcTcp0X929D_0RespContentVoice, false, true),
};
export const OidbSvcTrpcTcp0X929D_0RespContentVoice = {
voiceId: ProtoField(1, ScalarType.STRING),
voiceDisplayName: ProtoField(2, ScalarType.STRING),
voiceExampleUrl: ProtoField(3, ScalarType.STRING),
};
export const OidbSvcTrpcTcp0X929B_0 = {
groupUin: ProtoField(1, ScalarType.UINT32),
voiceId: ProtoField(2, ScalarType.STRING),
text: ProtoField(3, ScalarType.STRING),
chatType: ProtoField(4, ScalarType.UINT32),
session: ProtoField(5, () => OidbSvcTrpcTcp0X929B_0_Session),
};
export const OidbSvcTrpcTcp0X929B_0_Session = {
sessionId: ProtoField(1, ScalarType.UINT32),
};
export const OidbSvcTrpcTcp0X929B_0Resp = {
statusCode: ProtoField(1, ScalarType.UINT32),
field2: ProtoField(2, ScalarType.UINT32, true),
field3: ProtoField(3, ScalarType.UINT32),
msgInfo: ProtoField(4, () => MsgInfo, true),
};

View File

@@ -4,10 +4,11 @@ import { ProtoField } from "../NapProto";
export const OidbSvcTrpcTcpBase = {
command: ProtoField(1, ScalarType.UINT32),
subCommand: ProtoField(2, ScalarType.UINT32),
errorCode: ProtoField(3, ScalarType.UINT32),
body: ProtoField(4, ScalarType.BYTES),
errorMsg: ProtoField(5, ScalarType.STRING, true),
isReserved: ProtoField(12, ScalarType.UINT32)
};
export const OidbSvcTrpcTcpBaseRsp = {
body: ProtoField(4, ScalarType.BYTES)
};
};

View File

@@ -0,0 +1,41 @@
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',
properties: {
group_id: { type: ['number', 'string'] },
chat_type: { type: ['number', 'string'] },
},
required: ['group_id'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
interface GetAiCharactersResponse {
type: string;
characters: {
character_id: string;
character_name: string;
preview_url: string;
}[];
}
export class GetAiCharacters extends GetPacketStatusDepends<Payload, GetAiCharactersResponse[]> {
actionName = ActionName.GetAiCharacters;
payloadSchema = SchemaData;
async _handle(payload: Payload) {
const rawList = await this.core.apis.PacketApi.sendFetchAiVoiceListReq(+payload.group_id, +(payload.chat_type ?? 1) as AIVoiceChatType);
return rawList?.map((item) => ({
type: item.category,
characters: item.voices.map((voice) => ({
character_id: voice.voiceId,
character_name: voice.voiceDisplayName,
preview_url: voice.voiceExampleUrl,
})),
})) ?? [];
}
}

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

@@ -0,0 +1,28 @@
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',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class GetAiRecord extends GetPacketStatusDepends<Payload, string> {
actionName = ActionName.GetAiRecord;
payloadSchema = SchemaData;
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);
}
}

View File

@@ -0,0 +1,38 @@
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',
properties: {
character: { type: ['string'] },
group_id: { type: ['number', 'string'] },
text: { type: 'string' },
},
required: ['character', 'group_id', 'text'],
} as const satisfies JSONSchema;
type Payload = FromSchema<typeof SchemaData>;
export class SendGroupAiRecord extends GetPacketStatusDepends<Payload, {
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);
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 element = await this.core.apis.FileApi.createValidSendPttElement(path);
const sendRes = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [element], [path]);
return { message_id: sendRes.id ?? -1 };
}
}

View File

@@ -99,6 +99,9 @@ import { GoCQHTTPGetModelShow } from './go-cqhttp/GoCQHTTPGetModelShow';
import { GoCQHTTPSetModelShow } from './go-cqhttp/GoCQHTTPSetModelShow';
import { GoCQHTTPDeleteFriend } from './go-cqhttp/GoCQHTTPDeleteFriend';
import { GetMiniAppArk } from "@/onebot/action/extends/GetMiniAppArk";
import { GetAiRecord } from "@/onebot/action/group/GetAiRecord";
import { SendGroupAiRecord } from "@/onebot/action/group/SendGroupAiRecord";
import { GetAiCharacters } from "@/onebot/action/extends/GetAiCharacters";
export type ActionMap = Map<string, BaseAction<any, any>>;
@@ -212,6 +215,9 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo
new GetGroupShutList(obContext, core),
new GetGroupFileUrl(obContext, core),
new GetMiniAppArk(obContext, core),
new GetAiRecord(obContext, core),
new SendGroupAiRecord(obContext, core),
new GetAiCharacters(obContext, core),
];
const actionMap = new Map();
for (const action of actionHandlers) {

View File

@@ -138,4 +138,7 @@ export enum ActionName {
SetGroupSign = "set_group_sign",
GetMiniAppArk = "get_mini_app_ark",
// UploadForwardMsg = "upload_forward_msg",
GetAiRecord = "get_ai_record",
GetAiCharacters = "get_ai_characters",
SendGroupAiRecord = "send_group_ai_record",
}

View File

@@ -311,51 +311,37 @@ export class NapCatOneBot11Adapter {
}
};
//const msgIdSend = new LRUCache<string, boolean>(100);
const msgIdSend = new LRUCache<string, number>(100);
const recallMsgs = new LRUCache<string, boolean>(100);
msgListener.onAddSendMsg = async msg => {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS) {
this.apis.MsgApi.parseMessage(msg)
.then((ob11Msg) => {
if (!ob11Msg) return;
ob11Msg.target_id = parseInt(msg.peerUin);
if (this.configLoader.configData.reportSelfMessage) {
msg.id = MessageUnique.createUniqueMsgId({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
}, msg.msgId);
this.emitMsg(msg);
} else {
// logOB11Message(this.core, ob11Msg);
}
});
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SENDING) {
msgIdSend.put(msg.msgId, 0);
}
};
msgListener.onMsgInfoListUpdate = async msgList => {
this.emitRecallMsg(msgList, recallMsgs)
.catch(e => this.context.logger.logError.bind(this.context.logger)('处理消息失败', e));
// for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
// if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && !msgIdSend.get(msg.msgId)) {
// msgIdSend.put(msg.msgId, true);
// // 完成后再post
// this.apis.MsgApi.parseMessage(msg)
// .then((ob11Msg) => {
// if (!ob11Msg) return;
// ob11Msg.target_id = parseInt(msg.peerUin);
// if (this.configLoader.configData.reportSelfMessage) {
// msg.id = MessageUnique.createUniqueMsgId({
// chatType: msg.chatType,
// peerUid: msg.peerUid,
// guildId: '',
// }, msg.msgId);
// this.emitMsg(msg);
// } else {
// // logOB11Message(this.core, ob11Msg);
// }
// });
// }
// }
for (const msg of msgList.filter(e => e.senderUin == this.core.selfInfo.uin)) {
if (msg.sendStatus == SendStatusType.KSEND_STATUS_SUCCESS && msgIdSend.get(msg.msgId) == 0) {
msgIdSend.put(msg.msgId, 1);
// 完成后再post
this.apis.MsgApi.parseMessage(msg)
.then((ob11Msg) => {
if (!ob11Msg) return;
ob11Msg.target_id = parseInt(msg.peerUin);
if (this.configLoader.configData.reportSelfMessage) {
msg.id = MessageUnique.createUniqueMsgId({
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
}, msg.msgId);
this.emitMsg(msg);
} else {
// logOB11Message(this.core, ob11Msg);
}
});
}
}
};
this.context.session.getMsgService().addKernelMsgListener(

View File

@@ -164,7 +164,7 @@ async function onSettingWindowCreated(view) {
SettingItem(
'<span id="napcat-update-title">Napcat</span>',
void 0,
SettingButton("V3.4.3", "napcat-update-button", "secondary")
SettingButton("V3.4.7", "napcat-update-button", "secondary")
)
]),
SettingList([