From fc9a9a9d49baa8e088905f79af55434224d4bf23 Mon Sep 17 00:00:00 2001 From: ElnuDev Date: Wed, 16 Nov 2022 21:41:17 -0800 Subject: [PATCH] Improve submission command with slash command, embeds, crossposting --- src/commands/challenge.rs | 139 ++++++++++++++++++++++++++++++-------- src/utils.rs | 12 ++-- 2 files changed, 117 insertions(+), 34 deletions(-) diff --git a/src/commands/challenge.rs b/src/commands/challenge.rs index 03d0149..396719d 100644 --- a/src/commands/challenge.rs +++ b/src/commands/challenge.rs @@ -29,34 +29,42 @@ pub async fn challenge(ctx: Context<'_>) -> Result<(), Error> { #[command( prefix_command, + slash_command, + ephemeral, broadcast_typing, description_localized("en-US", "Submit to the latest handwriting challenge.") )] -pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { +pub async fn submit( + ctx: Context<'_>, + 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 { + Context::Application(_) => { ctx.defer_ephemeral().await? }, + Context::Prefix(ctx) => if ctx.msg.attachments.len() == 0 { + ctx.msg + .reply(&ctx.discord.http, "Please attach at least one image.") + .await?; + return Ok(()); + }, + } let guild_data = get_guild_data(); - let guild = ctx.msg.guild_id.unwrap().as_u64().to_string(); + let guild = ctx.guild_id().unwrap().as_u64().to_string(); if !guild_data.contains_key(&guild) || !&guild_data[&guild] .as_object() .unwrap() .contains_key("submissionChannel") { - ctx.msg - .reply( - &ctx.discord.http, - "Submissions aren't enabled for this server yet.", - ) - .await?; + ctx.say("Submissions aren't enabled for this server yet.").await?; return Ok(()); } let current_guild_data = &guild_data[&guild].as_object().unwrap(); let submission_channel = current_guild_data["submissionChannel"].as_str().unwrap(); - if submission_channel != ctx.msg.channel_id.as_u64().to_string() { - ctx.msg - .reply( - &ctx.discord.http, + if submission_channel != ctx.channel_id().as_u64().to_string() { + ctx.defer_ephemeral().await?; + ctx.say( format!( "Sorry, submissions aren't permitted here. Please go to <#{}>. Thanks!", guild @@ -65,12 +73,7 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { .await?; return Ok(()); } - if ctx.msg.attachments.len() == 0 { - ctx.msg - .reply(&ctx.discord.http, "Please attach at least one image.") - .await?; - return Ok(()); - } + let challenge_number = get_challenge_number(); let submission_images_dir = get_submission_images_dir(); @@ -82,11 +85,17 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { let mut existing_submitter = false; let mut invalid_types = false; let mut requires_rebuild = false; + let attachments = vec![submission]; + let attachments = match ctx { + Context::Application(_) => &attachments, + Context::Prefix(ctx) => &ctx.msg.attachments, + }; + let author = ctx.author(); for (i, submission) in submission_data.iter_mut().enumerate() { - if is_matching_submission(&submission, &ctx.msg.author) { + if is_matching_submission(&submission, author) { existing_submitter = true; let mut images = submission["images"].as_array_mut().unwrap().clone(); - for attachment in ctx.msg.attachments.iter() { + for attachment in attachments.iter() { let extension; if let Some(content_type) = &attachment.content_type { if content_type == "image/png" { @@ -105,8 +114,8 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { let file_name = format!( "{}-{}-{}-{}.{}", i + 1, - slugify(&ctx.msg.author.name), - ctx.msg.author.discriminator, + slugify(&author.name), + author.discriminator, images.len() + 1, extension ); @@ -125,10 +134,10 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { let mut submitter_data = Map::new(); submitter_data.insert( String::from("username"), - format!("{}#{}", ctx.msg.author.name, ctx.msg.author.discriminator).into(), + format!("{}#{}", author.name, author.discriminator).into(), ); let mut images: Vec = Vec::new(); - for attachment in ctx.msg.attachments.iter() { + for attachment in attachments.iter() { let extension; if let Some(content_type) = &attachment.content_type { if content_type == "image/png" { @@ -147,8 +156,8 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { let file_name = format!( "{}-{}-{}{}.{}", submission_data.len() + 1, - slugify(&ctx.msg.author.name), - ctx.msg.author.discriminator, + slugify(&author.name), + author.discriminator, if images.len() == 0 { String::from("") } else { @@ -165,23 +174,95 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> { submitter_data.insert(String::from("images"), images.into()); submitter_data.insert( String::from("id"), - ctx.msg.author.id.as_u64().to_string().into(), + author.id.as_u64().to_string().into(), ); submission_data.push(submitter_data.into()); } set_submission_data(submission_data); let mut message = String::new(); if requires_rebuild { - message.push_str(&format!("Thank you for submitting! You can view your submission at ", challenge_number)); + let thank_you = &format!("Thank you for submitting! You can view your submission at ", challenge_number); + let mut repost_here = true; + match ctx { + Context::Application(_) => message.push_str(thank_you), + Context::Prefix(ctx) => match ctx.msg.delete(&ctx.discord.http).await { + Ok(_) => {}, + // don't repost image on this server if no manage messages perm + // (if we can't delete user's image it'll be displayed twice) + Err(_) => { + repost_here = false; + message.push_str(thank_you); + } + }, + }; 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(); + for attachment in attachments.iter() { + for (guild, data) in guild_data.iter() { + let here = guild.eq(&ctx.guild_id().unwrap().as_u64().to_string()); + if !repost_here && here { + continue; + } + let data = data.as_object().unwrap(); + if !data.contains_key("submissionChannel") { + continue; + } + let channel = serenity::ChannelId( + data["submissionChannel"] + .as_str() + .unwrap() + .parse::() + .unwrap(), + ); + let invite = if data.contains_key("invite") { + Some(data["invite"].as_str().unwrap()) + } else { None }; + let accent_color = ctx + .discord() + .http + .get_user(*author.id.as_u64()) + .await + .unwrap() + .accent_colour; + 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})!"); + if !here { + let guild = ctx.guild().unwrap(); + description.push_str(&if let Some(invite) = invite { + format!("\nCrossposted from [{}](https://discord.gg/{invite})", guild.name) + } else { + format!("\nCrossposted from {}", guild.name) + }); + }; + e.description(description); + let mut embed_author = serenity::builder::CreateEmbedAuthor::default(); + embed_author + .icon_url(get_avatar(&author)) + .name(username) + .url(format!("https://discord.com/users/{}", author.id)); + e.set_author(embed_author); + e.image(&attachment.url); + if let Some(accent_color) = accent_color { + e.color(accent_color); + } + e + }); + m + }).await?; + }; + }; } else if invalid_types { message.push_str("Sorry, your submission could not be uploaded; only **.png**, **.jpg**, and **.jpeg** files are permitted."); } - ctx.msg.reply(&ctx.discord.http, message).await?; + if !message.is_empty() { + ctx.say(message).await?; + } Ok(()) } diff --git a/src/utils.rs b/src/utils.rs index e7dd5ed..9c60856 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,6 +1,6 @@ use crate::serenity; use crate::Error; -use crate::{Context, PrefixContext}; +use crate::Context; use rand::seq::IteratorRandom; use serde_json::Map; use serde_json::Value; @@ -154,7 +154,7 @@ pub fn set_guild_data(guild_data: Map) { pub async fn send(ctx: Context<'_>, message: &str, ping: bool, pin: bool) -> Result<(), Error> { let guild_data = get_guild_data(); - let mut announcements_count = 0; + // let mut announcements_count = 0; for (_guild, data) in guild_data.iter() { let data = data.as_object().unwrap(); if !data.contains_key("submissionChannel") { @@ -191,14 +191,16 @@ pub async fn send(ctx: Context<'_>, message: &str, ping: bool, pin: bool) -> Res Err(_) => (), }; } - announcements_count += 1; + // announcements_count += 1; } + /* ctx.say(format!( "Announced to {} server{}!", announcements_count, if announcements_count == 1 { "" } else { "s" } )) .await?; + */ Ok(()) } @@ -351,7 +353,7 @@ pub fn get_avatar(user: &serenity::User) -> String { } } -pub async fn leaderboard(ctx: &PrefixContext<'_>) -> Result<(), Error> { +pub async fn leaderboard(ctx: &Context<'_>) -> Result<(), Error> { const LENGTH: usize = 10; let mut submission_counts: HashMap = HashMap::new(); for challenge in 1..get_challenge_number() + 1 { @@ -371,7 +373,7 @@ pub async fn leaderboard(ctx: &PrefixContext<'_>) -> Result<(), Error> { for (i, (id, count)) in top_submitters[0..LENGTH].iter().enumerate() { let place = i + 1; let user = serenity::UserId(id.parse::().unwrap()) - .to_user(&ctx.discord.http) + .to_user(&ctx.discord().http) .await?; let avatar = get_avatar(&user); let profile = format!("https://discord.com/users/{id}");