macro stuff

This commit is contained in:
jellywx 2021-10-16 19:18:16 +01:00
parent 4490f19c04
commit 6cf660c7ee
7 changed files with 236 additions and 31 deletions

View File

@ -38,3 +38,13 @@ __Other Variables__
* `SHARD_COUNT` - default `None`, accepts the number of shards that are being ran * `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 * `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 * `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

View File

@ -2,11 +2,14 @@ use chrono::offset::Utc;
use chrono_tz::{Tz, TZ_VARIANTS}; use chrono_tz::{Tz, TZ_VARIANTS};
use levenshtein::levenshtein; use levenshtein::levenshtein;
use regex_command_attr::command; use regex_command_attr::command;
use serenity::{client::Context, model::misc::Mentionable}; use serenity::{
client::Context,
model::{id::GuildId, misc::Mentionable},
};
use crate::{ use crate::{
component_models::{ComponentDataModel, Restrict}, component_models::{pager::Pager, ComponentDataModel, Restrict},
consts::THEME_COLOR, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue}, framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue},
hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK}, hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK},
models::{channel_data::ChannelData, command_macro::CommandMacro, CtxData}, models::{channel_data::ChannelData, command_macro::CommandMacro, CtxData},
@ -396,7 +399,13 @@ Any commands ran as part of recording will be inconsequential")
lock.remove(&key); lock.remove(&key);
} }
} }
"list" => {} "list" => {
let macros = CommandMacro::from_guild(ctx, invoke.guild_id().unwrap()).await;
let resp = show_macro_page(&macros, 0, invoke.guild_id().unwrap());
invoke.respond(&ctx, resp).await;
}
"run" => { "run" => {
let macro_name = args.get("name").unwrap().to_string(); 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
})
}

View File

@ -8,7 +8,11 @@ use chrono::NaiveDateTime;
use chrono_tz::Tz; use chrono_tz::Tz;
use num_integer::Integer; use num_integer::Integer;
use regex_command_attr::command; 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::{ use crate::{
check_subscription_on_message, 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 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; let _ = invoke.respond(&ctx, resp).await;
} }
@ -394,8 +398,9 @@ pub fn show_delete_page(
reminders: &[Reminder], reminders: &[Reminder],
page: usize, page: usize,
timezone: Tz, timezone: Tz,
author_id: UserId,
) -> CreateGenericResponse { ) -> CreateGenericResponse {
let pager = Pager::new(page, DelData { timezone }); let pager = Pager::new(page, DelData { author_id, timezone });
if reminders.is_empty() { if reminders.is_empty() {
return CreateGenericResponse::new() return CreateGenericResponse::new()
@ -450,7 +455,7 @@ pub fn show_delete_page(
let display = display_vec.join("\n"); 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() CreateGenericResponse::new()
.embed(|e| { .embed(|e| {

View File

@ -37,6 +37,7 @@ pub enum ComponentDataModel {
LookPager(Pager<LookData>), LookPager(Pager<LookData>),
DelPager(Pager<DelData>), DelPager(Pager<DelData>),
TodoPager(Pager<TodoData>), TodoPager(Pager<TodoData>),
MacroPager(Pager<GuildId>),
} }
impl ComponentDataModel { impl ComponentDataModel {
@ -89,7 +90,16 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
.await .await
.unwrap(); .unwrap();
} else { } 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) => { ComponentDataModel::LookPager(pager) => {
@ -168,16 +178,33 @@ INSERT IGNORE INTO roles (role, name, guild_id) VALUES (?, \"Role\", (SELECT id
.await; .await;
} }
ComponentDataModel::DelPager(pager) => { ComponentDataModel::DelPager(pager) => {
if component.user.id == pager.data.author_id {
let reminders = let reminders =
Reminder::from_guild(ctx, component.guild_id, component.user.id).await; 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 = let resp = show_delete_page(
show_delete_page(&reminders, pager.next_page(max_pages), pager.data.timezone); &reminders,
pager.next_page(max_pages),
pager.data.timezone,
pager.data.author_id,
);
let mut invoke = CommandInvoke::component(component); let mut invoke = CommandInvoke::component(component);
let _ = invoke.respond(&ctx, resp).await; 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) => { ComponentDataModel::DelSelector(selector) => {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap(); 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 = let reminders =
Reminder::from_guild(ctx, component.guild_id, component.user.id).await; 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 mut invoke = CommandInvoke::component(component);
let _ = invoke.respond(&ctx, resp).await; 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 mut invoke = CommandInvoke::component(component);
let _ = invoke.respond(&ctx, resp).await; let _ = invoke.respond(&ctx, resp).await;
} }
ComponentDataModel::MacroPager(pager) => {}
} }
} }
} }
@ -275,6 +308,7 @@ pub struct Restrict {
pub struct DelSelector { pub struct DelSelector {
pub page: usize, pub page: usize,
pub timezone: Tz, pub timezone: Tz,
pub author_id: UserId,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]

View File

@ -2,7 +2,10 @@ use chrono_tz::Tz;
use rmp_serde::Serializer; use rmp_serde::Serializer;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::*; 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}; use crate::{component_models::ComponentDataModel, models::reminder::look_flags::LookFlags};
@ -102,6 +105,7 @@ pub struct LookData {
#[derive(Deserialize, Serialize, Clone)] #[derive(Deserialize, Serialize, Clone)]
pub struct DelData { pub struct DelData {
pub author_id: UserId,
pub timezone: Tz, pub timezone: Tz,
} }

View File

@ -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::TIMER_COMMAND)
.add_command(&reminder_cmds::REMIND_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("natural", &reminder_cmds::NATURAL_COMMAND)
.add_command("n", &reminder_cmds::NATURAL_COMMAND) .add_command("n", &reminder_cmds::NATURAL_COMMAND)
.add_command("", &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::TIMEZONE_COMMAND)
.add_command(&moderation_cmds::PREFIX_COMMAND) .add_command(&moderation_cmds::PREFIX_COMMAND)
.add_command(&moderation_cmds::MACRO_CMD_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::CHECK_SELF_PERMISSIONS_HOOK)
.add_hook(&hooks::MACRO_CHECK_HOOK) .add_hook(&hooks::MACRO_CHECK_HOOK)
.build(); .build();

View File

@ -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 struct CommandMacro {
pub guild_id: GuildId, pub guild_id: GuildId,
@ -8,3 +8,23 @@ pub struct CommandMacro {
pub description: Option<String>, pub description: Option<String>,
pub commands: Vec<CommandOptions>, 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>>()
}
}