diff --git a/src/commands/challenge.rs b/src/commands/challenge.rs index b105d72..6d8e116 100644 --- a/src/commands/challenge.rs +++ b/src/commands/challenge.rs @@ -34,7 +34,11 @@ pub async fn challenge(ctx: Context<'_>) -> Result<(), Error> { broadcast_typing, description_localized("en-US", "Submit to the latest handwriting challenge.") )] -pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Result<(), Error> { +pub async fn submit( + ctx: Context<'_>, + challenge: Option, + submission: serenity::Attachment +) -> Result<(), Error> { // TODO: The code for this command needs to be refactored, // there are large duplicated sections that need to be merged somehow. match ctx { @@ -71,16 +75,30 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul .await?; return Ok(()); } - - let challenge_number = get_challenge_number(); - let submission_images_dir = get_submission_images_dir(); + let latest_challenge = get_challenge_number(); + let late; + let challenge_number = match challenge { + Some(challenge) => { + if challenge > latest_challenge { + ctx.say(format!("That challenge doesn't exist, the latest challenge is #{latest_challenge}.")).await?; + return Ok(()); + }; + late = challenge < latest_challenge; + challenge + }, + None => { + late = false; + get_challenge_number() + }, + }; + let submission_images_dir = get_submission_images_dir(challenge_number); let timestamp = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_millis(); // Ensure that submission_images_dir exists let path = Path::new(&submission_images_dir); std::fs::create_dir_all(path)?; - let mut submission_data = get_current_submission_data(); + let mut submission_data = get_submission_data(challenge_number); let mut existing_submitter = false; let mut invalid_types = false; let mut requires_rebuild = false; @@ -92,7 +110,7 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul let author = ctx.author(); let mut submitted_images = Vec::new(); for submission in submission_data.iter_mut() { - if is_matching_submission(&submission, author) { + if is_matching_submission(&submission, author) && late == (submission.as_object().unwrap().contains_key("late") && submission["late"].as_bool().unwrap()) { existing_submitter = true; let mut images: Vec = submission["images"].as_array_mut().unwrap().clone().iter().map(|value| value.as_str().unwrap().to_owned()).collect(); for (i, attachment) in attachments.iter().enumerate() { @@ -173,9 +191,12 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul } submitter_data.insert(String::from("images"), images.into()); submitter_data.insert(String::from("id"), author.id.as_u64().to_string().into()); + if late { + submitter_data.insert(String::from("late"), true.into()); + } submission_data.push(submitter_data.into()); } - set_submission_data(submission_data); + set_submission_data(challenge_number, submission_data); let mut message = String::new(); if requires_rebuild { let thank_you = &format!("Thank you for submitting! You can view your submission at ", challenge_number); @@ -206,7 +227,7 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul for image in submitted_images.iter() { for (other_guild_id, data) in guild_data.iter() { let here = other_guild_id.eq(&ctx.guild_id().unwrap().as_u64().to_string()); - if !repost_here && here { + if (late && !here) || (!repost_here && here) { continue; } let data = data.as_object().unwrap(); @@ -230,8 +251,7 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul channel.send_message(&ctx.discord().http, |m| { m.embed(|e| { let username = format!("{}#{}", author.name, author.discriminator); - let n = get_challenge_number(); - let mut description = format!("New submission to [Tegaki Tuesday #{n}](https://tegakituesday.com/{n})!"); + let mut description = format!("New {}submission to [Tegaki Tuesday #{n}](https://tegakituesday.com/{n})!", if late { "late " } else { "" }, n = challenge_number); if !here { description.push_str(&if let Some(invite) = invite { format!("\nCrossposted from [{}](https://discord.gg/{invite})", guild.name) @@ -246,7 +266,7 @@ pub async fn submit(ctx: Context<'_>, submission: serenity::Attachment) -> Resul .name(username) .url(format!("https://discord.com/users/{}", author.id)); e.set_author(embed_author); - e.image(format!("https://tegakituesday.com/{n}/{image}#{timestamp}")); + e.image(format!("https://tegakituesday.com/{challenge_number}/{image}#{timestamp}")); if let Some(accent_color) = accent_color { e.color(accent_color); } @@ -358,7 +378,7 @@ pub async fn imagedelete(ctx: Context<'_>, number: i32) -> Result<(), Error> { } let index = number as usize - 1; let image = images[index].as_str().unwrap().to_owned(); - let submission_images_dir = get_submission_images_dir(); + let submission_images_dir = get_submission_images_dir(challenge_number); let image_path = format!("{}/{}", submission_images_dir, image); match fs::remove_file(image_path) { Ok(_) => (), @@ -374,7 +394,7 @@ pub async fn imagedelete(ctx: Context<'_>, number: i32) -> Result<(), Error> { images.remove(index); submission["images"] = images.into(); } - set_submission_data(submission_data); + set_submission_data(challenge_number, submission_data); rebuild_site(); ctx.say(message).await?; return Ok(()); @@ -387,7 +407,6 @@ pub async fn imagedelete(ctx: Context<'_>, number: i32) -> Result<(), Error> { Ok(()) } -// TODO: make also slash command #[command( prefix_command, slash_command, diff --git a/src/utils.rs b/src/utils.rs index a1994df..cf5ffc4 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -38,18 +38,14 @@ pub fn get_hugo_path() -> String { env::var("HUGO").unwrap() } -pub fn get_submission_images_dir() -> String { - format!("{}/assets/{}", get_hugo_path(), get_challenge_number()) +pub fn get_submission_images_dir(challenge: i32) -> String { + format!("{}/assets/{challenge}", get_hugo_path()) } pub fn get_submission_data_path(challenge: i32) -> String { format!("{}/data/challenges/{}.json", get_hugo_path(), challenge) } -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()) } @@ -74,12 +70,12 @@ pub fn get_submission_data(challenge: i32) -> Vec { submission_data.as_array_mut().unwrap().clone() } -pub fn set_submission_data(submission_data: Vec) { +pub fn set_submission_data(challenge: i32, submission_data: Vec) { let submission_data: Value = submission_data.into(); let mut submission_data_file = OpenOptions::new() .write(true) .truncate(true) - .open(get_current_submission_data_path()) + .open(get_submission_data_path(challenge)) .unwrap(); submission_data_file .write_all( @@ -359,9 +355,14 @@ pub async fn leaderboard(ctx: &Context<'_>) -> Result<(), Error> { for challenge in 1..get_challenge_number() + 1 { let submission_data = get_submission_data(challenge); for submission in submission_data.iter() { + let submission = submission.as_object().unwrap(); + if submission.contains_key("late") && submission["late"].as_bool().unwrap() { + // Don't count late submissions toward leaderboard + continue; + } let user = submission_counts .entry(String::from( - submission.as_object().unwrap()["id"].as_str().unwrap(), + submission["id"].as_str().unwrap(), )) .or_insert(0); *user += 1; @@ -384,6 +385,7 @@ pub async fn leaderboard(ctx: &Context<'_>) -> Result<(), Error> { leaderboard_html.push_str(""); let mut file = std::fs::OpenOptions::new() .create(true) + .truncate(true) // potential fix for trailing >table> after leaderboard .write(true) .open(env::var("LEADERBOARD").unwrap()) .unwrap();