From 381d320967764f885bbb1b3b598322df53859cff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Sun, 12 Oct 2025 15:50:34 +0800 Subject: [PATCH 01/14] feat: Add image and record stream download actions Introduces BaseDownloadStream as a shared base class for streaming file downloads. Adds DownloadFileImageStream and DownloadFileRecordStream for image and audio file streaming with support for format conversion. Refactors DownloadFileStream to use the new base class, and updates action registration and router to include the new actions. --- src/onebot/action/index.ts | 4 + src/onebot/action/router.ts | 2 + .../action/stream/BaseDownloadStream.ts | 99 +++++++++++++++++++ .../action/stream/DownloadFileImageStream.ts | 60 +++++++++++ .../action/stream/DownloadFileRecordStream.ts | 96 ++++++++++++++++++ .../action/stream/DownloadFileStream.ts | 94 ++---------------- 6 files changed, 268 insertions(+), 87 deletions(-) create mode 100644 src/onebot/action/stream/BaseDownloadStream.ts create mode 100644 src/onebot/action/stream/DownloadFileImageStream.ts create mode 100644 src/onebot/action/stream/DownloadFileRecordStream.ts diff --git a/src/onebot/action/index.ts b/src/onebot/action/index.ts index f78ceed0..b77ada21 100644 --- a/src/onebot/action/index.ts +++ b/src/onebot/action/index.ts @@ -132,6 +132,8 @@ import { SetGroupAlbumMediaLike } from './extends/SetGroupAlbumMediaLike'; import { DelGroupAlbumMedia } from './extends/DelGroupAlbumMedia'; import { CleanStreamTempFile } from './stream/CleanStreamTempFile'; import { DownloadFileStream } from './stream/DownloadFileStream'; +import { DownloadFileRecordStream } from './stream/DownloadFileRecordStream'; +import { DownloadFileImageStream } from './stream/DownloadFileImageStream'; import { TestDownloadStream } from './stream/TestStreamDownload'; import { UploadFileStream } from './stream/UploadFileStream'; @@ -140,6 +142,8 @@ export function createActionMap(obContext: NapCatOneBot11Adapter, core: NapCatCo const actionHandlers = [ new CleanStreamTempFile(obContext, core), new DownloadFileStream(obContext, core), + new DownloadFileRecordStream(obContext, core), + new DownloadFileImageStream(obContext, core), new TestDownloadStream(obContext, core), new UploadFileStream(obContext, core), new DelGroupAlbumMedia(obContext, core), diff --git a/src/onebot/action/router.ts b/src/onebot/action/router.ts index bb6de99f..41872cf8 100644 --- a/src/onebot/action/router.ts +++ b/src/onebot/action/router.ts @@ -17,6 +17,8 @@ export const ActionName = { TestDownloadStream: 'test_download_stream', UploadFileStream: 'upload_file_stream', DownloadFileStream: 'download_file_stream', + DownloadFileRecordStream: 'download_file_record_stream', + DownloadFileImageStream: 'download_file_image_stream', DelGroupAlbumMedia: 'del_group_album_media', SetGroupAlbumMediaLike: 'set_group_album_media_like', diff --git a/src/onebot/action/stream/BaseDownloadStream.ts b/src/onebot/action/stream/BaseDownloadStream.ts new file mode 100644 index 00000000..7a7379ea --- /dev/null +++ b/src/onebot/action/stream/BaseDownloadStream.ts @@ -0,0 +1,99 @@ +import { OneBotAction, OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; +import { StreamPacket, StreamStatus } from './StreamBasic'; +import fs from 'fs'; +import { FileNapCatOneBotUUID } from '@/common/file-uuid'; + +export interface ResolvedFileInfo { + downloadPath: string; + fileName: string; + fileSize: number; +} + +export interface DownloadResult { + // 文件信息 + file_name?: string; + file_size?: number; + chunk_size?: number; + + // 分片数据 + index?: number; + data?: string; + size?: number; + progress?: number; + base64_size?: number; + + // 完成信息 + total_chunks?: number; + total_bytes?: number; + message?: string; + data_type?: 'file_info' | 'file_chunk' | 'file_complete'; + + // 可选扩展字段 + width?: number; + height?: number; + out_format?: string; +} + +export abstract class BaseDownloadStream extends OneBotAction> { + protected async resolveDownload(file?: string): Promise { + const target = file || ''; + let downloadPath = ''; + let fileName = ''; + let fileSize = 0; + + const contextMsgFile = FileNapCatOneBotUUID.decode(target); + if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) { + const { peer, msgId, elementId } = contextMsgFile; + downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); + const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList + .find(msg => msg.msgId === msgId); + const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); + const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; + if (!mixElementInner) throw new Error('element not found'); + fileSize = parseInt(mixElementInner.fileSize?.toString() ?? '0'); + fileName = mixElementInner.fileName ?? ''; + return { downloadPath, fileName, fileSize }; + } + + const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(target); + if (contextModelIdFile && contextModelIdFile.modelId) { + const { peer, modelId } = contextModelIdFile; + downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); + return { downloadPath, fileName, fileSize }; + } + + const searchResult = (await this.core.apis.FileApi.searchForFile([target])); + if (searchResult) { + downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); + fileSize = parseInt(searchResult.fileSize); + fileName = searchResult.fileName; + return { downloadPath, fileName, fileSize }; + } + + throw new Error('file not found'); + } + + protected async streamFileChunks(req: OneBotRequestToolkit, streamPath: string, chunkSize: number, chunkDataType: string): Promise<{ totalChunks: number; totalBytes: number }> + { + const stats = await fs.promises.stat(streamPath); + const totalSize = stats.size; + const readStream = fs.createReadStream(streamPath, { highWaterMark: chunkSize }); + let chunkIndex = 0; + let bytesRead = 0; + for await (const chunk of readStream) { + const base64Chunk = (chunk as Buffer).toString('base64'); + bytesRead += (chunk as Buffer).length; + await req.send({ + type: StreamStatus.Stream, + data_type: chunkDataType, + index: chunkIndex, + data: base64Chunk, + size: (chunk as Buffer).length, + progress: Math.round((bytesRead / totalSize) * 100), + base64_size: base64Chunk.length + } as unknown as StreamPacket); + chunkIndex++; + } + return { totalChunks: chunkIndex, totalBytes: bytesRead }; + } +} diff --git a/src/onebot/action/stream/DownloadFileImageStream.ts b/src/onebot/action/stream/DownloadFileImageStream.ts new file mode 100644 index 00000000..a80985fd --- /dev/null +++ b/src/onebot/action/stream/DownloadFileImageStream.ts @@ -0,0 +1,60 @@ +import { ActionName } from '@/onebot/action/router'; +import { OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; +import { Static, Type } from '@sinclair/typebox'; +import { NetworkAdapterConfig } from '@/onebot/config/config'; +import { StreamPacket, StreamStatus } from './StreamBasic'; +import fs from 'fs'; +import { imageSizeFallBack } from '@/image-size'; +import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; + +const SchemaData = Type.Object({ + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), + chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })) // 默认64KB分块 +}); + +type Payload = Static; + +export class DownloadFileImageStream extends BaseDownloadStream { + override actionName = ActionName.DownloadFileImageStream; + override payloadSchema = SchemaData; + override useStream = true; + + async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { + try { + payload.file ||= payload.file_id || ''; + const chunkSize = payload.chunk_size || 64 * 1024; + + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); + + const stats = await fs.promises.stat(downloadPath); + const totalSize = fileSize || stats.size; + const { width, height } = await imageSizeFallBack(downloadPath); + + // 发送文件信息(与 DownloadFileStream 对齐,但包含宽高) + await req.send({ + type: StreamStatus.Stream, + data_type: 'file_info', + file_name: fileName, + file_size: totalSize, + chunk_size: chunkSize, + width, + height + }); + + const { totalChunks, totalBytes } = await this.streamFileChunks(req, downloadPath, chunkSize, 'file_chunk'); + + // 返回完成状态(与 DownloadFileStream 对齐) + return { + type: StreamStatus.Response, + data_type: 'file_complete', + total_chunks: totalChunks, + total_bytes: totalBytes, + message: 'Download completed' + }; + + } catch (error) { + throw new Error(`Download failed: ${(error as Error).message}`); + } + } +} diff --git a/src/onebot/action/stream/DownloadFileRecordStream.ts b/src/onebot/action/stream/DownloadFileRecordStream.ts new file mode 100644 index 00000000..e0fdec1e --- /dev/null +++ b/src/onebot/action/stream/DownloadFileRecordStream.ts @@ -0,0 +1,96 @@ + +import { ActionName } from '@/onebot/action/router'; +import { OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; +import { Static, Type } from '@sinclair/typebox'; +import { NetworkAdapterConfig } from '@/onebot/config/config'; +import { StreamPacket, StreamStatus } from './StreamBasic'; +import fs from 'fs'; +import { decode } from 'silk-wasm'; +import { FFmpegService } from '@/common/ffmpeg'; +import { BaseDownloadStream } from './BaseDownloadStream'; + +const out_format = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac']; + +const SchemaData = Type.Object({ + file: Type.Optional(Type.String()), + file_id: Type.Optional(Type.String()), + chunk_size: Type.Optional(Type.Number({ default: 64 * 1024 })), // 默认64KB分块 + out_format: Type.Optional(Type.String()) +}); + +type Payload = Static; + +import { DownloadResult } from './BaseDownloadStream'; + +export class DownloadFileRecordStream extends BaseDownloadStream { + override actionName = ActionName.DownloadFileRecordStream; + override payloadSchema = SchemaData; + override useStream = true; + + async _handle(payload: Payload, _adaptername: string, _config: NetworkAdapterConfig, req: OneBotRequestToolkit): Promise> { + try { + payload.file ||= payload.file_id || ''; + const chunkSize = payload.chunk_size || 64 * 1024; + + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); + + // 处理输出格式转换 + let streamPath = downloadPath; + if (payload.out_format && typeof payload.out_format === 'string') { + if (!out_format.includes(payload.out_format)) { + throw new Error('转换失败 out_format 字段可能格式不正确'); + } + + const pcmFile = `${downloadPath}.pcm`; + const outputFile = `${downloadPath}.${payload.out_format}`; + + try { + // 如果已存在目标文件则跳过转换 + await fs.promises.access(outputFile); + streamPath = outputFile; + } catch { + // 尝试解码 silk 到 pcm 再用 ffmpeg 转换 + await this.decodeFile(downloadPath, pcmFile); + await FFmpegService.convertFile(pcmFile, outputFile, payload.out_format); + streamPath = outputFile; + } + } + + const stats = await fs.promises.stat(streamPath); + const totalSize = fileSize || stats.size; + + await req.send({ + type: StreamStatus.Stream, + data_type: 'file_info', + file_name: fileName, + file_size: totalSize, + chunk_size: chunkSize, + out_format: payload.out_format + }); + + const { totalChunks, totalBytes } = await this.streamFileChunks(req, streamPath, chunkSize, 'file_chunk'); + + return { + type: StreamStatus.Response, + data_type: 'file_complete', + total_chunks: totalChunks, + total_bytes: totalBytes, + message: 'Download completed' + }; + + } catch (error) { + throw new Error(`Download failed: ${(error as Error).message}`); + } + } + + private async decodeFile(inputFile: string, outputFile: string): Promise { + try { + const inputData = await fs.promises.readFile(inputFile); + const decodedData = await decode(inputData, 24000); + await fs.promises.writeFile(outputFile, Buffer.from(decodedData.data)); + } catch (error) { + console.error('Error decoding file:', error); + throw error; + } + } +} diff --git a/src/onebot/action/stream/DownloadFileStream.ts b/src/onebot/action/stream/DownloadFileStream.ts index 2c0095ef..7062aab3 100644 --- a/src/onebot/action/stream/DownloadFileStream.ts +++ b/src/onebot/action/stream/DownloadFileStream.ts @@ -1,10 +1,10 @@ import { ActionName } from '@/onebot/action/router'; -import { OneBotAction, OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; +import { OneBotRequestToolkit } from '@/onebot/action/OneBotAction'; import { Static, Type } from '@sinclair/typebox'; import { NetworkAdapterConfig } from '@/onebot/config/config'; import { StreamPacket, StreamStatus } from './StreamBasic'; import fs from 'fs'; -import { FileNapCatOneBotUUID } from '@/common/file-uuid'; +import { BaseDownloadStream, DownloadResult } from './BaseDownloadStream'; const SchemaData = Type.Object({ file: Type.Optional(Type.String()), file_id: Type.Optional(Type.String()), @@ -13,28 +13,7 @@ const SchemaData = Type.Object({ type Payload = Static; -// 下载结果类型 -interface DownloadResult { - // 文件信息 - file_name?: string; - file_size?: number; - chunk_size?: number; - - // 分片数据 - index?: number; - data?: string; - size?: number; - progress?: number; - base64_size?: number; - - // 完成信息 - total_chunks?: number; - total_bytes?: number; - message?: string; - data_type?: 'file_info' | 'file_chunk' | 'file_complete'; -} - -export class DownloadFileStream extends OneBotAction> { +export class DownloadFileStream extends BaseDownloadStream { override actionName = ActionName.DownloadFileStream; override payloadSchema = SchemaData; override useStream = true; @@ -43,50 +22,12 @@ export class DownloadFileStream extends OneBotAction msg.msgId === msgId); - const mixElement = rawMessage?.elements.find(e => e.elementId === elementId); - const mixElementInner = mixElement?.videoElement ?? mixElement?.fileElement ?? mixElement?.pttElement ?? mixElement?.picElement; - if (!mixElementInner) throw new Error('element not found'); - fileSize = parseInt(mixElementInner.fileSize?.toString() ?? '0'); - fileName = mixElementInner.fileName ?? ''; - } - //群文件模式 - else if (FileNapCatOneBotUUID.decodeModelId(payload.file)) { - const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file); - if (contextModelIdFile && contextModelIdFile.modelId) { - const { peer, modelId } = contextModelIdFile; - downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); - } - } - //搜索名字模式 - else { - const searchResult = (await this.core.apis.FileApi.searchForFile([payload.file])); - if (searchResult) { - downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, parseInt(searchResult.fileSize)); - fileSize = parseInt(searchResult.fileSize); - fileName = searchResult.fileName; - } - } + const { downloadPath, fileName, fileSize } = await this.resolveDownload(payload.file); - if (!downloadPath) { - throw new Error('file not found'); - } - - // 获取文件大小 const stats = await fs.promises.stat(downloadPath); const totalSize = fileSize || stats.size; - // 发送文件信息 await req.send({ type: StreamStatus.Stream, data_type: 'file_info', @@ -95,34 +36,13 @@ export class DownloadFileStream extends OneBotAction Date: Sun, 12 Oct 2025 20:36:31 +0800 Subject: [PATCH 02/14] Update helper.ts (#1311) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复256级以后等级清零的问题。 --- src/common/helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/common/helper.ts b/src/common/helper.ts index e97de9b2..0213be69 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -163,8 +163,8 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined export function calcQQLevel(level?: QQLevel) { if (!level) return 0; - const { crownNum, sunNum, moonNum, starNum } = level; - return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; + const { penguinNum, crownNum, sunNum, moonNum, starNum } = level; + return penguinNum * 256 + crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; } export function stringifyWithBigInt(obj: any) { @@ -204,4 +204,4 @@ export function parseAppidFromMajor(nodeMajor: string): string | undefined { } return undefined; -} \ No newline at end of file +} From 98ef642cd1282b899901c70a8817a194481cbd2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E7=91=BE?= <74231782+sj817@users.noreply.github.com> Date: Sun, 12 Oct 2025 21:13:56 +0800 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=E5=8F=96=E6=B6=88=E7=BE=A4?= =?UTF-8?q?=E7=B2=BE=E5=8D=8E=E6=8E=A5=E5=8F=A3=E6=94=AF=E6=8C=81=E4=BC=A0?= =?UTF-8?q?=E9=80=92=E5=8E=9F=E5=A7=8B=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1. onebot v11标准: 传递message_id - 2. 通过官方http接口获取到的group_id、msg_random、msg_seq 二者任选其一 --- src/onebot/action/group/DelEssenceMsg.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/onebot/action/group/DelEssenceMsg.ts b/src/onebot/action/group/DelEssenceMsg.ts index ccb20a58..1fdfbc67 100644 --- a/src/onebot/action/group/DelEssenceMsg.ts +++ b/src/onebot/action/group/DelEssenceMsg.ts @@ -4,7 +4,10 @@ import { MessageUnique } from '@/common/message-unique'; import { Static, Type } from '@sinclair/typebox'; const SchemaData = Type.Object({ - message_id: Type.Union([Type.Number(), Type.String()]), + message_id: Type.Optional(Type.Union([Type.Number(), Type.String()])), + msg_seq: Type.Optional(Type.String()), + msg_random: Type.Optional(Type.String()), + group_id: Type.Optional(Type.String()), }); type Payload = Static; @@ -13,6 +16,20 @@ export default class DelEssenceMsg extends OneBotAction { override payloadSchema = SchemaData; async _handle(payload: Payload): Promise { + // 如果直接提供了 msg_seq, msg_random, group_id,优先使用 + if (payload.msg_seq && payload.msg_random && payload.group_id) { + return await this.core.apis.GroupApi.removeGroupEssenceBySeq( + payload.group_id, + payload.msg_random, + payload.msg_seq, + ); + } + + // 如果没有 message_id,则必须提供 msg_seq, msg_random, group_id + if (!payload.message_id) { + throw new Error('必须提供 message_id 或者同时提供 msg_seq, msg_random, group_id'); + } + const msg = MessageUnique.getMsgIdAndPeerByShortId(+payload.message_id); if (!msg) { const data = this.core.apis.GroupApi.essenceLRU.getValue(+payload.message_id); From 0129188739a2fa5862a0928840e54e2c7bc37970 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E7=91=BE?= <74231782+sj817@users.noreply.github.com> Date: Tue, 14 Oct 2025 09:38:18 +0800 Subject: [PATCH 04/14] fix: #1315 --- src/webui/src/utils/url.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webui/src/utils/url.ts b/src/webui/src/utils/url.ts index 0df403b0..fdeebebd 100644 --- a/src/webui/src/utils/url.ts +++ b/src/webui/src/utils/url.ts @@ -91,7 +91,9 @@ export const createUrl = ( url.searchParams.set(key, search[key]) } } - return url.toString() + + /** 进行url解码 对特殊字符进行处理 */ + return decodeURIComponent(url.toString()) } /** From 02980c4d1a22bd0a848f9bab2bae67d2efb73fc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Tue, 14 Oct 2025 20:32:41 +0800 Subject: [PATCH 05/14] feat: Update QQ version data and add macOS ARM64 native module Updated qqnt.json, appid.json, and offset.json to support QQ version 9.9.22-40768 and related Linux/macOS versions. Modified calcQQLevel in helper.ts to remove penguinNum from calculation. Added MoeHoo.darwin.arm64.new.node for macOS ARM64 support and updated LiteLoaderWrapper.zip binary. --- external/LiteLoaderWrapper.zip | Bin 76834 -> 76835 bytes launcher/qqnt.json | 10 +++++----- src/common/helper.ts | 6 ++++-- src/core/external/appid.json | 12 ++++++++++++ src/core/external/offset.json | 16 ++++++++++++++++ .../packet/MoeHoo.darwin.arm64.new.node | Bin 0 -> 74856 bytes 6 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 src/native/packet/MoeHoo.darwin.arm64.new.node diff --git a/external/LiteLoaderWrapper.zip b/external/LiteLoaderWrapper.zip index 1e8419c6ce19fc26464ee047c68a3ea5fda00f2c..b5cf06275fae5015215d36a657d6af6aac66f90f 100644 GIT binary patch delta 2637 zcmaJ@2~1R16#e)AfniZVK^$fV7#LOu6jVfoibgOZNJrCZtV`8cS=^%prCKD^QZ`v$ z@mWhnp&~F8MZ~2pBQ7ZsTp9r(8o`CAECz7F)+&AfzX4~Uk>taj^Ut~4yYCJKO`^gk zQI-^`RijW0D8weyUqtOcUs{_KEdu5o&RAxS+%9cR_PbWz z_2yYsLEEd*f68MU6LL;&%zE=MfNM^CxYD`iP}17sqQ-6F@RmDkYR)u-cFT)qM*G!w zuB*!mu4;KQt-UOI&f_ON9<66fccY-B{@_W<=-`^&2QS$b?Hqqdxh`3}cz@xj+mS8b z&4^dX@y*`Rrt@k=XEJlDehAxKEjxO$`0BgoCtjk0gbnuRL)Go+K_lKjo=}a(^w-B+ zD?SmCT;@30KctQ|vfNv%=+m?@@ozbGnAk)EiTiATVLHg84&y{N4F6L}dboMJhusy@ zKfcxcU&FT*+kMRS-r^n>WJtVYiimG_<*Xf3f#aR*j1L-4dl(-|Y2&}GFs9by#3?{6 z`=l5VwbGIsIFnbp6GTyIlLe3YY2KemORcp*f2X+&k6Er2qj~B6f@icRJ(q@X$hgl^ z>qBM;&A4K_4f-lu{)rfw*xnY2dhu-HeRbCg%p=Bcw@Kf6nv z-)avW?zmiQjsiO-QPI|pNS&hxUE@ST?dH9H!$oU*1E}CgFKZ>xZnaDBbq=g>w6`4ihXh@A=a;zK7Cbm|fzdK>$1`2Q5?`_d6TDMhs3*)j!KM4m(P~&YCU?0~HRFfQ(xk$Dr!% z_QpmG5Dre4fQmg9htEnNP(c=jforLOYu_C>LJD$pG=9{8dafN&r|9thc*S6)&g|^n z0(+kn!r8b)-rmSQ+Ji4jVGG-mf_-H$$!Taj6N7kPq%!DOn$(tNI;#GSRw^%@fArDV+0B?5P0ZPhn02#qbcx>sf0=Cal zfaHz@T69Q&P}5-t5veo}=kn;Z0xIrw1bc#+DF{}3gfizJl~ZOSk2)0N;|iEKte0Yg zjJK39sIJs*(*O6;pw!U@!c!&qFK4hpE}FqZk9Q))8kY(6vuDPK{_}iCb-I(+y<|6ye3?5BAltwMFv%nua9loK_}5lP;rX{+(%FU zKuPqP8vKtEgzqHm>q2H?RV{<8Zj8liU390j%Y~?`1ayL)y2k~ioc|qxj7sWd{448^ zX_z%I=$ppD(aLuvbu4Ybb*`|2y?hsYxj`hi=>dbTx9B^)??zI(*NWe|kqMmf1TS-k zg>2MQeB2%U1>f!NB*pKaGpMEAb6{-!JxGGh0w%$`JRp=)cQQ!R{?nGoY19i ztndUkVGct*i6e_121WOJe=J7``85D;B|D4XgLG)c;|TfW06c#j%;zFm9m!rrmeqxE z5W-y+e}v|9d(4DS6ZP77N=4fE)I!JVP=S(PeinG4Ex|=&0B^D-p7V&IwFO^n;RP1P Kvu(+LIm~~hQ^*tm delta 2536 zcmaJ@2~bm46#e)Au;x6Hf+*Idh#TU9RmZwigfZ>}(JESH>8J%P4Lw?ROXsb?L`u=}G4LD`ylbdtzx%ZxX-}@6|y{MpG zq?ci(8~E9pj+M@BzBE>g$GCw(R9f*;i(B1b5{gWf%)htuvA#Pxsa;r%(cagq&?5SQ(kuOHRj@T43Ds{49>FJ0^Uc(|! zi>0g5ZC>TnBd0I_Zpv`kUDz%B--~(S+Bqp}w)m99%C1CjJhbSHukrcpm{mIE9=o-D zf}ic~Gi^`8HC)yZ_DH%mbcy18E^|o{>U#9hb>p?n?5jy0Ni|bPTsbnKVf_;}KE-C; zcNr?9LvDWVG?Sv&>JcXOy~)LuNn5$1+ROVd1x9oWSu@df*Rw-;8%$Nk)Z!!7W8aG2 zau?DI-PD{ltKHhRHg)?i3eV}WX~R$GQ(oPpw!6NGy4O4@J;p!&zULZS<(`+tp>+Yn4=x)JzuGu;MpgImx+lMNZPS^QLmex| zN7>j`jVt$X0fu=c@zz6O)j&hR#9pwP^&3zzKB}U>uNdM5>lB@i8Psg zg^aP8J^rt+;>@tl1$E^%sB2a~(d;8HwP-}vX3_rPqqL^xtUp<5oyd-)?b_<8s!xax zBWfn-qq^pihT2Dh7Uas%N&WCHJjg$jlYcO|IP{*(t zGz1Q0*-=Y>G=+60+ngpG4)Z@3lvpmt#a`fyE~nX890OhXAWA$?KGa@RK<=&S#qUd5 zq2A)^@Q(H5NLAVeHB|SZB&YLfHq_dDHHAubul>YQm(>oSVW!%J=Gl1H8?7mJu5q3j z>vwY;N3qPm0?pRpd)H{SyAqhd zrKRy9p}e~{cQ9Ru#}!hD;Z*DCmtYx8;uKj7dY$1ZbS&gQCL{Sb5;QSW_5t1PLmI2k z!l`o7?m#(dOz19NPPz-+z{9hx>oy3AZYs!J1+T<|G*$K^gY?bU%F7R!HLiVIZ`K*!XqBK;2AjbE!Eq7zSQ!TbII z{#p&elvki8ye0vW;|2{my7pcKnSYFrk)yby$`3;q_7_osH+n%JWnbcBH&x(=UND<& zIg7`6L*Gu-#TM0foX79HL4uF+2UFPaZEup~nE=PK%W4EST5wg~q43B$to9`b=Y~%^ z(aM()w+Zk}miYsx`GS_+)5L#@xA;L6E4?L9P}iI0OS;*QFuuKm%lsh5e&>CD3BgHA z;?Ryp5+8h3rFcjXcw@)z;9&iQnEcXU{sXh^WXJ#j diff --git a/launcher/qqnt.json b/launcher/qqnt.json index 6bba1895..047c6bbb 100644 --- a/launcher/qqnt.json +++ b/launcher/qqnt.json @@ -1,9 +1,9 @@ { "name": "qq-chat", - "verHash": "cc326038", - "version": "9.9.21-39038", - "linuxVersion": "3.2.19-39038", - "linuxVerHash": "c773cdf7", + "verHash": "c50d6326", + "version": "9.9.22-40768", + "linuxVersion": "3.2.20-40768", + "linuxVerHash": "ab90fdfa", "private": true, "description": "QQ", "productName": "QQ", @@ -17,7 +17,7 @@ "qd": "externals/devtools/cli/index.js" }, "main": "./loadNapCat.js", - "buildVersion": "39038", + "buildVersion": "40768", "isPureShell": true, "isByteCodeShell": true, "platform": "win32", diff --git a/src/common/helper.ts b/src/common/helper.ts index 0213be69..9c049059 100644 --- a/src/common/helper.ts +++ b/src/common/helper.ts @@ -163,8 +163,10 @@ export function getQQVersionConfigPath(exePath: string = ''): string | undefined export function calcQQLevel(level?: QQLevel) { if (!level) return 0; - const { penguinNum, crownNum, sunNum, moonNum, starNum } = level; - return penguinNum * 256 + crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; + //const { penguinNum, crownNum, sunNum, moonNum, starNum } = level; + const { crownNum, sunNum, moonNum, starNum } = level + //没补类型 + return crownNum * 64 + sunNum * 16 + moonNum * 4 + starNum; } export function stringifyWithBigInt(obj: any) { diff --git a/src/core/external/appid.json b/src/core/external/appid.json index 60b6085f..20a98896 100644 --- a/src/core/external/appid.json +++ b/src/core/external/appid.json @@ -390,5 +390,17 @@ "9.9.22-40362": { "appid": 537314212, "qua": "V1_WIN_NQ_9.9.22_40362_GW_B" + }, + "3.2.20-40768": { + "appid": 537319840, + "qua": "V1_LNX_NQ_3.2.20_40768_GW_B" + }, + "9.9.22-40768": { + "appid": 537319804, + "qua": "V1_WIN_NQ_9.9.22_40768_GW_B" + }, + "6.9.82-40768": { + "appid": 537319829, + "qua": "V1_MAC_NQ_6.9.82_40768_GW_B" } } \ No newline at end of file diff --git a/src/core/external/offset.json b/src/core/external/offset.json index f6806035..523071ba 100644 --- a/src/core/external/offset.json +++ b/src/core/external/offset.json @@ -514,5 +514,21 @@ "9.9.22-40362-x64": { "send": "31C0EB8", "recv": "31C465C" + }, + "3.2.20-40768-x64": { + "send": "B69CFE0", + "recv": "B6A0A60" + }, + "9.9.22-40768-x64": { + "send": "31C1838", + "recv": "31C4FDC" + }, + "3.2.20-40768-arm64": { + "send": "7D49B18", + "recv": "7D4D4A8" + }, + "6.9.82-40768-arm64": { + "send": "202A198", + "recv": "202B718" } } \ No newline at end of file diff --git a/src/native/packet/MoeHoo.darwin.arm64.new.node b/src/native/packet/MoeHoo.darwin.arm64.new.node new file mode 100644 index 0000000000000000000000000000000000000000..6c2c3e40be36c4ae427104d01675abf6189d21bf GIT binary patch literal 74856 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 Date: Tue, 14 Oct 2025 12:33:43 +0000 Subject: [PATCH 06/14] release: v4.8.120 --- manifest.json | 2 +- package.json | 2 +- src/common/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 88000501..68d904bc 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "4.8.119", + "version": "4.8.120", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index afe892cc..6b64ffb1 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "4.8.119", + "version": "4.8.120", "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/version.ts b/src/common/version.ts index e43456b8..89344aed 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '4.8.119'; +export const napCatVersion = '4.8.120'; From cbacc89907c22e36fb07180b52505d0b021e0960 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Wed, 15 Oct 2025 22:31:08 +0800 Subject: [PATCH 07/14] Add appid and offset entries for version 40824 Added new entries for versions 3.2.20-40824, 9.9.22-40824, and 6.9.82-40824 in appid.json and corresponding offset values for x64 and arm64 architectures in offset.json. --- src/core/external/appid.json | 12 ++++++++++++ src/core/external/offset.json | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/src/core/external/appid.json b/src/core/external/appid.json index 20a98896..b3bb773e 100644 --- a/src/core/external/appid.json +++ b/src/core/external/appid.json @@ -402,5 +402,17 @@ "6.9.82-40768": { "appid": 537319829, "qua": "V1_MAC_NQ_6.9.82_40768_GW_B" + }, + "3.2.20-40824": { + "appid": 537319840, + "qua": "V1_LNX_NQ_3.2.20_40824_GW_B" + }, + "9.9.22-40824": { + "appid": 537319804, + "qua": "V1_WIN_NQ_9.9.22_40824_GW_B" + }, + "6.9.82-40824": { + "appid": 537319829, + "qua": "V1_MAC_NQ_6.9.82_40824_GW_B" } } \ No newline at end of file diff --git a/src/core/external/offset.json b/src/core/external/offset.json index 523071ba..93ea4bea 100644 --- a/src/core/external/offset.json +++ b/src/core/external/offset.json @@ -530,5 +530,17 @@ "6.9.82-40768-arm64": { "send": "202A198", "recv": "202B718" + }, + "9.9.22-40824-x64": { + "send": "31C1838", + "recv": "31C4FDC" + }, + "3.2.20-40824-arm64": { + "send": "7D49B18", + "recv": "7D4D4A8" + }, + "6.9.82-40824-arm64": { + "send": "202A198", + "recv": "202B718" } } \ No newline at end of file From 4f8c3206588ffca0782253ccd198cb62764fad4e Mon Sep 17 00:00:00 2001 From: Mlikiowa Date: Wed, 15 Oct 2025 14:31:35 +0000 Subject: [PATCH 08/14] release: v4.8.121 --- manifest.json | 2 +- package.json | 2 +- src/common/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index 68d904bc..a7b4c2fd 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "4.8.120", + "version": "4.8.121", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index 6b64ffb1..dfeea841 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "4.8.120", + "version": "4.8.121", "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/version.ts b/src/common/version.ts index 89344aed..03404edc 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '4.8.120'; +export const napCatVersion = '4.8.121'; From 42abc2b9cb70d3fd21ec54d5848a712dfc393a54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Thu, 16 Oct 2025 21:13:50 +0800 Subject: [PATCH 09/14] fix: #1286 --- src/common/health.ts | 342 +++++++++++++++++++------------------------ 1 file changed, 147 insertions(+), 195 deletions(-) diff --git a/src/common/health.ts b/src/common/health.ts index 166abaeb..139cffa8 100644 --- a/src/common/health.ts +++ b/src/common/health.ts @@ -9,41 +9,50 @@ export interface ResourceConfig { healthCheckInterval?: number; /** 最大健康检查失败次数,超过后永久禁用,默认 5 次 */ maxHealthCheckFailures?: number; - /** 资源名称(用于日志) */ - name?: string; - /** 测试参数(用于健康检查) */ - testArgs?: T; /** 健康检查函数,如果提供则优先使用此函数进行健康检查 */ healthCheckFn?: (...args: T) => Promise; + /** 测试参数(用于健康检查) */ + testArgs?: T; } -interface ResourceState { - config: ResourceConfig; +interface ResourceTypeState { + /** 资源配置 */ + config: { + resourceFn: (...args: any[]) => Promise; + healthCheckFn?: (...args: any[]) => Promise; + disableTime: number; + maxRetries: number; + healthCheckInterval: number; + maxHealthCheckFailures: number; + testArgs?: any[]; + }; + /** 是否启用 */ isEnabled: boolean; + /** 禁用截止时间 */ disableUntil: number; + /** 当前重试次数 */ currentRetries: number; + /** 健康检查失败次数 */ healthCheckFailureCount: number; + /** 是否永久禁用 */ isPermanentlyDisabled: boolean; - lastError?: Error; + /** 上次健康检查时间 */ lastHealthCheckTime: number; - registrationKey: string; + /** 成功次数统计 */ + successCount: number; + /** 失败次数统计 */ + failureCount: number; } export class ResourceManager { - private resources = new Map>(); + private resourceTypes = new Map(); private destroyed = false; - private healthCheckTimer?: NodeJS.Timeout; - private readonly HEALTH_CHECK_TASK_INTERVAL = 5000; // 5秒执行一次健康检查任务 - - constructor() { - this.startHealthCheckTask(); - } /** - * 注册资源(注册即调用,重复注册只实际注册一次) + * 调用资源(自动注册或复用已有配置) */ - async register( - key: string, + async callResource( + type: string, config: ResourceConfig, ...args: T ): Promise { @@ -51,81 +60,64 @@ export class ResourceManager { throw new Error('ResourceManager has been destroyed'); } - const registrationKey = this.generateRegistrationKey(key, config); + // 获取或创建资源类型状态 + let state = this.resourceTypes.get(type); - // 检查是否已经注册 - if (this.resources.has(key)) { - const existingState = this.resources.get(key)!; - - // 如果是相同的配置,直接调用 - if (existingState.registrationKey === registrationKey) { - return this.callResource(key, ...args); - } - - // 配置不同,清理旧的并重新注册 - this.unregister(key); - } - - // 创建新的资源状态 - const state: ResourceState = { - config: { - disableTime: 30000, - maxRetries: 3, - healthCheckInterval: 60000, - maxHealthCheckFailures: 5, - name: key, - ...config - }, - isEnabled: true, - disableUntil: 0, - currentRetries: 0, - healthCheckFailureCount: 0, - isPermanentlyDisabled: false, - lastHealthCheckTime: 0, - registrationKey - }; - - this.resources.set(key, state); - - // 注册即调用 - return await this.callResource(key, ...args); - } - - /** - * 调用资源 - */ - async callResource(key: string, ...args: T): Promise { - const state = this.resources.get(key) as ResourceState | undefined; if (!state) { - throw new Error(`Resource ${key} not registered`); + // 首次注册 + state = { + config: { + resourceFn: config.resourceFn as (...args: any[]) => Promise, + healthCheckFn: config.healthCheckFn as ((...args: any[]) => Promise) | undefined, + disableTime: config.disableTime ?? 30000, + maxRetries: config.maxRetries ?? 3, + healthCheckInterval: config.healthCheckInterval ?? 60000, + maxHealthCheckFailures: config.maxHealthCheckFailures ?? 20, + testArgs: config.testArgs as any[] | undefined, + }, + isEnabled: true, + disableUntil: 0, + currentRetries: 0, + healthCheckFailureCount: 0, + isPermanentlyDisabled: false, + lastHealthCheckTime: 0, + successCount: 0, + failureCount: 0, + }; + this.resourceTypes.set(type, state); } + // 在调用前检查是否需要进行健康检查 + await this.checkAndPerformHealthCheck(state); + + // 检查资源状态 if (state.isPermanentlyDisabled) { - throw new Error(`Resource ${key} is permanently disabled due to repeated health check failures`); + throw new Error(`Resource type '${type}' is permanently disabled (success: ${state.successCount}, failure: ${state.failureCount})`); } - if (!this.isResourceAvailable(key)) { + if (!this.isResourceAvailable(type)) { const disableUntilDate = new Date(state.disableUntil).toISOString(); - throw new Error(`Resource ${key} is currently disabled until ${disableUntilDate}`); + throw new Error(`Resource type '${type}' is currently disabled until ${disableUntilDate} (success: ${state.successCount}, failure: ${state.failureCount})`); } + // 调用资源 try { - const result = await state.config.resourceFn(...args); + const result = await config.resourceFn(...args); this.onResourceSuccess(state); return result; } catch (error) { - this.onResourceFailure(state, error as Error); + this.onResourceFailure(state); throw error; } } /** - * 检查资源是否可用 + * 检查资源类型是否可用 */ - isResourceAvailable(key: string): boolean { - const state = this.resources.get(key); + isResourceAvailable(type: string): boolean { + const state = this.resourceTypes.get(type); if (!state) { - return false; + return true; // 未注册的资源类型视为可用 } if (state.isPermanentlyDisabled || !state.isEnabled) { @@ -136,128 +128,97 @@ export class ResourceManager { } /** - * 注销资源 + * 获取资源类型统计信息 */ - unregister(key: string): boolean { - return this.resources.delete(key); + getResourceStats(type: string): { successCount: number; failureCount: number; isEnabled: boolean; isPermanentlyDisabled: boolean } | null { + const state = this.resourceTypes.get(type); + if (!state) { + return null; + } + + return { + successCount: state.successCount, + failureCount: state.failureCount, + isEnabled: state.isEnabled, + isPermanentlyDisabled: state.isPermanentlyDisabled, + }; } /** - * 销毁管理器,清理所有资源 + * 获取所有资源类型统计 + */ + getAllResourceStats(): Map { + const stats = new Map(); + for (const [type, state] of this.resourceTypes) { + stats.set(type, { + successCount: state.successCount, + failureCount: state.failureCount, + isEnabled: state.isEnabled, + isPermanentlyDisabled: state.isPermanentlyDisabled, + }); + } + return stats; + } + + /** + * 注销资源类型 + */ + unregister(type: string): boolean { + return this.resourceTypes.delete(type); + } + + /** + * 销毁管理器 */ destroy(): void { if (this.destroyed) { return; } - this.stopHealthCheckTask(); - this.resources.clear(); + this.resourceTypes.clear(); this.destroyed = true; } - private generateRegistrationKey(key: string, config: ResourceConfig): string { - const configStr = JSON.stringify({ - name: config.name, - disableTime: config.disableTime, - maxRetries: config.maxRetries, - healthCheckInterval: config.healthCheckInterval, - maxHealthCheckFailures: config.maxHealthCheckFailures, - functionStr: config.resourceFn.toString(), - healthCheckFnStr: config.healthCheckFn?.toString() - }); - - return `${key}_${this.simpleHash(configStr)}`; - } - - private simpleHash(str: string): string { - let hash = 0; - for (let i = 0; i < str.length; i++) { - const char = str.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer - } - return Math.abs(hash).toString(36); - } - - private onResourceSuccess(state: ResourceState): void { - state.currentRetries = 0; - state.disableUntil = 0; - state.healthCheckFailureCount = 0; - state.lastError = undefined; - } - - private onResourceFailure(state: ResourceState, error: Error): void { - state.currentRetries++; - state.lastError = error; - - // 如果重试次数达到上限,禁用资源 - if (state.currentRetries >= state.config.maxRetries!) { - state.disableUntil = Date.now() + state.config.disableTime!; - state.currentRetries = 0; - } - } - - private startHealthCheckTask(): void { - if (this.healthCheckTimer) { + /** + * 检查并执行健康检查(如果需要) + */ + private async checkAndPerformHealthCheck(state: ResourceTypeState): Promise { + // 如果资源可用或已永久禁用,无需健康检查 + if (state.isEnabled && Date.now() >= state.disableUntil) { return; } - this.healthCheckTimer = setInterval(() => { - this.runHealthCheckTask(); - }, this.HEALTH_CHECK_TASK_INTERVAL); - } - - private stopHealthCheckTask(): void { - if (this.healthCheckTimer) { - clearInterval(this.healthCheckTimer); - this.healthCheckTimer = undefined; - } - } - - private async runHealthCheckTask(): Promise { - if (this.destroyed) { + if (state.isPermanentlyDisabled) { return; } const now = Date.now(); - for (const [key, state] of this.resources) { - // 跳过永久禁用或可用的资源 - if (state.isPermanentlyDisabled || this.isResourceAvailable(key)) { - continue; - } - - // 跳过还在禁用期内的资源 - if (now < state.disableUntil) { - continue; - } - - // 检查是否需要进行健康检查(根据间隔时间) - const lastHealthCheck = state.lastHealthCheckTime || 0; - const healthCheckInterval = state.config.healthCheckInterval!; - - if (now - lastHealthCheck < healthCheckInterval) { - continue; - } - - // 执行健康检查 - await this.performHealthCheck(state); + // 检查是否还在禁用期内 + if (now < state.disableUntil) { + return; } + + // 检查是否需要进行健康检查(根据间隔时间) + if (now - state.lastHealthCheckTime < state.config.healthCheckInterval) { + return; + } + + // 执行健康检查 + await this.performHealthCheck(state); } - private async performHealthCheck(state: ResourceState): Promise { + private async performHealthCheck(state: ResourceTypeState): Promise { state.lastHealthCheckTime = Date.now(); try { let healthCheckResult: boolean; - // 如果有专门的健康检查函数,使用它 if (state.config.healthCheckFn) { - const testArgs = state.config.testArgs || [] as unknown as T; + const testArgs = state.config.testArgs || []; healthCheckResult = await state.config.healthCheckFn(...testArgs); } else { - // 否则使用原始函数进行检查 - const testArgs = state.config.testArgs || [] as unknown as T; + const testArgs = state.config.testArgs || []; await state.config.resourceFn(...testArgs); healthCheckResult = true; } @@ -268,26 +229,42 @@ export class ResourceManager { state.disableUntil = 0; state.currentRetries = 0; state.healthCheckFailureCount = 0; - state.lastError = undefined; } else { throw new Error('Health check function returned false'); } - } catch (error) { + } catch { // 健康检查失败,增加失败计数 state.healthCheckFailureCount++; - state.lastError = error as Error; // 检查是否达到最大健康检查失败次数 - if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures!) { + if (state.healthCheckFailureCount >= state.config.maxHealthCheckFailures) { // 永久禁用资源 state.isPermanentlyDisabled = true; state.disableUntil = 0; } else { // 继续禁用一段时间 - state.disableUntil = Date.now() + state.config.disableTime!; + state.disableUntil = Date.now() + state.config.disableTime; } } } + + private onResourceSuccess(state: ResourceTypeState): void { + state.currentRetries = 0; + state.disableUntil = 0; + state.healthCheckFailureCount = 0; + state.successCount++; + } + + private onResourceFailure(state: ResourceTypeState): void { + state.currentRetries++; + state.failureCount++; + + // 如果重试次数达到上限,禁用资源 + if (state.currentRetries >= state.config.maxRetries) { + state.disableUntil = Date.now() + state.config.disableTime; + state.currentRetries = 0; + } + } } // 创建全局实例 @@ -295,34 +272,9 @@ export const resourceManager = new ResourceManager(); // 便捷函数 export async function registerResource( - key: string, + type: string, config: ResourceConfig, ...args: T ): Promise { - return resourceManager.register(key, config, ...args); -} - -// 使用示例: -/* -await registerResource( - 'api-with-health-check', - { - resourceFn: async (id: string) => { - const response = await fetch(`https://api.example.com/data/${id}`); - return response.json(); - }, - healthCheckFn: async (id: string) => { - try { - const response = await fetch(`https://api.example.com/health`); - return response.ok; - } catch { - return false; - } - }, - testArgs: ['health-check-id'], - healthCheckInterval: 30000, - maxHealthCheckFailures: 3 - }, - 'user123' -); -*/ \ No newline at end of file + return resourceManager.callResource(type, config, ...args); +} \ No newline at end of file From 5b78dfbd5a0749e42fb3200f148a97f867fec071 Mon Sep 17 00:00:00 2001 From: Mlikiowa Date: Thu, 16 Oct 2025 13:28:29 +0000 Subject: [PATCH 10/14] release: v4.8.122 --- manifest.json | 2 +- package.json | 2 +- src/common/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index a7b4c2fd..a487cbde 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "4.8.121", + "version": "4.8.122", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index dfeea841..ba8531f4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "4.8.121", + "version": "4.8.122", "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/version.ts b/src/common/version.ts index 03404edc..0f514af3 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '4.8.121'; +export const napCatVersion = '4.8.122'; From d3e3527c2bba8f1bcfaa8442b02ddaa72f292603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=97=B6=E7=91=BE?= <74231782+sj817@users.noreply.github.com> Date: Tue, 21 Oct 2025 22:49:45 +0800 Subject: [PATCH 11/14] =?UTF-8?q?fix:=20go-cqhttp=20=E4=B8=8A=E4=BC=A0?= =?UTF-8?q?=E6=8E=A5=E5=8F=A3=E8=BF=94=E5=9B=9E=20file=5Fid=20(UploadGroup?= =?UTF-8?q?File,=20UploadPrivateFile)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/onebot/action/go-cqhttp/UploadGroupFile.ts | 18 +++++++++++++----- .../action/go-cqhttp/UploadPrivateFile.ts | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/onebot/action/go-cqhttp/UploadGroupFile.ts b/src/onebot/action/go-cqhttp/UploadGroupFile.ts index 5c636e16..072251a7 100644 --- a/src/onebot/action/go-cqhttp/UploadGroupFile.ts +++ b/src/onebot/action/go-cqhttp/UploadGroupFile.ts @@ -1,6 +1,6 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; -import { ChatType, Peer } from '@/core/types'; +import { ChatType, Peer, ElementType } from '@/core/types'; import fs from 'fs'; import { uriToLocalFile } from '@/common/file'; import { SendMessageContext } from '@/onebot/api'; @@ -16,11 +16,15 @@ const SchemaData = Type.Object({ type Payload = Static; -export default class GoCQHTTPUploadGroupFile extends OneBotAction { +interface UploadGroupFileResponse { + file_id: string | null; +} + +export default class GoCQHTTPUploadGroupFile extends OneBotAction { override actionName = ActionName.GoCQHTTP_UploadGroupFile; override payloadSchema = SchemaData; - async _handle(payload: Payload): Promise { + async _handle(payload: Payload): Promise { let file = payload.file; if (fs.existsSync(file)) { file = `file://${file}`; @@ -39,7 +43,11 @@ export default class GoCQHTTPUploadGroupFile extends OneBotAction }; const sendFileEle = await this.core.apis.FileApi.createValidSendFileElement(msgContext, downloadResult.path, payload.name, payload.folder ?? payload.folder_id); msgContext.deleteAfterSentFiles.push(downloadResult.path); - await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles); - return null; + const returnMsg = await this.obContext.apis.MsgApi.sendMsgWithOb11UniqueId(peer, [sendFileEle], msgContext.deleteAfterSentFiles); + + const fileElement = returnMsg.elements.find(ele => ele.elementType === ElementType.FILE); + return { + file_id: fileElement?.fileElement?.fileUuid || null + }; } } diff --git a/src/onebot/action/go-cqhttp/UploadPrivateFile.ts b/src/onebot/action/go-cqhttp/UploadPrivateFile.ts index 1a37a21f..b88cab49 100644 --- a/src/onebot/action/go-cqhttp/UploadPrivateFile.ts +++ b/src/onebot/action/go-cqhttp/UploadPrivateFile.ts @@ -1,6 +1,6 @@ import { OneBotAction } from '@/onebot/action/OneBotAction'; import { ActionName } from '@/onebot/action/router'; -import { ChatType, Peer, SendFileElement } from '@/core/types'; +import { ChatType, Peer, SendFileElement, ElementType } from '@/core/types'; import fs from 'fs'; import { uriToLocalFile } from '@/common/file'; import { SendMessageContext } from '@/onebot/api'; @@ -15,7 +15,11 @@ const SchemaData = Type.Object({ type Payload = Static; -export default class GoCQHTTPUploadPrivateFile extends OneBotAction { +interface UploadPrivateFileResponse { + file_id: string | null; +} + +export default class GoCQHTTPUploadPrivateFile extends OneBotAction { override actionName = ActionName.GOCQHTTP_UploadPrivateFile; override payloadSchema = SchemaData; @@ -31,7 +35,7 @@ export default class GoCQHTTPUploadPrivateFile extends OneBotAction { + async _handle(payload: Payload): Promise { let file = payload.file; if (fs.existsSync(file)) { file = `file://${file}`; @@ -49,7 +53,11 @@ export default class GoCQHTTPUploadPrivateFile extends OneBotAction ele.elementType === ElementType.FILE); + return { + file_id: fileElement?.fileElement?.fileUuid || null + }; } } From 376245b749635b6949addd61eb2e66c4aaa6e30e Mon Sep 17 00:00:00 2001 From: Mlikiowa Date: Tue, 21 Oct 2025 15:06:10 +0000 Subject: [PATCH 12/14] release: v4.8.123 --- manifest.json | 2 +- package.json | 2 +- src/common/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index a487cbde..d556b6f9 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "4.8.122", + "version": "4.8.123", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index ba8531f4..fee44d36 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "4.8.122", + "version": "4.8.123", "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/version.ts b/src/common/version.ts index 0f514af3..9bb48b55 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '4.8.122'; +export const napCatVersion = '4.8.123'; From 791a360f281d9735ffd22e4ad3d0a61dd27b97f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Wed, 22 Oct 2025 21:09:37 +0800 Subject: [PATCH 13/14] Add 40990 version entries to appid and offset configs Added new entries for version 40990 for multiple platforms in appid.json and offset.json, including appid, qua, and send/recv offsets. This update supports the latest client versions. --- src/core/external/appid.json | 12 ++++++++++++ src/core/external/offset.json | 16 ++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/src/core/external/appid.json b/src/core/external/appid.json index b3bb773e..9224f4fb 100644 --- a/src/core/external/appid.json +++ b/src/core/external/appid.json @@ -414,5 +414,17 @@ "6.9.82-40824": { "appid": 537319829, "qua": "V1_MAC_NQ_6.9.82_40824_GW_B" + }, + "6.9.82-40990": { + "appid": 537319880, + "qua": "V1_MAC_NQ_6.9.82_40990_GW_B" + }, + "9.9.22.40990": { + "appid": 537319855, + "qua": "V1_WIN_NQ_9.9.22.40990_GW_B" + }, + "3.2.20-40990": { + "appid": 537319891, + "qua": "V1_LNX_NQ_3.2.20_40990_GW_B" } } \ No newline at end of file diff --git a/src/core/external/offset.json b/src/core/external/offset.json index 93ea4bea..a4b7476d 100644 --- a/src/core/external/offset.json +++ b/src/core/external/offset.json @@ -542,5 +542,21 @@ "6.9.82-40824-arm64": { "send": "202A198", "recv": "202B718" + }, + "3.2.20-40990-x64": { + "send": "B69CFE0", + "recv": "B6A0A60" + }, + "3.2.20-40990-arm64": { + "send": "7D49B18", + "recv": "7D4D4A8" + }, + "9.9.22-40990-x64": { + "send": "31C1838", + "recv": "31C4FDC" + }, + "6.9.82-40990-arm64": { + "send": "202A198", + "recv": "202B718" } } \ No newline at end of file From 656bde25c8964085af07f28842ed2565b4d3764d Mon Sep 17 00:00:00 2001 From: Mlikiowa Date: Wed, 22 Oct 2025 13:12:25 +0000 Subject: [PATCH 14/14] release: v4.8.124 --- manifest.json | 2 +- package.json | 2 +- src/common/version.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/manifest.json b/manifest.json index d556b6f9..2fe1a7ea 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "name": "NapCatQQ", "slug": "NapCat.Framework", "description": "高性能的 OneBot 11 协议实现", - "version": "4.8.123", + "version": "4.8.124", "icon": "./logo.png", "authors": [ { diff --git a/package.json b/package.json index fee44d36..d578b802 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "napcat", "private": true, "type": "module", - "version": "4.8.123", + "version": "4.8.124", "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/version.ts b/src/common/version.ts index 9bb48b55..d6dfa798 100644 --- a/src/common/version.ts +++ b/src/common/version.ts @@ -1 +1 @@ -export const napCatVersion = '4.8.123'; +export const napCatVersion = '4.8.124';