Wip commit

This commit is contained in:
jude
2024-01-06 19:48:17 +00:00
parent cce0de7c75
commit e4e9af2bb4
37 changed files with 1051 additions and 1366 deletions

View File

@ -26,7 +26,7 @@ FROM macro
WHERE
guild_id = (SELECT id FROM guilds WHERE guild = ?)
AND name LIKE CONCAT(?, '%')",
ctx.guild_id().unwrap().0,
ctx.guild_id().unwrap().get(),
partial,
)
.fetch_all(&ctx.data().database)

View File

@ -18,7 +18,7 @@ pub async fn delete_macro(
match sqlx::query!(
"
SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
ctx.guild_id().unwrap().0,
ctx.guild_id().unwrap().get(),
name
)
.fetch_one(&ctx.data().database)

View File

@ -1,4 +1,7 @@
use poise::CreateReply;
use poise::{
serenity_prelude::{CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use crate::{
component_models::pager::{MacroPager, Pager},
@ -20,11 +23,7 @@ pub async fn list_macro(ctx: Context<'_>) -> Result<(), Error> {
let resp = show_macro_page(&macros, 0);
ctx.send(|m| {
*m = resp;
m
})
.await?;
ctx.send(resp).await?;
Ok(())
}
@ -37,15 +36,12 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
let pager = MacroPager::new(page);
if macros.is_empty() {
let mut reply = CreateReply::default();
reply.embed(|e| {
e.title("Macros")
return CreateReply::default().embed(
CreateEmbed::new()
.title("Macros")
.description("No Macros Set Up. Use `/macro record` to get started.")
.color(*THEME_COLOR)
});
return reply;
.color(*THEME_COLOR),
);
}
let pages = max_macro_page(macros);
@ -70,20 +66,13 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
}
});
let mut reply = CreateReply::default();
reply
.embed(|e| {
e.title("Macros")
CreateReply::default()
.embed(
CreateEmbed::new()
.title("Macros")
.fields(fields)
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR)
})
.components(|comp| {
pager.create_button_row(pages, comp);
comp
});
reply
.footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
)
.components(vec![pager.create_button_row(pages)])
}

View File

