Skip to content
Snippets Groups Projects
Commit 3ef4292e authored by Stephen D's avatar Stephen D
Browse files

Merge branch 'starboard_counts' into 'master'

Starboard counts

See merge request !12
parents fd3e0c90 f5dfb9f2
No related branches found
No related tags found
1 merge request!12Starboard counts
Pipeline #424 passed
......@@ -120,7 +120,7 @@ checksum = "ec8a7b6a70fde80372154c65702f00a0f56f3e1c36abbc6c440484be248856db"
[[package]]
name = "cat-disruptor-6500"
version = "0.1.2"
version = "0.2.0"
dependencies = [
"anyhow",
"bigdecimal",
......@@ -202,6 +202,20 @@ dependencies = [
"typenum",
]
[[package]]
name = "dashmap"
version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if 1.0.0",
"hashbrown",
"lock_api",
"once_cell",
"parking_lot_core",
"serde",
]
[[package]]
name = "deflate"
version = "0.8.6"
......@@ -1511,10 +1525,12 @@ dependencies = [
"bitflags",
"bytes 1.2.1",
"cfg-if 1.0.0",
"dashmap",
"flate2",
"futures",
"mime",
"mime_guess",
"parking_lot",
"percent-encoding",
"reqwest 0.11.12",
"serde",
......
[package]
name = "cat-disruptor-6500"
version = "0.1.2"
version = "0.2.0"
authors = ["Stephen <stephen@stephendownward.ca>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serenity = {version = "0.11", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "static_assertions"] }
serenity = {version = "0.11", default-features = false, features = ["client", "gateway", "rustls_backend", "model", "static_assertions", "cache"] }
tokio = {version = "1.21", features = ["full", "time"] }
phf = { version = "0.8", features = ["macros"] }
toml = "0.5"
......
......@@ -35,7 +35,8 @@ pub(crate) trait LineHandler: Send + Sync {
#[async_trait]
pub(crate) trait ReactionHandler: Send + Sync {
async fn reaction(&self, _ctx: &Context, reaction: &Reaction);
async fn reaction_add(&self, ctx: &Context, reaction: &Reaction);
async fn reaction_del(&self, ctx: &Context, reaction: &Reaction);
}
pub(crate) struct Dispatcher {
......@@ -53,7 +54,13 @@ impl EventHandler for Dispatcher {
async fn reaction_add(&self, ctx: Context, reaction: Reaction) {
for r in &self.reacts {
r.reaction(&ctx, &reaction).await;
r.reaction_add(&ctx, &reaction).await;
}
}
async fn reaction_remove(&self, ctx: Context, reaction: Reaction) {
for r in &self.reacts {
r.reaction_del(&ctx, &reaction).await;
}
}
......
......@@ -7,35 +7,45 @@ use anyhow::Context as AnyhowContext;
use bigdecimal::{BigDecimal, FromPrimitive, ToPrimitive};
use diesel::{ExpressionMethods, PgConnection, QueryDsl, RunQueryDsl};
use serenity::async_trait;
use serenity::model::prelude::{ChannelId, EmojiId, GuildId, Message, Reaction, ReactionType};
use serenity::builder::CreateEmbed;
use serenity::model::prelude::{
ChannelId, Emoji, EmojiId, Message, MessageReaction, Reaction, ReactionType,
};
use serenity::prelude::*;
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct StarboardHandler {
#[derive(Clone)]
pub struct SingleMessageHandler {
conn: Arc<Mutex<PgConnection>>,
server_settings: ServerSetting,
reaction_count: u64,
msg: Message,
emoji: Emoji,
name: String,
image: Option<String>,
}
impl StarboardHandler {
pub fn new(conn: Arc<Mutex<PgConnection>>) -> Self {
Self { conn }
}
async fn handle_reaction(&self, ctx: &Context, reaction: &Reaction) -> anyhow::Result<()> {
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 gs: Vec<ServerSetting> = server_settings::dsl::server_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(&*self.conn.lock().await)?;
let gs = match gs.first() {
.get_results(&*conn.lock().await)?;
let gs = match gs.pop() {
Some(x) => x,
None => return Ok(()),
};
......@@ -45,13 +55,92 @@ impl StarboardHandler {
.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;
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(());
}
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?;
Ok(())
}
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)?;
}
}
}
......@@ -59,83 +148,64 @@ impl StarboardHandler {
Ok(())
}
async fn handle_matching_reaction(
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,
gs: &ServerSetting,
count: u64,
msg: &Message,
guild: &GuildId,
message_id: u64,
) -> 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 image = msg
.attachments
.iter()
.filter(|a| a.width.is_some())
.map(|a| &a.url)
.next();
let repost = ChannelId(
gs.starboard_channel
.to_u64()
.context("Could not convert starboard channel to a u64")?,
)
.send_message(ctx, |m| {
m.embed(|e| {
let mut 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);
if let Some(url) = image {
e = e.image(url);
}
e
})
})
.await?;
let channel_id = ChannelId(
self.server_settings
.starboard_channel
.to_u64()
.context("Could not convert starboard channel to a u64")?,
);
// 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)?;
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;
}
}
Ok(())
0
}
fn emoji_match(rt: &ReactionType, em: EmojiId) -> bool {
......@@ -143,9 +213,29 @@ impl StarboardHandler {
}
}
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_trait]
impl ReactionHandler for StarboardHandler {
async fn reaction(&self, ctx: &Context, reaction: &Reaction) {
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) {
if let Err(e) = self.handle_reaction(ctx, reaction).await {
eprintln!("Error in starboard: {:?}", e);
}
......
......@@ -17,7 +17,7 @@ pub struct NewUserProgram<'a> {
pub code: &'a str,
}
#[derive(Queryable)]
#[derive(Clone, Queryable)]
pub struct ServerSetting {
pub id: i32,
pub guild_id: BigDecimal,
......
......@@ -67,7 +67,7 @@ impl Program {
.and(columns::name.eq(name)),
),
)
.set(columns::published.eq(if published { 1 } else { 0 }))
.set(columns::published.eq(i32::from(published)))
.execute(conn)
.ok()? == 1
{
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment