From 90ec453b7095b8f3c8c90622ec5edc511ce4d8c7 Mon Sep 17 00:00:00 2001 From: Joey Hines Date: Thu, 5 Jan 2023 22:04:25 -0700 Subject: [PATCH] Message router refactor + Created the WoxlfMessage struct to streamline interface + Message tasks are now joined at once instead of sequentially + Clippy + fmt --- Cargo.lock | 1 + Cargo.toml | 1 + src/discord/commands.rs | 118 +++++++------- src/discord/event_handler.rs | 27 ++-- src/game/message_router.rs | 290 +++++++++++++++++++++++------------ 5 files changed, 258 insertions(+), 179 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 53f2f94..711dd40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2304,6 +2304,7 @@ dependencies = [ name = "woxlf" version = "0.2.0" dependencies = [ + "bitflags", "chrono", "config", "futures", diff --git a/Cargo.toml b/Cargo.toml index f106dc9..0057d95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ regex = "1.5.5" futures = "0.3.21" reqwest = "0.11.10" tera = "1.15.0" +bitflags = "1.3.2" [dependencies.serenity] version = "0.11.5" diff --git a/src/discord/commands.rs b/src/discord/commands.rs index b015090..702a672 100644 --- a/src/discord/commands.rs +++ b/src/discord/commands.rs @@ -10,14 +10,16 @@ use serenity::framework::standard::{ use serenity::framework::StandardFramework; use serenity::model::guild::Member; use serenity::model::id::ChannelId; -use serenity::model::prelude::{GuildId, Message, UserId}; +use serenity::model::prelude::{Message, UserId}; use serenity::prelude::Context; use serenity::utils::MessageBuilder; use crate::discord::helper::{add_user_to_game, parse_duration_arg}; use crate::error::{Result, WoxlfError}; use crate::game::global_data::GlobalData; -use crate::game::message_router::{dispatch_message, MessageDest, MessageSource}; +use crate::game::message_router::{ + dispatch_message, Median, MessageDest, MessageSource, WoxlfMessage, +}; use crate::game::player_data::PlayerData; use crate::game::Phase; use crate::messages::DiscordUser; @@ -161,23 +163,20 @@ async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult { #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] -async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - let guild = msg.guild(&ctx.cache).unwrap(); +async fn say(ctx: &Context, _msg: &Message, args: Args) -> CommandResult { + let data = ctx.data.read().await; + let global_data = data.get::().unwrap(); - let mut global_data = global_data.lock().await; + let global_data = global_data.lock().await; - dispatch_message( - ctx, - &guild, - &mut global_data, - MessageSource::Host, - MessageDest::Broadcast, - args.rest(), - None, - ) - .await?; + let msg = WoxlfMessage::default() + .source(MessageSource::Host) + .dest(MessageDest::Broadcast) + .median(Median::Webhook) + .content(args.rest()) + .clone(); + + dispatch_message(ctx, &global_data, msg).await?; Ok(()) } @@ -185,27 +184,24 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult { #[command] #[only_in(guilds)] #[allowed_roles("wolfx host")] -async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult { - let mut data = ctx.data.write().await; - let global_data = data.get_mut::().unwrap(); - let guild = msg.guild(&ctx.cache).unwrap(); +async fn broadcast(ctx: &Context, _msg: &Message, args: Args) -> CommandResult { + let data = ctx.data.read().await; + let global_data = data.get::().unwrap(); - let mut global_data = global_data.lock().await; + let global_data = global_data.lock().await; - let msg = global_data + let broadcast = global_data .templates()? .build_announcement(&global_data, args.rest())?; - dispatch_message( - ctx, - &guild, - &mut global_data, - MessageSource::Automated, - MessageDest::Broadcast, - &msg, - None, - ) - .await?; + let woxlf_msg = WoxlfMessage::default() + .source(MessageSource::Automated) + .dest(MessageDest::Broadcast) + .content(&broadcast) + .median(Median::Webhook) + .clone(); + + dispatch_message(ctx, &global_data, woxlf_msg).await?; Ok(()) } @@ -234,16 +230,14 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu .templates()? .build_announcement(&global_data, &broadcast)?; - dispatch_message( - ctx, - &guild, - &mut global_data, - MessageSource::Automated, - MessageDest::Broadcast, - &broadcast, - None, - ) - .await?; + let woxlf_msg = WoxlfMessage::default() + .source(MessageSource::Automated) + .dest(MessageDest::Broadcast) + .median(Median::Webhook) + .content(&broadcast) + .clone(); + + dispatch_message(ctx, &global_data, woxlf_msg).await?; if global_data.game_state_mut()?.current_phase == Phase::Day { let vote_channel = guild @@ -311,7 +305,6 @@ async fn kill(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let mut data = ctx.data.write().await; let global_data = data.get_mut::().unwrap(); - let guild = msg.guild(&ctx.cache).unwrap(); let mut global_data = global_data.lock().await; @@ -335,16 +328,14 @@ async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult .templates()? .build_announcement(&global_data, &broadcast)?; - dispatch_message( - ctx, - &guild, - &mut global_data, - MessageSource::Automated, - MessageDest::Broadcast, - &broadcast, - None, - ) - .await?; + let woxlf_msg = WoxlfMessage::default() + .source(MessageSource::Automated) + .dest(MessageDest::Broadcast) + .median(Median::Webhook) + .content(&broadcast) + .clone(); + + dispatch_message(ctx, &global_data, woxlf_msg).await?; msg.reply(&ctx.http, "Phase has been updated") .await @@ -651,13 +642,9 @@ async fn whisper(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult msg.reply(&ctx.http, "Need a recipient and message!") .await?; } else { - let data = ctx.data.write().await; + let data = ctx.data.read().await; let global_data = data.get::().unwrap(); - let mut global_data = global_data.lock().await; - - let guild = GuildId::from(global_data.cfg.discord_config.guild_id) - .to_guild_cached(&ctx.cache) - .unwrap(); + let global_data = global_data.lock().await; let target = args.single::()?; let pm = args.rest(); @@ -680,9 +667,14 @@ async fn whisper(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult return Ok(()); } - let msg_src = MessageSource::Player(Box::new(src_player.clone())); - let msg_dest = MessageDest::PlayerDm(Box::new(target_player.clone())); - dispatch_message(ctx, &guild, &mut global_data, msg_src, msg_dest, pm, None).await?; + let woxlf_msg = WoxlfMessage::default() + .source(MessageSource::Player(Box::new(src_player.clone()))) + .dest(MessageDest::Player(Box::new(target_player.clone()))) + .median(Median::DirectMessage) + .content(pm) + .clone(); + + dispatch_message(ctx, &global_data, woxlf_msg).await?; } else { msg.reply( &ctx.http, diff --git a/src/discord/event_handler.rs b/src/discord/event_handler.rs index e58357c..7f294f3 100644 --- a/src/discord/event_handler.rs +++ b/src/discord/event_handler.rs @@ -6,8 +6,8 @@ use serenity::model::prelude::AttachmentType; use serenity::utils::parse_emoji; use crate::game::global_data::GlobalData; -use crate::game::message_router::MessageSource; use crate::game::message_router::{dispatch_message, MessageDest}; +use crate::game::message_router::{Median, MessageSource, WoxlfMessage}; pub struct Handler {} @@ -26,7 +26,7 @@ impl EventHandler for Handler { let global_data = data.get::().unwrap(); - let mut global_data = global_data.lock().await; + let global_data = global_data.lock().await; if global_data.game_state.is_none() { // no game in progress @@ -43,7 +43,6 @@ impl EventHandler for Handler { return; } - let guild = msg.guild(&ctx.cache).unwrap(); let user_msg = msg.content.clone(); let re = regex::Regex::new(r"").unwrap(); @@ -68,19 +67,17 @@ impl EventHandler for Handler { .map(|a| AttachmentType::Image((a.url).parse().unwrap())) .collect(); - let msg_source = MessageSource::Player(Box::new(player_data.clone())); + let woxlf_msg = WoxlfMessage::default() + .source(MessageSource::Player(Box::new(player_data.clone()))) + .dest(MessageDest::Broadcast) + .median(Median::Webhook) + .content(&user_msg) + .attachments(attachments) + .clone(); - dispatch_message( - &ctx, - &guild, - &mut global_data, - msg_source, - MessageDest::Broadcast, - &user_msg, - Some(attachments), - ) - .await - .expect("Unable to send message to players"); + dispatch_message(&ctx, &global_data, woxlf_msg) + .await + .expect("Unable to send message to players"); } } diff --git a/src/game/message_router.rs b/src/game/message_router.rs index eebc477..0892f7c 100644 --- a/src/game/message_router.rs +++ b/src/game/message_router.rs @@ -1,11 +1,13 @@ use crate::error; +use crate::error::WoxlfError; use crate::game::global_data::GlobalData; use crate::game::player_data::PlayerData; +use bitflags::bitflags; use serenity::client::Context; -use serenity::http::Http; +use serenity::http::{CacheHttp, Http}; use serenity::model::guild::Guild; -use serenity::model::id::UserId; -use serenity::model::prelude::AttachmentType; +use serenity::model::id::{ChannelId, UserId}; +use serenity::model::prelude::{AttachmentType, GuildId, WebhookId}; use serenity::utils::MessageBuilder; #[derive(Debug, Clone)] @@ -15,13 +17,111 @@ pub enum MessageSource { Automated, } +impl Default for MessageSource { + fn default() -> Self { + Self::Automated + } +} + #[derive(Debug, Clone)] +#[allow(dead_code)] pub enum MessageDest { - PlayerChannel(Box), - PlayerDm(Box), + Player(Box), + Host, Broadcast, } +impl Default for MessageDest { + fn default() -> Self { + Self::Broadcast + } +} + +#[derive(Debug, Clone)] +#[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 { @@ -34,13 +134,13 @@ fn filter_source_channel(player_data: &&PlayerData, msg_source: &MessageSource) async fn send_webhook_msg( http: &Http, - webhook_id: u64, + webhook_id: WebhookId, username: &str, profile_pic_url: Option, msg: &str, attachments: &Option>>, ) -> error::Result<()> { - let webhook = http.get_webhook(webhook_id).await?; + let webhook = http.get_webhook(webhook_id.0).await?; webhook .execute(http, false, move |w| { @@ -93,15 +193,10 @@ async fn send_private_message( async fn send_to_host_channel( http: &Http, guild: &Guild, - global_data: &mut GlobalData, - msg_username: &str, - msg_source: MessageSource, - msg_dest: MessageDest, - profile_pic_url: Option, - msg: &str, - attachments: &Option>>, + global_data: &GlobalData, + msg: WoxlfMessage<'_>, ) -> error::Result<()> { - let source = match &msg_source { + let source = match &msg.source { MessageSource::Player(player_data) => { let name = guild .members @@ -115,113 +210,106 @@ async fn send_to_host_channel( MessageSource::Automated => "Automated".to_string(), }; - let dest = match &msg_dest { - MessageDest::PlayerChannel(p) | MessageDest::PlayerDm(p) => { - let name = guild - .members - .get(&UserId::from(p.discord_id)) - .unwrap() - .display_name(); - - format!(" to {} ({})", p.codename, name) + 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() + } } - MessageDest::Broadcast => "".to_string(), + _ => "".to_string(), }; - let host_channel_username = format!("{} ({}){}", msg_username, source, dest); + let host_channel_username = format!( + "{} ({}){}", + msg.get_message_username(global_data)?, + source, + dest + ); send_webhook_msg( http, - global_data.cfg.discord_config.host_webhook_id, + WebhookId::from(global_data.cfg.discord_config.host_webhook_id), &host_channel_username, - profile_pic_url, - msg, - attachments, + 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 => { + send_private_message( + &ctx.http, + &msg.get_message_username(global_data)?, + UserId(dest_player.discord_id), + &msg.content, + &msg.attachments, + ) + .await?; + } + Median::StandardMessage => { + let channel = ChannelId::from(dest_player.channel); + channel.say(&ctx.http(), &msg.content).await?; + } + }; + + Ok(()) +} + pub async fn dispatch_message( ctx: &Context, - guild: &Guild, - global_data: &mut GlobalData, - msg_source: MessageSource, - msg_dest: MessageDest, - msg: &str, - attachments: Option>>, + global_data: &GlobalData, + msg: WoxlfMessage<'_>, ) -> error::Result<()> { - let msg_username = match &msg_source { - MessageSource::Player(p) => p.codename.clone(), - MessageSource::Host => "Woxlf".to_string(), - MessageSource::Automated => "Woxlf System Message".to_string(), - }; + let guild_id = global_data.cfg.discord_config.guild_id; + let guild = GuildId::from(guild_id).to_guild_cached(&ctx.cache).unwrap(); - let profile_pic = match &msg_source { - MessageSource::Player(p) => Some(p.profile_pic_url.clone()), - MessageSource::Host | MessageSource::Automated => { - Some(global_data.game_cfg()?.bot_profile_pic.clone()) - } - }; - - let msg_tasks: Vec<&PlayerData> = global_data - .game_state_mut()? + let msg_tasks = global_data + .game_state()? .player_data .iter() - .filter(|player| filter_source_channel(player, &msg_source)) + .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(); - for player_data in msg_tasks { - match &msg_dest { - MessageDest::PlayerChannel(dest_player) => { - if dest_player.discord_id == player_data.discord_id { - send_webhook_msg( - &ctx.http, - player_data.channel_webhook_id, - &msg_username, - profile_pic.clone(), - msg, - &attachments, - ) - .await?; - } - } - MessageDest::PlayerDm(dest_player) => { - send_private_message( - &ctx.http, - &msg_username, - UserId(dest_player.discord_id), - msg, - &attachments, - ) - .await? - } - MessageDest::Broadcast => { - send_webhook_msg( - &ctx.http, - player_data.channel_webhook_id, - &msg_username, - profile_pic.clone(), - msg, - &attachments, - ) - .await? - } - } - } + results?; - send_to_host_channel( - &ctx.http, - guild, - global_data, - &msg_username, - msg_source, - msg_dest, - profile_pic, - msg, - &attachments, - ) - .await?; + send_to_host_channel(&ctx.http, &guild, global_data, msg).await?; Ok(()) }