@ -1,5 +1,5 @@
use lazy_regex::regex;
use poise::serenity_prelude::CommandOptionType;
use poise::{serenity_prelude::CommandOptionType, CreateReply};
use regex::Captures;
use serde_json::{json, Value};
@ -25,7 +25,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
let aliases = sqlx::query_as!(
Alias,
"SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0
guild_id.get()
)
.fetch_all(&mut *transaction)
.await?;
@ -37,7 +37,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
Some(cmd_macro) => {
sqlx::query!(
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
cmd_macro.guild_id.0,
cmd_macro.guild_id.get(),
cmd_macro.name,
cmd_macro.description,
cmd_macro.commands
@ -54,7 +54,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
transaction.commit().await?;
ctx.send(|b| b.content(format!("Added {} macros.", added_aliases))).await?;
ctx.send(CreateReply::default().content(format!("Added {} macros.", added_aliases))).await?;
Ok(())
}

View File

@ -1,5 +1,7 @@
use std::collections::hash_map::Entry;
use poise::{serenity_prelude::CreateEmbed, CreateReply};
use crate::{consts::THEME_COLOR, models::command_macro::CommandMacro, Context, Error};
/// Start recording up to 5 commands to replay
@ -32,23 +34,24 @@ pub async fn record_macro(
let row = sqlx::query!(
"
SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
guild_id.0,
guild_id.get(),
name
)
.fetch_one(&ctx.data().database)
.await;
if row.is_ok() {
ctx.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Unique Name Required")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Unique Name Required")
.description(
"A macro already exists under this name.
Please select a unique name for your macro.",
)
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
} else {
let okay = {
@ -63,28 +66,30 @@ Please select a unique name for your macro.",
};
if okay {
ctx.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Macro Recording Started")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.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?;
} else {
ctx.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Macro Already Recording")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Macro Already Recording")
.description(
"You are already recording a macro in this server.
Please use `/macro finish` to end this recording before starting another.",
)
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
}
}
@ -108,13 +113,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
let contained = lock.get(&key);
if contained.map_or(true, |cmacro| cmacro.commands.is_empty()) {
ctx.send(|m| {
m.embed(|e| {
e.title("No Macro Recorded")
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("No Macro Recorded")
.description("Use `/macro record` to start recording a macro")
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
} else {
let command_macro = contained.unwrap();
@ -122,7 +128,7 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
sqlx::query!(
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
command_macro.guild_id.0,
command_macro.guild_id.get(),
command_macro.name,
command_macro.description,
json
@ -131,13 +137,14 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
.await
.unwrap();
ctx.send(|m| {
m.embed(|e| {
e.title("Macro Recorded")
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("Macro Recorded")
.description("Use `/macro run` to execute the macro")
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
}
}

View File

@ -1,3 +1,8 @@
use poise::{
serenity_prelude::{CommandOption, CreateEmbed},
CreateReply,
};
use super::super::autocomplete::macro_name_autocomplete;
use crate::{models::command_macro::guild_command_macro, Context, Data, Error, THEME_COLOR};
@ -18,15 +23,15 @@ pub async fn run_macro(
match guild_command_macro(&Context::Application(ctx), &name).await {
Some(command_macro) => {
Context::Application(ctx)
.send(|b| {
b.embed(|e| {
e.title("Running Macro").color(*THEME_COLOR).description(format!(
.send(CreateReply::default().embed(
CreateEmbed::new().title("Running Macro").color(*THEME_COLOR).description(
format!(
"Running macro {} ({} commands)",
command_macro.name,
command_macro.commands.len()
))
})
})
),
),
))
.await?;
for command in command_macro.commands {

View File

@ -1,22 +1,22 @@
use chrono::offset::Utc;
use poise::{serenity_prelude as serenity, serenity_prelude::Mentionable};
use poise::{
serenity_prelude as serenity,
serenity_prelude::{CreateEmbed, CreateEmbedFooter, Mentionable},
CreateReply,
};
use crate::{models::CtxData, Context, Error, THEME_COLOR};
fn footer(
ctx: Context<'_>,
) -> impl FnOnce(&mut serenity::CreateEmbedFooter) -> &mut serenity::CreateEmbedFooter {
fn footer(ctx: Context<'_>) -> CreateEmbedFooter {
let shard_count = ctx.serenity_context().cache.shard_count();
let shard = ctx.serenity_context().shard_id;
move |f| {
f.text(format!(
"{}\nshard {} of {}",
concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")),
shard,
shard_count,
))
}
CreateEmbedFooter::new(format!(
"{}\nshard {} of {}",
concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")),
shard,
shard_count,
))
}
/// Get an overview of bot commands
@ -24,9 +24,10 @@ fn footer(
pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Help")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Help")
.color(*THEME_COLOR)
.description(
"__Info Commands__
@ -55,9 +56,9 @@ __Advanced Commands__
`/macro` - Record and replay command sequences
",
)
.footer(footer)
})
})
.footer(footer),
),
)
.await?;
Ok(())
@ -69,9 +70,10 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx);
let _ = ctx
.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Info")
.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Info")
.description(
"Help: `/help`
@ -84,9 +86,9 @@ Invite the bot: https://invite.reminder-bot.com/
Use our dashboard: https://reminder-bot.com/",
)
.footer(footer)
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await;
Ok(())
@ -97,8 +99,7 @@ Use our dashboard: https://reminder-bot.com/",
pub async fn donate(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(|m| m.embed(|e| {
e.title("Donate")
ctx.send(CreateReply::default().embed(CreateEmbed::new().title("Donate")
.description("Thinking of adding a monthly contribution?
Click below for my Patreon and official bot server :)
@ -117,7 +118,7 @@ Just $2 USD/month!
*Please note, you must be in the JellyWX Discord server to receive Patreon features*")
.footer(footer)
.color(*THEME_COLOR)
}),
),
)
.await?;
@ -129,14 +130,15 @@ Just $2 USD/month!
pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx);
ctx.send(|m| {
m.ephemeral(true).embed(|e| {
e.title("Dashboard")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("Dashboard")
.description("**https://reminder-bot.com/dashboard**")
.footer(footer)
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
Ok(())
@ -150,9 +152,11 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
let tz = ctx.timezone().await;
let now = Utc::now().with_timezone(&tz);
ctx.send(|m| {
m.ephemeral(true).content(format!("Time in **{}**: `{}`", tz, now.format("%H:%M")))
})
ctx.send(CreateReply::default().ephemeral(true).content(format!(
"Time in **{}**: `{}`",
tz,
now.format("%H:%M")
)))
.await?;
Ok(())
@ -168,13 +172,11 @@ pub async fn clock_context_menu(ctx: Context<'_>, user: serenity::User) -> Resul
let now = Utc::now().with_timezone(&tz);
ctx.send(|m| {
m.ephemeral(true).content(format!(
"Time in {}'s timezone: `{}`",
user.mention(),
now.format("%H:%M")
))
})
ctx.send(CreateReply::default().ephemeral(true).content(format!(
"Time in {}'s timezone: `{}`",
user.mention(),
now.format("%H:%M")
)))
.await?;
Ok(())

View File

@ -2,6 +2,10 @@ use chrono::offset::Utc;
use chrono_tz::{Tz, TZ_VARIANTS};
use levenshtein::levenshtein;
use log::warn;
use poise::{
serenity_prelude::{CreateEmbed, CreateEmbedFooter},
CreateReply,
};
use super::autocomplete::timezone_autocomplete;
use crate::{consts::THEME_COLOR, models::CtxData, Context, Error};
@ -26,17 +30,18 @@ pub async fn timezone(
let now = Utc::now().with_timezone(&tz);
ctx.send(|m| {
m.embed(|e| {
e.title("Timezone Set")
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("Timezone Set")
.description(format!(
"Timezone has been set to **{}**. Your current time should be `{}`",
timezone,
now.format("%H:%M")
))
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
}
@ -60,16 +65,15 @@ pub async fn timezone(
)
});
ctx.send(|m| {
m.embed(|e| {
e.title("Timezone Not Recognized")
ctx.send(CreateReply::default().embed(CreateEmbed::new()
.title("Timezone Not Recognized")
.description("Possibly you meant one of the following timezones, otherwise click [here](https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee):")
.color(*THEME_COLOR)
.fields(fields)
.footer(|f| f.text(footer_text))
.footer(CreateEmbedFooter::new(footer_text))
.url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
})
})
)
)
.await?;
}
}
@ -78,9 +82,10 @@ pub async fn timezone(
(t.to_string(), format!("🕗 `{}`", Utc::now().with_timezone(t).format("%H:%M")), true)
});
ctx.send(|m| {
m.embed(|e| {
e.title("Timezone Usage")
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.title("Timezone Usage")
.description(
"**Usage:**
`/timezone Name`
@ -92,10 +97,10 @@ You may want to use one of the popular timezones below, otherwise click [here](h
)
.color(*THEME_COLOR)
.fields(popular_timezones_iter)
.footer(|f| f.text(footer_text))
.url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
})
})
.footer(CreateEmbedFooter::new(footer_text))
.url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee"),
),
)
.await?;
}
@ -136,13 +141,11 @@ pub async fn set_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error>
guild_data.ephemeral_confirmations = true;
guild_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| {
r.ephemeral(true).embed(|e| {
e.title("Confirmations ephemeral")
ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations ephemeral")
.description("Reminder confirmations will be sent privately, and removed when your client restarts.")
.color(*THEME_COLOR)
})
})
)
)
.await?;
Ok(())
@ -160,15 +163,13 @@ pub async fn unset_ephemeral_confirmations(ctx: Context<'_>) -> Result<(), Error
guild_data.ephemeral_confirmations = false;
guild_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| {
r.ephemeral(true).embed(|e| {
e.title("Confirmations public")
ctx.send(CreateReply::default().ephemeral(true).embed(CreateEmbed::new().title("Confirmations public")
.description(
"Reminder confirmations will be sent as regular messages, and won't be removed automatically.",
)
.color(*THEME_COLOR)
})
})
)
)
.await?;
Ok(())
@ -187,13 +188,14 @@ pub async fn set_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
user_data.allowed_dm = true;
user_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| {
r.ephemeral(true).embed(|e| {
e.title("DMs permitted")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("DMs permitted")
.description("You will receive a message if a user sets a DM reminder for you.")
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
Ok(())
@ -206,15 +208,16 @@ pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
user_data.allowed_dm = false;
user_data.commit_changes(&ctx.data().database).await;
ctx.send(|r| {
r.ephemeral(true).embed(|e| {
e.title("DMs blocked")
ctx.send(
CreateReply::default().ephemeral(true).embed(
CreateEmbed::new()
.title("DMs blocked")
.description(
"You can still set DM reminders for yourself or for users with DMs enabled.",
)
.color(*THEME_COLOR)
})
})
.color(*THEME_COLOR),
),
)
.await?;
Ok(())
@ -230,15 +233,13 @@ pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
match ctx.channel_data().await {
Ok(data) => {
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
ctx.send(|b| {
b.ephemeral(true).content(format!(
"**Warning!**
ctx.send(CreateReply::default().ephemeral(true).content(format!(
"**Warning!**
This link can be used by users to anonymously send messages, with or without permissions.
Do not share it!
|| https://discord.com/api/webhooks/{}/{} ||",
id, token,
))
})
id, token,
)))
.await?;
} else {
ctx.say("No webhook configured on this channel.").await?;

View File

@ -5,7 +5,11 @@ use chrono_tz::Tz;
use log::warn;
use num_integer::Integer;
use poise::{
serenity_prelude::{builder::CreateEmbed, model::channel::Channel, ButtonStyle, ReactionType},
serenity_prelude::{
builder::CreateEmbed, model::channel::Channel, ButtonStyle, CreateActionRow, CreateButton,
CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind, CreateSelectMenuOption,
ReactionType,
},
CreateReply, Modal,
};
@ -125,21 +129,19 @@ pub async fn offset(
let channels = guild
.channels
.iter()
.filter(|(_, channel)| match channel {
Channel::Guild(guild_channel) => guild_channel.is_text_based(),
_ => false,
})
.map(|(id, _)| id.0.to_string())
.filter(|(_, channel)| channel.is_text_based())
.map(|(id, _)| id.get().to_string())
.collect::<Vec<String>>()
.join(",");
sqlx::query!(
"
UPDATE reminders
INNER JOIN
`channels` ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
WHERE FIND_IN_SET(channels.`channel`, ?)",
UPDATE reminders
INNER JOIN `channels`
ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
WHERE FIND_IN_SET(channels.`channel`, ?)
",
combined_time as i64,
channels
)
@ -148,9 +150,15 @@ WHERE FIND_IN_SET(channels.`channel`, ?)",
.unwrap();
} else {
sqlx::query!(
"UPDATE reminders INNER JOIN `channels` ON `channels`.id = reminders.channel_id SET reminders.`utc_time` = reminders.`utc_time` + ? WHERE channels.`channel` = ?",
"
UPDATE reminders
INNER JOIN `channels`
ON `channels`.id = reminders.channel_id
SET reminders.`utc_time` = reminders.`utc_time` + ?
WHERE channels.`channel` = ?
",
combined_time as i64,
ctx.channel_id().0
ctx.channel_id().get()
)
.execute(&ctx.data().database)
.await
@ -216,9 +224,9 @@ pub async fn look(
}),
};
let channel_opt = ctx.channel_id().to_channel_cached(&ctx);
let channel_opt = ctx.channel_id().to_channel_cached(&ctx.cache());
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
let channel_id = if let Some(channel) = channel_opt {
if Some(channel.guild_id) == ctx.guild_id() {
flags.channel_id.unwrap_or_else(|| ctx.channel_id())
} else {
@ -228,11 +236,8 @@ pub async fn look(
ctx.channel_id()
};
let channel_name = if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) {
Some(channel.name)
} else {
None
};
let channel_name =
channel_id.to_channel_cached(&ctx.cache()).map(|channel| channel.name.clone());
let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
@ -260,23 +265,21 @@ pub async fn look(
let pager = LookPager::new(flags, timezone);
ctx.send(|r| {
r.ephemeral(true)
.embed(|e| {
e.title(format!(
"Reminders{}",
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
))
.description(display)
.footer(|f| f.text(format!("Page {} of {}", 1, pages)))
.color(*THEME_COLOR)
})
.components(|comp| {
pager.create_button_row(pages, comp);
comp
})
})
ctx.send(
CreateReply::default()
.ephemeral(true)
.embed(
CreateEmbed::new()
.title(format!(
"Reminders{}",
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
))
.description(display)
.footer(CreateEmbedFooter::new(format!("Page {} of {}", 1, pages)))
.color(*THEME_COLOR),
)
.components(vec![pager.create_button_row(pages)]),
)
.await?;
}
@ -298,11 +301,7 @@ pub async fn delete(ctx: Context<'_>) -> Result<(), Error> {
let resp = show_delete_page(&reminders, 0, timezone);
ctx.send(|r| {
*r = resp;
r
})
.await?;
ctx.send(resp).await?;
Ok(())
}
@ -333,16 +332,12 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
let pager = DelPager::new(page, timezone);
if reminders.is_empty() {
let mut reply = CreateReply::default();
let embed = CreateEmbed::new()
.title("Delete Reminders")
.description("No Reminders")
.color(*THEME_COLOR);
reply
.embed(|e| e.title("Delete Reminders").description("No Reminders").color(*THEME_COLOR))
.components(|comp| {
pager.create_button_row(0, comp);
comp
});
return reply;
return CreateReply::default().embed(embed).components(vec![pager.create_button_row(0)]);
}
let pages = max_delete_page(reminders, &timezone);
@ -391,49 +386,42 @@ pub fn show_delete_page(reminders: &[Reminder], page: usize, timezone: Tz) -> Cr
let del_selector = ComponentDataModel::DelSelector(DelSelector { page, timezone });
let mut reply = CreateReply::default();
let embed = CreateEmbed::new()
.title("Delete Reminders")
.description(display)
.footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR);
reply
.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);
let select_menu = CreateSelectMenu::new(
del_selector.to_custom_id(),
CreateSelectMenuKind::String {
options: shown_reminders
.iter()
.enumerate()
.map(|(count, reminder)| {
let c = reminder.display_content();
let description = if c.len() > 100 {
format!(
"{}...",
reminder.display_content().chars().take(97).collect::<String>()
)
} else {
c.to_string()
};
comp.create_action_row(|row| {
row.create_select_menu(|menu| {
menu.custom_id(del_selector.to_custom_id()).options(|opt| {
for (count, reminder) in shown_reminders.iter().enumerate() {
opt.create_option(|o| {
o.label(count + first_num).value(reminder.id).description({
let c = reminder.display_content();
if c.len() > 100 {
format!(
"{}...",
reminder
.display_content()
.chars()
.take(97)
.collect::<String>()
)
} else {
c.to_string()
}
})
});
}
opt
})
CreateSelectMenuOption::new(
(count + first_num).to_string(),
reminder.id.to_string(),
)
.description(description)
})
})
});
.collect(),
},
);
reply
CreateReply::default()
.embed(embed)
.components(vec![pager.create_button_row(pages), CreateActionRow::SelectMenu(select_menu)])
}
fn time_difference(start_time: DateTime<Utc>) -> String {
@ -465,19 +453,20 @@ pub async fn timer_base(_ctx: Context<'_>) -> Result<(), Error> {
default_member_permissions = "MANAGE_GUILD"
)]
pub async fn list_timer(ctx: Context<'_>) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0);
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let timers = Timer::from_owner(owner, &ctx.data().database).await;
if !timers.is_empty() {
ctx.send(|m| {
m.embed(|e| {
e.fields(timers.iter().map(|timer| {
(&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false)
}))
.color(*THEME_COLOR)
})
})
ctx.send(
CreateReply::default().embed(
CreateEmbed::new()
.fields(timers.iter().map(|timer| {
(&timer.name, format!("⌚ `{}`", time_difference(timer.start_time)), false)
}))
.color(*THEME_COLOR),
),
)
.await?;
} else {
ctx.say("No timers currently. Use `/timer start` to create a new timer").await?;
@ -497,7 +486,7 @@ pub async fn start_timer(
ctx: Context<'_>,
#[description = "Name for the new timer"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0);
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let count = Timer::count_from_owner(owner, &ctx.data().database).await;
@ -530,7 +519,7 @@ pub async fn delete_timer(
ctx: Context<'_>,
#[description = "Name of timer to delete"] name: String,
) -> Result<(), Error> {
let owner = ctx.guild_id().map(|g| g.0).unwrap_or_else(|| ctx.author().id.0);
let owner = ctx.guild_id().map(|g| g.get()).unwrap_or_else(|| ctx.author().id.get());
let exists =
sqlx::query!("SELECT 1 as _r FROM timers WHERE owner = ? AND name = ?", owner, name)
@ -604,7 +593,7 @@ pub async fn multiline(
None => {
warn!("Unexpected None encountered in /multiline");
Ok(Context::Application(ctx)
.send(|m| m.content("Unexpected error.").ephemeral(true))
.send(CreateReply::default().content("Unexpected error.").ephemeral(true))
.await
.map(|_| ())?)
}
@ -682,9 +671,9 @@ async fn create_reminder(
if list.is_empty() {
if ctx.guild_id().is_some() {
vec![ReminderScope::Channel(ctx.channel_id().0)]
vec![ReminderScope::Channel(ctx.channel_id().get())]
} else {
vec![ReminderScope::User(ctx.author().id.0)]
vec![ReminderScope::User(ctx.author().id.get())]
}
} else {
list
@ -709,10 +698,9 @@ async fn create_reminder(
},
)
} else {
ctx.send(|b| {
b.content(
"`repeat` is only available to Patreon subscribers or self-hosted users")
})
ctx.send(CreateReply::default().content(
"`repeat` is only available to Patreon subscribers or self-hosted users",
))
.await?;
return Ok(());
@ -722,17 +710,16 @@ async fn create_reminder(
};
if processed_interval.is_none() && interval.is_some() {
ctx.send(|b| {
b.content(
"Repeat interval could not be processed. Try similar to `1 hour` or `4 days`")
})
ctx.send(CreateReply::default().content(
"Repeat interval could not be processed. Try similar to `1 hour` or `4 days`",
))
.await?;
} else if processed_expires.is_none() && expires.is_some() {
ctx.send(|b| {
b.ephemeral(true).content(
ctx.send(
CreateReply::default().ephemeral(true).content(
"Expiry time failed to process. Please make it as clear as possible",
)
})
),
)
.await?;
} else {
let mut builder = MultiReminderBuilder::new(&ctx, ctx.guild_id())
@ -756,37 +743,20 @@ async fn create_reminder(
reminder_id: reminder,
});
ctx.send(|m| {
m.embed(|c| {
*c = embed;
c
})
.components(|c| {
c.create_action_row(|r| {
r.create_button(|b| {
b.emoji(ReactionType::Unicode("🔕".to_string()))
.label("Cancel")
.style(ButtonStyle::Danger)
.custom_id(undo_button.to_custom_id())
})
.create_button(|b| {
b.emoji(ReactionType::Unicode("📝".to_string()))
.label("Edit")
.style(ButtonStyle::Link)
.url("https://beta.reminder-bot.com/dashboard")
})
})
})
})
ctx.send(CreateReply::default().embed(embed).components(vec![
CreateActionRow::Buttons(vec![
CreateButton::new(undo_button.to_custom_id())
.emoji(ReactionType::Unicode("🔕".to_string()))
.label("Cancel")
.style(ButtonStyle::Danger),
CreateButton::new_link("https://beta.reminder-bot.com/dashboard")
.emoji(ReactionType::Unicode("📝".to_string()))
.label("Edit"),
]),
]))
.await?;
} else {
ctx.send(|m| {
m.embed(|c| {
*c = embed;
c
})
})
.await?;
ctx.send(CreateReply::default().embed(embed)).await?;
}
}
}

View File

@ -1,4 +1,10 @@
use poise::CreateReply;
use poise::{
serenity_prelude::{
CreateActionRow, CreateEmbed, CreateEmbedFooter, CreateSelectMenu, CreateSelectMenuKind,
CreateSelectMenuOption,
},
CreateReply,
};
use crate::{
component_models::{
@ -48,7 +54,7 @@ pub async fn todo_guild_add(
sqlx::query!(
"INSERT INTO todos (guild_id, value)
VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)",
ctx.guild_id().unwrap().0,
ctx.guild_id().unwrap().get(),
task
)
.execute(&ctx.data().database)
@ -73,7 +79,7 @@ pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?",
ctx.guild_id().unwrap().0,
ctx.guild_id().unwrap().get(),
)
.fetch_all(&ctx.data().database)
.await
@ -82,13 +88,9 @@ WHERE guilds.guild = ?",
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.0));
let resp = show_todo_page(&values, 0, None, None, ctx.guild_id().map(|g| g.get()));
ctx.send(|r| {
*r = resp;
r
})
.await?;
ctx.send(resp).await?;
Ok(())
}
@ -123,8 +125,8 @@ pub async fn todo_channel_add(
sqlx::query!(
"INSERT INTO todos (guild_id, channel_id, value)
VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)",
ctx.guild_id().unwrap().0,
ctx.channel_id().0,
ctx.guild_id().unwrap().get(),
ctx.channel_id().get(),
task
)
.execute(&ctx.data().database)
@ -149,7 +151,7 @@ pub async fn todo_channel_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?",
ctx.channel_id().0,
ctx.channel_id().get(),
)
.fetch_all(&ctx.data().database)
.await
@ -158,14 +160,15 @@ WHERE channels.channel = ?",
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let resp =
show_todo_page(&values, 0, None, Some(ctx.channel_id().0), ctx.guild_id().map(|g| g.0));
let resp = show_todo_page(
&values,
0,
None,
Some(ctx.channel_id().get()),
ctx.guild_id().map(|g| g.get()),
);
ctx.send(|r| {
*r = resp;
r
})
.await?;
ctx.send(resp).await?;
Ok(())
}
@ -185,7 +188,7 @@ pub async fn todo_user_add(
sqlx::query!(
"INSERT INTO todos (user_id, value)
VALUES ((SELECT id FROM users WHERE user = ?), ?)",
ctx.author().id.0,
ctx.author().id.get(),
task
)
.execute(&ctx.data().database)
@ -204,7 +207,7 @@ pub async fn todo_user_view(ctx: Context<'_>) -> Result<(), Error> {
"SELECT todos.id, value FROM todos
INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?",
ctx.author().id.0,
ctx.author().id.get(),
)
.fetch_all(&ctx.data().database)
.await
@ -213,13 +216,9 @@ WHERE users.user = ?",
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let resp = show_todo_page(&values, 0, Some(ctx.author().id.0), None, None);
let resp = show_todo_page(&values, 0, Some(ctx.author().id.get()), None, None);
ctx.send(|r| {
*r = resp;
r
})
.await?;
ctx.send(resp).await?;
Ok(())
}
@ -306,61 +305,51 @@ pub fn show_todo_page(
};
if todo_ids.is_empty() {
let mut reply = CreateReply::default();
reply.embed(|e| {
e.title(format!("{} Todo List", title))
CreateReply::default().embed(
CreateEmbed::new()
.title(format!("{} Todo List", title))
.description("Todo List Empty!")
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
.color(*THEME_COLOR)
});
reply
.footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
)
} else {
let todo_selector =
ComponentDataModel::TodoSelector(TodoSelector { page, user_id, channel_id, guild_id });
let mut reply = CreateReply::default();
reply
.embed(|e| {
e.title(format!("{} Todo List", title))
CreateReply::default()
.embed(
CreateEmbed::new()
.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);
.footer(CreateEmbedFooter::new(format!("Page {} of {}", page + 1, pages))),
)
.components(vec![
pager.create_button_row(pages),
CreateActionRow::SelectMenu(CreateSelectMenu::new(
todo_selector.to_custom_id(),
CreateSelectMenuKind::String {
options: todo_ids
.iter()
.zip(&display_vec)
.enumerate()
.map(|(count, (id, disp))| {
let c = disp.split_once(' ').unwrap_or(("", "")).1;
let description = if c.len() > 100 {
format!("{}...", c.chars().take(97).collect::<String>())
} else {
c.to_string()
};
comp.create_action_row(|row| {
row.create_select_menu(|menu| {
menu.custom_id(todo_selector.to_custom_id()).options(|opt| {
for (count, (id, disp)) in todo_ids.iter().zip(&display_vec).enumerate()
{
opt.create_option(|o| {
o.label(format!("Mark {} complete", count + first_num))
.value(id)
.description({
let c = disp.split_once(' ').unwrap_or(("", "")).1;
if c.len() > 100 {
format!(
"{}...",
c.chars().take(97).collect::<String>()
)
} else {
c.to_string()
}
})
});
}
opt
})
})
})
});
reply
CreateSelectMenuOption::new(
format!("Mark {} complete", count + first_num),
id.to_string(),
)
.description(description)
})
.collect(),
},
)),
])
}
}

View File

@ -8,15 +8,8 @@ use log::warn;
use poise::{
serenity_prelude as serenity,
serenity_prelude::{
builder::CreateEmbed,
model::{
application::interaction::{
message_component::MessageComponentInteraction, InteractionResponseType,
MessageFlags,
},
channel::Channel,
},
Context,
builder::CreateEmbed, ComponentInteraction, ComponentInteractionDataKind, Context,
CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage,
},
};
use rmp_serde::Serializer;
@ -31,7 +24,7 @@ use crate::{
component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::reminder::Reminder,
utils::send_as_initial_response,
utils::reply_to_interaction_response_message,
Data,
};
@ -64,21 +57,23 @@ impl ComponentDataModel {
rmp_serde::from_read(cur).unwrap()
}
pub async fn act(&self, ctx: &Context, data: &Data, component: &MessageComponentInteraction) {
pub async fn act(&self, ctx: &Context, data: &Data, component: &ComponentInteraction) {
match self {
ComponentDataModel::LookPager(pager) => {
let flags = pager.flags;
let channel_opt = component.channel_id.to_channel_cached(&ctx);
let channel_id = {
let channel_opt = component.channel_id.to_channel_cached(&ctx.cache);
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
if Some(channel.guild_id) == component.guild_id {
flags.channel_id.unwrap_or(component.channel_id)
if let Some(channel) = channel_opt {
if Some(channel.guild_id) == component.guild_id {
flags.channel_id.unwrap_or(component.channel_id)
} else {
component.channel_id
}
} else {
component.channel_id
}
} else {
component.channel_id
};
let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await;
@ -90,11 +85,7 @@ impl ComponentDataModel {
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name =
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) {
Some(channel.name)
} else {
None
};
channel_id.to_channel_cached(&ctx.cache).map(|channel| channel.name.clone());
let next_page = pager.next_page(pages);
@ -107,7 +98,7 @@ impl ComponentDataModel {
.skip_while(|p| {
skip_char_count += p.len();
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page as usize
skip_char_count < EMBED_DESCRIPTION_MAX_LENGTH * next_page
})
.take_while(|p| {
char_count += p.len();
@ -117,28 +108,24 @@ impl ComponentDataModel {
.collect::<Vec<String>>()
.join("");
let mut embed = CreateEmbed::default();
embed
let embed = CreateEmbed::default()
.title(format!(
"Reminders{}",
channel_name.map_or(String::new(), |n| format!(" on #{}", n))
))
.description(display)
.footer(|f| f.text(format!("Page {} of {}", next_page + 1, pages)))
.footer(CreateEmbedFooter::new(format!("Page {} of {}", next_page + 1, pages)))
.color(*THEME_COLOR);
let _ = component
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|response| {
response.set_embeds(vec![embed]).components(|comp| {
pager.create_button_row(pages, comp);
comp
})
},
)
})
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new()
.embed(embed)
.components(vec![pager.create_button_row(pages)]),
),
)
.await;
}
ComponentDataModel::DelPager(pager) => {
@ -155,55 +142,58 @@ impl ComponentDataModel {
let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
ComponentDataModel::DelSelector(selector) => {
let selected_id = component.data.values.join(",");
if let ComponentInteractionDataKind::StringSelect { values } = &component.data.kind
{
let selected_id = values.join(",");
sqlx::query!(
"UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)",
selected_id
)
.execute(&data.database)
.await
.unwrap();
sqlx::query!(
"
UPDATE reminders SET `status` = 'deleted' WHERE FIND_IN_SET(id, ?)
",
selected_id
)
.execute(&data.database)
.await
.unwrap();
let reminders = Reminder::from_guild(
&ctx,
&data.database,
component.guild_id,
component.user.id,
)
.await;
let resp = show_delete_page(&reminders, selector.page, selector.timezone);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
let reminders = Reminder::from_guild(
&ctx,
&data.database,
component.guild_id,
component.user.id,
)
.await;
let resp = show_delete_page(&reminders, selector.page, selector.timezone);
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
}
ComponentDataModel::TodoPager(pager) => {
if Some(component.user.id.0) == pager.user_id || pager.user_id.is_none() {
if Some(component.user.id.get()) == pager.user_id || pager.user_id.is_none() {
let values = if let Some(uid) = pager.user_id {
sqlx::query!(
"SELECT todos.id, value FROM todos
INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?",
"
SELECT todos.id, value FROM todos
INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?
",
uid,
)
.fetch_all(&data.database)
@ -214,9 +204,11 @@ WHERE users.user = ?",
.collect::<Vec<(usize, String)>>()
} else if let Some(cid) = pager.channel_id {
sqlx::query!(
"SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?",
"
SELECT todos.id, value FROM todos
INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?
",
cid,
)
.fetch_all(&data.database)
@ -227,9 +219,11 @@ WHERE channels.channel = ?",
.collect::<Vec<(usize, String)>>()
} else {
sqlx::query!(
"SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?",
"
SELECT todos.id, value FROM todos
INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?
",
pager.guild_id,
)
.fetch_all(&data.database)
@ -251,79 +245,86 @@ WHERE guilds.guild = ?",
);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
send_as_initial_response(resp, d);
d
})
})
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
} else {
let _ = component
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.flags(
MessageFlags::EPHEMERAL,
)
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.ephemeral(true)
.content("Only the user who performed the command can use these components")
})
})
)
)
.await;
}
}
ComponentDataModel::TodoSelector(selector) => {
if Some(component.user.id.0) == selector.user_id || selector.user_id.is_none() {
let selected_id = component.data.values.join(",");
if Some(component.user.id.get()) == selector.user_id || selector.user_id.is_none() {
if let ComponentInteractionDataKind::StringSelect { values } =
&component.data.kind
{
let selected_id = values.join(",");
sqlx::query!("DELETE FROM todos WHERE FIND_IN_SET(id, ?)", selected_id)
sqlx::query!(
"
DELETE FROM todos WHERE FIND_IN_SET(id, ?)
",
selected_id
)
.execute(&data.database)
.await
.unwrap();
let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison
"SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?",
selector.user_id,
selector.channel_id,
selector.guild_id,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let values = sqlx::query!(
// fucking braindead mysql use <=> instead of = for null comparison
"
SELECT id, value FROM todos WHERE user_id <=> ? AND channel_id <=> ? AND guild_id <=> ?
",
selector.user_id,
selector.channel_id,
selector.guild_id,
)
.fetch_all(&data.database)
.await
.unwrap()
.iter()
.map(|row| (row.id as usize, row.value.clone()))
.collect::<Vec<(usize, String)>>();
let resp = show_todo_page(
&values,
selector.page,
selector.user_id,
selector.channel_id,
selector.guild_id,
);
let resp = show_todo_page(
&values,
selector.page,
selector.user_id,
selector.channel_id,
selector.guild_id,
);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
send_as_initial_response(resp, d);
d
})
})
.await;
let _ = component
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
} else {
let _ = component
.create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.flags(
MessageFlags::EPHEMERAL,
)
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.ephemeral(true)
.content("Only the user who performed the command can use these components")
})
})
)
)
.await;
}
}
@ -336,14 +337,12 @@ WHERE guilds.guild = ?",
let resp = show_macro_page(&macros, page);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|d| {
send_as_initial_response(resp, d);
d
},
)
})
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
reply_to_interaction_response_message(resp),
),
)
.await;
}
ComponentDataModel::UndoReminder(undo_reminder) => {
@ -355,58 +354,56 @@ WHERE guilds.guild = ?",
match reminder.delete(&data.database).await {
Ok(()) => {
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::UpdateMessage)
.interaction_response_data(|d| {
d.embed(|e| {
e.title("Reminder Canceled")
.description(
"This reminder has been canceled.",
)
.color(*THEME_COLOR)
})
.components(|c| c)
})
})
.create_response(
&ctx,
CreateInteractionResponse::UpdateMessage(
CreateInteractionResponseMessage::new().embed(
CreateEmbed::new()
.title("Reminder Canceled")
.description("This reminder has been canceled.")
.color(*THEME_COLOR),
),
),
)
.await;
}
Err(e) => {
warn!("Error canceling reminder: {:?}", e);
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content(
"The reminder could not be canceled: it may have already been deleted. Check `/del`!")
.ephemeral(true)
})
})
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new().content(
"An error occurred trying to cancel this reminder.",
).ephemeral(true),
),
)
.await;
}
}
} else {
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content(
"The reminder could not be canceled: it may have already been deleted. Check `/del`!")
.ephemeral(true)
})
})
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new().content(
"The reminder could not be canceled. It may have already been deleted.",
).ephemeral(true),
),
)
.await;
}
} else {
let _ = component
.create_interaction_response(&ctx, |f| {
f.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| {
d.content(
"Only the user who performed the command can use this button.")
.ephemeral(true)
})
})
.create_response(
&ctx,
CreateInteractionResponse::Message(
CreateInteractionResponseMessage::new()
.content("Only the user who performed the command can use these components")
.ephemeral(true),
),
)
.await;
}
}

