@ -1,20 +1,20 @@
use crate ::utils ::* ;
use crate ::utils ::* ;
use serde_json ::Map ;
use serde_json ::Map ;
use serenity ::framework ::standard ::{ macros ::command , Args , CommandResult } ;
use serenity ::http ::typing ::Typing ;
use crate ::serenity ;
use serenity ::model ::prelude ::* ;
use crate ::Error ;
use serenity ::prelude ::* ;
use crate ::{ Context , PrefixContext } ;
use poise ::command ;
use slug ::slugify ;
use slug ::slugify ;
use std ::env ;
use std ::fs ;
use std ::fs ;
use std ::fs ::File ;
use std ::fs ::File ;
use std ::io ::Write ;
use std ::io ::Write ;
use std ::path ::Path ;
use std ::path ::Path ;
#[ command ]
#[ command(prefix_command, slash_command, description_localized( " en-US " , " View the latest handwriting challenge info. " )) ]
async fn challenge ( ctx : & Context , msg : & Message ) -> CommandResult {
pub async fn challenge ( ctx : Context < ' _ > ) -> Result < ( ) , Error > {
msg . reply (
ctx . say (
& ctx . http ,
format! (
format! (
"Tegaki Tuesday #{n}: <https://tegakituesday.com/{n}>" ,
"Tegaki Tuesday #{n}: <https://tegakituesday.com/{n}>" ,
n = get_challenge_number ( )
n = get_challenge_number ( )
@ -24,27 +24,26 @@ async fn challenge(ctx: &Context, msg: &Message) -> CommandResult {
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ command ]
#[ command (prefix_command, broadcast_typing, description_localized(" en-US " , " Submit to the latest handwriting challenge. " )) ]
async fn submit ( ctx : & Context , msg : & Message ) -> CommandResult {
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.
let guild_data = get_guild_data ( ) ;
let guild_data = get_guild_data ( ) ;
let guild = msg. guild_id . unwrap ( ) . as_u64 ( ) . to_string ( ) ;
let guild = ctx. msg. guild_id . unwrap ( ) . as_u64 ( ) . to_string ( ) ;
if ! guild_data . contains_key ( & guild )
if ! guild_data . contains_key ( & guild )
| | ! & guild_data [ & guild ]
| | ! & guild_data [ & guild ]
. as_object ( )
. as_object ( )
. unwrap ( )
. unwrap ( )
. contains_key ( "submissionChannel" )
. contains_key ( "submissionChannel" )
{
{
msg . reply ( & ctx . http , "Submissions aren't enabled for this server yet." )
ctx . msg . reply ( & ctx . discord . http , "Submissions aren't enabled for this server yet." ) . await ? ;
. 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 ! = & msg . channel_id . as_u64 ( ) . to_string ( ) {
if submission_channel ! = ctx . msg . channel_id . as_u64 ( ) . to_string ( ) {
msg. reply (
ctx. msg. reply (
& ctx . http,
& ctx . discord. http,
format! (
format! (
"Sorry, submissions aren't permitted here. Please go to <#{}>. Thanks!" ,
"Sorry, submissions aren't permitted here. Please go to <#{}>. Thanks!" ,
guild
guild
@ -53,12 +52,10 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
. await ? ;
. await ? ;
return Ok ( ( ) ) ;
return Ok ( ( ) ) ;
}
}
if msg . attachments . len ( ) = = 0 {
if ctx . msg . attachments . len ( ) = = 0 {
msg . reply ( & ctx . http , "Please attach at least one image." )
ctx . msg . reply ( & ctx . discord . http , "Please attach at least one image." ) . await ? ;
. await ? ;
return Ok ( ( ) ) ;
return Ok ( ( ) ) ;
}
}
let typing = Typing ::start ( ctx . http . clone ( ) , * msg . channel_id . as_u64 ( ) ) . unwrap ( ) ;
let challenge_number = get_challenge_number ( ) ;
let challenge_number = get_challenge_number ( ) ;
let submission_images_dir = get_submission_images_dir ( ) ;
let submission_images_dir = get_submission_images_dir ( ) ;
@ -71,10 +68,10 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
let mut invalid_types = false ;
let mut invalid_types = false ;
let mut requires_rebuild = false ;
let mut requires_rebuild = false ;
for ( i , submission ) in submission_data . iter_mut ( ) . enumerate ( ) {
for ( i , submission ) in submission_data . iter_mut ( ) . enumerate ( ) {
if is_matching_submission ( & submission , & msg) {
if is_matching_submission ( & submission , & ctx. msg. author ) {
existing_submitter = true ;
existing_submitter = true ;
let mut images = submission [ "images" ] . as_array_mut ( ) . unwrap ( ) . clone ( ) ;
let mut images = submission [ "images" ] . as_array_mut ( ) . unwrap ( ) . clone ( ) ;
for attachment in msg. attachments . iter ( ) {
for attachment in ctx. msg. attachments . iter ( ) {
let extension ;
let extension ;
if let Some ( content_type ) = & attachment . content_type {
if let Some ( content_type ) = & attachment . content_type {
if content_type = = "image/png" {
if content_type = = "image/png" {
@ -93,8 +90,8 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
let file_name = format! (
let file_name = format! (
"{}-{}-{}-{}.{}" ,
"{}-{}-{}-{}.{}" ,
i + 1 ,
i + 1 ,
slugify ( & msg. author . name ) ,
slugify ( & ctx. msg. author . name ) ,
msg. author . discriminator ,
ctx. msg. author . discriminator ,
images . len ( ) + 1 ,
images . len ( ) + 1 ,
extension
extension
) ;
) ;
@ -113,10 +110,10 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
let mut submitter_data = Map ::new ( ) ;
let mut submitter_data = Map ::new ( ) ;
submitter_data . insert (
submitter_data . insert (
String ::from ( "username" ) ,
String ::from ( "username" ) ,
format! ( "{}#{}" , msg. author . name , msg . author . discriminator ) . into ( ) ,
format! ( "{}#{}" , ctx. msg. author . name , ctx . msg . author . discriminator ) . into ( ) ,
) ;
) ;
let mut images : Vec < String > = Vec ::new ( ) ;
let mut images : Vec < String > = Vec ::new ( ) ;
for attachment in msg. attachments . iter ( ) {
for attachment in ctx. msg. attachments . iter ( ) {
let extension ;
let extension ;
if let Some ( content_type ) = & attachment . content_type {
if let Some ( content_type ) = & attachment . content_type {
if content_type = = "image/png" {
if content_type = = "image/png" {
@ -135,8 +132,8 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
let file_name = format! (
let file_name = format! (
"{}-{}-{}{}.{}" ,
"{}-{}-{}{}.{}" ,
submission_data . len ( ) + 1 ,
submission_data . len ( ) + 1 ,
slugify ( & msg. author . name ) ,
slugify ( & ctx. msg. author . name ) ,
msg. author . discriminator ,
ctx. msg. author . discriminator ,
if images . len ( ) = = 0 {
if images . len ( ) = = 0 {
String ::from ( "" )
String ::from ( "" )
} else {
} else {
@ -153,7 +150,7 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
submitter_data . insert ( String ::from ( "images" ) , images . into ( ) ) ;
submitter_data . insert ( String ::from ( "images" ) , images . into ( ) ) ;
submitter_data . insert (
submitter_data . insert (
String ::from ( "id" ) ,
String ::from ( "id" ) ,
msg. author . id . as_u64 ( ) . to_string ( ) . into ( ) ,
ctx. msg. author . id . as_u64 ( ) . to_string ( ) . into ( ) ,
) ;
) ;
submission_data . push ( submitter_data . into ( ) ) ;
submission_data . push ( submitter_data . into ( ) ) ;
}
}
@ -169,18 +166,17 @@ async fn submit(ctx: &Context, msg: &Message) -> CommandResult {
} else if invalid_types {
} else if invalid_types {
message . push_str ( "Sorry, your submission could not be uploaded; only **.png**, **.jpg**, and **.jpeg** files are permitted." ) ;
message . push_str ( "Sorry, your submission could not be uploaded; only **.png**, **.jpg**, and **.jpeg** files are permitted." ) ;
}
}
let _ = typing . stop ( ) ;
ctx . msg . reply ( & ctx . discord . http , message ) . await ? ;
msg . reply ( & ctx . http , message ) . await ? ;
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ command ]
#[ command (prefix_command, slash_command, description_localized(" en-US " , " List images in your current submission, if available. " )) ]
async fn images ( ctx : & Context , msg : & Message ) -> CommandResult {
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 > = {
let mut images = Vec ::new ( ) ;
let mut images = Vec ::new ( ) ;
for submission in submission_data . iter ( ) {
for submission in submission_data . iter ( ) {
if is_matching_submission ( & submission , & msg ) {
if is_matching_submission ( & submission , & ctx. author ( ) ) {
for image in submission [ "images" ] . as_array ( ) . unwrap ( ) . iter ( ) {
for image in submission [ "images" ] . as_array ( ) . unwrap ( ) . iter ( ) {
images . push ( String ::from ( image . as_str ( ) . unwrap ( ) ) ) ;
images . push ( String ::from ( image . as_str ( ) . unwrap ( ) ) ) ;
}
}
@ -191,8 +187,7 @@ async fn images(ctx: &Context, msg: &Message) -> CommandResult {
} ;
} ;
let challenge_number = get_challenge_number ( ) ;
let challenge_number = get_challenge_number ( ) ;
if images . len ( ) = = 0 {
if images . len ( ) = = 0 {
msg . reply (
ctx . say (
& ctx . http ,
format! (
format! (
"You haven't submitted anything for Tegaki Tuesday #{}." ,
"You haven't submitted anything for Tegaki Tuesday #{}." ,
challenge_number
challenge_number
@ -213,41 +208,29 @@ async fn images(ctx: &Context, msg: &Message) -> CommandResult {
image
image
) ) ;
) ) ;
}
}
msg. reply ( & ctx . http , message ) . await ? ;
ctx. say ( message ) . await ? ;
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ command ]
#[ command(prefix_command, slash_command, description_localized( " en-US " , " Delete images from your current submission using image numbers from the images command. " )) ]
// imageDelete instead of image_delete to keep command naming from Python version
pub async fn imagedelete (
#[ allow(non_snake_case) ]
ctx : Context < ' _ > ,
async fn imageDelete ( ctx : & Context , msg : & Message , mut args : Args ) -> CommandResult {
number : i32 ,
let number ;
) -> Result < ( ) , Error > {
match args . single ::< i32 > ( ) {
Ok ( value ) = > number = value ,
Err ( _ ) = > {
msg . reply ( & ctx . http , format! ( "Please provide the image number you want to delete. You can get a list of your submitted images using `{}images`." , env ::var ( "PREFIX" ) . unwrap ( ) ) ) . await ? ;
return Ok ( ( ) ) ;
}
}
if number < 1 {
if number < 1 {
msg . reply (
ctx . say ( "That isn't a valid image number. Image numbers start at 1." ) . await ? ;
& ctx . http ,
"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 ( ) ;
let mut submission_data = get_current_submission_data ( ) ;
let mut submission_data = get_current_submission_data ( ) ;
for ( i , submission ) in submission_data . iter_mut ( ) . enumerate ( ) {
for ( i , submission ) in submission_data . iter_mut ( ) . enumerate ( ) {
if ! is_matching_submission ( & submission , & msg ) {
if ! is_matching_submission ( & submission , & ctx. author ( ) ) {
continue ;
continue ;
}
}
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 ( ) {
msg . reply (
ctx . say (
& ctx . http ,
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.
@ -296,11 +279,12 @@ async fn imageDelete(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
for ( j , image ) in images . iter_mut ( ) . enumerate ( ) {
for ( j , image ) in images . iter_mut ( ) . enumerate ( ) {
let old = image . as_str ( ) . unwrap ( ) ;
let old = image . as_str ( ) . unwrap ( ) ;
let from = format! ( "{}/{}" , submission_images_dir , old ) ;
let from = format! ( "{}/{}" , submission_images_dir , old ) ;
let author = ctx . author ( ) ;
let new = format! (
let new = format! (
"{}-{}-{}{}.{}" ,
"{}-{}-{}{}.{}" ,
i + 1 ,
i + 1 ,
slugify ( & msg. author. name ) ,
slugify ( & author. name ) ,
msg. author. discriminator ,
author. discriminator ,
if j = = 0 {
if j = = 0 {
String ::from ( "" )
String ::from ( "" )
} else {
} else {
@ -317,11 +301,10 @@ async fn imageDelete(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
}
}
set_submission_data ( submission_data ) ;
set_submission_data ( submission_data ) ;
rebuild_site ( ) ;
rebuild_site ( ) ;
msg. reply ( & ctx . http , message ) . await ? ;
ctx. say ( message ) . await ? ;
return Ok ( ( ) ) ;
return Ok ( ( ) ) ;
}
}
msg . reply (
ctx . say (
& ctx . http ,
format! (
format! (
"You haven't submitted anything for Tegaki Tuesday #{}." ,
"You haven't submitted anything for Tegaki Tuesday #{}." ,
challenge_number
challenge_number
@ -331,11 +314,15 @@ async fn imageDelete(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
Ok ( ( ) )
Ok ( ( ) )
}
}
#[ command ]
// TODO: make also slash command
#[ allow(non_snake_case) ]
#[ command(prefix_command, description_localized( " en-US " , " Make a suggestion for future challenge prompts! " )) ]
async fn suggest ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
pub async fn suggest (
ctx : PrefixContext < ' _ > ,
#[ description = " Suggestion text. Please include passage and source. " ]
suggestion : String ,
) -> Result < ( ) , Error > {
let guild_data = get_guild_data ( ) ;
let guild_data = get_guild_data ( ) ;
let channel = ChannelId (
let channel = serenity:: ChannelId(
guild_data [ "suggestionChannel" ]
guild_data [ "suggestionChannel" ]
. as_str ( )
. as_str ( )
. unwrap ( )
. unwrap ( )
@ -344,33 +331,32 @@ async fn suggest(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
) ;
) ;
// 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 a ccent_color = ctx
let a uthor = & ctx . msg . author ;
. http
let accent_color = ctx . discord . http
. get_user ( * msg. author. id . as_u64 ( ) )
. get_user ( * author. id . as_u64 ( ) )
. await
. await
. unwrap ( )
. unwrap ( )
. accent_colour ;
. accent_colour ;
channel
channel
. send_message ( & ctx . http, | m | {
. send_message ( & ctx . discord. http, | m | {
m . allowed_mentions ( | am | {
m . allowed_mentions ( | am | {
am . empty_parse ( ) ;
am . empty_parse ( ) ;
am
am
} ) ;
} ) ;
m . embed ( | e | {
m . embed ( | e | {
let username = format! ( "{}#{}" , msg. author. name , msg . author . discriminator ) ;
let username = format! ( "{}#{}" , author. name , author . discriminator ) ;
e . title ( "New suggestion" ) ;
e . title ( "New suggestion" ) ;
e . description ( format! (
e . description ( format! (
"{}\n\n[See original message]({}) ({})" ,
"{}\n\n[See original message]({})" ,
args . rest ( ) ,
suggestion ,
msg . link ( ) ,
ctx . msg . link ( ) ,
msg . guild ( & ctx ) . unwrap ( ) . name
) ) ;
) ) ;
let mut author = serenity ::builder ::CreateEmbedAuthor ::default ( ) ;
let mut embed_ author = serenity ::builder ::CreateEmbedAuthor ::default ( ) ;
author
embed_ author
. icon_url ( get_avatar ( & msg. author) )
. icon_url ( get_avatar ( & author) )
. name ( username )
. name ( username )
. url ( format! ( "https://discord.com/users/{}" , msg. author. id ) ) ;
. url ( format! ( "https://discord.com/users/{}" , author. id ) ) ;
e . set_author ( author) ;
e . set_author ( embed_ author) ;
if let Some ( accent_color ) = accent_color {
if let Some ( accent_color ) = accent_color {
e . color ( accent_color ) ;
e . color ( accent_color ) ;
}
}
@ -380,6 +366,6 @@ async fn suggest(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
} )
} )
. await
. await
. unwrap ( ) ;
. 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 ? ;
ctx. msg. reply ( & ctx . discord . 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 ( ( ) )
Ok ( ( ) )
}
}