refactor: 大部分文件处理部分

This commit is contained in:
手瓜一十雪 2025-01-13 20:30:08 +08:00
parent 7f3dbe0552
commit ec0c2e8c33
12 changed files with 98 additions and 106 deletions

56
src/common/file-uuid.ts Normal file
View File

@ -0,0 +1,56 @@
import { Peer } from '@/core';
import { LRUCache } from './lru-cache';
import { randomUUID } from 'crypto';
interface FileUUIDData {
peer: Peer;
modelId?: string;
fileId?: string;
msgId?: string;
elementId?: string;
fileUUID?: string;
}
class FileUUIDManager {
private cache: LRUCache<string, FileUUIDData>;
constructor(capacity: number) {
this.cache = new LRUCache<string, FileUUIDData>(capacity);
}
public encode(data: FileUUIDData, endString: string = "", customUUID?: string): string {
const uuid = customUUID ? customUUID : randomUUID().replace(/-/g, '') + endString;
this.cache.put(uuid, data);
return uuid;
}
public decode(uuid: string): FileUUIDData | undefined {
return this.cache.get(uuid);
}
}
export class FileNapCatOneBotUUIDWrap {
private manager: FileUUIDManager;
constructor(capacity: number = 100) {
this.manager = new FileUUIDManager(capacity);
}
public encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = "", customUUID?: string): string {
return this.manager.encode({ peer, modelId, fileId, fileUUID }, endString, customUUID);
}
public decodeModelId(uuid: string): FileUUIDData | undefined {
return this.manager.decode(uuid);
}
public encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", customUUID?: string): string {
return this.manager.encode({ peer, msgId, elementId, fileUUID }, "", customUUID);
}
public decode(uuid: string): FileUUIDData | undefined {
return this.manager.decode(uuid);
}
}
export const FileNapCatOneBotUUID = new FileNapCatOneBotUUIDWrap();

View File

