优化消息渠道的实现代码

This commit is contained in:
xintaofei
2026-03-31 15:26:29 +08:00
parent 99c60ce4a5
commit a9f6ce9105
12 changed files with 571 additions and 274 deletions

View File

@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::time::Duration;
use async_trait::async_trait;
use tokio::sync::{mpsc, Mutex};
@@ -21,7 +22,11 @@ impl TelegramBackend {
Self {
bot_token,
chat_id,
client: reqwest::Client::new(),
client: reqwest::Client::builder()
.connect_timeout(Duration::from_secs(10))
.timeout(Duration::from_secs(60))
.build()
.unwrap_or_default(),
status: Arc::new(Mutex::new(ChannelConnectionStatus::Disconnected)),
channel_id,
shutdown_tx: Arc::new(Mutex::new(None)),
@@ -91,7 +96,7 @@ impl ChatChannelBackend for TelegramBackend {
) -> Result<(), ChatChannelError> {
*self.status.lock().await = ChannelConnectionStatus::Connecting;
// Verify bot token by calling getMe
// Verify bot token and extract bot username for group @mention filtering
let resp = self
.client
.get(self.api_url("getMe"))
@@ -99,13 +104,24 @@ impl ChatChannelBackend for TelegramBackend {
.await
.map_err(|e| ChatChannelError::ConnectionFailed(e.to_string()))?;
if !resp.status().is_success() {
let me_body: serde_json::Value = resp
.json()
.await
.map_err(|e| ChatChannelError::ConnectionFailed(e.to_string()))?;
if me_body.get("ok").and_then(|v| v.as_bool()) != Some(true) {
*self.status.lock().await = ChannelConnectionStatus::Error;
return Err(ChatChannelError::AuthenticationFailed(
"Invalid bot token".to_string(),
));
}
let bot_username = me_body
.pointer("/result/username")
.and_then(|v| v.as_str())
.unwrap_or("")
.to_lowercase();
*self.status.lock().await = ChannelConnectionStatus::Connected;
// Start long-polling loop
@@ -136,6 +152,14 @@ impl ChatChannelBackend for TelegramBackend {
match result {
Ok(resp) => {
// Recover from error state after successful poll
{
let mut s = status.lock().await;
if *s == ChannelConnectionStatus::Error {
*s = ChannelConnectionStatus::Connected;
}
}
if let Ok(body) = resp.json::<serde_json::Value>().await {
if let Some(updates) = body.get("result").and_then(|r| r.as_array()) {
for update in updates {
@@ -148,6 +172,25 @@ impl ChatChannelBackend for TelegramBackend {
.pointer("/message/text")
.and_then(|t| t.as_str())
{
// Group chat filtering: only process if @bot is mentioned
let chat_type = update
.pointer("/message/chat/type")
.and_then(|v| v.as_str())
.unwrap_or("private");
if (chat_type == "group" || chat_type == "supergroup")
&& !bot_username.is_empty()
{
let at_bot =
format!("@{}", bot_username);
if !text.to_lowercase().contains(&at_bot) {
continue;
}
}
// Strip @bot_username from command text (case-insensitive)
let clean_text = strip_bot_mention(text, &bot_username);
let sender_id = update
.pointer("/message/from/id")
.and_then(|i| i.as_i64())
@@ -157,7 +200,7 @@ impl ChatChannelBackend for TelegramBackend {
.send(IncomingCommand {
channel_id,
sender_id,
command_text: text.to_string(),
command_text: clean_text,
metadata: update.clone(),
})
.await;
@@ -170,7 +213,6 @@ impl ChatChannelBackend for TelegramBackend {
eprintln!("[Telegram] polling error: {e}");
*status.lock().await = ChannelConnectionStatus::Error;
tokio::time::sleep(tokio::time::Duration::from_secs(5)).await;
*status.lock().await = ChannelConnectionStatus::Connected;
}
}
}
@@ -222,16 +264,42 @@ impl ChatChannelBackend for TelegramBackend {
.await
.map_err(|e| ChatChannelError::ConnectionFailed(e.to_string()))?;
if resp.status().is_success() {
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| ChatChannelError::ConnectionFailed(e.to_string()))?;
if body.get("ok").and_then(|v| v.as_bool()) == Some(true) {
Ok(())
} else {
Err(ChatChannelError::AuthenticationFailed(
"Invalid bot token".to_string(),
))
let desc = body
.get("description")
.and_then(|v| v.as_str())
.unwrap_or("Invalid bot token");
Err(ChatChannelError::AuthenticationFailed(desc.to_string()))
}
}
}
/// Strip `@bot_username` from text (case-insensitive).
/// Handles Telegram convention: `/command@botname args` → `/command args`
fn strip_bot_mention(text: &str, bot_username: &str) -> String {
if bot_username.is_empty() {
return text.to_string();
}
let at_bot = format!("@{}", bot_username);
let text_lower = text.to_lowercase();
let at_bot_lower = at_bot.to_lowercase();
if let Some(pos) = text_lower.find(&at_bot_lower) {
let mut result = String::with_capacity(text.len());
result.push_str(&text[..pos]);
result.push_str(&text[pos + at_bot.len()..]);
result.trim().to_string()
} else {
text.to_string()
}
}
fn format_telegram_markdown(msg: &RichMessage) -> String {
let mut text = String::new();