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
308 lines
8.7 KiB
TypeScript
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;
|
|
}
|
|
}
|