mirror of
https://github.com/NapNeko/NapCatQQ.git
synced 2026-02-04 06:31:13 +00:00
Merge branch 'main' of https://github.com/NapNeko/NapCatQQ
This commit is contained in:
commit
6ec5bbeddf
264
packages/napcat-core/apis/flash.ts
Normal file
264
packages/napcat-core/apis/flash.ts
Normal file
@ -0,0 +1,264 @@
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -7,3 +7,5 @@ export * from './webapi';
|
||||
export * from './system';
|
||||
export * from './packet';
|
||||
export * from './file';
|
||||
export * from './online';
|
||||
export * from './flash';
|
||||
|
||||
@ -245,7 +245,7 @@ export class NTQQMsgApi {
|
||||
[
|
||||
'0',
|
||||
peer,
|
||||
msgElements,
|
||||
msgElements as any,
|
||||
new Map(),
|
||||
],
|
||||
(ret) => ret.result === 0,
|
||||
|
||||
239
packages/napcat-core/apis/online.ts
Normal file
239
packages/napcat-core/apis/online.ts
Normal file
@ -0,0 +1,239 @@
|
||||
import { InstanceContext, NapCatCore } from '@/napcat-core';
|
||||
import { Peer } from '@/napcat-core/types';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import { GeneralCallResultStatus } from '@/napcat-core/services/common';
|
||||
import { sleep } from '@/napcat-common/src/helper';
|
||||
|
||||
const normalizePath = (p: string) => path.normalize(p).toLowerCase();
|
||||
|
||||
export class NTQQOnlineApi {
|
||||
context: InstanceContext;
|
||||
core: NapCatCore;
|
||||
|
||||
constructor (context: InstanceContext, core: NapCatCore) {
|
||||
this.context = context;
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里不等待node返回,因为the fuck wrapper.node 根本不返回(会卡死不知道为什么)!!! 只能手动查询判断死活
|
||||
* @param peer
|
||||
* @param filePath
|
||||
* @param fileName
|
||||
*/
|
||||
async sendOnlineFile (peer: Peer, filePath: string, fileName: string): Promise<any> {
|
||||
if (!fs.existsSync(filePath)) {
|
||||
throw new Error(`[NapCat] 文件不存在: ${filePath}`);
|
||||
}
|
||||
const actualFileName = fileName || path.basename(filePath);
|
||||
const fileSize = fs.statSync(filePath).size.toString();
|
||||
|
||||
const fileElementToSend = [{
|
||||
elementType: 23,
|
||||
fileElement: {
|
||||
fileName: actualFileName,
|
||||
filePath,
|
||||
fileSize,
|
||||
},
|
||||
}];
|
||||
|
||||
const msgService = this.context.session.getMsgService();
|
||||
const startTime = Math.floor(Date.now() / 1000) - 2; // 容错时间窗口
|
||||
|
||||
msgService.sendMsg('0', peer, fileElementToSend, new Map()).catch((_e: any) => {
|
||||
});
|
||||
|
||||
const maxRetries = 10;
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
await sleep(1000);
|
||||
retryCount++;
|
||||
|
||||
try {
|
||||
const msgListResult = await msgService.getOnlineFileMsgs(peer);
|
||||
|
||||
const msgs = msgListResult?.msgList || [];
|
||||
|
||||
const foundMsg = msgs.find((msg: any) => {
|
||||
if (parseInt(msg.msgTime) < startTime) return false;
|
||||
|
||||
const validElement = msg.elements.find((el: any) => {
|
||||
if (el.elementType !== 23 || !el.fileElement) return false;
|
||||
|
||||
const isNameMatch = el.fileElement.fileName === actualFileName;
|
||||
const isPathMatch = normalizePath(el.fileElement.filePath) === normalizePath(filePath);
|
||||
|
||||
return isNameMatch && isPathMatch;
|
||||
});
|
||||
|
||||
return !!validElement;
|
||||
});
|
||||
|
||||
if (foundMsg) {
|
||||
const targetElement = foundMsg.elements.find((el: any) => el.elementType === 23);
|
||||
this.context.logger.log('[OnlineFile] 在线文件发送成功!');
|
||||
return {
|
||||
result: GeneralCallResultStatus.OK,
|
||||
errMsg: '',
|
||||
msgId: foundMsg.msgId,
|
||||
elementId: targetElement?.elementId || '',
|
||||
};
|
||||
}
|
||||
} catch (_e) {
|
||||
}
|
||||
}
|
||||
this.context.logger.logError('[OnlineFile] 在线文件发送失败!!!');
|
||||
return {
|
||||
result: GeneralCallResultStatus.ERROR,
|
||||
errMsg: '[NapCat] Send Online File Timeout: Message not found in history.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送在线文件夹
|
||||
* @param peer
|
||||
* @param folderPath
|
||||
* @param folderName
|
||||
*/
|
||||
async sendOnlineFolder (peer: Peer, folderPath: string, folderName?: string): Promise<any> {
|
||||
const actualFolderName = folderName || path.basename(folderPath);
|
||||
|
||||
if (!fs.existsSync(folderPath)) {
|
||||
return { result: GeneralCallResultStatus.ERROR, errMsg: `Folder not found: ${folderPath}` };
|
||||
}
|
||||
|
||||
if (!fs.statSync(folderPath).isDirectory()) {
|
||||
return { result: GeneralCallResultStatus.ERROR, errMsg: `Path is not a directory: ${folderPath}` };
|
||||
}
|
||||
const folderElementItem = {
|
||||
elementType: 30,
|
||||
elementId: '',
|
||||
fileElement: {
|
||||
fileName: actualFolderName,
|
||||
filePath: folderPath,
|
||||
},
|
||||
} as any;
|
||||
|
||||
const msgService = this.context.session.getMsgService();
|
||||
const startTime = Math.floor(Date.now() / 1000) - 2;
|
||||
msgService.sendMsg('0', peer, [folderElementItem], new Map()).catch((_e: any) => {
|
||||
|
||||
});
|
||||
|
||||
const maxRetries = 10;
|
||||
let retryCount = 0;
|
||||
|
||||
while (retryCount < maxRetries) {
|
||||
await sleep(1000);
|
||||
retryCount++;
|
||||
|
||||
try {
|
||||
const msgListResult = await msgService.getOnlineFileMsgs(peer);
|
||||
const msgs = msgListResult?.msgList || [];
|
||||
|
||||
const foundMsg = msgs.find((msg: any) => {
|
||||
if (parseInt(msg.msgTime) < startTime) return false;
|
||||
|
||||
const validElement = msg.elements.find((el: any) => {
|
||||
if (el.elementType !== 30 || !el.fileElement) return false;
|
||||
|
||||
const isNameMatch = el.fileElement.fileName === actualFolderName;
|
||||
const isPathMatch = normalizePath(el.fileElement.filePath) === normalizePath(folderPath);
|
||||
|
||||
return isNameMatch && isPathMatch;
|
||||
});
|
||||
return !!validElement;
|
||||
});
|
||||
|
||||
if (foundMsg) {
|
||||
const targetElement = foundMsg.elements.find((el: any) => el.elementType === 30);
|
||||
this.context.logger.log('[OnlineFile] 在线文件夹发送成功!');
|
||||
return {
|
||||
result: GeneralCallResultStatus.OK,
|
||||
errMsg: '',
|
||||
msgId: foundMsg.msgId,
|
||||
elementId: targetElement?.elementId || '',
|
||||
};
|
||||
}
|
||||
} catch (_e) {
|
||||
|
||||
}
|
||||
}
|
||||
this.context.logger.logError('[OnlineFile] 在线文件发送失败!!!');
|
||||
return {
|
||||
result: GeneralCallResultStatus.ERROR,
|
||||
errMsg: '[NapCat] Send Online Folder Timeout: Message not found in history.',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取好友的在线文件消息
|
||||
* @param peer
|
||||
*/
|
||||
async getOnlineFileMsg (peer: Peer) : Promise<any> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
return await msgService.getOnlineFileMsgs(peer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消在线文件的发送
|
||||
* @param peer
|
||||
* @param msgId
|
||||
*/
|
||||
async cancelMyOnlineFileMsg (peer: Peer, msgId: string) : Promise<void> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
await msgService.cancelSendMsg(peer, msgId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 拒绝接收在线文件
|
||||
* @param peer
|
||||
* @param msgId
|
||||
* @param elementId
|
||||
*/
|
||||
async refuseOnlineFileMsg (peer: Peer, msgId: string, elementId: string) : Promise<void> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
const arrToSend = {
|
||||
msgId,
|
||||
peerUid: peer.peerUid,
|
||||
chatType: 1,
|
||||
elementId,
|
||||
downloadType: 1,
|
||||
downSourceType: 1,
|
||||
};
|
||||
|
||||
await msgService.refuseGetRichMediaElement(arrToSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* 接收在线文件/文件夹
|
||||
* @param peer
|
||||
* @param msgId
|
||||
* @param elementId
|
||||
* @constructor
|
||||
*/
|
||||
async receiveOnlineFileOrFolder (peer: Peer, msgId: string, elementId: string) : Promise<any> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
const arrToSend = {
|
||||
msgId,
|
||||
peerUid: peer.peerUid,
|
||||
chatType: 1,
|
||||
elementId,
|
||||
downSourceType: 1,
|
||||
downloadType: 1,
|
||||
};
|
||||
return await msgService.getRichMediaElement(arrToSend);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线文件/文件夹转离线
|
||||
* @param peer
|
||||
* @param msgId
|
||||
*/
|
||||
async switchFileToOffline (peer: Peer, msgId: string) : Promise<void> {
|
||||
const msgService = this.context.session.getMsgService();
|
||||
await msgService.switchToOfflineSendMsg(peer, msgId);
|
||||
}
|
||||
}
|
||||
324
packages/napcat-core/data/flash.ts
Normal file
324
packages/napcat-core/data/flash.ts
Normal file
@ -0,0 +1,324 @@
|
||||
export interface FlashBaseRequest {
|
||||
fileSetId: string
|
||||
}
|
||||
|
||||
export interface UploaderInfo {
|
||||
uin: string,
|
||||
nickname: string,
|
||||
uid: string,
|
||||
sendEntrance: string, // ""
|
||||
}
|
||||
|
||||
export interface thumbnailInfo {
|
||||
id: string,
|
||||
url: {
|
||||
spec: number,
|
||||
uri: string,
|
||||
}[],
|
||||
localCachePath: string,
|
||||
}
|
||||
|
||||
export interface SendTarget {
|
||||
destType: number // 1私聊
|
||||
destUin?: string,
|
||||
destUid: string,
|
||||
}
|
||||
|
||||
export interface SendTargetRequests {
|
||||
fileSetId: string
|
||||
targets: SendTarget[]
|
||||
}
|
||||
|
||||
export interface DownloadStatusInfo {
|
||||
result: number; // 0
|
||||
fileSetId: string;
|
||||
status: number;
|
||||
info: {
|
||||
curDownLoadFailFileNum: number,
|
||||
curDownLoadedPauseFileNum: number,
|
||||
curDownLoadedFileNum: number,
|
||||
curRealDownLoadedFileNum: number,
|
||||
curDownloadingFileNum: number,
|
||||
totalDownLoadedFileNum: number,
|
||||
curDownLoadedBytes: string, // "0"
|
||||
totalDownLoadedBytes: string,
|
||||
curSpeedBps: number,
|
||||
avgSpeedBps: number,
|
||||
maxSpeedBps: number,
|
||||
remainDownLoadSeconds: number,
|
||||
failFileIdList: [],
|
||||
allFileIdList: [],
|
||||
hasNormalFileDownloading: boolean,
|
||||
onlyCompressInnerFileDownloading: boolean,
|
||||
isAllFileAlreadyDownloaded: boolean,
|
||||
saveFileSetDir: string,
|
||||
allWaitingStatusTask: boolean,
|
||||
downloadSceneType: number,
|
||||
retryCount: number,
|
||||
statisticInfo: {
|
||||
downloadTaskId: string,
|
||||
downloadFilesetName: string,
|
||||
downloadFileTypeDistribution: string,
|
||||
downloadFileSizeDistribution: string
|
||||
},
|
||||
albumStorageFailImageNum: number,
|
||||
albumStorageFailVideoNum: number,
|
||||
albumStorageFailFileIdList: [],
|
||||
albumStorageSucImageNum: number,
|
||||
albumStorageSucVideoNum: number,
|
||||
albumStorageSucFileIdList: [],
|
||||
albumStorageFileNum: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface physicalInfo {
|
||||
id: string,
|
||||
url: string,
|
||||
status: number, // 2 已下载
|
||||
processing: string,
|
||||
localPath: string,
|
||||
width: 0,
|
||||
height: 0,
|
||||
time: number,
|
||||
}
|
||||
|
||||
export interface downloadInfo {
|
||||
status: number,
|
||||
curDownLoadBytes: string,
|
||||
totalFileBytes: string,
|
||||
errorCode: number,
|
||||
}
|
||||
|
||||
export interface uploadInfo {
|
||||
uploadedBytes: string,
|
||||
errorCode: number,
|
||||
svrRrrCode: number,
|
||||
errMsg: string,
|
||||
isNeedDelDeviceInfo: boolean,
|
||||
thumbnailUploadState: number
|
||||
isSecondHit: boolean,
|
||||
hasModifiedErr: boolean,
|
||||
}
|
||||
|
||||
export interface folderUploadInfo {
|
||||
totalUploadedFileSize: string
|
||||
successCount: number
|
||||
failedCount: number
|
||||
}
|
||||
|
||||
export interface folderDownloadInfo {
|
||||
totalDownloadedFileSize: string
|
||||
totalFileSize: string
|
||||
totalDownloadFileCount: number
|
||||
successCount: number
|
||||
failedCount: number
|
||||
pausedCount: number
|
||||
cancelCount: number
|
||||
downloadingCount: number
|
||||
partialDownloadCount: number
|
||||
curLevelDownloadedFileCount: number
|
||||
curLevelUnDownloadedFileCount: number
|
||||
}
|
||||
|
||||
export interface compressFileFolderInfo {
|
||||
downloadStatus: number
|
||||
saveFileDirPath: string
|
||||
totalFileCount: string
|
||||
totalFileSize: string
|
||||
}
|
||||
|
||||
export interface albumStorgeInfo {
|
||||
status: number
|
||||
localIdentifier: string
|
||||
errorCode: number
|
||||
timeCost: number
|
||||
}
|
||||
|
||||
export interface FlashOneFileInfo {
|
||||
fileSetId: string
|
||||
cliFileId: string // client?? 或许可以换取url
|
||||
compressedFileFolderId: string
|
||||
archiveIndex: 0
|
||||
indexPath: string
|
||||
isDir: boolean // 文件或者文件夹!!
|
||||
parentId: string
|
||||
depth: number // 1
|
||||
cliFileIndex: number
|
||||
fileType: number // 枚举!! 已完成枚举!!
|
||||
name: string
|
||||
namePinyin: string
|
||||
isCover: boolean
|
||||
isCoverOriginal: boolean
|
||||
fileSize: string
|
||||
fileCount: number
|
||||
thumbnail: thumbnailInfo
|
||||
physical: physicalInfo
|
||||
srvFileId: string // service?? 服务器上面的id吗?
|
||||
srvParentFileId: string
|
||||
svrLastUpdateTimestamp: string
|
||||
downloadInfo: downloadInfo
|
||||
saveFilePath: string
|
||||
search_relative_path: string
|
||||
disk_relative_path: string
|
||||
uploadInfo: uploadInfo
|
||||
status: number
|
||||
uploadStatus: number // 3已上传成功
|
||||
downloadStatus: number // 0未下载
|
||||
folderUploadInfo: folderUploadInfo
|
||||
folderDownloadInfo: folderDownloadInfo
|
||||
sha1: string
|
||||
bookmark: string
|
||||
compressFileFolderInfo: compressFileFolderInfo
|
||||
uploadPauseReason: string
|
||||
downloadPauseReason: string
|
||||
filePhysicalSize: string
|
||||
thumbnail_sha1: string | null
|
||||
thumbnail_size: string | null
|
||||
needAlbumStorage: boolean
|
||||
albumStorageInfo: albumStorgeInfo
|
||||
}
|
||||
|
||||
export interface fileListsInfo {
|
||||
parentId: string,
|
||||
depth: number, // 1
|
||||
fileList: FlashOneFileInfo[],
|
||||
paginationInfo: {}
|
||||
isEnd: boolean,
|
||||
isCache: boolean,
|
||||
}
|
||||
|
||||
export interface FileListResponse {
|
||||
seq: number,
|
||||
result: number,
|
||||
errMs: string,
|
||||
fileLists: fileListsInfo[],
|
||||
}
|
||||
|
||||
export interface createFlashTransferResult {
|
||||
fileSetId: string,
|
||||
shareLink: string,
|
||||
expireTime: string,
|
||||
expireLeftTime: string,
|
||||
}
|
||||
|
||||
export interface StartFlashTaskRequests {
|
||||
screen?: number; // 1 PC-QQ
|
||||
uploaders: UploaderInfo[];
|
||||
permission?: {};
|
||||
coverPath?: string;
|
||||
paths: string[]; // 文件的绝对路径,可以是文件夹
|
||||
// excludePaths: [];
|
||||
// expireLeftTime: 0,
|
||||
// isNeedDelDeviceInfo: boolean,
|
||||
// isNeedDelLocation: boolean,
|
||||
// coverOriginalInfos: [],
|
||||
// uploadSceneType: 10, // 不知道怎么枚举 先硬编码吧
|
||||
// detectPrivacyInfoResult: {
|
||||
// exists: boolean,
|
||||
// allDetectResults: {}
|
||||
// }
|
||||
}
|
||||
|
||||
export interface FileListInfoRequests {
|
||||
seq: number, // 0
|
||||
fileSetId: string,
|
||||
isUseCache: boolean,
|
||||
sceneType: number, // 1
|
||||
reqInfos: {
|
||||
count: number, // 18 ?? 硬编码吧 不懂
|
||||
paginationInfo: {},
|
||||
parentId: string,
|
||||
reqIndexPath: string,
|
||||
reqDepth: number, // 1
|
||||
filterCondition: {
|
||||
fileCategory: number,
|
||||
filterType: number,
|
||||
}, // 0
|
||||
sortConditions: {
|
||||
sortField: number,
|
||||
sortOrder: number,
|
||||
}[],
|
||||
isNeedPhysicalInfoReady: boolean
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface FlashFileSetInfo {
|
||||
fileSetId: string,
|
||||
name: string,
|
||||
namePinyin: string,
|
||||
totalFileCount: number,
|
||||
totalFileSize: number,
|
||||
permission: {},
|
||||
shareInfo: {
|
||||
shareLink: string,
|
||||
extractionCode: string,
|
||||
},
|
||||
cover: {
|
||||
id: string,
|
||||
urls: [
|
||||
{
|
||||
spec: number, // 2
|
||||
url: string
|
||||
}
|
||||
],
|
||||
localCachePath: string
|
||||
},
|
||||
uploaders: [
|
||||
{
|
||||
uin: string,
|
||||
nickname: string,
|
||||
uid: string,
|
||||
sendEntrance: string
|
||||
}
|
||||
],
|
||||
expireLeftTime: number,
|
||||
aiClusteringStatus: {
|
||||
firstClusteringList: [],
|
||||
shouldPull: boolean
|
||||
},
|
||||
createTime: number,
|
||||
expireTime: number,
|
||||
firstLevelItemCount: 1,
|
||||
svrLastUpdateTimestamp: 0,
|
||||
taskId: string, // 同 fileSetId
|
||||
uploadInfo: {
|
||||
totalUploadedFileSize: number,
|
||||
successCount: number,
|
||||
failedCount: number
|
||||
},
|
||||
downloadInfo: {
|
||||
totalDownloadedFileSize: 0,
|
||||
totalFileSize: 0,
|
||||
totalDownloadFileCount: 0,
|
||||
successCount: 0,
|
||||
failedCount: 0,
|
||||
pausedCount: 0,
|
||||
cancelCount: 0,
|
||||
status: 0,
|
||||
curLevelDownloadedFileCount: number,
|
||||
curLevelUnDownloadedFileCount: 0
|
||||
},
|
||||
transferType: number,
|
||||
isLocalCreate: true,
|
||||
status: number, // todo 枚举全部状态
|
||||
uploadStatus: number, // todo 同上
|
||||
uploadPauseReason: 0,
|
||||
downloadStatus: 0,
|
||||
downloadPauseReason: 0,
|
||||
saveFileSetDir: string,
|
||||
uploadSceneType: 10,
|
||||
downloadSceneType: 0, // 0 PC-QQ 103 web
|
||||
retryCount: number,
|
||||
isMergeShareUpload: 0,
|
||||
isRemoveDeviceInfo: boolean,
|
||||
isRemoveLocation: boolean
|
||||
}
|
||||
|
||||
export interface SendStatus {
|
||||
result: number,
|
||||
msg: string,
|
||||
target: {
|
||||
destType: number,
|
||||
destUid: string,
|
||||
}
|
||||
}
|
||||
@ -5,6 +5,7 @@ import fs from 'node:fs/promises';
|
||||
import { NTMsgAtType, ChatType, ElementType, MessageElement, RawMessage, SelfInfo } from '@/napcat-core/index';
|
||||
import { ILogWrapper } from 'napcat-common/src/log-interface';
|
||||
import EventEmitter from 'node:events';
|
||||
|
||||
export enum LogLevel {
|
||||
DEBUG = 'debug',
|
||||
INFO = 'info',
|
||||
@ -263,7 +264,13 @@ function msgElementToText (element: MessageElement, msg: RawMessage, recursiveLe
|
||||
}
|
||||
|
||||
if (element.fileElement) {
|
||||
return `[文件 ${element.fileElement.fileName}]`;
|
||||
if (element.fileElement.fileUuid) {
|
||||
return `[文件 ${element.fileElement.fileName}]`;
|
||||
} else if (element.elementType === ElementType.TOFURECORD) {
|
||||
return `[在线文件 ${element.fileElement.fileName}]`;
|
||||
} else if (element.elementType === ElementType.ONLINEFOLDER) {
|
||||
return `[在线文件夹 ${element.fileElement.fileName}/]`;
|
||||
}
|
||||
}
|
||||
|
||||
if (element.videoElement) {
|
||||
@ -287,7 +294,12 @@ function msgElementToText (element: MessageElement, msg: RawMessage, recursiveLe
|
||||
}
|
||||
|
||||
if (element.markdownElement) {
|
||||
return '[Markdown 消息]';
|
||||
// console.log(element.markdownElement);
|
||||
if (element.markdownElement.mdSummary !== undefined && element.markdownElement.mdExtInfo !== undefined && element.markdownElement.mdExtInfo.flashTransferInfo) {
|
||||
return element.markdownElement.mdSummary;
|
||||
} else {
|
||||
return '[Markdown 消息]';
|
||||
}
|
||||
}
|
||||
|
||||
if (element.multiForwardMsgElement) {
|
||||
@ -296,6 +308,8 @@ function msgElementToText (element: MessageElement, msg: RawMessage, recursiveLe
|
||||
|
||||
if (element.elementType === ElementType.GreyTip) {
|
||||
return '[灰条消息]';
|
||||
} else if (element.elementType === ElementType.FILE) {
|
||||
return '[文件发送中]';
|
||||
}
|
||||
|
||||
return `[未实现 (ElementType = ${element.elementType})]`;
|
||||
|
||||
@ -6,6 +6,8 @@ import {
|
||||
NTQQSystemApi,
|
||||
NTQQUserApi,
|
||||
NTQQWebApi,
|
||||
NTQQFlashApi,
|
||||
NTQQOnlineApi,
|
||||
} from '@/napcat-core/apis';
|
||||
import { NTQQCollectionApi } from '@/napcat-core/apis/collection';
|
||||
import {
|
||||
@ -23,7 +25,7 @@ import path from 'node:path';
|
||||
import fs from 'node:fs';
|
||||
import { hostname, systemName, systemVersion } from 'napcat-common/src/system';
|
||||
import { NTEventWrapper } from '@/napcat-core/helper/event';
|
||||
import { KickedOffLineInfo, SelfInfo, SelfStatusInfo } from '@/napcat-core/types';
|
||||
import { KickedOffLineInfo, RawMessage, SelfInfo, SelfStatusInfo } from '@/napcat-core/types';
|
||||
import { NapCatConfigLoader, NapcatConfigSchema } from '@/napcat-core/helper/config';
|
||||
import os from 'node:os';
|
||||
import { NodeIKernelMsgListener, NodeIKernelProfileListener } from '@/napcat-core/listeners';
|
||||
@ -123,6 +125,8 @@ export class NapCatCore {
|
||||
MsgApi: new NTQQMsgApi(this.context, this),
|
||||
UserApi: new NTQQUserApi(this.context, this),
|
||||
GroupApi: new NTQQGroupApi(this.context, this),
|
||||
FlashApi: new NTQQFlashApi(this.context, this),
|
||||
OnlineApi: new NTQQOnlineApi(this.context, this),
|
||||
};
|
||||
container.bind(NapCatCore).toConstantValue(this);
|
||||
container.bind(TypedEventEmitter).toConstantValue(this.event);
|
||||
@ -178,6 +182,11 @@ export class NapCatCore {
|
||||
async initNapCatCoreListeners () {
|
||||
const msgListener = new NodeIKernelMsgListener();
|
||||
|
||||
// 在线文件/文件夹消息
|
||||
msgListener.onRecvOnlineFileMsg = (msgs: RawMessage[]) => {
|
||||
msgs.forEach(msg => this.context.logger.logMessage(msg, this.selfInfo));
|
||||
};
|
||||
|
||||
msgListener.onKickedOffLine = (Info: KickedOffLineInfo) => {
|
||||
// 下线通知
|
||||
const tips = `[KickedOffLine] [${Info.tipsTitle}] ${Info.tipsDesc}`;
|
||||
@ -297,4 +306,6 @@ export interface StableNTApiWrapper {
|
||||
MsgApi: NTQQMsgApi,
|
||||
UserApi: NTQQUserApi,
|
||||
GroupApi: NTQQGroupApi;
|
||||
FlashApi: NTQQFlashApi,
|
||||
OnlineApi: NTQQOnlineApi,
|
||||
}
|
||||
|
||||
302
packages/napcat-core/services/NodeIKernelFlashTransferService.ts
Normal file
302
packages/napcat-core/services/NodeIKernelFlashTransferService.ts
Normal file
@ -0,0 +1,302 @@
|
||||
import { GeneralCallResult } from './common';
|
||||
import {
|
||||
SendStatus,
|
||||
StartFlashTaskRequests,
|
||||
createFlashTransferResult,
|
||||
FlashBaseRequest,
|
||||
FlashFileSetInfo,
|
||||
FileListInfoRequests,
|
||||
FileListResponse,
|
||||
DownloadStatusInfo,
|
||||
SendTargetRequests,
|
||||
FlashOneFileInfo,
|
||||
} from '../data/flash';
|
||||
|
||||
export interface NodeIKernelFlashTransferService {
|
||||
/**
|
||||
* 开始闪传服务 并上传文件/文件夹(可以多选,非常好用)
|
||||
* @param timestamp
|
||||
* @param fileInfo
|
||||
*/
|
||||
createFlashTransferUploadTask(timestamp: number, fileInfo: StartFlashTaskRequests): Promise < GeneralCallResult & {
|
||||
createFlashTransferResult: createFlashTransferResult;
|
||||
seq: number;
|
||||
} >; // 2 arg 重点 // 自动上传
|
||||
|
||||
createMergeShareTask(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
updateFlashTransfer(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
getFileSetList(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
getFileSetListCount(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
/**
|
||||
* 获取file set 的信息
|
||||
* @param fileSetIdDict
|
||||
*/
|
||||
getFileSet(fileSetIdDict: FlashBaseRequest): Promise < GeneralCallResult & {
|
||||
seq: number;
|
||||
isCache: boolean;
|
||||
fileSet: FlashFileSetInfo;
|
||||
} >; // 1 arg
|
||||
|
||||
/**
|
||||
* 获取file set 里面的文件信息(文件夹结构)
|
||||
* @param requestArgs
|
||||
*/
|
||||
getFileList(requestArgs: FileListInfoRequests): Promise < {
|
||||
rsp: FileListResponse;
|
||||
} > ; // 1 arg 这个方法QQ有bug??? 并没有,是我参数有问题
|
||||
|
||||
getDownloadedFileCount(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
getLocalFileList(...args: unknown[]): unknown; // 3 arg
|
||||
|
||||
batchRemoveUserFileSetHistory(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
/**
|
||||
* 获取分享链接
|
||||
* @param fileSetId
|
||||
*/
|
||||
getShareLinkReq(fileSetId:string): Promise< GeneralCallResult & {
|
||||
shareLink: string;
|
||||
expireTimestamp: string;
|
||||
}>;
|
||||
|
||||
/**
|
||||
* 由分享链接到fileSetId
|
||||
* @param shareCode
|
||||
*/
|
||||
getFileSetIdByCode(shareCode: string): Promise < GeneralCallResult & {
|
||||
fileSetId: string;
|
||||
} > ; // 1 arg code == share code
|
||||
|
||||
batchRemoveFile(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
checkUploadPathValid(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
cleanFailedFiles(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
/**
|
||||
* 暂停所有的任务
|
||||
*/
|
||||
resumeAllUnfinishedTasks(): unknown; // 0 arg !!
|
||||
|
||||
addFileSetUploadListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFileSetUploadListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
/**
|
||||
* 开始上传任务 适用于已暂停的
|
||||
* @param fileSetId
|
||||
*/
|
||||
startFileSetUpload(fileSetId: string): void; // 1 arg 并不是新建任务,应该是暂停后的启动
|
||||
|
||||
/**
|
||||
* 结束,无法再次启动
|
||||
* @param fileSetId
|
||||
*/
|
||||
stopFileSetUpload(fileSetId: string): void; // 1 arg stop 后start无效
|
||||
|
||||
/**
|
||||
* 暂停上传
|
||||
* @param fileSetId
|
||||
*/
|
||||
pauseFileSetUpload(fileSetId: string): void; // 1 arg 暂停上传
|
||||
|
||||
/**
|
||||
* 继续上传
|
||||
* @param args
|
||||
*/
|
||||
resumeFileSetUpload(...args: unknown[]): unknown; // 1 arg 继续
|
||||
|
||||
pauseFileUpload(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
resumeFileUpload(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
stopFileUpload(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
asyncGetThumbnailPath(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
setDownLoadDefaultFileDir(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
setFileSetDownloadDir(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
getFileSetDownloadDir(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
setFlashTransferDir(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
addFileSetDownloadListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFileSetDownloadListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
/**
|
||||
* 开始下载file set的函数 同开始上传
|
||||
* @param fileSetId
|
||||
* @param chatType 聊天类型 //因为没有peer,其实可以硬编码为1 (好友私聊)
|
||||
* @param arg // 默认为false
|
||||
*/
|
||||
startFileSetDownload(fileSetId:string, chatType: number, arg: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} >; // 3 arg
|
||||
|
||||
stopFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg 结束不可重启!!
|
||||
|
||||
pauseFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg
|
||||
|
||||
resumeFileSetDownload(fileSetId: string, arg1: { isIncludeCompressInnerFiles: boolean }): Promise < GeneralCallResult & {
|
||||
extraInfo: 0
|
||||
} > ; // 2 arg
|
||||
|
||||
startFileListDownLoad(...args: unknown[]): unknown; // 4 arg // 大概率是选择set里面的部分文件进行下载,没必要,不想写
|
||||
|
||||
pauseFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
resumeFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
stopFileListDownLoad(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
startThumbnailListDownload(fileSetId: string): Promise < GeneralCallResult >; // 1 arg // 缩略图下载
|
||||
|
||||
stopThumbnailListDownload(fileSetId: string): Promise < GeneralCallResult >; // 1 arg
|
||||
|
||||
asyncRequestDownLoadStatus(fileSetId: string): Promise < DownloadStatusInfo >; // 1 arg
|
||||
|
||||
startFileTransferUrl(fileInfo: FlashOneFileInfo): Promise < {
|
||||
ret: number,
|
||||
url: string,
|
||||
expireTimestampSeconds: string
|
||||
} >; // 1 arg
|
||||
|
||||
startFileListDownLoadBySessionId(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
addFileSetSimpleStatusListener(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
addFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
removeFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
removeFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addDesktopFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addDesktopFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeDesktopFileSetSimpleStatusMonitoring(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeDesktopFileSetSimpleStatusListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addFileSetSimpleUploadInfoListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addFileSetSimpleUploadInfoMonitoring(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFileSetSimpleUploadInfoMonitoring(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFileSetSimpleUploadInfoListener(...args: unknown[]): unknown; // 1 arg
|
||||
/**
|
||||
* 发送闪传消息
|
||||
* @param sendArgs
|
||||
*/
|
||||
sendFlashTransferMsg(sendArgs: SendTargetRequests): Promise < {
|
||||
errCode: number,
|
||||
errMsg: string,
|
||||
rsp: {
|
||||
sendStatus: SendStatus[]
|
||||
}
|
||||
} >; // 1 arg 估计是file set id
|
||||
|
||||
addFlashTransferTaskInfoListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFlashTransferTaskInfoListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
retrieveLocalLastFailedSetTasksInfo(): unknown; // 0 arg
|
||||
|
||||
getFailedFileList(fileSetId: string): Promise < {
|
||||
rsp: {
|
||||
seq: number;
|
||||
result: number;
|
||||
errMs: string;
|
||||
fileSetId: string;
|
||||
fileList: []
|
||||
}
|
||||
} >; // 1 arg
|
||||
|
||||
getLocalFileListByStatuses(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addTransferStateListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeTransferStateListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
getFileSetFirstClusteringList(...args: unknown[]): unknown; // 3 arg
|
||||
|
||||
getFileSetClusteringList(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addFileSetClusteringListListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFileSetClusteringListListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
getFileSetClusteringDetail(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
doAIOFlashTransferBubbleActionWithStatus(...args: unknown[]): unknown; // 4 arg
|
||||
|
||||
getFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
pollFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
cancelPollFilesTransferProgress(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
checkDownloadStatusBeforeLocalFileOper(...args: unknown[]): unknown; // 3 arg
|
||||
|
||||
getCompressedFileFolder(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
addFolderListener(...args: unknown[]): unknown; // 1 arg
|
||||
|
||||
removeFolderListener(...args: unknown[]): unknown;
|
||||
|
||||
addCompressedFileListener(...args: unknown[]): unknown;
|
||||
|
||||
removeCompressedFileListener(...args: unknown[]): unknown;
|
||||
|
||||
getFileCategoryList(...args: unknown[]): unknown;
|
||||
|
||||
addDeviceStatusListener(...args: unknown[]): unknown;
|
||||
|
||||
removeDeviceStatusListener(...args: unknown[]): unknown;
|
||||
|
||||
checkDeviceStatus(...args: unknown[]): unknown;
|
||||
|
||||
pauseAllTasks(...args: unknown[]): unknown; // 2 arg
|
||||
|
||||
resumePausedTasksAfterDeviceStatus(...args: unknown[]): unknown;
|
||||
|
||||
onSystemGoingToSleep(...args: unknown[]): unknown;
|
||||
|
||||
onSystemWokeUp(...args: unknown[]): unknown;
|
||||
|
||||
getFileMetas(...args: unknown[]): unknown;
|
||||
|
||||
addDownloadCntStatisticsListener(...args: unknown[]): unknown;
|
||||
|
||||
removeDownloadCntStatisticsListener(...args: unknown[]): unknown;
|
||||
|
||||
detectPrivacyInfoInPaths(...args: unknown[]): unknown;
|
||||
|
||||
getFileThumbnailUrl(...args: unknown[]): unknown;
|
||||
|
||||
handleDownloadFinishAfterSaveToAlbum(...args: unknown[]): unknown;
|
||||
|
||||
checkBatchFilesDownloadStatus(...args: unknown[]): unknown;
|
||||
|
||||
onCheckAlbumStorageStatusResult(...args: unknown[]): unknown;
|
||||
|
||||
addFileAlbumStorageListener(...args: unknown[]): unknown;
|
||||
|
||||
removeFileAlbumStorageListener(...args: unknown[]): unknown;
|
||||
|
||||
refreshFolderStatus(...args: unknown[]): unknown;
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
import { ElementType, MessageElement, Peer, RawMessage, SendMessageElement } from '@/napcat-core/types';
|
||||
import { ElementType, MessageElement, Peer, RawMessage, FileElement } from '@/napcat-core/types';
|
||||
import { NodeIKernelMsgListener } from '@/napcat-core/listeners/NodeIKernelMsgListener';
|
||||
import { GeneralCallResult } from '@/napcat-core/services/common';
|
||||
import { MsgReqType, QueryMsgsParams, TmpChatInfoApi } from '@/napcat-core/types/msg';
|
||||
@ -10,7 +10,10 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
addKernelMsgListener(nodeIKernelMsgListener: NodeIKernelMsgListener): number;
|
||||
|
||||
sendMsg(msgId: string, peer: Peer, msgElements: SendMessageElement[], map: Map<unknown, unknown>): Promise<GeneralCallResult>;
|
||||
sendMsg(msgId: string, peer: Peer, msgElements: {
|
||||
elementType: number;
|
||||
fileElement: { fileName: string; filePath: string; fileSize: string }
|
||||
}[], map: Map<unknown, unknown>): Promise<GeneralCallResult>;
|
||||
|
||||
recallMsg(peer: Peer, msgIds: string[]): Promise<GeneralCallResult>;
|
||||
|
||||
@ -70,7 +73,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
addSendMsg(...args: unknown[]): unknown;
|
||||
|
||||
cancelSendMsg(...args: unknown[]): unknown;
|
||||
cancelSendMsg(peer: Peer, msgId: string): Promise<void> ;
|
||||
|
||||
switchToOfflineSendMsg(peer: Peer, MsgId: string): unknown;
|
||||
|
||||
@ -78,7 +81,7 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
refuseReceiveOnlineFileMsg(peer: Peer, MsgId: string): unknown;
|
||||
|
||||
resendMsg(...args: unknown[]): unknown;
|
||||
resendMsg(peer: Peer, msgId: string): Promise<void>;
|
||||
|
||||
recallMsg(...args: unknown[]): unknown;
|
||||
|
||||
@ -132,7 +135,20 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
isMsgMatched(...args: unknown[]): unknown;
|
||||
|
||||
getOnlineFileMsgs(...args: unknown[]): unknown;
|
||||
getOnlineFileMsgs(peer: Peer): Promise <GeneralCallResult & {
|
||||
msgList: {
|
||||
msgId: string;
|
||||
msgRandom: string;
|
||||
senderUid: string;
|
||||
peerUid: string;
|
||||
msgTime: string;
|
||||
elements: {
|
||||
elementType: number;
|
||||
elementId: string;
|
||||
fileElement: FileElement
|
||||
}[];
|
||||
}[] // 一大坨,懒得写
|
||||
} >;
|
||||
|
||||
getAllOnlineFileMsgs(...args: unknown[]): unknown;
|
||||
|
||||
@ -418,11 +434,25 @@ export interface NodeIKernelMsgService {
|
||||
|
||||
refreshMsgAbstractsByGuildIds(...args: unknown[]): unknown;
|
||||
|
||||
getRichMediaElement(...args: unknown[]): unknown;
|
||||
getRichMediaElement(arg: {
|
||||
msgId: string,
|
||||
peerUid: string,
|
||||
chatType: number,
|
||||
elementId: string,
|
||||
downSourceType: number,
|
||||
downloadType: number,
|
||||
}): Promise<any>;
|
||||
|
||||
cancelGetRichMediaElement(...args: unknown[]): unknown;
|
||||
|
||||
refuseGetRichMediaElement(...args: unknown[]): unknown;
|
||||
refuseGetRichMediaElement(args: {
|
||||
msgId: string,
|
||||
peerUid: string,
|
||||
chatType: number,
|
||||
elementId: string,
|
||||
downloadType: number, // 1
|
||||
downSourceType: number, // 1
|
||||
}): Promise<void>;
|
||||
|
||||
switchToOfflineGetRichMediaElement(...args: unknown[]): unknown;
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
export enum GeneralCallResultStatus {
|
||||
OK = 0,
|
||||
ERROR = -1,
|
||||
}
|
||||
|
||||
export interface GeneralCallResult {
|
||||
|
||||
21
packages/napcat-core/types/flashfile.ts
Normal file
21
packages/napcat-core/types/flashfile.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export enum fileType {
|
||||
MP3 = 1,
|
||||
VIDEO = 2,
|
||||
DOC = 3,
|
||||
ZIP = 4,
|
||||
XLS = 6,
|
||||
PPT = 7,
|
||||
CODE = 8,
|
||||
PDF = 9,
|
||||
TXT = 10,
|
||||
UNKNOW = 11,
|
||||
FOLDER = 25,
|
||||
IMG = 26,
|
||||
}
|
||||
|
||||
export enum FileStatus {
|
||||
UPLOADING = 0,
|
||||
// DOWNLOADED = 1, ??? 不太清楚
|
||||
OK = 2,
|
||||
STOP = 3,
|
||||
}
|
||||
@ -66,13 +66,14 @@ export enum ElementType {
|
||||
YOLOGAMERESULT = 20,
|
||||
AVRECORD = 21,
|
||||
FEED = 22,
|
||||
TOFURECORD = 23,
|
||||
TOFURECORD = 23, // tofu record?? 在线文件的id是这个
|
||||
ACEBUBBLE = 24,
|
||||
ACTIVITY = 25,
|
||||
TOFU = 26,
|
||||
FACEBUBBLE = 27,
|
||||
SHARELOCATION = 28,
|
||||
TASKTOPMSG = 29,
|
||||
ONLINEFOLDER = 30, // 在线文件夹
|
||||
RECOMMENDEDMSG = 43,
|
||||
ACTIONBAR = 44,
|
||||
}
|
||||
@ -303,11 +304,40 @@ export enum NTVideoType {
|
||||
VIDEO_FORMAT_WMV = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* 闪传图标
|
||||
*/
|
||||
export interface FlashTransferIcon {
|
||||
spec: number;
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 闪传文件信息
|
||||
*/
|
||||
export interface FlashTransferInfo {
|
||||
filesetId: string;
|
||||
name: string;
|
||||
fileSize: string;
|
||||
thnumbnail: {
|
||||
id: string;
|
||||
urls: FlashTransferIcon[];
|
||||
localCachePath: string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown元素接口
|
||||
*/
|
||||
export interface MarkdownElement {
|
||||
content: string;
|
||||
style?: {};
|
||||
processMsg?: string;
|
||||
mdSummary?: string;
|
||||
mdExtType?: number;
|
||||
mdExtInfo?: {
|
||||
flashTransferInfo: FlashTransferInfo;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -27,6 +27,7 @@ import { NodeIKernelMSFService } from './services/NodeIKernelMSFService';
|
||||
import { NodeIkernelTestPerformanceService } from './services/NodeIkernelTestPerformanceService';
|
||||
import { NodeIKernelECDHService } from './services/NodeIKernelECDHService';
|
||||
import { NodeIO3MiscService } from './services/NodeIO3MiscService';
|
||||
import { NodeIKernelFlashTransferService } from "./services/NodeIKernelFlashTransferService";
|
||||
|
||||
export interface NodeQQNTWrapperUtil {
|
||||
get(): NodeQQNTWrapperUtil;
|
||||
@ -202,6 +203,8 @@ export interface NodeIQQNTWrapperSession {
|
||||
|
||||
getSearchService(): NodeIKernelSearchService;
|
||||
|
||||
getFlashTransferService(): NodeIKernelFlashTransferService;
|
||||
|
||||
getDirectSessionService(): unknown;
|
||||
|
||||
getRDeliveryService(): unknown;
|
||||
|
||||
24
packages/napcat-onebot/action/file/flash/CreateFlashTask.ts
Normal file
24
packages/napcat-onebot/action/file/flash/CreateFlashTask.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
// 不全部使用json因为:一个文件解析Form-data会变字符串!!! 但是api文档就写List
|
||||
const SchemaData = Type.Object({
|
||||
files: Type.Union([
|
||||
Type.Array(Type.String()),
|
||||
Type.String(),
|
||||
]),
|
||||
});
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class CreateFlashTask extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.CreateFlashTask;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
// todo fileset的名字和缩略图还没实现!!
|
||||
const fileList = Array.isArray(payload.files) ? payload.files : [payload.files];
|
||||
|
||||
return await this.core.apis.FlashApi.createFlashTransferUploadTask(fileList);
|
||||
}
|
||||
}
|
||||
19
packages/napcat-onebot/action/file/flash/DownloadFileset.ts
Normal file
19
packages/napcat-onebot/action/file/flash/DownloadFileset.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class DownloadFileset extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.DownloadFileset;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
// 默认路径 / fileset_id /为下载路径
|
||||
return await this.core.apis.FlashApi.downloadFileSetBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,20 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
share_code: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetFilesetId extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetFilesetId;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
// 适配share_link 防止被传 Link无法解析
|
||||
const code = payload.share_code.includes('=') ? payload.share_code.split('=').slice(1).join('=') : payload.share_code;
|
||||
return await this.core.apis.FlashApi.fromShareLinkFindSetId(code);
|
||||
}
|
||||
}
|
||||
18
packages/napcat-onebot/action/file/flash/GetFilesetInfo.ts
Normal file
18
packages/napcat-onebot/action/file/flash/GetFilesetInfo.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetFilesetInfo extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetFilesetInfo;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
return await this.core.apis.FlashApi.getFileSetIndoBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
18
packages/napcat-onebot/action/file/flash/GetFlashFileList.ts
Normal file
18
packages/napcat-onebot/action/file/flash/GetFlashFileList.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetFlashFileList extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetFlashFileList;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
return await this.core.apis.FlashApi.getFileListBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
24
packages/napcat-onebot/action/file/flash/GetFlashFileUrl.ts
Normal file
24
packages/napcat-onebot/action/file/flash/GetFlashFileUrl.ts
Normal file
@ -0,0 +1,24 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
file_name: Type.Optional(Type.String()),
|
||||
file_index: Type.Optional(Type.Number()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetFlashFileUrl extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetFlashFileUrl;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
// 文件的索引依旧从0开始
|
||||
return await this.core.apis.FlashApi.getFileTransUrl(payload.fileset_id, {
|
||||
fileName: payload.file_name,
|
||||
fileIndex: payload.file_index,
|
||||
});
|
||||
}
|
||||
}
|
||||
18
packages/napcat-onebot/action/file/flash/GetShareLink.ts
Normal file
18
packages/napcat-onebot/action/file/flash/GetShareLink.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetShareLink extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetShareLink;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
return await this.core.apis.FlashApi.getShareLinkBySetId(payload.fileset_id);
|
||||
}
|
||||
}
|
||||
39
packages/napcat-onebot/action/file/flash/SendFlashMsg.ts
Normal file
39
packages/napcat-onebot/action/file/flash/SendFlashMsg.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType, Peer } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
fileset_id: Type.String(),
|
||||
user_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
group_id: Type.Optional(Type.Union([Type.Number(), Type.String()])),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class SendFlashMsg extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.SendFlashMsg;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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,26 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
msg_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class CancelOnlineFile extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.CancelOnlineFile;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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,25 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class GetOnlineFileMessages extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.GetOnlineFileMessages;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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,27 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
msg_id: Type.String(),
|
||||
element_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class ReceiveOnlineFile extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.ReceiveOnlineFile;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
// 默认下载路径
|
||||
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,26 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
msg_id: Type.String(),
|
||||
element_id: Type.String(),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class RefuseOnlineFile extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.RefuseOnlineFile;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
28
packages/napcat-onebot/action/file/online/SendOnlineFile.ts
Normal file
28
packages/napcat-onebot/action/file/online/SendOnlineFile.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
file_path: Type.String(),
|
||||
file_name: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class SendOnlineFile extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.SendOnlineFile;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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,26 @@
|
||||
import { OneBotAction } from '@/napcat-onebot/action/OneBotAction';
|
||||
import { ActionName } from '@/napcat-onebot/action/router';
|
||||
import { Static, Type } from '@sinclair/typebox';
|
||||
import { ChatType } from 'napcat-core/types';
|
||||
|
||||
const SchemaData = Type.Object({
|
||||
user_id: Type.Union([Type.Number(), Type.String()]),
|
||||
folder_path: Type.String(),
|
||||
folder_name: Type.Optional(Type.String()),
|
||||
});
|
||||
|
||||
type Payload = Static<typeof SchemaData>;
|
||||
|
||||
export class SendOnlineFolder extends OneBotAction<Payload, unknown> {
|
||||
override actionName = ActionName.SendOnlineFolder;
|
||||
override payloadSchema = SchemaData;
|
||||
|
||||
async _handle (payload: Payload) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -66,9 +66,9 @@ import { FetchCustomFace } from './extends/FetchCustomFace';
|
||||
import GoCQHTTPUploadPrivateFile from './go-cqhttp/UploadPrivateFile';
|
||||
import { FetchEmojiLike } from './extends/FetchEmojiLike';
|
||||
import { NapCatCore } from 'napcat-core';
|
||||
import { NapCatOneBot11Adapter } from '@/napcat-onebot/index';
|
||||
import type { NetworkAdapterConfig } from '../config/config';
|
||||
import { OneBotAction } from './OneBotAction';
|
||||
import { NapCatOneBot11Adapter } from '@/napcat-onebot';
|
||||
import { SetInputStatus } from './extends/SetInputStatus';
|
||||
import { GetCSRF } from './system/GetCSRF';
|
||||
import { DelGroupNotice } from './group/DelGroupNotice';
|
||||
@ -140,6 +140,20 @@ import { DownloadFileImageStream } from './stream/DownloadFileImageStream';
|
||||
import { TestDownloadStream } from './stream/TestStreamDownload';
|
||||
import { UploadFileStream } from './stream/UploadFileStream';
|
||||
import { AutoRegisterRouter } from './auto-register';
|
||||
import { CreateFlashTask } from './file/flash/CreateFlashTask';
|
||||
import { SendFlashMsg } from './file/flash/SendFlashMsg';
|
||||
import { GetFlashFileList } from './file/flash/GetFlashFileList';
|
||||
import { GetFlashFileUrl } from './file/flash/GetFlashFileUrl';
|
||||
import { GetShareLink } from './file/flash/GetShareLink';
|
||||
import { GetFilesetInfo } from './file/flash/GetFilesetInfo';
|
||||
import { DownloadFileset } from './file/flash/DownloadFileset';
|
||||
import { GetOnlineFileMessages } from './file/online/GetOnlineFileMessages';
|
||||
import { SendOnlineFile } from './file/online/SendOnlineFile';
|
||||
import { SendOnlineFolder } from './file/online/SendOnlineFolder';
|
||||
import { CancelOnlineFile } from './file/online/CancelOnlineFile';
|
||||
import { ReceiveOnlineFile } from './file/online/ReceiveOnlineFile';
|
||||
import { RefuseOnlineFile } from './file/online/RefuseOnlineFile';
|
||||
import { GetFilesetId } from './file/flash/GetFilesetIdByCode';
|
||||
|
||||
export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
const actionHandlers = [
|
||||
@ -293,6 +307,20 @@ export function createActionMap (obContext: NapCatOneBot11Adapter, core: NapCatC
|
||||
new CleanCache(obContext, core),
|
||||
new GetGroupAddRequest(obContext, core),
|
||||
new GetCollectionList(obContext, core),
|
||||
new CreateFlashTask(obContext, core),
|
||||
new GetFlashFileList(obContext, core),
|
||||
new GetFlashFileUrl(obContext, core),
|
||||
new SendFlashMsg(obContext, core),
|
||||
new GetShareLink(obContext, core),
|
||||
new GetFilesetInfo(obContext, core),
|
||||
new GetOnlineFileMessages(obContext, core),
|
||||
new SendOnlineFile(obContext, core),
|
||||
new SendOnlineFolder(obContext, core),
|
||||
new ReceiveOnlineFile(obContext, core),
|
||||
new RefuseOnlineFile(obContext, core),
|
||||
new CancelOnlineFile(obContext, core),
|
||||
new DownloadFileset(obContext, core),
|
||||
new GetFilesetId(obContext, core),
|
||||
];
|
||||
|
||||
type HandlerUnion = typeof actionHandlers[number];
|
||||
|
||||
@ -125,8 +125,8 @@ export const ActionName = {
|
||||
// 以下为扩展napcat扩展
|
||||
Unknown: 'unknown',
|
||||
SetDiyOnlineStatus: 'set_diy_online_status',
|
||||
SharePeer: 'ArkSharePeer',// @deprecated
|
||||
ShareGroupEx: 'ArkShareGroup',// @deprecated
|
||||
SharePeer: 'ArkSharePeer', // @deprecated
|
||||
ShareGroupEx: 'ArkShareGroup', // @deprecated
|
||||
// 标准化接口
|
||||
SendGroupArkShare: 'send_group_ark_share',
|
||||
SendArkShare: 'send_ark_share',
|
||||
@ -185,4 +185,22 @@ export const ActionName = {
|
||||
GetClientkey: 'get_clientkey',
|
||||
|
||||
SendPoke: 'send_poke',
|
||||
|
||||
// Flash (闪传) 扩展
|
||||
CreateFlashTask: 'create_flash_task',
|
||||
SendFlashMsg: 'send_flash_msg', // 因为不可能手动构造element,所以不走sendMsg
|
||||
GetShareLink: 'get_share_link',
|
||||
DownloadFileset: 'download_fileset',
|
||||
GetFilesetInfo: 'get_fileset_info',
|
||||
GetFlashFileList: 'get_flash_file_list',
|
||||
GetFlashFileUrl: 'get_flash_file_url',
|
||||
GetFilesetId: 'get_fileset_id',
|
||||
|
||||
// Online File (在线文件) 扩展
|
||||
SendOnlineFile: 'send_online_file',
|
||||
SendOnlineFolder: 'send_online_folder',
|
||||
GetOnlineFileMessages: 'get_online_file_msg',
|
||||
ReceiveOnlineFile: 'receive_online_file',
|
||||
RefuseOnlineFile: 'refuse_online_file',
|
||||
CancelOnlineFile: 'cancel_online_file',
|
||||
} as const;
|
||||
|
||||
@ -42,11 +42,18 @@ import { OB11GroupIncreaseEvent } from '../event/notice/OB11GroupIncreaseEvent';
|
||||
import { GroupDecreaseSubType, OB11GroupDecreaseEvent } from '../event/notice/OB11GroupDecreaseEvent';
|
||||
import { GroupAdmin } from 'napcat-core/packet/transformer/proto/message/groupAdmin';
|
||||
import { OB11GroupAdminNoticeEvent } from '../event/notice/OB11GroupAdminNoticeEvent';
|
||||
import { GroupChange, GroupChangeInfo, GroupInvite, PushMsgBody } from 'napcat-core/packet/transformer/proto';
|
||||
import {
|
||||
GroupChange,
|
||||
GroupChangeInfo,
|
||||
GroupInvite,
|
||||
PushMsgBody,
|
||||
} from 'napcat-core/packet/transformer/proto';
|
||||
import { OB11GroupRequestEvent } from '../event/request/OB11GroupRequest';
|
||||
import { LRUCache } from 'napcat-common/src/lru-cache';
|
||||
import { cleanTaskQueue } from 'napcat-common/src/clean-task';
|
||||
import { registerResource } from 'napcat-common/src/health';
|
||||
import { OB11OnlineFileReceiveEvent } from '@/napcat-onebot/event/notice/OB11OnlineFileReceiveEvent';
|
||||
import { OB11OnlineFileSendEvent } from '@/napcat-onebot/event/notice/OB11OnlineFileSendEvent';
|
||||
|
||||
type RawToOb11Converters = {
|
||||
[Key in keyof MessageElement as Key extends `${string}Element` ? Key : never]: (
|
||||
@ -143,6 +150,21 @@ export class OneBotMsgApi {
|
||||
},
|
||||
|
||||
fileElement: async (element, msg, elementWrapper, { disableGetUrl }) => {
|
||||
// 让在线文件/文件夹的消息单独出去(否则无法正确处理UUID!!!)
|
||||
if (+elementWrapper.elementType === 23 || +elementWrapper.elementType === 30) {
|
||||
// 判断为在线文件/文件夹
|
||||
return {
|
||||
type: OB11MessageDataType.onlinefile,
|
||||
data: {
|
||||
msgId: msg.msgId,
|
||||
elementId: elementWrapper.elementId,
|
||||
fileName: element.fileName,
|
||||
fileSize: element.fileSize,
|
||||
isDir: (elementWrapper.elementType === 30),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const peer = {
|
||||
chatType: msg.chatType,
|
||||
peerUid: msg.peerUid,
|
||||
@ -538,12 +560,22 @@ export class OneBotMsgApi {
|
||||
},
|
||||
|
||||
markdownElement: async (element) => {
|
||||
return {
|
||||
type: OB11MessageDataType.markdown,
|
||||
data: {
|
||||
content: element.content,
|
||||
},
|
||||
};
|
||||
// 让QQ闪传消息独立出去
|
||||
if (element.mdExtInfo !== undefined && element.mdExtInfo.flashTransferInfo) {
|
||||
return {
|
||||
type: OB11MessageDataType.flashtransfer,
|
||||
data: {
|
||||
fileSetId: element.mdExtInfo.flashTransferInfo.filesetId,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
type: OB11MessageDataType.markdown,
|
||||
data: {
|
||||
content: element.content,
|
||||
},
|
||||
};
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@ -880,6 +912,10 @@ export class OneBotMsgApi {
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
// 不需要支持发送
|
||||
[OB11MessageDataType.onlinefile]: async () => undefined,
|
||||
|
||||
[OB11MessageDataType.flashtransfer]: async () => undefined,
|
||||
};
|
||||
|
||||
constructor (obContext: NapCatOneBot11Adapter, core: NapCatCore) {
|
||||
@ -1329,6 +1365,7 @@ export class OneBotMsgApi {
|
||||
async parseSysMessage (msg: number[]) {
|
||||
const SysMessage = new NapProtoMsg(PushMsgBody).decode(Uint8Array.from(msg));
|
||||
// 邀请需要解grayTipElement
|
||||
// console.log(SysMessage.body?.msgContent);
|
||||
if (SysMessage.contentHead.type === 33 && SysMessage.body?.msgContent) {
|
||||
const groupChange = new NapProtoMsg(GroupChange).decode(SysMessage.body.msgContent);
|
||||
await this.core.apis.GroupApi.refreshGroupMemberCache(groupChange.groupUin.toString(), true);
|
||||
@ -1484,6 +1521,63 @@ export class OneBotMsgApi {
|
||||
);
|
||||
} else if (SysMessage.contentHead.type === 528 && SysMessage.contentHead.subType === 39 && SysMessage.body?.msgContent) {
|
||||
return await this.obContext.apis.UserApi.parseLikeEvent(SysMessage.body?.msgContent);
|
||||
} else if (SysMessage.contentHead.type === 166 && SysMessage.contentHead.c2CCmd === 133 && SysMessage.body?.msgContent) {
|
||||
this.core.context.logger.logDebug('在线文件通道断开');
|
||||
// 可能原因: 对方取消 对方拒绝 对方转离线
|
||||
// body不是proto,只能手动提取,可能是错的!!
|
||||
// console.log(SysMessage.body?.msgContent);
|
||||
const mainCmd = SysMessage.body.msgContent[15];
|
||||
const subCmd = SysMessage.body.msgContent[17];
|
||||
if (mainCmd === 101) {
|
||||
// 在线文件
|
||||
if (subCmd === 225) {
|
||||
// 对方取消或转离线
|
||||
this.core.context.logger.log(`好友:${SysMessage.responseHead.fromUin}取消了在线文件的传输(或转离线)`);
|
||||
return new OB11OnlineFileReceiveEvent(
|
||||
this.core,
|
||||
+SysMessage.responseHead.fromUin
|
||||
);
|
||||
} else if (subCmd === 230) {
|
||||
// 对方拒绝接收
|
||||
this.core.context.logger.log(`好友:${SysMessage.responseHead.fromUin}拒绝了你的在线文件传输`);
|
||||
return new OB11OnlineFileSendEvent(
|
||||
this.core,
|
||||
+SysMessage.responseHead.fromUin,
|
||||
'refuse'
|
||||
);
|
||||
}
|
||||
} else if (mainCmd === 136) {
|
||||
if (subCmd === 225) {
|
||||
// 对方取消或转离线
|
||||
this.core.context.logger.log(`好友:${SysMessage.responseHead.fromUin}取消了在线文件夹的传输(或转离线)`);
|
||||
return new OB11OnlineFileReceiveEvent(
|
||||
this.core,
|
||||
+SysMessage.responseHead.fromUin
|
||||
);
|
||||
} else if (subCmd === 230) {
|
||||
// 对方拒绝接收
|
||||
this.core.context.logger.log(`好友:${SysMessage.responseHead.fromUin}拒绝了你的在线文件夹传输`);
|
||||
return new OB11OnlineFileSendEvent(
|
||||
this.core,
|
||||
+SysMessage.responseHead.fromUin,
|
||||
'refuse'
|
||||
);
|
||||
}
|
||||
}
|
||||
this.core.context.logger.logDebug('未知的系统消息事件:', mainCmd, subCmd);
|
||||
return undefined;
|
||||
} else if (SysMessage.contentHead.type === 166 && SysMessage.contentHead.c2CCmd === 131 && SysMessage.body?.msgContent) {
|
||||
const mainCmd = SysMessage.body.msgContent[15];
|
||||
if (mainCmd === 101) {
|
||||
this.core.context.logger.log('在线文件传输成功!');
|
||||
} else if (mainCmd === 136) {
|
||||
this.core.context.logger.log('在线文件夹传输成功!');
|
||||
}
|
||||
return new OB11OnlineFileSendEvent(
|
||||
this.core,
|
||||
+SysMessage.responseHead.fromUin,
|
||||
'receive'
|
||||
);
|
||||
}
|
||||
// else if (SysMessage.contentHead.type == 732 && SysMessage.contentHead.subType == 16 && SysMessage.body?.msgContent) {
|
||||
// let data_wrap = PBString(2);
|
||||
|
||||
@ -0,0 +1,11 @@
|
||||
import { OB11BaseNoticeEvent } from './OB11BaseNoticeEvent';
|
||||
import { NapCatCore } from 'napcat-core';
|
||||
|
||||
export abstract class OB11OnlineFileNoticeEvent extends OB11BaseNoticeEvent {
|
||||
peer_id: number;
|
||||
|
||||
protected constructor (core: NapCatCore, peer_id: number) {
|
||||
super(core);
|
||||
this.peer_id = peer_id;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,13 @@
|
||||
import { OB11OnlineFileNoticeEvent } from './OB11OnlineFileNoticeEvent';
|
||||
import { NapCatCore } from '@/napcat-core';
|
||||
|
||||
export class OB11OnlineFileReceiveEvent extends OB11OnlineFileNoticeEvent {
|
||||
notice_type: string;
|
||||
sub_type: string;
|
||||
|
||||
constructor (core: NapCatCore, peer_id: number) {
|
||||
super(core, peer_id);
|
||||
this.notice_type = 'online_file_receive';
|
||||
this.sub_type = 'cancel';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
import { OB11OnlineFileNoticeEvent } from './OB11OnlineFileNoticeEvent';
|
||||
import { NapCatCore } from '@/napcat-core';
|
||||
|
||||
export class OB11OnlineFileSendEvent extends OB11OnlineFileNoticeEvent {
|
||||
notice_type = 'online_file_send';
|
||||
sub_type: 'receive' | 'refuse';
|
||||
|
||||
constructor (core: NapCatCore, peer_id: number, sub_type: 'receive' | 'refuse') {
|
||||
super(core, peer_id);
|
||||
this.sub_type = sub_type;
|
||||
}
|
||||
}
|
||||
@ -328,6 +328,38 @@ export class NapCatOneBot11Adapter {
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 加入在线文件的listener
|
||||
*/
|
||||
msgListener.onRecvOnlineFileMsg = async (msg: RawMessage[]) => {
|
||||
if (!this.networkManager.hasActiveAdapters()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const m of msg) {
|
||||
// this.context.logger.logMessage(m, this.core.selfInfo);
|
||||
|
||||
if (this.bootTime > parseInt(m.msgTime)) {
|
||||
this.context.logger.logDebug(`在线文件消息时间${m.msgTime}早于启动时间${this.bootTime},忽略上报`);
|
||||
continue;
|
||||
}
|
||||
|
||||
m.id = MessageUnique.createUniqueMsgId(
|
||||
{
|
||||
chatType: m.chatType,
|
||||
peerUid: m.peerUid,
|
||||
guildId: '',
|
||||
},
|
||||
m.msgId
|
||||
);
|
||||
|
||||
await this.emitMsg(m).catch((e) =>
|
||||
this.context.logger.logError('处理在线文件消息失败', e)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
msgListener.onAddSendMsg = async (msg) => {
|
||||
try {
|
||||
if (msg.sendStatus === SendStatusType.KSEND_STATUS_SENDING) {
|
||||
|
||||
@ -73,6 +73,8 @@ export enum OB11MessageDataType {
|
||||
miniapp = 'miniapp', // json类
|
||||
contact = 'contact',
|
||||
location = 'location',
|
||||
onlinefile = 'onlinefile', // 在线文件/文件夹
|
||||
flashtransfer = 'flashtransfer', // QQ闪传
|
||||
}
|
||||
|
||||
export interface OB11MessagePoke {
|
||||
@ -254,6 +256,24 @@ export interface OB11MessageForward {
|
||||
};
|
||||
}
|
||||
|
||||
export interface OB11MessageOnlineFile {
|
||||
type: OB11MessageDataType.onlinefile;
|
||||
data: {
|
||||
msgId: string;
|
||||
elementId: string;
|
||||
fileName: string;
|
||||
fileSize: string;
|
||||
isDir: boolean;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OB11MessageFlashTransfer {
|
||||
type: OB11MessageDataType.flashtransfer;
|
||||
data: {
|
||||
fileSetId: string;
|
||||
}
|
||||
}
|
||||
|
||||
// 消息数据类型定义
|
||||
export type OB11MessageData =
|
||||
OB11MessageText |
|
||||
@ -261,7 +281,8 @@ export type OB11MessageData =
|
||||
OB11MessageAt | OB11MessageReply |
|
||||
OB11MessageImage | OB11MessageRecord | OB11MessageFile | OB11MessageVideo |
|
||||
OB11MessageNode | OB11MessageIdMusic | OB11MessageCustomMusic | OB11MessageJson |
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward | OB11MessageContact | OB11MessagePoke;
|
||||
OB11MessageDice | OB11MessageRPS | OB11MessageMarkdown | OB11MessageForward | OB11MessageContact |
|
||||
OB11MessagePoke | OB11MessageOnlineFile | OB11MessageFlashTransfer;
|
||||
|
||||
// 发送消息接口定义
|
||||
export interface OB11PostSendMsg {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user