Initial refactor of message handling

+ Split all message handling into message_router.rs
+ Added whisper command
+ Updated serenity version
+ Fmt, but clippy failing
main
Joey Hines 2023-01-03 20:06:56 -07:00 committed by Gitea
parent 2aec084712
commit 3c219f5bff
11 changed files with 637 additions and 498 deletions

568
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
config = "0.12.0" config = "0.13.3"
structopt = "0.3.26" structopt = "0.3.26"
chrono = {version="0.4.19", features=["serde"]} chrono = {version="0.4.19", features=["serde"]}
serde = "1.0.136" serde = "1.0.136"
@ -18,7 +18,7 @@ reqwest = "0.11.10"
tera = "1.15.0" tera = "1.15.0"
[dependencies.serenity] [dependencies.serenity]
version = "0.10.10" version = "0.11.5"
features = ["framework", "standard_framework"] features = ["framework", "standard_framework"]
[dependencies.tokio] [dependencies.tokio]

View File

@ -15,10 +15,12 @@ pub struct Args {
pub struct GameConfig { pub struct GameConfig {
pub game_name: String, pub game_name: String,
pub bot_name: String, pub bot_name: String,
pub bot_profile_pic: String,
pub vote_phase_name: String, pub vote_phase_name: String,
pub enemy_phase_name: String, pub enemy_phase_name: String,
pub player_group_name: String, pub player_group_name: String,
pub profile_album_hash: String, pub profile_album_hash: String,
pub whispers_allowed: bool,
pub first_name: Vec<String>, pub first_name: Vec<String>,
pub last_name: Vec<String>, pub last_name: Vec<String>,
pub messages: MessageConfig, pub messages: MessageConfig,
@ -43,6 +45,7 @@ pub struct DiscordConfig {
pub host_webhook_id: u64, pub host_webhook_id: u64,
pub vote_channel: u64, pub vote_channel: u64,
pub category: u64, pub category: u64,
pub guild_id: u64,
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]

View File

@ -10,20 +10,22 @@ 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::{Message, UserId}; use serenity::model::prelude::{GuildId, 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, send_msg_to_player_channels}; 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::player_data::PlayerData; use crate::game::player_data::PlayerData;
use crate::game::MessageSource;
use crate::game::Phase; use crate::game::Phase;
use crate::messages::DiscordUser; use crate::messages::DiscordUser;
#[group] #[group]
#[commands(start, say, end, broadcast, next_phase, kill, add_time, test_theme)] #[commands(
start, say, end, broadcast, next_phase, kill, add_time, test_theme, whisper
)]
struct Host; struct Host;
#[command] #[command]
@ -34,7 +36,7 @@ async fn start(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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
let game_name = args.single::<String>()?; let game_name = args.single::<String>()?;
@ -136,7 +138,7 @@ async fn start(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult { async fn end(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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
@ -162,18 +164,18 @@ async fn end(ctx: &Context, msg: &Message, mut _args: Args) -> CommandResult {
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 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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
send_msg_to_player_channels( dispatch_message(
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Host, MessageSource::Host,
MessageDest::Broadcast,
args.rest(), args.rest(),
None, None,
false,
) )
.await?; .await?;
@ -186,7 +188,7 @@ async fn say(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
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 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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
@ -194,14 +196,14 @@ async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
.templates()? .templates()?
.build_announcement(&global_data, args.rest())?; .build_announcement(&global_data, args.rest())?;
send_msg_to_player_channels( dispatch_message(
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Automated, MessageSource::Automated,
MessageDest::Broadcast,
&msg, &msg,
None, None,
true,
) )
.await?; .await?;
@ -214,7 +216,7 @@ async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { async fn next_phase(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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
@ -232,14 +234,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)?;
send_msg_to_player_channels( dispatch_message(
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Automated, MessageSource::Automated,
MessageDest::Broadcast,
&broadcast, &broadcast,
None, None,
true,
) )
.await?; .await?;
@ -251,6 +253,7 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
)) ))
.unwrap(); .unwrap();
vote_channel vote_channel
.id()
.send_message(&ctx.http, |m| { .send_message(&ctx.http, |m| {
m.content(format!( m.content(format!(
"**{} {} Votes:**", "**{} {} Votes:**",
@ -308,7 +311,7 @@ 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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
@ -332,14 +335,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)?;
send_msg_to_player_channels( dispatch_message(
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Automated, MessageSource::Automated,
MessageDest::Broadcast,
&broadcast, &broadcast,
None, None,
true,
) )
.await?; .await?;
@ -501,7 +504,7 @@ struct Player;
async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult { async fn vote(ctx: &Context, msg: &Message, 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).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let mut global_data = global_data.lock().await; let mut global_data = global_data.lock().await;
@ -555,6 +558,7 @@ async fn vote(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
)?; )?;
vote_channel vote_channel
.id()
.send_message(&ctx.http, |m| m.content(vote_msg)) .send_message(&ctx.http, |m| m.content(vote_msg))
.await?; .await?;
} else { } else {
@ -626,7 +630,7 @@ async fn players(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
.push(alive_status); .push(alive_status);
if msg.channel_id.0 == global_data.cfg.discord_config.host_channel { if msg.channel_id.0 == global_data.cfg.discord_config.host_channel {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).unwrap();
let member = guild.members.get(&UserId::from(player.discord_id)).unwrap(); let member = guild.members.get(&UserId::from(player.discord_id)).unwrap();
msg_builder.push_line(format!(" ({})", member.display_name())); msg_builder.push_line(format!(" ({})", member.display_name()));
} else { } else {
@ -639,6 +643,58 @@ async fn players(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
Ok(()) Ok(())
} }
#[command]
#[aliases("pm", "w")]
#[description = "Send a private message to another player."]
async fn whisper(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
if args.len() < 2 {
msg.reply(&ctx.http, "Need a recipient and message!")
.await?;
} else {
let data = ctx.data.write().await;
let global_data = data.get::<GlobalData>().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 target = args.single::<String>()?;
let pm = args.rest();
let src_player = match global_data
.game_state()?
.get_player_from_discord_id(msg.author.id.0)
{
None => {
msg.reply(&ctx.http, "You are not in the game!").await?;
return Ok(());
}
Some(player) => player,
};
if let Some(target_player) = global_data.game_state()?.get_player_by_codename(&target) {
if src_player.discord_id == target_player.discord_id {
msg.reply(&ctx.http, "You can't send messages to yourself!")
.await?;
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?;
} else {
msg.reply(
&ctx.http,
format!("Could not find a player with codename {}.", target),
)
.await?;
}
}
Ok(())
}
#[help] #[help]
#[individual_command_tip = "If you want more information about a specific command, just pass the command as argument."] #[individual_command_tip = "If you want more information about a specific command, just pass the command as argument."]
#[command_not_found_text = "Could not find: `{}`."] #[command_not_found_text = "Could not find: `{}`."]

View File

@ -1,13 +1,13 @@
use serenity::async_trait; use serenity::async_trait;
use serenity::client::{Context, EventHandler}; use serenity::client::{Context, EventHandler};
use serenity::http::AttachmentType;
use serenity::model::channel::Message; use serenity::model::channel::Message;
use serenity::model::gateway::Ready; use serenity::model::gateway::Ready;
use serenity::model::prelude::AttachmentType;
use serenity::utils::parse_emoji; use serenity::utils::parse_emoji;
use crate::discord::helper::send_webhook_msg_to_player_channels;
use crate::game::global_data::GlobalData; use crate::game::global_data::GlobalData;
use crate::game::MessageSource; use crate::game::message_router::MessageSource;
use crate::game::message_router::{dispatch_message, MessageDest};
pub struct Handler {} pub struct Handler {}
@ -43,7 +43,7 @@ impl EventHandler for Handler {
return; return;
} }
let guild = msg.guild(&ctx.cache).await.unwrap(); 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();
@ -52,7 +52,6 @@ impl EventHandler for Handler {
if let Some(emoji) = parse_emoji(&emoji_cap[0]) { if let Some(emoji) = parse_emoji(&emoji_cap[0]) {
if !msg if !msg
.guild(&ctx.cache) .guild(&ctx.cache)
.await
.unwrap() .unwrap()
.emojis .emojis
.contains_key(&emoji.id) .contains_key(&emoji.id)
@ -66,16 +65,17 @@ impl EventHandler for Handler {
let attachments: Vec<AttachmentType> = msg let attachments: Vec<AttachmentType> = msg
.attachments .attachments
.iter() .iter()
.map(|a| AttachmentType::Image(&a.url)) .map(|a| AttachmentType::Image((a.url).parse().unwrap()))
.collect(); .collect();
let msg_source = MessageSource::Player(Box::new(player_data.clone())); let msg_source = MessageSource::Player(Box::new(player_data.clone()));
send_webhook_msg_to_player_channels( dispatch_message(
&ctx, &ctx,
&guild, &guild,
&mut global_data, &mut global_data,
msg_source, msg_source,
MessageDest::Broadcast,
&user_msg, &user_msg,
Some(attachments), Some(attachments),
) )

View File

@ -1,9 +1,8 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::Args; use serenity::framework::standard::Args;
use serenity::http::{AttachmentType, Http}; use serenity::model::channel::{PermissionOverwrite, PermissionOverwriteType};
use serenity::model::channel::{Message, PermissionOverwrite, PermissionOverwriteType};
use serenity::model::guild::{Guild, Member}; use serenity::model::guild::{Guild, Member};
use serenity::model::id::{ChannelId, UserId}; use serenity::model::id::ChannelId;
use serenity::model::Permissions; use serenity::model::Permissions;
use crate::error; use crate::error;
@ -11,201 +10,7 @@ use crate::error::WoxlfError;
use crate::game::game_state::PhaseDuration; use crate::game::game_state::PhaseDuration;
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 crate::game::MessageSource;
use crate::imgur::Image; use crate::imgur::Image;
use serenity::prelude::SerenityError;
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_msg_to_player_channels(
ctx: &Context,
guild: &Guild,
global_data: &mut GlobalData,
msg_source: MessageSource,
msg: &str,
attachment: Option<Vec<AttachmentType<'_>>>,
pin: bool,
) -> error::Result<()> {
let msg_tasks = global_data
.game_state_mut()?
.player_data
.iter()
.filter(|player| filter_source_channel(player, &msg_source))
.map(|player_data| {
let channel = guild
.channels
.get(&ChannelId::from(player_data.channel))
.unwrap();
channel.send_message(&ctx.http, |m| {
m.content(&msg);
if let Some(attachment) = attachment.clone() {
m.add_files(attachment);
}
m
})
});
let msgs: Result<Vec<Message>, SerenityError> = futures::future::join_all(msg_tasks)
.await
.into_iter()
.collect();
let msgs = msgs?;
if pin {
let pin_tasks = msgs.iter().map(|msg| msg.pin(&ctx.http));
let pins: Result<(), SerenityError> = futures::future::join_all(pin_tasks)
.await
.into_iter()
.collect();
pins?;
}
let host_channel = guild
.channels
.get(&ChannelId::from(
global_data.cfg.discord_config.host_channel,
))
.unwrap();
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(),
};
host_channel
.send_message(&ctx.http, |m| {
m.content(format!("({}): {}", source, msg));
if let Some(attachment) = attachment {
m.add_files(attachment);
}
m
})
.await?;
Ok(())
}
pub async fn send_webhook_msg(
http: &Http,
webhook_id: u64,
username: &str,
profile_pic_url: Option<String>,
msg: &str,
attachment: Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> {
let webhook = http.get_webhook(webhook_id).await?;
webhook
.execute(http, false, |w| {
w.content(&msg).username(username);
if let Some(profile_pic_url) = profile_pic_url {
w.avatar_url(profile_pic_url);
}
if let Some(attachment) = attachment.clone() {
w.add_files(attachment);
}
w
})
.await?;
Ok(())
}
pub async fn send_webhook_msg_to_player_channels(
ctx: &Context,
guild: &Guild,
global_data: &mut GlobalData,
msg_source: MessageSource,
msg: &str,
attachment: Option<Vec<AttachmentType<'_>>>,
) -> 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 profile_pic = match &msg_source {
MessageSource::Player(p) => Some(p.profile_pic_url.clone()),
MessageSource::Host | MessageSource::Automated => None,
};
let msg_tasks = global_data
.game_state_mut()?
.player_data
.iter()
.filter(|player| filter_source_channel(player, &msg_source))
.map(|player_data| {
send_webhook_msg(
&ctx.http,
player_data.channel_webhook_id,
&msg_username,
profile_pic.clone(),
msg,
attachment.clone(),
)
});
let msgs: Result<(), WoxlfError> = futures::future::join_all(msg_tasks)
.await
.into_iter()
.collect();
msgs?;
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 host_channel_username = format!("{} ({})", msg_username, source);
send_webhook_msg(
&ctx.http,
global_data.cfg.discord_config.host_webhook_id,
&host_channel_username,
profile_pic,
msg,
attachment,
)
.await?;
Ok(())
}
pub async fn add_user_to_game( pub async fn add_user_to_game(
ctx: &Context, ctx: &Context,
@ -237,7 +42,7 @@ pub async fn add_user_to_game(
.await?; .await?;
let allow = let allow =
Permissions::SEND_MESSAGES | Permissions::READ_MESSAGE_HISTORY | Permissions::READ_MESSAGES; Permissions::SEND_MESSAGES | Permissions::READ_MESSAGE_HISTORY | Permissions::VIEW_CHANNEL;
let overwrite = PermissionOverwrite { let overwrite = PermissionOverwrite {
allow, allow,

View File

@ -19,6 +19,7 @@ pub enum WoxlfError {
RanOutOfCodenames, RanOutOfCodenames,
TemplateError(tera::Error), TemplateError(tera::Error),
RanOutOfProfilePics, RanOutOfProfilePics,
UnsupportedMsgMedium,
} }
impl std::error::Error for WoxlfError {} impl std::error::Error for WoxlfError {}
@ -41,6 +42,9 @@ impl Display for WoxlfError {
} }
WoxlfError::TemplateError(e) => format!("Template error: {}", e), WoxlfError::TemplateError(e) => format!("Template error: {}", e),
WoxlfError::RanOutOfProfilePics => "Ran out of user profile pics".to_string(), WoxlfError::RanOutOfProfilePics => "Ran out of user profile pics".to_string(),
WoxlfError::UnsupportedMsgMedium => {
"Tried to send a message over an unsupported medium".to_string()
}
}; };
write!(f, "Woxlf Error: {}", msg) write!(f, "Woxlf Error: {}", msg)

View File

@ -0,0 +1,227 @@
use crate::error;
use crate::game::global_data::GlobalData;
use crate::game::player_data::PlayerData;
use serenity::client::Context;
use serenity::http::Http;
use serenity::model::guild::Guild;
use serenity::model::id::UserId;
use serenity::model::prelude::AttachmentType;
use serenity::utils::MessageBuilder;
#[derive(Debug, Clone)]
pub enum MessageSource {
Player(Box<PlayerData>),
Host,
Automated,
}
#[derive(Debug, Clone)]
pub enum MessageDest {
PlayerChannel(Box<PlayerData>),
PlayerDm(Box<PlayerData>),
Broadcast,
}
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
}
async fn send_webhook_msg(
http: &Http,
webhook_id: u64,
username: &str,
profile_pic_url: Option<String>,
msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> {
let webhook = http.get_webhook(webhook_id).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(())
}
async fn send_private_message(
http: &Http,
src_username: &str,
dest: UserId,
msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> {
let dest_user = dest.to_user(http).await?;
let mut dm_message = MessageBuilder::new();
dm_message.push_bold_line_safe(format!("{} has sent you a DM:", src_username));
dm_message.push(msg);
dest_user
.dm(http, |msg| {
msg.content(dm_message);
if let Some(attachments) = attachments {
msg.add_files(attachments.clone());
}
msg
})
.await?;
Ok(())
}
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<String>,
msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> 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_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)
}
MessageDest::Broadcast => "".to_string(),
};
let host_channel_username = format!("{} ({}){}", msg_username, source, dest);
send_webhook_msg(
http,
global_data.cfg.discord_config.host_webhook_id,
&host_channel_username,
profile_pic_url,
msg,
attachments,
)
.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<Vec<AttachmentType<'_>>>,
) -> 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 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()?
.player_data
.iter()
.filter(|player| filter_source_channel(player, &msg_source))
.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?
}
}
}
send_to_host_channel(
&ctx.http,
guild,
global_data,
&msg_username,
msg_source,
msg_dest,
profile_pic,
msg,
&attachments,
)
.await?;
Ok(())
}

View File

@ -1,9 +1,8 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::game::player_data::PlayerData;
pub mod game_state; pub mod game_state;
pub mod global_data; pub mod global_data;
pub mod message_router;
pub mod player_data; pub mod player_data;
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Copy)] #[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, Copy)]
@ -17,10 +16,3 @@ impl Default for Phase {
Self::Night Self::Night
} }
} }
#[derive(Debug, Clone)]
pub enum MessageSource {
Player(Box<PlayerData>),
Host,
Automated,
}

View File

@ -1,6 +1,5 @@
use std::sync::Arc; use std::sync::Arc;
use serenity::client::bridge::gateway::GatewayIntents;
use serenity::prelude::*; use serenity::prelude::*;
use structopt::StructOpt; use structopt::StructOpt;
@ -32,10 +31,9 @@ async fn main() {
.expect("Unable to open saved game state."); .expect("Unable to open saved game state.");
} }
let mut client = Client::builder(&bot_cfg.discord_config.token) let mut client = Client::builder(&bot_cfg.discord_config.token, GatewayIntents::all())
.event_handler(Handler {}) .event_handler(Handler {})
.framework(command_framework()) .framework(command_framework())
.intents(GatewayIntents::all())
.type_map_insert::<GlobalData>(Arc::new(Mutex::new(global_data))) .type_map_insert::<GlobalData>(Arc::new(Mutex::new(global_data)))
.await .await
.expect("Err creating client"); .expect("Err creating client");

View File

@ -8,9 +8,11 @@ use serenity::prelude::Mentionable;
use std::collections::HashMap; use std::collections::HashMap;
use tera::{Tera, Value}; use tera::{Tera, Value};
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, time_flag: &str,
) -> Box<dyn Fn(&HashMap<String, Value>) -> tera::Result<Value> + Send + Sync> { ) -> TeraFnRet {
let time_flag = time_flag.to_string(); let time_flag = time_flag.to_string();
Box::new( Box::new(