use log::info; use serenity::{async_trait, model::prelude::*, prelude::*}; use tokio::sync::mpsc; use crate::{ channels::ChannelReference, message_ast::{self, format_discord}, messages::{ DeletedMessage, EditedMessage, MessageAuthor, MessageEvent, MessageReference, SentMessage, }, }; pub use serenity::client::Context; pub use serenity::model::id::{ChannelId, MessageId}; impl From<&Message> for MessageReference { fn from(message: &Message) -> Self { Self::Discord(message.channel_id.0, message.id.0) } } struct DiscordHandler { ctx_tx: mpsc::UnboundedSender, event_tx: mpsc::UnboundedSender, } #[async_trait] impl EventHandler for DiscordHandler { async fn ready(&self, ctx: Context, _ready: Ready) { let _ = self.ctx_tx.send(ctx); info!("Discord ready!"); // TODO: Scan for channels to link } async fn message(&self, ctx: Context, message: Message) { if message.author.id == ctx.cache.current_user_id().await { return; } if let Some(target) = message.content.strip_prefix("phoebe!link ") { if message .member(&ctx) .await .unwrap() .roles(&ctx) .await .unwrap() .iter() .any(|r| r.name == "Phoebe") { let _ = self.event_tx.send(MessageEvent::AdminLinkChannels(vec![ ChannelReference::Discord(message.channel_id.0), ChannelReference::Matrix(target.to_string()), ])); message.reply(&ctx, "Linking with matrix.").await.unwrap(); return; } } let message_ref = MessageReference::from(&message); let content = discord_message_format::parse(&message.content); let content = message_ast::convert_discord(&content); let replies_to = message .referenced_message .as_ref() .map(|m| MessageReference::from(m.as_ref())); let _ = self.event_tx.send(MessageEvent::Send(SentMessage { source: message_ref, content, author: MessageAuthor { display_name: message .author_nick(&ctx) .await .unwrap_or(message.author.name), }, replies_to, })); } async fn message_update( &self, ctx: Context, _old_if_available: Option, new: Option, event: MessageUpdateEvent, ) { if let Ok(new_message) = { if let Some(m) = new { Ok(m) } else { event.channel_id.message(&ctx, event.id).await } } { let message_ref = MessageReference::from(&new_message); let content = discord_message_format::parse(&new_message.content); let content = message_ast::convert_discord(&content); let _ = self.event_tx.send(MessageEvent::Edit(EditedMessage { replacing: message_ref, content, author: MessageAuthor { display_name: new_message .author_nick(&ctx) .await .unwrap_or(new_message.author.name), }, })); } } async fn message_delete( &self, _ctx: Context, channel_id: ChannelId, deleted_message_id: MessageId, _guild_id: Option, ) { let message_ref = MessageReference::Discord(channel_id.0, deleted_message_id.0); let _ = self.event_tx.send(MessageEvent::Delete(DeletedMessage { reference: message_ref, })); } } pub async fn forward_to_discord( discord_ctx: &Context, channel: ChannelId, message: &SentMessage, reply: Option<(u64, u64)>, ) -> Option { channel .send_message(&discord_ctx, |m| { let b = m.content(format_discord(&message.content)); if let Some((channel_id, message_id)) = reply { b.reference_message((ChannelId(channel_id), MessageId(message_id))) } else { b } }) .await .as_ref() .ok() .map(MessageReference::from) } pub async fn edit_on_discord( discord_ctx: &Context, channel_id: ChannelId, message_id: MessageId, message: &EditedMessage, ) -> Option { channel_id .edit_message(&discord_ctx, &message_id, |m| { m.content(format_discord(&message.content)) }) .await .as_ref() .ok() .map(MessageReference::from) } pub async fn delete_on_discord( discord_ctx: &Context, channel_id: ChannelId, message_id: MessageId, _message: &DeletedMessage, ) -> bool { channel_id .delete_message(&discord_ctx, &message_id) .await .is_ok() } pub async fn create_discord_client( ctx_tx: mpsc::UnboundedSender, message_tx: mpsc::UnboundedSender, token: &str, ) -> Client { let handler = DiscordHandler { ctx_tx, event_tx: message_tx, }; info!("Discord logging in…"); let client = Client::builder(token) .event_handler(handler) .await .expect("Failed to create discord client"); info!("Discord starting…"); client }