Compare commits

...

8 Commits

Author SHA1 Message Date
手瓜一十雪
303a74f8fd feat: 背压问题 2025-05-07 17:14:57 +08:00
Mlikiowa
0b7f126ce1 release: v4.7.48 2025-05-07 08:21:34 +00:00
Clansty
308b5c027f fix: at 变成负数 2025-05-07 03:46:17 +08:00
手瓜一十雪
ed3abc4b43 feat 2025-05-04 21:11:34 +08:00
Mlikiowa
87ecb3b380 release: v4.7.47 2025-05-03 14:27:49 +00:00
手瓜一十雪
7e31763a25 fix 2025-05-03 22:26:41 +08:00
Mlikiowa
c9df57d16a release: v4.7.46 2025-05-03 08:08:25 +00:00
手瓜一十雪
3d0f8ee657 fix 2025-05-03 16:06:51 +08:00
8 changed files with 66 additions and 11 deletions

View File

@@ -1,8 +1,9 @@
<img src="https://napneko.github.io/assets/newnewlogo.png" width = "305" height = "411" alt="NapCat" align=right />
<div align="center">
# NapCat
![NapCatQQ](https://socialify.git.ci/NapNeko/NapCatQQ/image?font=Jost&logo=https%3A%2F%2Fnapneko.github.io%2Fassets%2Fnewlogo.png&name=1&owner=1&pattern=Diagonal+Stripes&stargazers=1&theme=Auto)
_Modern protocol-side framework implemented based on NTQQ._

Binary file not shown.

View File

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

View File

@@ -2,7 +2,7 @@
"name": "napcat",
"private": true,
"type": "module",
"version": "4.7.45",
"version": "4.7.48",
"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 +1 @@
export const napCatVersion = '4.7.45';
export const napCatVersion = '4.7.48';

View File

@@ -100,7 +100,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,

View File

@@ -39,8 +39,11 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter<WebsocketSer
wsClient.close();
return;
}
//鉴权
this.authorize(this.config.token, wsClient, wsReq);
// 鉴权 close 不会立刻销毁 当前返回可避免挂载message事件 close 并未立刻关闭 而是存在timer操作后关闭
// 引发高危漏洞
if (!this.authorize(this.config.token, wsClient, wsReq)) {
return;
}
const paramUrl = wsReq.url?.indexOf('?') !== -1 ? wsReq.url?.substring(0, wsReq.url?.indexOf('?')) : wsReq.url;
const isApiConnect = paramUrl === '/api' || paramUrl === '/api/';
if (!isApiConnect) {
@@ -145,15 +148,16 @@ export class OB11WebSocketServerAdapter extends IOB11NetworkAdapter<WebsocketSer
}
private authorize(token: string | undefined, wsClient: WebSocket, wsReq: IncomingMessage) {
if (!token || token.length == 0) return;//客户端未设置密钥
if (!token || token.length == 0) return true;//客户端未设置密钥
const QueryClientToken = urlParse.parse(wsReq?.url || '', true).query['access_token'];
const HeaderClientToken = wsReq.headers.authorization?.split('Bearer ').pop() || '';
const ClientToken = typeof (QueryClientToken) === 'string' && QueryClientToken !== '' ? QueryClientToken : HeaderClientToken;
if (ClientToken === token) {
return;
return true;
}
wsClient.send(JSON.stringify(OB11Response.res(null, 'failed', 1403, 'token验证失败')));
wsClient.close();
return false;
}
private checkStateAndReply<T>(data: T, wsClient: WebSocket) {

View File

@@ -1,6 +1,7 @@
import { LogWrapper } from '@/common/log';
import * as net from 'net';
import * as process from 'process';
import { Writable } from 'stream';
/**
* 连接到命名管道并重定向stdout
@@ -25,12 +26,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 +79,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 +95,7 @@ export function connectToNamedPipe(logger: LogWrapper, timeoutMs: number = 5000)
resolve({ disconnect });
});
// 管道错误处理
pipeSocket.on('error', (err) => {
clearTimeout(timeoutId);
process.stdout.write = originalStdoutWrite;
@@ -60,11 +103,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);