agentmux_srv/messaging/discord/
mod.rs1mod gateway;
14pub mod rest;
15pub mod types;
16
17use std::sync::{Arc, Mutex, OnceLock};
18
19use tokio::sync::mpsc;
20
21use crate::messaging::{BridgeHealth, OutboundMsg};
22
23#[derive(Debug, Clone)]
26pub struct DiscordConfig {
27 pub token: String,
29 pub channel_id: String,
32 pub target_agent: Option<String>,
35 #[allow(dead_code)]
37 pub guild_id: Option<String>,
38}
39
40pub struct DiscordBridge {
43 outbound_tx: mpsc::UnboundedSender<OutboundMsg>,
44 health: Arc<Mutex<BridgeHealth>>,
45}
46
47static GLOBAL_BRIDGE: OnceLock<DiscordBridge> = OnceLock::new();
48
49impl DiscordBridge {
50 pub fn init_global(config: DiscordConfig, http: reqwest::Client) {
53 let (outbound_tx, outbound_rx) = mpsc::unbounded_channel::<OutboundMsg>();
54 let health = Arc::new(Mutex::new(BridgeHealth::connecting("discord")));
55
56 let bridge = DiscordBridge {
57 outbound_tx,
58 health: health.clone(),
59 };
60
61 if GLOBAL_BRIDGE.set(bridge).is_err() {
62 return; }
64
65 let token = config.token.clone();
66 let channel_id = config.channel_id.clone();
67 let target_agent = config.target_agent.clone();
68
69 tokio::spawn(async move {
70 gateway::run_gateway_loop(token, channel_id, target_agent, http, outbound_rx, health)
71 .await;
72 });
73
74 tracing::info!(
75 "discord_bridge: initialized (channel={}, target={:?})",
76 config.channel_id,
77 config.target_agent
78 );
79 }
80
81 pub fn get() -> Option<&'static DiscordBridge> {
82 GLOBAL_BRIDGE.get()
83 }
84
85 pub fn send(&self, msg: OutboundMsg) -> Result<(), String> {
88 self.outbound_tx
89 .send(msg)
90 .map_err(|_| "discord_bridge: outbound channel closed".to_string())
91 }
92
93 pub fn health(&self) -> BridgeHealth {
94 self.health.lock().unwrap().clone()
95 }
96}
97
98impl crate::messaging::MessagingBridge for DiscordBridge {
99 fn health(&self) -> BridgeHealth {
100 DiscordBridge::health(self)
101 }
102}