Files
天天的鸟蛋蛋 b9655a5ab1 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
2026-11-12 06:27:36 +08:00

308 lines
8.7 KiB
TypeScript

import { GeneralCallResult, InstanceContext, SnowCatCore } from '@/snowcat-core';
import {
createFlashTransferResult,
FileListResponse,
FlashFileSetInfo,
SendStatus,
UploadSceneType,
} from '@/snowcat-core/data/flash';
import { Peer } from '@/snowcat-core/types';
export class NTQQFlashApi {
context: InstanceContext;
core: SnowCatCore;
constructor (context: InstanceContext, core: SnowCatCore) {
this.context = context;
this.core = core;
}
/**
* 发起闪传上传任务
* @param fileListToUpload 上传文件绝对路径的列表,可以是文件夹!!
* @param thumbnailPath
* @param filesetName
*/
async createFlashTransferUploadTask (fileListToUpload: string[], thumbnailPath: string, filesetName: string): Promise<GeneralCallResult & {
createFlashTransferResult: createFlashTransferResult;
seq: number;
}> {
const flashService = this.context.session.getFlashTransferService();
const timestamp: number = Date.now();
const selfInfo = this.core.selfInfo;
const fileUploadArg = {
screen: 1, // 1
name: filesetName,
uploaders: [{
uin: selfInfo.uin,
uid: selfInfo.uid,
sendEntrance: '',
nickname: selfInfo.nick,
}],
coverPath: thumbnailPath,
paths: fileListToUpload,
excludePaths: [],
expireLeftTime: 0,
isNeedDelDeviceInfo: false,
isNeedDelLocation: false,
coverOriginalInfos: [
{
path: fileListToUpload[0] || '',
thumbnailPath,
},
],
uploadSceneType: UploadSceneType.KUPLOADSCENEAIOFILESELECTOR, // 不知道怎么枚举 先硬编码吧 (PC QQ 10)
detectPrivacyInfoResult: {
exists: false,
allDetectResults: new Map(),
},
};
const uploadResult = await flashService.createFlashTransferUploadTask(timestamp, fileUploadArg);
if (uploadResult.result === 0) {
this.context.logger.log('[Flash] 发起闪传任务成功');
return uploadResult;
} else {
this.context.logger.logError('[Flash] 发起闪传上传任务失败!!');
return uploadResult;
}
}
/**
* 下载闪传文件集
* @param fileSetId
*/
async downloadFileSetBySetId (fileSetId: string): Promise<GeneralCallResult & {
extraInfo: unknown;
}> {
const flashService = this.context.session.getFlashTransferService();
const result = await flashService.startFileSetDownload(fileSetId, 1, { isIncludeCompressInnerFiles: false }); // 为了方便,暂时硬编码
if (result.result === 0) {
this.context.logger.log('[Flash] 成功开始下载文件集');
} else {
this.context.logger.logError('[Flash] 尝试下载文件集失败!');
}
return result;
}
/**
* 获取闪传的外链分享
* @param fileSetId
*/
async getShareLinkBySetId (fileSetId: string): Promise<GeneralCallResult & {
shareLink: string;
expireTimestamp: string;
}> {
const flashService = this.context.session.getFlashTransferService();
const result = await flashService.getShareLinkReq(fileSetId);
if (result.result === 0) {
this.context.logger.log('[Flash] 获取闪传外链分享成功:', result.shareLink);
} else {
this.context.logger.logError('[Flash] 获取闪传外链失败!!');
}
return result;
}
/**
* 从分享外链获取文件集id
* @param shareCode
*/
async fromShareLinkFindSetId (shareCode: string): Promise<GeneralCallResult & {
fileSetId: string;
}> {
const flashService = this.context.session.getFlashTransferService();
const result = await flashService.getFileSetIdByCode(shareCode);
if (result.result === 0) {
this.context.logger.log('[Flash] 获取shareCode的文件集Id成功!');
} else {
this.context.logger.logError('[Flash] 获取文件集ID失败!!');
}
return result;
}
/**
* 获取fileSet的文件结构信息 (未来可能需要深度遍历)
* == 注意返回结构和其它的不同,没有GeneralCallResult!!! ==
* @param fileSetId
*/
async getFileListBySetId (fileSetId: string): Promise<FileListResponse> {
const flashService = this.context.session.getFlashTransferService();
const requestArg = {
seq: 0,
fileSetId,
isUseCache: false,
sceneType: 1, // 硬编码
reqInfos: [
{
count: 18, // 18 ??
paginationInfo: {},
parentId: '',
reqIndexPath: '',
reqDepth: 1,
filterCondition: {
fileCategory: 0,
filterType: 0,
},
sortConditions: [
{
sortField: 0,
sortOrder: 0,
},
],
isNeedPhysicalInfoReady: false,
},
],
};
const result = await flashService.getFileList(requestArg);
if (result.rsp.result === 0) {
this.context.logger.log('[Flash] 获取fileSet文件信息成功!');
return result.rsp;
} else {
this.context.logger.logError(`[Flash] 获取文件信息失败:ErrMsg: ${result.rsp.errMs}`);
return result.rsp;
}
}
/**
* 获取闪传文件集合信息
* @param fileSetId
*/
async getFileSetIndoBySetId (fileSetId: string): Promise<GeneralCallResult & {
seq: number;
isCache: boolean;
fileSet: FlashFileSetInfo;
}> {
const flashService = this.context.session.getFlashTransferService();
const requestArg = {
fileSetId,
};
const result = await flashService.getFileSet(requestArg);
if (result.result === 0) {
this.context.logger.log('[Flash] 获取闪传文件集信息成功!');
} else {
this.context.logger.logError('[Flash] 获取闪传文件信息失败!!');
}
return result;
}
/**
* 发送闪传消息(私聊/群聊)
* @param fileSetId
* @param peer
*/
async sendFlashMessage (fileSetId: string, peer: Peer): Promise<{
errCode: number,
errMsg: string,
rsp: {
sendStatus: SendStatus[];
};
}> {
const flashService = this.context.session.getFlashTransferService();
const target = {
destUid: peer.peerUid,
destType: peer.chatType,
// destUin: peer.peerUin,
};
const requestsArg = {
fileSetId,
targets: [target],
};
const result = await flashService.sendFlashTransferMsg(requestsArg);
if (result.errCode === 0) {
this.context.logger.log('[Flash] 消息发送成功');
} else {
this.context.logger.logError(`[Flash] 消息发送失败!!原因:${result.errMsg}`);
}
return result;
}
/**
* 获取闪传文件集中某个文件的下载URL(外链)
* @param fileSetId
* @param options
*/
async getFileTransUrl (fileSetId: string, options: { fileName?: string; fileIndex?: number; }): Promise<GeneralCallResult & {
transferUrl: string;
}> {
const flashService = this.context.session.getFlashTransferService();
const result = await this.getFileListBySetId(fileSetId);
const { fileName, fileIndex } = options;
let targetFile: any;
let file: any;
const allFolder = result.fileLists;
// eslint-disable-next-line no-labels
searchLoop: for (const folder of allFolder) {
const fileList = folder.fileList;
for (let i = 0; i < fileList.length; i++) {
file = fileList[i];
if (fileName !== undefined && file.name === fileName) {
targetFile = file;
// eslint-disable-next-line no-labels
break searchLoop;
}
if (fileIndex !== undefined && i === fileIndex) {
targetFile = file;
// eslint-disable-next-line no-labels
break searchLoop;
}
}
}
if (targetFile === undefined) {
this.context.logger.logError('[Flash] 未找到对应文件!!');
return {
result: -1,
errMsg: '未找到对应文件',
transferUrl: '',
};
} else {
this.context.logger.log('[Flash] 找到对应文件,准备尝试获取传输链接');
const res = await flashService.startFileTransferUrl(targetFile);
return {
result: 0,
errMsg: '',
transferUrl: res.url,
};
}
}
async createFileThumbnail (filePath: string): Promise<any> {
const msgService = this.context.session.getMsgService();
const savePath = msgService.getFileThumbSavePathForSend(750, true);
const result = await this.core.util.createThumbnailImage(
'flashtransfer',
filePath,
savePath,
{
width: 520,
height: 520,
},
'jpeg',
null
);
if (result.result === 0) {
this.context.logger.log('获取缩略图成功!!');
result.targetPath = savePath;
return result;
}
return result;
}
}