Initial Webhook Support

+ Host messages still come through the bot instead of webhooks
+ Each player channel gets one webhook that all messages come through
+ The username is set per message
+ Currently, profile pics are the default
+ clippy + fmt
msg_refactor
Joey Hines 2022-03-20 11:42:04 -06:00
parent 673d5b56f0
commit 71b8bc6e20
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
9 changed files with 149 additions and 23 deletions

View File

@ -16,6 +16,7 @@ pub struct BotConfig {
pub token: String, pub token: String,
pub app_id: u64, pub app_id: u64,
pub host_channel: u64, pub host_channel: u64,
pub host_webhook: String,
pub vote_channel: u64, pub vote_channel: u64,
pub category: u64, pub category: u64,
pub game_state_dir: PathBuf, pub game_state_dir: PathBuf,

View File

@ -153,7 +153,7 @@ async fn broadcast(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Host, MessageSource::Automated,
&msg, &msg,
None, None,
true, true,
@ -189,7 +189,7 @@ async fn next_phase(ctx: &Context, msg: &Message, mut args: Args) -> CommandResu
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Host, MessageSource::Automated,
&broadcast, &broadcast,
None, None,
true, true,
@ -296,7 +296,7 @@ async fn add_time(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult
ctx, ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Host, MessageSource::Automated,
&broadcast, &broadcast,
None, None,
true, true,

View File

@ -4,7 +4,7 @@ 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 crate::discord::helper::send_msg_to_player_channels; 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::MessageSource;
@ -38,7 +38,7 @@ impl EventHandler for Handler {
.get_player_from_channel(msg.channel_id.0) .get_player_from_channel(msg.channel_id.0)
{ {
let guild = msg.guild(&ctx.cache).await.unwrap(); let guild = msg.guild(&ctx.cache).await.unwrap();
let user_msg = format!("**{}** > {}", player_data.codename, msg.content); let user_msg = msg.content.clone();
let attachments: Vec<AttachmentType> = msg let attachments: Vec<AttachmentType> = msg
.attachments .attachments
@ -46,21 +46,36 @@ impl EventHandler for Handler {
.map(|a| AttachmentType::Image(&a.url)) .map(|a| AttachmentType::Image(&a.url))
.collect(); .collect();
send_msg_to_player_channels( let msg_source = MessageSource::Player(Box::new(player_data.clone()));
send_webhook_msg_to_player_channels(
&ctx, &ctx,
&guild, &guild,
&mut global_data, &mut global_data,
MessageSource::Player(msg.channel_id.0), msg_source,
&user_msg, &user_msg,
Some(attachments), Some(attachments),
false,
) )
.await .await
.expect("Unable to send message to players"); .expect("Unable to send message to players");
} }
} }
async fn ready(&self, _ctx: Context, ready: Ready) { async fn ready(&self, ctx: Context, ready: Ready) {
let mut data = ctx.data.write().await;
let global_data = data.get_mut::<GlobalData>().unwrap();
let mut global_data = global_data.lock().await;
let host_webhook = ctx
.http
.get_webhook_from_url(&global_data.cfg.host_webhook)
.await
.expect("Unable to open host webhook");
global_data.host_webhook = Some(host_webhook);
println!("{} is connected!", ready.user.name); println!("{} is connected!", ready.user.name);
} }
} }

View File

@ -1,6 +1,6 @@
use serenity::client::Context; use serenity::client::Context;
use serenity::framework::standard::Args; use serenity::framework::standard::Args;
use serenity::http::AttachmentType; use serenity::http::{AttachmentType, Http};
use serenity::model::channel::{Message, 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, UserId};
@ -13,6 +13,7 @@ use crate::game::global_data::GlobalData;
use crate::game::player_data::PlayerData; use crate::game::player_data::PlayerData;
use crate::game::MessageSource; use crate::game::MessageSource;
use crate::{error, game}; use crate::{error, game};
use serenity::model::prelude::Webhook;
use serenity::prelude::SerenityError; use serenity::prelude::SerenityError;
pub async fn send_msg_to_player_channels( pub async fn send_msg_to_player_channels(
@ -29,8 +30,8 @@ pub async fn send_msg_to_player_channels(
.player_data .player_data
.iter() .iter()
.filter(|player_data| { .filter(|player_data| {
if let MessageSource::Player(channel_id) = msg_source { if let MessageSource::Player(source_player) = &msg_source {
if channel_id == player_data.channel { if source_player.channel == player_data.channel {
return false; return false;
} }
} }
@ -77,15 +78,10 @@ pub async fn send_msg_to_player_channels(
.unwrap(); .unwrap();
let source = match msg_source { let source = match msg_source {
MessageSource::Player(channel_id) => { MessageSource::Player(player_data) => {
let discord_id = global_data
.game_state_mut()?
.get_player_from_channel(channel_id)
.unwrap()
.discord_id;
let name = guild let name = guild
.members .members
.get(&UserId::from(discord_id)) .get(&UserId::from(player_data.discord_id))
.unwrap() .unwrap()
.display_name(); .display_name();
@ -109,6 +105,98 @@ pub async fn send_msg_to_player_channels(
Ok(()) Ok(())
} }
pub async fn send_webhook_msg(
http: &Http,
webhook: &Webhook,
username: &str,
msg: &str,
attachment: Option<Vec<AttachmentType<'_>>>,
) -> error::Result<()> {
webhook
.execute(http, false, |w| {
w.content(&msg).username(username);
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 msg_tasks = global_data
.game_state_mut()?
.player_data
.iter()
.filter(|player_data| {
if let MessageSource::Player(source_player) = &msg_source {
if source_player.channel == player_data.channel {
return false;
}
}
true
})
.map(|player_data| {
send_webhook_msg(
&ctx.http,
&player_data.channel_webhook,
&msg_username,
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.host_webhook()?,
&host_channel_username,
msg,
attachment,
)
.await?;
Ok(())
}
pub async fn add_user_to_game( pub async fn add_user_to_game(
ctx: &Context, ctx: &Context,
guild: &Guild, guild: &Guild,
@ -139,6 +227,13 @@ pub async fn add_user_to_game(
channel.create_permission(&ctx.http, &overwrite).await?; channel.create_permission(&ctx.http, &overwrite).await?;
let webhook = channel
.create_webhook(
&ctx.http,
format!("{}'s Woxlf Webhook", discord_user.display_name()),
)
.await?;
let msg = channel.send_message(&ctx.http, |m| { let msg = channel.send_message(&ctx.http, |m| {
m.content(MessageBuilder::new() m.content(MessageBuilder::new()
.push("Welcome ") .push("Welcome ")
@ -162,6 +257,7 @@ pub async fn add_user_to_game(
discord_id: discord_user.user.id.0, discord_id: discord_user.user.id.0,
vote_target: None, vote_target: None,
codename, codename,
channel_webhook: webhook,
}; };
global_data.game_state_mut()?.player_data.push(player_data); global_data.game_state_mut()?.player_data.push(player_data);

View File

@ -1,3 +1,3 @@
pub mod commands; pub mod commands;
pub mod helper;
pub mod event_handler; pub mod event_handler;
pub mod helper;

View File

@ -12,6 +12,7 @@ pub enum WoxlfError {
SerenityError(serenity::Error), SerenityError(serenity::Error),
DiscordIdParseError(String), DiscordIdParseError(String),
GameNotInProgress, GameNotInProgress,
HostWebhookError,
} }
impl std::error::Error for WoxlfError {} impl std::error::Error for WoxlfError {}
@ -26,6 +27,7 @@ impl Display for WoxlfError {
WoxlfError::SerenityError(e) => format!("Serenity error: {}", e), WoxlfError::SerenityError(e) => format!("Serenity error: {}", e),
WoxlfError::DiscordIdParseError(e) => format!("Unable to parse player id {}", e), WoxlfError::DiscordIdParseError(e) => format!("Unable to parse player id {}", e),
WoxlfError::GameNotInProgress => "A game is not currently in progress".to_string(), WoxlfError::GameNotInProgress => "A game is not currently in progress".to_string(),
WoxlfError::HostWebhookError => "Unable to communicate to the host webhook".to_string(),
}; };
write!(f, "Woxlf Error: {}", msg) write!(f, "Woxlf Error: {}", msg)

View File

@ -10,12 +10,14 @@ use crate::error::{Result, WoxlfError};
use crate::game::game_state::GameState; use crate::game::game_state::GameState;
use crate::game::Phase; use crate::game::Phase;
use chrono::Duration; use chrono::Duration;
use serenity::model::prelude::Webhook;
use serenity::utils::MessageBuilder; use serenity::utils::MessageBuilder;
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct GlobalData { pub struct GlobalData {
pub cfg: BotConfig, pub cfg: BotConfig,
pub game_state: Option<GameState>, pub game_state: Option<GameState>,
pub host_webhook: Option<Webhook>,
} }
impl GlobalData { impl GlobalData {
@ -23,6 +25,7 @@ impl GlobalData {
Self { Self {
cfg, cfg,
game_state: None, game_state: None,
host_webhook: None,
} }
} }
@ -101,6 +104,12 @@ impl GlobalData {
"Game not in progress.".to_string() "Game not in progress.".to_string()
} }
} }
pub fn host_webhook(&self) -> Result<&Webhook> {
let webhook = &self.host_webhook;
webhook.as_ref().ok_or(WoxlfError::HostWebhookError)
}
} }
impl TypeMapKey for GlobalData { impl TypeMapKey for GlobalData {

View File

@ -4,6 +4,7 @@ use rand::Rng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::config::BotConfig; use crate::config::BotConfig;
use crate::game::player_data::PlayerData;
pub mod game_state; pub mod game_state;
pub mod global_data; pub mod global_data;
@ -41,9 +42,9 @@ pub fn generate_codename(config: &BotConfig) -> String {
format!("{} {}", adj, occupation) format!("{} {}", adj, occupation)
} }
#[derive(Debug, Deserialize, Serialize, Clone)] #[derive(Debug, Clone)]
pub enum MessageSource { pub enum MessageSource {
Player(u64), Player(Box<PlayerData>),
Host, Host,
Automated, Automated,
} }

View File

@ -1,11 +1,13 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serenity::model::prelude::Webhook;
#[derive(Debug, Deserialize, Serialize, Clone, Default, Hash)] #[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PlayerData { pub struct PlayerData {
pub channel: u64, pub channel: u64,
pub discord_id: u64, pub discord_id: u64,
pub codename: String, pub codename: String,
pub vote_target: Option<u64>, pub vote_target: Option<u64>,
pub channel_webhook: Webhook,
} }
impl PlayerData { impl PlayerData {