From 54203d64a359a61650f152bf653032798eded850 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+shinku-chen@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:57:40 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0TG=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E9=80=89=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/config.ts | 1 + src/server/routes/api/settings.ts | 19 +++++++++++++-- src/server/services/notifyService.ts | 23 +++++++++++------- src/web/pages/NotificationSettings.tsx | 33 +++++++++++++++++++------- 4 files changed, 56 insertions(+), 20 deletions(-) diff --git a/src/server/config.ts b/src/server/config.ts index e0c5ec7..e8dc66c 100644 --- a/src/server/config.ts +++ b/src/server/config.ts @@ -73,6 +73,7 @@ export function buildConfig(env: NodeJS.ProcessEnv) { telegramApiBaseUrl: 'https://api.telegram.org', telegramBotToken: env.TELEGRAM_BOT_TOKEN || '', telegramChatId: env.TELEGRAM_CHAT_ID || '', + telegramUseSystemProxy: parseBoolean(env.TELEGRAM_USE_SYSTEM_PROXY, false), smtpEnabled: parseBoolean(env.SMTP_ENABLED, false), smtpHost: env.SMTP_HOST || '', smtpPort: parseInt(env.SMTP_PORT || '587'), diff --git a/src/server/routes/api/settings.ts b/src/server/routes/api/settings.ts index 6bb9628..07b7743 100644 --- a/src/server/routes/api/settings.ts +++ b/src/server/routes/api/settings.ts @@ -1,4 +1,4 @@ -import { FastifyInstance } from 'fastify'; +import { FastifyInstance } from 'fastify'; import cron from 'node-cron'; import { config } from '../../config.js'; import { db, runtimeDbDialect, schema } from '../../db/index.js'; @@ -43,6 +43,7 @@ interface RuntimeSettingsBody { telegramApiBaseUrl?: string; telegramBotToken?: string; telegramChatId?: string; + telegramUseSystemProxy?: boolean; smtpEnabled?: boolean; smtpHost?: string; smtpPort?: number; @@ -288,6 +289,10 @@ function applyImportedSettingToRuntime(key: string, value: unknown) { config.telegramChatId = value.trim(); return; } + case 'telegram_use_system_proxy': { + config.telegramUseSystemProxy = !!value; + return; + } case 'smtp_enabled': { config.smtpEnabled = !!value; return; @@ -380,6 +385,7 @@ function getRuntimeSettingsResponse(currentAdminIp = '') { telegramApiBaseUrl: config.telegramApiBaseUrl, telegramBotTokenMasked: maskSecret(config.telegramBotToken), telegramChatId: config.telegramChatId, + telegramUseSystemProxy: config.telegramUseSystemProxy, smtpEnabled: config.smtpEnabled, smtpHost: config.smtpHost, smtpPort: config.smtpPort, @@ -512,7 +518,8 @@ export async function settingsRoutes(app: FastifyInstance) { const telegramTouched = body.telegramEnabled !== undefined || body.telegramApiBaseUrl !== undefined || body.telegramBotToken !== undefined - || body.telegramChatId !== undefined; + || body.telegramChatId !== undefined + || body.telegramUseSystemProxy !== undefined; const nextTelegramEnabled = body.telegramEnabled !== undefined ? !!body.telegramEnabled : config.telegramEnabled; @@ -768,6 +775,14 @@ export async function settingsRoutes(app: FastifyInstance) { upsertSetting('telegram_chat_id', config.telegramChatId); } + if (body.telegramUseSystemProxy !== undefined) { + if (!!body.telegramUseSystemProxy !== config.telegramUseSystemProxy) { + changedLabels.push('Telegram 使用系统代理'); + } + config.telegramUseSystemProxy = !!body.telegramUseSystemProxy; + upsertSetting('telegram_use_system_proxy', config.telegramUseSystemProxy); + } + if (body.smtpEnabled !== undefined) { if (!!body.smtpEnabled !== config.smtpEnabled) { changedLabels.push('SMTP 开关'); diff --git a/src/server/services/notifyService.ts b/src/server/services/notifyService.ts index c1e9888..7af5201 100644 --- a/src/server/services/notifyService.ts +++ b/src/server/services/notifyService.ts @@ -1,5 +1,6 @@ import { fetch } from 'undici'; import { config } from '../config.js'; +import { withExplicitProxyRequestInit } from './siteProxy.js'; import nodemailer, { type Transporter } from 'nodemailer'; import { createNotificationSignature, @@ -224,15 +225,19 @@ export async function sendNotification( tasks.push({ channel: 'telegram', run: async () => { - const response = await fetch(telegramApiUrl, { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ - chat_id: config.telegramChatId, - text, - disable_web_page_preview: true, - }), - }); + const telegramRequestInit = withExplicitProxyRequestInit( + config.telegramUseSystemProxy ? config.systemProxyUrl : null, + { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + chat_id: config.telegramChatId, + text, + disable_web_page_preview: true, + }), + }, + ); + const response = await fetch(telegramApiUrl, telegramRequestInit); if (!response.ok) { throw new Error(`Telegram 响应状态 ${response.status}`); } diff --git a/src/web/pages/NotificationSettings.tsx b/src/web/pages/NotificationSettings.tsx index 75dc1e2..c0917fc 100644 --- a/src/web/pages/NotificationSettings.tsx +++ b/src/web/pages/NotificationSettings.tsx @@ -12,6 +12,7 @@ type RuntimeSettings = { telegramEnabled: boolean; telegramApiBaseUrl: string; telegramChatId: string; + telegramUseSystemProxy: boolean; smtpEnabled: boolean; smtpHost: string; smtpPort: number; @@ -35,6 +36,7 @@ export default function NotificationSettings() { telegramEnabled: false, telegramApiBaseUrl: 'https://api.telegram.org', telegramChatId: '', + telegramUseSystemProxy: false, smtpEnabled: false, smtpHost: '', smtpPort: 587, @@ -78,6 +80,7 @@ export default function NotificationSettings() { telegramEnabled: !!runtimeInfo.telegramEnabled, telegramApiBaseUrl: runtimeInfo.telegramApiBaseUrl || 'https://api.telegram.org', telegramChatId: runtimeInfo.telegramChatId || '', + telegramUseSystemProxy: !!runtimeInfo.telegramUseSystemProxy, smtpEnabled: !!runtimeInfo.smtpEnabled, smtpHost: runtimeInfo.smtpHost || '', smtpPort: Number(runtimeInfo.smtpPort) || 587, @@ -115,6 +118,7 @@ export default function NotificationSettings() { telegramEnabled: runtime.telegramEnabled, telegramApiBaseUrl: runtime.telegramApiBaseUrl, telegramChatId: runtime.telegramChatId, + telegramUseSystemProxy: runtime.telegramUseSystemProxy, smtpEnabled: runtime.smtpEnabled, smtpHost: runtime.smtpHost, smtpPort: runtime.smtpPort, @@ -317,15 +321,26 @@ export default function NotificationSettings() { - +
+ + +
From c5d55f0ab0730a9ecf4b12366fac3dc46135cb74 Mon Sep 17 00:00:00 2001 From: Shinku <17696928+shinku-chen@users.noreply.github.com> Date: Fri, 20 Mar 2026 01:03:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E5=A2=9E=E5=8A=A0TG=E4=BB=A3=E7=90=86?= =?UTF-8?q?=E9=80=89=E9=A1=B9,=E5=A2=9E=E5=8A=A0=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/server/routes/api/settings.events.test.ts | 27 +++++++ src/server/services/notifyService.test.ts | 60 ++++++++++++++++ src/web/pages/NotificationSettings.test.tsx | 70 +++++++++++++++++++ 3 files changed, 157 insertions(+) diff --git a/src/server/routes/api/settings.events.test.ts b/src/server/routes/api/settings.events.test.ts index e0918e6..86ffe92 100644 --- a/src/server/routes/api/settings.events.test.ts +++ b/src/server/routes/api/settings.events.test.ts @@ -55,6 +55,7 @@ describe('settings and auth events', () => { (config as any).telegramApiBaseUrl = 'https://api.telegram.org'; (config as any).telegramBotToken = ''; (config as any).telegramChatId = ''; + (config as any).telegramUseSystemProxy = false; }); afterAll(async () => { @@ -219,6 +220,32 @@ describe('settings and auth events', () => { expect(body.message).toContain('Telegram API Base URL'); }); + it('persists and returns telegram use system proxy from runtime settings', async () => { + const updateResponse = await app.inject({ + method: 'PUT', + url: '/api/settings/runtime', + payload: { + telegramUseSystemProxy: true, + }, + }); + + expect(updateResponse.statusCode).toBe(200); + const updated = updateResponse.json() as { telegramUseSystemProxy?: boolean }; + expect(updated.telegramUseSystemProxy).toBe(true); + expect((config as any).telegramUseSystemProxy).toBe(true); + + const saved = await db.select().from(schema.settings).where(eq(schema.settings.key, 'telegram_use_system_proxy')).get(); + expect(saved?.value).toBe(JSON.stringify(true)); + + const getResponse = await app.inject({ + method: 'GET', + url: '/api/settings/runtime', + }); + expect(getResponse.statusCode).toBe(200); + const runtime = getResponse.json() as { telegramUseSystemProxy?: boolean }; + expect(runtime.telegramUseSystemProxy).toBe(true); + }); + it('persists and returns routing fallback unit cost from runtime settings', async () => { const updateResponse = await app.inject({ method: 'PUT', diff --git a/src/server/services/notifyService.test.ts b/src/server/services/notifyService.test.ts index feba21e..f96b913 100644 --- a/src/server/services/notifyService.test.ts +++ b/src/server/services/notifyService.test.ts @@ -17,12 +17,24 @@ vi.mock('undici', () => ({ fetch: (...args: unknown[]) => fetchMock(...args), })); +const withExplicitProxyRequestInitMock = vi.fn( + (_proxyUrl: unknown, options?: Record) => { + if (_proxyUrl) return { ...(options || {}), dispatcher: 'mock-proxy-dispatcher' }; + return options ?? {}; + }, +); + +vi.mock('./siteProxy.js', () => ({ + withExplicitProxyRequestInit: (...args: unknown[]) => withExplicitProxyRequestInitMock(...args), +})); + describe('notifyService', () => { beforeEach(async () => { vi.resetModules(); sendMailMock.mockReset(); createTransportMock.mockClear(); fetchMock.mockReset(); + withExplicitProxyRequestInitMock.mockClear(); const { config } = await import('../config.js'); config.notifyCooldownSec = 300; @@ -35,6 +47,8 @@ describe('notifyService', () => { (config as any).telegramEnabled = false; (config as any).telegramBotToken = ''; (config as any).telegramChatId = ''; + (config as any).telegramUseSystemProxy = false; + config.systemProxyUrl = ''; config.smtpEnabled = true; config.smtpHost = 'smtp.example.com'; config.smtpPort = 465; @@ -222,4 +236,50 @@ describe('notifyService', () => { }), ); }); + + it('applies system proxy dispatcher when telegramUseSystemProxy is enabled', async () => { + const { config } = await import('../config.js'); + (config as any).telegramEnabled = true; + (config as any).telegramBotToken = '123456:telegram-token'; + (config as any).telegramChatId = '-1001234567890'; + (config as any).telegramUseSystemProxy = true; + config.systemProxyUrl = 'http://127.0.0.1:7890'; + config.smtpEnabled = false; + + fetchMock.mockResolvedValue({ ok: true, status: 200 }); + + const { sendNotification } = await import('./notifyService.js'); + await sendNotification('测试通知', 'proxy-test', 'info', { bypassThrottle: true, throwOnFailure: true }); + + expect(withExplicitProxyRequestInitMock).toHaveBeenCalledWith( + 'http://127.0.0.1:7890', + expect.objectContaining({ method: 'POST' }), + ); + expect(fetchMock).toHaveBeenCalledWith( + expect.stringContaining('/sendMessage'), + expect.objectContaining({ dispatcher: 'mock-proxy-dispatcher' }), + ); + }); + + it('does not apply proxy dispatcher when telegramUseSystemProxy is disabled', async () => { + const { config } = await import('../config.js'); + (config as any).telegramEnabled = true; + (config as any).telegramBotToken = '123456:telegram-token'; + (config as any).telegramChatId = '-1001234567890'; + (config as any).telegramUseSystemProxy = false; + config.systemProxyUrl = 'http://127.0.0.1:7890'; + config.smtpEnabled = false; + + fetchMock.mockResolvedValue({ ok: true, status: 200 }); + + const { sendNotification } = await import('./notifyService.js'); + await sendNotification('测试通知', 'no-proxy-test', 'info', { bypassThrottle: true, throwOnFailure: true }); + + expect(withExplicitProxyRequestInitMock).toHaveBeenCalledWith( + null, + expect.objectContaining({ method: 'POST' }), + ); + const fetchOptions = fetchMock.mock.calls[0]?.[1] as Record; + expect(fetchOptions.dispatcher).toBeUndefined(); + }); }); diff --git a/src/web/pages/NotificationSettings.test.tsx b/src/web/pages/NotificationSettings.test.tsx index 92c5415..b12c8e0 100644 --- a/src/web/pages/NotificationSettings.test.tsx +++ b/src/web/pages/NotificationSettings.test.tsx @@ -43,6 +43,7 @@ describe('NotificationSettings', () => { telegramApiBaseUrl: 'https://tg-proxy.example.com', telegramChatId: '-1001234567890', telegramBotTokenMasked: '1234****token', + telegramUseSystemProxy: false, smtpEnabled: false, smtpHost: '', smtpPort: 587, @@ -105,4 +106,73 @@ describe('NotificationSettings', () => { root?.unmount(); } }); + + it('loads and saves telegram use system proxy toggle', async () => { + apiMock.getRuntimeSettings.mockResolvedValue({ + webhookUrl: '', + barkUrl: '', + webhookEnabled: true, + barkEnabled: true, + serverChanEnabled: false, + telegramEnabled: true, + telegramApiBaseUrl: 'https://api.telegram.org', + telegramChatId: '-1001234567890', + telegramBotTokenMasked: '1234****token', + telegramUseSystemProxy: false, + smtpEnabled: false, + smtpHost: '', + smtpPort: 587, + smtpSecure: false, + smtpUser: '', + smtpFrom: '', + smtpTo: '', + notifyCooldownSec: 300, + }); + + let root: ReturnType | null = null; + try { + await act(async () => { + root = create( + + + + + , + ); + }); + await flushMicrotasks(); + + const allCheckboxes = root.root.findAll((node) => ( + node.type === 'input' && node.props.type === 'checkbox' + )); + const proxyCheckbox = allCheckboxes.find((node) => { + const parent = node.parent; + if (!parent) return false; + const text = collectText(parent); + return text.includes('使用系统代理'); + }); + expect(proxyCheckbox).toBeTruthy(); + expect(proxyCheckbox!.props.checked).toBe(false); + + await act(async () => { + proxyCheckbox!.props.onChange({ target: { checked: true } }); + }); + + const saveButton = root.root.find((node) => ( + node.type === 'button' + && typeof node.props.onClick === 'function' + && collectText(node).includes('保存通知设置') + )); + await act(async () => { + saveButton.props.onClick(); + }); + await flushMicrotasks(); + + expect(apiMock.updateRuntimeSettings).toHaveBeenCalledWith(expect.objectContaining({ + telegramUseSystemProxy: true, + })); + } finally { + root?.unmount(); + } + }); });