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();
+ }
+ });
});