Skip to main content

apollo/tools/
message.rs

1//! Message tool — send messages via Telegram (like OpenClaw's message tool).
2//! Allows the AI to proactively send messages, edit, delete, react.
3
4use async_trait::async_trait;
5use serde::Deserialize;
6use std::sync::Arc;
7
8use super::traits::*;
9use crate::channels::telegram::TelegramChannel;
10
11pub struct MessageTool {
12    tg: Arc<TelegramChannel>,
13}
14
15impl MessageTool {
16    pub fn new(tg: Arc<TelegramChannel>) -> Self {
17        Self { tg }
18    }
19}
20
21#[derive(Deserialize)]
22struct MessageArgs {
23    /// Action: send, edit, delete, react
24    action: String,
25    /// Message text (for send/edit)
26    message: Option<String>,
27    /// Message ID (for edit/delete/react)
28    message_id: Option<i64>,
29    /// Emoji (for react)
30    #[allow(dead_code)]
31    emoji: Option<String>,
32}
33
34#[async_trait]
35impl Tool for MessageTool {
36    fn name(&self) -> &str {
37        "message"
38    }
39
40    fn spec(&self) -> ToolSpec {
41        ToolSpec {
42            name: "message".to_string(),
43            description: "Send, edit, or delete Telegram messages. Actions: send, edit, delete."
44                .to_string(),
45            parameters: serde_json::json!({
46                "type": "object",
47                "properties": {
48                    "action": {
49                        "type": "string",
50                        "enum": ["send", "edit", "delete"],
51                        "description": "Message action"
52                    },
53                    "message": {
54                        "type": "string",
55                        "description": "Message text (for send/edit)"
56                    },
57                    "message_id": {
58                        "type": "integer",
59                        "description": "Message ID (for edit/delete)"
60                    }
61                },
62                "required": ["action"]
63            }),
64        }
65    }
66
67    async fn execute(&self, arguments: &str) -> anyhow::Result<ToolResult> {
68        let args: MessageArgs = serde_json::from_str(arguments)?;
69
70        match args.action.as_str() {
71            "send" => {
72                let text = args.message.unwrap_or_default();
73                if text.is_empty() {
74                    return Ok(ToolResult::error("Message text required for send action"));
75                }
76                let msg_id = self.tg.send_message(&text).await?;
77                Ok(ToolResult::success(format!(
78                    "Sent message (id: {})",
79                    msg_id
80                )))
81            }
82            "edit" => {
83                let msg_id = args.message_id.unwrap_or(0);
84                let text = args.message.unwrap_or_default();
85                if msg_id == 0 || text.is_empty() {
86                    return Ok(ToolResult::error(
87                        "message_id and message required for edit action",
88                    ));
89                }
90                self.tg.edit_message(msg_id, &text).await?;
91                Ok(ToolResult::success(format!("Edited message {}", msg_id)))
92            }
93            "delete" => {
94                let msg_id = args.message_id.unwrap_or(0);
95                if msg_id == 0 {
96                    return Ok(ToolResult::error("message_id required for delete action"));
97                }
98                self.tg.delete_message(msg_id).await?;
99                Ok(ToolResult::success(format!("Deleted message {}", msg_id)))
100            }
101            other => Ok(ToolResult::error(format!("Unknown action: {}", other))),
102        }
103    }
104}