SnowCat 雪猫底层全源码:NapCat 4.18.28 底座 + SnowLuma 能力移植 + V8 通道适配 9.9.37

26 包 monorepo(TS 全源码):
- snowcat-core: NT 内核 API 封装 + listener(含 9.9.37 六回调适配)+ Packet 发包
- snowcat-onebot: OneBot v11 全动作 + MCP Streamable HTTP 适配器 + 稳定线
- snowcat-webui-frontend/backend: 雪猫控制台(React+HeroUI)
- snowcat-shell: 单文件 bundle 出口(vite inlineDynamicImports)
- snowcat-native: napi2native / MoeHoo / ffmpeg / pty 运行时
- 56 项 SnowLuma 独门动作移植(qzone 全家桶/群相册/装扮/闪照/收藏)
- 工具件: 解包器/启动器/便携制作器/doctor

GitHub 关联已全量指向 DandanLLab/SnowCatQQ
This commit is contained in:
天天的鸟蛋蛋 committed 2026-11-12 06:27:36 +08:00
commit b9655a5ab1
1048 files changed
+130851

No files matched your search

@@ -0,0 +1,131 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import fs from 'fs/promises';
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
import { ActionName } from '@/snowcat-onebot/action/router';
import { OB11MessageImage, OB11MessageVideo } from '@/snowcat-onebot/types';
import { Static, Type } from '@sinclair/typebox';
import { calculateTimeout } from '@/snowcat-onebot/config/config';
import { FileActionsExamples } from '../example/FileActionsExamples';
export const GetFilePayloadSchema = Type.Object({
file: Type.Optional(Type.String({ description: '文件路径、URL或Base64' })),
file_id: Type.Optional(Type.String({ description: '文件ID' })),
});
export type GetFilePayload = Static<typeof GetFilePayloadSchema>;
export const GetFileReturnSchema = Type.Object({
file: Type.Optional(Type.String({ description: '本地路径' })),
url: Type.Optional(Type.String({ description: '下载URL' })),
file_size: Type.Optional(Type.String({ description: '文件大小' })),
file_name: Type.Optional(Type.String({ description: '文件名' })),
base64: Type.Optional(Type.String({ description: 'Base64编码' })),
}, { description: '文件信息' });
export type GetFileResponse = Static<typeof GetFileReturnSchema>;
export class GetFileBase extends OneBotAction<GetFilePayload, GetFileResponse> {
override payloadSchema = GetFilePayloadSchema;
override returnSchema = GetFileReturnSchema;
override actionTags = ['文件接口'];
async _handle (payload: GetFilePayload): Promise<GetFileResponse> {
payload.file ||= payload.file_id || '';
// 接收消息标记模式
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file);
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
.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');
const fileSize = mixElementInner.fileSize?.toString() ?? '';
const fileName = mixElementInner.fileName ?? '';
let url = '';
if (mixElement?.picElement && rawMessage) {
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.picElement?.(mixElement?.picElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageImage | undefined;
url = tempData?.data.url ?? '';
}
if (mixElement?.videoElement && rawMessage) {
const tempData =
await this.obContext.apis.MsgApi.rawToOb11Converters.videoElement?.(mixElement?.videoElement, rawMessage, mixElement, { parseMultMsg: false, disableGetUrl: false, quick_reply: true }) as OB11MessageVideo | undefined;
url = tempData?.data.url ?? '';
}
const res: GetFileResponse = {
file: downloadPath,
url: url !== '' ? url : downloadPath,
file_size: fileSize,
file_name: fileName,
};
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
return res;
}
// 群文件模式
const contextModelIdFile = FileNapCatOneBotUUID.decodeModelId(payload.file);
if (contextModelIdFile && contextModelIdFile.modelId) {
const { peer, modelId } = contextModelIdFile;
const downloadPath = await this.core.apis.FileApi.downloadFileForModelId(peer, modelId, '');
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: '',
file_name: '',
};
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
return res;
}
// 搜索名字模式
const searchResult = (await this.core.apis.FileApi.searchForFile([payload.file]));
if (searchResult) {
const fileSize = parseInt(searchResult.fileSize);
const timeoutConfig = this.obContext.configLoader.configData.timeout;
const estimatedTime = calculateTimeout(timeoutConfig, fileSize, timeoutConfig.downloadSpeedKBps);
const downloadPath = await this.core.apis.FileApi.downloadFileById(searchResult.id, fileSize, estimatedTime);
const res: GetFileResponse = {
file: downloadPath,
url: downloadPath,
file_size: searchResult.fileSize.toString(),
file_name: searchResult.fileName,
};
if (this.obContext.configLoader.configData.enableLocalFile2Url && downloadPath) {
try {
res.base64 = await fs.readFile(downloadPath, 'base64');
} catch (e) {
throw new Error('文件下载失败. ' + e);
}
}
return res;
}
throw new Error('file not found');
}
}
export default class GetFile extends GetFileBase {
override actionName = ActionName.GetFile;
override actionSummary = '获取文件';
override actionDescription = '获取指定文件的详细信息及下载路径';
override actionTags = ['文件接口'];
override payloadExample = FileActionsExamples.GetFile.payload;
override returnExample = FileActionsExamples.GetFile.response;
}
@@ -0,0 +1,40 @@
import { ActionName } from '@/snowcat-onebot/action/router';
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox';
import { FileActionsExamples } from '../example/FileActionsExamples';
const PayloadSchema = Type.Object({
group_id: Type.String({ description: '群号' }),
file_id: Type.String({ description: '文件ID' }),
});
type PayloadType = Static<typeof PayloadSchema>;
const ReturnSchema = Type.Object({
url: Type.Optional(Type.String({ description: '文件下载链接' })),
}, { description: '群文件URL信息' });
type ReturnType = Static<typeof ReturnSchema>;
export class GetGroupFileUrl extends GetPacketStatusDepends<PayloadType, ReturnType> {
override actionName = ActionName.GOCQHTTP_GetGroupFileUrl;
override payloadSchema = PayloadSchema;
override returnSchema = ReturnSchema;
override actionSummary = '获取群文件URL';
override actionDescription = '获取指定群文件的下载链接';
override actionTags = ['文件接口'];
override payloadExample = FileActionsExamples.GetGroupFileUrl.payload;
override returnExample = FileActionsExamples.GetGroupFileUrl.response;
async _handle (payload: PayloadType) {
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id) || FileNapCatOneBotUUID.decodeModelId(payload.file_id);
if (contextMsgFile?.fileUUID) {
return {
url: await this.core.apis.PacketApi.pkt.operation.GetGroupFileUrl(+payload.group_id, contextMsgFile.fileUUID),
};
}
throw new Error('real fileUUID not found!');
}
}
@@ -0,0 +1,13 @@
import { GetFileBase } from './GetFile';
import { ActionName } from '@/snowcat-onebot/action/router';
import { FileActionsExamples } from '../example/FileActionsExamples';
export default class GetImage extends GetFileBase {
override actionName = ActionName.GetImage;
override actionSummary = '获取图片';
override actionDescription = '获取指定图片的信息及路径';
override actionTags = ['文件接口'];
override payloadExample = FileActionsExamples.GetImage.payload;
override returnExample = FileActionsExamples.GetImage.response;
}
@@ -0,0 +1,45 @@
import { ActionName } from '@/snowcat-onebot/action/router';
import { FileNapCatOneBotUUID } from 'snowcat-common/src/file-uuid';
import { GetPacketStatusDepends } from '@/snowcat-onebot/action/packet/GetPacketStatus';
import { Static, Type } from '@sinclair/typebox';
import { FileActionsExamples } from '../example/FileActionsExamples';
const PayloadSchema = Type.Object({
file_id: Type.String({ description: '文件ID' }),
});
type PayloadType = Static<typeof PayloadSchema>;
const ReturnSchema = Type.Object({
url: Type.Optional(Type.String({ description: '文件下载链接' })),
}, { description: '私聊文件URL信息' });
type ReturnType = Static<typeof ReturnSchema>;
export class GetPrivateFileUrl extends GetPacketStatusDepends<PayloadType, ReturnType> {
override actionName = ActionName.NapCat_GetPrivateFileUrl;
override payloadSchema = PayloadSchema;
override returnSchema = ReturnSchema;
override actionSummary = '获取私聊文件URL';
override actionDescription = '获取指定私聊文件的下载链接';
override actionTags = ['文件接口'];
override payloadExample = FileActionsExamples.GetPrivateFileUrl.payload;
override returnExample = FileActionsExamples.GetPrivateFileUrl.response;
async _handle (payload: PayloadType) {
const contextMsgFile = FileNapCatOneBotUUID.decode(payload.file_id);
if (contextMsgFile?.fileUUID && contextMsgFile.msgId) {
const msg = await this.core.apis.MsgApi.getMsgsByMsgId(contextMsgFile.peer, [contextMsgFile.msgId]);
const self_id = this.core.selfInfo.uid;
const file_hash = msg.msgList[0]?.elements.map(ele => ele.fileElement?.file10MMd5)[0];
if (file_hash) {
return {
url: await this.core.apis.PacketApi.pkt.operation.GetPrivateFileUrl(self_id, contextMsgFile.fileUUID, file_hash),
};
}
}
throw new Error('real fileUUID not found!');
}
}
@@ -0,0 +1,55 @@
import { GetFileBase, GetFilePayload, GetFileResponse } from './GetFile';
import { ActionName } from '@/snowcat-onebot/action/router';
import { promises as fs } from 'fs';
import { FFmpegService } from '@/snowcat-core/helper/ffmpeg/ffmpeg';
import { Static, Type } from '@sinclair/typebox';
import { FileActionsExamples } from '../example/FileActionsExamples';
const out_format_list = ['mp3', 'amr', 'wma', 'm4a', 'spx', 'ogg', 'wav', 'flac'];
const PayloadSchema = Type.Object({
file: Type.Optional(Type.String({ description: '文件路径、URL或Base64' })),
file_id: Type.Optional(Type.String({ description: '文件ID' })),
out_format: Type.String({ description: '输出格式' }),
});
type PayloadType = Static<typeof PayloadSchema>;
export default class GetRecord extends GetFileBase {
override actionName = ActionName.GetRecord;
override payloadSchema = PayloadSchema;
override actionSummary = '获取语音';
override actionDescription = '获取指定语音文件的信息,并支持格式转换';
override actionTags = ['文件接口'];
override payloadExample = FileActionsExamples.GetRecord.payload;
override returnExample = FileActionsExamples.GetRecord.response;
override async _handle (payload: PayloadType): Promise<GetFileResponse> {
const res = await super._handle(payload as GetFilePayload);
if (payload.out_format && typeof payload.out_format === 'string') {
const inputFile = res.file;
if (!inputFile) throw new Error('file not found');
if (!out_format_list.includes(payload.out_format)) {
throw new Error('转换失败 out_format 字段可能格式不正确');
}
const outputFile = `${inputFile}.${payload.out_format}`;
try {
await fs.access(inputFile);
try {
await fs.access(outputFile);
} catch {
await FFmpegService.convertAudioFmt(inputFile, outputFile, payload.out_format);
}
const base64Data = await fs.readFile(outputFile, { encoding: 'base64' });
res.file = outputFile;
res.url = outputFile;
res.base64 = base64Data;
} catch (error) {
console.error('Error processing file:', error);
throw error; // 重新抛出错误以便调用者可以处理
}
}
return res;
}
}
@@ -0,0 +1,72 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import path from 'node:path';
const richMediaList = [
'.mp4', '.mov', '.avi', '.wmv', '.mpeg', '.mpg', '.flv', '.mkv',
'.png', '.gif', '.jpg', '.jpeg', '.webp', '.bmp',
];
export const CreateFlashTaskPayloadSchema = Type.Object({
files: Type.Union([
Type.Array(Type.String()),
Type.String(),
], { description: '文件列表或单个文件路径' }),
name: Type.Optional(Type.String({ description: '任务名称' })),
thumb_path: Type.Optional(Type.String({ description: '缩略图路径' })),
});
export type CreateFlashTaskPayload = Static<typeof CreateFlashTaskPayloadSchema>;
export class CreateFlashTask extends OneBotAction<CreateFlashTaskPayload, any> {
override actionName = ActionName.CreateFlashTask;
override payloadSchema = CreateFlashTaskPayloadSchema;
override returnSchema = Type.Any({ description: '任务创建结果' });
override actionSummary = '创建闪传任务';
override actionTags = ['文件扩展'];
override payloadExample = {
files: 'C:\\test.jpg',
name: 'test_task',
};
override returnExample = {
task_id: 'task_123',
};
async _handle (payload: CreateFlashTaskPayload) {
const fileList = Array.isArray(payload.files) ? payload.files : [payload.files];
let thumbPath: string = '';
if (fileList.length === 1) {
// 我是真没hook到那种合并的缩略图是哪个方法产生的,暂时不实现(怀疑是js直接canvas渲染的!!) // 确认了猜想
const filePath = fileList[0];
if (filePath === undefined) {
return {};
}
const ext = path.extname(filePath).toLowerCase();
if (richMediaList.includes(ext)) {
try {
const res = await this.core.apis.FlashApi.createFileThumbnail(filePath);
if (res && typeof res === 'object' && 'result' in res && res.result === 0) {
thumbPath = res.targetPath as string;
}
} catch (_e) {
}
}
}
function toPlatformPath (inputPath: string) {
const unifiedPath = inputPath.replace(/[\\/]/g, path.sep);
return path.normalize(unifiedPath);
}
let normalPath: string;
if (payload.thumb_path !== undefined) {
normalPath = path.normalize(payload.thumb_path);
} else {
normalPath = toPlatformPath(thumbPath);
}
return await this.core.apis.FlashApi.createFlashTransferUploadTask(fileList, normalPath, payload.name || '');
}
}
@@ -0,0 +1,27 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const DownloadFilesetPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
});
export type DownloadFilesetPayload = Static<typeof DownloadFilesetPayloadSchema>;
export class DownloadFileset extends OneBotAction<DownloadFilesetPayload, any> {
override actionName = ActionName.DownloadFileset;
override payloadSchema = DownloadFilesetPayloadSchema;
override returnSchema = Type.Any({ description: '下载结果' });
override actionSummary = '下载文件集';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
};
override returnExample = null;
async _handle (payload: DownloadFilesetPayload) {
// 默认路径 / fileset_id /为下载路径
return await this.core.apis.FlashApi.downloadFileSetBySetId(payload.fileset_id);
}
}
@@ -0,0 +1,34 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const GetFilesetIdPayloadSchema = Type.Object({
share_code: Type.String({ description: '分享码或分享链接' }),
});
export type GetFilesetIdPayload = Static<typeof GetFilesetIdPayloadSchema>;
export class GetFilesetId extends OneBotAction<GetFilesetIdPayload, { fileset_id: string }> {
override actionName = ActionName.GetFilesetId;
override payloadSchema = GetFilesetIdPayloadSchema;
override returnSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
});
override actionSummary = '获取文件集 ID';
override actionTags = ['文件扩展'];
override payloadExample = {
share_code: '123456',
};
override returnExample = {
fileset_id: 'set_123',
};
async _handle (payload: GetFilesetIdPayload) {
// 适配share_link 防止被传 Link无法解析
const code = payload.share_code.includes('=') ? payload.share_code.split('=').slice(1).join('=') : payload.share_code;
const result = await this.core.apis.FlashApi.fromShareLinkFindSetId(code);
return { fileset_id: result.fileSetId };
}
}
@@ -0,0 +1,29 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const GetFilesetInfoPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
});
export type GetFilesetInfoPayload = Static<typeof GetFilesetInfoPayloadSchema>;
export class GetFilesetInfo extends OneBotAction<GetFilesetInfoPayload, any> {
override actionName = ActionName.GetFilesetInfo;
override payloadSchema = GetFilesetInfoPayloadSchema;
override returnSchema = Type.Any({ description: '文件集信息' });
override actionSummary = '获取文件集信息';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
};
override returnExample = {
fileset_id: 'set_123',
file_list: [],
};
async _handle (payload: GetFilesetInfoPayload) {
return await this.core.apis.FlashApi.getFileSetIndoBySetId(payload.fileset_id);
}
}
@@ -0,0 +1,31 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const GetFlashFileListPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
});
export type GetFlashFileListPayload = Static<typeof GetFlashFileListPayloadSchema>;
export class GetFlashFileList extends OneBotAction<GetFlashFileListPayload, any> {
override actionName = ActionName.GetFlashFileList;
override payloadSchema = GetFlashFileListPayloadSchema;
override returnSchema = Type.Any({ description: '文件列表' });
override actionSummary = '获取闪传文件列表';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
};
override returnExample = [
{
file_name: 'test.jpg',
size: 1024,
},
];
async _handle (payload: GetFlashFileListPayload) {
return await this.core.apis.FlashApi.getFileListBySetId(payload.fileset_id);
}
}
@@ -0,0 +1,34 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const GetFlashFileUrlPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
file_name: Type.Optional(Type.String({ description: '文件名' })),
file_index: Type.Optional(Type.Number({ description: '文件索引' })),
});
export type GetFlashFileUrlPayload = Static<typeof GetFlashFileUrlPayloadSchema>;
export class GetFlashFileUrl extends OneBotAction<GetFlashFileUrlPayload, any> {
override actionName = ActionName.GetFlashFileUrl;
override payloadSchema = GetFlashFileUrlPayloadSchema;
override returnSchema = Type.Any({ description: '文件下载链接' });
override actionSummary = '获取闪传文件链接';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
};
override returnExample = {
url: 'http://example.com/flash.jpg',
};
async _handle (payload: GetFlashFileUrlPayload) {
// 文件的索引依旧从0开始
return await this.core.apis.FlashApi.getFileTransUrl(payload.fileset_id, {
fileName: payload.file_name,
fileIndex: payload.file_index,
});
}
}
@@ -0,0 +1,26 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
export const GetShareLinkPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
});
export type GetShareLinkPayload = Static<typeof GetShareLinkPayloadSchema>;
export class GetShareLink extends OneBotAction<GetShareLinkPayload, any> {
override actionName = ActionName.GetShareLink;
override payloadSchema = GetShareLinkPayloadSchema;
override returnSchema = Type.Any({ description: '分享链接' });
override actionSummary = '获取文件分享链接';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
};
override returnExample = 'http://example.com/share';
async _handle (payload: GetShareLinkPayload) {
return await this.core.apis.FlashApi.getShareLinkBySetId(payload.fileset_id);
}
}
@@ -0,0 +1,50 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType, Peer } from 'snowcat-core/types';
export const SendFlashMsgPayloadSchema = Type.Object({
fileset_id: Type.String({ description: '文件集 ID' }),
user_id: Type.Optional(Type.String({ description: '用户 QQ' })),
group_id: Type.Optional(Type.String({ description: '群号' })),
});
export type SendFlashMsgPayload = Static<typeof SendFlashMsgPayloadSchema>;
export class SendFlashMsg extends OneBotAction<SendFlashMsgPayload, any> {
override actionName = ActionName.SendFlashMsg;
override payloadSchema = SendFlashMsgPayloadSchema;
override returnSchema = Type.Any({ description: '发送结果' });
override actionSummary = '发送闪传消息';
override actionTags = ['文件扩展'];
override payloadExample = {
fileset_id: 'set_123',
user_id: '123456789',
};
override returnExample = {
message_id: 123456,
};
async _handle (payload: SendFlashMsgPayload) {
let peer: Peer;
if (payload.group_id) {
peer = { chatType: ChatType.KCHATTYPEGROUP, peerUid: payload.group_id.toString() };
} else if (payload.user_id) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
// 可能需要更严格的判断
const isBuddy = await this.core.apis.FriendApi.isBuddy(uid);
peer = {
chatType: isBuddy ? ChatType.KCHATTYPEC2C : ChatType.KCHATTYPETEMPC2CFROMGROUP,
peerUid: uid,
};
} else {
throw new Error('user_id or group_id is required');
}
return await this.core.apis.FlashApi.sendFlashMessage(payload.fileset_id, peer);
}
}
@@ -0,0 +1,35 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const CancelOnlineFilePayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
msg_id: Type.String({ description: '消息 ID' }),
});
export type CancelOnlineFilePayload = Static<typeof CancelOnlineFilePayloadSchema>;
export class CancelOnlineFile extends OneBotAction<CancelOnlineFilePayload, any> {
override actionName = ActionName.CancelOnlineFile;
override payloadSchema = CancelOnlineFilePayloadSchema;
override returnSchema = Type.Any({ description: '取消结果' });
override actionSummary = '取消在线文件';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
msg_id: '123',
};
override returnExample = null;
async _handle (payload: CancelOnlineFilePayload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
// 仅私聊
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
return await this.core.apis.OnlineApi.cancelMyOnlineFileMsg(peer, payload.msg_id);
}
}
@@ -0,0 +1,33 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const GetOnlineFileMessagesPayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
});
export type GetOnlineFileMessagesPayload = Static<typeof GetOnlineFileMessagesPayloadSchema>;
export class GetOnlineFileMessages extends OneBotAction<GetOnlineFileMessagesPayload, any> {
override actionName = ActionName.GetOnlineFileMessages;
override payloadSchema = GetOnlineFileMessagesPayloadSchema;
override returnSchema = Type.Any({ description: '在线文件消息列表' });
override actionSummary = '获取在线文件消息';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
};
override returnExample = [];
async _handle (payload: GetOnlineFileMessagesPayload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
// 仅私聊
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
return await this.core.apis.OnlineApi.getOnlineFileMsg(peer);
}
}
@@ -0,0 +1,37 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const ReceiveOnlineFilePayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
msg_id: Type.String({ description: '消息 ID' }),
element_id: Type.String({ description: '元素 ID' }),
});
export type ReceiveOnlineFilePayload = Static<typeof ReceiveOnlineFilePayloadSchema>;
export class ReceiveOnlineFile extends OneBotAction<ReceiveOnlineFilePayload, any> {
override actionName = ActionName.ReceiveOnlineFile;
override payloadSchema = ReceiveOnlineFilePayloadSchema;
override returnSchema = Type.Any({ description: '接收结果' });
override actionSummary = '接收在线文件';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
msg_id: '123',
save_path: 'C:\\save',
};
override returnExample = null;
async _handle (payload: ReceiveOnlineFilePayload) {
// 默认下载路径
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
return await this.core.apis.OnlineApi.receiveOnlineFileOrFolder(peer, payload.msg_id, payload.element_id);
}
}
@@ -0,0 +1,35 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const RefuseOnlineFilePayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
msg_id: Type.String({ description: '消息 ID' }),
element_id: Type.String({ description: '元素 ID' }),
});
export type RefuseOnlineFilePayload = Static<typeof RefuseOnlineFilePayloadSchema>;
export class RefuseOnlineFile extends OneBotAction<RefuseOnlineFilePayload, any> {
override actionName = ActionName.RefuseOnlineFile;
override payloadSchema = RefuseOnlineFilePayloadSchema;
override returnSchema = Type.Any({ description: '拒绝结果' });
override actionSummary = '拒绝在线文件';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
msg_id: '123',
};
override returnExample = null;
async _handle (payload: RefuseOnlineFilePayload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
return await this.core.apis.OnlineApi.refuseOnlineFileMsg(peer, payload.msg_id, payload.element_id);
}
}
@@ -0,0 +1,38 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const SendOnlineFilePayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
file_path: Type.String({ description: '本地文件路径' }),
file_name: Type.Optional(Type.String({ description: '文件名 (可选)' })),
});
export type SendOnlineFilePayload = Static<typeof SendOnlineFilePayloadSchema>;
export class SendOnlineFile extends OneBotAction<SendOnlineFilePayload, any> {
override actionName = ActionName.SendOnlineFile;
override payloadSchema = SendOnlineFilePayloadSchema;
override returnSchema = Type.Any({ description: '发送结果' });
override actionSummary = '发送在线文件';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
file_path: 'C:\\path\\to\\file.txt',
file_name: 'test.txt',
};
override returnExample = null;
async _handle (payload: SendOnlineFilePayload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
// 仅私聊
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
const fileName = payload.file_name || '';
return await this.core.apis.OnlineApi.sendOnlineFile(peer, payload.file_path, fileName);
}
}
@@ -0,0 +1,35 @@
import { OneBotAction } from '@/snowcat-onebot/action/OneBotAction';
import { ActionName } from '@/snowcat-onebot/action/router';
import { Static, Type } from '@sinclair/typebox';
import { ChatType } from 'snowcat-core/types';
export const SendOnlineFolderPayloadSchema = Type.Object({
user_id: Type.String({ description: '用户 QQ' }),
folder_path: Type.String({ description: '本地文件夹路径' }),
folder_name: Type.Optional(Type.String({ description: '文件夹名称 (可选)' })),
});
export type SendOnlineFolderPayload = Static<typeof SendOnlineFolderPayloadSchema>;
export class SendOnlineFolder extends OneBotAction<SendOnlineFolderPayload, any> {
override actionName = ActionName.SendOnlineFolder;
override payloadSchema = SendOnlineFolderPayloadSchema;
override returnSchema = Type.Any({ description: '发送结果' });
override actionSummary = '发送在线文件夹';
override actionTags = ['文件扩展'];
override payloadExample = {
user_id: '123456789',
folder_path: 'C:\\path\\to\\folder',
};
override returnExample = null;
async _handle (payload: SendOnlineFolderPayload) {
const uid = await this.core.apis.UserApi.getUidByUinV2(payload.user_id.toString());
if (!uid) throw new Error('User not found');
const peer = { chatType: ChatType.KCHATTYPEC2C, peerUid: uid };
return await this.core.apis.OnlineApi.sendOnlineFolder(peer, payload.folder_path, payload.folder_name);
}
}