From b62d24c02459d895af927000d569af7b1b1626d0 Mon Sep 17 00:00:00 2001 From: jude Date: Mon, 12 Sep 2022 17:49:10 +0100 Subject: [PATCH] Add an autocomplete for time hints Shows the approximate time until a reminder will send in the autocomplete area. --- src/commands/autocomplete.rs | 100 +++++++++++++++++++++++++++++++++- src/commands/reminder_cmds.rs | 26 +++------ src/hooks.rs | 44 ++++++++------- 3 files changed, 131 insertions(+), 39 deletions(-) diff --git a/src/commands/autocomplete.rs b/src/commands/autocomplete.rs index 75be35f..53725b4 100644 --- a/src/commands/autocomplete.rs +++ b/src/commands/autocomplete.rs @@ -1,6 +1,9 @@ -use chrono_tz::TZ_VARIANTS; +use std::time::{SystemTime, UNIX_EPOCH}; -use crate::Context; +use chrono_tz::TZ_VARIANTS; +use poise::AutocompleteChoice; + +use crate::{models::CtxData, time_parser::natural_parser, Context}; pub async fn timezone_autocomplete(ctx: Context<'_>, partial: &str) -> Vec { if partial.is_empty() { @@ -33,3 +36,96 @@ WHERE .map(|s| s.name.clone()) .collect() } + +pub async fn multiline_autocomplete( + _ctx: Context<'_>, + partial: &str, +) -> Vec> { + if partial.is_empty() { + vec![AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }] + } else { + vec![ + AutocompleteChoice { name: partial.to_string(), value: partial.to_string() }, + AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }, + ] + } +} + +pub async fn time_hint_autocomplete( + ctx: Context<'_>, + partial: &str, +) -> Vec> { + if partial.is_empty() { + vec![AutocompleteChoice { + name: "Start typing a time...".to_string(), + value: "now".to_string(), + }] + } else { + match natural_parser(partial, &ctx.timezone().await.to_string()).await { + Some(timestamp) => match SystemTime::now().duration_since(UNIX_EPOCH) { + Ok(now) => { + let diff = timestamp - now.as_secs() as i64; + + if diff < 0 { + vec![AutocompleteChoice { + name: "Time is in the past".to_string(), + value: "now".to_string(), + }] + } else { + if diff > 86400 { + vec![ + AutocompleteChoice { + name: partial.to_string(), + value: partial.to_string(), + }, + AutocompleteChoice { + name: format!( + "In approximately {} days, {} hours", + diff / 86400, + (diff % 86400) / 3600 + ), + value: partial.to_string(), + }, + ] + } else if diff > 3600 { + vec![ + AutocompleteChoice { + name: partial.to_string(), + value: partial.to_string(), + }, + AutocompleteChoice { + name: format!("In approximately {} hours", diff / 3600), + value: partial.to_string(), + }, + ] + } else { + vec![ + AutocompleteChoice { + name: partial.to_string(), + value: partial.to_string(), + }, + AutocompleteChoice { + name: format!("In approximately {} minutes", diff / 60), + value: partial.to_string(), + }, + ] + } + } + } + Err(_) => { + vec![AutocompleteChoice { + name: partial.to_string(), + value: partial.to_string(), + }] + } + }, + + None => { + vec![AutocompleteChoice { + name: "Time not recognised".to_string(), + value: "now".to_string(), + }] + } + } + } +} diff --git a/src/commands/reminder_cmds.rs b/src/commands/reminder_cmds.rs index bc83fb7..3e320d0 100644 --- a/src/commands/reminder_cmds.rs +++ b/src/commands/reminder_cmds.rs @@ -11,11 +11,13 @@ use poise::{ serenity_prelude::{ builder::CreateEmbed, component::ButtonStyle, model::channel::Channel, ReactionType, }, - AutocompleteChoice, CreateReply, Modal, + CreateReply, Modal, }; -use super::autocomplete::timezone_autocomplete; use crate::{ + commands::autocomplete::{ + multiline_autocomplete, time_hint_autocomplete, timezone_autocomplete, + }, component_models::{ pager::{DelPager, LookPager, Pager}, ComponentDataModel, DelSelector, UndoReminder, @@ -550,20 +552,6 @@ pub async fn delete_timer( Ok(()) } -async fn multiline_autocomplete( - _ctx: Context<'_>, - partial: &str, -) -> Vec> { - if partial.is_empty() { - vec![AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }] - } else { - vec![ - AutocompleteChoice { name: partial.to_string(), value: partial.to_string() }, - AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }, - ] - } -} - #[derive(poise::Modal)] #[name = "Reminder"] struct ContentModal { @@ -574,7 +562,7 @@ struct ContentModal { content: String, } -/// Create a reminder. Press "+5 more" for other options. A modal will open if "content" is not provided +/// Create a reminder. Press "+4 more" for other options. #[poise::command( slash_command, identifying_name = "remind", @@ -582,7 +570,9 @@ struct ContentModal { )] pub async fn remind( ctx: ApplicationContext<'_>, - #[description = "A description of the time to set the reminder for"] time: String, + #[description = "A description of the time to set the reminder for"] + #[autocomplete = "time_hint_autocomplete"] + time: String, #[description = "The message content to send"] #[autocomplete = "multiline_autocomplete"] content: String, diff --git a/src/hooks.rs b/src/hooks.rs index 81fb4d2..c4606a3 100644 --- a/src/hooks.rs +++ b/src/hooks.rs @@ -1,36 +1,42 @@ -use poise::serenity_prelude::model::channel::Channel; +use poise::{ + serenity_prelude::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction, +}; use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error}; async fn macro_check(ctx: Context<'_>) -> bool { if let Context::Application(app_ctx) = ctx { - if let Some(guild_id) = ctx.guild_id() { - if ctx.command().identifying_name != "finish_macro" { - let mut lock = ctx.data().recording_macros.write().await; + if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) = + app_ctx.interaction + { + if let Some(guild_id) = ctx.guild_id() { + if ctx.command().identifying_name != "finish_macro" { + let mut lock = ctx.data().recording_macros.write().await; - if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { - if command_macro.commands.len() >= MACRO_MAX_COMMANDS { - let _ = ctx.send(|m| { + if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { + if command_macro.commands.len() >= MACRO_MAX_COMMANDS { + let _ = ctx.send(|m| { m.ephemeral(true).content( format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS), ) }) .await; - } else { - let recorded = RecordedCommand { - action: None, - command_name: ctx.command().identifying_name.clone(), - options: Vec::from(app_ctx.args), - }; + } else { + let recorded = RecordedCommand { + action: None, + command_name: ctx.command().identifying_name.clone(), + options: Vec::from(app_ctx.args), + }; - command_macro.commands.push(recorded); + command_macro.commands.push(recorded); - let _ = ctx - .send(|m| m.ephemeral(true).content("Command recorded to macro")) - .await; + let _ = ctx + .send(|m| m.ephemeral(true).content("Command recorded to macro")) + .await; + } + + return false; } - - return false; } } }