Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Docs/Architecture_Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,9 @@ TelegramSearchBot 是一个功能丰富的 Telegram 机器人,提供消息搜

### 本地 Bot API 服务
- **内置 Telegram Bot API**: 可选启用内置 Bot API 服务
- **外部 Local API**: 也可连接到已在本机其他位置运行的 Local Bot API
- **优势**: 支持最大 2GB 文件发送(vs 云端 50MB)
- **配置**: 需要 `TelegramBotApiId` 和 `TelegramBotApiHash`(从 my.telegram.org 获取)
- **配置**: 内置模式需要 `TelegramBotApiId` 和 `TelegramBotApiHash`(从 my.telegram.org 获取);外部模式使用 `ExternalLocalBotApiBaseUrl`

## 平台兼容性

Expand Down Expand Up @@ -269,4 +270,4 @@ TelegramSearchBot/
---

*最后更新: 2026-03-26*
*文档版本: 1.1*
*文档版本: 1.1*
Comment on lines 272 to +273
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Refresh the document footer.

The architecture doc content changed for the new external Local Bot API mode, but the footer still shows the prior update date/version.

📝 Proposed footer update
-*最后更新: 2026-03-26*
-*文档版本: 1.1*
+*最后更新: 2026-04-21*
+*文档版本: 1.2*
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
*最后更新: 2026-03-26*
*文档版本: 1.1*
\ No newline at end of file
*文档版本: 1.1*
*最后更新: 2026-04-21*
*文档版本: 1.2*
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Docs/Architecture_Overview.md` around lines 272 - 273, Update the document
footer to reflect the new change by replacing the existing footer fields "*最后更新:
2026-03-26*" and "*文档版本: 1.1*" with the current last-updated date and new
document version (e.g., update the date and bump the version string accordingly)
so the footer matches the new external Local Bot API mode changes.

2 changes: 1 addition & 1 deletion Docs/README_MCP.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ TelegramSearchBot 内置了以下工具,通过 `BuiltInToolAttribute` 标记
| `send_document_file` | 发送文件(使用文件路径) | `file_path`, `caption?`, `reply_to_message_id?` |

**文件大小限制**:
- 本地Bot API (`EnableLocalBotAPI=true`): 最大 2GB
- 本地 Bot API(内置或外部): 最大 2GB
- 云端API: 最大 50MB

### 2.3 搜索工具
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
"EnableAutoASR": false,
"IsLocalAPI": false,
"EnableLocalBotAPI": false,
"ExternalLocalBotApiBaseUrl": "",
"TelegramBotApiId": "",
"TelegramBotApiHash": "",
"LocalBotApiPort": 8081,
Expand Down Expand Up @@ -80,11 +81,13 @@
- `AdminId`: 管理员Telegram用户ID(必须为数字)

- **本地Bot API服务**:
- `EnableLocalBotAPI`: 是否启用内置Telegram Bot API服务(默认false)
- `EnableLocalBotAPI`: 是否启用内置 Telegram Bot API 服务(默认false)
- `ExternalLocalBotApiBaseUrl`: 外部本地 Bot API 地址;配置后会自动按本地 API 模式运行,无需再手动设置 `BaseUrl`/`IsLocalAPI`
- `TelegramBotApiId`: Telegram Bot API的API ID(从my.telegram.org获取)
- `TelegramBotApiHash`: Telegram Bot API的API Hash(从my.telegram.org获取)
- `LocalBotApiPort`: 本地Bot API服务端口(默认8081)
- **优势**: 启用后可发送最大2GB文件(vs 云端50MB限制)
- **说明**: `EnableLocalBotAPI=true` 时会启动内置 `telegram-bot-api.exe`;若使用外部 local API,请保持 `EnableLocalBotAPI=false` 并填写 `ExternalLocalBotApiBaseUrl`
- **优势**: 使用本地 Bot API(内置或外部)后可发送最大2GB文件(vs 云端50MB限制)

- **AI相关**:
- `OllamaModelName`: 本地模型名称(默认"qwen2.5:72b-instruct-q2_K")
Expand Down
117 changes: 77 additions & 40 deletions TelegramSearchBot.Common/Env.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,52 +11,85 @@ static Env() {
if (!Directory.Exists(WorkDir)) {
Directory.CreateDirectory(WorkDir);
}
var config = new Config();
try {
var config = JsonConvert.DeserializeObject<Config>(File.ReadAllText(Path.Combine(WorkDir, "Config.json")));
if (config is null) return;
EnableLocalBotAPI = config.EnableLocalBotAPI;
TelegramBotApiId = config.TelegramBotApiId;
TelegramBotApiHash = config.TelegramBotApiHash;
LocalBotApiPort = config.LocalBotApiPort;
if (config.EnableLocalBotAPI) {
BaseUrl = $"http://127.0.0.1:{config.LocalBotApiPort}";
IsLocalAPI = true;
} else {
BaseUrl = config.BaseUrl;
IsLocalAPI = config.IsLocalAPI;
var loadedConfig = JsonConvert.DeserializeObject<Config>(File.ReadAllText(Path.Combine(WorkDir, "Config.json")));
if (loadedConfig is not null) {
config = loadedConfig;
}
BotToken = config.BotToken;
AdminId = config.AdminId;
EnableAutoOCR = config.EnableAutoOCR;
EnableAutoASR = config.EnableAutoASR;
//WorkDir = config.WorkDir;
TaskDelayTimeout = config.TaskDelayTimeout;
SameServer = config.SameServer;
OllamaModelName = config.OllamaModelName;
EnableVideoASR = config.EnableVideoASR;
EnableOpenAI = config.EnableOpenAI;
OpenAIModelName = config.OpenAIModelName;
OLTPAuth = config.OLTPAuth;
OLTPAuthUrl = config.OLTPAuthUrl;
OLTPName = config.OLTPName;
BraveApiKey = config.BraveApiKey;
EnableAccounting = config.EnableAccounting;
MaxToolCycles = config.MaxToolCycles;
EnableLLMAgentProcess = config.EnableLLMAgentProcess;
AgentHeartbeatIntervalSeconds = config.AgentHeartbeatIntervalSeconds;
AgentHeartbeatTimeoutSeconds = config.AgentHeartbeatTimeoutSeconds;
AgentChunkPollingIntervalMilliseconds = config.AgentChunkPollingIntervalMilliseconds;
AgentIdleTimeoutMinutes = config.AgentIdleTimeoutMinutes;
MaxConcurrentAgents = config.MaxConcurrentAgents;
AgentTaskTimeoutSeconds = config.AgentTaskTimeoutSeconds;
AgentShutdownGracePeriodSeconds = config.AgentShutdownGracePeriodSeconds;
AgentMaxRecoveryAttempts = config.AgentMaxRecoveryAttempts;
AgentQueueBacklogWarningThreshold = config.AgentQueueBacklogWarningThreshold;
AgentProcessMemoryLimitMb = config.AgentProcessMemoryLimitMb;
} catch {
}

var botApiEndpoint = ResolveBotApiEndpoint(config);
EnableLocalBotAPI = config.EnableLocalBotAPI;
TelegramBotApiId = config.TelegramBotApiId;
TelegramBotApiHash = config.TelegramBotApiHash;
LocalBotApiPort = config.LocalBotApiPort;
ExternalLocalBotApiBaseUrl = botApiEndpoint.ExternalLocalBotApiBaseUrl;
BaseUrl = botApiEndpoint.BaseUrl;
IsLocalAPI = botApiEndpoint.IsLocalApi;
BotToken = config.BotToken;
AdminId = config.AdminId;
EnableAutoOCR = config.EnableAutoOCR;
EnableAutoASR = config.EnableAutoASR;
//WorkDir = config.WorkDir;
TaskDelayTimeout = config.TaskDelayTimeout;
SameServer = config.SameServer;
OllamaModelName = config.OllamaModelName;
EnableVideoASR = config.EnableVideoASR;
EnableOpenAI = config.EnableOpenAI;
OpenAIModelName = config.OpenAIModelName;
OLTPAuth = config.OLTPAuth;
OLTPAuthUrl = config.OLTPAuthUrl;
OLTPName = config.OLTPName;
BraveApiKey = config.BraveApiKey;
EnableAccounting = config.EnableAccounting;
MaxToolCycles = config.MaxToolCycles;
EnableLLMAgentProcess = config.EnableLLMAgentProcess;
AgentHeartbeatIntervalSeconds = config.AgentHeartbeatIntervalSeconds;
AgentHeartbeatTimeoutSeconds = config.AgentHeartbeatTimeoutSeconds;
AgentChunkPollingIntervalMilliseconds = config.AgentChunkPollingIntervalMilliseconds;
AgentIdleTimeoutMinutes = config.AgentIdleTimeoutMinutes;
MaxConcurrentAgents = config.MaxConcurrentAgents;
AgentTaskTimeoutSeconds = config.AgentTaskTimeoutSeconds;
AgentShutdownGracePeriodSeconds = config.AgentShutdownGracePeriodSeconds;
AgentMaxRecoveryAttempts = config.AgentMaxRecoveryAttempts;
AgentQueueBacklogWarningThreshold = config.AgentQueueBacklogWarningThreshold;
AgentProcessMemoryLimitMb = config.AgentProcessMemoryLimitMb;
}

public static BotApiEndpointSettings ResolveBotApiEndpoint(Config config) {
ArgumentNullException.ThrowIfNull(config);

var normalizedExternalLocalBotApiBaseUrl = NormalizeBaseUrl(config.ExternalLocalBotApiBaseUrl, string.Empty);
if (config.EnableLocalBotAPI) {
return new BotApiEndpointSettings(
$"http://127.0.0.1:{config.LocalBotApiPort}",
true,
normalizedExternalLocalBotApiBaseUrl);
}

if (!string.IsNullOrWhiteSpace(normalizedExternalLocalBotApiBaseUrl)) {
return new BotApiEndpointSettings(
normalizedExternalLocalBotApiBaseUrl,
true,
normalizedExternalLocalBotApiBaseUrl);
Comment on lines +72 to +76
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate ExternalLocalBotApiBaseUrl before forcing local API mode.

Any non-empty value currently sets IsLocalApi=true, including malformed URLs or non-local hosts. That can leave startup checks skipped later and may make the Telegram client treat a remote endpoint as a trusted local Bot API. Please require an absolute http/https URI and, if this option is intended to be local-only, enforce loopback/localhost here.

🛡️ Proposed validation guard
-            if (!string.IsNullOrWhiteSpace(normalizedExternalLocalBotApiBaseUrl)) {
+            if (!string.IsNullOrWhiteSpace(normalizedExternalLocalBotApiBaseUrl)) {
+                if (!Uri.TryCreate(normalizedExternalLocalBotApiBaseUrl, UriKind.Absolute, out var externalUri) ||
+                    (externalUri.Scheme != Uri.UriSchemeHttp && externalUri.Scheme != Uri.UriSchemeHttps) ||
+                    !(externalUri.IsLoopback || string.Equals(externalUri.Host, "localhost", StringComparison.OrdinalIgnoreCase))) {
+                    throw new InvalidOperationException("ExternalLocalBotApiBaseUrl must be an absolute loopback http/https URL.");
+                }
+
                 return new BotApiEndpointSettings(
                     normalizedExternalLocalBotApiBaseUrl,
                     true,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!string.IsNullOrWhiteSpace(normalizedExternalLocalBotApiBaseUrl)) {
return new BotApiEndpointSettings(
normalizedExternalLocalBotApiBaseUrl,
true,
normalizedExternalLocalBotApiBaseUrl);
if (!string.IsNullOrWhiteSpace(normalizedExternalLocalBotApiBaseUrl)) {
if (!Uri.TryCreate(normalizedExternalLocalBotApiBaseUrl, UriKind.Absolute, out var externalUri) ||
(externalUri.Scheme != Uri.UriSchemeHttp && externalUri.Scheme != Uri.UriSchemeHttps) ||
!(externalUri.IsLoopback || string.Equals(externalUri.Host, "localhost", StringComparison.OrdinalIgnoreCase))) {
throw new InvalidOperationException("ExternalLocalBotApiBaseUrl must be an absolute loopback http/https URL.");
}
return new BotApiEndpointSettings(
normalizedExternalLocalBotApiBaseUrl,
true,
normalizedExternalLocalBotApiBaseUrl);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@TelegramSearchBot.Common/Env.cs` around lines 72 - 76, The current branch
that returns a new BotApiEndpointSettings when
normalizedExternalLocalBotApiBaseUrl is non-empty incorrectly forces
IsLocalApi=true for any string; update the guard in the code that checks
normalizedExternalLocalBotApiBaseUrl so it first parses and validates it as an
absolute URI with scheme http or https and, if this option is intended to be
local-only, also require the host is loopback/localhost (or an IP in
127.0.0.0/8). Only if the URI validation passes should you construct and return
new BotApiEndpointSettings(normalizedExternalLocalBotApiBaseUrl, true,
normalizedExternalLocalBotApiBaseUrl); otherwise treat it as invalid (skip local
mode and/or throw/log an error).

}

