From 0779628be56a89ec7d3a470565ec2cf5c9920c14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=89=8B=E7=93=9C=E4=B8=80=E5=8D=81=E9=9B=AA?= Date: Thu, 29 Jan 2026 20:32:01 +0800 Subject: [PATCH] Add image download proxy support to OneBot Introduces an 'imageDownloadProxy' config option to OneBot, allowing image downloads via a specified HTTP proxy. Updates the file download logic in napcat-common to use the undici library for proxy support, and propagates the new config through backend, frontend, and type definitions. Also adds undici as a dependency. --- packages/napcat-common/package.json | 49 +++++++------ packages/napcat-common/src/file.ts | 26 ++++++- packages/napcat-onebot/api/msg.ts | 3 +- packages/napcat-onebot/config/config.ts | 1 + .../napcat-webui-backend/src/onebot/config.ts | 1 + .../src/pages/dashboard/config/onebot.tsx | 18 +++++ .../src/store/modules/config.ts | 3 +- .../src/types/onebot_conf.d.ts | 73 ++++++++++--------- pnpm-lock.yaml | 9 +++ 9 files changed, 119 insertions(+), 64 deletions(-) diff --git a/packages/napcat-common/package.json b/packages/napcat-common/package.json index 44b76115..c26b39dd 100644 --- a/packages/napcat-common/package.json +++ b/packages/napcat-common/package.json @@ -1,28 +1,29 @@ { - "name": "napcat-common", - "version": "0.0.1", - "private": true, - "type": "module", - "main": "src/index.ts", - "scripts": { - "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + "name": "napcat-common", + "version": "0.0.1", + "private": true, + "type": "module", + "main": "src/index.ts", + "scripts": { + "typecheck": "tsc --noEmit --skipLibCheck -p tsconfig.json" + }, + "exports": { + ".": { + "import": "./src/index.ts" }, - "exports": { - ".": { - "import": "./src/index.ts" - }, - "./src/*": { - "import": "./src/*" - } - }, - "dependencies": { - "ajv": "^8.13.0", - "file-type": "^21.0.0" - }, - "devDependencies": { - "@types/node": "^22.0.1" - }, - "engines": { - "node": ">=18.0.0" + "./src/*": { + "import": "./src/*" } + }, + "dependencies": { + "ajv": "^8.13.0", + "file-type": "^21.0.0", + "undici": "^6.19.0" + }, + "devDependencies": { + "@types/node": "^22.0.1" + }, + "engines": { + "node": ">=18.0.0" + } } \ No newline at end of file diff --git a/packages/napcat-common/src/file.ts b/packages/napcat-common/src/file.ts index 20748f16..79e65aa1 100644 --- a/packages/napcat-common/src/file.ts +++ b/packages/napcat-common/src/file.ts @@ -7,6 +7,7 @@ import { solveProblem } from '@/napcat-common/src/helper'; export interface HttpDownloadOptions { url: string; headers?: Record | string; + proxy?: string; } type Uri2LocalRes = { @@ -96,6 +97,7 @@ export function calculateFileMD5 (filePath: string): Promise { async function tryDownload (options: string | HttpDownloadOptions, useReferer: boolean = false): Promise { let url: string; + let proxy: string | undefined; let headers: Record = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.71 Safari/537.36', }; @@ -104,6 +106,7 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b headers['Host'] = new URL(url).hostname; } else { url = options.url; + proxy = options.proxy; if (options.headers) { if (typeof options.headers === 'string') { headers = JSON.parse(options.headers); @@ -115,6 +118,25 @@ async function tryDownload (options: string | HttpDownloadOptions, useReferer: b if (useReferer && !headers['Referer']) { headers['Referer'] = url; } + + // 如果配置了代理,使用代理下载 + if (proxy) { + try { + // Node.js 18+ 内置 undici,使用动态导入 + const undici = await import('undici'); + const dispatcher = new undici.ProxyAgent(proxy); + const response = await undici.fetch(url, { + headers, + redirect: 'follow', + dispatcher, + }); + return response as unknown as Response; + } catch (proxyError) { + // 如果代理失败,记录错误并尝试直接下载 + console.error('代理下载失败,尝试直接下载:', proxyError); + } + } + const fetchRes = await fetch(url, { headers, redirect: 'follow' }).catch((err) => { if (err.cause) { throw err.cause; @@ -176,7 +198,7 @@ export async function checkUriType (Uri: string) { return { Uri, Type: FileUriType.Unknown }; } -export async function uriToLocalFile (dir: string, uri: string, filename: string = randomUUID(), headers?: Record): Promise { +export async function uriToLocalFile (dir: string, uri: string, filename: string = randomUUID(), headers?: Record, proxy?: string): Promise { const { Uri: HandledUri, Type: UriType } = await checkUriType(uri); const filePath = path.join(dir, filename); @@ -191,7 +213,7 @@ export async function uriToLocalFile (dir: string, uri: string, filename: string } case FileUriType.Remote: { - const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {} }); + const buffer = await httpDownload({ url: HandledUri, headers: headers ?? {}, proxy }); fs.writeFileSync(filePath, buffer); return { success: true, errMsg: '', fileName: filename, path: filePath }; } diff --git a/packages/napcat-onebot/api/msg.ts b/packages/napcat-onebot/api/msg.ts index 4ef362c7..57b1dd38 100644 --- a/packages/napcat-onebot/api/msg.ts +++ b/packages/napcat-onebot/api/msg.ts @@ -1288,7 +1288,8 @@ export class OneBotMsgApi { } realUri = await this.handleObfuckName(realUri) ?? realUri; try { - const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri); + const proxy = this.obContext.configLoader.configData.imageDownloadProxy || undefined; + const { path, fileName, errMsg, success } = await uriToLocalFile(this.core.NapCatTempPath, realUri, undefined, undefined, proxy); if (!success) { this.core.context.logger.logError('文件处理失败', errMsg); throw new Error('文件处理失败: ' + errMsg); diff --git a/packages/napcat-onebot/config/config.ts b/packages/napcat-onebot/config/config.ts index 189d61ad..f3db9e30 100644 --- a/packages/napcat-onebot/config/config.ts +++ b/packages/napcat-onebot/config/config.ts @@ -82,6 +82,7 @@ export const OneBotConfigSchema = Type.Object({ musicSignUrl: Type.String({ default: '' }), enableLocalFile2Url: Type.Boolean({ default: false }), parseMultMsg: Type.Boolean({ default: false }), + imageDownloadProxy: Type.String({ default: '' }), }); export type OneBotConfig = Static; diff --git a/packages/napcat-webui-backend/src/onebot/config.ts b/packages/napcat-webui-backend/src/onebot/config.ts index b43c2c49..164d7b8d 100644 --- a/packages/napcat-webui-backend/src/onebot/config.ts +++ b/packages/napcat-webui-backend/src/onebot/config.ts @@ -82,6 +82,7 @@ export const OneBotConfigSchema = Type.Object({ musicSignUrl: Type.String({ default: '' }), enableLocalFile2Url: Type.Boolean({ default: false }), parseMultMsg: Type.Boolean({ default: false }), + imageDownloadProxy: Type.String({ default: '' }), }); export type OneBotConfig = Static; diff --git a/packages/napcat-webui-frontend/src/pages/dashboard/config/onebot.tsx b/packages/napcat-webui-frontend/src/pages/dashboard/config/onebot.tsx index 6c9c4551..d9a0e34a 100644 --- a/packages/napcat-webui-frontend/src/pages/dashboard/config/onebot.tsx +++ b/packages/napcat-webui-frontend/src/pages/dashboard/config/onebot.tsx @@ -22,12 +22,14 @@ const OneBotConfigCard = () => { musicSignUrl: '', enableLocalFile2Url: false, parseMultMsg: false, + imageDownloadProxy: '', }, }); const reset = () => { setOnebotValue('musicSignUrl', config.musicSignUrl); setOnebotValue('enableLocalFile2Url', config.enableLocalFile2Url); setOnebotValue('parseMultMsg', config.parseMultMsg); + setOnebotValue('imageDownloadProxy', config.imageDownloadProxy); }; const onSubmit = handleOnebotSubmit(async (data) => { @@ -104,6 +106,22 @@ const OneBotConfigCard = () => { /> )} /> + ( + + )} + /> - httpClients: Array - httpSseServers: Array - websocketServers: Array - websocketClients: Array + httpServers: Array; + httpClients: Array; + httpSseServers: Array; + websocketServers: Array; + websocketClients: Array; } interface OneBotConfig { - network: NetworkConfig // 网络配置 - musicSignUrl: string // 音乐签名地址 - enableLocalFile2Url: boolean - parseMultMsg: boolean + network: NetworkConfig; // 网络配置 + musicSignUrl: string; // 音乐签名地址 + enableLocalFile2Url: boolean; + parseMultMsg: boolean; + imageDownloadProxy: string; // 图片下载代理地址 } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b079825e..ca6cc03e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,6 +51,9 @@ importers: file-type: specifier: ^21.0.0 version: 21.1.0 + undici: + specifier: ^6.19.0 + version: 6.23.0 devDependencies: '@types/node': specifier: ^22.0.1 @@ -6605,6 +6608,10 @@ packages: undici-types@6.21.0: resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} + engines: {node: '>=18.17'} + unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} @@ -14154,6 +14161,8 @@ snapshots: undici-types@6.21.0: {} + undici@6.23.0: {} + unified@11.0.5: dependencies: '@types/unist': 3.0.3