diff --git a/Cargo.toml b/Cargo.toml index 2e7dc4e3..2170de4b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" twilight-gateway = "0.11.0" twilight-http = "0.11.0" twilight-model = "0.11.2" -twilight-util = { version = "0.11.0", features = ["builder"] } +twilight-util = { version = "0.11.0", features = ["builder", "snowflake"] } twilight-interactions = "0.11.0" twilight-error = "0.11.4" twilight-mention = "0.11.0" diff --git a/sqlx-data.json b/sqlx-data.json index c20c6640..be8de52d 100644 --- a/sqlx-data.json +++ b/sqlx-data.json @@ -2547,6 +2547,50 @@ }, "query": "INSERT INTO overrides\n (id, guild_id, name, starboard_id)\n VALUES ($1, $2, $3, $4)\n RETURNING *" }, + "f593aa1cbdba23466e32e06ffbd59144e044dfad3f355cdb385b6941d28580ea": { + "describe": { + "columns": [ + { + "name": "user_id", + "ordinal": 0, + "type_info": "Int8" + }, + { + "name": "is_bot", + "ordinal": 1, + "type_info": "Bool" + }, + { + "name": "credits", + "ordinal": 2, + "type_info": "Int4" + }, + { + "name": "donated_cents", + "ordinal": 3, + "type_info": "Int8" + }, + { + "name": "patreon_status", + "ordinal": 4, + "type_info": "Int2" + } + ], + "nullable": [ + false, + false, + false, + false, + false + ], + "parameters": { + "Left": [ + "Int8" + ] + } + }, + "query": "SELECT * FROM users WHERE user_id=$1" + }, "f82801c420e139303f7a931b601b5d95be61de8607350bbd7d2c9729723623e9": { "describe": { "columns": [ diff --git a/src/core/starboard/reaction_events.rs b/src/core/starboard/reaction_events.rs index ec2d9325..226b31a2 100644 --- a/src/core/starboard/reaction_events.rs +++ b/src/core/starboard/reaction_events.rs @@ -1,5 +1,8 @@ -use twilight_model::gateway::payload::incoming::{ - ReactionAdd, ReactionRemove, ReactionRemoveAll, ReactionRemoveEmoji, +use twilight_model::{ + gateway::payload::incoming::{ + ReactionAdd, ReactionRemove, ReactionRemoveAll, ReactionRemoveEmoji, + }, + id::Id, }; use crate::{ @@ -37,7 +40,7 @@ pub async fn handle_reaction_add( } let orig_msg = Message::get_original(&bot.pool, unwrap_id!(event.message_id)).await?; - let orig_msg = match orig_msg { + let (orig_msg, author_is_bot) = match orig_msg { None => { // author data let (author_is_bot, author_id) = { @@ -81,18 +84,34 @@ pub async fn handle_reaction_add( ))?; match orig { - Some(msg) => msg, - None => Message::get(&bot.pool, unwrap_id!(event.message_id)) - .await? - .unwrap(), + Some(msg) => (msg, author_is_bot), + None => { + let msg = Message::get(&bot.pool, unwrap_id!(event.message_id)) + .await? + .unwrap(); + (msg, author_is_bot) + } } } - Some(msg) => msg, + Some(msg) => { + let user = User::get(&bot.pool, msg.author_id).await?.unwrap(); + (msg, user.is_bot) + } }; let configs = StarboardConfig::list_for_channel(bot, guild_id, event.channel_id).await?; - let status = - VoteStatus::get_vote_status(bot, &emoji, configs, event.message_id, event.channel_id).await; + let status = VoteStatus::get_vote_status( + bot, + &emoji, + configs, + Id::new(event.user_id.try_into().unwrap()), + Id::new(orig_msg.message_id.try_into().unwrap()), + Id::new(orig_msg.channel_id.try_into().unwrap()), + Id::new(orig_msg.author_id.try_into().unwrap()), + author_is_bot, + None, + ) + .await?; match status { VoteStatus::Ignore => Ok(()), @@ -172,12 +191,22 @@ pub async fn handle_reaction_remove( None => return Ok(()), Some(orig) => orig, }; + let author = User::get(&bot.pool, orig.author_id).await?.unwrap(); let emoji = SimpleEmoji::from(event.emoji.clone()); let configs = StarboardConfig::list_for_channel(bot, guild_id, event.channel_id).await?; - let status = - VoteStatus::get_vote_status(&bot, &emoji, configs, event.message_id, event.channel_id) - .await; + let status = VoteStatus::get_vote_status( + &bot, + &emoji, + configs, + Id::new(event.user_id.try_into().unwrap()), + Id::new(orig.message_id.try_into().unwrap()), + Id::new(orig.channel_id.try_into().unwrap()), + Id::new(orig.author_id.try_into().unwrap()), + author.is_bot, + None, + ) + .await?; match status { VoteStatus::Valid((upvote, downvote)) => { diff --git a/src/core/starboard/vote_status.rs b/src/core/starboard/vote_status.rs index 2b44acb5..f0f1275a 100644 --- a/src/core/starboard/vote_status.rs +++ b/src/core/starboard/vote_status.rs @@ -1,11 +1,18 @@ //! tool for checking if a vote is valid +use std::time::{SystemTime, UNIX_EPOCH}; + use twilight_model::id::{ - marker::{ChannelMarker, MessageMarker}, + marker::{ChannelMarker, MessageMarker, UserMarker}, Id, }; +use twilight_util::snowflake::Snowflake; -use crate::{client::bot::StarboardBot, core::emoji::SimpleEmoji}; +use crate::{ + client::bot::StarboardBot, + core::{emoji::SimpleEmoji, has_image::has_image}, + errors::StarboardResult, +}; use super::config::StarboardConfig; @@ -18,12 +25,29 @@ pub enum VoteStatus { impl VoteStatus { pub async fn get_vote_status( - _bot: &StarboardBot, + bot: &StarboardBot, emoji: &SimpleEmoji, configs: Vec, - _message_id: Id, - _channel_id: Id, - ) -> VoteStatus { + reactor_id: Id, + message_id: Id, + channel_id: Id, + message_author_id: Id, + message_author_is_bot: bool, + message_has_image: Option, + ) -> StarboardResult { + let message_has_image = match message_has_image { + Some(val) => Some(val), + None => match bot + .cache + .fog_message(&bot, channel_id, message_id) + .await? + .value() + { + Some(msg) => Some(has_image(&msg.embeds, &msg.attachments)), + None => None, + }, + }; + let mut invalid_exists = false; let mut allow_remove = true; @@ -50,7 +74,51 @@ impl VoteStatus { } // check settings - // TODO + let is_valid; + + // settings to check: + // - self_vote + // - allow_bots + // - require_image + // - older_than + // - newer_than + // alow need to check permroles + + let message_age: i64 = { + let now = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_millis(); + let created_at = message_id.timestamp(); + + ((now - created_at as u128) / 1000).try_into().unwrap() + }; + let older_than = config.resolved.older_than; + let newer_than = config.resolved.newer_than; + + if !config.resolved.self_vote && reactor_id == message_author_id { + // self-vote + is_valid = false; + } else if !config.resolved.allow_bots && message_author_is_bot { + // allow-bots + is_valid = false; + } else if config.resolved.require_image && !matches!(message_has_image, Some(true)) { + // require-image + is_valid = false; + } else if older_than != 0 && message_age < older_than { + // older-than + is_valid = false; + } else if newer_than != 0 && message_age > newer_than { + // newer-than + is_valid = false; + } else { + is_valid = true; + } + + if !is_valid { + invalid_exists = true; + continue; + } // add to corresponding list if is_downvote { @@ -61,12 +129,12 @@ impl VoteStatus { } if upvote.is_empty() && downvote.is_empty() { if invalid_exists && allow_remove { - VoteStatus::Remove + Ok(VoteStatus::Remove) } else { - VoteStatus::Ignore + Ok(VoteStatus::Ignore) } } else { - VoteStatus::Valid((upvote, downvote)) + Ok(VoteStatus::Valid((upvote, downvote))) } } } diff --git a/src/database/models/user.rs b/src/database/models/user.rs index f6a3e473..7779409b 100644 --- a/src/database/models/user.rs +++ b/src/database/models/user.rs @@ -23,4 +23,10 @@ impl User { .await .map_err(|e| e.into()) } + + pub async fn get(pool: &sqlx::PgPool, user_id: i64) -> sqlx::Result> { + sqlx::query_as!(Self, "SELECT * FROM users WHERE user_id=$1", user_id,) + .fetch_optional(pool) + .await + } }