Message router refactor

+ Created the WoxlfMessage struct to streamline interface
+ Message tasks are now joined at once instead of sequentially
+ Clippy + fmt
main
Joey Hines 2023-01-05 22:04:25 -07:00 committed by Gitea
parent 3c219f5bff
commit 9a91a16e0d
6 changed files with 259 additions and 182 deletions

1
Cargo.lock generated
View File

@ -2304,6 +2304,7 @@ dependencies = [
name = "woxlf" name = "woxlf"
version = "0.2.0" version = "0.2.0"
dependencies = [ dependencies = [
"bitflags",
"chrono", "chrono",
"config", "config",
"futures", "futures",

View File

@ -16,6 +16,7 @@ regex = "1.5.5"
futures = "0.3.21" futures = "0.3.21"
reqwest = "0.11.10" reqwest = "0.11.10"
tera = "1.15.0" tera = "1.15.0"
bitflags = "1.3.2"
[dependencies.serenity] [dependencies.serenity]
version = "0.11.5" version = "0.11.5"

View File

@ -10,14 +10,16 @@ use serenity::framework::standard::{
use serenity::framework::StandardFramework; use serenity::framework::StandardFramework;
use serenity::model::guild::Member; use serenity::model::guild::Member;
use serenity::model::id::ChannelId; use serenity::model::id::ChannelId;
use serenity::model::prelude::{GuildId, Message, UserId}; use serenity::model::prelude::{Message, UserId};
use serenity::prelude::Context; use serenity::prelude::Context;
use serenity::utils::MessageBuilder; use serenity::utils::MessageBuilder;
use crate::discord::helper::{add_user_to_game, parse_duration_arg}; use crate::discord::helper::{add_user_to_game, parse_duration_arg};
use crate::error::{Result, WoxlfError}; use crate::error::{Result, WoxlfError};
use crate::game::global_data::GlobalData; 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::player_data::PlayerData;
use crate::game::Phase; use crate::game::Phase;
use crate::messages::DiscordUser; use crate::messages::DiscordUser;
@ -161,23 +163,20 @@ async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult {
#[command] #[command]
#[only_in(guilds)] #[only_in(guilds)]
#[allowed_roles("wolfx host")] #[allowed_roles("wolfx host")]
async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn say(ctx: &Context, _msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await; let data = ctx.data.read().await;
let global_data = data.get_mut::<GlobalData>().unwrap(); let global_data = data.get::<GlobalData>().unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let global_data = global_data.lock().await;
dispatch_message( let msg = WoxlfMessage::default()
ctx, .source(MessageSource::Host)
&guild, .dest(MessageDest::Broadcast)
&mut global_data, .median(Median::Webhook)
MessageSource::Host, .content(args.rest())
MessageDest::Broadcast, .clone();
args.rest(),
None, dispatch_message(ctx, &global_data, msg).await?;
)
.await?;
Ok(()) Ok(())
} }
@ -185,27 +184,24 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
#[command] #[command]
#[only_in(guilds)] #[only_in(guilds)]
#[allowed_roles("wolfx host")] #[allowed_roles("wolfx host")]
async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn broadcast(ctx: &Context, _msg: &Message, args: Args) -> CommandResult {
let mut data = ctx.data.write().await; let data = ctx.data.read().await;
let global_data = data.get_mut::<GlobalData>().unwrap(); let global_data = data.get::<GlobalData>().unwrap();
let guild = msg.guild(&ctx.cache).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()? .templates()?
.build_announcement(&global_data, args.rest())?; .build_announcement(&global_data, args.rest())?;
dispatch_message( let woxlf_msg = WoxlfMessage::default()
ctx, .source(MessageSource::Automated)
&guild, .dest(MessageDest::Broadcast)
&mut global_data, .content(&broadcast)
MessageSource::Automated, .median(Median::Webhook)
MessageDest::Broadcast, .clone();
&msg,
None, dispatch_message(ctx, &global_data, woxlf_msg).await?;
)
.await?;
Ok(()) Ok(())
} }
@ -234,16 +230,14 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
.templates()? .templates()?
.build_announcement(&global_data, &broadcast)?; .build_announcement(&global_data, &broadcast)?;
dispatch_message( let woxlf_msg = WoxlfMessage::default()
ctx, .source(MessageSource::Automated)
&guild, .dest(MessageDest::Broadcast)
&mut global_data, .median(Median::Webhook)
MessageSource::Automated, .content(&broadcast)
MessageDest::Broadcast, .clone();
&broadcast,
None, dispatch_message(ctx, &global_data, woxlf_msg).await?;
)
.await?;
if global_data.game_state_mut()?.current_phase == Phase::Day { if global_data.game_state_mut()?.current_phase == Phase::Day {
let vote_channel = guild 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 { async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let mut data = ctx.data.write().await; let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap(); let global_data = data.get_mut::<GlobalData>().unwrap();
let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; 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()? .templates()?
.build_announcement(&global_data, &broadcast)?; .build_announcement(&global_data, &broadcast)?;
dispatch_message( let woxlf_msg = WoxlfMessage::default()
ctx, .source(MessageSource::Automated)
&guild, .dest(MessageDest::Broadcast)
&mut global_data, .median(Median::Webhook)
MessageSource::Automated, .content(&broadcast)
MessageDest::Broadcast, .clone();
&broadcast,
None, dispatch_message(ctx, &global_data, woxlf_msg).await?;
)
.await?;
msg.reply(&ctx.http, "Phase has been updated") msg.reply(&ctx.http, "Phase has been updated")
.await .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!") msg.reply(&ctx.http, "Need a recipient and message!")
.await?; .await?;
} else { } else {
let data = ctx.data.write().await; let data = ctx.data.read().await;
let global_data = data.get::<GlobalData>().unwrap(); let global_data = data.get::<GlobalData>().unwrap();
let mut global_data = global_data.lock().await; let global_data = global_data.lock().await;
let guild = GuildId::from(global_data.cfg.discord_config.guild_id)
.to_guild_cached(&ctx.cache)
.unwrap();
let target = args.single::<String>()?; let target = args.single::<String>()?;
let pm = args.rest(); let pm = args.rest();
@ -680,9 +667,14 @@ async fn whisper(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
return Ok(()); return Ok(());
} }
let msg_src = MessageSource::Player(Box::new(src_player.clone())); let woxlf_msg = WoxlfMessage::default()
let msg_dest = MessageDest::PlayerDm(Box::new(target_player.clone())); .source(MessageSource::Player(Box::new(src_player.clone())))
dispatch_message(ctx, &guild, &mut global_data, msg_src, msg_dest, pm, None).await?; .dest(MessageDest::Player(Box::new(target_player.clone())))
.median(Median::DirectMessage)
.content(pm)
.clone();
dispatch_message(ctx, &global_data, woxlf_msg).await?;
} else { } else {
msg.reply( msg.reply(
&ctx.http, &ctx.http,

View File

@ -6,8 +6,8 @@ use serenity::model::prelude::AttachmentType;
use serenity::utils::parse_emoji; use serenity::utils::parse_emoji;
use crate::game::global_data::GlobalData; 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::{dispatch_message, MessageDest};
use crate::game::message_router::{Median, MessageSource, WoxlfMessage};
pub struct Handler {} pub struct Handler {}
@ -26,7 +26,7 @@ impl EventHandler for Handler {
let global_data = data.get::<GlobalData>().unwrap(); let global_data = data.get::<GlobalData>().unwrap();
let mut global_data = global_data.lock().await; let global_data = global_data.lock().await;
if global_data.game_state.is_none() { if global_data.game_state.is_none() {
// no game in progress // no game in progress
@ -43,7 +43,6 @@ impl EventHandler for Handler {
return; return;
} }
let guild = msg.guild(&ctx.cache).unwrap();
let user_msg = msg.content.clone(); let user_msg = msg.content.clone();
let re = regex::Regex::new(r"<a?:.+:\d+>").unwrap(); let re = regex::Regex::new(r"<a?:.+:\d+>").unwrap();
@ -68,19 +67,17 @@ impl EventHandler for Handler {
.map(|a| AttachmentType::Image((a.url).parse().unwrap())) .map(|a| AttachmentType::Image((a.url).parse().unwrap()))
.collect(); .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( dispatch_message(&ctx, &global_data, woxlf_msg)
&ctx, .await
&guild, .expect("Unable to send message to players");
&mut global_data,
msg_source,
MessageDest::Broadcast,
&user_msg,
Some(attachments),
)
.await
.expect("Unable to send message to players");
} }
} }

View File

@ -1,11 +1,13 @@
use crate::error; use crate::error;
use crate::error::WoxlfError;
use crate::game::global_data::GlobalData; use crate::game::global_data::GlobalData;
use crate::game::player_data::PlayerData; use crate::game::player_data::PlayerData;
use bitflags::bitflags;
use serenity::client::Context; use serenity::client::Context;
use serenity::http::Http; use serenity::http::{CacheHttp, Http};
use serenity::model::guild::Guild; use serenity::model::guild::Guild;
use serenity::model::id::UserId; use serenity::model::id::{ChannelId, UserId};
use serenity::model::prelude::AttachmentType; use serenity::model::prelude::{AttachmentType, GuildId, WebhookId};
use serenity::utils::MessageBuilder; use serenity::utils::MessageBuilder;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -15,13 +17,111 @@ pub enum MessageSource {
Automated, Automated,
} }
impl Default for MessageSource {
fn default() -> Self {
Self::Automated
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum MessageDest { pub enum MessageDest {
PlayerChannel(Box<PlayerData>), Player(Box<PlayerData>),
PlayerDm(Box<PlayerData>), Host,
Broadcast, 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<Vec<AttachmentType<'a>>>,
pub flags: MsgFlags,
}
#[allow(dead_code)]
impl<'a> WoxlfMessage<'a> {
pub fn get_profile_pic(&self, global_data: &GlobalData) -> Result<String, WoxlfError> {
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<String, WoxlfError> {
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<AttachmentType<'a>>) -> 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 { fn filter_source_channel(player_data: &&PlayerData, msg_source: &MessageSource) -> bool {
if let MessageSource::Player(source_player) = &msg_source { 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( async fn send_webhook_msg(
http: &Http, http: &Http,
webhook_id: u64, webhook_id: WebhookId,
username: &str, username: &str,
profile_pic_url: Option<String>, profile_pic_url: Option<String>,
msg: &str, msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>, attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> { ) -> error::Result<()> {
let webhook = http.get_webhook(webhook_id).await?; let webhook = http.get_webhook(webhook_id.0).await?;
webhook webhook
.execute(http, false, move |w| { .execute(http, false, move |w| {
@ -93,15 +193,10 @@ async fn send_private_message(
async fn send_to_host_channel( async fn send_to_host_channel(
http: &Http, http: &Http,
guild: &Guild, guild: &Guild,
global_data: &mut GlobalData, global_data: &GlobalData,
msg_username: &str, msg: WoxlfMessage<'_>,
msg_source: MessageSource,
msg_dest: MessageDest,
profile_pic_url: Option<String>,
msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> { ) -> error::Result<()> {
let source = match &msg_source { let source = match &msg.source {
MessageSource::Player(player_data) => { MessageSource::Player(player_data) => {
let name = guild let name = guild
.members .members
@ -115,113 +210,106 @@ async fn send_to_host_channel(
MessageSource::Automated => "Automated".to_string(), MessageSource::Automated => "Automated".to_string(),
}; };
let dest = match &msg_dest { let dest = match &msg.median {
MessageDest::PlayerChannel(p) | MessageDest::PlayerDm(p) => { Median::DirectMessage => {
let name = guild if let MessageDest::Player(dest_player) = &msg.dest {
.members let name = guild
.get(&UserId::from(p.discord_id)) .members
.unwrap() .get(&UserId::from(dest_player.discord_id))
.display_name(); .unwrap()
.display_name();
format!(" to {} ({})", p.codename, 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( send_webhook_msg(
http, http,
global_data.cfg.discord_config.host_webhook_id, WebhookId::from(global_data.cfg.discord_config.host_webhook_id),
&host_channel_username, &host_channel_username,
profile_pic_url, Some(msg.get_profile_pic(global_data)?),
msg, &msg.content,
attachments, &msg.attachments,
) )
.await?; .await?;
Ok(()) 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( pub async fn dispatch_message(
ctx: &Context, ctx: &Context,
guild: &Guild, global_data: &GlobalData,
global_data: &mut GlobalData, msg: WoxlfMessage<'_>,
msg_source: MessageSource,
msg_dest: MessageDest,
msg: &str,
attachments: Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> { ) -> error::Result<()> {
let msg_username = match &msg_source { let guild_id = global_data.cfg.discord_config.guild_id;
MessageSource::Player(p) => p.codename.clone(), let guild = GuildId::from(guild_id).to_guild_cached(&ctx.cache).unwrap();
MessageSource::Host => "Woxlf".to_string(),
MessageSource::Automated => "Woxlf System Message".to_string(),
};
let profile_pic = match &msg_source { let msg_tasks = global_data
MessageSource::Player(p) => Some(p.profile_pic_url.clone()), .game_state()?
MessageSource::Host | MessageSource::Automated => {
Some(global_data.game_cfg()?.bot_profile_pic.clone())
}
};
let msg_tasks: Vec<&PlayerData> = global_data
.game_state_mut()?
.player_data .player_data
.iter() .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(); .collect();
for player_data in msg_tasks { results?;
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?
}
}
}
send_to_host_channel( send_to_host_channel(&ctx.http, &guild, global_data, msg).await?;
&ctx.http,
guild,
global_data,
&msg_username,
msg_source,
msg_dest,
profile_pic,
msg,
&attachments,
)
.await?;
Ok(()) Ok(())
} }

View File

@ -10,9 +10,7 @@ use tera::{Tera, Value};
type TeraFnRet = Box<dyn Fn(&HashMap<String, Value>) -> tera::Result<Value> + Send + Sync>; type TeraFnRet = Box<dyn Fn(&HashMap<String, Value>) -> tera::Result<Value> + Send + Sync>;
fn time_to_discord_time( fn time_to_discord_time(time_flag: &str) -> TeraFnRet {
time_flag: &str,
) -> TeraFnRet {
let time_flag = time_flag.to_string(); let time_flag = time_flag.to_string();
Box::new( Box::new(