component models

This commit is contained in:
jude 2022-02-19 22:11:21 +00:00
parent afc376c44f
commit 06c4deeaa9
15 changed files with 269 additions and 184 deletions

View File

@ -1,23 +1,23 @@
mod sender; mod sender;
use std::env;
use log::info; use log::info;
use serenity::client::Context; use serenity::client::Context;
use sqlx::{Executor, MySql}; use sqlx::{Executor, MySql};
use std::env; use tokio::time::{sleep_until, Duration, Instant};
use tokio::time::sleep_until;
use tokio::time::{Duration, Instant};
type Database = MySql; type Database = MySql;
pub async fn initialize(ctx: Context, pool: impl Executor<'_, Database = Database> + Copy) { pub async fn initialize(ctx: Context, pool: impl Executor<'_, Database = Database> + Copy) {
let REMIND_INTERVAL = env::var("REMIND_INTERVAL") let remind_interval = env::var("REMIND_INTERVAL")
.map(|inner| inner.parse::<u64>().ok()) .map(|inner| inner.parse::<u64>().ok())
.ok() .ok()
.flatten() .flatten()
.unwrap_or(10); .unwrap_or(10);
loop { loop {
let sleep_to = Instant::now() + Duration::from_secs(REMIND_INTERVAL); let sleep_to = Instant::now() + Duration::from_secs(remind_interval);
let reminders = sender::Reminder::fetch_reminders(pool).await; let reminders = sender::Reminder::fetch_reminders(pool).await;
if reminders.len() > 0 { if reminders.len() > 0 {

View File

@ -1,4 +1,3 @@
use crate::Database;
use chrono::Duration; use chrono::Duration;
use chrono_tz::Tz; use chrono_tz::Tz;
use lazy_static::lazy_static; use lazy_static::lazy_static;
@ -20,6 +19,8 @@ use sqlx::{
Executor, Executor,
}; };
use crate::Database;
lazy_static! { lazy_static! {
pub static ref TIMEFROM_REGEX: Regex = pub static ref TIMEFROM_REGEX: Regex =
Regex::new(r#"<<timefrom:(?P<time>\d+):(?P<format>.+)?>>"#).unwrap(); Regex::new(r#"<<timefrom:(?P<time>\d+):(?P<format>.+)?>>"#).unwrap();

View File

@ -1,9 +1,11 @@
use chrono::offset::Utc; use chrono::offset::Utc;
use poise::serenity::builder::CreateEmbedFooter; use poise::{serenity_prelude as serenity, serenity_prelude::Mentionable};
use crate::{models::CtxData, Context, Error, THEME_COLOR}; use crate::{models::CtxData, Context, Error, THEME_COLOR};
fn footer(ctx: Context<'_>) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter { fn footer(
ctx: Context<'_>,
) -> impl FnOnce(&mut serenity::CreateEmbedFooter) -> &mut serenity::CreateEmbedFooter {
let shard_count = ctx.discord().cache.shard_count(); let shard_count = ctx.discord().cache.shard_count();
let shard = ctx.discord().shard_id; let shard = ctx.discord().shard_id;
@ -22,13 +24,12 @@ fn footer(ctx: Context<'_>) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut Creat
pub async fn help(ctx: Context<'_>) -> Result<(), Error> { pub async fn help(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
let _ = ctx ctx.send(|m| {
.send(|m| { m.ephemeral(true).embed(|e| {
m.embed(|e| { e.title("Help")
e.title("Help") .color(*THEME_COLOR)
.color(*THEME_COLOR) .description(
.description( "__Info Commands__
"__Info Commands__
`/help` `/info` `/donate` `/dashboard` `/clock` `/help` `/info` `/donate` `/dashboard` `/clock`
*run these commands with no options* *run these commands with no options*
@ -52,11 +53,11 @@ __Setup Commands__
__Advanced Commands__ __Advanced Commands__
`/macro` - Record and replay command sequences `/macro` - Record and replay command sequences
", ",
) )
.footer(footer) .footer(footer)
})
}) })
.await; })
.await?;
Ok(()) Ok(())
} }
@ -68,7 +69,7 @@ pub async fn info(ctx: Context<'_>) -> Result<(), Error> {
let _ = ctx let _ = ctx
.send(|m| { .send(|m| {
m.embed(|e| { m.ephemeral(true).embed(|e| {
e.title("Info") e.title("Info")
.description(format!( .description(format!(
"Help: `/help` "Help: `/help`
@ -95,9 +96,10 @@ Use our dashboard: https://reminder-bot.com/",
pub async fn donate(ctx: Context<'_>) -> Result<(), Error> { pub async fn donate(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
let _ = ctx.send(|m| m.embed(|e| { ctx.send(|m| m.embed(|e| {
e.title("Donate") e.title("Donate")
.description("Thinking of adding a monthly contribution? Click below for my Patreon and official bot server :) .description("Thinking of adding a monthly contribution?
Click below for my Patreon and official bot server :)
**https://www.patreon.com/jellywx/** **https://www.patreon.com/jellywx/**
**https://discord.jellywx.com/** **https://discord.jellywx.com/**
@ -112,11 +114,11 @@ With your new rank, you'll be able to:
Just $2 USD/month! Just $2 USD/month!
*Please note, you must be in the JellyWX Discord server to receive Patreon features*") *Please note, you must be in the JellyWX Discord server to receive Patreon features*")
.footer(footer) .footer(footer)
.color(*THEME_COLOR) .color(*THEME_COLOR)
}), }),
) )
.await; .await?;
Ok(()) Ok(())
} }
@ -126,21 +128,20 @@ Just $2 USD/month!
pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> { pub async fn dashboard(ctx: Context<'_>) -> Result<(), Error> {
let footer = footer(ctx); let footer = footer(ctx);
let _ = ctx ctx.send(|m| {
.send(|m| { m.ephemeral(true).embed(|e| {
m.embed(|e| { e.title("Dashboard")
e.title("Dashboard") .description("**https://reminder-bot.com/dashboard**")
.description("**https://reminder-bot.com/dashboard**") .footer(footer)
.footer(footer) .color(*THEME_COLOR)
.color(*THEME_COLOR)
})
}) })
.await; })
.await?;
Ok(()) Ok(())
} }
/// View the current time in a user's selected timezone /// View the current time in your selected timezone
#[poise::command(slash_command)] #[poise::command(slash_command)]
pub async fn clock(ctx: Context<'_>) -> Result<(), Error> { pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
ctx.defer_ephemeral().await?; ctx.defer_ephemeral().await?;
@ -155,3 +156,25 @@ pub async fn clock(ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
/// View the current time in a user's selected timezone
#[poise::command(context_menu_command = "View Local Time")]
pub async fn clock_context_menu(ctx: Context<'_>, user: serenity::User) -> Result<(), Error> {
ctx.defer_ephemeral().await?;
let user_data = ctx.user_data(user.id).await?;
let tz = user_data.timezone();
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")
))
})
.await?;
Ok(())
}

View File

@ -9,7 +9,6 @@ use chrono_tz::Tz;
use num_integer::Integer; use num_integer::Integer;
use poise::{ use poise::{
serenity::{builder::CreateEmbed, model::channel::Channel}, serenity::{builder::CreateEmbed, model::channel::Channel},
serenity_prelude::ActionRole::Create,
CreateReply, CreateReply,
}; };
@ -32,7 +31,6 @@ use crate::{
Reminder, Reminder,
}, },
timer::Timer, timer::Timer,
user_data::UserData,
CtxData, CtxData,
}, },
time_parser::natural_parser, time_parser::natural_parser,
@ -212,7 +210,7 @@ pub async fn look(
None None
}; };
let reminders = Reminder::from_channel(&ctx, channel_id, &flags).await; let reminders = Reminder::from_channel(&ctx.data().database, channel_id, &flags).await;
if reminders.is_empty() { if reminders.is_empty() {
let _ = ctx.say("No reminders on specified channel").await; let _ = ctx.say("No reminders on specified channel").await;
@ -266,7 +264,9 @@ pub async fn look(
pub async fn delete(ctx: Context<'_>) -> Result<(), Error> { pub async fn delete(ctx: Context<'_>) -> Result<(), Error> {
let timezone = ctx.timezone().await; let timezone = ctx.timezone().await;
let reminders = Reminder::from_guild(&ctx, ctx.guild_id(), ctx.author().id).await; let reminders =
Reminder::from_guild(&ctx.discord(), &ctx.data().database, ctx.guild_id(), ctx.author().id)
.await;
let resp = show_delete_page(&reminders, 0, timezone); let resp = show_delete_page(&reminders, 0, timezone);

View File

@ -6,6 +6,7 @@ use crate::{
ComponentDataModel, TodoSelector, ComponentDataModel, TodoSelector,
}, },
consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, SELECT_MAX_ENTRIES, THEME_COLOR},
hooks::guild_only,
Context, Error, Context, Error,
}; };
@ -16,7 +17,7 @@ pub async fn todo_base(_ctx: Context<'_>) -> Result<(), Error> {
} }
/// Manage the server todo list /// Manage the server todo list
#[poise::command(slash_command, rename = "server")] #[poise::command(slash_command, rename = "server", check = "guild_only")]
pub async fn todo_guild_base(_ctx: Context<'_>) -> Result<(), Error> { pub async fn todo_guild_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }
@ -70,7 +71,7 @@ WHERE guilds.guild = ?",
} }
/// Manage the channel todo list /// Manage the channel todo list
#[poise::command(slash_command, rename = "channel")] #[poise::command(slash_command, rename = "channel", check = "guild_only")]
pub async fn todo_channel_base(_ctx: Context<'_>) -> Result<(), Error> { pub async fn todo_channel_base(_ctx: Context<'_>) -> Result<(), Error> {
Ok(()) Ok(())
} }

View File

@ -5,6 +5,7 @@ use std::io::Cursor;
use chrono_tz::Tz; use chrono_tz::Tz;
use poise::serenity::{ use poise::serenity::{
builder::CreateEmbed, builder::CreateEmbed,
client::Context,
model::{ model::{
channel::Channel, channel::Channel,
interactions::{message_component::MessageComponentInteraction, InteractionResponseType}, interactions::{message_component::MessageComponentInteraction, InteractionResponseType},
@ -22,8 +23,9 @@ use crate::{
}, },
component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager}, component_models::pager::{DelPager, LookPager, MacroPager, Pager, TodoPager},
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR}, consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
models::{reminder::Reminder, CtxData}, models::reminder::Reminder,
Context, Data, utils::send_as_initial_response,
Data,
}; };
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
@ -53,12 +55,12 @@ impl ComponentDataModel {
rmp_serde::from_read(cur).unwrap() rmp_serde::from_read(cur).unwrap()
} }
pub async fn act(&self, ctx: Context<'_>, component: &MessageComponentInteraction) { pub async fn act(&self, ctx: &Context, data: &Data, component: &MessageComponentInteraction) {
match self { match self {
ComponentDataModel::LookPager(pager) => { ComponentDataModel::LookPager(pager) => {
let flags = pager.flags; let flags = pager.flags;
let channel_opt = component.channel_id.to_channel_cached(&ctx.discord()); let channel_opt = component.channel_id.to_channel_cached(&ctx);
let channel_id = if let Some(Channel::Guild(channel)) = channel_opt { let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
if Some(channel.guild_id) == component.guild_id { if Some(channel.guild_id) == component.guild_id {
@ -70,7 +72,7 @@ impl ComponentDataModel {
component.channel_id component.channel_id
}; };
let reminders = Reminder::from_channel(&ctx, channel_id, &flags).await; let reminders = Reminder::from_channel(&data.database, channel_id, &flags).await;
let pages = reminders let pages = reminders
.iter() .iter()
@ -78,13 +80,12 @@ impl ComponentDataModel {
.fold(0, |t, r| t + r.len()) .fold(0, |t, r| t + r.len())
.div_ceil(EMBED_DESCRIPTION_MAX_LENGTH); .div_ceil(EMBED_DESCRIPTION_MAX_LENGTH);
let channel_name = if let Some(Channel::Guild(channel)) = let channel_name =
channel_id.to_channel_cached(&ctx.discord()) if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx) {
{ Some(channel.name)
Some(channel.name) } else {
} else { None
None };
};
let next_page = pager.next_page(pages); let next_page = pager.next_page(pages);
@ -118,7 +119,7 @@ impl ComponentDataModel {
.color(*THEME_COLOR); .color(*THEME_COLOR);
let _ = component let _ = component
.create_interaction_response(&ctx.discord(), |r| { .create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::UpdateMessage).interaction_response_data( r.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
|response| { |response| {
response.embeds(vec![embed]).components(|comp| { response.embeds(vec![embed]).components(|comp| {
@ -132,17 +133,26 @@ impl ComponentDataModel {
.await; .await;
} }
ComponentDataModel::DelPager(pager) => { ComponentDataModel::DelPager(pager) => {
let reminders = let reminders = Reminder::from_guild(
Reminder::from_guild(&ctx, component.guild_id, component.user.id).await; &ctx,
&data.database,
component.guild_id,
component.user.id,
)
.await;
let max_pages = max_delete_page(&reminders, &pager.timezone); let max_pages = max_delete_page(&reminders, &pager.timezone);
let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone); let resp = show_delete_page(&reminders, pager.next_page(max_pages), pager.timezone);
let _ = ctx let _ = component
.send(|r| { .create_interaction_response(&ctx, |f| {
*r = resp; f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
r |d| {
send_as_initial_response(resp, d);
d
},
)
}) })
.await; .await;
} }
@ -150,19 +160,28 @@ impl ComponentDataModel {
let selected_id = component.data.values.join(","); let selected_id = component.data.values.join(",");
sqlx::query!("DELETE FROM reminders WHERE FIND_IN_SET(id, ?)", selected_id) sqlx::query!("DELETE FROM reminders WHERE FIND_IN_SET(id, ?)", selected_id)
.execute(&ctx.data().database) .execute(&data.database)
.await .await
.unwrap(); .unwrap();
let reminders = let reminders = Reminder::from_guild(
Reminder::from_guild(&ctx, component.guild_id, component.user.id).await; &ctx,
&data.database,
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);
let _ = ctx let _ = component
.send(|r| { .create_interaction_response(&ctx, |f| {
*r = resp; f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
r |d| {
send_as_initial_response(resp, d);
d
},
)
}) })
.await; .await;
} }
@ -175,7 +194,7 @@ INNER JOIN users ON todos.user_id = users.id
WHERE users.user = ?", WHERE users.user = ?",
uid, uid,
) )
.fetch_all(&ctx.data().database) .fetch_all(&data.database)
.await .await
.unwrap() .unwrap()
.iter() .iter()
@ -188,7 +207,7 @@ INNER JOIN channels ON todos.channel_id = channels.id
WHERE channels.channel = ?", WHERE channels.channel = ?",
cid, cid,
) )
.fetch_all(&ctx.data().database) .fetch_all(&data.database)
.await .await
.unwrap() .unwrap()
.iter() .iter()
@ -201,7 +220,7 @@ INNER JOIN guilds ON todos.guild_id = guilds.id
WHERE guilds.guild = ?", WHERE guilds.guild = ?",
pager.guild_id, pager.guild_id,
) )
.fetch_all(&ctx.data().database) .fetch_all(&data.database)
.await .await
.unwrap() .unwrap()
.iter() .iter()
@ -219,15 +238,18 @@ WHERE guilds.guild = ?",
pager.guild_id, pager.guild_id,
); );
let _ = ctx let _ = component
.send(|r| { .create_interaction_response(&ctx, |f| {
*r = resp; f.kind(InteractionResponseType::UpdateMessage)
r .interaction_response_data(|d| {
send_as_initial_response(resp, d);
d
})
}) })
.await; .await;
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx.discord(), |r| { .create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource) r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| { .interaction_response_data(|d| {
d.flags( d.flags(
@ -244,7 +266,7 @@ WHERE guilds.guild = ?",
let selected_id = component.data.values.join(","); let selected_id = component.data.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(&ctx.data().database) .execute(&data.database)
.await .await
.unwrap(); .unwrap();
@ -255,7 +277,7 @@ WHERE guilds.guild = ?",
selector.channel_id, selector.channel_id,
selector.guild_id, selector.guild_id,
) )
.fetch_all(&ctx.data().database) .fetch_all(&data.database)
.await .await
.unwrap() .unwrap()
.iter() .iter()
@ -270,15 +292,18 @@ WHERE guilds.guild = ?",
selector.guild_id, selector.guild_id,
); );
let _ = ctx let _ = component
.send(|r| { .create_interaction_response(&ctx, |f| {
*r = resp; f.kind(InteractionResponseType::UpdateMessage)
r .interaction_response_data(|d| {
send_as_initial_response(resp, d);
d
})
}) })
.await; .await;
} else { } else {
let _ = component let _ = component
.create_interaction_response(&ctx.discord(), |r| { .create_interaction_response(&ctx, |r| {
r.kind(InteractionResponseType::ChannelMessageWithSource) r.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|d| { .interaction_response_data(|d| {
d.flags( d.flags(
@ -291,17 +316,21 @@ WHERE guilds.guild = ?",
} }
} }
ComponentDataModel::MacroPager(pager) => { ComponentDataModel::MacroPager(pager) => {
let macros = ctx.command_macros().await.unwrap(); let macros = data.command_macros(component.guild_id.unwrap()).await.unwrap();
let max_page = max_macro_page(&macros); let max_page = max_macro_page(&macros);
let page = pager.next_page(max_page); let page = pager.next_page(max_page);
let resp = show_macro_page(&macros, page); let resp = show_macro_page(&macros, page);
let _ = ctx let _ = component
.send(|r| { .create_interaction_response(&ctx, |f| {
*r = resp; f.kind(InteractionResponseType::UpdateMessage).interaction_response_data(
r |d| {
send_as_initial_response(resp, d);
d
},
)
}) })
.await; .await;
} }

View File

@ -1,21 +1,12 @@
use std::{ use std::{collections::HashMap, env, sync::atomic::Ordering};
collections::HashMap,
env,
sync::atomic::{AtomicBool, Ordering},
};
use log::{info, warn}; use log::{info, warn};
use poise::{ use poise::{
serenity::{model::interactions::Interaction, utils::shard_id}, serenity::{model::interactions::Interaction, utils::shard_id},
serenity_prelude as serenity, serenity_prelude as serenity,
serenity_prelude::{
ApplicationCommandInteraction, ApplicationCommandInteractionData, ApplicationCommandType,
InteractionType,
},
ApplicationCommandOrAutocompleteInteraction, ApplicationContext, Command,
}; };
use crate::{component_models::ComponentDataModel, Context, Data, Error}; use crate::{component_models::ComponentDataModel, Data, Error};
pub async fn listener( pub async fn listener(
ctx: &serenity::Context, ctx: &serenity::Context,
@ -56,15 +47,10 @@ pub async fn listener(
} }
} }
poise::Event::ChannelDelete { channel } => { poise::Event::ChannelDelete { channel } => {
sqlx::query!( sqlx::query!("DELETE FROM channels WHERE channel = ?", channel.id.as_u64())
" .execute(&data.database)
DELETE FROM channels WHERE channel = ? .await
", .unwrap();
channel.id.as_u64()
)
.execute(&data.database)
.await
.unwrap();
} }
poise::Event::GuildCreate { guild, is_new } => { poise::Event::GuildCreate { guild, is_new } => {
if *is_new { if *is_new {
@ -122,7 +108,7 @@ DELETE FROM channels WHERE channel = ?
Interaction::MessageComponent(component) => { Interaction::MessageComponent(component) => {
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id); let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
// component_model.act(ctx, component).await; component_model.act(ctx, data, component).await;
} }
_ => {} _ => {}
}, },

View File

@ -1,4 +1,4 @@
use poise::{serenity::model::channel::Channel, ApplicationCommandOrAutocompleteInteraction}; use poise::serenity::model::channel::Channel;
use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error}; use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error};
@ -14,39 +14,33 @@ pub async fn guild_only(ctx: Context<'_>) -> Result<bool, Error> {
async fn macro_check(ctx: Context<'_>) -> bool { async fn macro_check(ctx: Context<'_>) -> bool {
if let Context::Application(app_ctx) = ctx { if let Context::Application(app_ctx) = ctx {
if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(interaction) = if let Some(guild_id) = ctx.guild_id() {
app_ctx.interaction if ctx.command().identifying_name != "macro_finish" {
{ let mut lock = ctx.data().recording_macros.write().await;
if let Some(guild_id) = ctx.guild_id() {
if ctx.command().identifying_name != "macro_finish" {
let mut lock = ctx.data().recording_macros.write().await;
if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) { if let Some(command_macro) = lock.get_mut(&(guild_id, ctx.author().id)) {
if command_macro.commands.len() >= MACRO_MAX_COMMANDS { if command_macro.commands.len() >= MACRO_MAX_COMMANDS {
let _ = ctx.send(|m| { let _ = ctx.send(|m| {
m.ephemeral(true).content( m.ephemeral(true).content(
format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS), format!("{} commands already recorded. Please use `/macro finish` to end recording.", MACRO_MAX_COMMANDS),
) )
}) })
.await; .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"))
.await;
}
false
} else { } else {
true 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"))
.await;
} }
false
} else { } else {
true true
} }

View File

@ -59,6 +59,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
info_cmds::info(), info_cmds::info(),
info_cmds::donate(), info_cmds::donate(),
info_cmds::clock(), info_cmds::clock(),
info_cmds::clock_context_menu(),
info_cmds::dashboard(), info_cmds::dashboard(),
moderation_cmds::timezone(), moderation_cmds::timezone(),
poise::Command { poise::Command {

View File

@ -9,7 +9,7 @@ use poise::serenity::{async_trait, model::id::UserId};
use crate::{ use crate::{
models::{channel_data::ChannelData, user_data::UserData}, models::{channel_data::ChannelData, user_data::UserData},
CommandMacro, Context, Data, Error, CommandMacro, Context, Data, Error, GuildId,
}; };
#[async_trait] #[async_trait]
@ -49,13 +49,20 @@ impl CtxData for Context<'_> {
} }
async fn command_macros(&self) -> Result<Vec<CommandMacro<Data, Error>>, Error> { async fn command_macros(&self) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
let guild_id = self.guild_id().unwrap(); self.data().command_macros(self.guild_id().unwrap()).await
}
}
impl Data {
pub(crate) async fn command_macros(
&self,
guild_id: GuildId,
) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
let rows = sqlx::query!( let rows = sqlx::query!(
"SELECT name, description FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)", "SELECT name, description FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
guild_id.0 guild_id.0
) )
.fetch_all(&self.data().database) .fetch_all(&self.database)
.await?.iter().map(|row| CommandMacro { .await?.iter().map(|row| CommandMacro {
guild_id, guild_id,
name: row.name.clone(), name: row.name.clone(),

View File

@ -6,12 +6,15 @@ pub mod look_flags;
use chrono::{NaiveDateTime, TimeZone}; use chrono::{NaiveDateTime, TimeZone};
use chrono_tz::Tz; use chrono_tz::Tz;
use poise::serenity::model::id::{ChannelId, GuildId, UserId}; use poise::{
serenity::model::id::{ChannelId, GuildId, UserId},
serenity_prelude::Cache,
};
use sqlx::Executor; use sqlx::Executor;
use crate::{ use crate::{
models::reminder::look_flags::{LookFlags, TimeDisplayType}, models::reminder::look_flags::{LookFlags, TimeDisplayType},
Context, Data, Database, Database,
}; };
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
@ -70,7 +73,7 @@ WHERE
} }
pub async fn from_channel<C: Into<ChannelId>>( pub async fn from_channel<C: Into<ChannelId>>(
ctx: &Context<'_>, pool: impl Executor<'_, Database = Database>,
channel_id: C, channel_id: C,
flags: &LookFlags, flags: &LookFlags,
) -> Vec<Self> { ) -> Vec<Self> {
@ -111,18 +114,19 @@ ORDER BY
channel_id.as_u64(), channel_id.as_u64(),
enabled, enabled,
) )
.fetch_all(&ctx.data().database) .fetch_all(pool)
.await .await
.unwrap() .unwrap()
} }
pub async fn from_guild( pub async fn from_guild(
ctx: &Context<'_>, cache: impl AsRef<Cache>,
pool: impl Executor<'_, Database = Database>,
guild_id: Option<GuildId>, guild_id: Option<GuildId>,
user: UserId, user: UserId,
) -> Vec<Self> { ) -> Vec<Self> {
if let Some(guild_id) = guild_id { if let Some(guild_id) = guild_id {
let guild_opt = guild_id.to_guild_cached(&ctx.discord()); let guild_opt = guild_id.to_guild_cached(cache);
if let Some(guild) = guild_opt { if let Some(guild) = guild_opt {
let channels = guild let channels = guild
@ -163,7 +167,7 @@ WHERE
", ",
channels channels
) )
.fetch_all(&ctx.data().database) .fetch_all(pool)
.await .await
} else { } else {
sqlx::query_as_unchecked!( sqlx::query_as_unchecked!(
@ -196,7 +200,7 @@ WHERE
", ",
guild_id.as_u64() guild_id.as_u64()
) )
.fetch_all(&ctx.data().database) .fetch_all(pool)
.await .await
} }
} else { } else {
@ -230,7 +234,7 @@ WHERE
", ",
user.as_u64() user.as_u64()
) )
.fetch_all(&ctx.data().database) .fetch_all(pool)
.await .await
} }
.unwrap() .unwrap()

View File

@ -1,7 +1,10 @@
use poise::serenity::{ use poise::{
builder::CreateApplicationCommands, serenity::{
http::CacheHttp, builder::CreateApplicationCommands,
model::id::{GuildId, UserId}, http::CacheHttp,
model::id::{GuildId, UserId},
},
serenity_prelude as serenity,
}; };
use crate::{ use crate::{
@ -65,3 +68,40 @@ pub async fn check_guild_subscription(
false false
} }
} }
/// 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,
reference_message: _, // can't reply to a message in interactions
} = data;
if let Some(content) = content {
f.content(content);
}
f.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(serenity::InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
}
}

View File

@ -4,17 +4,15 @@ extern crate rocket;
mod consts; mod consts;
mod routes; mod routes;
use rocket::fs::{relative, FileServer}; use std::{collections::HashMap, env};
use std::collections::HashMap;
use oauth2::basic::BasicClient; use oauth2::{basic::BasicClient, AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl};
use oauth2::{AuthUrl, ClientId, ClientSecret, RedirectUrl, TokenUrl}; use rocket::fs::FileServer;
use crate::consts::{DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN};
use rocket_dyn_templates::Template; use rocket_dyn_templates::Template;
use serenity::client::Context; use serenity::client::Context;
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use std::env;
use crate::consts::{DISCORD_OAUTH_AUTHORIZE, DISCORD_OAUTH_TOKEN};
type Database = MySql; type Database = MySql;

View File

@ -1,14 +1,13 @@
use rocket::State; use rocket::{
serde::json::{json, Json, Value as JsonValue},
use crate::consts::DISCORD_CDN; State,
};
use serde::Serialize; use serde::Serialize;
use serenity::{client::Context, model::id::GuildId};
use sqlx::{MySql, Pool}; use sqlx::{MySql, Pool};
use super::Reminder; use super::Reminder;
use rocket::serde::json::{json, Json, Value as JsonValue}; use crate::consts::DISCORD_CDN;
use serenity::client::Context;
use serenity::http::CacheHttp;
use serenity::model::id::GuildId;
#[derive(Serialize)] #[derive(Serialize)]
struct ChannelInfo { struct ChannelInfo {

View File

@ -1,22 +1,24 @@
use rocket::serde::json::{json, Json, Value as JsonValue};
use rocket::{http::CookieJar, State};
use reqwest::Client;
use serde::{Deserialize, Serialize};
use serenity::model::{
id::{GuildId, RoleId},
permissions::Permissions,
};
use sqlx::{MySql, Pool};
use std::env; use std::env;
use super::Reminder;
use crate::consts::DISCORD_API;
use crate::routes::dashboard::DeleteReminder;
use chrono_tz::Tz; use chrono_tz::Tz;
use serenity::client::Context; use reqwest::Client;
use serenity::model::id::UserId; use rocket::{
http::CookieJar,
serde::json::{json, Json, Value as JsonValue},
State,
};
use serde::{Deserialize, Serialize};
use serenity::{
client::Context,
model::{
id::{GuildId, RoleId, UserId},
permissions::Permissions,
},
};
use sqlx::{MySql, Pool};
use super::Reminder;
use crate::{consts::DISCORD_API, routes::dashboard::DeleteReminder};
#[derive(Serialize)] #[derive(Serialize)]
struct UserInfo { struct UserInfo {
@ -166,7 +168,7 @@ pub async fn get_user_guilds(cookies: &CookieJar<'_>, reqwest_client: &State<Cli
#[post("/api/user/reminders", data = "<reminder>")] #[post("/api/user/reminders", data = "<reminder>")]
pub async fn create_reminder( pub async fn create_reminder(
reminder: Json<Reminder>, reminder: Json<Reminder>,
ctx: &State<Context>, _ctx: &State<Context>,
pool: &State<Pool<MySql>>, pool: &State<Pool<MySql>>,
) -> JsonValue { ) -> JsonValue {
match sqlx::query!( match sqlx::query!(