Skip to main content

rustyclaw_core/messengers/
telegram.rs

1//! Telegram messenger using Bot API.
2
3use super::{Message, Messenger, SendOptions};
4use anyhow::Result;
5use async_trait::async_trait;
6
7/// Telegram messenger using bot API
8pub struct TelegramMessenger {
9    name: String,
10    bot_token: String,
11    connected: bool,
12    http: reqwest::Client,
13    last_update_id: i64,
14}
15
16impl TelegramMessenger {
17    pub fn new(name: String, bot_token: String) -> Self {
18        Self {
19            name,
20            bot_token,
21            connected: false,
22            http: reqwest::Client::new(),
23            last_update_id: 0,
24        }
25    }
26
27    fn api_url(&self, method: &str) -> String {
28        format!("https://api.telegram.org/bot{}/{}", self.bot_token, method)
29    }
30}
31
32#[async_trait]
33impl Messenger for TelegramMessenger {
34    fn name(&self) -> &str {
35        &self.name
36    }
37
38    fn messenger_type(&self) -> &str {
39        "telegram"
40    }
41
42    async fn initialize(&mut self) -> Result<()> {
43        // Verify bot token with getMe
44        let resp = self.http.get(self.api_url("getMe")).send().await?;
45
46        if resp.status().is_success() {
47            let data: serde_json::Value = resp.json().await?;
48            if data["ok"].as_bool() == Some(true) {
49                self.connected = true;
50                return Ok(());
51            }
52        }
53        anyhow::bail!("Telegram auth failed")
54    }
55
56    async fn send_message(&self, chat_id: &str, content: &str) -> Result<String> {
57        let resp = self
58            .http
59            .post(self.api_url("sendMessage"))
60            .json(&serde_json::json!({
61                "chat_id": chat_id,
62                "text": content,
63                "parse_mode": "Markdown"
64            }))
65            .send()
66            .await?;
67
68        if resp.status().is_success() {
69            let data: serde_json::Value = resp.json().await?;
70            if data["ok"].as_bool() == Some(true) {
71                return Ok(data["result"]["message_id"].to_string());
72            }
73        }
74        anyhow::bail!("Telegram send failed")
75    }
76
77    async fn send_message_with_options(&self, opts: SendOptions<'_>) -> Result<String> {
78        let mut payload = serde_json::json!({
79            "chat_id": opts.recipient,
80            "text": opts.content,
81            "parse_mode": "Markdown"
82        });
83
84        if opts.silent {
85            payload["disable_notification"] = serde_json::json!(true);
86        }
87
88        if let Some(reply_to) = opts.reply_to {
89            if let Ok(msg_id) = reply_to.parse::<i64>() {
90                payload["reply_to_message_id"] = serde_json::json!(msg_id);
91            }
92        }
93
94        let resp = self
95            .http
96            .post(self.api_url("sendMessage"))
97            .json(&payload)
98            .send()
99            .await?;
100
101        if resp.status().is_success() {
102            let data: serde_json::Value = resp.json().await?;
103            if data["ok"].as_bool() == Some(true) {
104                return Ok(data["result"]["message_id"].to_string());
105            }
106        }
107        anyhow::bail!("Telegram send failed")
108    }
109
110    async fn receive_messages(&self) -> Result<Vec<Message>> {
111        let resp = self
112            .http
113            .post(self.api_url("getUpdates"))
114            .json(&serde_json::json!({
115                "offset": self.last_update_id + 1,
116                "timeout": 0,
117                "allowed_updates": ["message"]
118            }))
119            .send()
120            .await?;
121
122        if !resp.status().is_success() {
123            return Ok(Vec::new());
124        }
125
126        let data: serde_json::Value = resp.json().await?;
127        if data["ok"].as_bool() != Some(true) {
128            return Ok(Vec::new());
129        }
130
131        let updates = data["result"].as_array();
132        let Some(updates) = updates else {
133            return Ok(Vec::new());
134        };
135
136        let mut messages = Vec::new();
137        for update in updates {
138            let _update_id = update["update_id"].as_i64().unwrap_or(0);
139
140            if let Some(msg) = update.get("message") {
141                let id = msg["message_id"].to_string();
142                let sender = msg["from"]["id"].to_string();
143                let content = msg["text"].as_str().unwrap_or("").to_string();
144                let timestamp = msg["date"].as_i64().unwrap_or(0);
145                let channel = msg["chat"]["id"].to_string();
146
147                messages.push(Message {
148                    id,
149                    sender,
150                    content,
151                    timestamp,
152                    channel: Some(channel),
153                    reply_to: msg["reply_to_message"]["message_id"]
154                        .as_i64()
155                        .map(|id| id.to_string()),
156                    media: None,
157                });
158            }
159        }
160
161        Ok(messages)
162    }
163
164    fn is_connected(&self) -> bool {
165        self.connected
166    }
167
168    async fn disconnect(&mut self) -> Result<()> {
169        self.connected = false;
170        Ok(())
171    }
172}