import { OneBotAction } from '@/napcat-onebot/action/OneBotAction'; import { ActionName } from '@/napcat-onebot/action/router'; import fs from 'fs'; import { join as joinPath } from 'node:path'; import { calculateFileMD5, uriToLocalFile } from 'napcat-common/src/file'; import { randomUUID } from 'crypto'; import { Static, Type } from '@sinclair/typebox'; import { GoCQHTTPActionsExamples } from '../example/GoCQHTTPActionsExamples'; const PayloadSchema = Type.Object({ url: Type.Optional(Type.String({ description: '下载链接' })), base64: Type.Optional(Type.String({ description: 'base64数据' })), name: Type.Optional(Type.String({ description: '文件名' })), headers: Type.Optional(Type.Union([Type.String(), Type.Array(Type.String())], { description: '请求头' })), }); type PayloadType = Static; const ReturnSchema = Type.Object({ file: Type.String({ description: '文件路径' }), }, { description: '下载结果' }); type ReturnType = Static; export default class GoCQHTTPDownloadFile extends OneBotAction { override actionName = ActionName.GoCQHTTP_DownloadFile; override payloadSchema = PayloadSchema; override returnSchema = ReturnSchema; override actionSummary = '下载文件'; override actionDescription = '下载网络文件到本地临时目录'; override actionTags = ['Go-CQHTTP']; override payloadExample = GoCQHTTPActionsExamples.DownloadFile.payload; override returnExample = GoCQHTTPActionsExamples.DownloadFile.response; async _handle (payload: PayloadType): Promise { const isRandomName = !payload.name; const name = payload.name || randomUUID(); let result: Awaited>; if (payload.base64) { result = await uriToLocalFile(this.core.NapCatTempPath, `base64://${payload.base64}`, name); } else if (payload.url) { const headers = this.getHeaders(payload.headers); result = await uriToLocalFile(this.core.NapCatTempPath, payload.url, name, headers); } else { throw new Error('不存在任何文件, 无法下载'); } if (!result.success) { throw new Error(result.errMsg); } const filePath = result.path; if (fs.existsSync(filePath)) { if (isRandomName) { // 默认实现要名称未填写时文件名为文件 md5 const md5 = await calculateFileMD5(filePath); const newPath = joinPath(this.core.NapCatTempPath, md5); fs.renameSync(filePath, newPath); return { file: newPath }; } return { file: filePath }; } else { throw new Error('文件写入失败, 检查权限'); } } getHeaders (headersIn?: string | string[]): Record { const headers: Record = {}; if (typeof headersIn === 'string') { headersIn = headersIn.split('[\\r\\n]'); } if (Array.isArray(headersIn)) { for (const headerItem of headersIn) { const spilt = headerItem.indexOf('='); if (spilt < 0) { headers[headerItem] = ''; } else { const key = headerItem.substring(0, spilt); headers[key] = headerItem.substring(spilt + 1); } } } if (!headers['Content-Type']) { headers['Content-Type'] = 'application/octet-stream'; } return headers; } }