macro stuff
This commit is contained in:
parent
4490f19c04
commit
6cf660c7ee
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_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
|
||||||
|
@ -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(¯os, 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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -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| {
|
||||||
|
@ -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)]
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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();
|
||||||
|
@ -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>>()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user