summaryrefslogtreecommitdiff
path: root/src/handlers/event
diff options
context:
space:
mode:
Diffstat (limited to 'src/handlers/event')
-rw-r--r--src/handlers/event/message.rs35
-rw-r--r--src/handlers/event/mod.rs40
-rw-r--r--src/handlers/event/pinboard.rs77
-rw-r--r--src/handlers/event/reactboard.rs64
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(())
+}