2021-09-06 12:46:16 +00:00
use regex_command_attr ::command ;
2021-10-13 15:37:15 +00:00
use serenity ::client ::Context ;
2021-09-24 11:55:35 +00:00
2021-09-27 16:34:13 +00:00
use crate ::{
2021-10-11 20:19:08 +00:00
component_models ::{
2021-10-26 19:11:19 +00:00
pager ::{ Pager , TodoPager } ,
2021-10-11 20:19:08 +00:00
ComponentDataModel , TodoSelector ,
} ,
2021-10-02 21:54:34 +00:00
consts ::{ EMBED_DESCRIPTION_MAX_LENGTH , SELECT_MAX_ENTRIES , THEME_COLOR } ,
2021-09-27 16:34:13 +00:00
framework ::{ CommandInvoke , CommandOptions , CreateGenericResponse } ,
SQLPool ,
} ;
2021-09-24 11:55:35 +00:00
#[ command ]
#[ description( " Manage todo lists " ) ]
#[ subcommandgroup( " server " ) ]
#[ description( " Manage the server todo list " ) ]
#[ subcommand( " add " ) ]
#[ description( " Add an item to the server todo list " ) ]
#[ arg(
name = " task " ,
description = " The task to add to the todo list " ,
kind = " String " ,
required = true
) ]
#[ subcommand( " view " ) ]
#[ description( " View and remove from the server todo list " ) ]
#[ subcommandgroup( " channel " ) ]
#[ description( " Manage the channel todo list " ) ]
#[ subcommand( " add " ) ]
#[ description( " Add to the channel todo list " ) ]
#[ arg(
name = " task " ,
description = " The task to add to the todo list " ,
kind = " String " ,
required = true
) ]
#[ subcommand( " view " ) ]
#[ description( " View and remove from the channel todo list " ) ]
#[ subcommandgroup( " user " ) ]
#[ description( " Manage your personal todo list " ) ]
#[ subcommand( " add " ) ]
#[ description( " Add to your personal todo list " ) ]
#[ arg(
name = " task " ,
description = " The task to add to the todo list " ,
kind = " String " ,
required = true
) ]
#[ subcommand( " view " ) ]
#[ description( " View and remove from your personal todo list " ) ]
2021-10-13 15:37:15 +00:00
async fn todo ( ctx : & Context , invoke : & mut CommandInvoke , args : CommandOptions ) {
2021-09-27 16:34:13 +00:00
if invoke . guild_id ( ) . is_none ( ) & & args . subcommand_group ! = Some ( " user " . to_string ( ) ) {
let _ = invoke
. respond (
& ctx ,
CreateGenericResponse ::new ( ) . content ( " Please use `/todo user` in direct messages " ) ,
)
. await ;
} else {
let pool = ctx . data . read ( ) . await . get ::< SQLPool > ( ) . cloned ( ) . unwrap ( ) ;
let keys = match args . subcommand_group . as_ref ( ) . unwrap ( ) . as_str ( ) {
" server " = > ( None , None , invoke . guild_id ( ) . map ( | g | g . 0 ) ) ,
" channel " = > ( None , Some ( invoke . channel_id ( ) . 0 ) , invoke . guild_id ( ) . map ( | g | g . 0 ) ) ,
_ = > ( Some ( invoke . author_id ( ) . 0 ) , None , None ) ,
} ;
match args . get ( " task " ) {
Some ( task ) = > {
let task = task . to_string ( ) ;
sqlx ::query! (
2021-10-30 19:57:33 +00:00
" INSERT INTO todos (user_id, channel_id, guild_id, value) VALUES ((SELECT id FROM users WHERE user = ?), (SELECT id FROM channels WHERE channel = ?), (SELECT id FROM guilds WHERE guild = ?), ?) " ,
2021-09-27 16:34:13 +00:00
keys . 0 ,
keys . 1 ,
keys . 2 ,
task
)
. execute ( & pool )
. await
. unwrap ( ) ;
let _ = invoke
. respond ( & ctx , CreateGenericResponse ::new ( ) . content ( " Item added to todo list " ) )
. await ;
}
None = > {
let values = sqlx ::query! (
2021-10-02 21:54:34 +00:00
// fucking braindead mysql use <=> instead of = for null comparison
2021-10-30 19:57:33 +00:00
" SELECT todos.id, value FROM todos
INNER JOIN users ON todos . user_id = users . id
INNER JOIN channels ON todos . channel_id = channels . id
INNER JOIN guilds ON todos . guild_id = guilds . id
WHERE users . user < = > ? AND channels . channel < = > ? AND guilds . guild < = > ? " ,
2021-09-27 16:34:13 +00:00
keys . 0 ,
keys . 1 ,
keys . 2 ,
)
. fetch_all ( & pool )
. await
. unwrap ( )
. iter ( )
2021-10-11 20:19:08 +00:00
. map ( | row | ( row . id as usize , row . value . clone ( ) ) )
. collect ::< Vec < ( usize , String ) > > ( ) ;
2021-10-02 21:54:34 +00:00
let resp = show_todo_page ( & values , 0 , keys . 0 , keys . 1 , keys . 2 ) ;
2021-10-13 15:37:15 +00:00
let _ = invoke . respond ( & ctx , resp ) . await ;
2021-09-27 16:34:13 +00:00
}
}
}
}
2021-10-02 21:54:34 +00:00
2021-10-11 20:19:08 +00:00
pub fn max_todo_page ( todo_values : & [ ( usize , String ) ] ) -> usize {
2021-10-02 21:54:34 +00:00
let mut rows = 0 ;
let mut char_count = 0 ;
2021-10-11 20:19:08 +00:00
todo_values . iter ( ) . enumerate ( ) . map ( | ( c , ( _ , v ) ) | format! ( " {} : {} " , c , v ) ) . fold (
2021-10-02 21:54:34 +00:00
1 ,
| mut pages , text | {
rows + = 1 ;
char_count + = text . len ( ) ;
if char_count > EMBED_DESCRIPTION_MAX_LENGTH | | rows > SELECT_MAX_ENTRIES {
rows = 1 ;
char_count = text . len ( ) ;
pages + = 1 ;
}
pages
} ,
)
}
pub fn show_todo_page (
2021-10-11 20:19:08 +00:00
todo_values : & [ ( usize , String ) ] ,
2021-10-02 21:54:34 +00:00
page : usize ,
user_id : Option < u64 > ,
channel_id : Option < u64 > ,
guild_id : Option < u64 > ,
2021-10-13 15:37:15 +00:00
) -> CreateGenericResponse {
2021-10-26 19:11:19 +00:00
let pager = TodoPager ::new ( page , user_id , channel_id , guild_id ) ;
2021-10-02 21:54:34 +00:00
let pages = max_todo_page ( todo_values ) ;
let mut page = page ;
if page > = pages {
page = pages - 1 ;
}
let mut char_count = 0 ;
let mut rows = 0 ;
let mut skipped_rows = 0 ;
let mut skipped_char_count = 0 ;
let mut first_num = 0 ;
let mut skipped_pages = 0 ;
2021-10-11 20:19:08 +00:00
let ( todo_ids , display_vec ) : ( Vec < usize > , Vec < String > ) = todo_values
2021-10-02 21:54:34 +00:00
. iter ( )
. enumerate ( )
2021-10-11 20:19:08 +00:00
. map ( | ( c , ( i , v ) ) | ( i , format! ( " ` {} `: {} " , c + 1 , v ) ) )
. skip_while ( | ( _ , p ) | {
2021-10-02 21:54:34 +00:00
first_num + = 1 ;
skipped_rows + = 1 ;
skipped_char_count + = p . len ( ) ;
if skipped_char_count > EMBED_DESCRIPTION_MAX_LENGTH
| | skipped_rows > SELECT_MAX_ENTRIES
{
skipped_rows = 1 ;
skipped_char_count = p . len ( ) ;
skipped_pages + = 1 ;
}
skipped_pages < page
} )
2021-10-11 20:19:08 +00:00
. take_while ( | ( _ , p ) | {
2021-10-02 21:54:34 +00:00
rows + = 1 ;
char_count + = p . len ( ) ;
char_count < EMBED_DESCRIPTION_MAX_LENGTH & & rows < = SELECT_MAX_ENTRIES
} )
2021-10-11 20:19:08 +00:00
. unzip ( ) ;
2021-10-02 21:54:34 +00:00
let display = display_vec . join ( " \n " ) ;
let title = if user_id . is_some ( ) {
" Your "
} else if channel_id . is_some ( ) {
" Channel "
} else {
" Server "
} ;
2021-10-11 20:19:08 +00:00
let todo_selector =
ComponentDataModel ::TodoSelector ( TodoSelector { page , user_id , channel_id , guild_id } ) ;
2021-10-13 15:37:15 +00:00
CreateGenericResponse ::new ( )
. embed ( | e | {
e . title ( format! ( " {} Todo List " , title ) )
. description ( display )
. footer ( | f | f . text ( format! ( " Page {} of {} " , page + 1 , pages ) ) )
. color ( * THEME_COLOR )
} )
. components ( | comp | {
2021-10-11 20:19:08 +00:00
pager . create_button_row ( pages , comp ) ;
comp . create_action_row ( | row | {
row . create_select_menu ( | menu | {
menu . custom_id ( todo_selector . to_custom_id ( ) ) . options ( | opt | {
for ( count , ( id , disp ) ) in todo_ids . iter ( ) . zip ( & display_vec ) . enumerate ( ) {
opt . create_option ( | o | {
o . label ( format! ( " Mark {} complete " , count + first_num ) )
. value ( id )
. description ( disp . split_once ( " " ) . unwrap_or ( ( " " , " " ) ) . 1 )
} ) ;
}
opt
} )
} )
} )
} )
2021-10-02 21:54:34 +00:00
}