Compare commits
	
		
			13 Commits
		
	
	
		
			1.5-dead
			...
			discord-ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51d2ac2b92 | |||
| 2e153cffab | |||
| 540f120d7d | |||
| 59ffb505dc | |||
| 2bec2b9e12 | |||
| 507075d9d4 | |||
| 85659f05aa | |||
| eb07ece779 | |||
| 1a09f026c9 | |||
| b31843c478 | |||
| 9109250fe8 | |||
| 2346c2e978 | |||
| a0da4dcf00 | 
							
								
								
									
										543
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										543
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
[package]
 | 
			
		||||
name = "reminder_rs"
 | 
			
		||||
version = "1.5.0"
 | 
			
		||||
version = "1.5.0-2"
 | 
			
		||||
authors = ["jellywx <judesouthworth@pm.me>"]
 | 
			
		||||
edition = "2018"
 | 
			
		||||
 | 
			
		||||
@@ -23,7 +23,7 @@ rand = "0.7"
 | 
			
		||||
Inflector = "0.11"
 | 
			
		||||
levenshtein = "1.0"
 | 
			
		||||
# serenity = { version = "0.10", features = ["collector"] }
 | 
			
		||||
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "next", features = ["collector", "unstable_discord_api"] }
 | 
			
		||||
serenity = { path = "/home/jude/serenity", features = ["collector", "unstable_discord_api"] }
 | 
			
		||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
 | 
			
		||||
 | 
			
		||||
[dependencies.regex_command_attr]
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,5 @@
 | 
			
		||||
CREATE DATABASE IF NOT EXISTS reminders;
 | 
			
		||||
 | 
			
		||||
SET FOREIGN_KEY_CHECKS=0;
 | 
			
		||||
 | 
			
		||||
USE reminders;
 | 
			
		||||
 
 | 
			
		||||
@@ -48,15 +48,16 @@ CREATE TABLE reminders_new (
 | 
			
		||||
    PRIMARY KEY (id),
 | 
			
		||||
 | 
			
		||||
    FOREIGN KEY (`channel_id`) REFERENCES channels (`id`) ON DELETE CASCADE,
 | 
			
		||||
    FOREIGN KEY (`set_by`) REFERENCES users (`id`) ON DELETE SET NULL,
 | 
			
		||||
    FOREIGN KEY (`set_by`) REFERENCES users (`id`) ON DELETE SET NULL
 | 
			
		||||
 | 
			
		||||
    # disallow having a reminder as restartable if it has no interval
 | 
			
		||||
    CONSTRAINT restartable_interval_mutex CHECK (`restartable` = 0 OR `interval` IS NULL),
 | 
			
		||||
    -- , CONSTRAINT restartable_interval_mutex CHECK (`restartable` = 0 OR `interval` IS NULL)
 | 
			
		||||
    # disallow disabling if interval is unspecified
 | 
			
		||||
    CONSTRAINT interval_enabled_mutin CHECK (`enabled` = 1 OR `interval` IS NULL),
 | 
			
		||||
    -- , CONSTRAINT interval_enabled_mutin CHECK (`enabled` = 1 OR `interval` IS NULL)
 | 
			
		||||
    # disallow an expiry time if interval is unspecified
 | 
			
		||||
    CONSTRAINT interval_expires_mutin CHECK (`expires` IS NULL OR `interval` IS NOT NULL)
 | 
			
		||||
);
 | 
			
		||||
    -- , CONSTRAINT interval_expires_mutin CHECK (`expires` IS NULL OR `interval` IS NOT NULL)
 | 
			
		||||
)
 | 
			
		||||
COLLATE utf8mb4_unicode_ci;
 | 
			
		||||
 | 
			
		||||
# import data from other tables
 | 
			
		||||
