wOxlf/src/game/message_router.rs

349 lines
8.9 KiB
Rust

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, Message, WebhookId};
use serenity::prelude::Mentionable;
use serenity::utils::MessageBuilder;
#[derive(Debug, Clone)]
pub enum MessageSource {
Player(Box<PlayerData>),
Host,
Automated,
}
impl Default for MessageSource {
fn default() -> Self {
Self::Automated
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub enum MessageDest {
Player(Box<PlayerData>),
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;
const PING = 0b00000010;
}
}
#[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 Game 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
}
pub fn ping(mut self) -> Self {
self.flags |= MsgFlags::PING;
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<String>,
msg: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<Message> {
let webhook = http.get_webhook(webhook_id.0).await?;
let sent_msg = webhook
.execute(http, true, 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?;
if let Some(sent_msg) = sent_msg {
Ok(sent_msg)
} else {
Err(WoxlfError::WebhookMsgError)
}
}
pub async fn send_private_message(
http: &Http,
dest: UserId,
msg_content: &str,
attachments: &Option<Vec<AttachmentType<'_>>>,
) -> error::Result<Message> {
let dest_user = dest.to_user(http).await?;
let msg = dest_user
.dm(http, |msg| {
msg.content(msg_content);
if let Some(attachments) = attachments {
msg.add_files(attachments.clone());
}
msg
})
.await?;
Ok(msg)
}
pub async fn send_to_host_channel(
http: &Http,
guild: &Guild,
global_data: &GlobalData,
msg: WoxlfMessage<'_>,
) -> error::Result<Message> {
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!("**[DM to {} ({})]** ", dest_player.codename, name)
} else {
"".to_string()
}
}
_ => "".to_string(),
};
let host_channel_username = format!("{} ({})", msg.get_message_username(global_data)?, source,);
let content = format!("{}{}", dest, msg.content);
send_webhook_msg(
http,
WebhookId::from(global_data.cfg.discord_config.host_webhook_id),
&host_channel_username,
Some(msg.get_profile_pic(global_data)?),
&content,
&msg.attachments,
)
.await
}
pub async fn send_message(
ctx: &Context,
global_data: &GlobalData,
msg: &WoxlfMessage<'_>,
dest_player: &PlayerData,
) -> Result<(), WoxlfError> {
let content = if msg.flags.contains(MsgFlags::PING) {
let dest_player_id = UserId::from(dest_player.discord_id);
MessageBuilder::new()
.push_line(dest_player_id.mention())
.push(msg.content.clone())
.build()
} else {
msg.content.clone()
};
let send_msg = 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)?),
&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(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(), &content).await?
}
};
if msg.flags.contains(MsgFlags::PIN_MSG) {
send_msg.pin(&ctx.http).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::<Listeners>().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(())
}