mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 14:41:14 +00:00
* feat: implement QQ online file transfer and flash transfer support * fix: change OnlineFile OB11Message data * fix: add fileSize and isDir to OB11MessageOnlineFile * fix: resolve typescript strict mode errors
265 lines
7.5 KiB
TypeScript
265 lines
7.5 KiB
TypeScript
import { GeneralCallResult, InstanceContext, NapCatCore } from '@/napcat-core';
|
||
import {
|
||
createFlashTransferResult,
|
||
FileListResponse,
|
||
FlashFileSetInfo,
|
||
SendStatus,
|
||
} 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 上传文件绝对路径的列表,可以是文件夹!!
|
||
*/
|
||
async createFlashTransferUploadTask (fileListToUpload: 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
|
||
uploaders: [{
|
||
uin: selfInfo.uin,
|
||
uid: selfInfo.uid,
|
||
sendEntrance: '',
|
||
nickname: selfInfo.nick,
|
||
}],
|
||
paths: fileListToUpload,
|
||
};
|
||
|
||
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,
|
||
};
|
||
}
|
||
}
|
||
}
|