Compare commits

..

4 Commits

Author SHA1 Message Date
手瓜一十雪
fb20b2e16c fix: coerce 2025-04-17 22:17:35 +08:00
Mlikiowa
9df7c341a9 release: v4.7.30 2025-04-17 10:07:28 +00:00
手瓜一十雪
7c113d6e04 fix: 一些问题 2025-04-17 18:07:07 +08:00
Mlikiowa
a6f22167ff release: v4.7.29 2025-04-17 09:59:29 +00:00
13 changed files with 120 additions and 74 deletions

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.7.28",
"version": "4.7.30",
"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",

View File

@@ -1,14 +1,26 @@
import { z } from 'zod';
const boolean = () => z.preprocess(
val => typeof val === 'string' && (val.toLowerCase() === 'false' || val === '0') ? false : Boolean(val),
val => val === null || val === undefined
? val
: typeof val === 'string' && (val.toLowerCase() === 'false' || val === '0')
? false
: Boolean(val),
z.boolean()
);
const number = () => z.preprocess(
val => typeof val !== 'number' ? Number(val) : val,
val => val === null || val === undefined
? val
: typeof val !== 'number' ? Number(val) : val,
z.number()
);
const string = () => z.preprocess(
val => typeof val !== 'string' ? String(val) : val,
val => val === null || val === undefined
? val
: typeof val !== 'string' ? String(val) : val,
z.string()
);
export const coerce = { boolean, number, string };

View File

@@ -1 +1 @@
export const napCatVersion = '4.7.28';
export const napCatVersion = '4.7.30';

View File

