use crate::error; use crate::error::WoxlfError; use crate::game::global_data::GlobalData; use crate::game::listener::{EventStatus, Listeners, WoxlfEvent}; use crate::game::player_data::PlayerData; use bitflags::bitflags; use serenity::client::Context; use serenity::http::{CacheHttp, Http}; use serenity::model::guild::Guild; use serenity::model::id::{ChannelId, UserId}; use serenity::model::prelude::{AttachmentType, WebhookId}; use serenity::utils::MessageBuilder; #[derive(Debug, Clone)] pub enum MessageSource { Player(Box), Host, Automated, } impl Default for MessageSource { fn default() -> Self { Self::Automated } } #[derive(Debug, Clone)] #[allow(dead_code)] pub enum MessageDest { Player(Box), Host, Broadcast, } impl Default for MessageDest { fn default() -> Self { Self::Broadcast } } #[derive(Debug, Clone, Eq, PartialEq)] #[allow(dead_code)] pub enum Median { DirectMessage, Webhook, StandardMessage, } impl Default for Median { fn default() -> Self { Self::Webhook } } bitflags! { #[derive(Default)] pub struct MsgFlags: u32 { const PIN_MSG = 0b00000001; } } #[derive(Debug, Clone, Default)] pub struct WoxlfMessage<'a> { pub source: MessageSource, pub dest: MessageDest, pub median: Median, pub content: String, pub attachments: Option>>, pub flags: MsgFlags, } #[allow(dead_code)] impl<'a> WoxlfMessage<'a> { pub fn get_profile_pic(&self, global_data: &GlobalData) -> Result { Ok(match &self.source { MessageSource::Player(p) => p.profile_pic_url.clone(), MessageSource::Host | MessageSource::Automated => { global_data.game_cfg()?.bot_profile_pic.clone() } }) } pub fn get_message_username(&self, global_data: &GlobalData) -> Result { Ok(match &self.source { MessageSource::Player(p) => p.codename.clone(), MessageSource::Host => global_data.game_cfg()?.bot_name.clone(), MessageSource::Automated => "Woxlf System Message".to_string(), }) } pub fn source(mut self, source: MessageSource) -> Self { self.source = source; self } pub fn dest(mut self, dest: MessageDest) -> Self { self.dest = dest; self } pub fn median(mut self, median: Median) -> Self { self.median = median; self } pub fn content(mut self, content: &str) -> Self { self.content = content.to_string(); self } pub fn attachments(mut self, attachments: Vec>) -> Self { self.attachments = Some(attachments); self } pub fn flags(mut self, flags: MsgFlags) -> Self { self.flags = flags; self } pub fn pin(mut self) -> Self { self.flags |= MsgFlags::PIN_MSG; self } } fn filter_source_channel(player_data: &&PlayerData, msg_source: &MessageSource) -> bool { if let MessageSource::Player(source_player) = &msg_source { if source_player.channel == player_data.channel { return false; } } true } pub async fn send_webhook_msg( http: &Http, webhook_id: WebhookId, username: &str, profile_pic_url: Option, msg: &str, attachments: &Option>>, ) -> error::Result<()> { let webhook = http.get_webhook(webhook_id.0).await?; webhook .execute(http, false, move |w| { w.content(&msg).username(username); if let Some(profile_pic_url) = profile_pic_url { w.avatar_url(profile_pic_url); } if let Some(attachments) = attachments { w.add_files(attachments.clone()); } w }) .await?; Ok(()) } pub async fn send_private_message( http: &Http, dest: UserId, msg_content: &str, attachments: &Option>>, ) -> error::Result<()> { let dest_user = dest.to_user(http).await?; dest_user .dm(http, |msg| { msg.content(msg_content); if let Some(attachments) = attachments { msg.add_files(attachments.clone()); } msg }) .await?; Ok(()) } pub async fn send_to_host_channel( http: &Http, guild: &Guild, global_data: &GlobalData, msg: WoxlfMessage<'_>, ) -> error::Result<()> { let source = match &msg.source { MessageSource::Player(player_data) => { let name = guild .members .get(&UserId::from(player_data.discord_id)) .unwrap() .display_name(); name.to_string() } MessageSource::Host => "Host".to_string(), MessageSource::Automated => "Automated".to_string(), }; let dest = match &msg.median { Median::DirectMessage => { if let MessageDest::Player(dest_player) = &msg.dest { let name = guild .members .get(&UserId::from(dest_player.discord_id)) .unwrap() .display_name(); format!(" to {} ({})", dest_player.codename, name) } else { "".to_string() } } _ => "".to_string(), }; let host_channel_username = format!( "{} ({}){}", msg.get_message_username(global_data)?, source, dest ); send_webhook_msg( http, WebhookId::from(global_data.cfg.discord_config.host_webhook_id), &host_channel_username, Some(msg.get_profile_pic(global_data)?), &msg.content, &msg.attachments, ) .await?; Ok(()) } pub async fn send_message( ctx: &Context, global_data: &GlobalData, msg: &WoxlfMessage<'_>, dest_player: &PlayerData, ) -> Result<(), WoxlfError> { match &msg.median { Median::Webhook => { send_webhook_msg( &ctx.http, WebhookId::from(dest_player.channel_webhook_id), &msg.get_message_username(global_data)?, Some(msg.get_profile_pic(global_data)?), &msg.content, &msg.attachments, ) .await?; } Median::DirectMessage => { let dm_msg = MessageBuilder::new() .push_bold_line_safe(format!( "{} has sent you a private message:", &msg.get_message_username(global_data)? )) .push(&msg.content) .build(); send_private_message( &ctx.http, UserId(dest_player.discord_id), &dm_msg, &msg.attachments, ) .await?; } Median::StandardMessage => { let channel = ChannelId::from(dest_player.channel); channel.say(&ctx.http(), &msg.content).await?; } }; Ok(()) } /// Send a message to the proper channels /// Note safe to use in an event handler pub async fn dispatch_message( ctx: &Context, global_data: &mut GlobalData, msg: WoxlfMessage<'_>, ) -> error::Result<()> { let data = ctx.data.read().await; let listeners = data.get::().unwrap(); let mut listeners = listeners.lock().await; if listeners .process_event(ctx, global_data, WoxlfEvent::Chat(msg.clone())) .await? == EventStatus::Canceled { return Ok(()); }; let msg_tasks = global_data .game_state()? .player_data .iter() .filter(|player| filter_source_channel(player, &msg.source)) .filter(|player| match &msg.dest { MessageDest::Player(dest_user) => dest_user.discord_id == player.discord_id, MessageDest::Host => false, MessageDest::Broadcast => true, }) .map(|p| send_message(ctx, global_data, &msg, p)); let results: Result<(), WoxlfError> = futures::future::join_all(msg_tasks) .await .into_iter() .collect(); results?; Ok(()) }