feat(telegram): support custom Bot API base_url

(cherry picked from commit 3ea7b6a996)
This commit is contained in:
Chummy 2026-02-25 17:16:45 +00:00 committed by Chum Yin
parent ec16c387b5
commit 63fcd7dd54
7 changed files with 50 additions and 19 deletions

View File

@ -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),
});
}

View File

@ -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<Vec<u8>> {
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(

View File

@ -50,6 +50,7 @@ mod tests {
interrupt_on_new_message: false,
mention_only: false,
group_reply: None,
base_url: None,
};
let discord = DiscordConfig {

View File

@ -3487,6 +3487,11 @@ pub struct TelegramConfig {
/// Group-chat trigger controls.
#[serde(default)]
pub group_reply: Option<GroupReplyConfig>,
/// 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<String>,
}
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#"{

View File

@ -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();

View File

@ -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();

View File

@ -3838,6 +3838,7 @@ fn setup_channels() -> Result<ChannelsConfig> {
interrupt_on_new_message: false,
mention_only: false,
group_reply: None,
base_url: None,
});
}
ChannelMenuChoice::Discord => {