@ -1,7 +1,7 @@
import path from 'node:path'; import path from 'node:path';
import fs from 'fs'; import fs from 'fs';
import os from 'node:os'; import os from 'node:os';
import { Peer, QQLevel } from '@/core'; import { QQLevel } from '@/core';
export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> { export async function solveProblem<T extends (...arg: any[]) => any>(func: T, ...args: Parameters<T>): Promise<ReturnType<T> | undefined> {
return new Promise<ReturnType<T> | undefined>((resolve) => { return new Promise<ReturnType<T> | undefined>((resolve) => {
@ -24,81 +24,6 @@ export async function solveAsyncProblem<T extends (...args: any[]) => Promise<an
}); });
} }
export class FileNapCatOneBotUUID {
static encodeModelId(peer: Peer, modelId: string, fileId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|ModelIdFile|${peer.chatType}|${peer.peerUid}|${modelId}|${fileId}|${fileUUID}`;
//前四个字节塞data长度
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);//储存data的hex长度
length.write(data, 4);
return length.toString('hex') + endString;
}
static decodeModelId(uuid: string): undefined | {
peer: Peer,
modelId: string,
fileId: string,
fileUUID?: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|ModelIdFile|')) return undefined;
const data = realData.split('|');
if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, modelId, fileId, fileUUID = undefined] = data;
return {
peer: {
chatType: +chatType,
peerUid: peerUid,
},
modelId,
fileId,
fileUUID
};
}
static encode(peer: Peer, msgId: string, elementId: string, fileUUID: string = "", endString: string = ""): string {
const data = `NapCatOneBot|MsgFile|${peer.chatType}|${peer.peerUid}|${msgId}|${elementId}|${fileUUID}`;
//前四个字节塞data长度
//一个字节8位 一个ascii字符1字节 一个hex字符4位 表示一个ascii字符需要两个hex字符
const length = Buffer.alloc(4 + data.length);
length.writeUInt32BE(data.length * 2, 0);
length.write(data, 4);
return length.toString('hex') + endString;
}
static decode(uuid: string): undefined | {
peer: Peer,
msgId: string,
elementId: string,
fileUUID?: string
} {
//前四个字节是data长度
const length = Buffer.from(uuid.slice(0, 8), 'hex').readUInt32BE(0);
//根据length计算需要读取的长度
const dataId = uuid.slice(8, 8 + length);
//hex还原为string
const realData = Buffer.from(dataId, 'hex').toString();
if (!realData.startsWith('NapCatOneBot|MsgFile|')) return undefined;
const data = realData.split('|');
if (data.length < 6) return undefined; // compatibility requirement
const [, , chatType, peerUid, msgId, elementId, fileUUID = undefined] = data;
return {
peer: {
chatType: +chatType,
peerUid: peerUid,
},
msgId,
elementId,
fileUUID
};
}
}
export function sleep(ms: number): Promise<void> { export function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms)); return new Promise((resolve) => setTimeout(resolve, ms));
} }

View File

@ -174,5 +174,9 @@
"6.9.63-31245": { "6.9.63-31245": {
"appid": 537266474, "appid": 537266474,
"qua": "V1_MAC_NQ_6.9.63_31245_GW_B" "qua": "V1_MAC_NQ_6.9.63_31245_GW_B"
},
"9.9.17-31363": {
"appid": 537266500,
"qua": "V1_WIN_NQ_9.9.17_31363_GW_B"
} }
} }

View File

@ -226,5 +226,9 @@
"3.2.15-31245-arm64": { "3.2.15-31245-arm64": {
"send": "71BEBB8", "send": "71BEBB8",
"recv": "71C23F0" "recv": "71C23F0"
},
"9.9.17-31363-x64": {
"send": "39C1910",
"recv": "39C5d44"
} }
} }

View File

@ -1,6 +1,6 @@
import { OneBotAction } from '@/onebot/action/OneBotAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import fs from 'fs/promises'; import fs from 'fs/promises';
import { FileNapCatOneBotUUID } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types'; import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
@ -28,7 +28,7 @@ export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
payload.file ||= payload.file_id || ''; payload.file ||= payload.file_id || '';
//接收消息标记模式 //接收消息标记模式
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file); const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
if (contextMsgFile) { if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
const { peer, msgId, elementId } = contextMsgFile; const { peer, msgId, elementId } = contextMsgFile;
const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', ''); const downloadPath = await this.core.apis.FileApi.downloadMedia(msgId, peer.chatType, peer.peerUid, elementId, '', '');
const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList const rawMessage = (await this.core.apis.MsgApi.getMsgsByMsgId(peer, [msgId]))?.msgList
@ -68,7 +68,7 @@ export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
//群文件模式 //群文件模式
const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file); const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file);
if (contextModelIdFile) { if (contextModelIdFile && contextModelIdFile.modelId) {
const { peer, modelId } = contextModelIdFile; const { peer, modelId } = contextModelIdFile;
const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, ''); const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, '');
const res: GetFileResponse = { const res: GetFileResponse = {

View File

@ -1,5 +1,5 @@
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import { FileNapCatOneBotUUID } from "@/common/helper"; import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus"; import { GetPacketStatusDepends } from "@/onebot/action/packet/GetPacketStatus";
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';

View File

@ -1,7 +1,7 @@
import { OneBotAction } from '@/onebot/action/OneBotAction'; import { OneBotAction } from '@/onebot/action/OneBotAction';
import { ActionName } from '@/onebot/action/router'; import { ActionName } from '@/onebot/action/router';
import { FileNapCatOneBotUUID } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { Static, Type } from '@sinclair/typebox'; import { Static, Type } from '@sinclair/typebox';
const SchemaData = Type.Object({ const SchemaData = Type.Object({
@ -16,7 +16,7 @@ export class DeleteGroupFile extends OneBotAction<Payload, any> {
payloadSchema = SchemaData; payloadSchema = SchemaData;
async _handle(payload: Payload) { async _handle(payload: Payload) {
const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id); const data = FileNapCatOneBotUUID.decodeModelId(payload.file_id);
if (!data) throw new Error('Invalid file_id'); if (!data || !data.fileId) throw new Error('Invalid file_id');
return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]); return await this.core.apis.GroupApi.delGroupFile(payload.group_id.toString(), [data.fileId]);
} }
} }

View File

@ -23,7 +23,7 @@ import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent'; import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
import { OB11GroupNameEvent } from '../event/notice/OB11GroupNameEvent'; import { OB11GroupNameEvent } from '../event/notice/OB11GroupNameEvent';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { FileNapCatOneBotUUID } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent'; import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
export class OneBotGroupApi { export class OneBotGroupApi {
@ -199,7 +199,7 @@ export class OneBotGroupApi {
id: FileNapCatOneBotUUID.encode({ id: FileNapCatOneBotUUID.encode({
chatType: ChatType.KCHATTYPEGROUP, chatType: ChatType.KCHATTYPEGROUP,
peerUid: msg.peerUid, peerUid: msg.peerUid,
}, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, "." + element.fileName), }, msg.msgId, elementWrapper.elementId, elementWrapper?.fileElement?.fileUuid, element.fileName),
url: pathToFileURL(element.filePath).href, url: pathToFileURL(element.filePath).href,
name: element.fileName, name: element.fileName,
size: parseInt(element.fileSize), size: parseInt(element.fileSize),

View File

@ -1,4 +1,4 @@
import { FileNapCatOneBotUUID } from '@/common/helper'; import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { MessageUnique } from '@/common/message-unique'; import { MessageUnique } from '@/common/message-unique';
import { pathToFileURL } from 'node:url'; import { pathToFileURL } from 'node:url';
import { import {
@ -116,18 +116,22 @@ export class OneBotMsgApi {
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: '',
}; };
const encodedFileId = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, "." + element.fileName); FileNapCatOneBotUUID.encode(
peer,
msg.msgId,
elementWrapper.elementId,
element.fileUuid,
element.fileName
);
return { return {
type: OB11MessageDataType.image, type: OB11MessageDataType.image,
data: { data: {
summary: element.summary, summary: element.summary,
file: encodedFileId, file: element.fileName,
sub_type: element.picSubType, sub_type: element.picSubType,
file_id: encodedFileId,
url: await this.core.apis.FileApi.getImageUrl(element), url: await this.core.apis.FileApi.getImageUrl(element),
path: element.filePath, path: element.filePath,
file_size: element.fileSize, file_size: element.fileSize,
file_unique: element.md5HexStr ?? element.fileName,
}, },
}; };
} catch (e: any) { } catch (e: any) {
@ -142,15 +146,15 @@ export class OneBotMsgApi {
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: '',
}; };
const file = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
return { return {
type: OB11MessageDataType.file, type: OB11MessageDataType.file,
data: { data: {
file: element.fileName, file: file,
path: element.filePath, path: element.filePath,
url: pathToFileURL(element.filePath).href, url: pathToFileURL(element.filePath).href,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, "." + element.fileName), file_id: file,
file_size: element.fileSize, file_size: element.fileSize,
file_unique: element.fileMd5 ?? element.fileSha ?? element.fileName,
}, },
}; };
}, },
@ -201,18 +205,18 @@ export class OneBotMsgApi {
const { emojiId } = _; const { emojiId } = _;
const dir = emojiId.substring(0, 2); const dir = emojiId.substring(0, 2);
const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`; const url = `https://gxh.vip.qq.com/club/item/parcel/item/${dir}/${emojiId}/raw300.gif`;
const filename = `${dir}-${emojiId}.gif`;
FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", filename)
return { return {
type: OB11MessageDataType.image, type: OB11MessageDataType.image,
data: { data: {
summary: _.faceName, // 商城表情名称 summary: _.faceName, // 商城表情名称
file: 'marketface', file: filename,
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + _.key + ".jpg"),
path: url, path: url,
url: url, url: url,
key: _.key, key: _.key,
emoji_id: _.emojiId, emoji_id: _.emojiId,
emoji_package_id: _.emojiPackageId, emoji_package_id: _.emojiPackageId,
file_unique: _.key
}, },
}; };
}, },
@ -327,16 +331,14 @@ export class OneBotMsgApi {
if (!videoDownUrl) { if (!videoDownUrl) {
videoDownUrl = element.filePath; videoDownUrl = element.filePath;
} }
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + element.fileName); const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
return { return {
type: OB11MessageDataType.video, type: OB11MessageDataType.video,
data: { data: {
file: fileCode, file: fileCode,
path: videoDownUrl, path: videoDownUrl,
url: videoDownUrl ?? pathToFileURL(element.filePath).href, url: videoDownUrl ?? pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize, file_size: element.fileSize,
file_unique: element.videoMd5 ?? element.thumbMd5 ?? element.fileName,
}, },
}; };
}, },
@ -347,16 +349,14 @@ export class OneBotMsgApi {
peerUid: msg.peerUid, peerUid: msg.peerUid,
guildId: '', guildId: '',
}; };
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + element.fileName); const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", element.fileName);
return { return {
type: OB11MessageDataType.voice, type: OB11MessageDataType.voice,
data: { data: {
file: fileCode, file: fileCode,
path: element.filePath, path: element.filePath,
url: pathToFileURL(element.filePath).href, url: pathToFileURL(element.filePath).href,
file_id: fileCode,
file_size: element.fileSize, file_size: element.fileSize,
file_unique: element.fileUuid
}, },
}; };
}, },

