feat: api /get_file

This commit is contained in:
linyuchen
2024-03-19 18:39:22 +08:00
parent 37c4f02118
commit 01d77827a8
7 changed files with 110 additions and 38 deletions

View File

@@ -222,14 +222,14 @@ class DBUtil {
return this.currentShortId;
}
async addFileCache(fileName: string, data: FileCache) {
const key = this.DB_KEY_PREFIX_FILE + fileName;
async addFileCache(fileNameOrUuid: string, data: FileCache) {
const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid;
if (this.cache[key]) {
return
}
let cacheDBData = {...data}
delete cacheDBData['downloadFunc']
this.cache[fileName] = data;
this.cache[fileNameOrUuid] = data;
try {
await this.db.put(key, JSON.stringify(cacheDBData));
} catch (e) {
@@ -237,8 +237,8 @@ class DBUtil {
}
}
async getFileCache(fileName: string): Promise<FileCache | undefined> {
const key = this.DB_KEY_PREFIX_FILE + fileName;
async getFileCache(fileNameOrUuid: string): Promise<FileCache | undefined> {
const key = this.DB_KEY_PREFIX_FILE + (fileNameOrUuid);
if (this.cache[key]) {
return this.cache[key] as FileCache
}

View File

@@ -38,6 +38,8 @@ export interface FileCache {
fileName: string
filePath: string
fileSize: string
fileUuid?: string
url?: string
msgId?: string
downloadFunc?: () => Promise<void>
}

View File

@@ -4,7 +4,8 @@ import {
CacheFileListItem,
CacheFileType,
CacheScanResult,
ChatCacheList, ChatCacheListItemBasic,
ChatCacheList,
ChatCacheListItemBasic,
ChatType,
ElementType
} from "../types";
@@ -13,12 +14,13 @@ import fs from "fs";
import {ReceiveCmdS} from "../hook";
import {log} from "../../common/utils/log";
export class NTQQFileApi{
export class NTQQFileApi {
static async getFileType(filePath: string) {
return await callNTQQApi<{ ext: string }>({
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_TYPE, args: [filePath]
})
}
static async getFileMd5(filePath: string) {
return await callNTQQApi<string>({
className: NTQQApiClass.FS_API,
@@ -26,6 +28,7 @@ export class NTQQFileApi{
args: [filePath]
})
}
static async copyFile(filePath: string, destPath: string) {
return await callNTQQApi<string>({
className: NTQQApiClass.FS_API,
@@ -36,11 +39,13 @@ export class NTQQFileApi{
}]
})
}
static async getFileSize(filePath: string) {
return await callNTQQApi<number>({
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.FILE_SIZE, args: [filePath]
})
}
// 上传文件到QQ的文件夹
static async uploadFile(filePath: string, elementType: ElementType = ElementType.PIC) {
const md5 = await NTQQFileApi.getFileMd5(filePath);
@@ -79,14 +84,18 @@ export class NTQQFileApi{
fileSize
}
}
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string) {
static async downloadMedia(msgId: string, chatType: ChatType, peerUid: string, elementId: string, thumbPath: string, sourcePath: string, isFile: boolean = false) {
// 用于下载收到的消息中的图片等
if (fs.existsSync(sourcePath)) {
if (sourcePath && fs.existsSync(sourcePath)) {
return sourcePath
}
const apiParams = [
{
getReq: {
fileModelId: "0",
downloadSourceType: 0,
triggerType: 1,
msgId: msgId,
chatType: chatType,
peerUid: peerUid,
@@ -96,20 +105,21 @@ export class NTQQFileApi{
filePath: thumbPath,
},
},
undefined,
null,
]
// log("需要下载media", sourcePath);
await callNTQQApi({
methodName: NTQQApiMethod.DOWNLOAD_MEDIA,
args: apiParams,
cbCmd: ReceiveCmdS.MEDIA_DOWNLOAD_COMPLETE,
cmdCB: (payload: { notifyInfo: { filePath: string } }) => {
// log("media 下载完成判断", payload.notifyInfo.filePath, sourcePath);
return payload.notifyInfo.filePath == sourcePath;
cmdCB: (payload: { notifyInfo: { filePath: string, msgId: string } }) => {
log("media 下载完成判断", payload.notifyInfo.msgId, msgId);
return payload.notifyInfo.msgId == msgId;
}
})
return sourcePath
}
static async getImageSize(filePath: string) {
return await callNTQQApi<{ width: number, height: number }>({
className: NTQQApiClass.FS_API, methodName: NTQQApiMethod.IMAGE_SIZE, args: [filePath]
@@ -118,7 +128,7 @@ export class NTQQFileApi{
}
export class NTQQFileCacheApi{
export class NTQQFileCacheApi {
static async setCacheSilentScan(isSilent: boolean = true) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_SET_SILENCE,
@@ -127,6 +137,7 @@ export class NTQQFileCacheApi{
}, null]
});
}
static getCacheSessionPathList() {
return callNTQQApi<{
key: string,
@@ -136,6 +147,7 @@ export class NTQQFileCacheApi{
methodName: NTQQApiMethod.CACHE_PATH_SESSION,
});
}
static clearCache(cacheKeys: Array<string> = ['tmp', 'hotUpdate']) {
return callNTQQApi<any>({ // TODO: 目前还不知道真正的返回值是什么
methodName: NTQQApiMethod.CACHE_CLEAR,
@@ -144,6 +156,7 @@ export class NTQQFileCacheApi{
}, null]
});
}
static addCacheScannedPaths(pathMap: object = {}) {
return callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_ADD_SCANNED_PATH,
@@ -152,6 +165,7 @@ export class NTQQFileCacheApi{
}, null]
});
}
static scanCache() {
callNTQQApi<GeneralCallResult>({
methodName: ReceiveCmdS.CACHE_SCAN_FINISH,
@@ -163,6 +177,7 @@ export class NTQQFileCacheApi{
timeoutSecond: 300,
});
}
static getHotUpdateCachePath() {
return callNTQQApi<string>({
className: NTQQApiClass.HOTUPDATE_API,
@@ -176,6 +191,7 @@ export class NTQQFileCacheApi{
methodName: NTQQApiMethod.CACHE_PATH_DESKTOP_TEMP
});
}
static getChatCacheList(type: ChatType, pageSize: number = 1000, pageIndex: number = 0) {
return new Promise<ChatCacheList>((res, rej) => {
callNTQQApi<ChatCacheList>({
@@ -190,6 +206,7 @@ export class NTQQFileCacheApi{
.catch(e => rej(e));
});
}
static getFileCacheInfo(fileType: CacheFileType, pageSize: number = 1000, lastRecord?: CacheFileListItem) {
const _lastRecord = lastRecord ? lastRecord : {fileType: fileType};
@@ -204,6 +221,7 @@ export class NTQQFileCacheApi{
}, null]
})
}
static async clearChatCache(chats: ChatCacheListItemBasic[] = [], fileKeys: string[] = []) {
return await callNTQQApi<GeneralCallResult>({
methodName: NTQQApiMethod.CACHE_CHAT_CLEAR,

View File

@@ -2,10 +2,12 @@ import BaseAction from "./BaseAction";
import fs from "fs/promises";
import {dbUtil} from "../../common/db";
import {getConfigUtil} from "../../common/config";
import {log, uri2local} from "../../common/utils";
import {log, sleep, uri2local} from "../../common/utils";
import {NTQQFileApi} from "../../ntqqapi/api/file";
import {ActionName} from "./types";
export interface GetFilePayload {
file: string // 文件名
file: string // 文件名或者fileUuid
}
export interface GetFileResponse {
@@ -31,13 +33,37 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
await fs.access(cache.filePath, fs.constants.F_OK)
} catch (e) {
log("file not found", e)
const downloadResult = await uri2local(cache.url)
if (downloadResult.success) {
cache.filePath = downloadResult.path
dbUtil.addFileCache(payload.file, cache).then()
} else {
throw new Error("file download failed. " + downloadResult.errMsg)
if (cache.url){
const downloadResult = await uri2local(cache.url)
if (downloadResult.success) {
cache.filePath = downloadResult.path
dbUtil.addFileCache(payload.file, cache).then()
} else {
throw new Error("file download failed. " + downloadResult.errMsg)
}
}
else{
// 没有url的可能是私聊文件或者群文件需要自己下载
log("需要调用 NTQQ 下载文件api")
if (cache.msgId) {
let msg = await dbUtil.getMsgByLongId(cache.msgId)
if (msg){
log("找到了文件 msg", msg)
const element = msg.elements.find(e=>e.fileElement)
log("找到了文件 element", element);
// 构建下载函数
await NTQQFileApi.downloadMedia(msg.msgId, msg.chatType, msg.peerUid,
element.elementId, "", "", true)
await sleep(1000);
msg = await dbUtil.getMsgByLongId(cache.msgId)
log("下载完成后的msg", msg)
cache.filePath = msg?.elements.find(e=>e.fileElement)?.fileElement?.filePath
dbUtil.addFileCache(payload.file, cache).then()
}
}
}
}
let res: GetFileResponse = {
file: cache.filePath,
@@ -47,7 +73,11 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
}
if (enableLocalFile2Url) {
if (!cache.url) {
res.base64 = await fs.readFile(cache.filePath, 'base64')
try{
res.base64 = await fs.readFile(cache.filePath, 'base64')
}catch (e) {
throw new Error("文件下载失败. " + e)
}
}
}
// if (autoDeleteFile) {
@@ -57,4 +87,16 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
// }
return res
}
}
export default class GetFile extends GetFileBase {
actionName = ActionName.GetFile
protected async _handle(payload: {file_id: string, file: string}): Promise<GetFileResponse> {
if (!payload.file_id) {
throw new Error('file_id 不能为空')
}
payload.file = payload.file_id
return super._handle(payload);
}
}

View File

@@ -38,8 +38,10 @@ import GetGroupAddRequest from "./llonebot/GetGroupAddRequest";
import SetQQAvatar from './llonebot/SetQQAvatar'
import GoCQHTTPDownloadFile from "./go-cqhttp/DownloadFile";
import GoCQHTTPGetGroupMsgHistory from "./go-cqhttp/GetGroupMsgHistory";
import GetFile from "./GetFile";
export const actionHandlers = [
new GetFile(),
new Debug(),
new GetConfigAction(),
new SetConfigAction(),

View File

@@ -14,11 +14,14 @@ export interface InvalidCheckResult {
}
export enum ActionName {
// llonebot
GetGroupIgnoreAddRequest = "get_group_ignore_add_request",
SetQQAvatar = "set_qq_avatar",
GetConfig = "get_config",
SetConfig = "set_config",
Debug = "llonebot_debug",
GetFile = "get_file",
// onebot 11
SendLike = "send_like",
GetLoginInfo = "get_login_info",
GetFriendList = "get_friend_list",

View File

@@ -16,7 +16,8 @@ import {
GroupMember,
IMAGE_HTTP_HOST,
RawMessage,
SelfInfo, Sex,
SelfInfo,
Sex,
TipGroupElementType,
User
} from '../ntqqapi/types';
@@ -174,10 +175,12 @@ export class OB11Constructor {
message_data["type"] = OB11MessageDataType.file;
message_data["data"]["file"] = element.fileElement.fileName
// message_data["data"]["path"] = element.fileElement.filePath
// message_data["data"]["file_id"] = element.fileElement.fileUuid
message_data["data"]["file_id"] = element.fileElement.fileUuid
message_data["data"]["file_size"] = element.fileElement.fileSize
dbUtil.addFileCache(element.fileElement.fileName, {
dbUtil.addFileCache(element.fileElement.fileUuid, {
msgId: msg.msgId,
fileName: element.fileElement.fileName,
fileUuid: element.fileElement.fileUuid,
filePath: element.fileElement.filePath,
fileSize: element.fileElement.fileSize,
downloadFunc: async () => {
@@ -251,18 +254,16 @@ export class OB11Constructor {
// log("构造群增加事件", event)
return event;
}
}
else if (groupElement.type === TipGroupElementType.ban) {
} else if (groupElement.type === TipGroupElementType.ban) {
log("收到群群员禁言提示", groupElement)
const memberUid = groupElement.shutUp.member.uid
const adminUid = groupElement.shutUp.admin.uid
let memberUin: string = ""
let duration = parseInt(groupElement.shutUp.duration)
let sub_type: "ban" | "lift_ban" = duration > 0 ? "ban" : "lift_ban"
if (memberUid){
if (memberUid) {
memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
}
else {
} else {
memberUin = "0"; // 0表示全员禁言
if (duration > 0) {
duration = -1
@@ -273,16 +274,19 @@ export class OB11Constructor {
return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type);
}
}
}
else if (element.fileElement){
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {id: element.fileElement.fileUuid, name: element.fileElement.fileName, size: parseInt(element.fileElement.fileSize)})
} else if (element.fileElement) {
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
id: element.fileElement.fileUuid,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize)
})
}
if (grayTipElement) {
if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER){
if (grayTipElement.subElementType == GrayTipElementSubType.INVITE_NEW_MEMBER) {
log("收到新人被邀请进群消息", grayTipElement)
const xmlElement = grayTipElement.xmlElement
if (xmlElement?.content){
if (xmlElement?.content) {
const regex = /jp="(\d+)"/g;
let matches = [];
@@ -291,7 +295,7 @@ export class OB11Constructor {
while ((match = regex.exec(xmlElement.content)) !== null) {
matches.push(match[1]);
}
if (matches.length === 2){
if (matches.length === 2) {
const [inviter, invitee] = matches;
return new OB11GroupIncreaseEvent(parseInt(msg.peerUid), parseInt(invitee), parseInt(inviter), "invite");
}
@@ -330,7 +334,7 @@ export class OB11Constructor {
}[role]
}
static sex(sex: Sex): OB11UserSex{
static sex(sex: Sex): OB11UserSex {
const sexMap = {
[Sex.male]: OB11UserSex.male,
[Sex.female]: OB11UserSex.female,
@@ -338,6 +342,7 @@ export class OB11Constructor {
}
return sexMap[sex] || OB11UserSex.unknown
}
static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
return {
group_id: parseInt(group_id),