diff --git a/.env.example b/.env.example index 72f2def..4e67ce9 100644 --- a/.env.example +++ b/.env.example @@ -1,4 +1,5 @@ DISCORD_TOKEN= PREFIX="-h " HUGO=/path/to/hugo -GUILD_DATA=/path/to/guilds.json \ No newline at end of file +GUILD_DATA=/path/to/guilds.json +LEADERBOARD=/path/to/leaderboard.html \ No newline at end of file diff --git a/src/commands/challenge.rs b/src/commands/challenge.rs index fef8f13..c0ebd59 100644 --- a/src/commands/challenge.rs +++ b/src/commands/challenge.rs @@ -66,7 +66,7 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult { let path = Path::new(&submission_images_dir); std::fs::create_dir_all(path)?; - let mut submission_data = get_submission_data(); + let mut submission_data = get_current_submission_data(); let mut existing_submitter = false; let mut invalid_types = false; let mut requires_rebuild = false; @@ -164,6 +164,7 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult { if invalid_types { message.push_str("\nSome of your attachments could not be uploaded; only **.png**, **.jpg**, and **.jpeg** files are permitted."); } + leaderboard(&ctx).await?; rebuild_site(); } else if invalid_types { message.push_str("Sorry, your submission could not be uploaded; only **.png**, **.jpg**, and **.jpeg** files are permitted."); @@ -175,7 +176,7 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult { #[command] async fn images(ctx: &Context, msg: &Message) -> CommandResult { - let submission_data = get_submission_data(); + let submission_data = get_current_submission_data(); let images: Vec = { let mut images = Vec::new(); for submission in submission_data.iter() { @@ -237,7 +238,7 @@ async fn imageDelete(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes return Ok(()); } let challenge_number = get_challenge_number(); - let mut submission_data = get_submission_data(); + let mut submission_data = get_current_submission_data(); for (i, submission) in submission_data.iter_mut().enumerate() { if !is_matching_submission(&submission, &msg) { continue; @@ -366,11 +367,7 @@ async fn suggest(ctx: &Context, msg: &Message, args: Args) -> CommandResult { )); let mut author = serenity::builder::CreateEmbedAuthor::default(); author - .icon_url(format!( - "https://cdn.discordapp.com/avatars/{}/{}.webp", - msg.author.id, - msg.author.avatar.as_ref().unwrap() - )) + .icon_url(get_avatar(&msg.author)) .name(username) .url(format!("https://discord.com/users/{}", msg.author.id)); e.set_author(author); @@ -385,4 +382,4 @@ async fn suggest(ctx: &Context, msg: &Message, args: Args) -> CommandResult { .unwrap(); msg.reply(&ctx.http, "Suggestion sent! Thank you for making a suggestion. If it is chosen to be used in a future challenge, you will be mentioned in the challenge description!").await?; Ok(()) -} +} \ No newline at end of file diff --git a/src/utils.rs b/src/utils.rs index 56ffbf4..32e3d0e 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -11,6 +11,7 @@ use std::fs::OpenOptions; use std::io::Read; use std::io::Write; use std::process::Command; +use std::collections::HashMap; pub fn get_challenge_number() -> i32 { let challenge_dir = format!("{}/content/challenges", env::var("HUGO").unwrap()); @@ -41,16 +42,24 @@ pub fn get_submission_images_dir() -> String { format!("{}/assets/{}", get_hugo_path(), get_challenge_number()) } -pub fn get_submission_data_path() -> String { +pub fn get_submission_data_path(challenge: i32) -> String { format!( "{}/data/challenges/{}.json", get_hugo_path(), - get_challenge_number() + challenge ) } -pub fn get_submission_data() -> Vec { - let submission_data_path = get_submission_data_path(); +pub fn get_current_submission_data_path() -> String { + get_submission_data_path(get_challenge_number()) +} + +pub fn get_current_submission_data() -> Vec { + get_submission_data(get_challenge_number()) +} + +pub fn get_submission_data(challenge: i32) -> Vec { + let submission_data_path = get_submission_data_path(challenge); let submission_data_json = match File::open(&submission_data_path) { Ok(mut file) => { let mut json = String::new(); @@ -74,7 +83,7 @@ pub fn set_submission_data(submission_data: Vec) { let mut submission_data_file = OpenOptions::new() .write(true) .truncate(true) - .open(get_submission_data_path()) + .open(get_current_submission_data_path()) .unwrap(); submission_data_file .write_all( @@ -364,3 +373,42 @@ pub async fn random_kanji( } Ok(()) } + +pub fn get_avatar(user: &User) -> String { + match user.avatar_url() { + Some(avatar_url) => avatar_url, + None => user.default_avatar_url() + } +} + +pub async fn leaderboard(ctx: &Context) -> CommandResult { + const LENGTH: usize = 5; + let mut submission_counts: HashMap = HashMap::new(); + for challenge in 1..get_challenge_number() + 1 { + let submission_data = get_submission_data(challenge); + for submission in submission_data.iter() { + let user = submission_counts.entry(String::from(submission.as_object().unwrap()["id"].as_str().unwrap())).or_insert(0); + *user += 1; + } + } + let mut top_submitters: Vec<(&String, &u32)> = submission_counts.iter().collect(); + top_submitters.sort_by(|a, b| b.1.cmp(a.1)); + let mut leaderboard_html = String::from(""); + for (i, (id, count)) in top_submitters[0..LENGTH].iter().enumerate() { + let place = i + 1; + let user = UserId(id.parse::().unwrap()).to_user(&ctx.http).await?; + let avatar = get_avatar(&user); + let profile = format!("https://discord.com/users/{id}"); + let name = user.name; + let discriminator = user.discriminator; + leaderboard_html.push_str(&format!("")); + } + leaderboard_html.push_str("
{place}\"avatar\"{name}#{discriminator}{count}
"); + let mut file = std::fs::OpenOptions::new() + .write(true) + .open(env::var("LEADERBOARD").unwrap()) + .unwrap(); + file.write_all(leaderboard_html.as_bytes())?; + file.flush()?; + Ok(()) +} \ No newline at end of file