mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
Compare commits
11 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
fb20b2e16c | ||
![]() |
9df7c341a9 | ||
![]() |
7c113d6e04 | ||
![]() |
a6f22167ff | ||
![]() |
d49e69735a | ||
![]() |
eca73eae18 | ||
![]() |
d3a34dfdf9 | ||
![]() |
623188d884 | ||
![]() |
f093f52792 | ||
![]() |
d53607a118 | ||
![]() |
5f637e064a |
@@ -4,7 +4,7 @@
|
|||||||
"name": "NapCatQQ",
|
"name": "NapCatQQ",
|
||||||
"slug": "NapCat.Framework",
|
"slug": "NapCat.Framework",
|
||||||
"description": "高性能的 OneBot 11 协议实现",
|
"description": "高性能的 OneBot 11 协议实现",
|
||||||
"version": "4.7.25",
|
"version": "4.7.30",
|
||||||
"icon": "./logo.png",
|
"icon": "./logo.png",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
"name": "napcat",
|
"name": "napcat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "4.7.25",
|
"version": "4.7.30",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
"build:universal": "npm run build:webui && vite build --mode universal || exit 1",
|
||||||
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
"build:framework": "npm run build:webui && vite build --mode framework || exit 1",
|
||||||
|
@@ -1,14 +1,26 @@
|
|||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
const boolean = () => z.preprocess(
|
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()
|
z.boolean()
|
||||||
);
|
);
|
||||||
|
|
||||||
const number = () => z.preprocess(
|
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()
|
z.number()
|
||||||
);
|
);
|
||||||
|
|
||||||
const string = () => z.preprocess(
|
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()
|
z.string()
|
||||||
);
|
);
|
||||||
|
|
||||||
export const coerce = { boolean, number, string };
|
export const coerce = { boolean, number, string };
|
@@ -281,28 +281,72 @@ export async function downloadFFmpeg(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export async function downloadFFmpegIfNotExists(log:LogWrapper) {
|
|
||||||
|
/**
|
||||||
|
* 检查系统PATH环境变量中是否存在指定可执行文件
|
||||||
|
* @param executable 可执行文件名
|
||||||
|
* @returns 如果找到返回完整路径,否则返回null
|
||||||
|
*/
|
||||||
|
function findExecutableInPath(executable: string): string | null {
|
||||||
|
// 仅适用于Windows系统
|
||||||
|
if (os.platform() !== 'win32') return null;
|
||||||
|
|
||||||
|
// 获取PATH环境变量
|
||||||
|
const pathEnv = process.env['PATH'] || '';
|
||||||
|
const pathDirs = pathEnv.split(';');
|
||||||
|
|
||||||
|
// 检查每个目录
|
||||||
|
for (const dir of pathDirs) {
|
||||||
|
if (!dir) continue;
|
||||||
|
try {
|
||||||
|
const filePath = path.join(dir, executable);
|
||||||
|
if (fs.existsSync(filePath)) {
|
||||||
|
return filePath;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function downloadFFmpegIfNotExists(log: LogWrapper) {
|
||||||
// 仅限Windows
|
// 仅限Windows
|
||||||
if (os.platform() !== 'win32') {
|
if (os.platform() !== 'win32') {
|
||||||
return {
|
return {
|
||||||
path: null,
|
path: null,
|
||||||
isExist: false
|
reset: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
const ffmpegInPath = findExecutableInPath('ffmpeg.exe');
|
||||||
|
const ffprobeInPath = findExecutableInPath('ffprobe.exe');
|
||||||
|
|
||||||
|
if (ffmpegInPath && ffprobeInPath) {
|
||||||
|
const ffmpegDir = path.dirname(ffmpegInPath);
|
||||||
|
return {
|
||||||
|
path: ffmpegDir,
|
||||||
|
reset: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果环境变量中没有,检查项目目录中是否存在
|
||||||
const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
const currentPath = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const ffmpeg_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffmpeg.exe'));
|
const ffmpeg_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffmpeg.exe'));
|
||||||
const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe'));
|
const ffprobe_exist = fs.existsSync(path.join(currentPath, 'ffmpeg', 'ffprobe.exe'));
|
||||||
|
|
||||||
if (!ffmpeg_exist || !ffprobe_exist) {
|
if (!ffmpeg_exist || !ffprobe_exist) {
|
||||||
await downloadFFmpeg('./ffmpeg', './cache', (percentage: number, message: string) => {
|
await downloadFFmpeg(path.join(currentPath, 'ffmpeg'), path.join(currentPath, 'cache'), (percentage: number, message: string) => {
|
||||||
log.log(`[Ffmpeg] [Download] ${percentage}% - ${message}`);
|
log.log(`[FFmpeg] [Download] ${percentage}% - ${message}`);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
path: path.join(currentPath, 'ffmpeg'),
|
path: path.join(currentPath, 'ffmpeg'),
|
||||||
isExist: false
|
reset: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: path.join(currentPath, 'ffmpeg'),
|
path: path.join(currentPath, 'ffmpeg'),
|
||||||
isExist: true
|
reset: true
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -7,6 +7,7 @@ import { fileTypeFromFile } from 'file-type';
|
|||||||
import imageSize from 'image-size';
|
import imageSize from 'image-size';
|
||||||
import { fileURLToPath } from 'node:url';
|
import { fileURLToPath } from 'node:url';
|
||||||
import { platform } from 'node:os';
|
import { platform } from 'node:os';
|
||||||
|
import { LogWrapper } from './log';
|
||||||
const currentPath = dirname(fileURLToPath(import.meta.url));
|
const currentPath = dirname(fileURLToPath(import.meta.url));
|
||||||
const execFileAsync = promisify(execFile);
|
const execFileAsync = promisify(execFile);
|
||||||
const getFFmpegPath = (tool: string): string => {
|
const getFFmpegPath = (tool: string): string => {
|
||||||
@@ -19,16 +20,14 @@ const getFFmpegPath = (tool: string): string => {
|
|||||||
};
|
};
|
||||||
export let FFMPEG_CMD = getFFmpegPath('ffmpeg');
|
export let FFMPEG_CMD = getFFmpegPath('ffmpeg');
|
||||||
export let FFPROBE_CMD = getFFmpegPath('ffprobe');
|
export let FFPROBE_CMD = getFFmpegPath('ffprobe');
|
||||||
console.log('[Info] ffmpeg:', FFMPEG_CMD);
|
|
||||||
console.log('[Info] ffprobe:', FFPROBE_CMD);
|
|
||||||
export class FFmpegService {
|
export class FFmpegService {
|
||||||
// 确保目标目录存在
|
// 确保目标目录存在
|
||||||
public static setFfmpegPath(ffmpegPath: string): void {
|
public static setFfmpegPath(ffmpegPath: string,logger:LogWrapper): void {
|
||||||
if (platform() === 'win32') {
|
if (platform() === 'win32') {
|
||||||
FFMPEG_CMD = path.join(ffmpegPath, 'ffmpeg.exe');
|
FFMPEG_CMD = path.join(ffmpegPath, 'ffmpeg.exe');
|
||||||
FFPROBE_CMD = path.join(ffmpegPath, 'ffprobe.exe');
|
FFPROBE_CMD = path.join(ffmpegPath, 'ffprobe.exe');
|
||||||
console.log('[Info] ffmpeg:', FFMPEG_CMD);
|
logger.log('[Check] ffmpeg:', FFMPEG_CMD);
|
||||||
console.log('[Info] ffprobe:', FFPROBE_CMD);
|
logger.log('[Check] ffprobe:', FFPROBE_CMD);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static ensureDirExists(filePath: string): void {
|
private static ensureDirExists(filePath: string): void {
|
||||||
|
@@ -1 +1 @@
|
|||||||
export const napCatVersion = '4.7.25';
|
export const napCatVersion = '4.7.30';
|
||||||
|
4
src/core/external/appid.json
vendored
4
src/core/external/appid.json
vendored
@@ -242,5 +242,9 @@
|
|||||||
"3.2.17-34231": {
|
"3.2.17-34231": {
|
||||||
"appid": 537279245,
|
"appid": 537279245,
|
||||||
"qua": "V1_LNX_NQ_3.2.17_34231_GW_B"
|
"qua": "V1_LNX_NQ_3.2.17_34231_GW_B"
|
||||||
|
},
|
||||||
|
"9.9.19-34362": {
|
||||||
|
"appid": 537279260,
|
||||||
|
"qua": "V1_WIN_NQ_9.9.19_34362_GW_B"
|
||||||
}
|
}
|
||||||
}
|
}
|
4
src/core/external/offset.json
vendored
4
src/core/external/offset.json
vendored
@@ -326,5 +326,9 @@
|
|||||||
"3.2.17-34231-arm64": {
|
"3.2.17-34231-arm64": {
|
||||||
"send": "770CDC0",
|
"send": "770CDC0",
|
||||||
"recv": "77106F0"
|
"recv": "77106F0"
|
||||||
|
},
|
||||||
|
"9.9.19-34362-x64":{
|
||||||
|
"send": "3BD80D0",
|
||||||
|
"recv": "3BDC8D0"
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -38,10 +38,12 @@ export async function NCoreInitFramework(
|
|||||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
|
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
|
||||||
downloadFFmpegIfNotExists(logger).then(({ path, isExist }) => {
|
downloadFFmpegIfNotExists(logger).then(({ path, reset }) => {
|
||||||
if (!isExist && path) {
|
if (reset && path) {
|
||||||
FFmpegService.setFfmpegPath(path);
|
FFmpegService.setFfmpegPath(path,logger);
|
||||||
}
|
}
|
||||||
|
}).catch(e => {
|
||||||
|
logger.logError('[Ffmpeg] Error:', e);
|
||||||
});
|
});
|
||||||
//直到登录成功后,执行下一步
|
//直到登录成功后,执行下一步
|
||||||
const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => {
|
const selfInfo = await new Promise<SelfInfo>((resolveSelfInfo) => {
|
||||||
|
@@ -32,7 +32,7 @@ export class OB11Response {
|
|||||||
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
||||||
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
actionName: typeof ActionName[keyof typeof ActionName] = ActionName.Unknown;
|
||||||
core: NapCatCore;
|
core: NapCatCore;
|
||||||
payloadSchema?: z.ZodType<unknown> = undefined;
|
payloadSchema?: z.ZodType<PayloadType, z.ZodTypeDef, unknown> = undefined;
|
||||||
obContext: NapCatOneBot11Adapter;
|
obContext: NapCatOneBot11Adapter;
|
||||||
|
|
||||||
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
constructor(obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||||
@@ -40,15 +40,15 @@ export abstract class OneBotAction<PayloadType, ReturnDataType> {
|
|||||||
this.core = core;
|
this.core = core;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async check(payload: unknown): Promise<BaseCheckResult> {
|
protected async check(payload: unknown): Promise<BaseCheckResult & { parsedPayload?: PayloadType }> {
|
||||||
if (!this.payloadSchema) {
|
if (!this.payloadSchema) {
|
||||||
return { valid: true };
|
return { valid: true, parsedPayload: payload as PayloadType };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 使用 zod 验证并转换数据
|
// 使用 zod 验证并转换数据,并返回解析后的数据
|
||||||
this.payloadSchema.parse(payload);
|
const parsedPayload = this.payloadSchema.parse(payload) as PayloadType;
|
||||||
return { valid: true };
|
return { valid: true, parsedPayload };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof z.ZodError) {
|
if (error instanceof z.ZodError) {
|
||||||
const errorMessages = error.errors.map(e =>
|
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);
|
const result = await this.check(payload);
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
return OB11Response.error(result.message, 400);
|
return OB11Response.error(result.message!, 400);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const resData = await this._handle(payload, adaptername, config);
|
const resData = await this._handle(result.parsedPayload as PayloadType, adaptername, config);
|
||||||
return OB11Response.ok(resData);
|
return OB11Response.ok(resData);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
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);
|
const result = await this.check(payload);
|
||||||
if (!result.valid) {
|
if (!result.valid) {
|
||||||
return OB11Response.error(result.message, 1400, echo);
|
return OB11Response.error(result.message!, 1400, echo);
|
||||||
}
|
}
|
||||||
try {
|
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);
|
return OB11Response.ok(resData, echo);
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
this.core.context.logger.logError('发生错误', e);
|
this.core.context.logger.logError('发生错误', e);
|
||||||
|
@@ -4,18 +4,13 @@ import { ActionName } from '@/onebot/action/router';
|
|||||||
|
|
||||||
// 未验证
|
// 未验证
|
||||||
export class GoCQHTTPSendForwardMsgBase extends SendMsgBase {
|
export class GoCQHTTPSendForwardMsgBase extends SendMsgBase {
|
||||||
protected override async check(payload: OB11PostSendMsg) {
|
override async _handle(payload: OB11PostSendMsg) {
|
||||||
if (payload.messages) payload.message = normalize(payload.messages);
|
if (payload.messages) payload.message = normalize(payload.messages);
|
||||||
return super.check(payload);
|
return super._handle(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
export class GoCQHTTPSendForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
||||||
override actionName = ActionName.GoCQHTTP_SendForwardMsg;
|
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 {
|
export class GoCQHTTPSendPrivateForwardMsg extends GoCQHTTPSendForwardMsgBase {
|
||||||
override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;
|
override actionName = ActionName.GoCQHTTP_SendPrivateForwardMsg;
|
||||||
|
@@ -1,16 +1,15 @@
|
|||||||
import { ContextMode, SendMsgBase } from '@/onebot/action/msg/SendMsg';
|
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';
|
import { OB11PostSendMsg } from '@/onebot/types';
|
||||||
|
|
||||||
// 未检测参数
|
// 未检测参数
|
||||||
class SendGroupMsg extends SendMsgBase {
|
class SendGroupMsg extends SendMsgBase {
|
||||||
override actionName = ActionName.SendGroupMsg;
|
override actionName = ActionName.SendGroupMsg;
|
||||||
override contextMode: ContextMode = ContextMode.Group;
|
override contextMode: ContextMode = ContextMode.Group;
|
||||||
|
override async _handle(payload: OB11PostSendMsg) {
|
||||||
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
|
||||||
delete payload.user_id;
|
delete payload.user_id;
|
||||||
payload.message_type = 'group';
|
payload.message_type = 'group';
|
||||||
return super.check(payload);
|
return super._handle(payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -91,7 +91,7 @@ function getSpecialMsgNum(payload: OB11PostSendMsg, msgType: OB11MessageDataType
|
|||||||
export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
||||||
contextMode = ContextMode.Normal;
|
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 messages = normalize(payload.message);
|
||||||
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
|
const nodeElementLength = getSpecialMsgNum(payload, OB11MessageDataType.node);
|
||||||
if (nodeElementLength > 0 && nodeElementLength != messages.length) {
|
if (nodeElementLength > 0 && nodeElementLength != messages.length) {
|
||||||
@@ -100,7 +100,7 @@ export class SendMsgBase extends OneBotAction<OB11PostSendMsg, ReturnDataType> {
|
|||||||
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
message: '转发消息不能和普通消息混在一起发送,转发需要保证message只有type为node的元素',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return { valid: true };
|
return { valid: true , parsedPayload: payload };
|
||||||
}
|
}
|
||||||
|
|
||||||
async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
async _handle(payload: OB11PostSendMsg): Promise<ReturnDataType> {
|
||||||
|
@@ -1,15 +1,14 @@
|
|||||||
import { ContextMode, SendMsgBase } from './SendMsg';
|
import { ContextMode, SendMsgBase } from './SendMsg';
|
||||||
import { ActionName, BaseCheckResult } from '@/onebot/action/router';
|
import { ActionName } from '@/onebot/action/router';
|
||||||
import { OB11PostSendMsg } from '@/onebot/types';
|
import { OB11PostSendMsg } from '@/onebot/types';
|
||||||
|
|
||||||
// 未检测参数
|
// 未检测参数
|
||||||
class SendPrivateMsg extends SendMsgBase {
|
class SendPrivateMsg extends SendMsgBase {
|
||||||
override actionName = ActionName.SendPrivateMsg;
|
override actionName = ActionName.SendPrivateMsg;
|
||||||
override contextMode: ContextMode = ContextMode.Private;
|
override contextMode: ContextMode = ContextMode.Private;
|
||||||
|
override async _handle(payload: OB11PostSendMsg) {
|
||||||
protected override async check(payload: OB11PostSendMsg): Promise<BaseCheckResult> {
|
if (payload.messages) payload.message = payload.messages;
|
||||||
payload.message_type = 'private';
|
return super._handle(payload);
|
||||||
return super.check(payload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -1,9 +1,9 @@
|
|||||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
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> {
|
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) {
|
if (!this.core.apis.PacketApi.available) {
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
|
@@ -33,7 +33,7 @@ import { NodeIO3MiscListener } from '@/core/listeners/NodeIO3MiscListener';
|
|||||||
import { sleep } from '@/common/helper';
|
import { sleep } from '@/common/helper';
|
||||||
import { downloadFFmpegIfNotExists } from '@/common/download-ffmpeg';
|
import { downloadFFmpegIfNotExists } from '@/common/download-ffmpeg';
|
||||||
import { FFmpegService } from '@/common/ffmpeg';
|
import { FFmpegService } from '@/common/ffmpeg';
|
||||||
|
import { connectToNamedPipe } from '@/shell/pipe';
|
||||||
// NapCat Shell App ES 入口文件
|
// NapCat Shell App ES 入口文件
|
||||||
async function handleUncaughtExceptions(logger: LogWrapper) {
|
async function handleUncaughtExceptions(logger: LogWrapper) {
|
||||||
process.on('uncaughtException', (err) => {
|
process.on('uncaughtException', (err) => {
|
||||||
@@ -313,11 +313,14 @@ export async function NCoreInitShell() {
|
|||||||
const pathWrapper = new NapCatPathWrapper();
|
const pathWrapper = new NapCatPathWrapper();
|
||||||
const logger = new LogWrapper(pathWrapper.logsPath);
|
const logger = new LogWrapper(pathWrapper.logsPath);
|
||||||
handleUncaughtExceptions(logger);
|
handleUncaughtExceptions(logger);
|
||||||
downloadFFmpegIfNotExists(logger).then(({ path, isExist }) => {
|
await connectToNamedPipe(logger).catch(e => logger.logError('命名管道连接失败', e));
|
||||||
if (!isExist && path) {
|
downloadFFmpegIfNotExists(logger).then(({ path, reset }) => {
|
||||||
FFmpegService.setFfmpegPath(path);
|
if (reset && path) {
|
||||||
|
FFmpegService.setFfmpegPath(path, logger);
|
||||||
}
|
}
|
||||||
});
|
}).catch(e => {
|
||||||
|
logger.logError('[Ffmpeg] Error:', e);
|
||||||
|
});
|
||||||
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
const basicInfoWrapper = new QQBasicInfoWrapper({ logger });
|
||||||
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
|
const wrapper = loadQQWrapper(basicInfoWrapper.getFullQQVesion());
|
||||||
|
|
||||||
|
@@ -1,36 +1,2 @@
|
|||||||
import { NCoreInitShell } from './base';
|
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();
|
NCoreInitShell();
|
74
src/shell/pipe.ts
Normal file
74
src/shell/pipe.ts
Normal 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
Reference in New Issue
Block a user