@@ -32,7 +32,7 @@ export class OB11Response {
export abstract class OneBotAction<PayloadType, ReturnDataType> {
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
core: NapCatCore;
payloadSchema?: z.ZodType<unknown> = undefined;
payloadSchema?: z.ZodType<PayloadType, z.ZodTypeDef, unknown> = undefined;
obContext: NapCatOneBot11Adapter;
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
@@ -40,15 +40,15 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
this.core = core;
}
protected async check(payload: unknown): Promise<BaseCheckResult> {
protected async check(payload: unknown): Promise<BaseCheckResult & { parsedPayload?: PayloadType }> {
if (!this.payloadSchema) {
return { valid: true };
return { valid: true, parsedPayload: payload as PayloadType };
}
try {
// 使用 zod 验证并转换数据
this.payloadSchema.parse(payload);
return { valid: true };
// 使用 zod 验证并转换数据,并返回解析后的数据
const parsedPayload = this.payloadSchema.parse(payload) as PayloadType;
return { valid: true, parsedPayload };
} catch (error) {
if (error instanceof z.ZodError) {
const errorMessages = error.errors.map(e =>
@@ -66,13 +66,13 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
}
}
public async handle(payload: PayloadType, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
public async handle(payload: unknown, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload);
if (!result.valid) {
return OB11Response.error(result.message, 400);
return OB11Response.error(result.message!, 400);
}
try {
const resData = await this._handle(payload, adaptername, config);
const resData = await this._handle(result.parsedPayload as PayloadType, adaptername, config);
return OB11Response.ok(resData);
} catch (e: unknown) {
this.core.context.logger.logError('发生错误', e);
@@ -80,13 +80,13 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
}
}
public async websocketHandle(payload: PayloadType, echo: unknown, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
public async websocketHandle(payload: unknown, echo: unknown, adaptername: string, config: NetworkAdapterConfig): Promise<OB11Return<ReturnDataType | null>> {
const result = await this.check(payload);
if (!result.valid) {
return OB11Response.error(result.message, 1400, echo);
return OB11Response.error(result.message!, 1400, echo);
}
try {
const resData = await this._handle(payload, adaptername, config);
const resData = await this._handle(result.parsedPayload as PayloadType, adaptername, config);
return OB11Response.ok(resData, echo);
} catch (e: unknown) {
this.core.context.logger.logError('发生错误', e);

View File

@@ -4,18 +4,13 @@ import { ActionName } from '@/onebot/action/router';
// 未验证
export class GoCQHTTPSendForwardMsgBase extends SendMsgBase {
protected override async check(payload: OB11PostSendMsg) {
override async _handle(payload: OB11PostSendMsg) {
if (payload.messages) payload.message = normalize(payload.messages);
return super.check(payload);
return super._handle(payload);
}
}
export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase {
override actionName = ActionName.GoCQHTTP_SendForwardMsg;
protected override async check(payload: OB11PostSendMsg) {
if (payload.messages) payload.message = normalize(payload.messages);
return super.check(payload);
}
}
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase {
override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;

View File

@@ -1,16 +1,15 @@
import { ContextMode, SendMsgBase } from '@/onebot/action/msg/SendMsg';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { ActionName } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数
class SendGroupMsg extends SendMsgBase {
override actionName = ActionName.SendGroupMsg;
override contextMode: ContextMode = ContextMode.Group;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
override async _handle(payload: OB11PostSendMsg) {
delete payload.user_id;
payload.message_type = 'group';
return super.check(payload);
return super._handle(payload);
}
}

View File

@@ -91,7 +91,7 @@ function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType
export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
contextMode = ContextMode.Normal;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult & { parsedPayload?: OB11PostSendMsg }> {
const messages = normalize(payload.message);
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
if (nodeElementLength > 0 && nodeElementLength != messages.length) {
@@ -100,7 +100,7 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
};
}
return { valid: true };
return { valid: true , parsedPayload: payload };
}
async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {

View File

@@ -1,15 +1,14 @@
import { ContextMode, SendMsgBase } from './SendMsg';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { ActionName } from '@/onebot/action/router';
import { OB11PostSendMsg } from '@/onebot/types';
// 未检测参数
class SendPrivateMsg extends SendMsgBase {
override actionName = ActionName.SendPrivateMsg;
override contextMode: ContextMode = ContextMode.Private;
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
payload.message_type = 'private';
return super.check(payload);
override async _handle(payload: OB11PostSendMsg) {
if (payload.messages) payload.message = payload.messages;
return super._handle(payload);
}
}

View File

@@ -1,9 +1,9 @@
import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
import { ActionName } from '@/onebot/action/router';
export abstract class GetPacketStatusDepends<PT, RT> extends OneBotAction<PT, RT> {
protected override async check(payload: PT): Promise<BaseCheckResult>{
protected override async check(payload: PT) {
if (!this.core.apis.PacketApi.available) {
return {
valid: false,

View File

@@ -33,7 +33,7 @@ import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
import { sleep } from '@/common/helper';
import { downloadFFmpegIfNotExists } from '@/common/download-ffmpeg';
import { FFmpegService } from '@/common/ffmpeg';
import { connectToNamedPipe } from '@/shell/pipe';
// NapCat Shell App ES 入口文件
async function handleUncaughtExceptions(logger: LogWrapper) {
process.on('uncaughtException', (err) => {
@@ -313,9 +313,10 @@ export async function NCoreInitShell() {
const pathWrapper = new NapCatPathWrapper();
const logger = new LogWrapper(pathWrapper.logsPath);
handleUncaughtExceptions(logger);
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
downloadFFmpegIfNotExists(logger).then(({ path, reset }) => {
if (reset && path) {
FFmpegService.setFfmpegPath(path,logger);
FFmpegService.setFfmpegPath(path, logger);
}
}).catch(e => {
logger.logError('[Ffmpeg] Error:', e);

View File

@@ -1,36 +1,2 @@
import { NCoreInitShell } from './base';
import * as net from 'net';
import * as process from 'process';
if (process.platform === 'win32') {
const pid = process.pid;
const pipePath = `\\\\.\\pipe\\NapCat_${pid}`;
try {
const pipeSocket = net.connect(pipePath, () => {
console.log(`已连接到命名管道: ${pipePath}`);
process.stdout.write = (
chunk: any,
encoding?: BufferEncoding | (() => void),
cb?: () => void
): boolean => {
if (typeof encoding === 'function') {
cb = encoding;
encoding = undefined;
}
return pipeSocket.write(chunk, encoding as BufferEncoding, cb);
};
console.log(`stdout 已重定向到命名管道: ${pipePath}`);
});
pipeSocket.on('error', (err) => {
console.log(`连接命名管道 ${pipePath} 时出错:`, err);
});
pipeSocket.on('end', () => {
console.log('命名管道连接已关闭');
});
} catch (error) {
console.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error);
}
}
NCoreInitShell();

74
src/shell/pipe.ts Normal file
View File

@@ -0,0 +1,74 @@
import { LogWrapper } from '@/common/log';
import * as net from 'net';
import * as process from 'process';
/**
* 连接到命名管道并重定向stdout
* @param logger 日志记录器
* @param timeoutMs 连接超时时间(毫秒)默认5000ms
* @returns Promise连接成功时resolve失败时reject
*/
export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000): Promise<{ disconnect: () => void }> {
return new Promise((resolve, reject) => {
if (process.platform !== 'win32') {
logger.log('只有Windows平台支持命名管道');
// 非Windows平台不reject而是返回一个空的disconnect函数
return resolve({ disconnect: () => { } });
}
const pid = process.pid;
const pipePath = `\\\\.\\pipe\\NapCat_${pid}`;
// 设置连接超时
const timeoutId = setTimeout(() => {
reject(new Error(`连接命名管道超时: ${pipePath}`));
}, timeoutMs);
try {
let originalStdoutWrite = process.stdout.write.bind(process.stdout);
const pipeSocket = net.connect(pipePath, () => {
// 清除超时
clearTimeout(timeoutId);
logger.log(`[StdOut] 已重定向到命名管道: ${pipePath}`);
process.stdout.write = (
chunk: any,
encoding?: BufferEncoding | (() => void),
cb?: () => void
): boolean => {
if (typeof encoding === 'function') {
cb = encoding;
encoding = undefined;
}
return pipeSocket.write(chunk, encoding as BufferEncoding, cb);
};
// 提供断开连接的方法
const disconnect = () => {
process.stdout.write = originalStdoutWrite;
pipeSocket.end();
logger.log(`已手动断开命名管道连接: ${pipePath}`);
};
// 返回成功和断开连接的方法
resolve({ disconnect });
});
pipeSocket.on('error', (err) => {
clearTimeout(timeoutId);
process.stdout.write = originalStdoutWrite;
logger.log(`连接命名管道 ${pipePath} 时出错:`, err);
reject(err);
});
pipeSocket.on('end', () => {
process.stdout.write = originalStdoutWrite;
logger.log('命名管道连接已关闭');
});
} catch (error) {
clearTimeout(timeoutId);
logger.log(`尝试连接命名管道 ${pipePath} 时发生异常:`, error);
reject(error);
}
});
}