From a1d453cffffe62fba93591827c7d23b76a0da897 Mon Sep 17 00:00:00 2001 From: Natsu Kagami Date: Mon, 17 Feb 2020 14:12:34 -0500 Subject: [PATCH] Implement contestranks --- youmubot-cf/src/lib.rs | 135 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/youmubot-cf/src/lib.rs b/youmubot-cf/src/lib.rs index 9eb5a64..73209e5 100644 --- a/youmubot-cf/src/lib.rs +++ b/youmubot-cf/src/lib.rs @@ -1,3 +1,4 @@ +use codeforces::Contest; use serenity::{ framework::standard::{ macros::{command, group}, @@ -6,6 +7,7 @@ use serenity::{ model::channel::Message, utils::MessageBuilder, }; +use std::{collections::HashMap, time::Duration}; use youmubot_prelude::*; mod announcer; @@ -31,7 +33,7 @@ pub fn setup(path: &std::path::Path, data: &mut ShareMap, announcers: &mut Annou #[group] #[prefix = "cf"] #[description = "Codeforces-related commands"] -#[commands(profile, save, ranks, watch)] +#[commands(profile, save, ranks, watch, contestranks)] #[default_command(profile)] pub struct Codeforces; @@ -209,6 +211,137 @@ pub fn ranks(ctx: &mut Context, m: &Message) -> CommandResult { Ok(()) } +#[command] +#[description = "See the server ranks on a certain contest"] +#[usage = "[the contest id]"] +#[num_args(1)] +#[only_in(guilds)] +pub fn contestranks(ctx: &mut Context, m: &Message, mut args: Args) -> CommandResult { + let contest_id: u64 = args.single()?; + let guild = m.guild_id.unwrap(); // Guild-only command + let members = CfSavedUsers::open(&*ctx.data.read()).borrow()?.clone(); + let members = members + .into_iter() + .filter_map(|(user_id, cf_user)| { + guild + .member(&ctx, user_id) + .ok() + .map(|v| (cf_user.handle, v)) + }) + .collect::>(); + let http = ctx.data.get_cloned::(); + let (contest, problems, ranks) = Contest::standings(&http, contest_id, |f| { + f.handles(members.iter().map(|(k, _)| k.clone()).collect()) + })?; + + // Table me + let ranks = ranks + .iter() + .flat_map(|v| { + v.party + .members + .iter() + .filter_map(|m| members.get(&m.handle).map(|mem| (mem, m.handle.clone(), v))) + .collect::>() + }) + .collect::>(); + + if ranks.is_empty() { + m.reply(&ctx, "No one in this server participated in the contest...")?; + return Ok(()); + } + + const ITEMS_PER_PAGE: usize = 10; + let total_pages = (ranks.len() + ITEMS_PER_PAGE - 1) / ITEMS_PER_PAGE; + + ctx.data.get_cloned::().paginate_fn( + ctx.clone(), + m.channel_id, + move |page, e| { + let page = page as usize; + let start = page * ITEMS_PER_PAGE; + let end = total_pages.min(start + ITEMS_PER_PAGE); + if start >= end { + return (e, Err(Error::from("no more pages to show"))); + } + let ranks = &ranks[start..end]; + let hw = ranks + .iter() + .map(|(mem, handle, _)| format!("{} ({})", handle, mem.distinct()).len()) + .max() + .unwrap_or(0) + .max(6); + let hackw = ranks + .iter() + .map(|(_, _, row)| { + format!( + "{}/{}", + row.successful_hack_count, row.unsuccessful_hack_count + ) + .len() + }) + .max() + .unwrap_or(0) + .max(5); + + let mut table = MessageBuilder::new(); + let mut header = MessageBuilder::new(); + // Header + header.push(format!( + " Rank | {:hw$} | Total | {:hackw$}", + "Handle", + "Hacks", + hw = hw, + hackw = hackw + )); + for p in &problems { + header.push(format!(" | {:4}", p.index)); + } + let header = header.build(); + table + .push_line(&header) + .push_line(format!("{:-5} | {:5.0} | {: 0.0 { + table.push(format!("{:^4.0}", p.points)); + } else if let Some(_) = p.best_submission_time_seconds { + table.push(format!("{:^4}", "?")); + } else if p.rejected_attempt_count > 0 { + table.push(format!("{:^4}", format!("-{}", p.rejected_attempt_count))); + } else { + table.push(format!("{:^4}", "")); + } + } + } + + let mut m = MessageBuilder::new(); + m.push_bold_safe(&contest.name) + .push(" ") + .push_line(contest.url()) + .push_codeblock(table.build(), None) + .push_line(format!("Page **{}/{}**", page + 1, total_pages)); + (e.content(m.build()), Ok(())) + }, + Duration::from_secs(60), + ) +} + #[command] #[description = "Watch a contest and announce any change on the members of the server assigned to the contest."] #[usage = "[the contest id]"]