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

View File

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

View File

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

View File

@@ -2,10 +2,12 @@ import BaseAction from "./BaseAction";
import fs from "fs/promises"; import fs from "fs/promises";
import {dbUtil} from "../../common/db"; import {dbUtil} from "../../common/db";
import {getConfigUtil} from "../../common/config"; 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 { export interface GetFilePayload {
file: string // 文件名 file: string // 文件名或者fileUuid
} }
export interface GetFileResponse { export interface GetFileResponse {
@@ -31,6 +33,7 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
await fs.access(cache.filePath, fs.constants.F_OK) await fs.access(cache.filePath, fs.constants.F_OK)
} catch (e) { } catch (e) {
log("file not found", e) log("file not found", e)
if (cache.url){
const downloadResult = await uri2local(cache.url) const downloadResult = await uri2local(cache.url)
if (downloadResult.success) { if (downloadResult.success) {
cache.filePath = downloadResult.path cache.filePath = downloadResult.path
@@ -39,6 +42,29 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
throw new Error("file download failed. " + downloadResult.errMsg) 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 = { let res: GetFileResponse = {
file: cache.filePath, file: cache.filePath,
url: cache.url, url: cache.url,
@@ -47,7 +73,11 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
} }
if (enableLocalFile2Url) { if (enableLocalFile2Url) {
if (!cache.url) { if (!cache.url) {
try{
res.base64 = await fs.readFile(cache.filePath, 'base64') res.base64 = await fs.readFile(cache.filePath, 'base64')
}catch (e) {
throw new Error("文件下载失败. " + e)
}
} }
} }
// if (autoDeleteFile) { // if (autoDeleteFile) {
@@ -58,3 +88,15 @@ export class GetFileBase extends BaseAction<GetFilePayload, GetFileResponse> {
return res 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 SetQQAvatar from './llonebot/SetQQAvatar'
import GoCQHTTPDownloadFile from "./go-cqhttp/DownloadFile"; import GoCQHTTPDownloadFile from "./go-cqhttp/DownloadFile";
import GoCQHTTPGetGroupMsgHistory from "./go-cqhttp/GetGroupMsgHistory"; import GoCQHTTPGetGroupMsgHistory from "./go-cqhttp/GetGroupMsgHistory";
import GetFile from "./GetFile";
export const actionHandlers = [ export const actionHandlers = [
new GetFile(),
new Debug(), new Debug(),
new GetConfigAction(), new GetConfigAction(),
new SetConfigAction(), new SetConfigAction(),

View File

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

View File

@@ -16,7 +16,8 @@ import {
GroupMember, GroupMember,
IMAGE_HTTP_HOST, IMAGE_HTTP_HOST,
RawMessage, RawMessage,
SelfInfo, Sex, SelfInfo,
Sex,
TipGroupElementType, TipGroupElementType,
User User
} from '../ntqqapi/types'; } from '../ntqqapi/types';
@@ -174,10 +175,12 @@ export class OB11Constructor {
message_data["type"] = OB11MessageDataType.file; message_data["type"] = OB11MessageDataType.file;
message_data["data"]["file"] = element.fileElement.fileName message_data["data"]["file"] = element.fileElement.fileName
// message_data["data"]["path"] = element.fileElement.filePath // 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 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, fileName: element.fileElement.fileName,
fileUuid: element.fileElement.fileUuid,
filePath: element.fileElement.filePath, filePath: element.fileElement.filePath,
fileSize: element.fileElement.fileSize, fileSize: element.fileElement.fileSize,
downloadFunc: async () => { downloadFunc: async () => {
@@ -251,8 +254,7 @@ export class OB11Constructor {
// log("构造群增加事件", event) // log("构造群增加事件", event)
return event; return event;
} }
} } else if (groupElement.type === TipGroupElementType.ban) {
else if (groupElement.type === TipGroupElementType.ban) {
log("收到群群员禁言提示", groupElement) log("收到群群员禁言提示", groupElement)
const memberUid = groupElement.shutUp.member.uid const memberUid = groupElement.shutUp.member.uid
const adminUid = groupElement.shutUp.admin.uid const adminUid = groupElement.shutUp.admin.uid
@@ -261,8 +263,7 @@ export class OB11Constructor {
let sub_type: "ban" | "lift_ban" = duration > 0 ? "ban" : "lift_ban" 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 memberUin = (await getGroupMember(msg.peerUid, memberUid))?.uin || (await NTQQUserApi.getUserDetailInfo(memberUid))?.uin
} } else {
else {
memberUin = "0"; // 0表示全员禁言 memberUin = "0"; // 0表示全员禁言
if (duration > 0) { if (duration > 0) {
duration = -1 duration = -1
@@ -273,9 +274,12 @@ export class OB11Constructor {
return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type); return new OB11GroupBanEvent(parseInt(msg.peerUid), parseInt(memberUin), parseInt(adminUin), duration, sub_type);
} }
} }
} } else if (element.fileElement) {
else if (element.fileElement){ return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {
return new OB11GroupUploadNoticeEvent(parseInt(msg.peerUid), parseInt(msg.senderUin), {id: element.fileElement.fileUuid, name: element.fileElement.fileName, size: parseInt(element.fileElement.fileSize)}) id: element.fileElement.fileUuid,
name: element.fileElement.fileName,
size: parseInt(element.fileElement.fileSize)
})
} }
if (grayTipElement) { if (grayTipElement) {
@@ -338,6 +342,7 @@ export class OB11Constructor {
} }
return sexMap[sex] || OB11UserSex.unknown return sexMap[sex] || OB11UserSex.unknown
} }
static groupMember(group_id: string, member: GroupMember): OB11GroupMember { static groupMember(group_id: string, member: GroupMember): OB11GroupMember {
return { return {
group_id: parseInt(group_id), group_id: parseInt(group_id),