macro stuff
This commit is contained in:
		
							
								
								
									
										10
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								README.md
									
									
									
									
									
								
							| @@ -38,3 +38,13 @@ __Other Variables__ | ||||
| * `SHARD_COUNT` - default `None`, accepts the number of shards that are being ran | ||||
| * `SHARD_RANGE` - default `None`, if `SHARD_COUNT` is specified, specifies what range of shards to start on this process  | ||||
| * `DM_ENABLED` - default `1`, if `1`, Reminder Bot will respond to direct messages | ||||
|  | ||||
| ### Todo List | ||||
|  | ||||
| * Implement remainder of the `macro` command | ||||
| * Convert aliases to macros | ||||
| * Block users from interacting with another users' components | ||||
| * Split out framework | ||||
| * Help command | ||||
| * Change all db keys to be discord IDs | ||||
| * Test everything | ||||
|   | ||||
| @@ -2,11 +2,14 @@ use chrono::offset::Utc; | ||||
| use chrono_tz::{Tz, TZ_VARIANTS}; | ||||
| use levenshtein::levenshtein; | ||||
| use regex_command_attr::command; | ||||
| use serenity::{client::Context, model::misc::Mentionable}; | ||||
| use serenity::{ | ||||
|     client::Context, | ||||
|     model::{id::GuildId, misc::Mentionable}, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     component_models::{ComponentDataModel, Restrict}, | ||||
|     consts::THEME_COLOR, | ||||
|     component_models::{pager::Pager, ComponentDataModel, Restrict}, | ||||
|     consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, | ||||
|     framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue}, | ||||
|     hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK}, | ||||
|     models::{channel_data::ChannelData, command_macro::CommandMacro, CtxData}, | ||||
| @@ -335,11 +338,11 @@ async fn macro_cmd(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptio | ||||
|                     &ctx, | ||||
|                     CreateGenericResponse::new().ephemeral().embed(|e| { | ||||
|                         e | ||||
|                                     .title("Macro Recording Started") | ||||
|                                     .description( | ||||
|                             .title("Macro Recording Started") | ||||
|                             .description( | ||||
| "Run up to 5 commands, or type `/macro finish` to stop at any point. | ||||
| Any commands ran as part of recording will be inconsequential") | ||||
|                                     .color(*THEME_COLOR) | ||||
|                             .color(*THEME_COLOR) | ||||
|                     }), | ||||
|                 ) | ||||
|                 .await; | ||||
| @@ -396,7 +399,13 @@ Any commands ran as part of recording will be inconsequential") | ||||
|                 lock.remove(&key); | ||||
|             } | ||||
|         } | ||||
|         "list" => {} | ||||
|         "list" => { | ||||
|             let macros = CommandMacro::from_guild(ctx, invoke.guild_id().unwrap()).await; | ||||
|  | ||||
|             let resp = show_macro_page(¯os, 0, invoke.guild_id().unwrap()); | ||||
|  | ||||
|             invoke.respond(&ctx, resp).await; | ||||
|         } | ||||
|         "run" => { | ||||
|             let macro_name = args.get("name").unwrap().to_string(); | ||||
|  | ||||
| @@ -435,7 +444,137 @@ Any commands ran as part of recording will be inconsequential") | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         "delete" => {} | ||||
|         "delete" => { | ||||
|             let macro_name = args.get("name").unwrap().to_string(); | ||||
|  | ||||
|             match sqlx::query!( | ||||
|                 "SELECT id FROM macro WHERE guild_id = ? AND name = ?", | ||||
|                 invoke.guild_id().unwrap().0, | ||||
|                 macro_name | ||||
|             ) | ||||
|             .fetch_one(&pool) | ||||
|             .await | ||||
|             { | ||||
|                 Ok(row) => { | ||||
|                     sqlx::query!("DELETE FROM macro WHERE id = ?", row.id).execute(&pool).await; | ||||
|  | ||||
|                     let _ = invoke | ||||
|                         .respond( | ||||
|                             &ctx, | ||||
|                             CreateGenericResponse::new() | ||||
|                                 .content(format!("Macro \"{}\" deleted", macro_name)), | ||||
|                         ) | ||||
|                         .await; | ||||
|                 } | ||||
|  | ||||
|                 Err(sqlx::Error::RowNotFound) => { | ||||
|                     let _ = invoke | ||||
|                         .respond( | ||||
|                             &ctx, | ||||
|                             CreateGenericResponse::new() | ||||
|                                 .content(format!("Macro \"{}\" not found", macro_name)), | ||||
|                         ) | ||||
|                         .await; | ||||
|                 } | ||||
|  | ||||
|                 Err(e) => { | ||||
|                     panic!("{}", e); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         _ => {} | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub fn max_macro_page(macros: &[CommandMacro]) -> usize { | ||||
|     let mut skipped_char_count = 0; | ||||
|  | ||||
|     macros | ||||
|         .iter() | ||||
|         .map(|m| { | ||||
|             if let Some(description) = &m.description { | ||||
|                 format!("**{}**\n- *{}*\n- Has {} commands", m.name, description, m.commands.len()) | ||||
|             } else { | ||||
|                 format!("**{}**\n- Has {} commands", m.name, m.commands.len()) | ||||
|             } | ||||
|         }) | ||||
|         .fold(1, |mut pages, p| { | ||||
|             skipped_char_count += p.len(); | ||||
|  | ||||
|             if skipped_char_count > EMBED_DESCRIPTION_MAX_LENGTH { | ||||
|                 skipped_char_count = p.len(); | ||||
|                 pages += 1; | ||||
|             } | ||||
|  | ||||
|             pages | ||||
|         }) | ||||
| } | ||||
|  | ||||
| fn show_macro_page( | ||||
|     macros: &[CommandMacro], | ||||
|     page: usize, | ||||
|     guild_id: GuildId, | ||||
| ) -> CreateGenericResponse { | ||||
|     let pager = Pager::new(page, guild_id); | ||||
|  | ||||
|     if macros.is_empty() { | ||||
|         return CreateGenericResponse::new().embed(|e| { | ||||
|             e.title("Macros") | ||||
|                 .description("No Macros Set Up. Use `/macro record` to get started.") | ||||
|                 .color(*THEME_COLOR) | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let pages = max_macro_page(macros); | ||||
|  | ||||
|     let mut page = page; | ||||
|     if page >= pages { | ||||
|         page = pages - 1; | ||||
|     } | ||||
|  | ||||
|     let mut char_count = 0; | ||||
|     let mut skipped_char_count = 0; | ||||
|  | ||||
|     let mut skipped_pages = 0; | ||||
|  | ||||
|     let display_vec: Vec<String> = macros | ||||
|         .iter() | ||||
|         .map(|m| { | ||||
|             if let Some(description) = &m.description { | ||||
|                 format!("**{}**\n- *{}*\n- Has {} commands", m.name, description, m.commands.len()) | ||||
|             } else { | ||||
|                 format!("**{}**\n- Has {} commands", m.name, m.commands.len()) | ||||
|             } | ||||
|         }) | ||||
|         .skip_while(|p| { | ||||
|             skipped_char_count += p.len(); | ||||
|  | ||||
|             if skipped_char_count > EMBED_DESCRIPTION_MAX_LENGTH { | ||||
|                 skipped_char_count = p.len(); | ||||
|                 skipped_pages += 1; | ||||
|             } | ||||
|  | ||||
|             skipped_pages < page | ||||
|         }) | ||||
|         .take_while(|p| { | ||||
|             char_count += p.len(); | ||||
|  | ||||
|             char_count < EMBED_DESCRIPTION_MAX_LENGTH | ||||
|         }) | ||||
|         .collect::<Vec<String>>(); | ||||
|  | ||||
|     let display = display_vec.join("\n"); | ||||
|  | ||||
|     CreateGenericResponse::new() | ||||
|         .embed(|e| { | ||||
|             e.title("Macros") | ||||
|                 .description(display) | ||||
|                 .footer(|f| f.text(format!("Page {} of {}", page + 1, pages))) | ||||
|                 .color(*THEME_COLOR) | ||||
|         }) | ||||
|         .components(|comp| { | ||||
|             pager.create_button_row(pages, comp); | ||||
|  | ||||
|             comp | ||||
|         }) | ||||
| } | ||||
|   | ||||
| @@ -8,7 +8,11 @@ use chrono::NaiveDateTime; | ||||
| use chrono_tz::Tz; | ||||
| use num_integer::Integer; | ||||
| use regex_command_attr::command; | ||||
| use serenity::{builder::CreateEmbed, client::Context, model::channel::Channel}; | ||||
| use serenity::{ | ||||
|     builder::CreateEmbed, | ||||
|     client::Context, | ||||
|     model::{channel::Channel, id::UserId}, | ||||
| }; | ||||
|  | ||||
| use crate::{ | ||||
|     check_subscription_on_message, | ||||
| @@ -363,7 +367,7 @@ async fn delete(ctx: &Context, invoke: &mut CommandInvoke, _args: CommandOptions | ||||
|  | ||||
|     let reminders = Reminder::from_guild(ctx, invoke.guild_id(), invoke.author_id()).await; | ||||
|  | ||||
|     let resp = show_delete_page(&reminders, 0, timezone); | ||||
|     let resp = show_delete_page(&reminders, 0, timezone, invoke.author_id()); | ||||
|  | ||||
|     let _ = invoke.respond(&ctx, resp).await; | ||||
| } | ||||
| @@ -394,8 +398,9 @@ pub fn show_delete_page( | ||||
|     reminders: &[Reminder], | ||||
|     page: usize, | ||||
|     timezone: Tz, | ||||
|     author_id: UserId, | ||||
| ) -> CreateGenericResponse { | ||||
|     let pager = Pager::new(page, DelData { timezone }); | ||||
|     let pager = Pager::new(page, DelData { author_id, timezone }); | ||||
|  | ||||
|     if reminders.is_empty() { | ||||
|         return CreateGenericResponse::new() | ||||
| @@ -450,7 +455,7 @@ pub fn show_delete_page( | ||||
|  | ||||
|     let display = display_vec.join("\n"); | ||||
|  | ||||
|     let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone }); | ||||
|     let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone, author_id }); | ||||
|  | ||||
|     CreateGenericResponse::new() | ||||
|         .embed(|e| { | ||||
|   | ||||
| @@ -37,6 +37,7 @@ pub enum ComponentDataModel { | ||||
|     LookPager(Pager<LookData>), | ||||
|     DelPager(Pager<DelData>), | ||||
|     TodoPager(Pager<TodoData>), | ||||
|     MacroPager(Pager<GuildId>), | ||||
| } | ||||
|  | ||||
| impl ComponentDataModel { | ||||
| @@ -89,7 +90,16 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id | ||||
|                         .await | ||||
|                         .unwrap(); | ||||
|                 } else { | ||||
|                     // tell them they cant do this | ||||
|                     component | ||||
|                         .create_interaction_response(&ctx, |r| { | ||||
|                             r.kind(InteractionResponseType::ChannelMessageWithSource) | ||||
|                                 .interaction_response_data(|response| response | ||||
|                                     .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) | ||||
|                                     .content("Only the original command user can interact with this message") | ||||
|                                 ) | ||||
|                         }) | ||||
|                         .await | ||||
|                         .unwrap(); | ||||
|                 } | ||||
|             } | ||||
|             ComponentDataModel::LookPager(pager) => { | ||||
| @@ -168,16 +178,33 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id | ||||
|                     .await; | ||||
|             } | ||||
|             ComponentDataModel::DelPager(pager) => { | ||||
|                 let reminders = | ||||
|                     Reminder::from_guild(ctx, component.guild_id, component.user.id).await; | ||||
|                 if component.user.id == pager.data.author_id { | ||||
|                     let reminders = | ||||
|                         Reminder::from_guild(ctx, component.guild_id, component.user.id).await; | ||||
|  | ||||
|                 let max_pages = max_delete_page(&reminders, &pager.data.timezone); | ||||
|                     let max_pages = max_delete_page(&reminders, &pager.data.timezone); | ||||
|  | ||||
|                 let resp = | ||||
|                     show_delete_page(&reminders, pager.next_page(max_pages), pager.data.timezone); | ||||
|                     let resp = show_delete_page( | ||||
|                         &reminders, | ||||
|                         pager.next_page(max_pages), | ||||
|                         pager.data.timezone, | ||||
|                         pager.data.author_id, | ||||
|                     ); | ||||
|  | ||||
|                 let mut invoke = CommandInvoke::component(component); | ||||
|                 let _ = invoke.respond(&ctx, resp).await; | ||||
|                     let mut invoke = CommandInvoke::component(component); | ||||
|                     let _ = invoke.respond(&ctx, resp).await; | ||||
|                 } else { | ||||
|                     component | ||||
|                         .create_interaction_response(&ctx, |r| { | ||||
|                             r.kind(InteractionResponseType::ChannelMessageWithSource) | ||||
|                                 .interaction_response_data(|response| response | ||||
|                                     .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL) | ||||
|                                     .content("Only the original command user can interact with this message") | ||||
|                                 ) | ||||
|                         }) | ||||
|                         .await | ||||
|                         .unwrap(); | ||||
|                 } | ||||
|             } | ||||
|             ComponentDataModel::DelSelector(selector) => { | ||||
|                 let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap(); | ||||
| @@ -191,7 +218,12 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id | ||||
|                 let reminders = | ||||
|                     Reminder::from_guild(ctx, component.guild_id, component.user.id).await; | ||||
|  | ||||
|                 let resp = show_delete_page(&reminders, selector.page, selector.timezone); | ||||
|                 let resp = show_delete_page( | ||||
|                     &reminders, | ||||
|                     selector.page, | ||||
|                     selector.timezone, | ||||
|                     selector.author_id, | ||||
|                 ); | ||||
|  | ||||
|                 let mut invoke = CommandInvoke::component(component); | ||||
|                 let _ = invoke.respond(&ctx, resp).await; | ||||
| @@ -260,6 +292,7 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id | ||||
|                 let mut invoke = CommandInvoke::component(component); | ||||
|                 let _ = invoke.respond(&ctx, resp).await; | ||||
|             } | ||||
|             ComponentDataModel::MacroPager(pager) => {} | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -275,6 +308,7 @@ pub struct Restrict { | ||||
| pub struct DelSelector { | ||||
|     pub page: usize, | ||||
|     pub timezone: Tz, | ||||
|     pub author_id: UserId, | ||||
| } | ||||
|  | ||||
| #[derive(Serialize, Deserialize)] | ||||
|   | ||||
| @@ -2,7 +2,10 @@ use chrono_tz::Tz; | ||||
| use rmp_serde::Serializer; | ||||
| use serde::{Deserialize, Serialize}; | ||||
| use serde_repr::*; | ||||
| use serenity::{builder::CreateComponents, model::interactions::message_component::ButtonStyle}; | ||||
| use serenity::{ | ||||
|     builder::CreateComponents, | ||||
|     model::{id::UserId, interactions::message_component::ButtonStyle}, | ||||
| }; | ||||
|  | ||||
| use crate::{component_models::ComponentDataModel, models::reminder::look_flags::LookFlags}; | ||||
|  | ||||
| @@ -102,6 +105,7 @@ pub struct LookData { | ||||
|  | ||||
| #[derive(Deserialize, Serialize, Clone)] | ||||
| pub struct DelData { | ||||
|     pub author_id: UserId, | ||||
|     pub timezone: Tz, | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -305,9 +305,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|         .add_command(&reminder_cmds::TIMER_COMMAND) | ||||
|         .add_command(&reminder_cmds::REMIND_COMMAND) | ||||
|         /* | ||||
|         .add_command("r", &reminder_cmds::REMIND_COMMAND) | ||||
|         .add_command("interval", &reminder_cmds::INTERVAL_COMMAND) | ||||
|         .add_command("i", &reminder_cmds::INTERVAL_COMMAND) | ||||
|         .add_command("natural", &reminder_cmds::NATURAL_COMMAND) | ||||
|         .add_command("n", &reminder_cmds::NATURAL_COMMAND) | ||||
|         .add_command("", &reminder_cmds::NATURAL_COMMAND) | ||||
| @@ -326,10 +323,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|         .add_command(&moderation_cmds::TIMEZONE_COMMAND) | ||||
|         .add_command(&moderation_cmds::PREFIX_COMMAND) | ||||
|         .add_command(&moderation_cmds::MACRO_CMD_COMMAND) | ||||
|         /* | ||||
|         .add_command("alias", &moderation_cmds::ALIAS_COMMAND) | ||||
|         .add_command("a", &moderation_cmds::ALIAS_COMMAND) | ||||
|         */ | ||||
|         .add_hook(&hooks::CHECK_SELF_PERMISSIONS_HOOK) | ||||
|         .add_hook(&hooks::MACRO_CHECK_HOOK) | ||||
|         .build(); | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use serenity::model::id::GuildId; | ||||
| use serenity::{client::Context, model::id::GuildId}; | ||||
|  | ||||
| use crate::framework::CommandOptions; | ||||
| use crate::{framework::CommandOptions, SQLPool}; | ||||
|  | ||||
| pub struct CommandMacro { | ||||
|     pub guild_id: GuildId, | ||||
| @@ -8,3 +8,23 @@ pub struct CommandMacro { | ||||
|     pub description: Option<String>, | ||||
|     pub commands: Vec<CommandOptions>, | ||||
| } | ||||
|  | ||||
| impl CommandMacro { | ||||
|     pub async fn from_guild(ctx: &Context, guild_id: impl Into<GuildId>) -> Vec<Self> { | ||||
|         let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap(); | ||||
|         let guild_id = guild_id.into(); | ||||
|  | ||||
|         sqlx::query!("SELECT * FROM macro WHERE guild_id = ?", guild_id.0) | ||||
|             .fetch_all(&pool) | ||||
|             .await | ||||
|             .unwrap() | ||||
|             .iter() | ||||
|             .map(|row| Self { | ||||
|                 guild_id: GuildId(row.guild_id), | ||||
|                 name: row.name.clone(), | ||||
|                 description: row.description.clone(), | ||||
|                 commands: serde_json::from_str(&row.commands).unwrap(), | ||||
|             }) | ||||
|             .collect::<Vec<Self>>() | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user