component models
This commit is contained in:
parent
afc376c44f
commit
06c4deeaa9
@ -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 {
|
||||||
|
@ -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();
|
||||||
|
@ -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(())
|
||||||
|
}
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
@ -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(¯os);
|
let max_page = max_macro_page(¯os);
|
||||||
let page = pager.next_page(max_page);
|
let page = pager.next_page(max_page);
|
||||||
|
|
||||||
let resp = show_macro_page(¯os, page);
|
let resp = show_macro_page(¯os, 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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
|
46
src/hooks.rs
46
src/hooks.rs
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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(),
|
||||||
|
@ -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()
|
||||||
|
48
src/utils.rs
48
src/utils.rs
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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!(
|
||||||
|
Loading…
Reference in New Issue
Block a user