diff --git a/src/commands/moderation_cmds.rs b/src/commands/moderation_cmds.rs index 43962a4..57b3c08 100644 --- a/src/commands/moderation_cmds.rs +++ b/src/commands/moderation_cmds.rs @@ -416,132 +416,12 @@ Any commands ran as part of recording will be inconsequential") } } -/* -#[command("alias")] -#[supports_dm(false)] -#[permission_level(Managed)] -async fn alias(ctx: &Context, msg: &Message, args: String) { - let (pool, lm) = get_ctx_data(&ctx).await; - - let language = UserData::language_of(&msg.author, &pool).await; - - let guild_id = msg.guild_id.unwrap().as_u64().to_owned(); - - let matches_opt = REGEX_ALIAS.captures(&args); - - if let Some(matches) = matches_opt { - let name = matches.name("name").unwrap().as_str(); - let command_opt = matches.name("cmd").map(|m| m.as_str()); - - match name { - "list" => { - let aliases = sqlx::query!( - " -SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) - ", - guild_id - ) - .fetch_all(&pool) - .await - .unwrap(); - - let content = iter::once("Aliases:".to_string()).chain( - aliases - .iter() - .map(|row| format!("**{}**: `{}`", row.name, row.command)), - ); - - let _ = msg.channel_id.say_lines(&ctx, content).await; - } - - "remove" => { - if let Some(command) = command_opt { - let deleted_count = sqlx::query!( - " -SELECT COUNT(1) AS count FROM command_aliases WHERE name = ? AND guild_id = (SELECT id FROM guilds WHERE guild = ?) - ", command, guild_id) - .fetch_one(&pool) - .await - .unwrap(); - - sqlx::query!( - " -DELETE FROM command_aliases WHERE name = ? AND guild_id = (SELECT id FROM guilds WHERE guild = ?) - ", - command, - guild_id - ) - .execute(&pool) - .await - .unwrap(); - - let content = lm - .get(&language, "alias/removed") - .replace("{count}", &deleted_count.count.to_string()); - - let _ = msg.channel_id.say(&ctx, content).await; - } else { - let _ = msg - .channel_id - .say(&ctx, lm.get(&language, "alias/help")) - .await; - } - } - - name => { - if let Some(command) = command_opt { - let res = sqlx::query!( - " -INSERT INTO command_aliases (guild_id, name, command) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?) - ", guild_id, name, command) - .execute(&pool) - .await; - - if res.is_err() { - sqlx::query!( - " -UPDATE command_aliases SET command = ? WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ? - ", command, guild_id, name) - .execute(&pool) - .await - .unwrap(); - } - - let content = lm.get(&language, "alias/created").replace("{name}", name); - - let _ = msg.channel_id.say(&ctx, content).await; - } else { - match sqlx::query!( - " -SELECT command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ? - ", guild_id, name) - .fetch_one(&pool) - .await { - - Ok(row) => { - let framework = ctx.data.read().await - .get::().cloned().expect("Could not get FrameworkCtx from data"); - - let mut new_msg = msg.clone(); - new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id(), row.command); - new_msg.id = MessageId(0); - - framework.dispatch(ctx.clone(), new_msg).await; - }, - - Err(_) => { - let content = lm.get(&language, "alias/not_found").replace("{name}", name); - - let _ = msg.channel_id.say(&ctx, content).await; - }, - } - } - } - } - } else { - let prefix = ctx.prefix(msg.guild_id).await; - - command_help(ctx, msg, lm, &prefix, &language, "alias").await; - } -} -*/ +#[command("webhook")] +#[description("Modify this channel's webhooks")] +#[subcommand("username")] +#[description("Change the webhook username")] +#[arg(name = "username", description = "The username to use", kind = "String", required = true)] +#[subcommand("avatar")] +#[description("Change the webhook avatar")] +#[arg(name = "url", description = "The URL of the image to use", kind = "String", required = true)] +async fn configure_webhook(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {} diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index 60222de..6f9f8ae 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -22,7 +22,7 @@ use crate::{ }, consts::{ EMBED_DESCRIPTION_MAX_LENGTH, REGEX_CHANNEL_USER, REGEX_NATURAL_COMMAND_1, - REGEX_NATURAL_COMMAND_2, THEME_COLOR, + REGEX_NATURAL_COMMAND_2, SELECT_MAX_ENTRIES, THEME_COLOR, }, framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue}, hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK}, @@ -290,7 +290,7 @@ async fn look(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) { let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { if Some(channel.guild_id) == invoke.guild_id() { - flags.channel_id.unwrap_or(invoke.channel_id()) + flags.channel_id.unwrap_or_else(|| invoke.channel_id()) } else { invoke.channel_id() } @@ -374,45 +374,57 @@ async fn delete(ctx: &Context, invoke: CommandInvoke, _args: CommandOptions) { let _ = interaction .create_interaction_response(&ctx, |r| { *r = resp; - r + r.kind(InteractionResponseType::ChannelMessageWithSource) }) - .await; + .await + .unwrap(); } -pub fn max_delete_page(reminders: &Vec, timezone: &Tz) -> usize { +pub fn max_delete_page(reminders: &[Reminder], timezone: &Tz) -> usize { + let mut rows = 0; + let mut char_count = 0; + reminders .iter() .enumerate() .map(|(count, reminder)| reminder.display_del(count, timezone)) - .fold(0, |t, r| t + r.len()) - .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH) + .fold(1, |mut pages, reminder| { + rows += 1; + char_count += reminder.len(); + + if char_count > EMBED_DESCRIPTION_MAX_LENGTH || rows > SELECT_MAX_ENTRIES { + rows = 1; + char_count = reminder.len(); + pages += 1; + } + + pages + }) } pub async fn show_delete_page( - reminders: &Vec, + reminders: &[Reminder], page: usize, timezone: Tz, ) -> CreateInteractionResponse { - let pager = DelPager::new(timezone); + let pager = DelPager::new(page, timezone); if reminders.is_empty() { let mut embed = CreateEmbed::default(); embed.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR); let mut response = CreateInteractionResponse::default(); - response.kind(InteractionResponseType::UpdateMessage).interaction_response_data( - |response| { - response.embeds(vec![embed]).components(|comp| { - pager.create_button_row(0, comp); - comp - }) - }, - ); + response.interaction_response_data(|response| { + response.embeds(vec![embed]).components(|comp| { + pager.create_button_row(0, comp); + comp + }) + }); return response; } - let pages = max_delete_page(&reminders, &timezone); + let pages = max_delete_page(reminders, &timezone); let mut page = page; if page >= pages { @@ -420,23 +432,37 @@ pub async fn show_delete_page( } let mut char_count = 0; - let mut skip_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; + let (shown_reminders, display_vec): (Vec<&Reminder>, Vec) = reminders .iter() .enumerate() .map(|(count, reminder)| (reminder, reminder.display_del(count, &timezone))) .skip_while(|(_, p)| { first_num += 1; - skip_char_count += p.len(); + skipped_rows += 1; + skipped_char_count += p.len(); - skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * page + 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 }) .take_while(|(_, p)| { + rows += 1; char_count += p.len(); - char_count < EMBED_DESCRIPTION_MAX_LENGTH + char_count < EMBED_DESCRIPTION_MAX_LENGTH && rows <= SELECT_MAX_ENTRIES }) .unzip(); @@ -452,7 +478,7 @@ pub async fn show_delete_page( .color(*THEME_COLOR); let mut response = CreateInteractionResponse::default(); - response.kind(InteractionResponseType::UpdateMessage).interaction_response_data(|d| { + response.interaction_response_data(|d| { d.embeds(vec![embed]).components(|comp| { pager.create_button_row(pages, comp); @@ -596,7 +622,7 @@ DELETE FROM timers WHERE owner = ? AND name = ? "list" => { let timers = Timer::from_owner(owner, &pool).await; - if timers.len() > 0 { + if !timers.is_empty() { let _ = invoke .respond( ctx.http.clone(), @@ -699,7 +725,7 @@ async fn remind(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) { let list = args .get("channels") .map(|arg| parse_mention_list(&arg.to_string())) - .unwrap_or(vec![]); + .unwrap_or_default(); if list.is_empty() { vec![ReminderScope::Channel(interaction.channel_id.0)] diff --git a/src/commands/todo_cmds.rs b/src/commands/todo_cmds.rs index 1732d66..ce7f26f 100644 --- a/src/commands/todo_cmds.rs +++ b/src/commands/todo_cmds.rs @@ -1,7 +1,10 @@ use regex_command_attr::command; use serenity::client::Context; -use crate::framework::{CommandInvoke, CommandOptions}; +use crate::{ + framework::{CommandInvoke, CommandOptions, CreateGenericResponse}, + SQLPool, +}; #[command] #[description("Manage todo lists")] @@ -41,4 +44,55 @@ use crate::framework::{CommandInvoke, CommandOptions}; )] #[subcommand("view")] #[description("View and remove from your personal todo list")] -async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {} +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::().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!( + "SELECT value FROM todos WHERE user_id = ? AND channel_id = ? AND guild_id = ?", + keys.0, + keys.1, + keys.2, + ) + .fetch_all(&pool) + .await + .unwrap() + .iter() + .map(|row| &row.value); + } + } + } +} diff --git a/src/component_models/mod.rs b/src/component_models/mod.rs index 22780f1..f5cbb2a 100644 --- a/src/component_models/mod.rs +++ b/src/component_models/mod.rs @@ -174,7 +174,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id let _ = component .create_interaction_response(&ctx, move |r| { *r = resp; - r + r.kind(InteractionResponseType::UpdateMessage) }) .await; } @@ -195,7 +195,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id let _ = component .create_interaction_response(&ctx, move |r| { *r = resp; - r + r.kind(InteractionResponseType::UpdateMessage) }) .await; } diff --git a/src/component_models/pager.rs b/src/component_models/pager.rs index 40d25a1..eb2eed7 100644 --- a/src/component_models/pager.rs +++ b/src/component_models/pager.rs @@ -186,8 +186,8 @@ impl Pager for DelPager { } impl DelPager { - pub fn new(timezone: Tz) -> Self { - Self { page: 0, action: PageAction::First, timezone } + pub fn new(page: usize, timezone: Tz) -> Self { + Self { page, action: PageAction::Refresh, timezone } } pub fn buttons( diff --git a/src/consts.rs b/src/consts.rs index 0bf03ad..c0a0bf1 100644 --- a/src/consts.rs +++ b/src/consts.rs @@ -2,6 +2,7 @@ pub const DAY: u64 = 86_400; pub const HOUR: u64 = 3_600; pub const MINUTE: u64 = 60; pub const EMBED_DESCRIPTION_MAX_LENGTH: usize = 4000; +pub const SELECT_MAX_ENTRIES: usize = 25; pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_"; diff --git a/src/framework.rs b/src/framework.rs index e79c50a..823fe7c 100644 --- a/src/framework.rs +++ b/src/framework.rs @@ -144,7 +144,7 @@ impl CommandInvoke { d.content(generic_response.content); if let Some(embed) = generic_response.embed { - d.add_embed(embed.clone()); + d.add_embed(embed); } if let Some(components) = generic_response.components { @@ -164,7 +164,7 @@ impl CommandInvoke { d.content(generic_response.content); if let Some(embed) = generic_response.embed { - d.add_embed(embed.clone()); + d.add_embed(embed); } if let Some(components) = generic_response.components { @@ -186,7 +186,7 @@ impl CommandInvoke { m.content(generic_response.content); if let Some(embed) = generic_response.embed { - m.set_embed(embed.clone()); + m.set_embed(embed); } if let Some(components) = generic_response.components { @@ -308,7 +308,7 @@ impl CommandOptions { ApplicationCommandOptionType::String => { cmd_opts.options.insert( option.name, - OptionValue::String(option.value.unwrap().to_string()), + OptionValue::String(option.value.unwrap().as_str().unwrap().to_string()), ); } ApplicationCommandOptionType::Integer => { @@ -404,10 +404,7 @@ pub enum CommandFnType { impl CommandFnType { pub fn is_slash(&self) -> bool { - match self { - CommandFnType::Text(_) => false, - _ => true, - } + !matches!(self, CommandFnType::Text(_)) } }