Newer
Older
use crate::handlers::ReactionHandler;
use crate::models::ServerSetting;
use crate::schema;
use crate::schema::{server_settings, starboard_mappings};
use anyhow::Context as AnyhowContext;
use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive};
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use serenity::model::prelude::{
ChannelId, Emoji, EmojiId, Message, MessageReaction, Reaction, ReactionType,
};
use serenity::prelude::*;
use std::sync::Arc;
use tokio::sync::Mutex;
#[derive(Clone)]
pub struct SingleMessageHandler {
server_settings: ServerSetting,
reaction_count: u64,
msg: Message,
emoji: Emoji,
name: String,
image: Option<String>,
impl SingleMessageHandler {
pub async fn handle_reaction(
conn: Arc<Mutex<PgConnection>>,
ctx: &Context,
reaction: &Reaction,
) -> anyhow::Result<()> {
let guild = match reaction.channel_id.to_channel(ctx).await?.guild() {
Some(x) => x,
None => return Ok(()),
};
// get corresponding guild settings
let mut gs: Vec<ServerSetting> = server_settings::dsl::server_settings
.filter(
schema::server_settings::columns::guild_id
.eq(BigDecimal::from_u64(guild.guild_id.0).unwrap()),
)
.limit(1)
.get_results(&*conn.lock().await)?;
let gs = match gs.pop() {
Some(x) => x,
None => return Ok(()),
};
let emoji = EmojiId(
gs.starboard_emoji_id
.to_u64()
.context("Could not convert emoji id to u64")?,
);
let msg = reaction.message(ctx).await?;
// reaction from event handler must match
// otherwise we'll update the repost (and add an "edited"
// to the message)
// whenever someone reacts with any reaction
if !Self::emoji_match(&reaction.emoji, emoji) {
return Ok(());
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
let reaction_count = Self::find_emoji_match(msg.reactions.iter(), emoji);
let guild_id = guild.guild_id;
let emoji = guild_id
.emoji(ctx, emoji)
.await
.context("Could not get emoji from guild")?;
let name = msg
.author
.nick_in(ctx, guild_id)
.await
.unwrap_or_else(|| msg.author.tag());
let image = msg
.attachments
.iter()
.filter(|a| a.width.is_some())
.map(|a| &a.url)
.next()
.cloned();
let handler = Self {
conn,
server_settings: gs,
reaction_count,
msg,
emoji,
name,
image,
};
handler.process_match(ctx).await?;
async fn process_match(&self, ctx: &Context) -> anyhow::Result<()> {
let original_id = BigDecimal::from(self.msg.id.0);
diesel::insert_into(starboard_mappings::dsl::starboard_mappings)
.values(starboard_mappings::columns::original_id.eq(&original_id))
.returning(starboard_mappings::columns::repost_id)
.on_conflict_do_nothing()
.execute(&*self.conn.lock().await)?;
let repost_id = starboard_mappings::dsl::starboard_mappings
.filter(starboard_mappings::columns::original_id.eq(&original_id))
.select(starboard_mappings::columns::repost_id)
.limit(1)
.get_results(&*self.conn.lock().await)?;
let repost_id: &Option<BigDecimal> =
repost_id.get(0).context("Insert of mapping failed")?;
match repost_id {
Some(id) => {
self.edit_existing_starboard_message(
ctx,
id.to_u64()
.context("Could not convert repost message id to a u64")?,
.await?;
}
None => {
if self.reaction_count >= self.server_settings.starboard_threshold as u64 {
// post to repost
let repost = self.post_new_starboard_message(ctx).await?;
// update the DB
let repost_id = BigDecimal::from_u64(repost);
diesel::update(
starboard_mappings::dsl::starboard_mappings
.filter(starboard_mappings::columns::original_id.eq(original_id)),
)
.set(starboard_mappings::columns::repost_id.eq(repost_id))
.execute(&*self.conn.lock().await)?;
}
async fn post_new_starboard_message(&self, ctx: &Context) -> anyhow::Result<u64> {
let repost = ChannelId(
self.server_settings
.starboard_channel
.to_u64()
.context("Could not convert starboard channel to a u64")?,
)
.send_message(ctx, |m| m.embed(|e| self.clone().starboard_message(e)))
.await?;
Ok(repost.id.0)
}
async fn edit_existing_starboard_message(
&self,
ctx: &Context,
message_id: u64,
) -> anyhow::Result<()> {
let channel_id = ChannelId(
self.server_settings
.starboard_channel
.to_u64()
.context("Could not convert starboard channel to a u64")?,
);
let mut msg = channel_id.message(ctx, message_id).await?;
msg.edit(ctx, |m| m.embed(|e| self.clone().starboard_message(e)))
.await?;
Ok(())
}
fn starboard_message(self, e: &mut CreateEmbed) -> &mut CreateEmbed {
let mut e = e
.description(format!(
"[Jump to source]({})\n{}",
self.msg.link(),
self.msg.content
))
.title(format!("{} {}", self.reaction_count, self.emoji))
.author(|a| a.name(&self.name).icon_url(self.msg.author.face()))
.timestamp(&self.msg.timestamp);
if let Some(url) = self.image {
e = e.image(url);
}
e
}
fn find_emoji_match<'a, I: Iterator<Item = &'a MessageReaction>>(iter: I, em: EmojiId) -> u64 {
for mr in iter {
if Self::emoji_match(&mr.reaction_type, em) {
return mr.count;
}
}
0
}
fn emoji_match(rt: &ReactionType, em: EmojiId) -> bool {
matches!(rt, ReactionType::Custom { id, .. } if *id == em)
}
}
pub struct StarboardHandler {
conn: Arc<Mutex<PgConnection>>,
}
impl StarboardHandler {
pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
Self { conn }
}
async fn handle_reaction(&self, ctx: &Context, reaction: &Reaction) -> anyhow::Result<()> {
SingleMessageHandler::handle_reaction(self.conn.clone(), ctx, reaction).await
}
}
async fn reaction_add(&self, ctx: &Context, reaction: &Reaction) {
if let Err(e) = self.handle_reaction(ctx, reaction).await {
eprintln!("Error in starboard: {:?}", e);
}
}
async fn reaction_del(&self, ctx: &Context, reaction: &Reaction) {