diff --git a/Cargo.lock b/Cargo.lock index 243913c..61e973c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -179,6 +179,7 @@ dependencies = [ "rand 0.8.5", "regex 1.11.0", "rust-i18n", + "to_markdown_table", "tokio", "url", ] @@ -2594,6 +2595,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "to_markdown_table" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8450ade61b78735ed7811cc14639462723d87a6cd748a41e7bfde554ac5033dd" +dependencies = [ + "thiserror", +] + [[package]] name = "tokio" version = "1.40.0" diff --git a/Cargo.toml b/Cargo.toml index 5194156..b361fd3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,6 +21,7 @@ poise = { version = "0.5.*", features = [ "cache" ] } rand = { version = "0.8.5", features = [ "small_rng" ] } regex = "1.9.5" rust-i18n = "3.1.2" +to_markdown_table = "0.1.5" tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] } url = "2.5.2" diff --git a/src/badgey/commands/ranks.rs b/src/badgey/commands/ranks.rs index 87605a9..b3d3018 100644 --- a/src/badgey/commands/ranks.rs +++ b/src/badgey/commands/ranks.rs @@ -2,7 +2,8 @@ use built::chrono; use manifold::error::{ManifoldError, ManifoldResult}; use manifold::{ManifoldContext, ManifoldData}; use poise::serenity_prelude::{CreateEmbed, Mentionable, RoleId}; -use crate::badgey::models::xp::{Rank, Track, Xp}; +use to_markdown_table::MarkdownTable; +use crate::badgey::models::xp::{Leaderboard, Rank, Track, Xp}; #[poise::command(prefix_command, slash_command, user_cooldown = 86400)] async fn switch_rank_track(ctx: ManifoldContext<'_>, #[description = "Track to switch to: officer or enlisted"] track: String) -> ManifoldResult<()> { @@ -125,47 +126,34 @@ async fn leaderboard(ctx: ManifoldContext<'_>) -> ManifoldResult<()> { let reply_handle = ctx.reply("Retrieving leaderboard, please stand by...".to_string()).await?; let entries_per_page = 20; let mut pages = Vec::::new(); - let leaderboard = Xp::get_leaderboard(&ctx.data().database)?; - let total = leaderboard.len(); + let leaderboard = Leaderboard::get_leaderboard(&ctx.data().database)?; + let userinfo = ctx.data().user_info.lock().await; + let total = leaderboard.rows.len(); let pages_needed = (total / entries_per_page) + 1; for i in 0..pages_needed { let mut page = CreateEmbed::default() .title(format!("XP Leaderboard Page {page} of {total_pages}", page=(i + 1), total_pages=&pages_needed)).to_owned(); - let mut fields = Vec::new(); - let mut ranks = String::new(); - let mut users = String::new(); - let mut values = String::new(); - let offset = i*entries_per_page; + let mut leaderboard_rows = Vec::new(); - leaderboard.iter().skip(offset).enumerate().for_each(|(j, f)| { + leaderboard.rows.iter().skip(offset).enumerate().for_each(|(j, f)| { // cap at per-page limit if j >= entries_per_page { return; } - let new_rank = format!("{rank}\n", rank=(j+1+offset)); - let new_user = format!("<@{user}>\n", user=f.user_id); - let new_value = format!("{xp}\n", xp=f.xp_value); + let mut row = f.to_owned(); + row.rank = (j + 1 + offset) as i32; + row.user_name = userinfo.get(&(row.uid as u64)).unwrap().username.clone(); - // field values are capped at 1024 characters - // so we have to check if what we're about to add will break that limit and bail out if so - if (ranks.len() + new_rank.len()) > 1024 || (users.len() + new_user.len()) > 1024 || (values.len() + new_value.len()) > 1024 { - return; - } - - ranks.push_str(new_rank.as_str()); - users.push_str(new_user.as_str()); - values.push_str(new_value.as_str()); + leaderboard_rows.push(row); }); - fields.push(("Rank", &ranks, true)); - fields.push(("User", &users, true)); - fields.push(("XP", &values, true)); + let leaderboard_table = MarkdownTable::new(Some(vec!["Rank".to_string(), "User".to_string(), "XP".to_string()]), leaderboard_rows)?; - page.fields(fields); + page.description(format!("```{table}```", table=leaderboard_table)); pages.push(page); } diff --git a/src/badgey/models/xp.rs b/src/badgey/models/xp.rs index ffd13a6..4bc7283 100644 --- a/src/badgey/models/xp.rs +++ b/src/badgey/models/xp.rs @@ -3,13 +3,14 @@ use diesel::prelude::*; use diesel::insert_into; use rand::rngs::SmallRng; use rand::{Rng, SeedableRng}; +use to_markdown_table::{TableRow}; use manifold::{Db, ManifoldData}; use manifold::error::{ManifoldError, ManifoldResult}; use manifold::models::user::UserInfo; use manifold::schema::userinfo; use poise::FrameworkContext; -use poise::serenity_prelude::{Context, Mentionable, Message, RoleId}; +use poise::serenity_prelude::{Context, Mention, Mentionable, Message, RoleId, UserId}; use crate::badgey::models::quarantine_channel::QuarantineChannel; use crate::badgey::schema::xp as xp_table; use crate::badgey::schema::*; @@ -49,6 +50,47 @@ pub struct Track { pub xp_award_range_max: i32, } +pub struct Leaderboard { + pub rows: Vec +} + +#[derive(Clone)] +pub struct LeaderboardRow { + pub rank: i32, + pub uid: i64, + pub user_name: String, + pub value: i64, +} + +impl Into for LeaderboardRow { + fn into(self) -> TableRow { + TableRow::new(vec![self.rank.to_string(), self.user_name, self.value.to_string()]) + } +} + +impl From<&Xp> for LeaderboardRow { + fn from(value: &Xp) -> Self { + LeaderboardRow { + rank: 0, + uid: value.user_id, + user_name: "".to_string(), + value: value.xp_value, + } + } +} + +impl Leaderboard { + pub fn get_leaderboard(conn: &Db) -> ManifoldResult { + let xp_rows: Vec = xp_table::dsl::xp + .order_by(xp::xp_value.desc()) + .load::(&mut conn.get()?)?.iter().map(|x| {LeaderboardRow::from(x)}).collect(); + + Ok(Leaderboard { + rows: xp_rows + }) + } +} + impl Xp { pub fn new(user_id: &i64) -> Self { Self { @@ -119,12 +161,6 @@ impl Xp { Ok(()) } - pub fn get_leaderboard(conn: &Db) -> ManifoldResult> { - Ok(xp_table::dsl::xp - .order_by(xp::xp_value.desc()) - .load::(&mut conn.get()?)?) - } - pub async fn award(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, msg: &Message, db: &Db) -> ManifoldResult<()>{ if fctx.user_data().await.user_info.lock().await.get_mut(&msg.author.id.as_u64()).is_none() { debug!("Tried to add XP to a user we don't know about, aborting.");