diff options
| author | seth <[email protected]> | 2023-11-30 22:18:51 -0500 |
|---|---|---|
| committer | seth <[email protected]> | 2023-12-01 07:12:49 -0500 |
| commit | 76c0f94e6d7aa108424b34826eb7d8514b026287 (patch) | |
| tree | 7315bd6dfe52c158041bed64ba39781718a69335 /src/handlers/event | |
| parent | db52e639b85d79bed870020aec7a045851ca5ee3 (diff) | |
feat: use eyre, better logging, & refactor
small commits be damned
Diffstat (limited to 'src/handlers/event')
| -rw-r--r-- | src/handlers/event/message.rs | 35 | ||||
| -rw-r--r-- | src/handlers/event/mod.rs | 40 | ||||
| -rw-r--r-- | src/handlers/event/pinboard.rs | 77 | ||||
| -rw-r--r-- | src/handlers/event/reactboard.rs | 64 |
4 files changed, 216 insertions, 0 deletions
diff --git a/src/handlers/event/message.rs b/src/handlers/event/message.rs new file mode 100644 index 0000000..a84ec59 --- /dev/null +++ b/src/handlers/event/message.rs @@ -0,0 +1,35 @@ +use crate::{consts, utils, Data}; + +use color_eyre::eyre::{Report, Result}; +use log::*; +use poise::serenity_prelude::{Context, Message}; +use poise::FrameworkContext; + +pub async fn handle( + ctx: &Context, + framework: FrameworkContext<'_, Data, Report>, + msg: &Message, +) -> Result<()> { + if should_echo(framework, msg) { + msg.reply(ctx, &msg.content).await?; + } + + Ok(()) +} + +fn should_echo(framework: FrameworkContext<'_, Data, Report>, msg: &Message) -> bool { + let gid = msg.guild_id.unwrap_or_default(); + if msg.author.id == framework.bot_id || !utils::is_guild_allowed(gid) { + info!("not running copypasta command in {gid}"); + return false; + } + + let content = &msg.content; + + content == "🗿" + || consts::TEAMOJIS.contains(&content.as_str()) + || content.to_ascii_lowercase() == "moyai" + || content + .to_ascii_lowercase() + .contains("twitter's recommendation algorithm") +} diff --git a/src/handlers/event/mod.rs b/src/handlers/event/mod.rs new file mode 100644 index 0000000..09be62b --- /dev/null +++ b/src/handlers/event/mod.rs @@ -0,0 +1,40 @@ +use crate::Data; + +use color_eyre::eyre::{Report, Result}; +use poise::serenity_prelude as serenity; +use poise::{Event, FrameworkContext}; + +mod message; +mod pinboard; +mod reactboard; + +pub async fn handle( + ctx: &serenity::Context, + event: &Event<'_>, + framework: FrameworkContext<'_, Data, Report>, + data: &Data, +) -> Result<()> { + match event { + Event::Ready { data_about_bot } => { + log::info!("logged in as {}", data_about_bot.user.name) + } + + Event::Message { new_message } => message::handle(ctx, framework, new_message).await?, + + Event::ChannelPinsUpdate { pin } => { + if let Some(settings) = &data.settings { + pinboard::handle(ctx, pin, settings).await + } + } + + Event::ReactionAdd { add_reaction } => { + if let Some(settings) = &data.settings { + reactboard::handle(ctx, add_reaction, settings).await? + } + } + + _ => {} + } + + Ok(()) +} diff --git a/src/handlers/event/pinboard.rs b/src/handlers/event/pinboard.rs new file mode 100644 index 0000000..0c87a5b --- /dev/null +++ b/src/handlers/event/pinboard.rs @@ -0,0 +1,77 @@ +use crate::settings::Settings; +use crate::utils; + +use log::*; +use poise::serenity_prelude::model::prelude::*; +use poise::serenity_prelude::Context; + +pub async fn handle(ctx: &Context, pin: &ChannelPinsUpdateEvent, settings: &Settings) { + if let Some(sources) = &settings.pinboard_sources { + if !sources.contains(&pin.channel_id) { + warn!("can't access source of pin!"); + return; + } + } + + let mut pinner = guess_pinner(ctx, pin).await; + let pins = pin + .channel_id + .pins(&ctx.http) + .await + .expect("couldn't get a list of pins!?"); + + for pin in pins { + // We call `take` because it's supposed to be just for the latest message. + redirect(ctx, &pin, pinner.take(), settings.pinboard_target).await; + pin.unpin(&ctx).await.expect("couldn't unpin message"); + } +} + +async fn redirect(ctx: &Context, pin: &Message, pinner: Option<UserId>, target: ChannelId) { + let pinner = pinner.map_or("*someone*".to_owned(), |u| format!("<@{u}>")); + let embed = utils::resolve_message_to_embed(ctx, pin).await; + + target + .send_message(&ctx.http, |m| { + m.allowed_mentions(|am| am.empty_parse()) + .content(format!("📌'd by {pinner} in {}", pin.link())) + .set_embed(embed) + }) + .await + .expect("couldn't redirect message"); +} + +/// (Desperate, best-effort) attempt to get the user that pinned the last message +/// +/// Now, since Discord is SUPER annoying, it doesn't actually tell you which bloody user +/// that triggered the pins update event. So, you have to dig into the audit log. +/// Unfortunately, while you do get a timestamp, the REST API does not return the time at +/// which each action is logged, which, to me, means that it is not a freaking log *at all*. +/// +/// I love Discord. +/// +/// So, the plan is that only the first pinned message gets clear pinner information, +/// since we can just get the latest pin, which should happen on the exact second. +/// We can't reliably say the same for any existing pins, so we can only /shrug and say +/// *somebody* did it. Ugh. +async fn guess_pinner(ctx: &Context, pin: &ChannelPinsUpdateEvent) -> Option<UserId> { + if let Some(g) = pin.guild_id { + g.audit_logs( + &ctx.http, + // This `num` call shouldn't be necessary. + // See https://github.com/serenity-rs/serenity/issues/2488 + Some(Action::Message(MessageAction::Pin).num()), + None, // user id + None, // before + Some(1), // limit + ) + .await + .ok() + .and_then(|mut logs| logs.entries.pop()) + .map(|first| first.user_id) + } else { + // TODO: mayyyyybe we can guess who pinned something in a DM...? + warn!("couldn't figure out who pinned in {}!", pin.channel_id); + None + } +} diff --git a/src/handlers/event/reactboard.rs b/src/handlers/event/reactboard.rs new file mode 100644 index 0000000..3972931 --- /dev/null +++ b/src/handlers/event/reactboard.rs @@ -0,0 +1,64 @@ +use crate::{settings::Settings, utils}; + +use color_eyre::eyre::{eyre, Context as _, Result}; +use log::*; +use poise::serenity_prelude::{Context, Message, MessageReaction, Reaction}; + +pub async fn handle(ctx: &Context, reaction: &Reaction, settings: &Settings) -> Result<()> { + let msg = reaction + .message(&ctx.http) + .await + .wrap_err("couldn't get reaction from message!")?; + + let matched = msg + .clone() + .reactions + .into_iter() + .find(|r| r.reaction_type == reaction.emoji) + .ok_or_else(|| { + eyre!( + "couldn't find any matching reactions for {} in message {}!", + reaction.emoji.as_data(), + msg.id + ) + })?; + + send_to_reactboard(ctx, &matched, &msg, settings).await?; + + Ok(()) +} + +async fn send_to_reactboard( + ctx: &Context, + reaction: &MessageReaction, + msg: &Message, + settings: &Settings, +) -> Result<()> { + if !settings.can_use_reaction(reaction) { + info!("reaction {} can't be used!", reaction.reaction_type); + return Ok(()); + } + + if reaction.count == settings.reactboard_requirement.unwrap_or(5) { + let embed = utils::resolve_message_to_embed(ctx, msg).await; + + settings + .reactboard_target + .send_message(&ctx.http, |m| { + m.allowed_mentions(|am| am.empty_parse()) + .content(format!( + "{} **#{}**", + reaction.reaction_type, reaction.count + )) + .set_embed(embed) + }) + .await?; + } else { + info!( + "not putting message {} on reactboard, not enough reactions", + msg.id + ) + } + + Ok(()) +} |
