late-submissions
Elnu 2 years ago
parent 9e9eb48025
commit 6aff34ceab

@ -12,19 +12,25 @@ use std::fs::File;
use std::io::Write; use std::io::Write;
use std::path::Path; use std::path::Path;
#[command(prefix_command, slash_command, description_localized("en-US", "View the latest handwriting challenge info."))] #[command(
prefix_command,
slash_command,
description_localized("en-US", "View the latest handwriting challenge info.")
)]
pub async fn challenge(ctx: Context<'_>) -> Result<(), Error> { pub async fn challenge(ctx: Context<'_>) -> Result<(), Error> {
ctx.say( ctx.say(format!(
format!( "Tegaki Tuesday #{n}: <https://tegakituesday.com/{n}>",
"Tegaki Tuesday #{n}: <https://tegakituesday.com/{n}>", n = get_challenge_number()
n = get_challenge_number() ))
),
)
.await?; .await?;
Ok(()) Ok(())
} }
#[command(prefix_command, broadcast_typing, description_localized("en-US", "Submit to the latest handwriting challenge."))] #[command(
prefix_command,
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: PrefixContext<'_>) -> Result<(), Error> {
// TODO: The code for this command needs to be refactored, // TODO: The code for this command needs to be refactored,
// there are large duplicated sections that need to be merged somehow. // there are large duplicated sections that need to be merged somehow.
@ -36,24 +42,32 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> {
.unwrap() .unwrap()
.contains_key("submissionChannel") .contains_key("submissionChannel")
{ {
ctx.msg.reply(&ctx.discord.http, "Submissions aren't enabled for this server yet.").await?; ctx.msg
.reply(
&ctx.discord.http,
"Submissions aren't enabled for this server yet.",
)
.await?;
return Ok(()); return Ok(());
} }
let current_guild_data = &guild_data[&guild].as_object().unwrap(); let current_guild_data = &guild_data[&guild].as_object().unwrap();
let submission_channel = current_guild_data["submissionChannel"].as_str().unwrap(); let submission_channel = current_guild_data["submissionChannel"].as_str().unwrap();
if submission_channel != ctx.msg.channel_id.as_u64().to_string() { if submission_channel != ctx.msg.channel_id.as_u64().to_string() {
ctx.msg.reply( ctx.msg
&ctx.discord.http, .reply(
format!( &ctx.discord.http,
"Sorry, submissions aren't permitted here. Please go to <#{}>. Thanks!", format!(
guild "Sorry, submissions aren't permitted here. Please go to <#{}>. Thanks!",
), guild
) ),
.await?; )
.await?;
return Ok(()); return Ok(());
} }
if ctx.msg.attachments.len() == 0 { if ctx.msg.attachments.len() == 0 {
ctx.msg.reply(&ctx.discord.http, "Please attach at least one image.").await?; ctx.msg
.reply(&ctx.discord.http, "Please attach at least one image.")
.await?;
return Ok(()); return Ok(());
} }
let challenge_number = get_challenge_number(); let challenge_number = get_challenge_number();
@ -170,7 +184,11 @@ pub async fn submit(ctx: PrefixContext<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[command(prefix_command, slash_command, description_localized("en-US", "List images in your current submission, if available."))] #[command(
prefix_command,
slash_command,
description_localized("en-US", "List images in your current submission, if available.")
)]
pub async fn images(ctx: Context<'_>) -> Result<(), Error> { pub async fn images(ctx: Context<'_>) -> Result<(), Error> {
let submission_data = get_current_submission_data(); let submission_data = get_current_submission_data();
let images: Vec<String> = { let images: Vec<String> = {
@ -187,12 +205,10 @@ pub async fn images(ctx: Context<'_>) -> Result<(), Error> {
}; };
let challenge_number = get_challenge_number(); let challenge_number = get_challenge_number();
if images.len() == 0 { if images.len() == 0 {
ctx.say( ctx.say(format!(
format!( "You haven't submitted anything for Tegaki Tuesday #{}.",
"You haven't submitted anything for Tegaki Tuesday #{}.", challenge_number
challenge_number ))
),
)
.await?; .await?;
return Ok(()); return Ok(());
} }
@ -212,13 +228,18 @@ pub async fn images(ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
#[command(prefix_command, slash_command, description_localized("en-US", "Delete images from your current submission using image numbers from the images command."))] #[command(
pub async fn imagedelete( prefix_command,
ctx: Context<'_>, slash_command,
number: i32, description_localized(
) -> Result<(), Error> { "en-US",
"Delete images from your current submission using image numbers from the images command."
)
)]
pub async fn imagedelete(ctx: Context<'_>, number: i32) -> Result<(), Error> {
if number < 1 { if number < 1 {
ctx.say("That isn't a valid image number. Image numbers start at 1.").await?; ctx.say("That isn't a valid image number. Image numbers start at 1.")
.await?;
return Ok(()); return Ok(());
} }
let challenge_number = get_challenge_number(); let challenge_number = get_challenge_number();
@ -230,23 +251,21 @@ pub async fn imagedelete(
let mut images = submission["images"].as_array().unwrap().clone(); let mut images = submission["images"].as_array().unwrap().clone();
let image_count = images.len(); let image_count = images.len();
if image_count < number.try_into().unwrap() { if image_count < number.try_into().unwrap() {
ctx.say( ctx.say(if image_count == 0 {
if image_count == 0 { // This is an edge case that should never happen.
// This is an edge case that should never happen. // In this scenario, there is submission data with an empty image list.
// In this scenario, there is submission data with an empty image list. // Submission data should be deleted uppon imageDelete if there are no images remaining.
// Submission data should be deleted uppon imageDelete if there are no images remaining. format!(
format!( "You haven't submitted anything for Tegaki Tuesday #{}.",
"You haven't submitted anything for Tegaki Tuesday #{}.", challenge_number
challenge_number )
) } else {
} else { format!(
format!( "That image number doesn't exist, you only have {} image{}.",
"That image number doesn't exist, you only have {} image{}.", image_count,
image_count, if image_count == 1 { "" } else { "s" }
if image_count == 1 { "" } else { "s" } )
) })
},
)
.await?; .await?;
return Ok(()); return Ok(());
} }
@ -304,22 +323,22 @@ pub async fn imagedelete(
ctx.say(message).await?; ctx.say(message).await?;
return Ok(()); return Ok(());
} }
ctx.say( ctx.say(format!(
format!( "You haven't submitted anything for Tegaki Tuesday #{}.",
"You haven't submitted anything for Tegaki Tuesday #{}.", challenge_number
challenge_number ))
),
)
.await?; .await?;
Ok(()) Ok(())
} }
// TODO: make also slash command // TODO: make also slash command
#[command(prefix_command, description_localized("en-US", "Make a suggestion for future challenge prompts!"))] #[command(
prefix_command,
description_localized("en-US", "Make a suggestion for future challenge prompts!")
)]
pub async fn suggest( pub async fn suggest(
ctx: PrefixContext<'_>, ctx: PrefixContext<'_>,
#[description = "Suggestion text. Please include passage and source."] #[description = "Suggestion text. Please include passage and source."] suggestion: String,
suggestion: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
let guild_data = get_guild_data(); let guild_data = get_guild_data();
let channel = serenity::ChannelId( let channel = serenity::ChannelId(
@ -332,7 +351,9 @@ pub async fn suggest(
// User::accent_colour is only available via the REST API // User::accent_colour is only available via the REST API
// If we just do msg.author.accent_colour here, we will get None // If we just do msg.author.accent_colour here, we will get None
let author = &ctx.msg.author; let author = &ctx.msg.author;
let accent_color = ctx.discord.http let accent_color = ctx
.discord
.http
.get_user(*author.id.as_u64()) .get_user(*author.id.as_u64())
.await .await
.unwrap() .unwrap()

@ -1,15 +1,14 @@
use crate::utils::*; use crate::utils::*;
use crate::serenity; use crate::serenity;
use crate::Error;
use crate::Context; use crate::Context;
use crate::Error;
use poise::command; use poise::command;
#[command(slash_command, prefix_command, description_localized("en-US", "Get category info and links to Jisho for character(s), with stroke order for single characters."))] #[command(slash_command, prefix_command, description_localized("en-US", "Get category info and links to Jisho for character(s), with stroke order for single characters."))]
pub async fn i( pub async fn i(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Input kanji to get info for"] #[description = "Input kanji to get info for"] input: String,
input: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
let chars = input.chars(); let chars = input.chars();
let mut message = String::from(""); let mut message = String::from("");
@ -46,47 +45,55 @@ pub async fn i(
)); ));
} }
ctx.send(|m| { ctx.send(|m| {
m.allowed_mentions(|am| { m.allowed_mentions(|am| {
am.empty_parse(); am.empty_parse();
am am
}); });
m.embed(|e| { m.embed(|e| {
e.title(input); e.title(input);
e.description(message); e.description(message);
let mut author = serenity::builder::CreateEmbedAuthor::default(); let mut author = serenity::builder::CreateEmbedAuthor::default();
author author
.icon_url("https://raw.githubusercontent.com/ElnuDev/ji-chan/main/ji-chan.png") .icon_url("https://raw.githubusercontent.com/ElnuDev/ji-chan/main/ji-chan.png")
.name("字ちゃん") .name("字ちゃん")
.url("https://github.com/ElnuDev/ji-chan"); .url("https://github.com/ElnuDev/ji-chan");
e.set_author(author); e.set_author(author);
e.color(serenity::utils::Colour::from_rgb(251, 73, 52)); e.color(serenity::utils::Colour::from_rgb(251, 73, 52));
if found_chars.len() == 1 { if found_chars.len() == 1 {
e.thumbnail(get_so_diagram(found_chars[0])); e.thumbnail(get_so_diagram(found_chars[0]));
} }
e e
}); });
m m
}) })
.await .await
.unwrap(); .unwrap();
Ok(()) Ok(())
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Random Jōyō kanji"))] #[command(
pub async fn joyo( slash_command,
ctx: Context<'_>, prefix_command,
) -> Result<(), Error> { description_localized("en-US", "Random Jōyō kanji")
)]
pub async fn joyo(ctx: Context<'_>) -> Result<(), Error> {
random_kanji("JOYO", ctx, None).await random_kanji("JOYO", ctx, None).await
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Random Jinmeiyō kanji"))] #[command(
pub async fn jinmeiyo( slash_command,
ctx: Context<'_>, prefix_command,
)-> Result<(), Error> { description_localized("en-US", "Random Jinmeiyō kanji")
)]
pub async fn jinmeiyo(ctx: Context<'_>) -> Result<(), Error> {
random_kanji("JINMEIYO", ctx, None).await random_kanji("JINMEIYO", ctx, None).await
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Random Kyōiku kanji"))] #[command(
slash_command,
prefix_command,
description_localized("en-US", "Random Kyōiku kanji")
)]
pub async fn kyoiku( pub async fn kyoiku(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Kyōiku subcategory. GRADE1, GRADE2, GRADE3, GRADE4, GRADE5, GRADE6, or ALL."] #[description = "Kyōiku subcategory. GRADE1, GRADE2, GRADE3, GRADE4, GRADE5, GRADE6, or ALL."]
@ -95,29 +102,38 @@ pub async fn kyoiku(
random_kanji("KYOIKU", ctx, subcategory).await random_kanji("KYOIKU", ctx, subcategory).await
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Random JLPT kanji"))] #[command(
slash_command,
prefix_command,
description_localized("en-US", "Random JLPT kanji")
)]
pub async fn jlpt( pub async fn jlpt(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "JLPT subcategory. N1, N2, N3, N4, N5, or ALL"] #[description = "JLPT subcategory. N1, N2, N3, N4, N5, or ALL"] subcategory: Option<String>,
subcategory: Option<String>
) -> Result<(), Error> { ) -> Result<(), Error> {
random_kanji("JLPT", ctx, subcategory).await random_kanji("JLPT", ctx, subcategory).await
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Random Hyōgai kanji"))] #[command(
slash_command,
prefix_command,
description_localized("en-US", "Random Hyōgai kanji")
)]
pub async fn hyogai( pub async fn hyogai(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Hyōgai subcategory. ELEMENTS, MAIN, or ALL."] #[description = "Hyōgai subcategory. ELEMENTS, MAIN, or ALL."] subcategory: Option<String>,
subcategory: Option<String>,
) -> Result<(), Error> { ) -> Result<(), Error> {
random_kanji("HYOGAI", ctx, subcategory).await random_kanji("HYOGAI", ctx, subcategory).await
} }
#[command(slash_command, prefix_command, description_localized("en-US", "Get stroke order diagrams for character(s), maximum 4"))] #[command(
slash_command,
prefix_command,
description_localized("en-US", "Get stroke order diagrams for character(s), maximum 4")
)]
pub async fn so( pub async fn so(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Input characters to get stroke order for"] #[description = "Input characters to get stroke order for"] text: String,
text: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
const MAX_CHARS: i32 = 4; const MAX_CHARS: i32 = 4;
let mut displayed_characters: Vec<char> = Vec::new(); let mut displayed_characters: Vec<char> = Vec::new();

@ -1,12 +1,16 @@
use crate::Error;
use crate::Context; use crate::Context;
use crate::Error;
use poise::command; use poise::command;
use std::env; use std::env;
// TODO: Implement proper help text for command-specific help, // TODO: Implement proper help text for command-specific help,
// see https://github.com/kangalioo/poise/blob/90ac24a8ef621ec6dc3fc452762dc9cfa144f693/examples/framework_usage/main.rs#L18-L38 // see https://github.com/kangalioo/poise/blob/90ac24a8ef621ec6dc3fc452762dc9cfa144f693/examples/framework_usage/main.rs#L18-L38
#[command(prefix_command, slash_command, description_localized("en-US", "Get help for the 字ちゃん Tegaki Tuesday bot"))] #[command(
prefix_command,
slash_command,
description_localized("en-US", "Get help for the 字ちゃん Tegaki Tuesday bot")
)]
pub async fn help(ctx: Context<'_>) -> Result<(), Error> { pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
let p = env::var("PREFIX").unwrap(); let p = env::var("PREFIX").unwrap();
let message = format!( let message = format!(

@ -12,7 +12,12 @@ use crate::utils::*;
#[command(prefix_command, hide_in_help, owners_only)] #[command(prefix_command, hide_in_help, owners_only)]
pub async fn sleep(ctx: Context<'_>) -> Result<(), crate::Error> { pub async fn sleep(ctx: Context<'_>) -> Result<(), crate::Error> {
ctx.say("Good night!").await?; ctx.say("Good night!").await?;
ctx.framework().shard_manager.lock().await.shutdown_all().await; ctx.framework()
.shard_manager
.lock()
.await
.shutdown_all()
.await;
Ok(()) Ok(())
} }
@ -33,13 +38,11 @@ pub async fn setsubmissionchannel(ctx: Context<'_>) -> Result<(), Error> {
guild_data.insert(guild, json!({ "submissionChannel": channel })); guild_data.insert(guild, json!({ "submissionChannel": channel }));
} }
set_guild_data(guild_data); set_guild_data(guild_data);
ctx.say( ctx.say(format!(
format!( "Submission channel for **{}** set to <#{}>.",
"Submission channel for **{}** set to <#{}>.", ctx.guild().unwrap().name,
ctx.guild().unwrap().name, ctx.channel_id()
ctx.channel_id() ))
),
)
.await?; .await?;
Ok(()) Ok(())
} }
@ -63,8 +66,7 @@ pub async fn setsuggestionchannel(ctx: Context<'_>) -> Result<(), Error> {
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub async fn setannouncementrole( pub async fn setannouncementrole(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Announcement role ID"] #[description = "Announcement role ID"] role: u64,
role: u64,
) -> Result<(), Error> { ) -> Result<(), Error> {
let mut guild_data = get_guild_data(); let mut guild_data = get_guild_data();
let guild = ctx.guild_id().unwrap().as_u64().to_string(); let guild = ctx.guild_id().unwrap().as_u64().to_string();
@ -87,8 +89,7 @@ pub async fn setannouncementrole(
#[command(prefix_command, hide_in_help, owners_only)] #[command(prefix_command, hide_in_help, owners_only)]
pub async fn announce( pub async fn announce(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Announcement text"] #[description = "Announcement text"] announcement: String,
announcement: String,
) -> Result<(), Error> { ) -> Result<(), Error> {
send(ctx, &announcement, true, false).await send(ctx, &announcement, true, false).await
} }

@ -10,10 +10,8 @@ type PrefixContext<'a> = poise::PrefixContext<'a, Data, Error>;
pub struct Data {} pub struct Data {}
use commands::{challenge::*, kanji::*, meta::*, owner::*}; use commands::{challenge::*, kanji::*, meta::*, owner::*};
use poise::serenity_prelude::{
model::gateway::GatewayIntents
};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use poise::serenity_prelude::model::gateway::GatewayIntents;
#[poise::command(prefix_command)] #[poise::command(prefix_command)]
async fn register(ctx: Context<'_>) -> Result<(), Error> { async fn register(ctx: Context<'_>) -> Result<(), Error> {
@ -36,7 +34,6 @@ async fn main() {
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands: vec![ commands: vec![
register(), register(),
// owner // owner
sleep(), sleep(),
setsubmissionchannel(), setsubmissionchannel(),
@ -45,17 +42,14 @@ async fn main() {
announce(), announce(),
announcechallenge(), announcechallenge(),
rebuildsite(), rebuildsite(),
// challenge // challenge
challenge(), challenge(),
submit(), submit(),
images(), images(),
imagedelete(), imagedelete(),
suggest(), suggest(),
// meta // meta
help(), help(),
// kanji // kanji
i(), i(),
joyo(), joyo(),
@ -63,7 +57,7 @@ async fn main() {
kyoiku(), kyoiku(),
jlpt(), jlpt(),
hyogai(), hyogai(),
so() so(),
], ],
prefix_options: poise::PrefixFrameworkOptions { prefix_options: poise::PrefixFrameworkOptions {
prefix: Some(env::var("PREFIX").expect("Expected a prefix in the environment")), prefix: Some(env::var("PREFIX").expect("Expected a prefix in the environment")),
@ -72,10 +66,12 @@ async fn main() {
..Default::default() ..Default::default()
}) })
.token(std::env::var("DISCORD_TOKEN").expect("Expected a token in the environment")) .token(std::env::var("DISCORD_TOKEN").expect("Expected a token in the environment"))
.intents(GatewayIntents::GUILD_MESSAGES .intents(
| GatewayIntents::DIRECT_MESSAGES GatewayIntents::GUILD_MESSAGES
| GatewayIntents::MESSAGE_CONTENT | GatewayIntents::DIRECT_MESSAGES
| GatewayIntents::GUILDS) | GatewayIntents::MESSAGE_CONTENT
| GatewayIntents::GUILDS,
)
.user_data_setup(move |_ctx, _ready, _framework| Box::pin(async move { Ok(Data {}) })); .user_data_setup(move |_ctx, _ready, _framework| Box::pin(async move { Ok(Data {}) }));
framework.run().await.unwrap(); framework.run().await.unwrap();

@ -1,3 +1,6 @@
use crate::serenity;
use crate::Error;
use crate::{Context, PrefixContext};
use rand::seq::IteratorRandom; use rand::seq::IteratorRandom;
use serde_json::Map; use serde_json::Map;
use serde_json::Value; use serde_json::Value;
@ -9,9 +12,6 @@ use std::fs::OpenOptions;
use std::io::Read; use std::io::Read;
use std::io::Write; use std::io::Write;
use std::process::Command; use std::process::Command;
use crate::serenity;
use crate::Error;
use crate::{Context, PrefixContext};
pub fn get_challenge_number() -> i32 { pub fn get_challenge_number() -> i32 {
let challenge_dir = format!("{}/content/challenges", env::var("HUGO").unwrap()); let challenge_dir = format!("{}/content/challenges", env::var("HUGO").unwrap());
@ -152,12 +152,7 @@ pub fn set_guild_data(guild_data: Map<String, Value>) {
.unwrap(); .unwrap();
} }
pub async fn send( pub async fn send(ctx: Context<'_>, message: &str, ping: bool, pin: bool) -> Result<(), Error> {
ctx: Context<'_>,
message: &str,
ping: bool,
pin: bool,
) -> Result<(), Error> {
let guild_data = get_guild_data(); let guild_data = get_guild_data();
let mut announcements_count = 0; let mut announcements_count = 0;
for (_guild, data) in guild_data.iter() { for (_guild, data) in guild_data.iter() {
@ -198,13 +193,11 @@ pub async fn send(
} }
announcements_count += 1; announcements_count += 1;
} }
ctx.say( ctx.say(format!(
format!( "Announced to {} server{}!",
"Announced to {} server{}!", announcements_count,
announcements_count, if announcements_count == 1 { "" } else { "s" }
if announcements_count == 1 { "" } else { "s" } ))
),
)
.await?; .await?;
Ok(()) Ok(())
} }
@ -220,13 +213,8 @@ pub fn get_so_diagram(kanji: char) -> String {
) )
} }
pub async fn display_kanji( pub async fn display_kanji(ctx: Context<'_>, kanji: char, comment: &str) -> Result<(), Error> {
ctx: Context<'_>, ctx.say(format!("{}{}", kanji, comment)).await?;
kanji: char,
comment: &str,
) -> Result<(), Error> {
ctx.say(format!("{}{}", kanji, comment))
.await?;
let url = get_so_diagram(kanji); let url = get_so_diagram(kanji);
let client = reqwest::Client::new(); let client = reqwest::Client::new();
let request = client.head(&url).build().unwrap(); let request = client.head(&url).build().unwrap();
@ -339,8 +327,7 @@ pub async fn random_kanji(
.unwrap(); .unwrap();
let list = subcategories[subcategory_key].as_str().unwrap(); let list = subcategories[subcategory_key].as_str().unwrap();
let kanji = random_from_string(&list); let kanji = random_from_string(&list);
display_kanji(ctx, kanji, &format!(", **{}**", subcategory_key)) display_kanji(ctx, kanji, &format!(", **{}**", subcategory_key)).await?;
.await?;
} else if subcategories.contains_key(&subcategory) { } else if subcategories.contains_key(&subcategory) {
let list = list[&subcategory].as_str().unwrap(); let list = list[&subcategory].as_str().unwrap();
let kanji = random_from_string(&list); let kanji = random_from_string(&list);

Loading…
Cancel
Save