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]]
 | 
			
		||||
name = "reminder_rs"
 | 
			
		||||
version = "1.6.6"
 | 
			
		||||
version = "1.6.5"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "base64",
 | 
			
		||||
 "chrono",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "reminder_rs"
 | 
			
		||||
version = "1.6.6"
 | 
			
		||||
version = "1.6.5"
 | 
			
		||||
authors = ["jellywx <judesouthworth@pm.me>"]
 | 
			
		||||
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 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> {
 | 
			
		||||
    if partial.is_empty() {
 | 
			
		||||
@@ -24,7 +21,7 @@ pub async fn macro_name_autocomplete(ctx: Context<'_>, partial: &str) -> Vec<Str
 | 
			
		||||
SELECT name
 | 
			
		||||
FROM macro
 | 
			
		||||
WHERE
 | 
			
		||||
    guild_id = ?
 | 
			
		||||
    guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
			
		||||
    AND name LIKE CONCAT(?, '%')",
 | 
			
		||||
        ctx.guild_id().unwrap().0,
 | 
			
		||||
        partial,
 | 
			
		||||
@@ -36,96 +33,3 @@ WHERE
 | 
			
		||||
    .map(|s| s.name.clone())
 | 
			
		||||
    .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> {
 | 
			
		||||
    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,
 | 
			
		||||
        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::{
 | 
			
		||||
    component_models::pager::{MacroPager, Pager},
 | 
			
		||||
    consts::THEME_COLOR,
 | 
			
		||||
    consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
 | 
			
		||||
    models::{command_macro::CommandMacro, CtxData},
 | 
			
		||||
    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 {
 | 
			
		||||
    ((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 {
 | 
			
		||||
@@ -55,27 +75,45 @@ pub fn show_macro_page<U, E>(macros: &[CommandMacro<U, E>], page: usize) -> Crea
 | 
			
		||||
        page = pages - 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let lower = (page * 25).min(macros.len());
 | 
			
		||||
    let upper = ((page + 1) * 25).min(macros.len());
 | 
			
		||||
    let mut char_count = 0;
 | 
			
		||||
    let mut skipped_char_count = 0;
 | 
			
		||||
 | 
			
		||||
    let fields = macros[lower..upper].iter().map(|m| {
 | 
			
		||||
    let mut skipped_pages = 0;
 | 
			
		||||
 | 
			
		||||
    let display_vec: Vec<String> = macros
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|m| {
 | 
			
		||||
            if let Some(description) = &m.description {
 | 
			
		||||
            (
 | 
			
		||||
                m.name.clone(),
 | 
			
		||||
                format!("*{}*\n- Has {} commands", description, m.commands.len()),
 | 
			
		||||
                true,
 | 
			
		||||
            )
 | 
			
		||||
                format!("**{}**\n- *{}*\n- Has {} commands", m.name, description, m.commands.len())
 | 
			
		||||
            } 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();
 | 
			
		||||
 | 
			
		||||
    reply
 | 
			
		||||
        .embed(|e| {
 | 
			
		||||
            e.title("Macros")
 | 
			
		||||
                .fields(fields)
 | 
			
		||||
                .description(display)
 | 
			
		||||
                .footer(|f| f.text(format!("Page {} of {}", page + 1, pages)))
 | 
			
		||||
                .color(*THEME_COLOR)
 | 
			
		||||
        })
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ pub async fn migrate_macro(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
 | 
			
		||||
    let aliases = sqlx::query_as!(
 | 
			
		||||
        Alias,
 | 
			
		||||
        "SELECT name, command FROM command_aliases WHERE guild_id = ?",
 | 
			
		||||
        "SELECT name, command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHERE guild = ?)",
 | 
			
		||||
        guild_id.0
 | 
			
		||||
    )
 | 
			
		||||
    .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) {
 | 
			
		||||
            Some(cmd_macro) => {
 | 
			
		||||
                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.name,
 | 
			
		||||
                    cmd_macro.description,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
use crate::{Context, Error};
 | 
			
		||||
 | 
			
		||||
pub mod delete;
 | 
			
		||||
pub mod install;
 | 
			
		||||
pub mod list;
 | 
			
		||||
pub mod migrate;
 | 
			
		||||
pub mod record;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,23 +15,11 @@ pub async fn record_macro(
 | 
			
		||||
    #[description = "Name for the new macro"] name: String,
 | 
			
		||||
    #[description = "Description for the new macro"] description: Option<String>,
 | 
			
		||||
) -> 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 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,
 | 
			
		||||
        name
 | 
			
		||||
    )
 | 
			
		||||
@@ -121,7 +109,7 @@ pub async fn finish_macro(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
            let json = serde_json::to_string(&command_macro.commands).unwrap();
 | 
			
		||||
 | 
			
		||||
            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.name,
 | 
			
		||||
                command_macro.description,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
mod autocomplete;
 | 
			
		||||
pub mod autocomplete;
 | 
			
		||||
pub mod command_macro;
 | 
			
		||||
pub mod info_cmds;
 | 
			
		||||
pub mod moderation_cmds;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,6 @@
 | 
			
		||||
use chrono::offset::Utc;
 | 
			
		||||
use chrono_tz::{Tz, TZ_VARIANTS};
 | 
			
		||||
use levenshtein::levenshtein;
 | 
			
		||||
use log::warn;
 | 
			
		||||
use poise::serenity_prelude::{ChannelId, Mentionable};
 | 
			
		||||
 | 
			
		||||
use super::autocomplete::timezone_autocomplete;
 | 
			
		||||
use crate::{consts::THEME_COLOR, models::CtxData, Context, Error};
 | 
			
		||||
@@ -149,57 +147,18 @@ pub async fn unset_allowed_dm(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    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
 | 
			
		||||
#[poise::command(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "webhook_url",
 | 
			
		||||
    required_permissions = "ADMINISTRATOR",
 | 
			
		||||
    default_member_permissions = "ADMINISTRATOR"
 | 
			
		||||
    required_permissions = "ADMINISTRATOR"
 | 
			
		||||
)]
 | 
			
		||||
pub async fn webhook(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    match ctx.channel_data().await {
 | 
			
		||||
        Ok(data) => {
 | 
			
		||||
            if let (Some(id), Some(token)) = (data.webhook_id, data.webhook_token) {
 | 
			
		||||
                ctx.send(|b| {
 | 
			
		||||
                let _ = ctx
 | 
			
		||||
                    .send(|b| {
 | 
			
		||||
                        b.ephemeral(true).content(format!(
 | 
			
		||||
                            "**Warning!**
 | 
			
		||||
This link can be used by users to anonymously send messages, with or without permissions.
 | 
			
		||||
@@ -208,15 +167,13 @@ Do not share it!
 | 
			
		||||
                            id, token,
 | 
			
		||||
                        ))
 | 
			
		||||
                    })
 | 
			
		||||
                .await?;
 | 
			
		||||
                    .await;
 | 
			
		||||
            } else {
 | 
			
		||||
                ctx.say("No webhook configured on this channel.").await?;
 | 
			
		||||
                let _ = ctx.say("No webhook configured on this channel.").await;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        Err(e) => {
 | 
			
		||||
            warn!("Error fetching channel data: {:?}", e);
 | 
			
		||||
 | 
			
		||||
            ctx.say("No webhook configured on this channel.").await?;
 | 
			
		||||
        Err(_) => {
 | 
			
		||||
            let _ = ctx.say("No webhook configured on this channel.").await;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -11,13 +11,11 @@ use poise::{
 | 
			
		||||
    serenity_prelude::{
 | 
			
		||||
        builder::CreateEmbed, component::ButtonStyle, model::channel::Channel, ReactionType,
 | 
			
		||||
    },
 | 
			
		||||
    CreateReply, Modal,
 | 
			
		||||
    AutocompleteChoice, CreateReply, Modal,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use super::autocomplete::timezone_autocomplete;
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::autocomplete::{
 | 
			
		||||
        multiline_autocomplete, time_hint_autocomplete, timezone_autocomplete,
 | 
			
		||||
    },
 | 
			
		||||
    component_models::{
 | 
			
		||||
        pager::{DelPager, LookPager, Pager},
 | 
			
		||||
        ComponentDataModel, DelSelector, UndoReminder,
 | 
			
		||||
@@ -552,6 +550,20 @@ pub async fn delete_timer(
 | 
			
		||||
    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)]
 | 
			
		||||
#[name = "Reminder"]
 | 
			
		||||
struct ContentModal {
 | 
			
		||||
@@ -562,7 +574,7 @@ struct ContentModal {
 | 
			
		||||
    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(
 | 
			
		||||
    slash_command,
 | 
			
		||||
    identifying_name = "remind",
 | 
			
		||||
@@ -570,9 +582,7 @@ struct ContentModal {
 | 
			
		||||
)]
 | 
			
		||||
pub async fn remind(
 | 
			
		||||
    ctx: ApplicationContext<'_>,
 | 
			
		||||
    #[description = "A description of the time to set the reminder for"]
 | 
			
		||||
    #[autocomplete = "time_hint_autocomplete"]
 | 
			
		||||
    time: String,
 | 
			
		||||
    #[description = "A description of the time to set the reminder for"] time: String,
 | 
			
		||||
    #[description = "The message content to send"]
 | 
			
		||||
    #[autocomplete = "multiline_autocomplete"]
 | 
			
		||||
    content: String,
 | 
			
		||||
@@ -653,9 +663,7 @@ async fn create_reminder(
 | 
			
		||||
                let list = channels.map(|arg| parse_mention_list(&arg)).unwrap_or_default();
 | 
			
		||||
 | 
			
		||||
                if list.is_empty() {
 | 
			
		||||
                    if let Some(channel_id) = ctx.default_channel().await {
 | 
			
		||||
                        vec![ReminderScope::Channel(channel_id.0)]
 | 
			
		||||
                    } else if ctx.guild_id().is_some() {
 | 
			
		||||
                    if ctx.guild_id().is_some() {
 | 
			
		||||
                        vec![ReminderScope::Channel(ctx.channel_id().0)]
 | 
			
		||||
                    } else {
 | 
			
		||||
                        vec![ReminderScope::User(ctx.author().id.0)]
 | 
			
		||||
 
 | 
			
		||||
@@ -47,7 +47,7 @@ pub async fn todo_guild_add(
 | 
			
		||||
) -> Result<(), Error> {
 | 
			
		||||
    sqlx::query!(
 | 
			
		||||
        "INSERT INTO todos (guild_id, value)
 | 
			
		||||
VALUES (?, ?)",
 | 
			
		||||
VALUES ((SELECT id FROM guilds WHERE guild = ?), ?)",
 | 
			
		||||
        ctx.guild_id().unwrap().0,
 | 
			
		||||
        task
 | 
			
		||||
    )
 | 
			
		||||
@@ -70,7 +70,9 @@ VALUES (?, ?)",
 | 
			
		||||
)]
 | 
			
		||||
pub async fn todo_guild_view(ctx: Context<'_>) -> Result<(), Error> {
 | 
			
		||||
    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,
 | 
			
		||||
    )
 | 
			
		||||
    .fetch_all(&ctx.data().database)
 | 
			
		||||
@@ -120,7 +122,7 @@ pub async fn todo_channel_add(
 | 
			
		||||
 | 
			
		||||
    sqlx::query!(
 | 
			
		||||
        "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.channel_id().0,
 | 
			
		||||
        task
 | 
			
		||||
 
 | 
			
		||||
@@ -222,7 +222,9 @@ WHERE channels.channel = ?",
 | 
			
		||||
                        .collect::<Vec<(usize, String)>>()
 | 
			
		||||
                    } else {
 | 
			
		||||
                        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,
 | 
			
		||||
                        )
 | 
			
		||||
                        .fetch_all(&data.database)
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ pub const DAY: u64 = 86_400;
 | 
			
		||||
pub const HOUR: u64 = 3_600;
 | 
			
		||||
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 CHARACTERS: &str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,7 @@ use poise::{
 | 
			
		||||
    serenity_prelude::{model::application::interaction::Interaction, utils::shard_id},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    component_models::ComponentDataModel, models::guild_data::GuildData, Data, Error, THEME_COLOR,
 | 
			
		||||
};
 | 
			
		||||
use crate::{component_models::ComponentDataModel, Data, Error, THEME_COLOR};
 | 
			
		||||
 | 
			
		||||
pub async fn listener(
 | 
			
		||||
    ctx: &serenity::Context,
 | 
			
		||||
@@ -29,7 +27,7 @@ pub async fn listener(
 | 
			
		||||
            if *is_new {
 | 
			
		||||
                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)
 | 
			
		||||
                    .await?;
 | 
			
		||||
 | 
			
		||||
@@ -63,28 +61,16 @@ To stay up to date on the latest features and fixes, join our [Discord](https://
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        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)
 | 
			
		||||
                .await;
 | 
			
		||||
        }
 | 
			
		||||
        poise::Event::InteractionCreate { interaction } => {
 | 
			
		||||
            match interaction {
 | 
			
		||||
                Interaction::ApplicationCommand(app_command) => {
 | 
			
		||||
                    if let Some(guild_id) = app_command.guild_id {
 | 
			
		||||
                        // check database guild exists
 | 
			
		||||
                        GuildData::from_guild(guild_id, &data.database).await?;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                Interaction::MessageComponent(component) => {
 | 
			
		||||
                    let component_model =
 | 
			
		||||
                        ComponentDataModel::from_custom_id(&component.data.custom_id);
 | 
			
		||||
            if let Interaction::MessageComponent(component) = interaction {
 | 
			
		||||
                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};
 | 
			
		||||
 | 
			
		||||
async fn macro_check(ctx: Context<'_>) -> bool {
 | 
			
		||||
async fn recording_macro_check(ctx: Context<'_>) -> bool {
 | 
			
		||||
    if let Context::Application(app_ctx) = ctx {
 | 
			
		||||
        if let ApplicationCommandOrAutocompleteInteraction::ApplicationCommand(_) =
 | 
			
		||||
            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> {
 | 
			
		||||
    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::run::run_macro(),
 | 
			
		||||
                    command_macro::migrate::migrate_macro(),
 | 
			
		||||
                    command_macro::install::install_macro(),
 | 
			
		||||
                ],
 | 
			
		||||
                ..command_macro::macro_base()
 | 
			
		||||
            },
 | 
			
		||||
            poise::Command {
 | 
			
		||||
                subcommands: vec![moderation_cmds::default_channel()],
 | 
			
		||||
                ..moderation_cmds::default()
 | 
			
		||||
            },
 | 
			
		||||
            reminder_cmds::pause(),
 | 
			
		||||
            reminder_cmds::offset(),
 | 
			
		||||
            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();
 | 
			
		||||
 | 
			
		||||
    let popular_timezones = sqlx::query!(
 | 
			
		||||
        "SELECT IFNULL(timezone, 'UTC') AS timezone
 | 
			
		||||
        FROM users
 | 
			
		||||
        WHERE timezone IS NOT NULL
 | 
			
		||||
        GROUP BY timezone
 | 
			
		||||
        ORDER BY COUNT(timezone) DESC
 | 
			
		||||
        LIMIT 21"
 | 
			
		||||
        "SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21"
 | 
			
		||||
    )
 | 
			
		||||
    .fetch_all(&database)
 | 
			
		||||
    .await
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_u
 | 
			
		||||
 | 
			
		||||
            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_name,
 | 
			
		||||
 
 | 
			
		||||
@@ -37,13 +37,14 @@ pub struct RawCommandMacro {
 | 
			
		||||
    pub commands: Value,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Get a macro by name form a guild.
 | 
			
		||||
pub async fn guild_command_macro(
 | 
			
		||||
    ctx: &Context<'_>,
 | 
			
		||||
    name: &str,
 | 
			
		||||
) -> Option<CommandMacro<Data, Error>> {
 | 
			
		||||
    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,
 | 
			
		||||
        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 command_macro;
 | 
			
		||||
pub mod guild_data;
 | 
			
		||||
pub mod reminder;
 | 
			
		||||
pub mod timer;
 | 
			
		||||
pub mod user_data;
 | 
			
		||||
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
use log::warn;
 | 
			
		||||
use poise::serenity_prelude::{async_trait, model::id::UserId, ChannelId};
 | 
			
		||||
use poise::serenity_prelude::{async_trait, model::id::UserId};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
 | 
			
		||||
    models::{channel_data::ChannelData, user_data::UserData},
 | 
			
		||||
    CommandMacro, Context, Data, Error, GuildId,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
pub trait CtxData {
 | 
			
		||||
    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 timezone(&self) -> Tz;
 | 
			
		||||
 | 
			
		||||
    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 default_channel(&self) -> Option<ChannelId>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
@@ -51,55 +51,24 @@ impl CtxData for Context<'_> {
 | 
			
		||||
    async fn command_macros(&self) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
 | 
			
		||||
        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 {
 | 
			
		||||
    pub async fn command_macros(
 | 
			
		||||
    pub(crate) async fn command_macros(
 | 
			
		||||
        &self,
 | 
			
		||||
        guild_id: GuildId,
 | 
			
		||||
    ) -> Result<Vec<CommandMacro<Data, Error>>, Error> {
 | 
			
		||||
        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
 | 
			
		||||
        )
 | 
			
		||||
        .fetch_all(&self.database)
 | 
			
		||||
        .await?
 | 
			
		||||
        .iter()
 | 
			
		||||
        .map(|row| CommandMacro {
 | 
			
		||||
        .await?.iter().map(|row| CommandMacro {
 | 
			
		||||
            guild_id,
 | 
			
		||||
            name: row.name.clone(),
 | 
			
		||||
            description: row.description.clone(),
 | 
			
		||||
            commands: serde_json::from_str(&row.commands).unwrap(),
 | 
			
		||||
        })
 | 
			
		||||
        .collect();
 | 
			
		||||
        }).collect();
 | 
			
		||||
 | 
			
		||||
        Ok(rows)
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -245,7 +245,7 @@ LEFT JOIN
 | 
			
		||||
ON
 | 
			
		||||
    reminders.set_by = users.id
 | 
			
		||||
WHERE
 | 
			
		||||
    channels.guild_id = ?
 | 
			
		||||
    channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
			
		||||
                ",
 | 
			
		||||
                    guild_id.as_u64()
 | 
			
		||||
                )
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ impl UserData {
 | 
			
		||||
 | 
			
		||||
        match sqlx::query!(
 | 
			
		||||
            "
 | 
			
		||||
SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?
 | 
			
		||||
SELECT timezone FROM users WHERE user = ?
 | 
			
		||||
            ",
 | 
			
		||||
            user_id
 | 
			
		||||
        )
 | 
			
		||||
 
 | 
			
		||||
@@ -61,10 +61,7 @@ pub async fn get_user_info(
 | 
			
		||||
            .member(&ctx.inner(), user_id)
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        let timezone = sqlx::query!(
 | 
			
		||||
            "SELECT IFNULL(timezone, 'UTC') AS timezone FROM users WHERE user = ?",
 | 
			
		||||
            user_id
 | 
			
		||||
        )
 | 
			
		||||
        let timezone = sqlx::query!("SELECT timezone FROM users WHERE user = ?", user_id)
 | 
			
		||||
            .fetch_one(pool.inner())
 | 
			
		||||
            .await
 | 
			
		||||
            .map_or(None, |q| Some(q.timezone));
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user