View File

@ -10,17 +10,16 @@ pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWX
const THEME_COLOR_FALLBACK: u32 = 0x8fb677;
pub const MACRO_MAX_COMMANDS: usize = 5;
use poise::serenity_prelude::CreateAttachment;
use std::{collections::HashSet, env, iter::FromIterator};
use poise::serenity_prelude::model::prelude::AttachmentType;
use regex::Regex;
lazy_static! {
pub static ref DEFAULT_AVATAR: AttachmentType<'static> = (
pub static ref DEFAULT_AVATAR: CreateAttachment = CreateAttachment::bytes(
include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/webhook.jpg")) as &[u8],
"webhook.jpg",
)
.into();
);
pub static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"\s*<(#|@)(?:!)?(\d+)>\s*"#).unwrap();
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
env::var("PATREON_ROLE_ID")

View File

@ -1,48 +1,44 @@
use std::{collections::HashMap, env};
use log::error;
use poise::serenity_prelude::ActivityData;
use poise::{
serenity_prelude as serenity,
serenity_prelude::{model::application::interaction::Interaction, utils::shard_id},
serenity_prelude::{CreateEmbed, CreateMessage, FullEvent},
};
use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR};
pub async fn listener(
ctx: &serenity::Context,
event: &poise::Event<'_>,
event: &FullEvent,
data: &Data,
) -> Result<(), Error> {
match event {
poise::Event::Ready { .. } => {
ctx.set_activity(serenity::Activity::watching("for /remind")).await;
FullEvent::Ready { .. } => {
ctx.set_activity(Some(ActivityData::watching("for /remind")));
}
poise::Event::ChannelDelete { channel } => {
sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.as_u64())
FullEvent::ChannelDelete { channel, .. } => {
sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.get())
.execute(&data.database)
.await
.unwrap();
}
poise::Event::GuildCreate { guild, is_new } => {
if *is_new {
let guild_id = guild.id.as_u64().to_owned();
FullEvent::GuildCreate { guild, is_new } => {
if is_new.unwrap_or(false) {
let guild_id = guild.id.get().to_owned();
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id)
.execute(&data.database)
.await?;
if let Err(e) = post_guild_count(ctx, &data.http, guild_id).await {
error!("DiscordBotList: {:?}", e);
}
let default_channel = guild.default_channel_guaranteed();
if let Some(default_channel) = default_channel {
default_channel
.send_message(&ctx, |m| {
m.embed(|e| {
e.title("Thank you for adding Reminder Bot!").description(
"To get started:
default_channel.send_message(
&ctx,
CreateMessage::new()
.embed(
CreateEmbed::new()
.title("Thank you for adding Reminder Bot!")
.description("To get started:
• Set your timezone with `/timezone`
• Set up permissions in Server Settings 🠚 Integrations 🠚 Reminder Bot (desktop only)
• Create your first reminder with `/remind`
@ -52,24 +48,24 @@ If you need any support, please come and ask us! Join our [Discord](https://disc
__Updates__
To stay up to date on the latest features and fixes, join our [Discord](https://discord.jellywx.com).
",
).color(*THEME_COLOR)
})
})
")
.color(*THEME_COLOR)
)
)
.await?;
}
}
}
poise::Event::GuildDelete { incomplete, .. } => {
let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0)
FullEvent::GuildDelete { incomplete, .. } => {
let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.get())
.execute(&data.database)
.await;
}
poise::Event::InteractionCreate { interaction } => {
if let Interaction::MessageComponent(component) = interaction {
FullEvent::InteractionCreate { interaction } => {
if let Some(component) = interaction.clone().message_component() {
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
component_model.act(ctx, data, component).await;
component_model.act(ctx, data, &component).await;
}
}
_ => {}
@ -77,38 +73,3 @@ To stay up to date on the latest features and fixes, join our [Discord](https://
Ok(())
}
async fn post_guild_count(
ctx: &serenity::Context,
http: &reqwest::Client,
guild_id: u64,
) -> Result<(), reqwest::Error> {
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
let shard_count = ctx.cache.shard_count();
let current_shard_id = shard_id(guild_id, shard_count);
let guild_count = ctx
.cache
.guilds()
.iter()
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
.count() as u64;
let mut hm = HashMap::new();
hm.insert("server_count", guild_count);
hm.insert("shard_id", current_shard_id);
hm.insert("shard_count", shard_count);
http.post(
format!("https://top.gg/api/bots/{}/stats", ctx.cache.current_user_id().as_u64())
.as_str(),
)
.header("Authorization", token)
.json(&hm)
.send()
.await
.map(|_| ())
} else {
Ok(())
}
}

View File

@ -1,42 +1,41 @@
use poise::{
serenity_prelude::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction,
};
use poise::{serenity_prelude::model::channel::Channel, CreateReply};
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 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(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| {
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),
};
command_macro.commands.push(recorded);
let _ = ctx
.send(|m| m.ephemeral(true).content("Command recorded to macro"))
if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
let _ = ctx
.send(
CreateReply::default()
.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: app_ctx.interaction.data.options.clone(),
};
return false;
command_macro.commands.push(recorded);
let _ = ctx
.send(
CreateReply::default()
.ephemeral(true)
.content("Command recorded to macro"),
)
.await;
}
return false;
}
}
}
@ -46,11 +45,13 @@ async fn macro_check(ctx: Context<'_>) -> bool {
}
async fn check_self_permissions(ctx: Context<'_>) -> bool {
if let Some(guild) = ctx.guild() {
let user_id = ctx.serenity_context().cache.current_user_id();
if let Some(guild_id) = ctx.guild_id() {
let user_id = ctx.serenity_context().cache.current_user().id;
let manage_webhooks =
guild.member_permissions(&ctx, user_id).await.map_or(false, |p| p.manage_webhooks());
let manage_webhooks = guild_id
.current_user_member(&ctx)
.await
.map_or(false, |m| m.permissions(&ctx).map_or(false, |p| p.manage_webhooks()));
let (view_channel, send_messages, embed_links) = ctx
.channel_id()
@ -72,20 +73,18 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
true
} else {
let _ = ctx
.send(|m| {
m.content(format!(
"Please ensure the bot has the correct permissions:
.send(CreateReply::default().content(format!(
"Please ensure the bot has the correct permissions:
{} **View Channel**
{} **Send Message**
{} **Embed Links**
{} **Manage Webhooks**",
if view_channel { "" } else { "" },
if send_messages { "" } else { "" },
if embed_links { "" } else { "" },
if manage_webhooks { "" } else { "" },
))
})
if view_channel { "" } else { "" },
if send_messages { "" } else { "" },
if embed_links { "" } else { "" },
if manage_webhooks { "" } else { "" },
)))
.await;
false

View File

@ -23,9 +23,12 @@ use std::{
use chrono_tz::Tz;
use log::{error, warn};
use poise::serenity_prelude::model::{
gateway::GatewayIntents,
id::{GuildId, UserId},
use poise::serenity_prelude::{
model::{
gateway::GatewayIntents,
id::{GuildId, UserId},
},
ClientBuilder,
};
use sqlx::{MySql, Pool};
use tokio::sync::{broadcast, broadcast::Sender, RwLock};
@ -36,7 +39,6 @@ use crate::{
event_handlers::listener,
hooks::all_checks,
models::command_macro::CommandMacro,
utils::register_application_commands,
};
type Database = MySql;
@ -213,11 +215,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
.map(|t| t.timezone.parse::<Tz>().unwrap())
.collect::<Vec<Tz>>();
poise::Framework::builder()
.token(discord_token)
let framework = poise::Framework::builder()
.setup(move |ctx, _bot, framework| {
Box::pin(async move {
register_application_commands(ctx, framework, None).await.unwrap();
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
let kill_tx = tx.clone();
let kill_recv = tx.subscribe();
@ -261,9 +262,12 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
})
})
.options(options)
.intents(GatewayIntents::GUILDS)
.run_autosharded()
.await?;
.build();
let mut client =
ClientBuilder::new(&discord_token, GatewayIntents::GUILDS).framework(framework).await?;
client.start_autosharded().await?;
Ok(())
}

View File

@ -18,7 +18,7 @@ impl ChannelData {
channel: &Channel,
pool: &MySqlPool,
) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
let channel_id = channel.id().as_u64().to_owned();
let channel_id = channel.id().get().to_owned();
if let Ok(c) = sqlx::query_as_unchecked!(
Self,
@ -30,7 +30,7 @@ impl ChannelData {
{
Ok(c)
} else {
let props = channel.to_owned().guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
let props = channel.to_owned().guild().map(|g| (g.guild_id.get().to_owned(), g.name));
let (guild_id, channel_name) = if let Some((a, b)) = props { (Some(a), Some(b)) } else { (None, None) };

View File

@ -1,6 +1,4 @@
use poise::serenity_prelude::model::{
application::interaction::application_command::CommandDataOption, id::GuildId,
};
use poise::serenity_prelude::{model::id::GuildId, CommandDataOption};
use serde::{Deserialize, Serialize};
use serde_json::Value;
@ -45,7 +43,7 @@ pub async fn guild_command_macro(
"
SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?
",
ctx.guild_id().unwrap().0,
ctx.guild_id().unwrap().get(),
name
)
.fetch_one(&ctx.data().database)

View File

@ -14,21 +14,21 @@ impl GuildData {
if let Ok(c) = sqlx::query_as_unchecked!(
Self,
"SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?",
guild_id.0
guild_id.get()
)
.fetch_one(pool)
.await
{
Ok(c)
} else {
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.0)
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id.get())
.execute(&pool.clone())
.await?;
Ok(sqlx::query_as_unchecked!(
Self,
"SELECT id, ephemeral_confirmations FROM guilds WHERE guild = ?",
guild_id.0
guild_id.get()
)
.fetch_one(pool)
.await?)

View File

@ -6,7 +6,7 @@ pub mod timer;
pub mod user_data;
use chrono_tz::Tz;
use poise::serenity_prelude::{async_trait, model::id::UserId, ChannelType};
use poise::serenity_prelude::{async_trait, model::id::UserId};
use crate::{
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
@ -53,20 +53,7 @@ impl CtxData for Context<'_> {
async fn channel_data(&self) -> Result<ChannelData, Box<dyn std::error::Error + Sync + Send>> {
// If we're in a thread, get the parent channel.
let recv_channel = self.channel_id().to_channel(&self).await?;
let channel = match recv_channel.guild() {
Some(guild_channel) => {
if guild_channel.kind == ChannelType::PublicThread {
guild_channel.parent_id.unwrap().to_channel_cached(&self).unwrap()
} else {
self.channel_id().to_channel_cached(&self).unwrap()
}
}
None => self.channel_id().to_channel_cached(&self).unwrap(),
};
let channel = self.channel_id().to_channel(&self).await?;
ChannelData::from_channel(&channel, &self.data().database).await
}
@ -82,7 +69,7 @@ impl Data {
) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
let rows = sqlx::query!(
"SELECT name, description, commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0
guild_id.get()
)
.fetch_all(&self.database)
.await?.iter().map(|row| CommandMacro {

View File

@ -1,4 +1,4 @@
use std::{collections::HashSet, fmt::Display};
use std::collections::HashSet;
use chrono::{Duration, NaiveDateTime, Utc};
use chrono_tz::Tz;
@ -9,8 +9,9 @@ use poise::serenity_prelude::{
id::{ChannelId, GuildId, UserId},
webhook::Webhook,
},
ChannelType, Result as SerenityResult,
ChannelType, CreateWebhook, Result as SerenityResult,
};
use secrecy::ExposeSecret;
use sqlx::MySqlPool;
use crate::{
@ -27,9 +28,9 @@ use crate::{
async fn create_webhook(
ctx: impl CacheHttp,
channel: GuildChannel,
name: impl Display,
name: impl Into<String>,
) -> SerenityResult<Webhook> {
channel.create_webhook_with_avatar(ctx.http(), name, DEFAULT_AVATAR.clone()).await
channel.create_webhook(ctx.http(), CreateWebhook::new(name).avatar(&*DEFAULT_AVATAR)).await
}
#[derive(Hash, PartialEq, Eq)]
@ -230,7 +231,7 @@ impl<'a> MultiReminderBuilder<'a> {
let thread_id = None;
let db_channel_id = match scope {
ReminderScope::User(user_id) => {
if let Ok(user) = UserId(user_id).to_user(&self.ctx).await {
if let Ok(user) = UserId::new(user_id).to_user(&self.ctx).await {
let user_data = UserData::from_user(
&user,
&self.ctx.serenity_context(),
@ -257,7 +258,8 @@ impl<'a> MultiReminderBuilder<'a> {
}
}
ReminderScope::Channel(channel_id) => {
let channel = ChannelId(channel_id).to_channel(&self.ctx).await.unwrap();
let channel =
ChannelId::new(channel_id).to_channel(&self.ctx).await.unwrap();
if let Some(mut guild_channel) = channel.clone().guild() {
if Some(guild_channel.guild_id) != self.guild_id {
@ -290,8 +292,9 @@ impl<'a> MultiReminderBuilder<'a> {
{
Ok(webhook) => {
channel_data.webhook_id =
Some(webhook.id.as_u64().to_owned());
channel_data.webhook_token = webhook.token;
Some(webhook.id.get().to_owned());
channel_data.webhook_token =
webhook.token.map(|s| s.expose_secret().clone());
channel_data
.commit_changes(&self.ctx.data().database)

View File

@ -54,31 +54,31 @@ impl Reminder {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
reminders.uid = ?
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
reminders.uid = ?
",
uid
)
@ -91,31 +91,31 @@ WHERE
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
reminders.id = ?
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
reminders.id = ?
",
id
)
@ -135,37 +135,37 @@ WHERE
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.channel = ? AND
FIND_IN_SET(reminders.enabled, ?)
ORDER BY
reminders.utc_time
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
reminders.channel_id = channels.id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.channel = ? AND
FIND_IN_SET(reminders.enabled, ?)
ORDER BY
reminders.utc_time
",
channel_id.as_u64(),
channel_id.get(),
enabled,
)
.fetch_all(pool)
@ -180,119 +180,126 @@ ORDER BY
user: UserId,
) -> Vec<Self> {
if let Some(guild_id) = guild_id {
let guild_opt = guild_id.to_guild_cached(cache);
if let Some(guild) = guild_opt {
let channels = guild
.channels
.keys()
.into_iter()
.map(|k| k.as_u64().to_string())
.collect::<Vec<String>>()
.join(",");
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
FIND_IN_SET(channels.channel, ?)
",
channels
let channel_query = if let Some(guild) = guild_id.to_guild_cached(&cache) {
Some(
guild
.channels
.keys()
.into_iter()
.map(|k| k.get().to_string())
.collect::<Vec<String>>()
.join(","),
)
.fetch_all(pool)
.await
} else {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
",
guild_id.as_u64()
)
.fetch_all(pool)
.await
None
};
match channel_query {
Some(channel_query) => {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
FIND_IN_SET(channels.channel, ?)
",
channel_query
)
.fetch_all(pool)
.await
}
None => {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
LEFT JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
",
guild_id.get()
)
.fetch_all(pool)
.await
}
}
} else {
sqlx::query_as_unchecked!(
Self,
"
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.id = (SELECT dm_channel FROM users WHERE user = ?)
",
user.as_u64()
SELECT
reminders.id,
reminders.uid,
channels.channel,
reminders.utc_time,
reminders.interval_seconds,
reminders.interval_days,
reminders.interval_months,
reminders.expires,
reminders.enabled,
reminders.content,
reminders.embed_description,
users.user AS set_by
FROM
reminders
INNER JOIN
channels
ON
channels.id = reminders.channel_id
LEFT JOIN
users
ON
reminders.set_by = users.id
WHERE
`status` = 'pending' AND
channels.id = (SELECT dm_channel FROM users WHERE user = ?)
",
user.get()
)
.fetch_all(pool)
.await
@ -304,10 +311,15 @@ WHERE
&self,
db: impl Executor<'_, Database = Database>,
) -> Result<(), sqlx::Error> {
sqlx::query!("UPDATE reminders SET `status` = 'deleted' WHERE uid = ?", self.uid)
.execute(db)
.await
.map(|_| ())
sqlx::query!(
"
UPDATE reminders SET `status` = 'deleted' WHERE uid = ?
",
self.uid
)
.execute(db)
.await
.map(|_| ())
}
pub fn display_content(&self) -> &str {

View File

@ -18,7 +18,7 @@ impl UserData {
where
U: Into<UserId>,
{
let user_id = user.into().as_u64().to_owned();
let user_id = user.into().get().to_owned();
match sqlx::query!(
"
@ -50,7 +50,7 @@ SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?
SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allowed_dm FROM users WHERE user = ?
",
*LOCAL_TIMEZONE,
user_id.0
user_id.get()
)
.fetch_one(pool)
.await
@ -65,7 +65,7 @@ SELECT id, user, dm_channel, IF(timezone IS NULL, ?, timezone) AS timezone, allo
"
INSERT IGNORE INTO channels (channel) VALUES (?)
",
dm_channel.id.0
dm_channel.id.get()
)
.execute(&pool_c)
.await?;
@ -74,8 +74,8 @@ INSERT IGNORE INTO channels (channel) VALUES (?)
"
INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id FROM channels WHERE channel = ?), ?)
",
user_id.0,
dm_channel.id.0,
user_id.get(),
dm_channel.id.get(),
*LOCAL_TIMEZONE
)
.execute(&pool_c)
@ -86,7 +86,7 @@ INSERT INTO users (name, user, dm_channel, timezone) VALUES ('', ?, (SELECT id F
"
SELECT id, user, dm_channel, timezone, allowed_dm FROM users WHERE user = ?
",
user_id.0
user_id.get()
)
.fetch_one(pool)
.await?)

View File

@ -1,51 +1,21 @@
use poise::{
serenity_prelude as serenity,
serenity_prelude::{
builder::CreateApplicationCommands,
http::CacheHttp,
interaction::MessageFlags,
model::id::{GuildId, UserId},
CreateInteractionResponseMessage,
},
CreateReply,
};
use crate::{
consts::{CNC_GUILD, SUBSCRIPTION_ROLES},
Data, Error,
};
pub async fn register_application_commands(
ctx: &serenity::Context,
framework: &poise::Framework<Data, Error>,
guild_id: Option<GuildId>,
) -> Result<(), serenity::Error> {
let mut commands_builder = CreateApplicationCommands::default();
let commands = &framework.options().commands;
for command in commands {
if let Some(slash_command) = command.create_as_slash_command() {
commands_builder.add_application_command(slash_command);
}
if let Some(context_menu_command) = command.create_as_context_menu_command() {
commands_builder.add_application_command(context_menu_command);
}
}
let commands_builder = poise::serenity_prelude::json::Value::Array(commands_builder.0);
if let Some(guild_id) = guild_id {
ctx.http.create_guild_application_commands(guild_id.0, &commands_builder).await?;
} else {
ctx.http.create_global_application_commands(&commands_builder).await?;
}
Ok(())
}
use crate::consts::{CNC_GUILD, SUBSCRIPTION_ROLES};
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
if let Some(subscription_guild) = *CNC_GUILD {
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
let guild_member = GuildId::new(subscription_guild).member(cache_http, user_id).await;
if let Ok(member) = guild_member {
for role in member.roles {
if SUBSCRIPTION_ROLES.contains(role.as_u64()) {
if SUBSCRIPTION_ROLES.contains(&role.get()) {
return true;
}
}
@ -70,39 +40,14 @@ pub async fn check_guild_subscription(
}
}
/// Sends the message, specified via [`crate::CreateReply`], to the interaction initial response
/// endpoint
pub fn send_as_initial_response(
data: poise::CreateReply<'_>,
f: &mut serenity::CreateInteractionResponseData,
) {
let poise::CreateReply {
content,
embeds,
attachments: _, // serenity doesn't support attachments in initial response yet
components,
ephemeral,
allowed_mentions,
reply: _,
} = data;
pub fn reply_to_interaction_response_message(
reply: CreateReply,
) -> CreateInteractionResponseMessage {
let mut builder = CreateInteractionResponseMessage::new().embeds(reply.embeds);
if let Some(content) = content {
f.content(content);
}
f.set_embeds(embeds);
if let Some(allowed_mentions) = allowed_mentions {
f.allowed_mentions(|f| {
*f = allowed_mentions.clone();
f
});
}
if let Some(components) = components {
f.components(|f| {
f.0 = components.0;
f
});
}
if ephemeral {
f.flags(MessageFlags::EPHEMERAL);
if let Some(components) = reply.components {
builder = builder.components(components)
}
builder
}