From b1e066e370ec52414f16e58a664691e4fbc4f671 Mon Sep 17 00:00:00 2001 From: Hackermon Date: Tue, 7 May 2024 23:58:11 +0000 Subject: [PATCH] feat(reputation): breakdown --- src/sexurity-api/src/redis.rs | 2 +- src/sexurity-discord/src/breakdown.rs | 118 ++++++++++++++++++ src/sexurity-discord/src/main.rs | 1 + .../src/subscriptions/reputation.rs | 51 ++++++-- 4 files changed, 164 insertions(+), 8 deletions(-) create mode 100644 src/sexurity-discord/src/breakdown.rs diff --git a/src/sexurity-api/src/redis.rs b/src/sexurity-api/src/redis.rs index ee837ec..c6bd72a 100644 --- a/src/sexurity-api/src/redis.rs +++ b/src/sexurity-api/src/redis.rs @@ -14,7 +14,7 @@ pub fn save_vec_to_set<'a, V: serde::Deserialize<'a> + serde::Serialize>( if overwrite { redis::cmd("DEL").arg(&name).query(&mut conn)?; } - + for i in data { let value_name = serde_json::to_string(&i)?; redis::cmd("SADD") diff --git a/src/sexurity-discord/src/breakdown.rs b/src/sexurity-discord/src/breakdown.rs new file mode 100644 index 0000000..f581b7e --- /dev/null +++ b/src/sexurity-discord/src/breakdown.rs @@ -0,0 +1,118 @@ +static HIGH: i32 = 50; +static MEDIUM: i32 = 25; +static LOW: i32 = 15; +static TRIAGED: i32 = 7; +static NOT_APPLICABLE: i32 = -2; +static SPAM: i32 = -10; + +#[derive(Debug)] +pub struct ReputationBreakdown { + high_bounty: i32, + medium_bounty: i32, + low_bounty: i32, + triaged: i32, + not_applicable: i32, + spam: i32, +} + +impl ToString for ReputationBreakdown { + fn to_string(&self) -> String { + let mut parts = Vec::new(); + + macro_rules! add_part { + ($condition:expr, $text:expr, $count:expr) => { + if $condition > 0 { + parts.push(if $count < 2 { + $text.to_string() + } else { + format!("{}({})", $text, $count) + }); + } + }; + } + + add_part!(self.high_bounty, "High", self.high_bounty); + add_part!(self.medium_bounty, "Medium", self.medium_bounty); + add_part!(self.low_bounty, "Low", self.low_bounty); + add_part!(self.triaged, "Triage", self.triaged); + add_part!(self.not_applicable, "N/A", self.not_applicable); + add_part!(self.spam, "Spam", self.spam); + + parts.join(", ") + } +} + +pub fn calculate_rep_breakdown(mut rep_points: i32) -> ReputationBreakdown { + let mut breakdown = ReputationBreakdown { + high_bounty: 0, + medium_bounty: 0, + low_bounty: 0, + triaged: 0, + not_applicable: 0, + spam: 0, + }; + + let thresholds = [HIGH, MEDIUM, LOW, TRIAGED, NOT_APPLICABLE, SPAM]; + let mut index = 0; + + while index < thresholds.len() { + let is_over = { + if thresholds[index] > 0 { + rep_points >= thresholds[index] + } else { + rep_points <= thresholds[index] + } + }; + + if is_over { + let count = rep_points / thresholds[index]; + match index { + 0 => breakdown.high_bounty = count, + 1 => breakdown.medium_bounty = count, + 2 => breakdown.low_bounty = count, + 3 => breakdown.triaged = count, + 4 => breakdown.not_applicable = count, + 5 => breakdown.spam = count, + _ => {} + } + + rep_points -= count * thresholds[index]; + } + + index += 1; + } + + breakdown +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple() { + let a = calculate_rep_breakdown(7); + let b = calculate_rep_breakdown(7 + 7); + + assert!(a.to_string() == "Triage"); + assert!(b.to_string() == "Triage(2)"); + } + + #[test] + fn negative() { + let a = calculate_rep_breakdown(-2); + let b = calculate_rep_breakdown(-2 + -2); + + assert!(a.to_string() == "N/A"); + assert!(b.to_string() == "N/A(2)"); + } + + #[test] + fn none() { + let a = calculate_rep_breakdown(0); + let b = calculate_rep_breakdown(3); + + assert!(a.to_string() == ""); + assert!(b.to_string() == ""); + } +} diff --git a/src/sexurity-discord/src/main.rs b/src/sexurity-discord/src/main.rs index a655b3c..41b9afd 100644 --- a/src/sexurity-discord/src/main.rs +++ b/src/sexurity-discord/src/main.rs @@ -2,6 +2,7 @@ extern crate pretty_env_logger; #[macro_use] extern crate log; +mod breakdown; mod subscriptions; use clap::Parser; use reqwest::blocking as reqwest; diff --git a/src/sexurity-discord/src/subscriptions/reputation.rs b/src/sexurity-discord/src/subscriptions/reputation.rs index 4fb8449..f716862 100644 --- a/src/sexurity-discord/src/subscriptions/reputation.rs +++ b/src/sexurity-discord/src/subscriptions/reputation.rs @@ -7,6 +7,7 @@ use twilight_model::channel::message::embed::Embed; use twilight_model::util::Timestamp; use twilight_util::builder::embed::{EmbedBuilder, EmbedFooterBuilder}; +use crate::breakdown::calculate_rep_breakdown; static MAX_BACKLOG: usize = 100; pub fn consume_backlog)>(mut conn: Connection, on_message_data: E) { let backlog_raw = cmd("ZRANGE") @@ -138,26 +139,46 @@ fn build_embed_data(diff: Vec, handle: &str) -> Option { return Some(embed); } else if new.reputation > old.reputation { // reputation gain + let change = new.reputation - old.reputation; + let breakdown = calculate_rep_breakdown(change as i32); let text = format!( "[**``{}``**]({}) gained **+{} reputation** and now has **{} reputation**", new.user_name, format!("https://hackerone.com/{}", new.user_name), - new.reputation - old.reputation, + change, new.reputation, ); let mut embed_builder = EmbedBuilder::new() .description(text) .color(models::embed_colors::POSTIVE); - if new.rank < old.rank { - let footer = format!("#{} -> #{} (+{})", old.rank, new.rank, old.rank - new.rank); - embed_builder = embed_builder.footer(EmbedFooterBuilder::new(footer)); + + { + let mut footer = String::from(""); + if new.rank < old.rank { + footer += &format!("#{} -> #{} (+{})", old.rank, new.rank, old.rank - new.rank); + } + + let breakdown = breakdown.to_string(); + if breakdown.len() > 0 { + if footer.len() > 0 { + footer += "| " + }; + + footer += &breakdown; + } + + if footer.len() > 0 { + embed_builder = embed_builder.footer(EmbedFooterBuilder::new(footer)); + } } let embed = embed_builder.build(); return Some(embed); } else if old.reputation > new.reputation { // reputation lost + let change = new.reputation - old.reputation; + let breakdown = calculate_rep_breakdown(change as i32); let text = format!( "[**``{}``**]({}) lost **{} reputation** and now has **{} reputation**", new.user_name, @@ -169,9 +190,25 @@ fn build_embed_data(diff: Vec, handle: &str) -> Option { let mut embed_builder = EmbedBuilder::new() .description(text) .color(models::embed_colors::NEGATIVE); - if new.rank > old.rank { - let footer = format!("#{} -> #{} (-{})", old.rank, new.rank, new.rank - old.rank); - embed_builder = embed_builder.footer(EmbedFooterBuilder::new(footer)); + + { + let mut footer = String::from(""); + if new.rank < old.rank { + footer += &format!("#{} -> #{} (-{})", old.rank, new.rank, new.rank - old.rank); + } + + let breakdown = breakdown.to_string(); + if breakdown.len() > 0 { + if footer.len() > 0 { + footer += "| " + }; + + footer += &breakdown; + } + + if footer.len() > 0 { + embed_builder = embed_builder.footer(EmbedFooterBuilder::new(footer)); + } } let embed = embed_builder.build();