return new BotApiEndpointSettings(
NormalizeBaseUrl(config.BaseUrl, "https://api.telegram.org"),
config.IsLocalAPI,
normalizedExternalLocalBotApiBaseUrl);
}

private static string NormalizeBaseUrl(string? baseUrl, string fallback) {
if (string.IsNullOrWhiteSpace(baseUrl)) {
return fallback;
}

return baseUrl.Trim().TrimEnd('/');
}

public static readonly string BaseUrl = null!;
#pragma warning disable CS8604 // 引用类型参数可能为 null。
public static readonly bool IsLocalAPI;
Expand All @@ -69,6 +102,7 @@ static Env() {
public static readonly string TelegramBotApiId = null!;
public static readonly string TelegramBotApiHash = null!;
public static readonly int LocalBotApiPort;
public static readonly string ExternalLocalBotApiBaseUrl = string.Empty;
public static readonly string WorkDir = null!;
public static readonly int TaskDelayTimeout;
public static readonly bool SameServer;
Expand Down Expand Up @@ -107,6 +141,7 @@ public class Config {
//public string WorkDir { get; set; } = "/data/TelegramSearchBot";
public bool IsLocalAPI { get; set; } = false;
public bool EnableLocalBotAPI { get; set; } = false;
public string ExternalLocalBotApiBaseUrl { get; set; } = string.Empty;
public string TelegramBotApiId { get; set; } = null!;
public string TelegramBotApiHash { get; set; } = null!;
public int LocalBotApiPort { get; set; } = 8081;
Expand Down Expand Up @@ -134,4 +169,6 @@ public class Config {
public int AgentQueueBacklogWarningThreshold { get; set; } = 20;
public int AgentProcessMemoryLimitMb { get; set; } = 256;
}

public sealed record BotApiEndpointSettings(string BaseUrl, bool IsLocalApi, string ExternalLocalBotApiBaseUrl);
}
45 changes: 45 additions & 0 deletions TelegramSearchBot.Test/Common/EnvBotApiEndpointTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
using TelegramSearchBot.Common;
using Xunit;

namespace TelegramSearchBot.Test.Common {
public class EnvBotApiEndpointTests {
[Fact]
public void ResolveBotApiEndpoint_UsesEmbeddedLocalApiWhenEnabled() {
var result = Env.ResolveBotApiEndpoint(new Config {
BaseUrl = "https://api.telegram.org",
EnableLocalBotAPI = true,
LocalBotApiPort = 8081,
ExternalLocalBotApiBaseUrl = "http://127.0.0.1:8082/"
});

Assert.Equal("http://127.0.0.1:8081", result.BaseUrl);
Assert.True(result.IsLocalApi);
Assert.Equal("http://127.0.0.1:8082", result.ExternalLocalBotApiBaseUrl);
}

[Fact]
public void ResolveBotApiEndpoint_UsesExternalLocalApiWhenConfigured() {
var result = Env.ResolveBotApiEndpoint(new Config {
BaseUrl = "https://api.telegram.org",
IsLocalAPI = false,
EnableLocalBotAPI = false,
ExternalLocalBotApiBaseUrl = "http://127.0.0.1:8082/"
});

Assert.Equal("http://127.0.0.1:8082", result.BaseUrl);
Assert.True(result.IsLocalApi);
}

[Fact]
public void ResolveBotApiEndpoint_FallsBackToConfiguredBaseUrlWhenNoLocalApiConfigured() {
var result = Env.ResolveBotApiEndpoint(new Config {
BaseUrl = "https://api.telegram.org/",
IsLocalAPI = false,
EnableLocalBotAPI = false
});

Assert.Equal("https://api.telegram.org", result.BaseUrl);
Assert.False(result.IsLocalApi);
}
}
}
Loading
Loading