diff --git a/README.md b/README.md
index 6d3d7f25..95d7527e 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,9 @@
+
# NapCat
-
+
_Modern protocol-side framework implemented based on NTQQ._
@@ -50,6 +51,8 @@ _Modern protocol-side framework implemented based on NTQQ._
+ [Lagrange](https://github.com/LagrangeDev/Lagrange.Core) 对本项目的大力支持 参考部分代码 已获授权
++ [AstrBot](https://github.com/AstrBotDevs/AstrBot) 是完美适配本项目的LLM Bot框架 在此推荐一下
+
+ 不过最最重要的 还是需要感谢屏幕前的你哦~
---
@@ -61,7 +64,3 @@ _Modern protocol-side framework implemented based on NTQQ._
2. 项目其余逻辑代码采用[本仓库开源许可](./LICENSE).
**本仓库仅用于提高易用性,实现消息推送类功能,此外,禁止任何项目未经仓库主作者授权基于 NapCat 代码开发。使用请遵守当地法律法规,由此造成的问题由使用者和提供违规使用教程者负责。**
-
-## Warnings
-
-[某框架抄袭部分分析](https://napneko.github.io/other/about-copy)
diff --git a/external/LiteLoaderWrapper.zip b/external/LiteLoaderWrapper.zip
index 9861a5a1..e38ceabc 100644
Binary files a/external/LiteLoaderWrapper.zip and b/external/LiteLoaderWrapper.zip differ
diff --git a/external/logo.png b/external/logo.png
index a87d363e..839691c4 100644
Binary files a/external/logo.png and b/external/logo.png differ
diff --git a/launcher/NapCatWinBootMain.exe b/launcher/NapCatWinBootMain.exe
index 66f69e09..99a69b9e 100644
Binary files a/launcher/NapCatWinBootMain.exe and b/launcher/NapCatWinBootMain.exe differ
diff --git a/launcher/qqnt.json b/launcher/qqnt.json
index 74d47b93..f3db1840 100644
--- a/launcher/qqnt.json
+++ b/launcher/qqnt.json
@@ -1,9 +1,9 @@
{
"name": "qq-chat",
- "version": "9.9.18-32869",
- "verHash": "e735296c",
- "linuxVersion": "3.2.16-32869",
- "linuxVerHash": "4c192ba9",
+ "version": "9.9.19-34740",
+ "verHash": "f31348f2",
+ "linuxVersion": "3.2.17-34740",
+ "linuxVerHash": "5aa2d8d6",
"private": true,
"description": "QQ",
"productName": "QQ",
@@ -16,27 +16,10 @@
"bin": {
"qd": "externals/devtools/cli/index.js"
},
- "appid": {
- "win32": "537258389",
- "darwin": "537258412",
- "linux": "537258424"
- },
"main": "./loadNapCat.js",
- "peerDependenciesMeta": {
- "*": {
- "optional": true
- }
- },
- "pnpm": {
- "patchedDependencies": {
- "@vue/runtime-dom@3.5.12": "patches/@vue__runtime-dom@3.5.12.patch",
- "@swc/helpers@0.5.3": "patches/@swc__helpers@0.5.3.patch",
- "vuex@4.1.0": "patches/vuex@4.1.0.patch"
- }
- },
- "buildVersion": "32869",
+ "buildVersion": "34740",
"isPureShell": true,
"isByteCodeShell": true,
"platform": "win32",
"eleArch": "x64"
-}
+}
\ No newline at end of file
diff --git a/manifest.json b/manifest.json
index 6a8caaa2..0b11828d 100644
--- a/manifest.json
+++ b/manifest.json
@@ -4,7 +4,7 @@
"name": "NapCatQQ",
"slug": "NapCat.Framework",
"description": "高性能的 OneBot 11 协议实现",
- "version": "4.7.46",
+ "version": "4.7.60",
"icon": "./logo.png",
"authors": [
{
diff --git a/package.json b/package.json
index 00090fdc..5580e8b2 100644
--- a/package.json
+++ b/package.json
@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
- "version": "4.7.46",
+ "version": "4.7.60",
"scripts": {
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
diff --git a/src/common/download-ffmpeg.ts b/src/common/download-ffmpeg.ts
index 1eb6dd27..25d4a798 100644
--- a/src/common/download-ffmpeg.ts
+++ b/src/common/download-ffmpeg.ts
@@ -8,11 +8,12 @@ import { pipeline } from 'stream/promises';
import { fileURLToPath } from 'url';
import { LogWrapper } from './log';
-const downloadOri = "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2025-04-16-12-54/ffmpeg-n7.1.1-6-g48c0f071d4-win64-lgpl-7.1.zip"
+const downloadOri = "https://github.com/NapNeko/ffmpeg-build/releases/download/v1.0.0/ffmpeg-7.1.1-win64.zip"
const urls = [
"https://github.moeyy.xyz/" + downloadOri,
"https://ghp.ci/" + downloadOri,
"https://gh.api.99988866.xyz/" + downloadOri,
+ "https://gh.api.99988866.xyz/" + downloadOri,
downloadOri
];
@@ -336,9 +337,16 @@ export async function downloadFFmpegIfNotExists(log: LogWrapper) {
const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe'));
if (!ffmpeg_exist || !ffprobe_exist) {
- await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => {
+ let url = await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => {
log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`);
});
+ if (!url) {
+ log.log('[FFmpeg] [Error] 下载FFmpeg失败');
+ return {
+ path: null,
+ reset: false
+ };
+ }
return {
path: path.join(currentPath, 'ffmpeg'),
reset: true
diff --git a/src/common/version.ts b/src/common/version.ts
index b798e50d..d9a8f0ba 100644
--- a/src/common/version.ts
+++ b/src/common/version.ts
@@ -1 +1 @@
-export const napCatVersion = '4.7.46';
+export const napCatVersion = '4.7.60';
diff --git a/src/core/apis/file.ts b/src/core/apis/file.ts
index 31f3417e..8b0c56f6 100644
--- a/src/core/apis/file.ts
+++ b/src/core/apis/file.ts
@@ -27,6 +27,8 @@ import { SendMessageContext } from '@/onebot/api';
import { getFileTypeForSendType } from '../helper/msg';
import { FFmpegService } from '@/common/ffmpeg';
import { rkeyDataType } from '../types/file';
+import { NapProtoMsg } from '@napneko/nap-proto-core';
+import { FileId } from '../packet/transformer/proto/misc/fileid';
import { imageSizeFromFile } from '@/image-size/fromFile';
export class NTQQFileApi {
@@ -63,6 +65,76 @@ export class NTQQFileApi {
}
}
+ async getFileUrl(chatType: ChatType, peer: string, fileUUID?: string, file10MMd5?: string | undefined) {
+ if (this.core.apis.PacketApi.available) {
+ try {
+ if (chatType === ChatType.KCHATTYPEGROUP && fileUUID) {
+ return this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+peer, fileUUID);
+ } else if (file10MMd5 && fileUUID) {
+ return this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(peer, fileUUID, file10MMd5);
+ }
+ } catch (error) {
+ this.context.logger.logError('获取文件URL失败', (error as Error).message);
+ }
+ }
+ throw new Error('fileUUID or file10MMd5 is undefined');
+ }
+
+ async getPttUrl(peer: string, fileUUID?: string) {
+ if (this.core.apis.PacketApi.available && fileUUID) {
+ let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid;
+ try {
+ if (appid && appid === 1403) {
+ return this.core.apis.PacketApi.pkt.operation.GetGroupPttUrl(+peer, {
+ fileUuid: fileUUID,
+ storeId: 1,
+ uploadTime: 0,
+ ttl: 0,
+ subType: 0,
+ });
+ } else if (fileUUID) {
+ return this.core.apis.PacketApi.pkt.operation.GetPttUrl(peer, {
+ fileUuid: fileUUID,
+ storeId: 1,
+ uploadTime: 0,
+ ttl: 0,
+ subType: 0,
+ });
+ }
+ } catch (error) {
+ this.context.logger.logError('获取文件URL失败', (error as Error).message);
+ }
+ }
+ throw new Error('packet cant get ptt url');
+ }
+
+ async getVideoUrlPacket(peer: string, fileUUID?: string) {
+ if (this.core.apis.PacketApi.available && fileUUID) {
+ let appid = new NapProtoMsg(FileId).decode(Buffer.from(fileUUID.replaceAll('-', '+').replaceAll('_', '/'), 'base64')).appid;
+ try {
+ if (appid && appid === 1415) {
+ return this.core.apis.PacketApi.pkt.operation.GetGroupVideoUrl(+peer, {
+ fileUuid: fileUUID,
+ storeId: 1,
+ uploadTime: 0,
+ ttl: 0,
+ subType: 0,
+ });
+ } else if (fileUUID) {
+ return this.core.apis.PacketApi.pkt.operation.GetVideoUrl(peer, {
+ fileUuid: fileUUID,
+ storeId: 1,
+ uploadTime: 0,
+ ttl: 0,
+ subType: 0,
+ });
+ }
+ } catch (error) {
+ this.context.logger.logError('获取文件URL失败', (error as Error).message);
+ }
+ }
+ throw new Error('packet cant get video url');
+ }
async copyFile(filePath: string, destPath: string) {
await this.core.util.copyFile(filePath, destPath);
@@ -325,6 +397,7 @@ export class NTQQFileApi {
}
});
});
+ return res.flat();
}
async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, timeout = 1000 * 60 * 2, force: boolean = false) {
diff --git a/src/core/apis/msg.ts b/src/core/apis/msg.ts
index 5dd4d969..7c2d3614 100644
--- a/src/core/apis/msg.ts
+++ b/src/core/apis/msg.ts
@@ -71,6 +71,7 @@ export class NTQQMsgApi {
async queryMsgsWithFilterExWithSeq(peer: Peer, msgSeq: string) {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
+ //searchFields: 3,
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
@@ -84,6 +85,7 @@ export class NTQQMsgApi {
return await this.context.session.getMsgService().queryMsgsWithFilterEx('0', '0', msgSeq, {
chatInfo: peer,
filterMsgType: [],
+ //searchFields: 3,
filterSendersUid: SendersUid,
filterMsgToTime: MsgTime,
filterMsgFromTime: MsgTime,
@@ -100,6 +102,7 @@ export class NTQQMsgApi {
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: false,
+ //searchFields: 3,
isIncludeCurrent: true,
pageLimit: 1,
});
@@ -110,6 +113,7 @@ export class NTQQMsgApi {
filterMsgType: [],
filterSendersUid: [],
filterMsgToTime: '0',
+ //searchFields: 3,
filterMsgFromTime: '0',
isReverseOrder: true,
isIncludeCurrent: true,
@@ -128,6 +132,7 @@ export class NTQQMsgApi {
chatInfo: peer,//此处为Peer 为关键查询参数 没有啥也没有 by mlik iowa
filterMsgType: [],
filterSendersUid: [],
+ //searchFields: 3,
filterMsgToTime: filterMsgToTime,
filterMsgFromTime: filterMsgFromTime,
isReverseOrder: false,
@@ -142,6 +147,7 @@ export class NTQQMsgApi {
chatInfo: peer,
filterMsgType: [],
filterSendersUid: SendersUid,
+ //searchFields: 3,
filterMsgToTime: '0',
filterMsgFromTime: '0',
isReverseOrder: true,
diff --git a/src/core/external/appid.json b/src/core/external/appid.json
index 9a7ff556..56742929 100644
--- a/src/core/external/appid.json
+++ b/src/core/external/appid.json
@@ -282,5 +282,9 @@
"3.2.17-34740": {
"appid": 537290727,
"qua": "V1_LNX_NQ_3.2.17_34740_GW_B"
+ },
+ "9.9.19-34958": {
+ "appid": 537290742,
+ "qua": "V1_WIN_NQ_9.9.19_34958_GW_B"
}
}
\ No newline at end of file
diff --git a/src/core/external/offset.json b/src/core/external/offset.json
index 48f84d17..dafe490b 100644
--- a/src/core/external/offset.json
+++ b/src/core/external/offset.json
@@ -354,5 +354,17 @@
"9.9.19-34740-x64": {
"send": "3BDD8D0",
"recv": "3BE20D0"
+ },
+ "3.2.17-34740-x64": {
+ "send": "ADDF0A0",
+ "recv": "ADE2AC0"
+ },
+ "3.2.17-34740-arm64": {
+ "send": "7753BB8",
+ "recv": "77574E8"
+ },
+ "9.9.19-34958-x64": {
+ "send": "3BDD8D0",
+ "recv": "3BE20D0"
}
}
\ No newline at end of file
diff --git a/src/core/listeners/NodeIKernelBuddyListener.ts b/src/core/listeners/NodeIKernelBuddyListener.ts
index edf29044..c85883ad 100644
--- a/src/core/listeners/NodeIKernelBuddyListener.ts
+++ b/src/core/listeners/NodeIKernelBuddyListener.ts
@@ -3,43 +3,43 @@ import { BuddyCategoryType, FriendRequestNotify } from '@/core/types';
export type OnBuddyChangeParams = BuddyCategoryType[];
export class NodeIKernelBuddyListener {
- onBuddyListChangedV2(arg: unknown): any {
+ onBuddyListChangedV2(_arg: unknown): any {
}
- onAddBuddyNeedVerify(arg: unknown): any {
+ onAddBuddyNeedVerify(_arg: unknown): any {
}
- onAddMeSettingChanged(arg: unknown): any {
+ onAddMeSettingChanged(_arg: unknown): any {
}
- onAvatarUrlUpdated(arg: unknown): any {
+ onAvatarUrlUpdated(_arg: unknown): any {
}
- onBlockChanged(arg: unknown): any {
+ onBlockChanged(_arg: unknown): any {
}
- onBuddyDetailInfoChange(arg: unknown): any {
+ onBuddyDetailInfoChange(_arg: unknown): any {
}
- onBuddyInfoChange(arg: unknown): any {
+ onBuddyInfoChange(_arg: unknown): any {
}
- onBuddyListChange(arg: OnBuddyChangeParams): any {
+ onBuddyListChange(_arg: OnBuddyChangeParams): any {
}
- onBuddyRemarkUpdated(arg: unknown): any {
+ onBuddyRemarkUpdated(_arg: unknown): any {
}
- onBuddyReqChange(arg: FriendRequestNotify): any {
+ onBuddyReqChange(_arg: FriendRequestNotify): any {
}
- onBuddyReqUnreadCntChange(arg: unknown): any {
+ onBuddyReqUnreadCntChange(_arg: unknown): any {
}
- onCheckBuddySettingResult(arg: unknown): any {
+ onCheckBuddySettingResult(_arg: unknown): any {
}
- onDelBatchBuddyInfos(arg: unknown): any {
+ onDelBatchBuddyInfos(_arg: unknown): any {
console.log('onDelBatchBuddyInfos not implemented', ...arguments);
}
@@ -66,12 +66,12 @@ export class NodeIKernelBuddyListener {
onDoubtBuddyReqUnreadNumChange(_num: number): void | Promise
{
}
- onNickUpdated(arg: unknown): any {
+ onNickUpdated(_arg: unknown): any {
}
- onSmartInfos(arg: unknown): any {
+ onSmartInfos(_arg: unknown): any {
}
- onSpacePermissionInfos(arg: unknown): any {
+ onSpacePermissionInfos(_arg: unknown): any {
}
}
diff --git a/src/core/packet/context/operationContext.ts b/src/core/packet/context/operationContext.ts
index d89e8899..76866709 100644
--- a/src/core/packet/context/operationContext.ts
+++ b/src/core/packet/context/operationContext.ts
@@ -124,6 +124,20 @@ export class PacketOperationContext {
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
+ async GetPttUrl(selfUid: string, node: NapProtoEncodeStructType) {
+ const req = trans.DownloadPtt.build(selfUid, node);
+ const resp = await this.context.client.sendOidbPacket(req, true);
+ const res = trans.DownloadPtt.parse(resp);
+ return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
+ }
+
+ async GetVideoUrl(selfUid: string, node: NapProtoEncodeStructType) {
+ const req = trans.DownloadVideo.build(selfUid, node);
+ const resp = await this.context.client.sendOidbPacket(req, true);
+ const res = trans.DownloadVideo.parse(resp);
+ return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
+ }
+
async GetGroupImageUrl(groupUin: number, node: NapProtoEncodeStructType) {
const req = trans.DownloadGroupImage.build(groupUin, node);
const resp = await this.context.client.sendOidbPacket(req, true);
@@ -131,6 +145,21 @@ export class PacketOperationContext {
return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
}
+ async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType) {
+ const req = trans.DownloadGroupPtt.build(groupUin, node);
+ const resp = await this.context.client.sendOidbPacket(req, true);
+ const res = trans.DownloadImage.parse(resp);
+ return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
+ }
+
+ async GetGroupVideoUrl(groupUin: number, node: NapProtoEncodeStructType) {
+ const req = trans.DownloadGroupVideo.build(groupUin, node);
+ const resp = await this.context.client.sendOidbPacket(req, true);
+ const res = trans.DownloadImage.parse(resp);
+ return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
+ }
+
+
async ImageOCR(imgUrl: string) {
const req = trans.ImageOCR.build(imgUrl);
const resp = await this.context.client.sendOidbPacket(req, true);
@@ -154,7 +183,7 @@ export class PacketOperationContext {
private async SendPreprocess(msg: PacketMsg[], groupUin: number = 0) {
const ps = msg.map((m) => {
- return m.msg.map(async(e) => {
+ return m.msg.map(async (e) => {
if (e instanceof PacketMsgReplyElement && !e.targetElems) {
this.context.logger.debug(`Cannot find reply element's targetElems, prepare to fetch it...`);
if (!e.targetPeer?.peerUid) {
@@ -222,6 +251,7 @@ export class PacketOperationContext {
const res = trans.DownloadGroupFile.parse(resp);
return `https://${res.download.downloadDns}/ftn_handler/${Buffer.from(res.download.downloadUrl).toString('hex')}/?fname=`;
}
+
async GetPrivateFileUrl(self_id: string, fileUUID: string, md5: string) {
const req = trans.DownloadPrivateFile.build(self_id, fileUUID, md5);
const resp = await this.context.client.sendOidbPacket(req, true);
@@ -229,13 +259,6 @@ export class PacketOperationContext {
return `http://${res.body?.result?.server}:${res.body?.result?.port}${res.body?.result?.url?.slice(8)}&isthumb=0`;
}
- async GetGroupPttUrl(groupUin: number, node: NapProtoEncodeStructType) {
- const req = trans.DownloadGroupPtt.build(groupUin, node);
- const resp = await this.context.client.sendOidbPacket(req, true);
- const res = trans.DownloadGroupPtt.parse(resp);
- return `https://${res.download.info.domain}${res.download.info.urlPath}${res.download.rKeyParam}`;
- }
-
async GetMiniAppAdaptShareInfo(param: MiniAppReqParams) {
const req = trans.GetMiniAppAdaptShareInfo.build(param);
const resp = await this.context.client.sendOidbPacket(req, true);
diff --git a/src/core/packet/transformer/highway/DownloadGroupVideo.ts b/src/core/packet/transformer/highway/DownloadGroupVideo.ts
new file mode 100644
index 00000000..22fe2e8e
--- /dev/null
+++ b/src/core/packet/transformer/highway/DownloadGroupVideo.ts
@@ -0,0 +1,50 @@
+import * as proto from '@/core/packet/transformer/proto';
+import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
+import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
+import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
+import { IndexNode } from '@/core/packet/transformer/proto';
+
+class DownloadGroupVideo extends PacketTransformer {
+ constructor() {
+ super();
+ }
+
+ build(groupUin: number, node: NapProtoEncodeStructType): OidbPacket {
+ const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
+ reqHead: {
+ common: {
+ requestId: 1,
+ command: 200
+ },
+ scene: {
+ requestType: 2,
+ businessType: 2,
+ sceneType: 2,
+ group: {
+ groupUin: groupUin
+ }
+ },
+ client: {
+ agentType: 2,
+ }
+ },
+ download: {
+ node: node,
+ download: {
+ video: {
+ busiType: 0,
+ sceneType: 0
+ }
+ }
+ }
+ });
+ return OidbBase.build(0x11EA, 200, body, true, false);
+ }
+
+ parse(data: Buffer) {
+ const oidbBody = OidbBase.parse(data).body;
+ return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
+ }
+}
+
+export default new DownloadGroupVideo();
diff --git a/src/core/packet/transformer/highway/DownloadPtt.ts b/src/core/packet/transformer/highway/DownloadPtt.ts
new file mode 100644
index 00000000..41ab6c7e
--- /dev/null
+++ b/src/core/packet/transformer/highway/DownloadPtt.ts
@@ -0,0 +1,51 @@
+import * as proto from '@/core/packet/transformer/proto';
+import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
+import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
+import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
+import { IndexNode } from '@/core/packet/transformer/proto';
+
+class DownloadPtt extends PacketTransformer {
+ constructor() {
+ super();
+ }
+
+ build(selfUid: string, node: NapProtoEncodeStructType): OidbPacket {
+ const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
+ reqHead: {
+ common: {
+ requestId: 1,
+ command: 200
+ },
+ scene: {
+ requestType: 1,
+ businessType: 3,
+ sceneType: 1,
+ c2C: {
+ accountType: 2,
+ targetUid: selfUid
+ },
+ },
+ client: {
+ agentType: 2,
+ }
+ },
+ download: {
+ node: node,
+ download: {
+ video: {
+ busiType: 0,
+ sceneType: 0
+ }
+ }
+ }
+ });
+ return OidbBase.build(0x126D, 200, body, true, false);
+ }
+
+ parse(data: Buffer) {
+ const oidbBody = OidbBase.parse(data).body;
+ return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
+ }
+}
+
+export default new DownloadPtt();
diff --git a/src/core/packet/transformer/highway/DownloadVideo.ts b/src/core/packet/transformer/highway/DownloadVideo.ts
new file mode 100644
index 00000000..0731b258
--- /dev/null
+++ b/src/core/packet/transformer/highway/DownloadVideo.ts
@@ -0,0 +1,51 @@
+import * as proto from '@/core/packet/transformer/proto';
+import { NapProtoEncodeStructType, NapProtoMsg } from '@napneko/nap-proto-core';
+import { OidbPacket, PacketTransformer } from '@/core/packet/transformer/base';
+import OidbBase from '@/core/packet/transformer/oidb/oidbBase';
+import { IndexNode } from '@/core/packet/transformer/proto';
+
+class DownloadVideo extends PacketTransformer {
+ constructor() {
+ super();
+ }
+
+ build(selfUid: string, node: NapProtoEncodeStructType): OidbPacket {
+ const body = new NapProtoMsg(proto.NTV2RichMediaReq).encode({
+ reqHead: {
+ common: {
+ requestId: 1,
+ command: 200
+ },
+ scene: {
+ requestType: 2,
+ businessType: 2,
+ sceneType: 1,
+ c2C: {
+ accountType: 2,
+ targetUid: selfUid
+ },
+ },
+ client: {
+ agentType: 2,
+ }
+ },
+ download: {
+ node: node,
+ download: {
+ video: {
+ busiType: 0,
+ sceneType: 0
+ }
+ }
+ }
+ });
+ return OidbBase.build(0x11E9, 200, body, true, false);
+ }
+
+ parse(data: Buffer) {
+ const oidbBody = OidbBase.parse(data).body;
+ return new NapProtoMsg(proto.NTV2RichMediaResp).decode(oidbBody);
+ }
+}
+
+export default new DownloadVideo();
diff --git a/src/core/packet/transformer/highway/index.ts b/src/core/packet/transformer/highway/index.ts
index 7ca56645..ef202112 100644
--- a/src/core/packet/transformer/highway/index.ts
+++ b/src/core/packet/transformer/highway/index.ts
@@ -13,3 +13,6 @@ export { default as UploadPrivatePtt } from './UploadPrivatePtt';
export { default as UploadPrivateVideo } from './UploadPrivateVideo';
export { default as DownloadImage } from './DownloadImage';
export { default as DownloadGroupImage } from './DownloadGroupImage';
+export { default as DownloadVideo } from './DownloadVideo';
+export { default as DownloadGroupVideo } from './DownloadGroupVideo';
+export { default as DownloadPtt } from './DownloadPtt';
\ No newline at end of file
diff --git a/src/core/packet/transformer/proto/misc/fileid.ts b/src/core/packet/transformer/proto/misc/fileid.ts
new file mode 100644
index 00000000..71426f3f
--- /dev/null
+++ b/src/core/packet/transformer/proto/misc/fileid.ts
@@ -0,0 +1,6 @@
+import { ProtoField, ScalarType } from '@napneko/nap-proto-core';
+
+export const FileId = {
+ appid: ProtoField(4, ScalarType.UINT32, true),
+ ttl: ProtoField(10, ScalarType.UINT32, true),
+};
diff --git a/src/core/services/NodeIKernelGroupService.ts b/src/core/services/NodeIKernelGroupService.ts
index babcad73..d072b394 100644
--- a/src/core/services/NodeIKernelGroupService.ts
+++ b/src/core/services/NodeIKernelGroupService.ts
@@ -249,7 +249,7 @@ export interface NodeIKernelGroupService {
reqToJoinGroup(groupCode: string, arg: unknown): void;
- setGroupShutUp(groupCode: string, shutUp: boolean): void;
+ setGroupShutUp(groupCode: string, shutUp: boolean): Promise;
getGroupShutUpMemberList(groupCode: string): Promise;
diff --git a/src/core/services/NodeIKernelMsgService.ts b/src/core/services/NodeIKernelMsgService.ts
index abd6c969..34dd8e0c 100644
--- a/src/core/services/NodeIKernelMsgService.ts
+++ b/src/core/services/NodeIKernelMsgService.ts
@@ -148,10 +148,11 @@ export interface NodeIKernelMsgService {
msgList: RawMessage[]
}>;
- //@deprecated
- getMsgs(peer: Peer, msgId: string, count: unknown, queryOrder: boolean): Promise;
+ // getMsgService/getMsgs { chatType: 2, peerUid: '975206796', privilegeFlag: 336068800 } 0 20 true
+ getMsgs(peer: Peer & { privilegeFlag: number }, msgId: string, count: number, queryOrder: boolean): Promise;
- //@deprecated
getMsgsIncludeSelf(peer: Peer, msgId: string, count: number, queryOrder: boolean): Promise;
diff --git a/src/core/types/msg.ts b/src/core/types/msg.ts
index 9e542ad8..e949bbd9 100644
--- a/src/core/types/msg.ts
+++ b/src/core/types/msg.ts
@@ -508,7 +508,8 @@ export interface RawMessage {
* 查询消息参数接口
*/
export interface QueryMsgsParams {
- chatInfo: Peer;
+ chatInfo: Peer & { privilegeFlag?: number };
+ //searchFields: number;
filterMsgType: Array<{ type: NTMsgType, subType: Array }>;
filterSendersUid: string[];
filterMsgFromTime: string;
diff --git a/src/core/types/notify.ts b/src/core/types/notify.ts
index 52a1bafc..45fe4c9b 100644
--- a/src/core/types/notify.ts
+++ b/src/core/types/notify.ts
@@ -132,18 +132,26 @@ export enum BuddyReqType {
KMEINITIATORWAITPEERCONFIRM = 13
}
+// 其中 ? 代表新版本参数
export interface FriendRequest {
- isBuddy?: boolean;
isInitiator?: boolean;
isDecide: boolean;
friendUid: string;
reqType: BuddyReqType,
reqTime: string; // 时间戳 秒
+ flag?: number; // 0
+ preGroupingId?: number; // 0
+ commFriendNum?: number; // 共同好友数
extWords: string; // 申请人填写的验证消息
isUnread: boolean;
+ isDoubt?: boolean; // 是否是可疑的好友请求
+ nameMore?: string;
friendNick: string;
sourceId: number;
- groupCode: string
+ groupCode: string;
+ isBuddy?: boolean;
+ isAgreed?: boolean;
+ relation?: number;
}
export interface FriendRequestNotify {
diff --git a/src/onebot/action/group/SetGroupWholeBan.ts b/src/onebot/action/group/SetGroupWholeBan.ts
index a4c84c44..3ff633ca 100644
--- a/src/onebot/action/group/SetGroupWholeBan.ts
+++ b/src/onebot/action/group/SetGroupWholeBan.ts
@@ -15,7 +15,10 @@ export default class SetGroupWholeBan extends OneBotAction {
async _handle(payload: Payload): Promise {
const enable = payload.enable?.toString() !== 'false';
- await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable);
+ let res = await this.core.apis.GroupApi.banGroup(payload.group_id.toString(), enable);
+ if (res.result !== 0) {
+ throw new Error(`SetGroupWholeBan failed: ${res.errMsg} ${res.result}`);
+ }
return null;
}
}
diff --git a/src/onebot/action/msg/SendMsg.ts b/src/onebot/action/msg/SendMsg.ts
index bb17807c..dcf96fac 100644
--- a/src/onebot/action/msg/SendMsg.ts
+++ b/src/onebot/action/msg/SendMsg.ts
@@ -174,9 +174,11 @@ export class SendMsgBase extends OneBotAction {
nickname: string,
}, dp: number = 0): Promise<{
finallySendElements: SendArkElement,
- res_id?: string
+ res_id?: string,
+ deleteAfterSentFiles: string[],
} | null> {
const packetMsg: PacketMsg[] = [];
+ let delFiles: string[] = [];
for (const node of messageNodes) {
if (dp >= 3) {
this.core.context.logger.logWarn('转发消息深度超过3层,将停止解析!');
@@ -192,9 +194,11 @@ export class SendMsgBase extends OneBotAction {
nickname: (node.data.nickname || node.data.name) ?? parentMeta?.nickname ?? 'QQ用户',
}, dp + 1);
sendElements = uploadReturnData?.finallySendElements ? [uploadReturnData.finallySendElements] : [];
+ delFiles.push(...(uploadReturnData?.deleteAfterSentFiles || []));
} else {
const sendElementsCreateReturn = await this.obContext.apis.MsgApi.createSendElements(OB11Data, msgPeer);
sendElements = sendElementsCreateReturn.sendElements;
+ delFiles.push(...sendElementsCreateReturn.deleteAfterSentFiles);
}
const packetMsgElements: rawMsgWithSendMsg = {
@@ -218,7 +222,8 @@ export class SendMsgBase extends OneBotAction {
const msg = (await this.core.apis.MsgApi.getMsgsByMsgId(nodeMsg.Peer, [nodeMsg.MsgId])).msgList[0];
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 开始转换 ${stringifyWithBigInt(msg)}`);
if (msg) {
- await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
+ let msgCache = await this.core.apis.FileApi.downloadRawMsgMedia([msg]);
+ delFiles.push(...msgCache);
const transformedMsg = this.core.apis.PacketApi.pkt.msgConverter.rawMsgToPacketMsg(msg, msgPeer);
this.core.context.logger.logDebug(`handleForwardedNodesPacket[PureRaw] 转换为 ${stringifyWithBigInt(transformedMsg)}`);
packetMsg.push(transformedMsg);
@@ -234,6 +239,7 @@ export class SendMsgBase extends OneBotAction {
const resid = await this.core.apis.PacketApi.pkt.operation.UploadForwardMsg(packetMsg, msgPeer.chatType === ChatType.KCHATTYPEGROUP ? +msgPeer.peerUid : 0);
const forwardJson = ForwardMsgBuilder.fromPacketMsg(resid, packetMsg, source, news, summary, prompt);
return {
+ deleteAfterSentFiles: delFiles,
finallySendElements: {
elementType: ElementType.ARK,
elementId: '',
@@ -255,7 +261,7 @@ export class SendMsgBase extends OneBotAction {
const res_id = uploadReturnData?.res_id;
const finallySendElements = uploadReturnData?.finallySendElements;
if (!finallySendElements) throw Error('转发消息失败,生成节点为空');
- const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], []).catch(() => undefined);
+ const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(msgPeer, [finallySendElements], uploadReturnData.deleteAfterSentFiles || []).catch(() => undefined);
return { message: returnMsg ?? null, res_id: res_id! };
}
diff --git a/src/onebot/api/msg.ts b/src/onebot/api/msg.ts
index ffef73b0..88b5d988 100644
--- a/src/onebot/api/msg.ts
+++ b/src/onebot/api/msg.ts
@@ -85,9 +85,6 @@ export class OneBotMsgApi {
textElement: async element => {
if (element.atType === NTMsgAtType.ATTYPEUNKNOWN) {
let text = element.content;
- if (!text.trim()) {
- return null;
- }
// 兼容 9.7.x 换行符
if (text.indexOf('\n') === -1 && text.indexOf('\r\n') === -1) {
text = text.replace(/\r/g, '\n');
@@ -100,7 +97,7 @@ export class OneBotMsgApi {
let qq: string = 'all';
if (element.atType !== NTMsgAtType.ATTYPEALL) {
const { atNtUid, atUid } = element;
- qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : atUid;
+ qq = !atUid || atUid === '0' ? await this.core.apis.UserApi.getUinByUidV2(atNtUid) : String(Number(atUid) >>> 0);
}
return {
type: OB11MessageDataType.at,
@@ -150,12 +147,31 @@ export class OneBotMsgApi {
};
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileUuid);
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
+ if (this.core.apis.PacketApi.available) {
+ let url;
+ try {
+ url = await this.core.apis.FileApi.getFileUrl(msg.chatType, msg.peerUid, element.fileUuid, element.file10MMd5)
+ } catch (error) {
+ url = '';
+ }
+ if (url) {
+ return {
+ type: OB11MessageDataType.file,
+ data: {
+ file: element.fileName,
+ file_id: element.fileUuid,
+ file_size: element.fileSize,
+ url: url,
+ },
+ }
+ }
+ }
return {
type: OB11MessageDataType.file,
data: {
file: element.fileName,
file_id: element.fileUuid,
- file_size: element.fileSize,
+ file_size: element.fileSize
},
};
},
@@ -225,17 +241,13 @@ export class OneBotMsgApi {
},
replyElement: async (element, msg) => {
- const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
const peer = {
chatType: msg.chatType,
peerUid: msg.peerUid,
guildId: '',
};
- if (!records || !element.replyMsgTime || !element.senderUidStr) {
- this.core.context.logger.logError('似乎是旧版客户端,获取不到引用的消息', element.replayMsgSeq);
- return null;
- }
+ // 创建回复数据的通用方法
const createReplyData = (msgId: string): OB11MessageData => ({
type: OB11MessageDataType.reply,
data: {
@@ -243,48 +255,96 @@ export class OneBotMsgApi {
},
});
- if (records.peerUin === '284840486' || records.peerUin === '1094950020') {
+ // 查找记录
+ const records = msg.records.find(msgRecord => msgRecord.msgId === element?.sourceMsgIdInRecords);
+
+ // 特定账号的特殊处理
+ if (records && (records.peerUin === '284840486' || records.peerUin === '1094950020')) {
return createReplyData(records.msgId);
}
- let replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(peer, element.replayMsgSeq, records.msgTime, [element.senderUidStr])).msgList;
- let replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
- if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
- this.core.context.logger.logError(
- '筛选结果,筛选消息失败,将使用Fallback-1 Seq: ',
+ // 获取消息的通用方法组
+ const tryFetchMethods = async (msgSeq: string, senderUid?: string, msgTime?: string, msgRandom?: string): Promise => {
+ try {
+ // 方法1:通过序号和时间筛选
+ if (senderUid && msgTime) {
+ const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV2(
+ peer, msgSeq, msgTime, [senderUid]
+ )).msgList;
+
+ const replyMsg = msgRandom
+ ? replyMsgList.find(msg => msg.msgRandom === msgRandom)
+ : replyMsgList.find(msg => msg.msgSeq === msgSeq);
+
+ if (replyMsg) return replyMsg;
+
+ this.core.context.logger.logWarn(`方法1查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
+ }
+
+ // 方法2:直接通过序号获取
+ const replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(
+ peer, msgSeq, 1, true, true
+ )).msgList;
+
+ const replyMsg = msgRandom
+ ? replyMsgList.find(msg => msg.msgRandom === msgRandom)
+ : replyMsgList.find(msg => msg.msgSeq === msgSeq);
+
+ if (replyMsg) return replyMsg;
+
+ this.core.context.logger.logWarn(`方法2查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
+
+ // 方法3:另一种筛选方式
+ if (senderUid) {
+ const replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(
+ peer, msgSeq, [senderUid]
+ )).msgList;
+
+ const replyMsg = msgRandom
+ ? replyMsgList.find(msg => msg.msgRandom === msgRandom)
+ : replyMsgList.find(msg => msg.msgSeq === msgSeq);
+
+ if (replyMsg) return replyMsg;
+
+ this.core.context.logger.logWarn(`方法3查询失败,序号: ${msgSeq}, 消息数: ${replyMsgList.length}`);
+ }
+
+ return undefined;
+ } catch (error) {
+ this.core.context.logger.logError('查询回复消息出错', error);
+ return undefined;
+ }
+ };
+
+ // 有记录情况下,使用完整信息查询
+ if (records && element.replyMsgTime && element.senderUidStr) {
+ const replyMsg = await tryFetchMethods(
element.replayMsgSeq,
- ',消息长度:',
- replyMsgList.length
+ element.senderUidStr,
+ records.msgTime,
+ records.msgRandom
);
- replyMsgList = (await this.core.apis.MsgApi.getMsgsBySeqAndCount(peer, element.replayMsgSeq, 1, true, true)).msgList;
- replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
+
+ if (replyMsg) {
+ return createReplyData(replyMsg.msgId);
+ }
+
+ this.core.context.logger.logError('所有查找方法均失败,获取不到带记录的引用消息', element.replayMsgSeq);
+ } else {
+ // 旧版客户端或不完整记录的情况,也尝试使用相同流程
+ this.core.context.logger.logWarn('似乎是旧版客户端,尝试仅通过序号获取引用消息', element.replayMsgSeq);
+
+ const replyMsg = await tryFetchMethods(element.replayMsgSeq);
+
+ if (replyMsg) {
+ return createReplyData(replyMsg.msgId);
+ }
+
+ this.core.context.logger.logError('所有查找方法均失败,获取不到旧客户端的引用消息', element.replayMsgSeq);
}
- if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
- this.core.context.logger.logWarn(
- '筛选消息失败,将使用Fallback-2 Seq:',
- element.replayMsgSeq,
- ',消息长度:',
- replyMsgList.length
- );
- replyMsgList = (await this.core.apis.MsgApi.queryMsgsWithFilterExWithSeqV3(peer, element.replayMsgSeq, [element.senderUidStr])).msgList;
- replyMsg = replyMsgList.find(msg => msg.msgRandom === records.msgRandom);
- }
-
-
- // 丢弃该消息段
- if (!replyMsg || records.msgRandom !== replyMsg.msgRandom) {
- this.core.context.logger.logError(
- '最终筛选结果,筛选消息失败,获取不到引用的消息 Seq: ',
- element.replayMsgSeq,
- ',消息长度:',
- replyMsgList.length
- );
- return null;
- }
- return createReplyData(replyMsg.msgId);
+ return null;
},
-
videoElement: async (element, msg, elementWrapper) => {
const peer = {
chatType: msg.chatType,
@@ -331,7 +391,17 @@ export class OneBotMsgApi {
//开始兜底
if (!videoDownUrl) {
- videoDownUrl = element.filePath;
+ if (this.core.apis.PacketApi.available) {
+ try {
+ videoDownUrl = await this.core.apis.FileApi.getVideoUrlPacket(msg.peerUid, element.fileUuid);
+ } catch (e) {
+ this.core.context.logger.logError('获取视频url失败', (e as Error).stack);
+ videoDownUrl = element.filePath;
+ }
+ } else {
+ videoDownUrl = element.filePath;
+ }
+
}
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
return {
@@ -351,6 +421,28 @@ export class OneBotMsgApi {
guildId: '',
};
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, '', element.fileName);
+ let pttUrl = '';
+ if (this.core.apis.PacketApi.available) {
+ try {
+ pttUrl = await this.core.apis.FileApi.getPttUrl(msg.peerUid, element.fileUuid);
+ } catch (e) {
+ this.core.context.logger.logError('获取语音url失败', (e as Error).stack);
+ pttUrl = element.filePath;
+ }
+ } else {
+ pttUrl = element.filePath;
+ }
+ if (pttUrl) {
+ return {
+ type: OB11MessageDataType.voice,
+ data: {
+ file: fileCode,
+ path: element.filePath,
+ url: pttUrl,
+ file_size: element.fileSize,
+ },
+ }
+ }
return {
type: OB11MessageDataType.voice,
data: {
diff --git a/src/onebot/index.ts b/src/onebot/index.ts
index 4fb0089c..baa4406a 100644
--- a/src/onebot/index.ts
+++ b/src/onebot/index.ts
@@ -334,7 +334,7 @@ export class NapCatOneBot11Adapter {
for (let i = 0; i < reqs.unreadNums; i++) {
const req = reqs.buddyReqs[i];
if (!req) continue;
- if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM)) {
+ if (!!req.isInitiator || (req.isDecide && req.reqType !== BuddyReqType.KMEINITIATORWAITPEERCONFIRM) || !req.isUnread) {
continue;
}
try {
@@ -352,7 +352,6 @@ export class NapCatOneBot11Adapter {
}
}
};
-
this.context.session
.getBuddyService()
.addKernelBuddyListener(proxiedListenerOf(buddyListener, this.context.logger));
diff --git a/src/onebot/network/websocket-server.ts b/src/onebot/network/websocket-server.ts
index e56ff884..e96157bf 100644
--- a/src/onebot/network/websocket-server.ts
+++ b/src/onebot/network/websocket-server.ts
@@ -148,7 +148,7 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter void }> {
return new Promise((resolve, reject) => {
if (process.platform !== 'win32') {
- logger.log('只有Windows平台支持命名管道');
// 非Windows平台不reject,而是返回一个空的disconnect函数
return resolve({ disconnect: () => { } });
}
@@ -25,12 +25,50 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
}, timeoutMs);
try {
- let originalStdoutWrite = process.stdout.write.bind(process.stdout);
+ const originalStdoutWrite = process.stdout.write.bind(process.stdout);
const pipeSocket = net.connect(pipePath, () => {
// 清除超时
clearTimeout(timeoutId);
+ // 优化网络性能设置
+ pipeSocket.setNoDelay(true); // 减少延迟
+
+ // 设置更高的高水位线,允许更多数据缓冲
+
logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`);
+
+ // 创建拥有更优雅背压处理的 Writable 流
+ const pipeWritable = new Writable({
+ highWaterMark: 1024 * 64, // 64KB 高水位线
+ write(chunk, encoding, callback) {
+ if (!pipeSocket.writable) {
+ // 如果管道不可写,退回到原始stdout
+ logger.log('[StdOut] 管道不可写,回退到控制台输出');
+ return originalStdoutWrite(chunk, encoding, callback);
+ }
+
+ // 尝试写入数据到管道
+ const canContinue = pipeSocket.write(chunk, encoding, () => {
+ // 数据已被发送或放入内部缓冲区
+ });
+
+ if (canContinue) {
+ // 如果返回true,表示可以继续写入更多数据
+ // 立即通知写入流可以继续
+ process.nextTick(callback);
+ } else {
+ // 如果返回false,表示内部缓冲区已满
+ // 等待drain事件再恢复写入
+ pipeSocket.once('drain', () => {
+ callback();
+ });
+ }
+ // 明确返回true,表示写入已处理
+ return true;
+ }
+ });
+
+ // 重定向stdout
process.stdout.write = (
chunk: any,
encoding?: BufferEncoding | (() => void),
@@ -40,8 +78,11 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
cb = encoding;
encoding = undefined;
}
- return pipeSocket.write(chunk, encoding as BufferEncoding, cb);
+
+ // 使用优化的writable流处理写入
+ return pipeWritable.write(chunk, encoding as BufferEncoding, cb as () => void);
};
+
// 提供断开连接的方法
const disconnect = () => {
process.stdout.write = originalStdoutWrite;
@@ -53,6 +94,7 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
resolve({ disconnect });
});
+ // 管道错误处理
pipeSocket.on('error', (err) => {
clearTimeout(timeoutId);
process.stdout.write = originalStdoutWrite;
@@ -60,11 +102,18 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
reject(err);
});
+ // 管道关闭处理
pipeSocket.on('end', () => {
process.stdout.write = originalStdoutWrite;
logger.log('命名管道连接已关闭');
});
+ // 确保在连接意外关闭时恢复stdout
+ pipeSocket.on('close', () => {
+ process.stdout.write = originalStdoutWrite;
+ logger.log('命名管道连接已关闭');
+ });
+
} catch (error) {
clearTimeout(timeoutId);
logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error);