View File

@ -1,4 +1,5 @@
import { calcQQLevel, FileNapCatOneBotUUID } from '@/common/helper'; import { calcQQLevel } from '@/common/helper';
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
import { FriendV2, Group, GroupFileInfoUpdateParamType, GroupMember, SelfInfo, NTSex } from '@/core'; import { FriendV2, Group, GroupFileInfoUpdateParamType, GroupMember, SelfInfo, NTSex } from '@/core';
import { import {
OB11Group, OB11Group,

View File

@ -180,8 +180,11 @@ export class NapCatOneBot11Adapter {
WebUiDataRuntime.setQQLoginStatus(true); WebUiDataRuntime.setQQLoginStatus(true);
WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => { WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => {
const prev = this.configLoader.configData; const prev = this.configLoader.configData;
// 保证默认配置
newConfig = mergeOneBotConfigs(newConfig);
this.configLoader.save(newConfig); this.configLoader.save(newConfig);
this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`); //this.context.logger.log(`OneBot11 配置更改:${JSON.stringify(prev)} -> ${JSON.stringify(newConfig)}`);
await this.reloadNetwork(prev, newConfig); await this.reloadNetwork(prev, newConfig);
}); });
} }

View File

@ -110,7 +110,6 @@ export interface OB11MessageContext {
// 文件消息基础接口定义 // 文件消息基础接口定义
export interface OB11MessageFileBase { export interface OB11MessageFileBase {
data: { data: {
file_unique?: string;
path?: string; path?: string;
thumb?: string; thumb?: string;
name?: string; name?: string;