mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2025-07-19 12:03:37 +00:00
refactor: 大部分文件处理部分
This commit is contained in:
56
src/common/file-uuid.ts
Normal file
56
src/common/file-uuid.ts
Normal 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();
|
@@ -1,7 +1,7 @@
|
||||
import path from 'node:path';
|
||||
import fs from 'fs';
|
||||
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> {
|
||||
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> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
6
src/core/external/appid.json
vendored
6
src/core/external/appid.json
vendored
@@ -174,5 +174,9 @@
|
||||
"6.9.63-31245": {
|
||||
"appid": 537266474,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
6
src/core/external/offset.json
vendored
6
src/core/external/offset.json
vendored
@@ -226,5 +226,9 @@
|
||||
"3.2.15-31245-arm64": {
|
||||
"send": "71BEBB8",
|
||||
"recv": "71C23F0"
|
||||
},
|
||||
"9.9.17-31363-x64": {
|
||||
"send": "39C1910",
|
||||
"recv": "39C5d44"
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import fs from 'fs/promises';
|
||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { OB11MessageImage, OB11MessageVideo } from '@/onebot/types';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
@@ -28,7 +28,7 @@ export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
||||
payload.file ||= payload.file_id || '';
|
||||
//接收消息标记模式
|
||||
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
|
||||
if (contextMsgFile) {
|
||||
if (contextMsgFile && contextMsgFile.msgId && contextMsgFile.elementId) {
|
||||
const { peer, msgId, elementId } = contextMsgFile;
|
||||
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
|
||||
@@ -68,7 +68,7 @@ export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
|
||||
|
||||
//群文件模式
|
||||
const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file);
|
||||
if (contextModelIdFile) {
|
||||
if (contextModelIdFile && contextModelIdFile.modelId) {
|
||||
const { peer, modelId } = contextModelIdFile;
|
||||
const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, '');
|
||||
const res: GetFileResponse = {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
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 { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
|
||||
import { OneBotAction } from '@/onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/onebot/action/router';
|
||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
@@ -16,7 +16,7 @@ export class DeleteGroupFile extends OneBotAction<Payload, any> {
|
||||
payloadSchema = SchemaData;
|
||||
async _handle(payload: Payload) {
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
@@ -23,7 +23,7 @@ import { OB11GroupTitleEvent } from '@/onebot/event/notice/OB11GroupTitleEvent';
|
||||
import { OB11GroupUploadNoticeEvent } from '../event/notice/OB11GroupUploadNoticeEvent';
|
||||
import { OB11GroupNameEvent } from '../event/notice/OB11GroupNameEvent';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
|
||||
export class OneBotGroupApi {
|
||||
@@ -199,7 +199,7 @@ export class OneBotGroupApi {
|
||||
id: FileNapCatOneBotUUID.encode({
|
||||
chatType: ChatType.KCHATTYPEGROUP,
|
||||
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,
|
||||
name: element.fileName,
|
||||
size: parseInt(element.fileSize),
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { FileNapCatOneBotUUID } from '@/common/helper';
|
||||
import { FileNapCatOneBotUUID } from '@/common/file-uuid';
|
||||
import { MessageUnique } from '@/common/message-unique';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import {
|
||||
@@ -116,18 +116,22 @@ export class OneBotMsgApi {
|
||||
peerUid: msg.peerUid,
|
||||
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 {
|
||||
type: OB11MessageDataType.image,
|
||||
data: {
|
||||
summary: element.summary,
|
||||
file: encodedFileId,
|
||||
file: element.fileName,
|
||||
sub_type: element.picSubType,
|
||||
file_id: encodedFileId,
|
||||
url: await this.core.apis.FileApi.getImageUrl(element),
|
||||
path: element.filePath,
|
||||
file_size: element.fileSize,
|
||||
file_unique: element.md5HexStr ?? element.fileName,
|
||||
},
|
||||
};
|
||||
} catch (e: any) {
|
||||
@@ -142,15 +146,15 @@ export class OneBotMsgApi {
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
const file = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, element.fileUuid, element.fileName);
|
||||
return {
|
||||
type: OB11MessageDataType.file,
|
||||
data: {
|
||||
file: element.fileName,
|
||||
file: file,
|
||||
path: element.filePath,
|
||||
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_unique: element.fileMd5 ?? element.fileSha ?? element.fileName,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -201,18 +205,18 @@ export class OneBotMsgApi {
|
||||
const { emojiId } = _;
|
||||
const dir = emojiId.substring(0, 2);
|
||||
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 {
|
||||
type: OB11MessageDataType.image,
|
||||
data: {
|
||||
summary: _.faceName, // 商城表情名称
|
||||
file: 'marketface',
|
||||
file_id: FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + _.key + ".jpg"),
|
||||
file: filename,
|
||||
path: url,
|
||||
url: url,
|
||||
key: _.key,
|
||||
emoji_id: _.emojiId,
|
||||
emoji_package_id: _.emojiPackageId,
|
||||
file_unique: _.key
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -327,16 +331,14 @@ export class OneBotMsgApi {
|
||||
if (!videoDownUrl) {
|
||||
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 {
|
||||
type: OB11MessageDataType.video,
|
||||
data: {
|
||||
file: fileCode,
|
||||
path: videoDownUrl,
|
||||
url: videoDownUrl ?? pathToFileURL(element.filePath).href,
|
||||
file_id: fileCode,
|
||||
file_size: element.fileSize,
|
||||
file_unique: element.videoMd5 ?? element.thumbMd5 ?? element.fileName,
|
||||
},
|
||||
};
|
||||
},
|
||||
@@ -347,16 +349,14 @@ export class OneBotMsgApi {
|
||||
peerUid: msg.peerUid,
|
||||
guildId: '',
|
||||
};
|
||||
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", "." + element.fileName);
|
||||
const fileCode = FileNapCatOneBotUUID.encode(peer, msg.msgId, elementWrapper.elementId, "", element.fileName);
|
||||
return {
|
||||
type: OB11MessageDataType.voice,
|
||||
data: {
|
||||
file: fileCode,
|
||||
path: element.filePath,
|
||||
url: pathToFileURL(element.filePath).href,
|
||||
file_id: fileCode,
|
||||
file_size: element.fileSize,
|
||||
file_unique: element.fileUuid
|
||||
},
|
||||
};
|
||||
},
|
||||
|
@@ -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 {
|
||||
OB11Group,
|
||||
|
@@ -180,8 +180,11 @@ export class NapCatOneBot11Adapter {
|
||||
WebUiDataRuntime.setQQLoginStatus(true);
|
||||
WebUiDataRuntime.setOnOB11ConfigChanged(async (newConfig) => {
|
||||
const prev = this.configLoader.configData;
|
||||
// 保证默认配置
|
||||
newConfig = mergeOneBotConfigs(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);
|
||||
});
|
||||
}
|
||||
|
@@ -110,7 +110,6 @@ export interface OB11MessageContext {
|
||||
// 文件消息基础接口定义
|
||||
export interface OB11MessageFileBase {
|
||||
data: {
|
||||
file_unique?: string;
|
||||
path?: string;
|
||||
thumb?: string;
|
||||
name?: string;
|
||||
|
Reference in New Issue
Block a user