diff --git a/src/channels/mod.rs b/src/channels/mod.rs index 3b5d913d8..aa728fe38 100644 --- a/src/channels/mod.rs +++ b/src/channels/mod.rs @@ -3959,19 +3959,23 @@ fn collect_configured_channels( let mut channels = Vec::new(); if let Some(ref tg) = config.channels_config.telegram { + let mut telegram = TelegramChannel::new( + tg.bot_token.clone(), + tg.allowed_users.clone(), + tg.effective_group_reply_mode().requires_mention(), + ) + .with_group_reply_allowed_senders(tg.group_reply_allowed_sender_ids()) + .with_streaming(tg.stream_mode, tg.draft_update_interval_ms) + .with_transcription(config.transcription.clone()) + .with_workspace_dir(config.workspace_dir.clone()); + + if let Some(ref base_url) = tg.base_url { + telegram = telegram.with_api_base(base_url.clone()); + } + channels.push(ConfiguredChannel { display_name: "Telegram", - channel: Arc::new( - TelegramChannel::new( - tg.bot_token.clone(), - tg.allowed_users.clone(), - tg.effective_group_reply_mode().requires_mention(), - ) - .with_group_reply_allowed_senders(tg.group_reply_allowed_sender_ids()) - .with_streaming(tg.stream_mode, tg.draft_update_interval_ms) - .with_transcription(config.transcription.clone()) - .with_workspace_dir(config.workspace_dir.clone()), - ), + channel: Arc::new(telegram), }); } diff --git a/src/channels/telegram.rs b/src/channels/telegram.rs index 8343464c3..0c3f5262d 100644 --- a/src/channels/telegram.rs +++ b/src/channels/telegram.rs @@ -1014,10 +1014,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", /// Download a file from the Telegram CDN. async fn download_file(&self, file_path: &str) -> anyhow::Result> { - let url = format!( - "https://api.telegram.org/file/bot{}/{file_path}", - self.bot_token - ); + let url = format!("{}/file/bot{}/{file_path}", self.api_base, self.bot_token); let resp = self .http_client() .get(&url) @@ -1687,10 +1684,7 @@ Allowlist Telegram username (without '@') or numeric user ID.", .to_string(); // Step 2: download the actual file - let download_url = format!( - "https://api.telegram.org/file/bot{}/{}", - self.bot_token, file_path - ); + let download_url = format!("{}/file/bot{}/{}", self.api_base, self.bot_token, file_path); let img_resp = self.http_client().get(&download_url).send().await?; let bytes = img_resp.bytes().await?; @@ -3237,6 +3231,17 @@ mod tests { ); } + #[test] + fn telegram_custom_base_url() { + let ch = TelegramChannel::new("123:ABC".into(), vec![], false) + .with_api_base("https://tapi.bale.ai".to_string()); + assert_eq!(ch.api_url("getMe"), "https://tapi.bale.ai/bot123:ABC/getMe"); + assert_eq!( + ch.api_url("sendMessage"), + "https://tapi.bale.ai/bot123:ABC/sendMessage" + ); + } + #[test] fn telegram_markdown_to_html_escapes_quotes_in_link_href() { let rendered = TelegramChannel::markdown_to_telegram_html( diff --git a/src/config/mod.rs b/src/config/mod.rs index 3e4840057..b733ad042 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -50,6 +50,7 @@ mod tests { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }; let discord = DiscordConfig { diff --git a/src/config/schema.rs b/src/config/schema.rs index d43fa00dd..7d8c87975 100644 --- a/src/config/schema.rs +++ b/src/config/schema.rs @@ -3487,6 +3487,11 @@ pub struct TelegramConfig { /// Group-chat trigger controls. #[serde(default)] pub group_reply: Option, + /// Optional custom base URL for Telegram-compatible APIs. + /// Defaults to "https://api.telegram.org" when omitted. + /// Example for Bale messenger: "https://tapi.bale.ai" + #[serde(default)] + pub base_url: Option, } impl ChannelConfig for TelegramConfig { @@ -6498,6 +6503,7 @@ mod tests { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); config.agents.insert( "worker".into(), @@ -6851,6 +6857,7 @@ default_temperature = 0.7 interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }), discord: None, slack: None, @@ -7312,6 +7319,7 @@ tool_dispatcher = "xml" interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); config.agents.insert( @@ -7471,6 +7479,7 @@ tool_dispatcher = "xml" interrupt_on_new_message: true, mention_only: false, group_reply: None, + base_url: None, }; let json = serde_json::to_string(&tc).unwrap(); let parsed: TelegramConfig = serde_json::from_str(&json).unwrap(); @@ -7488,6 +7497,7 @@ tool_dispatcher = "xml" assert_eq!(parsed.stream_mode, StreamMode::Off); assert_eq!(parsed.draft_update_interval_ms, 1000); assert!(!parsed.interrupt_on_new_message); + assert!(parsed.base_url.is_none()); assert_eq!( parsed.effective_group_reply_mode(), GroupReplyMode::AllMessages @@ -7495,6 +7505,13 @@ tool_dispatcher = "xml" assert!(parsed.group_reply_allowed_sender_ids().is_empty()); } + #[test] + async fn telegram_config_custom_base_url() { + let json = r#"{"bot_token":"tok","allowed_users":[],"base_url":"https://tapi.bale.ai"}"#; + let parsed: TelegramConfig = serde_json::from_str(json).unwrap(); + assert_eq!(parsed.base_url, Some("https://tapi.bale.ai".to_string())); + } + #[test] async fn telegram_group_reply_config_overrides_legacy_mention_only() { let json = r#"{ diff --git a/src/daemon/mod.rs b/src/daemon/mod.rs index 4a9f6c9f0..76f02e762 100644 --- a/src/daemon/mod.rs +++ b/src/daemon/mod.rs @@ -405,6 +405,7 @@ mod tests { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); assert!(has_supervised_channels(&config)); } @@ -540,6 +541,7 @@ mod tests { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); let target = heartbeat_delivery_target(&config).unwrap(); diff --git a/src/integrations/registry.rs b/src/integrations/registry.rs index 6b0b2212c..b3df9e531 100644 --- a/src/integrations/registry.rs +++ b/src/integrations/registry.rs @@ -793,6 +793,7 @@ mod tests { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); let entries = all_integrations(); let tg = entries.iter().find(|e| e.name == "Telegram").unwrap(); diff --git a/src/onboard/wizard.rs b/src/onboard/wizard.rs index 2e38dc944..8be38cd09 100644 --- a/src/onboard/wizard.rs +++ b/src/onboard/wizard.rs @@ -3838,6 +3838,7 @@ fn setup_channels() -> Result { interrupt_on_new_message: false, mention_only: false, group_reply: None, + base_url: None, }); } ChannelMenuChoice::Discord => {