INSERT INTO reminders_new (
 | 
			
		||||
@@ -86,7 +87,7 @@ INSERT INTO reminders_new (
 | 
			
		||||
                                    reminders.uid,
 | 
			
		||||
                                    reminders.name,
 | 
			
		||||
                                    reminders.channel_id,
 | 
			
		||||
                                    FROM_UNIXTIME(reminders.time),
 | 
			
		||||
                                    DATE_ADD(FROM_UNIXTIME(0), INTERVAL reminders.`time` SECOND),
 | 
			
		||||
                                    reminders.`interval`,
 | 
			
		||||
                                    reminders.enabled,
 | 
			
		||||
                                    reminders.expires,
 | 
			
		||||
@@ -120,7 +121,7 @@ CREATE TABLE embed_fields_new (
 | 
			
		||||
 | 
			
		||||
    PRIMARY KEY (id),
 | 
			
		||||
 | 
			
		||||
    FOREIGN KEY (reminder_id) REFERENCES reminders_new (id)
 | 
			
		||||
    FOREIGN KEY (reminder_id) REFERENCES reminders_new (id) ON DELETE CASCADE
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
INSERT INTO embed_fields_new (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,22 +1,16 @@
 | 
			
		||||
use regex_command_attr::command;
 | 
			
		||||
 | 
			
		||||
use serenity::{
 | 
			
		||||
    builder::CreateEmbedFooter,
 | 
			
		||||
    client::Context,
 | 
			
		||||
    model::{
 | 
			
		||||
        channel::Message,
 | 
			
		||||
        interactions::{Interaction, InteractionResponseType},
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
use serenity::{client::Context, model::channel::Message};
 | 
			
		||||
 | 
			
		||||
use chrono::offset::Utc;
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    command_help, consts::DEFAULT_PREFIX, get_ctx_data, language_manager::LanguageManager,
 | 
			
		||||
    models::CtxGuildData, models::UserData, FrameworkCtx, THEME_COLOR,
 | 
			
		||||
    models::UserData, FrameworkCtx, THEME_COLOR,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use inflector::Inflector;
 | 
			
		||||
use crate::models::CtxGuildData;
 | 
			
		||||
use serenity::builder::CreateEmbedFooter;
 | 
			
		||||
use std::sync::Arc;
 | 
			
		||||
use std::time::{SystemTime, UNIX_EPOCH};
 | 
			
		||||
 | 
			
		||||
@@ -136,137 +130,6 @@ async fn help(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn help_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    async fn default_help(
 | 
			
		||||
        ctx: &Context,
 | 
			
		||||
        interaction: Interaction,
 | 
			
		||||
        lm: Arc<LanguageManager>,
 | 
			
		||||
        language: &str,
 | 
			
		||||
    ) {
 | 
			
		||||
        let desc = lm.get(language, "help/desc").replace("{prefix}", "/");
 | 
			
		||||
        let footer = footer(ctx).await;
 | 
			
		||||
 | 
			
		||||
        interaction
 | 
			
		||||
            .create_interaction_response(ctx, |response| {
 | 
			
		||||
                response
 | 
			
		||||
                    .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                    .interaction_response_data(|data| {
 | 
			
		||||
                        data.embed(move |e| {
 | 
			
		||||
                            e.title("Help Menu")
 | 
			
		||||
                                .description(desc)
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/setup_title"),
 | 
			
		||||
                                    "`lang` `timezone` `meridian`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/mod_title"),
 | 
			
		||||
                                    "`prefix` `blacklist` `restrict` `alias`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/reminder_title"),
 | 
			
		||||
                                    "`remind` `interval` `natural` `look` `countdown`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/reminder_mod_title"),
 | 
			
		||||
                                    "`del` `offset` `pause` `nudge`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/info_title"),
 | 
			
		||||
                                    "`help` `info` `donate` `clock`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(
 | 
			
		||||
                                    lm.get(language, "help/todo_title"),
 | 
			
		||||
                                    "`todo` `todos` `todoc`",
 | 
			
		||||
                                    true,
 | 
			
		||||
                                )
 | 
			
		||||
                                .field(lm.get(language, "help/other_title"), "`timer`", true)
 | 
			
		||||
                                .footer(footer)
 | 
			
		||||
                                .color(*THEME_COLOR)
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn command_help(
 | 
			
		||||
        ctx: &Context,
 | 
			
		||||
        interaction: Interaction,
 | 
			
		||||
        lm: Arc<LanguageManager>,
 | 
			
		||||
        language: &str,
 | 
			
		||||
        command_name: &str,
 | 
			
		||||
    ) {
 | 
			
		||||
        interaction
 | 
			
		||||
            .create_interaction_response(ctx, |r| {
 | 
			
		||||
                r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                    .interaction_response_data(|data| {
 | 
			
		||||
                        data.embed(move |e| {
 | 
			
		||||
                            e.title(format!("{} Help", command_name.to_title_case()))
 | 
			
		||||
                                .description(
 | 
			
		||||
                                    lm.get(&language, &format!("help/{}", command_name))
 | 
			
		||||
                                        .replace("{prefix}", "/"),
 | 
			
		||||
                                )
 | 
			
		||||
                                .footer(|f| {
 | 
			
		||||
                                    f.text(concat!(
 | 
			
		||||
                                        env!("CARGO_PKG_NAME"),
 | 
			
		||||
                                        " ver ",
 | 
			
		||||
                                        env!("CARGO_PKG_VERSION")
 | 
			
		||||
                                    ))
 | 
			
		||||
                                })
 | 
			
		||||
                                .color(*THEME_COLOR)
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(interaction.member.user.id, &pool);
 | 
			
		||||
 | 
			
		||||
    if let Some(data) = &interaction.data {
 | 
			
		||||
        if let Some(command_name) = data
 | 
			
		||||
            .options
 | 
			
		||||
            .first()
 | 
			
		||||
            .map(|opt| {
 | 
			
		||||
                opt.value
 | 
			
		||||
                    .clone()
 | 
			
		||||
                    .map(|inner| inner.as_str().unwrap().to_string())
 | 
			
		||||
            })
 | 
			
		||||
            .flatten()
 | 
			
		||||
        {
 | 
			
		||||
            let framework = ctx
 | 
			
		||||
                .data
 | 
			
		||||
                .read()
 | 
			
		||||
                .await
 | 
			
		||||
                .get::<FrameworkCtx>()
 | 
			
		||||
                .cloned()
 | 
			
		||||
                .expect("Could not get FrameworkCtx from data");
 | 
			
		||||
 | 
			
		||||
            let matched = framework
 | 
			
		||||
                .commands
 | 
			
		||||
                .get(&command_name)
 | 
			
		||||
                .map(|inner| inner.name);
 | 
			
		||||
 | 
			
		||||
            if let Some(command_name) = matched {
 | 
			
		||||
                command_help(ctx, interaction, lm, &language.await, command_name).await
 | 
			
		||||
            } else {
 | 
			
		||||
                default_help(ctx, interaction, lm, &language.await).await;
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            default_help(ctx, interaction, lm, &language.await).await;
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        default_help(ctx, interaction, lm, &language.await).await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[command]
 | 
			
		||||
async fn info(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
@@ -295,36 +158,6 @@ async fn info(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
        .await;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn info_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&interaction.member, &pool);
 | 
			
		||||
    let current_user = ctx.cache.current_user();
 | 
			
		||||
    let footer = footer(ctx).await;
 | 
			
		||||
 | 
			
		||||
    let desc = lm
 | 
			
		||||
        .get(&language.await, "info")
 | 
			
		||||
        .replacen("{user}", ¤t_user.await.name, 1)
 | 
			
		||||
        .replace("{default_prefix}", &*DEFAULT_PREFIX)
 | 
			
		||||
        .replace("{prefix}", "/");
 | 
			
		||||
 | 
			
		||||
    interaction
 | 
			
		||||
        .create_interaction_response(ctx, |response| {
 | 
			
		||||
            response
 | 
			
		||||
                .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                .interaction_response_data(|data| {
 | 
			
		||||
                    data.embed(move |e| {
 | 
			
		||||
                        e.title("Info")
 | 
			
		||||
                            .description(desc)
 | 
			
		||||
                            .footer(footer)
 | 
			
		||||
                            .color(*THEME_COLOR)
 | 
			
		||||
                    })
 | 
			
		||||
                })
 | 
			
		||||
        })
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[command]
 | 
			
		||||
async fn donate(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
@@ -346,30 +179,6 @@ async fn donate(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
        .await;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn donate_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&interaction.member, &pool).await;
 | 
			
		||||
    let desc = lm.get(&language, "donate");
 | 
			
		||||
    let footer = footer(ctx).await;
 | 
			
		||||
 | 
			
		||||
    interaction
 | 
			
		||||
        .create_interaction_response(ctx, |response| {
 | 
			
		||||
            response
 | 
			
		||||
                .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                .interaction_response_data(|data| {
 | 
			
		||||
                    data.embed(move |e| {
 | 
			
		||||
                        e.title("Donate")
 | 
			
		||||
                            .description(desc)
 | 
			
		||||
                            .footer(footer)
 | 
			
		||||
                            .color(*THEME_COLOR)
 | 
			
		||||
                    })
 | 
			
		||||
                })
 | 
			
		||||
        })
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[command]
 | 
			
		||||
async fn dashboard(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
    let footer = footer(ctx).await;
 | 
			
		||||
@@ -407,30 +216,3 @@ async fn clock(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
        )
 | 
			
		||||
        .await;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn clock_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&interaction.member, &pool).await;
 | 
			
		||||
    let timezone = UserData::timezone_of(&interaction.member, &pool).await;
 | 
			
		||||
    let meridian = UserData::meridian_of(&interaction.member, &pool).await;
 | 
			
		||||
 | 
			
		||||
    let now = Utc::now().with_timezone(&timezone);
 | 
			
		||||
 | 
			
		||||
    let clock_display = lm.get(&language, "clock/time");
 | 
			
		||||
 | 
			
		||||
    interaction
 | 
			
		||||
        .create_interaction_response(ctx, |response| {
 | 
			
		||||
            response
 | 
			
		||||
                .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                .interaction_response_data(|data| {
 | 
			
		||||
                    data.content(clock_display.replacen(
 | 
			
		||||
                        "{}",
 | 
			
		||||
                        &now.format(meridian.fmt_str()).to_string(),
 | 
			
		||||
                        1,
 | 
			
		||||
                    ))
 | 
			
		||||
                })
 | 
			
		||||
        })
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,13 @@
 | 
			
		||||
use regex_command_attr::command;
 | 
			
		||||
 | 
			
		||||
use serenity::{
 | 
			
		||||
    builder::CreateActionRow,
 | 
			
		||||
    client::Context,
 | 
			
		||||
    framework::Framework,
 | 
			
		||||
    model::{
 | 
			
		||||
        channel::ReactionType,
 | 
			
		||||
        channel::{Channel, Message},
 | 
			
		||||
        id::{ChannelId, RoleId},
 | 
			
		||||
        interactions::{Interaction, InteractionResponseType},
 | 
			
		||||
        channel::Message,
 | 
			
		||||
        id::{ChannelId, MessageId, RoleId},
 | 
			
		||||
        interactions::ButtonStyle,
 | 
			
		||||
    },
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
@@ -29,7 +29,7 @@ use crate::{
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::models::CtxGuildData;
 | 
			
		||||
use std::{collections::HashMap, iter, time::Duration};
 | 
			
		||||
use std::{collections::HashMap, iter};
 | 
			
		||||
 | 
			
		||||
#[command]
 | 
			
		||||
#[supports_dm(false)]
 | 
			
		||||
@@ -140,22 +140,28 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
            Err(_) => {
 | 
			
		||||
                let filtered_tz = TZ_VARIANTS
 | 
			
		||||
                    .iter()
 | 
			
		||||
                    .map(|tz| (tz, tz.to_string(), levenshtein(&tz.to_string(), &args)))
 | 
			
		||||
                    .filter(|(_, tz, dist)| args.contains(tz) || tz.contains(&args) || dist < &4)
 | 
			
		||||
                    .filter(|tz| {
 | 
			
		||||
                        args.contains(&tz.to_string())
 | 
			
		||||
                            || tz.to_string().contains(&args)
 | 
			
		||||
                            || levenshtein(&tz.to_string(), &args) < 4
 | 
			
		||||
                    })
 | 
			
		||||
                    .take(25)
 | 
			
		||||
                    .map(|(tz, tz_s, _)| {
 | 
			
		||||
                        (
 | 
			
		||||
                            tz_s,
 | 
			
		||||
                            format!(
 | 
			
		||||
                                "🕗 `{}`",
 | 
			
		||||
                                Utc::now()
 | 
			
		||||
                                    .with_timezone(tz)
 | 
			
		||||
                                    .format(user_data.meridian().fmt_str_short())
 | 
			
		||||
                                    .to_string()
 | 
			
		||||
                            ),
 | 
			
		||||
                            true,
 | 
			
		||||
                        )
 | 
			
		||||
                    });
 | 
			
		||||
                    .map(|t| t.to_owned())
 | 
			
		||||
                    .collect::<Vec<Tz>>();
 | 
			
		||||
 | 
			
		||||
                let fields = filtered_tz.iter().map(|tz| {
 | 
			
		||||
                    (
 | 
			
		||||
                        tz.to_string(),
 | 
			
		||||
                        format!(
 | 
			
		||||
                            "🕗 `{}`",
 | 
			
		||||
                            Utc::now()
 | 
			
		||||
                                .with_timezone(tz)
 | 
			
		||||
                                .format(user_data.meridian().fmt_str_short())
 | 
			
		||||
                                .to_string()
 | 
			
		||||
                        ),
 | 
			
		||||
                        true,
 | 
			
		||||
                    )
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
                let _ = msg
 | 
			
		||||
                    .channel_id
 | 
			
		||||
@@ -164,9 +170,24 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                            e.title(lm.get(&user_data.language, "timezone/no_timezone_title"))
 | 
			
		||||
                                .description(lm.get(&user_data.language, "timezone/no_timezone"))
 | 
			
		||||
                                .color(*THEME_COLOR)
 | 
			
		||||
                                .fields(filtered_tz)
 | 
			
		||||
                                .fields(fields)
 | 
			
		||||
                                .footer(|f| f.text(footer_text))
 | 
			
		||||
                                .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
			
		||||
                        }).components(|c| {
 | 
			
		||||
                            for row in filtered_tz.as_slice().chunks(5) {
 | 
			
		||||
                                let mut action_row = CreateActionRow::default();
 | 
			
		||||
                                for timezone in row {
 | 
			
		||||
                                    action_row.create_button(|b| {
 | 
			
		||||
                                        b.style(ButtonStyle::Secondary)
 | 
			
		||||
                                            .label(timezone.to_string())
 | 
			
		||||
                                            .custom_id(format!("timezone:{}", timezone.to_string()))
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                c.add_action_row(action_row);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            c
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                    .await;
 | 
			
		||||
@@ -210,121 +231,27 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                        .footer(|f| f.text(footer_text))
 | 
			
		||||
                        .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
			
		||||
                })
 | 
			
		||||
                .components(|c| {
 | 
			
		||||
                    for row in popular_timezones.as_slice().chunks(5) {
 | 
			
		||||
                        let mut action_row = CreateActionRow::default();
 | 
			
		||||
                        for timezone in row {
 | 
			
		||||
                            action_row.create_button(|b| {
 | 
			
		||||
                                b.style(ButtonStyle::Secondary)
 | 
			
		||||
                                    .label(timezone.to_string())
 | 
			
		||||
                                    .custom_id(format!("timezone:{}", timezone.to_string()))
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        c.add_action_row(action_row);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    c
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
            .await;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn timezone_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let mut user_data = UserData::from_user(&interaction.member.user, &ctx, &pool)
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    let footer_text = lm.get(&user_data.language, "timezone/footer").replacen(
 | 
			
		||||
        "{timezone}",
 | 
			
		||||
        &user_data.timezone,
 | 
			
		||||
        1,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if let Some(data) = &interaction.data {
 | 
			
		||||
        if let Some(timezone) = data
 | 
			
		||||
            .options
 | 
			
		||||
            .first()
 | 
			
		||||
            .map(|inner| {
 | 
			
		||||
                inner
 | 
			
		||||
                    .value
 | 
			
		||||
                    .clone()
 | 
			
		||||
                    .map(|v| v.as_str().map(|s| s.to_string()))
 | 
			
		||||
                    .flatten()
 | 
			
		||||
            })
 | 
			
		||||
            .flatten()
 | 
			
		||||
            .map(|tz| tz.parse::<Tz>().ok())
 | 
			
		||||
            .flatten()
 | 
			
		||||
        {
 | 
			
		||||
            user_data.timezone = timezone.to_string();
 | 
			
		||||
            user_data.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
            let now = Utc::now().with_timezone(&user_data.timezone());
 | 
			
		||||
 | 
			
		||||
            let content = lm
 | 
			
		||||
                .get(&user_data.language, "timezone/set_p")
 | 
			
		||||
                .replacen("{timezone}", &user_data.timezone, 1)
 | 
			
		||||
                .replacen(
 | 
			
		||||
                    "{time}",
 | 
			
		||||
                    &now.format(user_data.meridian().fmt_str_short()).to_string(),
 | 
			
		||||
                    1,
 | 
			
		||||
                );
 | 
			
		||||
 | 
			
		||||
            interaction
 | 
			
		||||
                .create_interaction_response(&ctx, |response| {
 | 
			
		||||
                    response
 | 
			
		||||
                        .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                        .interaction_response_data(|data| {
 | 
			
		||||
                            data.embed(|e| {
 | 
			
		||||
                                e.title(lm.get(&user_data.language, "timezone/set_p_title"))
 | 
			
		||||
                                    .description(content)
 | 
			
		||||
                                    .color(*THEME_COLOR)
 | 
			
		||||
                                    .footer(|f| {
 | 
			
		||||
                                        f.text(
 | 
			
		||||
                                            lm.get(&user_data.language, "timezone/footer")
 | 
			
		||||
                                                .replacen("{timezone}", &user_data.timezone, 1),
 | 
			
		||||
                                        )
 | 
			
		||||
                                    })
 | 
			
		||||
                            })
 | 
			
		||||
                        })
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        } else {
 | 
			
		||||
            let content = lm
 | 
			
		||||
                .get(&user_data.language, "timezone/no_argument")
 | 
			
		||||
                .replace("{prefix}", "/");
 | 
			
		||||
 | 
			
		||||
            let popular_timezones = ctx
 | 
			
		||||
                .data
 | 
			
		||||
                .read()
 | 
			
		||||
                .await
 | 
			
		||||
                .get::<PopularTimezones>()
 | 
			
		||||
                .cloned()
 | 
			
		||||
                .unwrap();
 | 
			
		||||
 | 
			
		||||
            let popular_timezones_iter = popular_timezones.iter().map(|t| {
 | 
			
		||||
                (
 | 
			
		||||
                    t.to_string(),
 | 
			
		||||
                    format!(
 | 
			
		||||
                        "🕗 `{}`",
 | 
			
		||||
                        Utc::now()
 | 
			
		||||
                            .with_timezone(t)
 | 
			
		||||
                            .format(user_data.meridian().fmt_str_short())
 | 
			
		||||
                            .to_string()
 | 
			
		||||
                    ),
 | 
			
		||||
                    true,
 | 
			
		||||
                )
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            interaction
 | 
			
		||||
                .create_interaction_response(&ctx, |response| {
 | 
			
		||||
                    response
 | 
			
		||||
                        .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                        .interaction_response_data(|data| {
 | 
			
		||||
                            data.embed(|e| {
 | 
			
		||||
                                e.title(lm.get(&user_data.language, "timezone/no_argument_title"))
 | 
			
		||||
                            .description(content)
 | 
			
		||||
                            .color(*THEME_COLOR)
 | 
			
		||||
                            .fields(popular_timezones_iter)
 | 
			
		||||
                            .footer(|f| f.text(footer_text))
 | 
			
		||||
                            .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
			
		||||
                            })
 | 
			
		||||
                        })
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[command("meridian")]
 | 
			
		||||
async fn change_meridian(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
@@ -411,6 +338,28 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                                .description(lm.get(&user_data.language, "lang/invalid"))
 | 
			
		||||
                                .fields(language_codes)
 | 
			
		||||
                        })
 | 
			
		||||
                        .components(|c| {
 | 
			
		||||
                            for row in lm
 | 
			
		||||
                                .all_languages()
 | 
			
		||||
                                .map(|(k, v)| (k.to_string(), v.to_string()))
 | 
			
		||||
                                .collect::<Vec<(String, String)>>()
 | 
			
		||||
                                .as_slice()
 | 
			
		||||
                                .chunks(5)
 | 
			
		||||
                            {
 | 
			
		||||
                                let mut action_row = CreateActionRow::default();
 | 
			
		||||
                                for (code, name) in row {
 | 
			
		||||
                                    action_row.create_button(|b| {
 | 
			
		||||
                                        b.style(ButtonStyle::Primary)
 | 
			
		||||
                                            .label(name.to_title_case())
 | 
			
		||||
                                            .custom_id(format!("lang:{}", code.to_uppercase()))
 | 
			
		||||
                                    });
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                c.add_action_row(action_row);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            c
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
                    .await;
 | 
			
		||||
            }
 | 
			
		||||
@@ -424,21 +373,7 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
            )
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        let flags = lm
 | 
			
		||||
            .all_languages()
 | 
			
		||||
            .map(|(k, _)| ReactionType::Unicode(lm.get(k, "flag").to_string()));
 | 
			
		||||
 | 
			
		||||
        let can_react = if let Some(Channel::Guild(channel)) = msg.channel(&ctx).await {
 | 
			
		||||
            channel
 | 
			
		||||
                .permissions_for_user(&ctx, ctx.cache.current_user().await)
 | 
			
		||||
                .await
 | 
			
		||||
                .map(|p| p.add_reactions())
 | 
			
		||||
                .unwrap_or(false)
 | 
			
		||||
        } else {
 | 
			
		||||
            true
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        let reactor = msg
 | 
			
		||||
        let _ = msg
 | 
			
		||||
            .channel_id
 | 
			
		||||
            .send_message(&ctx, |m| {
 | 
			
		||||
                m.embed(|e| {
 | 
			
		||||
@@ -446,87 +381,31 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                        .color(*THEME_COLOR)
 | 
			
		||||
                        .description(lm.get(&user_data.language, "lang/select"))
 | 
			
		||||
                        .fields(language_codes)
 | 
			
		||||
                });
 | 
			
		||||
                })
 | 
			
		||||
                .components(|c| {
 | 
			
		||||
                    for row in lm
 | 
			
		||||
                        .all_languages()
 | 
			
		||||
                        .map(|(k, v)| (k.to_string(), v.to_string()))
 | 
			
		||||
                        .collect::<Vec<(String, String)>>()
 | 
			
		||||
                        .as_slice()
 | 
			
		||||
                        .chunks(5)
 | 
			
		||||
                    {
 | 
			
		||||
                        let mut action_row = CreateActionRow::default();
 | 
			
		||||
                        for (code, name) in row {
 | 
			
		||||
                            action_row.create_button(|b| {
 | 
			
		||||
                                b.style(ButtonStyle::Primary)
 | 
			
		||||
                                    .label(name.to_title_case())
 | 
			
		||||
                                    .custom_id(format!("lang:{}", code.to_uppercase()))
 | 
			
		||||
                            });
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                if can_react {
 | 
			
		||||
                    m.reactions(flags);
 | 
			
		||||
                }
 | 
			
		||||
                        c.add_action_row(action_row);
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                m
 | 
			
		||||
                    c
 | 
			
		||||
                })
 | 
			
		||||
            })
 | 
			
		||||
            .await;
 | 
			
		||||
 | 
			
		||||
        if let Ok(sent_msg) = reactor {
 | 
			
		||||
            let reaction_reply = sent_msg
 | 
			
		||||
                .await_reaction(&ctx)
 | 
			
		||||
                .timeout(Duration::from_secs(45))
 | 
			
		||||
                .await;
 | 
			
		||||
 | 
			
		||||
            if let Some(reaction_action) = reaction_reply {
 | 
			
		||||
                if reaction_action.is_added() {
 | 
			
		||||
                    if let ReactionType::Unicode(emoji) = &reaction_action.as_inner_ref().emoji {
 | 
			
		||||
                        if let Some(lang) = lm.get_language_by_flag(emoji) {
 | 
			
		||||
                            user_data.language = lang.to_string();
 | 
			
		||||
 | 
			
		||||
                            user_data.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
                            let _ = msg
 | 
			
		||||
                                .channel_id
 | 
			
		||||
                                .send_message(&ctx, |m| {
 | 
			
		||||
                                    m.embed(|e| {
 | 
			
		||||
                                        e.title(lm.get(&user_data.language, "lang/set_p_title"))
 | 
			
		||||
                                            .color(*THEME_COLOR)
 | 
			
		||||
                                            .description(lm.get(&user_data.language, "lang/set_p"))
 | 
			
		||||
                                    })
 | 
			
		||||
                                })
 | 
			
		||||
                                .await;
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if let Some(Channel::Guild(channel)) = msg.channel(&ctx).await {
 | 
			
		||||
                let has_perms = channel
 | 
			
		||||
                    .permissions_for_user(&ctx, ctx.cache.current_user().await)
 | 
			
		||||
                    .await
 | 
			
		||||
                    .map(|p| p.manage_messages())
 | 
			
		||||
                    .unwrap_or(false);
 | 
			
		||||
 | 
			
		||||
                if has_perms {
 | 
			
		||||
                    let _ = sent_msg.delete_reactions(&ctx).await;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn language_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let mut user_data = UserData::from_user(&interaction.member.user, &ctx, &pool)
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
    if let Some(data) = &interaction.data {
 | 
			
		||||
        let option = &data.options[0];
 | 
			
		||||
 | 
			
		||||
        user_data.language = option.value.clone().unwrap().as_str().unwrap().to_string();
 | 
			
		||||
        user_data.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
        interaction
 | 
			
		||||
            .create_interaction_response(ctx, |response| {
 | 
			
		||||
                response
 | 
			
		||||
                    .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                    .interaction_response_data(|data| {
 | 
			
		||||
                        data.embed(|e| {
 | 
			
		||||
                            e.title(lm.get(&user_data.language, "lang/set_p_title"))
 | 
			
		||||
                                .color(*THEME_COLOR)
 | 
			
		||||
                                .description(lm.get(&user_data.language, "lang/set_p"))
 | 
			
		||||
                        })
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -537,7 +416,6 @@ async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let guild_data = ctx.guild_data(msg.guild_id.unwrap()).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
			
		||||
 | 
			
		||||
    if args.len() > 5 {
 | 
			
		||||
@@ -552,6 +430,7 @@ async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
            .await;
 | 
			
		||||
    } else {
 | 
			
		||||
        guild_data.write().await.prefix = args;
 | 
			
		||||
 | 
			
		||||
        guild_data.read().await.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
        let content = lm.get(&language, "prefix/success").replacen(
 | 
			
		||||
@@ -564,49 +443,6 @@ async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn prefix_interaction(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let guild_data = ctx.guild_data(interaction.guild_id).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&interaction.member, &pool).await;
 | 
			
		||||
 | 
			
		||||
    if let Some(data) = &interaction.data {
 | 
			
		||||
        let option = &data.options[0];
 | 
			
		||||
 | 
			
		||||
        let new_prefix = option.value.clone().unwrap().as_str().unwrap().to_string();
 | 
			
		||||
 | 
			
		||||
        if new_prefix.len() > 5 {
 | 
			
		||||
            interaction
 | 
			
		||||
                .create_interaction_response(ctx, |response| {
 | 
			
		||||
                    response
 | 
			
		||||
                        .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                        .interaction_response_data(|data| {
 | 
			
		||||
                            data.content(lm.get(&language, "prefix/too_long"))
 | 
			
		||||
                        })
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        } else {
 | 
			
		||||
            guild_data.write().await.prefix = new_prefix.clone();
 | 
			
		||||
            guild_data.read().await.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
            let content = lm
 | 
			
		||||
                .get(&language, "prefix/success")
 | 
			
		||||
                .replacen("{prefix}", &new_prefix, 1);
 | 
			
		||||
 | 
			
		||||
            interaction
 | 
			
		||||
                .create_interaction_response(ctx, |response| {
 | 
			
		||||
                    response
 | 
			
		||||
                        .kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                        .interaction_response_data(|data| data.content(content))
 | 
			
		||||
                })
 | 
			
		||||
                .await
 | 
			
		||||
                .unwrap();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[command]
 | 
			
		||||
#[supports_dm(false)]
 | 
			
		||||
#[permission_level(Restricted)]
 | 
			
		||||
@@ -845,6 +681,7 @@ SELECT command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHER
 | 
			
		||||
 | 
			
		||||
                            let mut new_msg = msg.clone();
 | 
			
		||||
                            new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id().await, row.command);
 | 
			
		||||
                            new_msg.id = MessageId(0);
 | 
			
		||||
 | 
			
		||||
                            framework.dispatch(ctx.clone(), new_msg).await;
 | 
			
		||||
                        },
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,5 @@
 | 
			
		||||
use regex_command_attr::command;
 | 
			
		||||
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
 | 
			
		||||
use serenity::{
 | 
			
		||||
    cache::Cache,
 | 
			
		||||
    client::Context,
 | 
			
		||||
@@ -47,20 +45,9 @@ use std::{
 | 
			
		||||
    time::{SystemTime, UNIX_EPOCH},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use crate::models::{CtxGuildData, MeridianType};
 | 
			
		||||
use crate::models::CtxGuildData;
 | 
			
		||||
use regex::Captures;
 | 
			
		||||
use serenity::model::channel::Channel;
 | 
			
		||||
use serenity::model::interactions::Interaction;
 | 
			
		||||
 | 
			
		||||
fn shorthand_displacement(seconds: u64) -> String {
 | 
			
		||||
    let (days, seconds) = seconds.div_rem(&DAY);
 | 
			
		||||
    let (hours, seconds) = seconds.div_rem(&HOUR);
 | 
			
		||||
    let (minutes, seconds) = seconds.div_rem(&MINUTE);
 | 
			
		||||
 | 
			
		||||
    let time_repr = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
 | 
			
		||||
 | 
			
		||||
    format!("{} days, {}", days, time_repr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
fn longhand_displacement(seconds: u64) -> String {
 | 
			
		||||
    let (days, seconds) = seconds.div_rem(&DAY);
 | 
			
		||||
@@ -362,27 +349,11 @@ impl LookReminder {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fn display(
 | 
			
		||||
        &self,
 | 
			
		||||
        flags: &LookFlags,
 | 
			
		||||
        meridian: &MeridianType,
 | 
			
		||||
        timezone: &Tz,
 | 
			
		||||
        inter: &str,
 | 
			
		||||
    ) -> String {
 | 
			
		||||
    fn display(&self, flags: &LookFlags, inter: &str) -> String {
 | 
			
		||||
        let time_display = match flags.time_display {
 | 
			
		||||
            TimeDisplayType::Absolute => timezone
 | 
			
		||||
                .from_utc_datetime(&self.time)
 | 
			
		||||
                .format(meridian.fmt_str())
 | 
			
		||||
                .to_string(),
 | 
			
		||||
            TimeDisplayType::Absolute => format!("<t:{}>", self.time.timestamp()),
 | 
			
		||||
 | 
			
		||||
            TimeDisplayType::Relative => {
 | 
			
		||||
                let now = SystemTime::now()
 | 
			
		||||
                    .duration_since(UNIX_EPOCH)
 | 
			
		||||
                    .unwrap()
 | 
			
		||||
                    .as_secs();
 | 
			
		||||
 | 
			
		||||
                longhand_displacement((self.time.timestamp() as u64).checked_sub(now).unwrap_or(1))
 | 
			
		||||
            }
 | 
			
		||||
            TimeDisplayType::Relative => format!("<t:{}:R>", self.time.timestamp()),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        if let Some(interval) = self.interval {
 | 
			
		||||
@@ -410,8 +381,6 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
			
		||||
    let timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
			
		||||
    let meridian = UserData::meridian_of(&msg.author, &pool).await;
 | 
			
		||||
 | 
			
		||||
    let flags = LookFlags::from_string(&args);
 | 
			
		||||
 | 
			
		||||
@@ -435,7 +404,12 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
        LookReminder,
 | 
			
		||||
        "
 | 
			
		||||
SELECT
 | 
			
		||||
    reminders.id, reminders.utc_time AS time, reminders.interval, channels.channel, reminders.content, reminders.embed_description AS description
 | 
			
		||||
    reminders.id,
 | 
			
		||||
    reminders.utc_time AS time,
 | 
			
		||||
    reminders.interval,
 | 
			
		||||
    channels.channel,
 | 
			
		||||
    reminders.content,
 | 
			
		||||
    reminders.embed_description AS description
 | 
			
		||||
FROM
 | 
			
		||||
    reminders
 | 
			
		||||
INNER JOIN
 | 
			
		||||
@@ -468,7 +442,7 @@ LIMIT
 | 
			
		||||
 | 
			
		||||
        let display = reminders
 | 
			
		||||
            .iter()
 | 
			
		||||
            .map(|reminder| reminder.display(&flags, &meridian, &timezone, &inter));
 | 
			
		||||
            .map(|reminder| reminder.display(&flags, &inter));
 | 
			
		||||
 | 
			
		||||
        let _ = msg.channel_id.say_lines(&ctx, display).await;
 | 
			
		||||
    }
 | 
			
		||||
@@ -502,10 +476,15 @@ async fn delete(ctx: &Context, msg: &Message, _args: String) {
 | 
			
		||||
                LookReminder,
 | 
			
		||||
                "
 | 
			
		||||
SELECT
 | 
			
		||||
    reminders.id, reminders.utc_time AS time, reminders.interval, channels.channel, reminders.content, reminders.embed_description AS description
 | 
			
		||||
    reminders.id,
 | 
			
		||||
    reminders.utc_time AS time,
 | 
			
		||||
    reminders.interval,
 | 
			
		||||
    channels.channel,
 | 
			
		||||
    reminders.content,
 | 
			
		||||
    reminders.embed_description AS description
 | 
			
		||||
FROM
 | 
			
		||||
    reminders
 | 
			
		||||
INNER JOIN
 | 
			
		||||
LEFT OUTER JOIN
 | 
			
		||||
    channels
 | 
			
		||||
ON
 | 
			
		||||
    channels.id = reminders.channel_id
 | 
			
		||||
@@ -521,10 +500,15 @@ WHERE
 | 
			
		||||
                LookReminder,
 | 
			
		||||
                "
 | 
			
		||||
SELECT
 | 
			
		||||
    reminders.id, reminders.utc_time AS time, reminders.interval, channels.channel, reminders.content, reminders.embed_description AS description
 | 
			
		||||
    reminders.id,
 | 
			
		||||
    reminders.utc_time AS time,
 | 
			
		||||
    reminders.interval,
 | 
			
		||||
    channels.channel,
 | 
			
		||||
    reminders.content,
 | 
			
		||||
    reminders.embed_description AS description
 | 
			
		||||
FROM
 | 
			
		||||
    reminders
 | 
			
		||||
INNER JOIN
 | 
			
		||||
LEFT OUTER JOIN
 | 
			
		||||
    channels
 | 
			
		||||
ON
 | 
			
		||||
    channels.id = reminders.channel_id
 | 
			
		||||
@@ -541,7 +525,12 @@ WHERE
 | 
			
		||||
            LookReminder,
 | 
			
		||||
            "
 | 
			
		||||
SELECT
 | 
			
		||||
    reminders.id, reminders.utc_time AS time, reminders.interval, channels.channel, reminders.content, reminders.embed_description AS description
 | 
			
		||||
    reminders.id,
 | 
			
		||||
    reminders.utc_time AS time,
 | 
			
		||||
    reminders.interval,
 | 
			
		||||
    channels.channel,
 | 
			
		||||
    reminders.content,
 | 
			
		||||
    reminders.embed_description AS description
 | 
			
		||||
FROM
 | 
			
		||||
    reminders
 | 
			
		||||
INNER JOIN
 | 
			
		||||
@@ -562,14 +551,13 @@ WHERE
 | 
			
		||||
 | 
			
		||||
    let enumerated_reminders = reminders.iter().enumerate().map(|(count, reminder)| {
 | 
			
		||||
        reminder_ids.push(reminder.id);
 | 
			
		||||
        let time = user_data.timezone().timestamp(reminder.time.timestamp(), 0);
 | 
			
		||||
 | 
			
		||||
        format!(
 | 
			
		||||
            "**{}**: '{}' *<#{}>* at {}",
 | 
			
		||||
            "**{}**: '{}' *<#{}>* at <t:{}>",
 | 
			
		||||
            count + 1,
 | 
			
		||||
            reminder.display_content(),
 | 
			
		||||
            reminder.channel,
 | 
			
		||||
            time.format(user_data.meridian().fmt_str())
 | 
			
		||||
            reminder.time.timestamp()
 | 
			
		||||
        )
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@@ -1021,36 +1009,36 @@ async fn countdown(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
INSERT INTO reminders (
 | 
			
		||||
    `uid`,
 | 
			
		||||
    `name`,
 | 
			
		||||
    `channel_id`,
 | 
			
		||||
    `utc_time`,
 | 
			
		||||
    `interval`,
 | 
			
		||||
    `expires`,
 | 
			
		||||
    `embed_title`,
 | 
			
		||||
    `embed_description`,
 | 
			
		||||
    `embed_color`,
 | 
			
		||||
    `set_by`
 | 
			
		||||
    `channel_id`,
 | 
			
		||||
    `utc_time`,
 | 
			
		||||
    `interval`,
 | 
			
		||||
    `set_by`,
 | 
			
		||||
    `expires`
 | 
			
		||||
) VALUES (
 | 
			
		||||
    ?,
 | 
			
		||||
    'Countdown',
 | 
			
		||||
    (SELECT id FROM channels WHERE channel = ?),
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    FROM_UNIXTIME(?),
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    (SELECT id FROM users WHERE user = ?)
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    (SELECT id FROM users WHERE user = ?),
 | 
			
		||||
    FROM_UNIXTIME(?)
 | 
			
		||||
)
 | 
			
		||||
                    ",
 | 
			
		||||
                        generate_uid(),
 | 
			
		||||
                        msg.channel_id.as_u64(),
 | 
			
		||||
                        first_time,
 | 
			
		||||
                        interval,
 | 
			
		||||
                        target_ts,
 | 
			
		||||
                        event_name,
 | 
			
		||||
                        description,
 | 
			
		||||
                        *THEME_COLOR,
 | 
			
		||||
                        msg.channel_id.as_u64(),
 | 
			
		||||
                        first_time,
 | 
			
		||||
                        interval,
 | 
			
		||||
                        msg.author.id.as_u64(),
 | 
			
		||||
                        target_ts
 | 
			
		||||
                    )
 | 
			
		||||
                    .execute(&pool)
 | 
			
		||||
                    .await
 | 
			
		||||
@@ -1190,7 +1178,6 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
			
		||||
                                    msg.guild_id,
 | 
			
		||||
                                    &scope,
 | 
			
		||||
                                    &time_parser,
 | 
			
		||||
                                    timezone.to_string(),
 | 
			
		||||
                                    expires_parser.as_ref().clone(),
 | 
			
		||||
                                    interval,
 | 
			
		||||
                                    &mut content,
 | 
			
		||||
@@ -1212,9 +1199,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
			
		||||
                                    .replace("{location}", &ok_locations[0].mention())
 | 
			
		||||
                                    .replace(
 | 
			
		||||
                                        "{offset}",
 | 
			
		||||
                                        &shorthand_displacement(
 | 
			
		||||
                                            time_parser.displacement().unwrap() as u64,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
			
		||||
                                    ),
 | 
			
		||||
                                n => lm
 | 
			
		||||
                                    .get(&language, "remind/success_bulk")
 | 
			
		||||
@@ -1229,9 +1214,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .replace(
 | 
			
		||||
                                        "{offset}",
 | 
			
		||||
                                        &shorthand_displacement(
 | 
			
		||||
                                            time_parser.displacement().unwrap() as u64,
 | 
			
		||||
                                        ),
 | 
			
		||||
                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
			
		||||
                                    ),
 | 
			
		||||
                            };
 | 
			
		||||
 | 
			
		||||
@@ -1338,11 +1321,6 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
			
		||||
async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let now = SystemTime::now();
 | 
			
		||||
    let since_epoch = now
 | 
			
		||||
        .duration_since(UNIX_EPOCH)
 | 
			
		||||
        .expect("Time calculated as going backwards. Very bad");
 | 
			
		||||
 | 
			
		||||
    let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap();
 | 
			
		||||
 | 
			
		||||
    match REGEX_NATURAL_COMMAND_1.captures(&args) {
 | 
			
		||||
@@ -1406,8 +1384,6 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
 | 
			
		||||
                match content_res {
 | 
			
		||||
                    Ok(mut content) => {
 | 
			
		||||
                        let offset = timestamp as u64 - since_epoch.as_secs();
 | 
			
		||||
 | 
			
		||||
                        let mut ok_locations = vec![];
 | 
			
		||||
                        let mut err_locations = vec![];
 | 
			
		||||
                        let mut err_types = HashSet::new();
 | 
			
		||||
@@ -1420,7 +1396,6 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                                msg.guild_id,
 | 
			
		||||
                                &scope,
 | 
			
		||||
                                timestamp,
 | 
			
		||||
                                user_data.timezone.clone(),
 | 
			
		||||
                                expires,
 | 
			
		||||
                                interval.clone(),
 | 
			
		||||
                                &mut content,
 | 
			
		||||
@@ -1440,7 +1415,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                            1 => lm
 | 
			
		||||
                                .get(&user_data.language, "remind/success")
 | 
			
		||||
                                .replace("{location}", &ok_locations[0].mention())
 | 
			
		||||
                                .replace("{offset}", &shorthand_displacement(offset)),
 | 
			
		||||
                                .replace("{offset}", &format!("<t:{}:R>", timestamp)),
 | 
			
		||||
                            n => lm
 | 
			
		||||
                                .get(&user_data.language, "remind/success_bulk")
 | 
			
		||||
                                .replace("{number}", &n.to_string())
 | 
			
		||||
@@ -1452,7 +1427,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
                                        .collect::<Vec<String>>()
 | 
			
		||||
                                        .join(", "),
 | 
			
		||||
                                )
 | 
			
		||||
                                .replace("{offset}", &shorthand_displacement(offset)),
 | 
			
		||||
                                .replace("{offset}", &format!("<t:{}:R>", timestamp)),
 | 
			
		||||
                        };
 | 
			
		||||
 | 
			
		||||
                        let error_part = format!(
 | 
			
		||||
@@ -1553,19 +1528,6 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn set_reminder(ctx: &Context, interaction: Interaction) {
 | 
			
		||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
			
		||||
 | 
			
		||||
    let now = SystemTime::now();
 | 
			
		||||
    let since_epoch = now
 | 
			
		||||
        .duration_since(UNIX_EPOCH)
 | 
			
		||||
        .expect("Time calculated as going backwards. Very bad");
 | 
			
		||||
 | 
			
		||||
    let user_data = UserData::from_user(&interaction.member.user, &ctx, &pool)
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
			
		||||
    ctx: impl CacheHttp + AsRef<Cache>,
 | 
			
		||||
    pool: &MySqlPool,
 | 
			
		||||
@@ -1573,7 +1535,6 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
			
		||||
    guild_id: Option<GuildId>,
 | 
			
		||||
    scope_id: &ReminderScope,
 | 
			
		||||
    time_parser: T,
 | 
			
		||||
    timezone: String,
 | 
			
		||||
    expires_parser: Option<T>,
 | 
			
		||||
    interval: Option<i64>,
 | 
			
		||||
    content: &mut Content,
 | 
			
		||||
@@ -1654,8 +1615,29 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
			
		||||
                            } else {
 | 
			
		||||
                                sqlx::query!(
 | 
			
		||||
                                    "
 | 
			
		||||
INSERT INTO reminders (uid, content, tts, attachment, attachment_name, channel_id, `utc_time`, timezone, expires, `interval`, set_by) VALUES
 | 
			
		||||
    (?, ?, ?, ?, ?, ?, FROM_UNIXTIME(?), ?, FROM_UNIXTIME(?), ?, (SELECT id FROM users WHERE user = ? LIMIT 1))
 | 
			
		||||
INSERT INTO reminders (
 | 
			
		||||
    uid,
 | 
			
		||||
    content,
 | 
			
		||||
    tts,
 | 
			
		||||
    attachment,
 | 
			
		||||
    attachment_name,
 | 
			
		||||
    channel_id,
 | 
			
		||||
    `utc_time`,
 | 
			
		||||
    expires,
 | 
			
		||||
    `interval`,
 | 
			
		||||
    set_by
 | 
			
		||||
) VALUES (
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    ?,
 | 
			
		||||
    DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
 | 
			
		||||
    DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
 | 
			
		||||
    ?,
 | 
			
		||||
    (SELECT id FROM users WHERE user = ? LIMIT 1)
 | 
			
		||||
)
 | 
			
		||||
                            ",
 | 
			
		||||
                                    generate_uid(),
 | 
			
		||||
                                    content.content,
 | 
			
		||||
@@ -1663,8 +1645,7 @@ INSERT INTO reminders (uid, content, tts, attachment, attachment_name, channel_i
 | 
			
		||||
                                    content.attachment,
 | 
			
		||||
                                    content.attachment_name,
 | 
			
		||||
                                    db_channel_id,
 | 
			
		||||
                                    time,
 | 
			
		||||
                                    timezone,
 | 
			
		||||
                                    time as u32,
 | 
			
		||||
                                    expires,
 | 
			
		||||
                                    interval,
 | 
			
		||||
                                    user_id
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										256
									
								
								src/framework.rs
									
									
									
									
									
								
							
							
						
						
									
										256
									
								
								src/framework.rs
									
									
									
									
									
								
							@@ -21,7 +21,8 @@ use std::{collections::HashMap, fmt};
 | 
			
		||||
 | 
			
		||||
use crate::language_manager::LanguageManager;
 | 
			
		||||
use crate::models::{CtxGuildData, GuildData, UserData};
 | 
			
		||||
use crate::{models::ChannelData, SQLPool};
 | 
			
		||||
use crate::{models::ChannelData, LimitExecutors, SQLPool};
 | 
			
		||||
use serenity::model::id::MessageId;
 | 
			
		||||
 | 
			
		||||
type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, String) -> BoxFuture<'fut, ()>;
 | 
			
		||||
 | 
			
		||||
@@ -297,9 +298,9 @@ impl RegexFramework {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum PermissionCheck {
 | 
			
		||||
    None,                          // No permissions
 | 
			
		||||
    Basic(bool, bool, bool, bool), // Send + Embed permissions (sufficient to reply)
 | 
			
		||||
    All,                           // Above + Manage Webhooks (sufficient to operate)
 | 
			
		||||
    None,              // No permissions
 | 
			
		||||
    Basic(bool, bool), // Send + Embed permissions (sufficient to reply)
 | 
			
		||||
    All,               // Above + Manage Webhooks (sufficient to operate)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
@@ -324,8 +325,6 @@ impl Framework for RegexFramework {
 | 
			
		||||
                    PermissionCheck::Basic(
 | 
			
		||||
                        guild_perms.manage_webhooks(),
 | 
			
		||||
                        channel_perms.embed_links(),
 | 
			
		||||
                        channel_perms.add_reactions(),
 | 
			
		||||
                        channel_perms.manage_messages(),
 | 
			
		||||
                    )
 | 
			
		||||
                } else {
 | 
			
		||||
                    PermissionCheck::None
 | 
			
		||||
@@ -345,144 +344,153 @@ impl Framework for RegexFramework {
 | 
			
		||||
 | 
			
		||||
        // gate to prevent analysing messages unnecessarily
 | 
			
		||||
        if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
 | 
			
		||||
        }
 | 
			
		||||
        // Guild Command
 | 
			
		||||
        else if let (Some(guild), Some(Channel::Guild(channel))) =
 | 
			
		||||
            (msg.guild(&ctx).await, msg.channel(&ctx).await)
 | 
			
		||||
        {
 | 
			
		||||
            let data = ctx.data.read().await;
 | 
			
		||||
        } else {
 | 
			
		||||
            // Guild Command
 | 
			
		||||
            if let (Some(guild), Some(Channel::Guild(channel))) =
 | 
			
		||||
                (msg.guild(&ctx).await, msg.channel(&ctx).await)
 | 
			
		||||
            {
 | 
			
		||||
                let data = ctx.data.read().await;
 | 
			
		||||
 | 
			
		||||
            let pool = data
 | 
			
		||||
                .get::<SQLPool>()
 | 
			
		||||
                .cloned()
 | 
			
		||||
                .expect("Could not get SQLPool from data");
 | 
			
		||||
                let pool = data
 | 
			
		||||
                    .get::<SQLPool>()
 | 
			
		||||
                    .cloned()
 | 
			
		||||
                    .expect("Could not get SQLPool from data");
 | 
			
		||||
 | 
			
		||||
            if let Some(full_match) = self.command_matcher.captures(&msg.content) {
 | 
			
		||||
                if check_prefix(&ctx, &guild, full_match.name("prefix")).await {
 | 
			
		||||
                    let lm = data.get::<LanguageManager>().unwrap();
 | 
			
		||||
                if let Some(full_match) = self.command_matcher.captures(&msg.content) {
 | 
			
		||||
                    if check_prefix(&ctx, &guild, full_match.name("prefix")).await {
 | 
			
		||||
                        let lm = data.get::<LanguageManager>().unwrap();
 | 
			
		||||
 | 
			
		||||
                    let language = UserData::language_of(&msg.author, &pool);
 | 
			
		||||
                        let language = UserData::language_of(&msg.author, &pool);
 | 
			
		||||
 | 
			
		||||
                    match check_self_permissions(&ctx, &guild, &channel).await {
 | 
			
		||||
                        Ok(perms) => match perms {
 | 
			
		||||
                            PermissionCheck::All => {
 | 
			
		||||
                                let command = self
 | 
			
		||||
                                    .commands
 | 
			
		||||
                                    .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
			
		||||
                        match check_self_permissions(&ctx, &guild, &channel).await {
 | 
			
		||||
                            Ok(perms) => match perms {
 | 
			
		||||
                                PermissionCheck::All => {
 | 
			
		||||
                                    let command = self
 | 
			
		||||
                                        .commands
 | 
			
		||||
                                        .get(
 | 
			
		||||
                                            &full_match
 | 
			
		||||
                                                .name("cmd")
 | 
			
		||||
                                                .unwrap()
 | 
			
		||||
                                                .as_str()
 | 
			
		||||
                                                .to_lowercase(),
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .unwrap();
 | 
			
		||||
 | 
			
		||||
                                    let channel_data = ChannelData::from_channel(
 | 
			
		||||
                                        msg.channel(&ctx).await.unwrap(),
 | 
			
		||||
                                        &pool,
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .await
 | 
			
		||||
                                    .unwrap();
 | 
			
		||||
 | 
			
		||||
                                let channel_data = ChannelData::from_channel(
 | 
			
		||||
                                    msg.channel(&ctx).await.unwrap(),
 | 
			
		||||
                                    &pool,
 | 
			
		||||
                                )
 | 
			
		||||
                                .await
 | 
			
		||||
                                .unwrap();
 | 
			
		||||
                                    if !command.can_blacklist || !channel_data.blacklisted {
 | 
			
		||||
                                        let args = full_match
 | 
			
		||||
                                            .name("args")
 | 
			
		||||
                                            .map(|m| m.as_str())
 | 
			
		||||
                                            .unwrap_or("")
 | 
			
		||||
                                            .to_string();
 | 
			
		||||
 | 
			
		||||
                                if !command.can_blacklist || !channel_data.blacklisted {
 | 
			
		||||
                                    let args = full_match
 | 
			
		||||
                                        .name("args")
 | 
			
		||||
                                        .map(|m| m.as_str())
 | 
			
		||||
                                        .unwrap_or("")
 | 
			
		||||
                                        .to_string();
 | 
			
		||||
                                        let member = guild.member(&ctx, &msg.author).await.unwrap();
 | 
			
		||||
 | 
			
		||||
                                    let member = guild.member(&ctx, &msg.author).await.unwrap();
 | 
			
		||||
                                        if command.check_permissions(&ctx, &guild, &member).await {
 | 
			
		||||
                                            dbg!(command.name);
 | 
			
		||||
 | 
			
		||||
                                    if command.check_permissions(&ctx, &guild, &member).await {
 | 
			
		||||
                                        dbg!(command.name);
 | 
			
		||||
                                            {
 | 
			
		||||
                                                let guild_id = guild.id.as_u64().to_owned();
 | 
			
		||||
 | 
			
		||||
                                        {
 | 
			
		||||
                                            let guild_id = guild.id.as_u64().to_owned();
 | 
			
		||||
 | 
			
		||||
                                            GuildData::from_guild(guild, &pool).await.expect(
 | 
			
		||||
                                                &format!(
 | 
			
		||||
                                                    "Failed to create new guild object for {}",
 | 
			
		||||
                                                    guild_id
 | 
			
		||||
                                                ),
 | 
			
		||||
                                            );
 | 
			
		||||
                                        }
 | 
			
		||||
 | 
			
		||||
                                        (command.func)(&ctx, &msg, args).await;
 | 
			
		||||
                                    } else if command.required_perms == PermissionLevel::Restricted
 | 
			
		||||
                                    {
 | 
			
		||||
                                        let _ = msg
 | 
			
		||||
                                            .channel_id
 | 
			
		||||
                                            .say(
 | 
			
		||||
                                                &ctx,
 | 
			
		||||
                                                lm.get(&language.await, "no_perms_restricted"),
 | 
			
		||||
                                            )
 | 
			
		||||
                                            .await;
 | 
			
		||||
                                    } else if command.required_perms == PermissionLevel::Managed {
 | 
			
		||||
                                        let _ = msg
 | 
			
		||||
                                            .channel_id
 | 
			
		||||
                                            .say(
 | 
			
		||||
                                                &ctx,
 | 
			
		||||
                                                lm.get(&language.await, "no_perms_managed")
 | 
			
		||||
                                                    .replace(
 | 
			
		||||
                                                        "{prefix}",
 | 
			
		||||
                                                        &ctx.prefix(msg.guild_id).await,
 | 
			
		||||
                                                GuildData::from_guild(guild, &pool).await.expect(
 | 
			
		||||
                                                    &format!(
 | 
			
		||||
                                                        "Failed to create new guild object for {}",
 | 
			
		||||
                                                        guild_id
 | 
			
		||||
                                                    ),
 | 
			
		||||
                                            )
 | 
			
		||||
                                            .await;
 | 
			
		||||
                                                );
 | 
			
		||||
                                            }
 | 
			
		||||
 | 
			
		||||
                                            if msg.id == MessageId(0)
 | 
			
		||||
                                                || !ctx.check_executing(msg.author.id).await
 | 
			
		||||
                                            {
 | 
			
		||||
                                                ctx.set_executing(msg.author.id).await;
 | 
			
		||||
                                                (command.func)(&ctx, &msg, args).await;
 | 
			
		||||
                                                ctx.drop_executing(msg.author.id).await;
 | 
			
		||||
                                            }
 | 
			
		||||
                                        } else if command.required_perms
 | 
			
		||||
                                            == PermissionLevel::Restricted
 | 
			
		||||
                                        {
 | 
			
		||||
                                            let _ = msg
 | 
			
		||||
                                                .channel_id
 | 
			
		||||
                                                .say(
 | 
			
		||||
                                                    &ctx,
 | 
			
		||||
                                                    lm.get(&language.await, "no_perms_restricted"),
 | 
			
		||||
                                                )
 | 
			
		||||
                                                .await;
 | 
			
		||||
                                        } else if command.required_perms == PermissionLevel::Managed
 | 
			
		||||
                                        {
 | 
			
		||||
                                            let _ = msg
 | 
			
		||||
                                                .channel_id
 | 
			
		||||
                                                .say(
 | 
			
		||||
                                                    &ctx,
 | 
			
		||||
                                                    lm.get(&language.await, "no_perms_managed")
 | 
			
		||||
                                                        .replace(
 | 
			
		||||
                                                            "{prefix}",
 | 
			
		||||
                                                            &ctx.prefix(msg.guild_id).await,
 | 
			
		||||
                                                        ),
 | 
			
		||||
                                                )
 | 
			
		||||
                                                .await;
 | 
			
		||||
                                        }
 | 
			
		||||
                                    }
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                PermissionCheck::Basic(manage_webhooks, embed_links) => {
 | 
			
		||||
                                    let response = lm
 | 
			
		||||
                                        .get(&language.await, "no_perms_general")
 | 
			
		||||
                                        .replace(
 | 
			
		||||
                                            "{manage_webhooks}",
 | 
			
		||||
                                            if manage_webhooks { "✅" } else { "❌" },
 | 
			
		||||
                                        )
 | 
			
		||||
                                        .replace(
 | 
			
		||||
                                            "{embed_links}",
 | 
			
		||||
                                            if embed_links { "✅" } else { "❌" },
 | 
			
		||||
                                        );
 | 
			
		||||
 | 
			
		||||
                                    let _ = msg.channel_id.say(&ctx, response).await;
 | 
			
		||||
                                }
 | 
			
		||||
 | 
			
		||||
                                PermissionCheck::None => {
 | 
			
		||||
                                    warn!("Missing enough permissions for guild {}", guild.id);
 | 
			
		||||
                                }
 | 
			
		||||
                            },
 | 
			
		||||
 | 
			
		||||
                            Err(e) => {
 | 
			
		||||
                                error!(
 | 
			
		||||
                                    "Error occurred getting permissions in guild {}: {:?}",
 | 
			
		||||
                                    guild.id, e
 | 
			
		||||
                                );
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            PermissionCheck::Basic(
 | 
			
		||||
                                manage_webhooks,
 | 
			
		||||
                                embed_links,
 | 
			
		||||
                                add_reactions,
 | 
			
		||||
                                manage_messages,
 | 
			
		||||
                            ) => {
 | 
			
		||||
                                let response = lm
 | 
			
		||||
                                    .get(&language.await, "no_perms_general")
 | 
			
		||||
                                    .replace(
 | 
			
		||||
                                        "{manage_webhooks}",
 | 
			
		||||
                                        if manage_webhooks { "✅" } else { "❌" },
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .replace("{embed_links}", if embed_links { "✅" } else { "❌" })
 | 
			
		||||
                                    .replace(
 | 
			
		||||
                                        "{add_reactions}",
 | 
			
		||||
                                        if add_reactions { "✅" } else { "❌" },
 | 
			
		||||
                                    )
 | 
			
		||||
                                    .replace(
 | 
			
		||||
                                        "{manage_messages}",
 | 
			
		||||
                                        if manage_messages { "✅" } else { "❌" },
 | 
			
		||||
                                    );
 | 
			
		||||
 | 
			
		||||
                                let _ = msg.channel_id.say(&ctx, response).await;
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            PermissionCheck::None => {
 | 
			
		||||
                                warn!("Missing enough permissions for guild {}", guild.id);
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
 | 
			
		||||
                        Err(e) => {
 | 
			
		||||
                            error!(
 | 
			
		||||
                                "Error occurred getting permissions in guild {}: {:?}",
 | 
			
		||||
                                guild.id, e
 | 
			
		||||
                            );
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        // DM Command
 | 
			
		||||
        else if self.dm_enabled {
 | 
			
		||||
            if let Some(full_match) = self.dm_regex_matcher.captures(&msg.content[..]) {
 | 
			
		||||
                let command = self
 | 
			
		||||
                    .commands
 | 
			
		||||
                    .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
			
		||||
                    .unwrap();
 | 
			
		||||
                let args = full_match
 | 
			
		||||
                    .name("args")
 | 
			
		||||
                    .map(|m| m.as_str())
 | 
			
		||||
                    .unwrap_or("")
 | 
			
		||||
                    .to_string();
 | 
			
		||||
            // DM Command
 | 
			
		||||
            else if self.dm_enabled {
 | 
			
		||||
                if let Some(full_match) = self.dm_regex_matcher.captures(&msg.content[..]) {
 | 
			
		||||
                    let command = self
 | 
			
		||||
                        .commands
 | 
			
		||||
                        .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
			
		||||
                        .unwrap();
 | 
			
		||||
                    let args = full_match
 | 
			
		||||
                        .name("args")
 | 
			
		||||
                        .map(|m| m.as_str())
 | 
			
		||||
                        .unwrap_or("")
 | 
			
		||||
                        .to_string();
 | 
			
		||||
 | 
			
		||||
                dbg!(command.name);
 | 
			
		||||
                    dbg!(command.name);
 | 
			
		||||
 | 
			
		||||
                (command.func)(&ctx, &msg, args).await;
 | 
			
		||||
                    if msg.id == MessageId(0) || !ctx.check_executing(msg.author.id).await {
 | 
			
		||||
                        ctx.set_executing(msg.author.id).await;
 | 
			
		||||
                        (command.func)(&ctx, &msg, args).await;
 | 
			
		||||
                        ctx.drop_executing(msg.author.id).await;
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										350
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										350
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -12,12 +12,14 @@ use serenity::{
 | 
			
		||||
    async_trait,
 | 
			
		||||
    cache::Cache,
 | 
			
		||||
    client::{bridge::gateway::GatewayIntents, Client},
 | 
			
		||||
    futures::TryFutureExt,
 | 
			
		||||
    http::{client::Http, CacheHttp},
 | 
			
		||||
    model::{
 | 
			
		||||
        channel::GuildChannel,
 | 
			
		||||
        channel::Message,
 | 
			
		||||
        guild::{Guild, GuildUnavailable},
 | 
			
		||||
        id::{GuildId, UserId},
 | 
			
		||||
        interactions::{Interaction, InteractionData, InteractionType},
 | 
			
		||||
    },
 | 
			
		||||
    prelude::{Context, EventHandler, TypeMapKey},
 | 
			
		||||
    utils::shard_id,
 | 
			
		||||
@@ -27,7 +29,7 @@ use sqlx::mysql::MySqlPool;
 | 
			
		||||
 | 
			
		||||
use dotenv::dotenv;
 | 
			
		||||
 | 
			
		||||
use std::{collections::HashMap, env, sync::Arc};
 | 
			
		||||
use std::{collections::HashMap, env, sync::Arc, time::Instant};
 | 
			
		||||
 | 
			
		||||
use crate::{
 | 
			
		||||
    commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
 | 
			
		||||
@@ -37,8 +39,6 @@ use crate::{
 | 
			
		||||
    models::GuildData,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
use serenity::futures::TryFutureExt;
 | 
			
		||||
 | 
			
		||||
use inflector::Inflector;
 | 
			
		||||
use log::info;
 | 
			
		||||
 | 
			
		||||
@@ -46,10 +46,12 @@ use dashmap::DashMap;
 | 
			
		||||
 | 
			
		||||
use tokio::sync::RwLock;
 | 
			
		||||
 | 
			
		||||
use crate::models::UserData;
 | 
			
		||||
use chrono::Utc;
 | 
			
		||||
use chrono_tz::Tz;
 | 
			
		||||
use serenity::model::interactions::{Interaction, InteractionType};
 | 
			
		||||
use serenity::model::prelude::ApplicationCommandOptionType;
 | 
			
		||||
use std::collections::HashSet;
 | 
			
		||||
use serenity::model::prelude::{
 | 
			
		||||
    InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct GuildDataCache;
 | 
			
		||||
 | 
			
		||||
@@ -81,6 +83,65 @@ impl TypeMapKey for PopularTimezones {
 | 
			
		||||
    type Value = Arc<Vec<Tz>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct CurrentlyExecuting;
 | 
			
		||||
 | 
			
		||||
impl TypeMapKey for CurrentlyExecuting {
 | 
			
		||||
    type Value = Arc<RwLock<HashMap<UserId, Instant>>>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
trait LimitExecutors {
 | 
			
		||||
    async fn check_executing(&self, user: UserId) -> bool;
 | 
			
		||||
    async fn set_executing(&self, user: UserId);
 | 
			
		||||
    async fn drop_executing(&self, user: UserId);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
impl LimitExecutors for Context {
 | 
			
		||||
    async fn check_executing(&self, user: UserId) -> bool {
 | 
			
		||||
        let currently_executing = self
 | 
			
		||||
            .data
 | 
			
		||||
            .read()
 | 
			
		||||
            .await
 | 
			
		||||
            .get::<CurrentlyExecuting>()
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let lock = currently_executing.read().await;
 | 
			
		||||
 | 
			
		||||
        lock.get(&user)
 | 
			
		||||
            .map_or(false, |now| now.elapsed().as_secs() < 4)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn set_executing(&self, user: UserId) {
 | 
			
		||||
        let currently_executing = self
 | 
			
		||||
            .data
 | 
			
		||||
            .read()
 | 
			
		||||
            .await
 | 
			
		||||
            .get::<CurrentlyExecuting>()
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut lock = currently_executing.write().await;
 | 
			
		||||
 | 
			
		||||
        lock.insert(user, Instant::now());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn drop_executing(&self, user: UserId) {
 | 
			
		||||
        let currently_executing = self
 | 
			
		||||
            .data
 | 
			
		||||
            .read()
 | 
			
		||||
            .await
 | 
			
		||||
            .get::<CurrentlyExecuting>()
 | 
			
		||||
            .cloned()
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        let mut lock = currently_executing.write().await;
 | 
			
		||||
 | 
			
		||||
        lock.remove(&user);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct Handler;
 | 
			
		||||
 | 
			
		||||
#[async_trait]
 | 
			
		||||
@@ -199,25 +260,86 @@ DELETE FROM guilds WHERE guild = ?
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
 | 
			
		||||
        let (pool, lm) = get_ctx_data(&&ctx).await;
 | 
			
		||||
 | 
			
		||||
        match interaction.kind {
 | 
			
		||||
            InteractionType::ApplicationCommand => {
 | 
			
		||||
                if let Some(data) = &interaction.data {
 | 
			
		||||
                    match data.name.as_str() {
 | 
			
		||||
                        "timezone" => {
 | 
			
		||||
                            moderation_cmds::timezone_interaction(&ctx, interaction).await
 | 
			
		||||
            InteractionType::ApplicationCommand => {}
 | 
			
		||||
            InteractionType::MessageComponent => {
 | 
			
		||||
                if let (Some(InteractionData::MessageComponent(data)), Some(member)) =
 | 
			
		||||
                    (interaction.clone().data, interaction.clone().member)
 | 
			
		||||
                {
 | 
			
		||||
                    println!("{}", data.custom_id);
 | 
			
		||||
 | 
			
		||||
                    if data.custom_id.starts_with("timezone:") {
 | 
			
		||||
                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
			
		||||
                            .await
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        let new_timezone = data.custom_id.replace("timezone:", "").parse::<Tz>();
 | 
			
		||||
 | 
			
		||||
                        if let Ok(timezone) = new_timezone {
 | 
			
		||||
                            user_data.timezone = timezone.to_string();
 | 
			
		||||
                            user_data.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
                            let _ = interaction.create_interaction_response(&ctx, |r| {
 | 
			
		||||
                                r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                                    .interaction_response_data(|d| {
 | 
			
		||||
                                        let footer_text = lm.get(&user_data.language, "timezone/footer").replacen(
 | 
			
		||||
                                            "{timezone}",
 | 
			
		||||
                                            &user_data.timezone,
 | 
			
		||||
                                            1,
 | 
			
		||||
                                        );
 | 
			
		||||
 | 
			
		||||
                                        let now = Utc::now().with_timezone(&user_data.timezone());
 | 
			
		||||
 | 
			
		||||
                                        let content = lm
 | 
			
		||||
                                            .get(&user_data.language, "timezone/set_p")
 | 
			
		||||
                                            .replacen("{timezone}", &user_data.timezone, 1)
 | 
			
		||||
                                            .replacen(
 | 
			
		||||
                                                "{time}",
 | 
			
		||||
                                                &now.format(user_data.meridian().fmt_str_short()).to_string(),
 | 
			
		||||
                                                1,
 | 
			
		||||
                                            );
 | 
			
		||||
 | 
			
		||||
                                        d.create_embed(|e| e.title(lm.get(&user_data.language, "timezone/set_p_title"))
 | 
			
		||||
                                            .color(*THEME_COLOR)
 | 
			
		||||
                                            .description(content)
 | 
			
		||||
                                            .footer(|f| f.text(footer_text)))
 | 
			
		||||
                                            .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
 | 
			
		||||
 | 
			
		||||
                                        d
 | 
			
		||||
                                    })
 | 
			
		||||
                            }).await;
 | 
			
		||||
                        }
 | 
			
		||||
                    } else if data.custom_id.starts_with("lang:") {
 | 
			
		||||
                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
			
		||||
                            .await
 | 
			
		||||
                            .unwrap();
 | 
			
		||||
                        let lang_code = data.custom_id.replace("lang:", "");
 | 
			
		||||
 | 
			
		||||
                        if let Some(lang) = lm.get_language(&lang_code) {
 | 
			
		||||
                            user_data.language = lang.to_string();
 | 
			
		||||
                            user_data.commit_changes(&pool).await;
 | 
			
		||||
 | 
			
		||||
                            let _ = interaction
 | 
			
		||||
                                .create_interaction_response(&ctx, |r| {
 | 
			
		||||
                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
			
		||||
                                        .interaction_response_data(|d| {
 | 
			
		||||
                                            d.create_embed(|e| {
 | 
			
		||||
                                                e.title(
 | 
			
		||||
                                                    lm.get(&user_data.language, "lang/set_p_title"),
 | 
			
		||||
                                                )
 | 
			
		||||
                                                .color(*THEME_COLOR)
 | 
			
		||||
                                                .description(
 | 
			
		||||
                                                    lm.get(&user_data.language, "lang/set_p"),
 | 
			
		||||
                                                )
 | 
			
		||||
                                            })
 | 
			
		||||
                                        })
 | 
			
		||||
                                })
 | 
			
		||||
                                .await;
 | 
			
		||||
                        }
 | 
			
		||||
                        "lang" => moderation_cmds::language_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "prefix" => moderation_cmds::prefix_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "help" => info_cmds::help_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "info" => info_cmds::info_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "donate" => info_cmds::donate_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "clock" => info_cmds::clock_interaction(&ctx, interaction).await,
 | 
			
		||||
                        "remind" => reminder_cmds::set_reminder(&ctx, interaction).await,
 | 
			
		||||
                        _ => {}
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            _ => {}
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -237,6 +359,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
        .get_current_user()
 | 
			
		||||
        .map_ok(|user| user.id.as_u64().to_owned())
 | 
			
		||||
        .await?;
 | 
			
		||||
    let application_id = http.get_current_application_info().await?.id;
 | 
			
		||||
 | 
			
		||||
    let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1");
 | 
			
		||||
 | 
			
		||||
@@ -302,20 +425,12 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
                | GatewayIntents::GUILDS
 | 
			
		||||
                | GatewayIntents::GUILD_MESSAGE_REACTIONS
 | 
			
		||||
        })
 | 
			
		||||
        .application_id(application_id.0)
 | 
			
		||||
        .event_handler(Handler)
 | 
			
		||||
        .framework_arc(framework_arc.clone())
 | 
			
		||||
        .await
 | 
			
		||||
        .expect("Error occurred creating client");
 | 
			
		||||
 | 
			
		||||
    let language_manager = Arc::new(
 | 
			
		||||
        LanguageManager::from_compiled(include_str!(concat!(
 | 
			
		||||
            env!("CARGO_MANIFEST_DIR"),
 | 
			
		||||
            "/assets/",
 | 
			
		||||
            env!("STRINGS_FILE")
 | 
			
		||||
        )))
 | 
			
		||||
        .unwrap(),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        let guild_data_cache = dashmap::DashMap::new();
 | 
			
		||||
 | 
			
		||||
@@ -325,6 +440,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
        .await
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let language_manager = LanguageManager::from_compiled(include_str!(concat!(
 | 
			
		||||
            env!("CARGO_MANIFEST_DIR"),
 | 
			
		||||
            "/assets/",
 | 
			
		||||
            env!("STRINGS_FILE")
 | 
			
		||||
        )))
 | 
			
		||||
        .unwrap();
 | 
			
		||||
 | 
			
		||||
        let popular_timezones = sqlx::query!(
 | 
			
		||||
            "SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21"
 | 
			
		||||
        )
 | 
			
		||||
@@ -338,21 +460,14 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
        let mut data = client.data.write().await;
 | 
			
		||||
 | 
			
		||||
        data.insert::<GuildDataCache>(Arc::new(guild_data_cache));
 | 
			
		||||
 | 
			
		||||
        data.insert::<CurrentlyExecuting>(Arc::new(RwLock::new(HashMap::new())));
 | 
			
		||||
        data.insert::<SQLPool>(pool);
 | 
			
		||||
        data.insert::<PopularTimezones>(Arc::new(popular_timezones));
 | 
			
		||||
        data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new()));
 | 
			
		||||
        data.insert::<FrameworkCtx>(framework_arc.clone());
 | 
			
		||||
        data.insert::<LanguageManager>(language_manager.clone())
 | 
			
		||||
        data.insert::<LanguageManager>(Arc::new(language_manager))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    create_interactions(
 | 
			
		||||
        &client.cache_and_http,
 | 
			
		||||
        framework_arc.clone(),
 | 
			
		||||
        language_manager.clone(),
 | 
			
		||||
    )
 | 
			
		||||
    .await;
 | 
			
		||||
 | 
			
		||||
    if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| {
 | 
			
		||||
        let mut split = sr
 | 
			
		||||
            .split(',')
 | 
			
		||||
@@ -396,165 +511,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
			
		||||
    Ok(())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn create_interactions(
 | 
			
		||||
    cache_http: impl CacheHttp,
 | 
			
		||||
    framework: Arc<RegexFramework>,
 | 
			
		||||
    lm: Arc<LanguageManager>,
 | 
			
		||||
) {
 | 
			
		||||
    let http = cache_http.http();
 | 
			
		||||
    let app_id = {
 | 
			
		||||
        let app_info = http.get_current_application_info().await.unwrap();
 | 
			
		||||
 | 
			
		||||
        app_info.id.as_u64().to_owned()
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    if let Some(guild_id) = env::var("TEST_GUILD")
 | 
			
		||||
        .map(|i| i.parse::<u64>().ok().map(|u| GuildId(u)))
 | 
			
		||||
        .ok()
 | 
			
		||||
        .flatten()
 | 
			
		||||
    {
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("timezone")
 | 
			
		||||
                    .description("Select your local timezone. Do `/timezone` for more information")
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("region")
 | 
			
		||||
                            .description("Name of your time region")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String)
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("lang")
 | 
			
		||||
                    .description("Select your language")
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("language")
 | 
			
		||||
                            .description("Name of supported language you wish to use")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String)
 | 
			
		||||
                            .required(true);
 | 
			
		||||
 | 
			
		||||
                        for (code, language) in lm.all_languages() {
 | 
			
		||||
                            option.add_string_choice(language, code);
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        option
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("prefix")
 | 
			
		||||
                    .description("Select the prefix for normal commands")
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("prefix")
 | 
			
		||||
                            .description("New prefix to use")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String)
 | 
			
		||||
                            .required(true)
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("info")
 | 
			
		||||
                    .description("Get information about the bot")
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("donate")
 | 
			
		||||
                    .description("View information about the Patreon")
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("clock")
 | 
			
		||||
                    .description("View the current time in your timezone")
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("help")
 | 
			
		||||
                    .description("Get details about commands. Do `/help` to view all commands")
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("command")
 | 
			
		||||
                            .description("Name of the command to view help for")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String);
 | 
			
		||||
 | 
			
		||||
                        let mut command_set = HashSet::new();
 | 
			
		||||
                        command_set.insert("help");
 | 
			
		||||
                        command_set.insert("info");
 | 
			
		||||
                        command_set.insert("donate");
 | 
			
		||||
 | 
			
		||||
                        for (_, command) in &framework.commands {
 | 
			
		||||
                            if !command_set.contains(command.name) {
 | 
			
		||||
                                option.add_string_choice(&command.name, &command.name);
 | 
			
		||||
 | 
			
		||||
                                command_set.insert(command.name);
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
 | 
			
		||||
                        option
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
 | 
			
		||||
        guild_id
 | 
			
		||||
            .create_application_command(&http, app_id, |command| {
 | 
			
		||||
                command
 | 
			
		||||
                    .name("remind")
 | 
			
		||||
                    .description("Set a reminder")
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("message")
 | 
			
		||||
                            .description("Message to send with the reminder")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String)
 | 
			
		||||
                            .required(true)
 | 
			
		||||
                    })
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("time")
 | 
			
		||||
                            .description("Time to send the reminder")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::String)
 | 
			
		||||
                            .required(true)
 | 
			
		||||
                    })
 | 
			
		||||
                    .create_interaction_option(|option| {
 | 
			
		||||
                        option
 | 
			
		||||
                            .name("channel")
 | 
			
		||||
                            .description("Channel to send reminder to (default: this channel)")
 | 
			
		||||
                            .kind(ApplicationCommandOptionType::Channel)
 | 
			
		||||
                            .required(false)
 | 
			
		||||
                    })
 | 
			
		||||
            })
 | 
			
		||||
            .await
 | 
			
		||||
            .unwrap();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
 | 
			
		||||
    if let Some(subscription_guild) = *CNC_GUILD {
 | 
			
		||||
        let guild_member = GuildId(subscription_guild)
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,7 @@ enum ParseType {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub struct TimeParser {
 | 
			
		||||
    pub timezone: Tz,
 | 
			
		||||
    timezone: Tz,
 | 
			
		||||
    inverted: bool,
 | 
			
		||||
    time_string: String,
 | 
			
		||||
    parse_type: ParseType,
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user