1use axum::{
21 extract::State,
22 http::StatusCode,
23 response::{IntoResponse, Json},
24};
25use serde::Deserialize;
26use serde_json::json;
27
28use super::AppState;
29use crate::messaging::{EmbedField, MsgEmbed, MessagingBridge, OutboundMsg};
30use crate::messaging::discord::DiscordBridge;
31use crate::messaging::slack::SlackBridge;
32use crate::messaging::telegram::TelegramBridge;
33use crate::messaging::whatsapp::WhatsAppBridge;
34
35pub(super) async fn handle_status(State(_state): State<AppState>) -> impl IntoResponse {
37 let mut bridges = vec![];
38 if let Some(b) = DiscordBridge::get() {
39 bridges.push((b as &dyn MessagingBridge).health());
40 }
41 if let Some(b) = TelegramBridge::get() {
42 bridges.push((b as &dyn MessagingBridge).health());
43 }
44 if let Some(b) = SlackBridge::get() {
45 bridges.push((b as &dyn MessagingBridge).health());
46 }
47 if let Some(b) = WhatsAppBridge::get() {
48 bridges.push((b as &dyn MessagingBridge).health());
49 }
50 Json(json!({ "bridges": bridges }))
51}
52
53#[derive(Deserialize)]
55pub(super) struct DiscordSendRequest {
56 #[serde(default)]
58 pub text: String,
59 #[serde(default)]
61 pub channel_id: String,
62 pub title: Option<String>,
64 pub embed_description: Option<String>,
66 pub footer: Option<String>,
68 #[serde(default)]
70 pub fields: Vec<EmbedFieldRequest>,
71}
72
73#[derive(Deserialize)]
74pub(super) struct EmbedFieldRequest {
75 pub name: String,
76 pub value: String,
77 #[serde(default)]
78 pub inline: bool,
79}
80
81pub(super) async fn handle_discord_send(
82 State(_state): State<AppState>,
83 Json(req): Json<DiscordSendRequest>,
84) -> impl IntoResponse {
85 let bridge = match DiscordBridge::get() {
86 Some(b) => b,
87 None => {
88 return (
89 StatusCode::SERVICE_UNAVAILABLE,
90 Json(json!({"error": "discord bridge not initialized — set messaging:discord:enabled in settings"})),
91 )
92 .into_response();
93 }
94 };
95
96 let embed = req.embed_description.map(|desc| MsgEmbed {
97 title: req.title,
98 description: desc,
99 color: Some(5_763_719), fields: req
101 .fields
102 .into_iter()
103 .map(|f| EmbedField {
104 name: f.name,
105 value: f.value,
106 inline: f.inline,
107 })
108 .collect(),
109 footer: Some(req.footer.unwrap_or_else(|| "via AgentMux".to_string())),
110 });
111
112 let msg = OutboundMsg {
113 text: req.text,
114 channel_id: req.channel_id,
115 reply_to: None,
116 embed,
117 edit_message_id: None,
118 blocks: None,
119 };
120
121 match bridge.send(msg) {
122 Ok(()) => Json(json!({ "ok": true })).into_response(),
123 Err(e) => (
124 StatusCode::INTERNAL_SERVER_ERROR,
125 Json(json!({ "error": e })),
126 )
127 .into_response(),
128 }
129}
130
131#[derive(Deserialize)]
138pub(super) struct WhatsAppSendRequest {
139 #[serde(default)]
141 pub to: String,
142 #[serde(default)]
143 pub text: String,
144}
145
146pub(super) async fn handle_whatsapp_send(
147 State(_state): State<AppState>,
148 Json(req): Json<WhatsAppSendRequest>,
149) -> impl IntoResponse {
150 let bridge = match WhatsAppBridge::get() {
151 Some(b) => b,
152 None => {
153 return (
154 StatusCode::SERVICE_UNAVAILABLE,
155 Json(json!({"error": "whatsapp bridge not initialized — set messaging:whatsapp:enabled in settings"})),
156 )
157 .into_response();
158 }
159 };
160
161 if req.to.trim().is_empty() {
162 return (
163 StatusCode::BAD_REQUEST,
164 Json(json!({"error": "whatsapp send: \"to\" (recipient phone number) is required"})),
165 )
166 .into_response();
167 }
168
169 let msg = OutboundMsg {
170 text: req.text,
171 channel_id: req.to,
172 reply_to: None,
173 embed: None,
174 edit_message_id: None,
175 blocks: None,
176 };
177
178 match bridge.send(msg) {
179 Ok(()) => Json(json!({ "ok": true })).into_response(),
180 Err(e) => (
181 StatusCode::INTERNAL_SERVER_ERROR,
182 Json(json!({ "error": e })),
183 )
184 .into_response(),
185 }
186}
187
188#[derive(Deserialize)]
190pub(super) struct TelegramSendRequest {
191 #[serde(default)]
192 pub text: String,
193 #[serde(default)]
196 pub chat_id: String,
197 pub edit_message_id: Option<i64>,
200}
201
202pub(super) async fn handle_telegram_send(
203 State(_state): State<AppState>,
204 Json(req): Json<TelegramSendRequest>,
205) -> impl IntoResponse {
206 let bridge = match TelegramBridge::get() {
207 Some(b) => b,
208 None => {
209 return (
210 StatusCode::SERVICE_UNAVAILABLE,
211 Json(json!({"error": "telegram bridge not initialized — set messaging:telegram:enabled in settings"})),
212 )
213 .into_response();
214 }
215 };
216
217 let msg = OutboundMsg {
218 text: req.text,
219 channel_id: req.chat_id,
220 reply_to: None,
221 embed: None,
222 edit_message_id: req.edit_message_id,
223 blocks: None,
224 };
225
226 match bridge.send(msg) {
227 Ok(()) => Json(json!({ "ok": true })).into_response(),
228 Err(e) => (
229 StatusCode::INTERNAL_SERVER_ERROR,
230 Json(json!({ "error": e })),
231 )
232 .into_response(),
233 }
234}
235
236#[derive(Deserialize)]
245pub(super) struct SlackSendRequest {
246 #[serde(default)]
250 pub text: String,
251 #[serde(default)]
253 pub channel_id: String,
254 pub title: Option<String>,
257 pub body: Option<String>,
258 pub blocks: Option<Vec<serde_json::Value>>,
261}
262
263pub(super) async fn handle_slack_send(
264 State(_state): State<AppState>,
265 Json(req): Json<SlackSendRequest>,
266) -> impl IntoResponse {
267 let bridge = match SlackBridge::get() {
268 Some(b) => b,
269 None => {
270 return (
271 StatusCode::SERVICE_UNAVAILABLE,
272 Json(json!({"error": "slack bridge not initialized — set messaging:slack:enabled in settings"})),
273 )
274 .into_response();
275 }
276 };
277
278 let blocks = if let Some(raw_blocks) = req.blocks {
279 Some(raw_blocks)
280 } else if req.title.is_some() || req.body.is_some() {
281 let mut blocks = vec![];
282 if let Some(title) = &req.title {
283 blocks.push(json!({
284 "type": "header",
285 "text": { "type": "plain_text", "text": title }
286 }));
287 }
288 if let Some(body) = &req.body {
289 blocks.push(json!({
290 "type": "section",
291 "text": { "type": "mrkdwn", "text": body }
292 }));
293 }
294 Some(blocks)
295 } else {
296 None
297 };
298
299 let text = if req.text.is_empty() {
300 match (&req.title, &req.body) {
304 (Some(t), Some(b)) => format!("{t}: {b}"),
305 (Some(t), None) => t.clone(),
306 (None, Some(b)) => b.clone(),
307 (None, None) => String::new(),
308 }
309 } else {
310 req.text
311 };
312
313 let msg = OutboundMsg {
314 text,
315 channel_id: req.channel_id,
316 reply_to: None,
317 embed: None,
318 edit_message_id: None,
319 blocks,
320 };
321
322 match bridge.send(msg) {
323 Ok(()) => Json(json!({ "ok": true })).into_response(),
324 Err(e) => (
325 StatusCode::INTERNAL_SERVER_ERROR,
326 Json(json!({ "error": e })),
327 )
328 .into_response(),
329 }
330}