changed a bunch of types so the macro run command works nicely

This commit is contained in:
2021-10-13 16:37:15 +01:00
parent 903daf65e6
commit a362a24cfc
13 changed files with 338 additions and 293 deletions

View File

@ -27,7 +27,7 @@ fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEm
#[aliases("invite")]
#[description("Get information about the bot")]
#[group("Info")]
async fn info(ctx: &Context, invoke: CommandInvoke) {
async fn info(ctx: &Context, invoke: &mut CommandInvoke) {
let prefix = ctx.prefix(invoke.guild_id()).await;
let current_user = ctx.cache.current_user();
let footer = footer(ctx);
@ -61,7 +61,7 @@ Use our dashboard: https://reminder-bot.com/",
#[command]
#[description("Details on supporting the bot and Patreon benefits")]
#[group("Info")]
async fn donate(ctx: &Context, invoke: CommandInvoke) {
async fn donate(ctx: &Context, invoke: &mut CommandInvoke) {
let footer = footer(ctx);
let _ = invoke
@ -94,7 +94,7 @@ Just $2 USD/month!
#[command]
#[description("Get the link to the online dashboard")]
#[group("Info")]
async fn dashboard(ctx: &Context, invoke: CommandInvoke) {
async fn dashboard(ctx: &Context, invoke: &mut CommandInvoke) {
let footer = footer(ctx);
let _ = invoke
@ -113,7 +113,7 @@ async fn dashboard(ctx: &Context, invoke: CommandInvoke) {
#[command]
#[description("View the current time in your selected timezone")]
#[group("Info")]
async fn clock(ctx: &Context, invoke: CommandInvoke) {
async fn clock(ctx: &Context, invoke: &mut CommandInvoke) {
let ud = ctx.user_data(&invoke.author_id()).await.unwrap();
let now = Utc::now().with_timezone(&ud.timezone());

View File

@ -2,18 +2,10 @@ use chrono::offset::Utc;
use chrono_tz::{Tz, TZ_VARIANTS};
use levenshtein::levenshtein;
use regex_command_attr::command;
use serenity::{
client::Context,
http::AttachmentType,
model::{
interactions::InteractionResponseType, misc::Mentionable,
prelude::InteractionApplicationCommandCallbackDataFlags,
},
};
use serenity::{client::Context, model::misc::Mentionable};
use crate::{
component_models::{ComponentDataModel, Restrict},
consts,
consts::THEME_COLOR,
framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue},
hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK},
@ -32,7 +24,7 @@ use crate::{
#[supports_dm(false)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
#[can_blacklist(false)]
async fn blacklist(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn blacklist(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let channel = match args.get("channel") {
@ -75,7 +67,7 @@ async fn blacklist(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
kind = "String",
required = false
)]
async fn timezone(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn timezone(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let mut user_data = ctx.user_data(invoke.author_id()).await.unwrap();
@ -182,7 +174,7 @@ You may want to use one of the popular timezones below, otherwise click [here](h
#[description("Configure a prefix for text-based commands (deprecated)")]
#[supports_dm(false)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
async fn prefix(ctx: &Context, invoke: CommandInvoke, args: String) {
async fn prefix(ctx: &Context, invoke: &mut CommandInvoke, args: String) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let guild_data = ctx.guild_data(invoke.guild_id().unwrap()).await.unwrap();
@ -226,7 +218,7 @@ async fn prefix(ctx: &Context, invoke: CommandInvoke, args: String) {
)]
#[supports_dm(false)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
async fn restrict(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn restrict(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let framework = ctx.data.read().await.get::<RegexFramework>().cloned().unwrap();
@ -310,10 +302,13 @@ async fn restrict(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
#[subcommand("run")]
#[description("Run a recorded macro")]
#[arg(name = "name", description = "Name of the macro to run", kind = "String", required = true)]
#[subcommand("delete")]
#[description("Delete a recorded macro")]
#[arg(name = "name", description = "Name of the macro to delete", kind = "String", required = true)]
#[supports_dm(false)]
#[hook(CHECK_MANAGED_PERMISSIONS_HOOK)]
async fn macro_cmd(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
let interaction = invoke.interaction().unwrap();
async fn macro_cmd(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
match args.subcommand.clone().unwrap().as_str() {
"record" => {
@ -322,10 +317,10 @@ async fn macro_cmd(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
{
let mut lock = macro_buffer.write().await;
let guild_id = interaction.guild_id.unwrap();
let guild_id = invoke.guild_id().unwrap();
lock.insert(
(guild_id, interaction.user.id),
(guild_id, invoke.author_id()),
CommandMacro {
guild_id,
name: args.get("name").unwrap().to_string(),
@ -335,25 +330,22 @@ async fn macro_cmd(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
);
}
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
.create_embed(|e| {
e
let _ = invoke
.respond(
&ctx,
CreateGenericResponse::new().ephemeral().embed(|e| {
e
.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)
})
})
})
}),
)
.await;
}
"finish" => {
let key = (interaction.guild_id.unwrap(), interaction.user.id);
let key = (invoke.guild_id().unwrap(), invoke.author_id());
let macro_buffer = ctx.data.read().await.get::<RecordingMacros>().cloned().unwrap();
{
@ -361,28 +353,22 @@ Any commands ran as part of recording will be inconsequential")
let contained = lock.get(&key);
if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) {
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.create_embed(|e| {
e.title("No Macro Recorded")
.description(
"Use `/macro record` to start recording a macro",
)
.color(*THEME_COLOR)
})
})
})
let _ = invoke
.respond(
&ctx,
CreateGenericResponse::new().embed(|e| {
e.title("No Macro Recorded")
.description("Use `/macro record` to start recording a macro")
.color(*THEME_COLOR)
}),
)
.await;
} else {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let command_macro = contained.unwrap();
let json = serde_json::to_string(&command_macro.commands).unwrap();
sqlx::query!(
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
"INSERT INTO macro (guild_id, name, description, commands) VALUES (?, ?, ?, ?)",
command_macro.guild_id.0,
command_macro.name,
command_macro.description,
@ -392,17 +378,15 @@ Any commands ran as part of recording will be inconsequential")
.await
.unwrap();
let _ = interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.create_embed(|e| {
e.title("Macro Recorded")
.description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR)
})
})
})
let _ = invoke
.respond(
&ctx,
CreateGenericResponse::new().embed(|e| {
e.title("Macro Recorded")
.description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR)
}),
)
.await;
}
}
@ -413,54 +397,45 @@ Any commands ran as part of recording will be inconsequential")
}
}
"list" => {}
"run" => {}
"run" => {
let macro_name = args.get("name").unwrap().to_string();
match sqlx::query!(
"SELECT commands FROM macro WHERE guild_id = ? AND name = ?",
invoke.guild_id().unwrap().0,
macro_name
)
.fetch_one(&pool)
.await
{
Ok(row) => {
invoke.defer(&ctx).await;
let commands: Vec<CommandOptions> =
serde_json::from_str(&row.commands).unwrap();
let framework = ctx.data.read().await.get::<RegexFramework>().cloned().unwrap();
for command in commands {
framework.run_command_from_options(ctx, invoke, command).await;
}
}
Err(sqlx::Error::RowNotFound) => {
let _ = invoke
.respond(
&ctx,
CreateGenericResponse::new()
.content(format!("Macro \"{}\" not found", macro_name)),
)
.await;
}
Err(e) => {
panic!("{}", e);
}
}
}
"delete" => {}
_ => {}
}
}
#[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) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let mut channel_data = ctx.channel_data(invoke.channel_id()).await.unwrap();
let (username, avatar) = (
args.get("username").map_or("Reminder".to_string(), |i| i.to_string()),
args.get("url").map_or(consts::DEFAULT_AVATAR, |i| AttachmentType::Image(&i.to_string())),
);
if let (Some(id), Some(token)) = (channel_data.webhook_id, channel_data.webhook_token) {
match ctx.http.get_webhook_with_token(id, &token).await {
Ok(mut webhook) => {
webhook.edit(&ctx, Some(username), Some(avatar)).await;
}
Err(_) => {
let webhook = invoke
.channel_id()
.create_webhook_with_avatar(&ctx, username, avatar)
.await
.unwrap();
channel_data.webhook_token = webhook.token;
channel_data.webhook_id = Some(webhook.id.0);
channel_data.commit_changes(&pool).await;
}
}
} else {
let webhook =
invoke.channel_id().create_webhook_with_avatar(&ctx, username, avatar).await.unwrap();
channel_data.webhook_token = webhook.token;
channel_data.webhook_id = Some(webhook.id.0);
channel_data.commit_changes(&pool).await;
}
}

View File

@ -8,11 +8,7 @@ use chrono::NaiveDateTime;
use chrono_tz::Tz;
use num_integer::Integer;
use regex_command_attr::command;
use serenity::{
builder::{CreateEmbed, CreateInteractionResponse},
client::Context,
model::{channel::Channel, interactions::InteractionResponseType},
};
use serenity::{builder::CreateEmbed, client::Context, model::channel::Channel};
use crate::{
check_subscription_on_message,
@ -54,7 +50,7 @@ use crate::{
)]
#[supports_dm(false)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
async fn pause(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn pause(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let timezone = UserData::timezone_of(&invoke.author_id(), &pool).await;
@ -140,7 +136,7 @@ async fn pause(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
required = false
)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
async fn offset(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn offset(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let combined_time = args.get("hours").map_or(0, |h| h.as_i64().unwrap() * 3600)
@ -217,7 +213,7 @@ WHERE FIND_IN_SET(channels.`channel`, ?)",
required = false
)]
#[hook(CHECK_GUILD_PERMISSIONS_HOOK)]
async fn nudge(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn nudge(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let combined_time = args.get("minutes").map_or(0, |m| m.as_i64().unwrap() * 60)
@ -269,7 +265,7 @@ async fn nudge(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
required = false
)]
#[hook(CHECK_MANAGED_PERMISSIONS_HOOK)]
async fn look(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn look(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
let timezone = UserData::timezone_of(&invoke.author_id(), &pool).await;
@ -362,22 +358,14 @@ async fn look(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
#[command("del")]
#[description("Delete reminders")]
#[hook(CHECK_MANAGED_PERMISSIONS_HOOK)]
async fn delete(ctx: &Context, invoke: CommandInvoke, _args: CommandOptions) {
let interaction = invoke.interaction().unwrap();
async fn delete(ctx: &Context, invoke: &mut CommandInvoke, _args: CommandOptions) {
let timezone = ctx.timezone(invoke.author_id()).await;
let timezone = ctx.timezone(interaction.user.id).await;
let reminders = Reminder::from_guild(ctx, interaction.guild_id, interaction.user.id).await;
let reminders = Reminder::from_guild(ctx, invoke.guild_id(), invoke.author_id()).await;
let resp = show_delete_page(&reminders, 0, timezone);
let _ = interaction
.create_interaction_response(&ctx, |r| {
*r = resp;
r.kind(InteractionResponseType::ChannelMessageWithSource)
})
.await
.unwrap();
let _ = invoke.respond(&ctx, resp).await;
}
pub fn max_delete_page(reminders: &[Reminder], timezone: &Tz) -> usize {
@ -406,22 +394,16 @@ pub fn show_delete_page(
reminders: &[Reminder],
page: usize,
timezone: Tz,
) -> CreateInteractionResponse {
) -> CreateGenericResponse {
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.interaction_response_data(|response| {
response.embeds(vec![embed]).components(|comp| {
return CreateGenericResponse::new()
.embed(|e| e.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR))
.components(|comp| {
pager.create_button_row(0, comp);
comp
})
});
return response;
});
}
let pages = max_delete_page(reminders, &timezone);
@ -470,16 +452,14 @@ pub fn show_delete_page(
let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone });
let mut embed = CreateEmbed::default();
embed
.title("Delete Reminders")
.description(display)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR);
let mut response = CreateInteractionResponse::default();
response.interaction_response_data(|d| {
d.embeds(vec![embed]).components(|comp| {
CreateGenericResponse::new()
.embed(|e| {
e.title("Delete Reminders")
.description(display)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR)
})
.components(|comp| {
pager.create_button_row(pages, comp);
comp.create_action_row(|row| {
@ -511,8 +491,6 @@ pub fn show_delete_page(
})
})
})
});
response
}
#[command("timer")]
@ -526,7 +504,7 @@ pub fn show_delete_page(
#[description("Delete a timer")]
#[arg(name = "name", description = "Name of the timer to delete", kind = "String", required = true)]
#[hook(CHECK_MANAGED_PERMISSIONS_HOOK)]
async fn timer(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
async fn timer(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
fn time_difference(start_time: NaiveDateTime) -> String {
let unix_time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as i64;
let now = NaiveDateTime::from_timestamp(unix_time, 0);
@ -692,18 +670,10 @@ DELETE FROM timers WHERE owner = ? AND name = ?
required = false
)]
#[hook(CHECK_MANAGED_PERMISSIONS_HOOK)]
async fn remind(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
let interaction = invoke.interaction().unwrap();
async fn remind(ctx: &Context, invoke: &mut CommandInvoke, args: CommandOptions) {
invoke.defer(&ctx).await;
// defer response since processing times can take some time
interaction
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::DeferredChannelMessageWithSource)
})
.await
.unwrap();
let user_data = ctx.user_data(interaction.user.id).await.unwrap();
let user_data = ctx.user_data(invoke.author_id()).await.unwrap();
let timezone = user_data.timezone();
let time = {
@ -728,7 +698,7 @@ async fn remind(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
.unwrap_or_default();
if list.is_empty() {
vec![ReminderScope::Channel(interaction.channel_id.0)]
vec![ReminderScope::Channel(invoke.channel_id().0)]
} else {
list
}
@ -751,7 +721,7 @@ async fn remind(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
}
};
let mut builder = MultiReminderBuilder::new(ctx, interaction.guild_id)
let mut builder = MultiReminderBuilder::new(ctx, invoke.guild_id())
.author(user_data)
.content(content)
.time(time)
@ -764,16 +734,19 @@ async fn remind(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
let embed = create_response(successes, errors, time);
interaction
.edit_original_interaction_response(&ctx, |r| r.add_embed(embed))
.await
.unwrap();
let _ = invoke
.respond(
&ctx,
CreateGenericResponse::new().embed(|c| {
*c = embed;
c
}),
)
.await;
}
None => {
let _ = interaction
.edit_original_interaction_response(&ctx, |r| {
r.content("Time could not be processed.")
})
let _ = invoke
.respond(&ctx, CreateGenericResponse::new().content("Time could not be processed"))
.await;
}
}

View File

@ -1,9 +1,5 @@
use regex_command_attr::command;
use serenity::{
builder::{CreateEmbed, CreateInteractionResponse},
client::Context,
model::interactions::InteractionResponseType,
};
use serenity::client::Context;
use crate::{
component_models::{
@ -53,7 +49,7 @@ use crate::{
)]
#[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: &mut CommandInvoke, args: CommandOptions) {
if invoke.guild_id().is_none() && args.subcommand_group != Some("user".to_string()) {
let _ = invoke
.respond(
@ -106,15 +102,7 @@ async fn todo(ctx: &Context, invoke: CommandInvoke, args: CommandOptions) {
let resp = show_todo_page(&values, 0, keys.0, keys.1, keys.2);
let interaction = invoke.interaction().unwrap();
let _ = interaction
.create_interaction_response(&ctx, |r| {
*r = resp;
r.kind(InteractionResponseType::ChannelMessageWithSource)
})
.await
.unwrap();
let _ = invoke.respond(&ctx, resp).await;
}
}
}
@ -147,7 +135,7 @@ pub fn show_todo_page(
user_id: Option<u64>,
channel_id: Option<u64>,
guild_id: Option<u64>,
) -> CreateInteractionResponse {
) -> CreateGenericResponse {
let pager = TodoPager::new(page, user_id, channel_id, guild_id);
let pages = max_todo_page(todo_values);
@ -204,16 +192,14 @@ pub fn show_todo_page(
let todo_selector =
ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id });
let mut embed = CreateEmbed::default();
embed
.title(format!("{} Todo List", title))
.description(display)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR);
let mut response = CreateInteractionResponse::default();
response.interaction_response_data(|d| {
d.embeds(vec![embed]).components(|comp| {
CreateGenericResponse::new()
.embed(|e| {
e.title(format!("{} Todo List", title))
.description(display)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR)
})
.components(|comp| {
pager.create_button_row(pages, comp);
comp.create_action_row(|row| {
@ -232,7 +218,4 @@ pub fn show_todo_page(
})
})
})
});
response
}