Compare commits
1 Commits
jellywx/gu
...
jellywx/ma
Author | SHA1 | Date | |
---|---|---|---|
e2bf23f194 |
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -2145,7 +2145,7 @@ checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "reminder_rs"
|
name = "reminder_rs"
|
||||||
version = "1.6.6"
|
version = "1.6.5"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "reminder_rs"
|
name = "reminder_rs"
|
||||||
version = "1.6.6"
|
version = "1.6.5"
|
||||||
authors = ["jellywx <judesouthworth@pm.me>"]
|
authors = ["jellywx <judesouthworth@pm.me>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
@ -1,92 +0,0 @@
|
|||||||
SET foreign_key_checks = 0;
|
|
||||||
|
|
||||||
START TRANSACTION;
|
|
||||||
|
|
||||||
-- drop existing constraints
|
|
||||||
ALTER TABLE channels DROP FOREIGN KEY `channels_ibfk_1`;
|
|
||||||
ALTER TABLE command_aliases DROP FOREIGN KEY `command_aliases_ibfk_1`;
|
|
||||||
ALTER TABLE events DROP FOREIGN KEY `events_ibfk_1`;
|
|
||||||
ALTER TABLE guild_users DROP FOREIGN KEY `guild_users_ibfk_1`;
|
|
||||||
ALTER TABLE macro DROP FOREIGN KEY `macro_ibfk_1`;
|
|
||||||
ALTER TABLE roles DROP FOREIGN KEY `roles_ibfk_1`;
|
|
||||||
ALTER TABLE todos DROP FOREIGN KEY `todos_ibfk_2`;
|
|
||||||
ALTER TABLE reminder_template DROP FOREIGN KEY `reminder_template_ibfk_1`;
|
|
||||||
|
|
||||||
-- update foreign key types
|
|
||||||
ALTER TABLE channels MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE command_aliases MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE events MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE guild_users MODIFY `guild` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE macro MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE roles MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE todos MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE reminder_template MODIFY `guild_id` BIGINT UNSIGNED;
|
|
||||||
|
|
||||||
-- update foreign key values
|
|
||||||
UPDATE channels SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE command_aliases SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE events SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE guild_users SET `guild` = (SELECT `guild` FROM guilds WHERE guilds.`id` = guild_users.`guild`);
|
|
||||||
UPDATE macro SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE roles SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE todos SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
UPDATE reminder_template SET `guild_id` = (SELECT `guild` FROM guilds WHERE guilds.`id` = `guild_id`);
|
|
||||||
|
|
||||||
-- update guilds table
|
|
||||||
ALTER TABLE guilds MODIFY `id` BIGINT UNSIGNED NOT NULL;
|
|
||||||
UPDATE guilds SET `id` = `guild`;
|
|
||||||
ALTER TABLE guilds DROP COLUMN `guild`;
|
|
||||||
ALTER TABLE guilds ADD COLUMN `default_channel` BIGINT UNSIGNED;
|
|
||||||
ALTER TABLE guilds ADD CONSTRAINT `default_channel_fk`
|
|
||||||
FOREIGN KEY (`default_channel`)
|
|
||||||
REFERENCES channels(`channel`)
|
|
||||||
ON DELETE SET NULL
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
-- re-add constraints
|
|
||||||
ALTER TABLE channels ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE command_aliases ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE events ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE guild_users ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE macro ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE roles ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
ALTER TABLE todos ADD CONSTRAINT
|
|
||||||
FOREIGN KEY (`guild_id`)
|
|
||||||
REFERENCES guilds(`id`)
|
|
||||||
ON DELETE CASCADE
|
|
||||||
ON UPDATE CASCADE;
|
|
||||||
|
|
||||||
|
|
||||||
COMMIT;
|
|
||||||
|
|
||||||
SET foreign_key_checks = 1;
|
|
@ -1,9 +1,6 @@
|
|||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
|
||||||
|
|
||||||
use chrono_tz::TZ_VARIANTS;
|
use chrono_tz::TZ_VARIANTS;
|
||||||
use poise::AutocompleteChoice;
|
|
||||||
|
|
||||||
use crate::{models::CtxData, time_parser::natural_parser, Context};
|
use crate::Context;
|
||||||
|
|
||||||
pub async fn timezone_autocomplete(ctx: Context<'_>, partial: &str) -> Vec<String> {
|
pub async fn timezone_autocomplete(ctx: Context<'_>, partial: &str) -> Vec<String> {
|
||||||
if partial.is_empty() {
|
if partial.is_empty() {
|
||||||
@ -24,7 +21,7 @@ pub async fn macro_name_autocomplete(ctx: Context<'_>, partial: &str) -> Vec<Str
|
|||||||
SELECT name
|
SELECT name
|
||||||
FROM macro
|
FROM macro
|
||||||
WHERE
|
WHERE
|
||||||
guild_id = ?
|
guild_id = (SELECT id FROM guilds WHERE guild = ?)
|
||||||
AND name LIKE CONCAT(?, '%')",
|
AND name LIKE CONCAT(?, '%')",
|
||||||
ctx.guild_id().unwrap().0,
|
ctx.guild_id().unwrap().0,
|
||||||
partial,
|
partial,
|
||||||
@ -36,96 +33,3 @@ WHERE
|
|||||||
.map(|s| s.name.clone())
|
.map(|s| s.name.clone())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn multiline_autocomplete(
|
|
||||||
_ctx: Context<'_>,
|
|
||||||
partial: &str,
|
|
||||||
) -> Vec<AutocompleteChoice<String>> {
|
|
||||||
if partial.is_empty() {
|
|
||||||
vec![AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
AutocompleteChoice { name: partial.to_string(), value: partial.to_string() },
|
|
||||||
AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() },
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn time_hint_autocomplete(
|
|
||||||
ctx: Context<'_>,
|
|
||||||
partial: &str,
|
|
||||||
) -> Vec<AutocompleteChoice<String>> {
|
|
||||||
if partial.is_empty() {
|
|
||||||
vec![AutocompleteChoice {
|
|
||||||
name: "Start typing a time...".to_string(),
|
|
||||||
value: "now".to_string(),
|
|
||||||
}]
|
|
||||||
} else {
|
|
||||||
match natural_parser(partial, &ctx.timezone().await.to_string()).await {
|
|
||||||
Some(timestamp) => match SystemTime::now().duration_since(UNIX_EPOCH) {
|
|
||||||
Ok(now) => {
|
|
||||||
let diff = timestamp - now.as_secs() as i64;
|
|
||||||
|
|
||||||
if diff < 0 {
|
|
||||||
vec![AutocompleteChoice {
|
|
||||||
name: "Time is in the past".to_string(),
|
|
||||||
value: "now".to_string(),
|
|
||||||
}]
|
|
||||||
} else {
|
|
||||||
if diff > 86400 {
|
|
||||||
vec![
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: partial.to_string(),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: format!(
|
|
||||||
"In approximately {} days, {} hours",
|
|
||||||
diff / 86400,
|
|
||||||
(diff % 86400) / 3600
|
|
||||||
),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else if diff > 3600 {
|
|
||||||
vec![
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: partial.to_string(),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: format!("In approximately {} hours", diff / 3600),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
} else {
|
|
||||||
vec![
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: partial.to_string(),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
AutocompleteChoice {
|
|
||||||
name: format!("In approximately {} minutes", diff / 60),
|
|
||||||
value: partial.to_string(),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(_) => {
|
|
||||||
vec![AutocompleteChoice {
|
|
||||||
name: partial.to_string(),
|
|
||||||
value: partial.to_string(),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
None => {
|
|
||||||
vec![AutocompleteChoice {
|
|
||||||
name: "Time not recognised".to_string(),
|
|
||||||
value: "now".to_string(),
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -17,7 +17,7 @@ pub async fn delete_macro(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match sqlx::query!(
|
match sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT id FROM macro WHERE guild_id = ? AND name = ?",
|
SELECT id FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
|
||||||
ctx.guild_id().unwrap().0,
|
ctx.guild_id().unwrap().0,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
|
38
src/commands/command_macro/install.rs
Normal file
38
src/commands/command_macro/install.rs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
use poise::serenity_prelude::CommandType;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
commands::autocomplete::macro_name_autocomplete, models::command_macro::guild_command_macro,
|
||||||
|
Context, Error,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Add a macro as a slash-command to this server. Enables controlling permissions per-macro.
|
||||||
|
#[poise::command(
|
||||||
|
slash_command,
|
||||||
|
rename = "install",
|
||||||
|
guild_only = true,
|
||||||
|
default_member_permissions = "MANAGE_GUILD",
|
||||||
|
identifying_name = "install_macro"
|
||||||
|
)]
|
||||||
|
pub async fn install_macro(
|
||||||
|
ctx: Context<'_>,
|
||||||
|
#[description = "Name of macro to install"]
|
||||||
|
#[autocomplete = "macro_name_autocomplete"]
|
||||||
|
name: String,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let guild_id = ctx.guild_id().unwrap();
|
||||||
|
|
||||||
|
if let Some(command_macro) = guild_command_macro(&ctx, &name).await {
|
||||||
|
guild_id
|
||||||
|
.create_application_command(&ctx.discord(), |a| {
|
||||||
|
a.kind(CommandType::ChatInput)
|
||||||
|
.name(command_macro.name)
|
||||||
|
.description(command_macro.description.unwrap_or_else(|| "".to_string()))
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
ctx.send(|r| r.ephemeral(true).content("Macro installed. Go to Server Settings 🠚 Integrations 🠚 Reminder Bot to configure permissions.")).await?;
|
||||||
|
} else {
|
||||||
|
ctx.send(|r| r.ephemeral(true).content("No macro found with that name")).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
@ -2,7 +2,7 @@ use poise::CreateReply;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
component_models::pager::{MacroPager, Pager},
|
component_models::pager::{MacroPager, Pager},
|
||||||
consts::THEME_COLOR,
|
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
|
||||||
models::{command_macro::CommandMacro, CtxData},
|
models::{command_macro::CommandMacro, CtxData},
|
||||||
Context, Error,
|
Context, Error,
|
||||||
};
|
};
|
||||||
@ -30,7 +30,27 @@ pub async fn list_macro(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn max_macro_page<U, E>(macros: &[CommandMacro<U, E>]) -> usize {
|
pub fn max_macro_page<U, E>(macros: &[CommandMacro<U, E>]) -> usize {
|
||||||
((macros.len() as f64) / 25.0).ceil() as usize
|
let mut skipped_char_count = 0;
|
||||||
|
|
||||||
|
macros
|
||||||
|
.iter()
|
||||||
|
.map(|m| {
|
||||||
|
if let Some(description) = &m.description {
|
||||||
|
format!("**{}**\n- *{}*\n- Has {} commands", m.name, description, m.commands.len())
|
||||||
|
} else {
|
||||||
|
format!("**{}**\n- Has {} commands", m.name, m.commands.len())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.fold(1, |mut pages, p| {
|
||||||
|
skipped_char_count += p.len();
|
||||||
|
|
||||||
|
if skipped_char_count > EMBED_DESCRIPTION_MAX_LENGTH {
|
||||||
|
skipped_char_count = p.len();
|
||||||
|
pages += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pages
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> CreateReply {
|
pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> CreateReply {
|
||||||
@ -55,27 +75,45 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
|
|||||||
page = pages - 1;
|
page = pages - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let lower = (page * 25).min(macros.len());
|
let mut char_count = 0;
|
||||||
let upper = ((page + 1) * 25).min(macros.len());
|
let mut skipped_char_count = 0;
|
||||||
|
|
||||||
let fields = macros[lower..upper].iter().map(|m| {
|
let mut skipped_pages = 0;
|
||||||
if let Some(description) = &m.description {
|
|
||||||
(
|
let display_vec: Vec<String> = macros
|
||||||
m.name.clone(),
|
.iter()
|
||||||
format!("*{}*\n- Has {} commands", description, m.commands.len()),
|
.map(|m| {
|
||||||
true,
|
if let Some(description) = &m.description {
|
||||||
)
|
format!("**{}**\n- *{}*\n- Has {} commands", m.name, description, m.commands.len())
|
||||||
} else {
|
} else {
|
||||||
(m.name.clone(), format!("- Has {} commands", m.commands.len()), true)
|
format!("**{}**\n- Has {} commands", m.name, m.commands.len())
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
.skip_while(|p| {
|
||||||
|
skipped_char_count += p.len();
|
||||||
|
|
||||||
|
if skipped_char_count > EMBED_DESCRIPTION_MAX_LENGTH {
|
||||||
|
skipped_char_count = p.len();
|
||||||
|
skipped_pages += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
skipped_pages < page
|
||||||
|
})
|
||||||
|
.take_while(|p| {
|
||||||
|
char_count += p.len();
|
||||||
|
|
||||||
|
char_count < EMBED_DESCRIPTION_MAX_LENGTH
|
||||||
|
})
|
||||||
|
.collect::<Vec<String>>();
|
||||||
|
|
||||||
|
let display = display_vec.join("\n");
|
||||||
|
|
||||||
let mut reply = CreateReply::default();
|
let mut reply = CreateReply::default();
|
||||||
|
|
||||||
reply
|
reply
|
||||||
.embed(|e| {
|
.embed(|e| {
|
||||||
e.title("Macros")
|
e.title("Macros")
|
||||||
.fields(fields)
|
.description(display)
|
||||||
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
|
.footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
|
||||||
.color(*THEME_COLOR)
|
.color(*THEME_COLOR)
|
||||||
})
|
})
|
||||||
|
@ -24,7 +24,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
|
|
||||||
let aliases = sqlx::query_as!(
|
let aliases = sqlx::query_as!(
|
||||||
Alias,
|
Alias,
|
||||||
"SELECT name, command FROM command_aliases WHERE guild_id = ?",
|
"SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||||
guild_id.0
|
guild_id.0
|
||||||
)
|
)
|
||||||
.fetch_all(&mut transaction)
|
.fetch_all(&mut transaction)
|
||||||
@ -36,7 +36,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
match parse_text_command(guild_id, alias.name, &alias.command) {
|
match parse_text_command(guild_id, alias.name, &alias.command) {
|
||||||
Some(cmd_macro) => {
|
Some(cmd_macro) => {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO macro (guild_id, name, description, commands) VALUES (?, ?, ?, ?)",
|
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
|
||||||
cmd_macro.guild_id.0,
|
cmd_macro.guild_id.0,
|
||||||
cmd_macro.name,
|
cmd_macro.name,
|
||||||
cmd_macro.description,
|
cmd_macro.description,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::{Context, Error};
|
use crate::{Context, Error};
|
||||||
|
|
||||||
pub mod delete;
|
pub mod delete;
|
||||||
|
pub mod install;
|
||||||
pub mod list;
|
pub mod list;
|
||||||
pub mod migrate;
|
pub mod migrate;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
|
@ -15,23 +15,11 @@ pub async fn record_macro(
|
|||||||
#[description = "Name for the new macro"] name: String,
|
#[description = "Name for the new macro"] name: String,
|
||||||
#[description = "Description for the new macro"] description: Option<String>,
|
#[description = "Description for the new macro"] description: Option<String>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
if name.len() > 100 {
|
|
||||||
ctx.say("Name must be less than 100 characters").await?;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if description.as_ref().map_or(0, |d| d.len()) > 100 {
|
|
||||||
ctx.say("Description must be less than 100 characters").await?;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let guild_id = ctx.guild_id().unwrap();
|
let guild_id = ctx.guild_id().unwrap();
|
||||||
|
|
||||||
let row = sqlx::query!(
|
let row = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT 1 as _e FROM macro WHERE guild_id = ? AND name = ?",
|
SELECT 1 as _e FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?",
|
||||||
guild_id.0,
|
guild_id.0,
|
||||||
name
|
name
|
||||||
)
|
)
|
||||||
@ -121,15 +109,15 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
let json = serde_json::to_string(&command_macro.commands).unwrap();
|
let json = serde_json::to_string(&command_macro.commands).unwrap();
|
||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO macro (guild_id, name, description, commands) VALUES (?, ?, ?, ?)",
|
"INSERT INTO macro (guild_id, name, description, commands) VALUES ((SELECT id FROM guilds WHERE guild = ?), ?, ?, ?)",
|
||||||
command_macro.guild_id.0,
|
command_macro.guild_id.0,
|
||||||
command_macro.name,
|
command_macro.name,
|
||||||
command_macro.description,
|
command_macro.description,
|
||||||
json
|
json
|
||||||
)
|
)
|
||||||
.execute(&ctx.data().database)
|
.execute(&ctx.data().database)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
ctx.send(|m| {
|
ctx.send(|m| {
|
||||||
m.embed(|e| {
|
m.embed(|e| {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
mod autocomplete;
|
pub mod autocomplete;
|
||||||
pub mod command_macro;
|
pub mod command_macro;
|
||||||
pub mod info_cmds;
|
pub mod info_cmds;
|
||||||
pub mod moderation_cmds;
|
pub mod moderation_cmds;
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use chrono::offset::Utc;
|
use chrono::offset::Utc;
|
||||||
use chrono_tz::{Tz, TZ_VARIANTS};
|
use chrono_tz::{Tz, TZ_VARIANTS};
|
||||||
use levenshtein::levenshtein;
|
use levenshtein::levenshtein;
|
||||||
use log::warn;
|
|
||||||
use poise::serenity_prelude::{ChannelId, Mentionable};
|
|
||||||
|
|
||||||
use super::autocomplete::timezone_autocomplete;
|
use super::autocomplete::timezone_autocomplete;
|
||||||
use crate::{consts::THEME_COLOR, models::CtxData, Context, Error};
|
use crate::{consts::THEME_COLOR, models::CtxData, Context, Error};
|
||||||
@ -149,74 +147,33 @@ pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set defaults for commands
|
|
||||||
#[poise::command(
|
|
||||||
slash_command,
|
|
||||||
identifying_name = "default",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn default(_ctx: Context<'_>) -> Result<(), Error> {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a default channel for reminders to be sent to
|
|
||||||
#[poise::command(
|
|
||||||
slash_command,
|
|
||||||
guild_only = true,
|
|
||||||
identifying_name = "default_channel",
|
|
||||||
default_member_permissions = "MANAGE_GUILD"
|
|
||||||
)]
|
|
||||||
pub async fn default_channel(
|
|
||||||
ctx: Context<'_>,
|
|
||||||
#[description = "Channel to send reminders to by default"] channel: Option<ChannelId>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if let Some(mut guild_data) = ctx.guild_data().await {
|
|
||||||
guild_data.default_channel = channel.map(|c| c.0);
|
|
||||||
|
|
||||||
guild_data.commit_changes(&ctx.data().database).await?;
|
|
||||||
|
|
||||||
if let Some(channel) = channel {
|
|
||||||
ctx.send(|r| {
|
|
||||||
r.ephemeral(true).content(format!("Default channel set to {}", channel.mention()))
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
ctx.send(|r| r.ephemeral(true).content("Default channel unset.")).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// View the webhook being used to send reminders to this channel
|
/// View the webhook being used to send reminders to this channel
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
identifying_name = "webhook_url",
|
identifying_name = "webhook_url",
|
||||||
required_permissions = "ADMINISTRATOR",
|
required_permissions = "ADMINISTRATOR"
|
||||||
default_member_permissions = "ADMINISTRATOR"
|
|
||||||
)]
|
)]
|
||||||
pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
match ctx.channel_data().await {
|
match ctx.channel_data().await {
|
||||||
Ok(data) => {
|
Ok(data) => {
|
||||||
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
|
if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
|
||||||
ctx.send(|b| {
|
let _ = ctx
|
||||||
b.ephemeral(true).content(format!(
|
.send(|b| {
|
||||||
"**Warning!**
|
b.ephemeral(true).content(format!(
|
||||||
|
"**Warning!**
|
||||||
This link can be used by users to anonymously send messages, with or without permissions.
|
This link can be used by users to anonymously send messages, with or without permissions.
|
||||||
Do not share it!
|
Do not share it!
|
||||||
|| https://discord.com/api/webhooks/{}/{} ||",
|
|| https://discord.com/api/webhooks/{}/{} ||",
|
||||||
id, token,
|
id, token,
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
.await?;
|
.await;
|
||||||
} else {
|
} else {
|
||||||
ctx.say("No webhook configured on this channel.").await?;
|
let _ = ctx.say("No webhook configured on this channel.").await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(_) => {
|
||||||
warn!("Error fetching channel data: {:?}", e);
|
let _ = ctx.say("No webhook configured on this channel.").await;
|
||||||
|
|
||||||
ctx.say("No webhook configured on this channel.").await?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,11 @@ use poise::{
|
|||||||
serenity_prelude::{
|
serenity_prelude::{
|
||||||
builder::CreateEmbed, component::ButtonStyle, model::channel::Channel, ReactionType,
|
builder::CreateEmbed, component::ButtonStyle, model::channel::Channel, ReactionType,
|
||||||
},
|
},
|
||||||
CreateReply, Modal,
|
AutocompleteChoice, CreateReply, Modal,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use super::autocomplete::timezone_autocomplete;
|
||||||
use crate::{
|
use crate::{
|
||||||
commands::autocomplete::{
|
|
||||||
multiline_autocomplete, time_hint_autocomplete, timezone_autocomplete,
|
|
||||||
},
|
|
||||||
component_models::{
|
component_models::{
|
||||||
pager::{DelPager, LookPager, Pager},
|
pager::{DelPager, LookPager, Pager},
|
||||||
ComponentDataModel, DelSelector, UndoReminder,
|
ComponentDataModel, DelSelector, UndoReminder,
|
||||||
@ -552,6 +550,20 @@ pub async fn delete_timer(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn multiline_autocomplete(
|
||||||
|
_ctx: Context<'_>,
|
||||||
|
partial: &str,
|
||||||
|
) -> Vec<AutocompleteChoice<String>> {
|
||||||
|
if partial.is_empty() {
|
||||||
|
vec![AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() }]
|
||||||
|
} else {
|
||||||
|
vec![
|
||||||
|
AutocompleteChoice { name: partial.to_string(), value: partial.to_string() },
|
||||||
|
AutocompleteChoice { name: "Multiline content...".to_string(), value: "".to_string() },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(poise::Modal)]
|
#[derive(poise::Modal)]
|
||||||
#[name = "Reminder"]
|
#[name = "Reminder"]
|
||||||
struct ContentModal {
|
struct ContentModal {
|
||||||
@ -562,7 +574,7 @@ struct ContentModal {
|
|||||||
content: String,
|
content: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a reminder. Press "+4 more" for other options.
|
/// Create a reminder. Press "+5 more" for other options. A modal will open if "content" is not provided
|
||||||
#[poise::command(
|
#[poise::command(
|
||||||
slash_command,
|
slash_command,
|
||||||
identifying_name = "remind",
|
identifying_name = "remind",
|
||||||
@ -570,9 +582,7 @@ struct ContentModal {
|
|||||||
)]
|
)]
|
||||||
pub async fn remind(
|
pub async fn remind(
|
||||||
ctx: ApplicationContext<'_>,
|
ctx: ApplicationContext<'_>,
|
||||||
#[description = "A description of the time to set the reminder for"]
|
#[description = "A description of the time to set the reminder for"] time: String,
|
||||||
#[autocomplete = "time_hint_autocomplete"]
|
|
||||||
time: String,
|
|
||||||
#[description = "The message content to send"]
|
#[description = "The message content to send"]
|
||||||
#[autocomplete = "multiline_autocomplete"]
|
#[autocomplete = "multiline_autocomplete"]
|
||||||
content: String,
|
content: String,
|
||||||
@ -653,9 +663,7 @@ async fn create_reminder(
|
|||||||
let list = channels.map(|arg| parse_mention_list(&arg)).unwrap_or_default();
|
let list = channels.map(|arg| parse_mention_list(&arg)).unwrap_or_default();
|
||||||
|
|
||||||
if list.is_empty() {
|
if list.is_empty() {
|
||||||
if let Some(channel_id) = ctx.default_channel().await {
|
if ctx.guild_id().is_some() {
|
||||||
vec![ReminderScope::Channel(channel_id.0)]
|
|
||||||
} else if ctx.guild_id().is_some() {
|
|
||||||
vec![ReminderScope::Channel(ctx.channel_id().0)]
|
vec![ReminderScope::Channel(ctx.channel_id().0)]
|
||||||
} else {
|
} else {
|
||||||
vec![ReminderScope::User(ctx.author().id.0)]
|
vec![ReminderScope::User(ctx.author().id.0)]
|
||||||
|
@ -47,7 +47,7 @@ pub async fn todo_guild_add(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO todos (guild_id, value)
|
"INSERT INTO todos (guild_id, value)
|
||||||
VALUES (?, ?)",
|
VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)",
|
||||||
ctx.guild_id().unwrap().0,
|
ctx.guild_id().unwrap().0,
|
||||||
task
|
task
|
||||||
)
|
)
|
||||||
@ -70,7 +70,9 @@ VALUES (?, ?)",
|
|||||||
)]
|
)]
|
||||||
pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> {
|
pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> {
|
||||||
let values = sqlx::query!(
|
let values = sqlx::query!(
|
||||||
"SELECT todos.id, value FROM todos WHERE guild_id = ?",
|
"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().0,
|
||||||
)
|
)
|
||||||
.fetch_all(&ctx.data().database)
|
.fetch_all(&ctx.data().database)
|
||||||
@ -120,7 +122,7 @@ pub async fn todo_channel_add(
|
|||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"INSERT INTO todos (guild_id, channel_id, value)
|
"INSERT INTO todos (guild_id, channel_id, value)
|
||||||
VALUES (?, (SELECT id FROM channels WHERE channel = ?), ?)",
|
VALUES ((SELECT id FROM guilds WHERE guild = ?), (SELECT id FROM channels WHERE channel = ?), ?)",
|
||||||
ctx.guild_id().unwrap().0,
|
ctx.guild_id().unwrap().0,
|
||||||
ctx.channel_id().0,
|
ctx.channel_id().0,
|
||||||
task
|
task
|
||||||
|
@ -222,7 +222,9 @@ WHERE channels.channel = ?",
|
|||||||
.collect::<Vec<(usize, String)>>()
|
.collect::<Vec<(usize, String)>>()
|
||||||
} else {
|
} else {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"SELECT todos.id, value FROM todos WHERE guild_id = ?",
|
"SELECT todos.id, value FROM todos
|
||||||
|
INNER JOIN guilds ON todos.guild_id = guilds.id
|
||||||
|
WHERE guilds.guild = ?",
|
||||||
pager.guild_id,
|
pager.guild_id,
|
||||||
)
|
)
|
||||||
.fetch_all(&data.database)
|
.fetch_all(&data.database)
|
||||||
|
@ -2,7 +2,7 @@ pub const DAY: u64 = 86_400;
|
|||||||
pub const HOUR: u64 = 3_600;
|
pub const HOUR: u64 = 3_600;
|
||||||
pub const MINUTE: u64 = 60;
|
pub const MINUTE: u64 = 60;
|
||||||
|
|
||||||
pub const EMBED_DESCRIPTION_MAX_LENGTH: usize = 4096;
|
pub const EMBED_DESCRIPTION_MAX_LENGTH: usize = 4000;
|
||||||
pub const SELECT_MAX_ENTRIES: usize = 25;
|
pub const SELECT_MAX_ENTRIES: usize = 25;
|
||||||
|
|
||||||
pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
pub const CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
|
||||||
|
@ -6,9 +6,7 @@ use poise::{
|
|||||||
serenity_prelude::{model::application::interaction::Interaction, utils::shard_id},
|
serenity_prelude::{model::application::interaction::Interaction, utils::shard_id},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR};
|
||||||
component_models::ComponentDataModel, models::guild_data::GuildData, Data, Error, THEME_COLOR,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn listener(
|
pub async fn listener(
|
||||||
ctx: &serenity::Context,
|
ctx: &serenity::Context,
|
||||||
@ -29,7 +27,7 @@ pub async fn listener(
|
|||||||
if *is_new {
|
if *is_new {
|
||||||
let guild_id = guild.id.as_u64().to_owned();
|
let guild_id = guild.id.as_u64().to_owned();
|
||||||
|
|
||||||
sqlx::query!("INSERT IGNORE INTO guilds (id) VALUES (?)", guild_id)
|
sqlx::query!("INSERT IGNORE INTO guilds (guild) VALUES (?)", guild_id)
|
||||||
.execute(&data.database)
|
.execute(&data.database)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
@ -63,27 +61,15 @@ To stay up to date on the latest features and fixes, join our [Discord](https://
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
poise::Event::GuildDelete { incomplete, .. } => {
|
poise::Event::GuildDelete { incomplete, .. } => {
|
||||||
let _ = sqlx::query!("DELETE FROM guilds WHERE id = ?", incomplete.id.0)
|
let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0)
|
||||||
.execute(&data.database)
|
.execute(&data.database)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
poise::Event::InteractionCreate { interaction } => {
|
poise::Event::InteractionCreate { interaction } => {
|
||||||
match interaction {
|
if let Interaction::MessageComponent(component) = interaction {
|
||||||
Interaction::ApplicationCommand(app_command) => {
|
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
|
||||||
if let Some(guild_id) = app_command.guild_id {
|
|
||||||
// check database guild exists
|
|
||||||
GuildData::from_guild(guild_id, &data.database).await?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Interaction::MessageComponent(component) => {
|
component_model.act(ctx, data, component).await;
|
||||||
let component_model =
|
|
||||||
ComponentDataModel::from_custom_id(&component.data.custom_id);
|
|
||||||
|
|
||||||
component_model.act(ctx, data, component).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
|
@ -4,7 +4,7 @@ use poise::{
|
|||||||
|
|
||||||
use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error};
|
use crate::{consts::MACRO_MAX_COMMANDS, models::command_macro::RecordedCommand, Context, Error};
|
||||||
|
|
||||||
async fn macro_check(ctx: Context<'_>) -> bool {
|
async fn recording_macro_check(ctx: Context<'_>) -> bool {
|
||||||
if let Context::Application(app_ctx) = ctx {
|
if let Context::Application(app_ctx) = ctx {
|
||||||
if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) =
|
if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) =
|
||||||
app_ctx.interaction
|
app_ctx.interaction
|
||||||
@ -95,5 +95,5 @@ async fn check_self_permissions(ctx: Context<'_>) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn all_checks(ctx: Context<'_>) -> Result<bool, Error> {
|
pub async fn all_checks(ctx: Context<'_>) -> Result<bool, Error> {
|
||||||
Ok(macro_check(ctx).await && check_self_permissions(ctx).await)
|
Ok(recording_macro_check(ctx).await && check_self_permissions(ctx).await)
|
||||||
}
|
}
|
||||||
|
12
src/main.rs
12
src/main.rs
@ -117,13 +117,10 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
|||||||
command_macro::record::record_macro(),
|
command_macro::record::record_macro(),
|
||||||
command_macro::run::run_macro(),
|
command_macro::run::run_macro(),
|
||||||
command_macro::migrate::migrate_macro(),
|
command_macro::migrate::migrate_macro(),
|
||||||
|
command_macro::install::install_macro(),
|
||||||
],
|
],
|
||||||
..command_macro::macro_base()
|
..command_macro::macro_base()
|
||||||
},
|
},
|
||||||
poise::Command {
|
|
||||||
subcommands: vec![moderation_cmds::default_channel()],
|
|
||||||
..moderation_cmds::default()
|
|
||||||
},
|
|
||||||
reminder_cmds::pause(),
|
reminder_cmds::pause(),
|
||||||
reminder_cmds::offset(),
|
reminder_cmds::offset(),
|
||||||
reminder_cmds::nudge(),
|
reminder_cmds::nudge(),
|
||||||
@ -172,12 +169,7 @@ async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
|
|||||||
Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap();
|
Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap();
|
||||||
|
|
||||||
let popular_timezones = sqlx::query!(
|
let popular_timezones = sqlx::query!(
|
||||||
"SELECT IFNULL(timezone, 'UTC') AS timezone
|
"SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21"
|
||||||
FROM users
|
|
||||||
WHERE timezone IS NOT NULL
|
|
||||||
GROUP BY timezone
|
|
||||||
ORDER BY COUNT(timezone) DESC
|
|
||||||
LIMIT 21"
|
|
||||||
)
|
)
|
||||||
.fetch_all(&database)
|
.fetch_all(&database)
|
||||||
.await
|
.await
|
||||||
|
@ -38,7 +38,7 @@ SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_u
|
|||||||
|
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, ?)
|
INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))
|
||||||
",
|
",
|
||||||
channel_id,
|
channel_id,
|
||||||
channel_name,
|
channel_name,
|
||||||
|
@ -37,13 +37,14 @@ pub struct RawCommandMacro {
|
|||||||
pub commands: Value,
|
pub commands: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a macro by name form a guild.
|
||||||
pub async fn guild_command_macro(
|
pub async fn guild_command_macro(
|
||||||
ctx: &Context<'_>,
|
ctx: &Context<'_>,
|
||||||
name: &str,
|
name: &str,
|
||||||
) -> Option<CommandMacro<Data, Error>> {
|
) -> Option<CommandMacro<Data, Error>> {
|
||||||
let row = sqlx::query!(
|
let row = sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT * FROM macro WHERE guild_id = ? AND name = ?
|
SELECT * FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?) AND name = ?
|
||||||
",
|
",
|
||||||
ctx.guild_id().unwrap().0,
|
ctx.guild_id().unwrap().0,
|
||||||
name
|
name
|
||||||
|
@ -1,52 +0,0 @@
|
|||||||
use sqlx::MySqlPool;
|
|
||||||
|
|
||||||
use crate::GuildId;
|
|
||||||
|
|
||||||
pub struct GuildData {
|
|
||||||
pub id: u64,
|
|
||||||
pub default_channel: Option<u64>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GuildData {
|
|
||||||
pub async fn from_guild(guild: GuildId, pool: &MySqlPool) -> Result<Self, sqlx::Error> {
|
|
||||||
let guild_id = guild.0;
|
|
||||||
|
|
||||||
if let Ok(row) = sqlx::query_as_unchecked!(
|
|
||||||
Self,
|
|
||||||
"
|
|
||||||
SELECT id, default_channel FROM guilds WHERE id = ?
|
|
||||||
",
|
|
||||||
guild_id
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(row)
|
|
||||||
} else {
|
|
||||||
sqlx::query!(
|
|
||||||
"
|
|
||||||
INSERT IGNORE INTO guilds (id) VALUES (?)
|
|
||||||
",
|
|
||||||
guild_id
|
|
||||||
)
|
|
||||||
.execute(&pool.clone())
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(Self { id: guild_id, default_channel: None })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn commit_changes(&self, pool: &MySqlPool) -> Result<(), sqlx::Error> {
|
|
||||||
sqlx::query!(
|
|
||||||
"
|
|
||||||
UPDATE guilds SET default_channel = ? WHERE id = ?
|
|
||||||
",
|
|
||||||
self.default_channel,
|
|
||||||
self.id
|
|
||||||
)
|
|
||||||
.execute(pool)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,28 +1,28 @@
|
|||||||
pub mod channel_data;
|
pub mod channel_data;
|
||||||
pub mod command_macro;
|
pub mod command_macro;
|
||||||
pub mod guild_data;
|
|
||||||
pub mod reminder;
|
pub mod reminder;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod user_data;
|
pub mod user_data;
|
||||||
|
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use log::warn;
|
use poise::serenity_prelude::{async_trait, model::id::UserId};
|
||||||
use poise::serenity_prelude::{async_trait, model::id::UserId, ChannelId};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
|
models::{channel_data::ChannelData, user_data::UserData},
|
||||||
CommandMacro, Context, Data, Error, GuildId,
|
CommandMacro, Context, Data, Error, GuildId,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait CtxData {
|
pub trait CtxData {
|
||||||
async fn user_data<U: Into<UserId> + Send>(&self, user_id: U) -> Result<UserData, Error>;
|
async fn user_data<U: Into<UserId> + Send>(&self, user_id: U) -> Result<UserData, Error>;
|
||||||
|
|
||||||
async fn author_data(&self) -> Result<UserData, Error>;
|
async fn author_data(&self) -> Result<UserData, Error>;
|
||||||
|
|
||||||
async fn timezone(&self) -> Tz;
|
async fn timezone(&self) -> Tz;
|
||||||
|
|
||||||
async fn channel_data(&self) -> Result<ChannelData, Error>;
|
async fn channel_data(&self) -> Result<ChannelData, Error>;
|
||||||
async fn guild_data(&self) -> Option<GuildData>;
|
|
||||||
async fn command_macros(&self) -> Result<Vec<CommandMacro<Data, Error>>, Error>;
|
async fn command_macros(&self) -> Result<Vec<CommandMacro<Data, Error>>, Error>;
|
||||||
async fn default_channel(&self) -> Option<ChannelId>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
@ -51,55 +51,24 @@ 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> {
|
||||||
self.data().command_macros(self.guild_id().unwrap()).await
|
self.data().command_macros(self.guild_id().unwrap()).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn default_channel(&self) -> Option<ChannelId> {
|
|
||||||
match self.guild_id() {
|
|
||||||
Some(guild_id) => {
|
|
||||||
let guild_data = GuildData::from_guild(guild_id, &self.data().database).await;
|
|
||||||
|
|
||||||
match guild_data {
|
|
||||||
Ok(data) => data.default_channel.map(|c| ChannelId(c)),
|
|
||||||
|
|
||||||
Err(e) => {
|
|
||||||
warn!("SQL error: {:?}", e);
|
|
||||||
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn guild_data(&self) -> Option<GuildData> {
|
|
||||||
match self.guild_id() {
|
|
||||||
Some(guild_id) => GuildData::from_guild(guild_id, &self.data().database).await.ok(),
|
|
||||||
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Data {
|
impl Data {
|
||||||
pub async fn command_macros(
|
pub(crate) async fn command_macros(
|
||||||
&self,
|
&self,
|
||||||
guild_id: GuildId,
|
guild_id: GuildId,
|
||||||
) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
|
) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
|
||||||
let rows = sqlx::query!(
|
let rows = sqlx::query!(
|
||||||
"SELECT name, description, commands FROM macro WHERE guild_id = ?",
|
"SELECT name, description, commands FROM macro WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
|
||||||
guild_id.0
|
guild_id.0
|
||||||
)
|
)
|
||||||
.fetch_all(&self.database)
|
.fetch_all(&self.database)
|
||||||
.await?
|
.await?.iter().map(|row| CommandMacro {
|
||||||
.iter()
|
|
||||||
.map(|row| CommandMacro {
|
|
||||||
guild_id,
|
guild_id,
|
||||||
name: row.name.clone(),
|
name: row.name.clone(),
|
||||||
description: row.description.clone(),
|
description: row.description.clone(),
|
||||||
commands: serde_json::from_str(&row.commands).unwrap(),
|
commands: serde_json::from_str(&row.commands).unwrap(),
|
||||||
})
|
}).collect();
|
||||||
.collect();
|
|
||||||
|
|
||||||
Ok(rows)
|
Ok(rows)
|
||||||
}
|
}
|
||||||
|
@ -245,7 +245,7 @@ LEFT JOIN
|
|||||||
ON
|
ON
|
||||||
reminders.set_by = users.id
|
reminders.set_by = users.id
|
||||||
WHERE
|
WHERE
|
||||||
channels.guild_id = ?
|
channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
|
||||||
",
|
",
|
||||||
guild_id.as_u64()
|
guild_id.as_u64()
|
||||||
)
|
)
|
||||||
|
@ -22,7 +22,7 @@ impl UserData {
|
|||||||
|
|
||||||
match sqlx::query!(
|
match sqlx::query!(
|
||||||
"
|
"
|
||||||
SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?
|
SELECT timezone FROM users WHERE user = ?
|
||||||
",
|
",
|
||||||
user_id
|
user_id
|
||||||
)
|
)
|
||||||
|
@ -61,13 +61,10 @@ pub async fn get_user_info(
|
|||||||
.member(&ctx.inner(), user_id)
|
.member(&ctx.inner(), user_id)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let timezone = sqlx::query!(
|
let timezone = sqlx::query!("SELECT timezone FROM users WHERE user = ?", user_id)
|
||||||
"SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?",
|
.fetch_one(pool.inner())
|
||||||
user_id
|
.await
|
||||||
)
|
.map_or(None, |q| Some(q.timezone));
|
||||||
.fetch_one(pool.inner())
|
|
||||||
.await
|
|
||||||
.map_or(None, |q| Some(q.timezone));
|
|
||||||
|
|
||||||
let user_info = UserInfo {
|
let user_info = UserInfo {
|
||||||
name: cookies
|
name: cookies
|
||||||
|
Reference in New Issue
Block a user