rustyclaw_core/messengers/
teams.rs1use super::{Message, Messenger, SendOptions};
7use anyhow::{Context, Result};
8use async_trait::async_trait;
9
10pub struct TeamsMessenger {
12 name: String,
13 webhook_url: Option<String>,
15 app_id: Option<String>,
17 app_password: Option<String>,
19 connected: bool,
20 http: reqwest::Client,
21}
22
23impl TeamsMessenger {
24 pub fn new(name: String) -> Self {
25 Self {
26 name,
27 webhook_url: None,
28 app_id: None,
29 app_password: None,
30 connected: false,
31 http: reqwest::Client::new(),
32 }
33 }
34
35 pub fn with_webhook_url(mut self, url: String) -> Self {
37 self.webhook_url = Some(url);
38 self
39 }
40
41 pub fn with_bot_framework(mut self, app_id: String, app_password: String) -> Self {
43 self.app_id = Some(app_id);
44 self.app_password = Some(app_password);
45 self
46 }
47}
48
49#[async_trait]
50impl Messenger for TeamsMessenger {
51 fn name(&self) -> &str {
52 &self.name
53 }
54
55 fn messenger_type(&self) -> &str {
56 "teams"
57 }
58
59 async fn initialize(&mut self) -> Result<()> {
60 if self.webhook_url.is_none() && self.app_id.is_none() {
61 anyhow::bail!(
62 "Teams requires either 'webhook_url' (incoming webhook) or \
63 'app_id'+'app_password' (Bot Framework)"
64 );
65 }
66 self.connected = true;
67 tracing::info!(
68 has_webhook = self.webhook_url.is_some(),
69 has_bot_framework = self.app_id.is_some(),
70 "Teams initialized"
71 );
72 Ok(())
73 }
74
75 async fn send_message(&self, _channel: &str, content: &str) -> Result<String> {
76 if let Some(ref webhook_url) = self.webhook_url {
77 let payload = serde_json::json!({
79 "@type": "MessageCard",
80 "@context": "http://schema.org/extensions",
81 "text": content
82 });
83
84 let resp = self
85 .http
86 .post(webhook_url)
87 .json(&payload)
88 .send()
89 .await
90 .context("Teams webhook POST failed")?;
91
92 if resp.status().is_success() {
93 return Ok(format!("teams-{}", chrono::Utc::now().timestamp_millis()));
94 }
95 anyhow::bail!("Teams webhook returned {}", resp.status());
96 }
97
98 anyhow::bail!(
100 "Teams Bot Framework send not yet implemented. Use webhook_url or the claw-me-maybe skill."
101 )
102 }
103
104 async fn send_message_with_options(&self, opts: SendOptions<'_>) -> Result<String> {
105 self.send_message(opts.recipient, opts.content).await
106 }
107
108 async fn receive_messages(&self) -> Result<Vec<Message>> {
109 Ok(Vec::new())
112 }
113
114 fn is_connected(&self) -> bool {
115 self.connected
116 }
117
118 async fn disconnect(&mut self) -> Result<()> {
119 self.connected = false;
120 Ok(())
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn test_teams_creation() {
130 let m = TeamsMessenger::new("test".to_string())
131 .with_webhook_url("https://outlook.office.com/webhook/xxx".to_string());
132 assert_eq!(m.name(), "test");
133 assert_eq!(m.messenger_type(), "teams");
134 assert!(!m.is_connected());
135 }
136
137 #[test]
138 fn test_with_bot_framework() {
139 let m = TeamsMessenger::new("test".to_string())
140 .with_bot_framework("app-id".to_string(), "app-pass".to_string());
141 assert_eq!(m.app_id, Some("app-id".to_string()));
142 assert_eq!(m.app_password, Some("app-pass".to_string()));
143 }
144}