use std::sync::Arc; use async_trait::async_trait; use log::{info, debug}; use serde::Deserialize; use serenity::{prelude::*, model::prelude::*, http::Http}; use crate::{linkmap::Linkmap, supervisor::{Task, TaskResult, Id, Sender, Receiver}}; use crate::supervisor::Message as BMessage; use super::DiscordConfig; #[derive(Debug, Deserialize)] pub struct DiscordTask { config: DiscordConfig, links: Arc>, } #[async_trait] impl Task for DiscordTask { async fn start(&self, id: Id, tx: Sender, mut rx: Receiver) -> TaskResult { let handler = Handler { links: self.links.clone(), suffix: self.config.suffix.clone(), tx, id, }; let intents = GatewayIntents::GUILD_MESSAGES | GatewayIntents::MESSAGE_CONTENT; debug!("{id}: building Discord client"); let mut client = Client::builder(&self.config.token, intents) .event_handler(handler) .await?; let http = client.cache_and_http.http.clone(); let bridge_handler = async move { loop { let msg = rx.recv().await?; self.handle_bridge_msg(msg, id, http.as_ref()).await?; } }; debug!("{id}: entering event loop"); tokio::select! { result = bridge_handler => result, result = client.start() => result.map_err(|e| e.into()), } } } impl DiscordTask { async fn handle_bridge_msg(&self, msg: BMessage, id: Id, http: &Http) -> TaskResult { let content = format!("<{}> {}", msg.author, msg.content); for channel in self.links.get_channels(&msg.link) { if msg.origin.0 == id && msg.origin.1 == channel.to_string() { continue } debug!("{id}: bridging message from {:?}/{:?} to {channel:?}", msg.origin, msg.link); if let Some(hook) = self.config.webhooks.get(channel) { let hook = http.get_webhook(*hook.as_u64()).await?; hook.execute(http, false, |hook| { hook.username(&msg.author) .content(&msg.content) .avatar_url(msg.avatar.as_deref().unwrap_or("")) }).await?; } else { channel.say(http, &content).await?; } } Ok(()) } } struct Handler { id: Id, suffix: Arc, links: Arc>, tx: Sender, } #[async_trait] impl EventHandler for Handler { async fn message(&self, ctx: Context, msg: Message) { if msg.author.bot { return } let avatar = msg.author.avatar_url(); let Some(link) = self.links.get_link(&msg.channel_id) else { return }; let author = msg.author.nick_in(ctx.http, msg.guild_id.expect("failed to get guild ID")) .await.unwrap_or(msg.author.name); debug!("{}: broadcasting message from {:?} to {:?}", self.id, msg.channel_id, link); self.tx.send(BMessage { origin: (self.id, msg.channel_id.to_string()), link: link.clone(), author, suffix: self.suffix.clone(), content: msg.content, avatar, }).expect("failed to broadcast message"); } async fn ready(&self, _: Context, _: Ready) { info!("{}: ready", self.id); } }