2021-09-06 12:46:16 +00:00
|
|
|
use regex_command_attr::command;
|
2021-10-02 21:54:34 +00:00
|
|
|
use serenity::{
|
|
|
|
builder::{CreateEmbed, CreateInteractionResponse},
|
|
|
|
client::Context,
|
|
|
|
model::interactions::InteractionResponseType,
|
|
|
|
};
|
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::{
|
|
|
|
pager::{Pager, TodoPager},
|
|
|
|
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-09-27 16:34:13 +00:00
|
|
|
async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
|
|
|
|
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!(
|
|
|
|
"INSERT INTO todos (user_id, channel_id, guild_id, value) VALUES (?, ?, ?, ?)",
|
|
|
|
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-11 20:19:08 +00:00
|
|
|
"SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?",
|
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);
|
|
|
|
|
|
|
|
let interaction = invoke.interaction().unwrap();
|
|
|
|
|
|
|
|
let _ = interaction
|
|
|
|
.create_interaction_response(&ctx, |r| {
|
|
|
|
*r = resp;
|
|
|
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
.unwrap();
|
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>,
|
|
|
|
) -> CreateInteractionResponse {
|
2021-10-11 20:19:08 +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-02 21:54:34 +00:00
|
|
|
let mut embed = CreateEmbed::default();
|
|
|
|
embed
|
|
|
|
.title(format!("{} Todo List", title))
|
|
|
|
.description(display)
|
|
|
|
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
|
|
|
|
.color(*THEME_COLOR);
|
|
|
|
|
|
|
|
let mut response = CreateInteractionResponse::default();
|
2021-10-11 20:19:08 +00:00
|
|
|
response.interaction_response_data(|d| {
|
|
|
|
d.embeds(vec![embed]).components(|comp| {
|
|
|
|
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
|
|
|
|
|
|
|
response
|
|
|
|
}
|