mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 14:41:14 +00:00
* feat: support thumbnail for flash-transfer * fix: fix get thumbnail path unknown type error * Refactor flash module types and enums Standardized TypeScript interface property formatting in flash.ts, flash data, and wrapper files. Introduced the UploadSceneType enum for upload scene types, replacing hardcoded numeric values. Improved type annotations and consistency across the flash API and related data structures. * Update arg type in NodeQQNTWrapperUtil interface Changed the type of the 'arg' parameter in the NodeQQNTWrapperUtil interface from optional number to 'number | null | undefined' for improved type clarity. * Refactor flash scene type and update method params Introduced BusiScene enum for sceneType in FileListInfoRequests to improve type safety. Renamed parameters in getFileThumbSavePathForSend for better clarity. * Refactor downloadSceneType to use enum type Replaced numeric downloadSceneType fields with the DownloadSceneType enum in relevant interfaces. Updated NodeIKernelFlashTransferService method signatures to use DownloadSceneType for download operations, improving type safety and code clarity. * refactor: remove thumbnail dependency for QQ resource icons * fix: remove useless console.log --------- Co-authored-by: 手瓜一十雪 <nanaeonn@outlook.com>
308 lines
8.7 KiB
TypeScript
308 lines
8.7 KiB
TypeScript
import { GeneralCallResult, InstanceContext, NapCatCore } from '@/napcat-core';
|
||
import {
|
||
createFlashTransferResult,
|
||
FileListResponse,
|
||
FlashFileSetInfo,
|
||
SendStatus,
|
||
UploadSceneType,
|
||
} from '@/napcat-core/data/flash';
|
||
import { Peer } from '@/napcat-core/types';
|
||
|
||
export class NTQQFlashApi {
|
||
context: InstanceContext;
|
||
core: NapCatCore;
|
||
|
||
constructor (context: InstanceContext, core: NapCatCore) {
|
||
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;
|
||
}
|
||
}
|