Newer
Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
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::{BoolExpressionMethods, ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use serenity::async_trait;
use serenity::model::prelude::{
ChannelId, Embed, EmojiId, GuildChannel, GuildId, Message, Reaction, ReactionType,
};
use serenity::prelude::*;
use std::sync::Arc;
use tokio::sync::Mutex;
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<()> {
let guild = match reaction.channel_id.to_channel(ctx).await?.guild() {
Some(x) => x,
None => return Ok(()),
};
// get corresponding guild settings
let 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(&*self.conn.lock().await)?;
let gs = match gs.first() {
Some(x) => x,
None => return Ok(()),
};
let emoji = EmojiId(
gs.starboard_emoji_id
.to_u64()
.context("Could not convert emoji id to u64")?,
);
if Self::emoji_match(&reaction.emoji, emoji) {
let msg = reaction.message(ctx).await?;
for mr in &msg.reactions {
if Self::emoji_match(&mr.reaction_type, emoji) {
self.handle_matching_reaction(ctx, gs, mr.count, &msg, &guild.guild_id)
.await?;
break;
}
}
}
Ok(())
}
async fn handle_matching_reaction(
&self,
ctx: &Context,
gs: &ServerSetting,
count: u64,
msg: &Message,
guild: &GuildId,
) -> anyhow::Result<()> {
if count >= gs.starboard_threshold as u64 {
let original_id = BigDecimal::from(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")?;
if repost_id.is_none() {
// post to repost channel
let name = msg
.author
.nick_in(&ctx, guild)
.await
.unwrap_or_else(|| msg.author.tag());
let repost = ChannelId(
gs.starboard_channel
.to_u64()
.context("Could not convert starboard channel to a u64")?,
)
.send_message(ctx, |m| {
m.embed(|e| {
e.description(format!("[Jump to source]({})\n{}", msg.link(), msg.content))
.author(|a| a.name(&name).icon_url(msg.author.face()))
.timestamp(&msg.timestamp)
})
})
.await?;
// update the DB
let repost_id = BigDecimal::from_u64(repost.id.0);
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)?;
}
}
Ok(())
}
fn emoji_match(rt: &ReactionType, em: EmojiId) -> bool {
matches!(rt, ReactionType::Custom { id, .. } if *id == em)
}
}
#[async_trait]
impl ReactionHandler for StarboardHandler {
async fn reaction(&self, ctx: &Context, reaction: &Reaction) {
if let Err(e) = self.handle_reaction(ctx, reaction).await {
eprintln!("Error in starboard: {:?}", e);
}
}
}