Compare commits
	
		
			1 Commits
		
	
	
		
			master
			...
			discord-ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51d2ac2b92 | 
							
								
								
									
										1243
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1243
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "reminder_rs"
 | 
					name = "reminder_rs"
 | 
				
			||||||
version = "1.5.2"
 | 
					version = "1.5.0-2"
 | 
				
			||||||
authors = ["jellywx <judesouthworth@pm.me>"]
 | 
					authors = ["jellywx <judesouthworth@pm.me>"]
 | 
				
			||||||
edition = "2018"
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -22,7 +22,8 @@ serde_json = "1.0"
 | 
				
			|||||||
rand = "0.7"
 | 
					rand = "0.7"
 | 
				
			||||||
Inflector = "0.11"
 | 
					Inflector = "0.11"
 | 
				
			||||||
levenshtein = "1.0"
 | 
					levenshtein = "1.0"
 | 
				
			||||||
serenity = { git = "https://github.com/jellywx/serenity", branch = "jellywx-attachment_option", features = ["collector", "unstable_discord_api"] }
 | 
					# serenity = { version = "0.10", features = ["collector"] }
 | 
				
			||||||
 | 
					serenity = { path = "/home/jude/serenity", features = ["collector", "unstable_discord_api"] }
 | 
				
			||||||
sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
 | 
					sqlx = { version = "0.5", features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal", "chrono"]}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[dependencies.regex_command_attr]
 | 
					[dependencies.regex_command_attr]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,22 +1,18 @@
 | 
				
			|||||||
use regex_command_attr::command;
 | 
					use regex_command_attr::command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serenity::{builder::CreateEmbedFooter, client::Context, model::channel::Message};
 | 
					use serenity::{client::Context, model::channel::Message};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::offset::Utc;
 | 
					use chrono::offset::Utc;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    command_help,
 | 
					    command_help, consts::DEFAULT_PREFIX, get_ctx_data, language_manager::LanguageManager,
 | 
				
			||||||
    consts::DEFAULT_PREFIX,
 | 
					    models::UserData, FrameworkCtx, THEME_COLOR,
 | 
				
			||||||
    get_ctx_data,
 | 
					 | 
				
			||||||
    language_manager::LanguageManager,
 | 
					 | 
				
			||||||
    models::{user_data::UserData, CtxGuildData},
 | 
					 | 
				
			||||||
    FrameworkCtx, THEME_COLOR,
 | 
					 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
    sync::Arc,
 | 
					use serenity::builder::CreateEmbedFooter;
 | 
				
			||||||
    time::{SystemTime, UNIX_EPOCH},
 | 
					use std::sync::Arc;
 | 
				
			||||||
};
 | 
					use std::time::{SystemTime, UNIX_EPOCH};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command]
 | 
					#[command]
 | 
				
			||||||
#[can_blacklist(false)]
 | 
					#[can_blacklist(false)]
 | 
				
			||||||
@@ -35,7 +31,7 @@ async fn ping(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter {
 | 
					async fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEmbedFooter {
 | 
				
			||||||
    let shard_count = ctx.cache.shard_count();
 | 
					    let shard_count = ctx.cache.shard_count().await;
 | 
				
			||||||
    let shard = ctx.shard_id;
 | 
					    let shard = ctx.shard_id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    move |f| {
 | 
					    move |f| {
 | 
				
			||||||
@@ -145,7 +141,7 @@ async fn info(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let desc = lm
 | 
					    let desc = lm
 | 
				
			||||||
        .get(&language.await, "info")
 | 
					        .get(&language.await, "info")
 | 
				
			||||||
        .replacen("{user}", ¤t_user.name, 1)
 | 
					        .replacen("{user}", ¤t_user.await.name, 1)
 | 
				
			||||||
        .replace("{default_prefix}", &*DEFAULT_PREFIX)
 | 
					        .replace("{default_prefix}", &*DEFAULT_PREFIX)
 | 
				
			||||||
        .replace("{prefix}", &prefix.await);
 | 
					        .replace("{prefix}", &prefix.await);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -206,6 +202,7 @@ async fn clock(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
					    let language = UserData::language_of(&msg.author, &pool).await;
 | 
				
			||||||
    let timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
					    let timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
				
			||||||
 | 
					    let meridian = UserData::meridian_of(&msg.author, &pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let now = Utc::now().with_timezone(&timezone);
 | 
					    let now = Utc::now().with_timezone(&timezone);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -215,7 +212,7 @@ async fn clock(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
        .channel_id
 | 
					        .channel_id
 | 
				
			||||||
        .say(
 | 
					        .say(
 | 
				
			||||||
            &ctx,
 | 
					            &ctx,
 | 
				
			||||||
            clock_display.replacen("{}", &now.format("%H:%M").to_string(), 1),
 | 
					            clock_display.replacen("{}", &now.format(meridian.fmt_str()).to_string(), 1),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ use serenity::{
 | 
				
			|||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        channel::Message,
 | 
					        channel::Message,
 | 
				
			||||||
        id::{ChannelId, MessageId, RoleId},
 | 
					        id::{ChannelId, MessageId, RoleId},
 | 
				
			||||||
        interactions::message_component::ButtonStyle,
 | 
					        interactions::ButtonStyle,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -24,10 +24,11 @@ use crate::{
 | 
				
			|||||||
    consts::{REGEX_ALIAS, REGEX_CHANNEL, REGEX_COMMANDS, REGEX_ROLE, THEME_COLOR},
 | 
					    consts::{REGEX_ALIAS, REGEX_CHANNEL, REGEX_COMMANDS, REGEX_ROLE, THEME_COLOR},
 | 
				
			||||||
    framework::SendIterator,
 | 
					    framework::SendIterator,
 | 
				
			||||||
    get_ctx_data,
 | 
					    get_ctx_data,
 | 
				
			||||||
    models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxGuildData},
 | 
					    models::{ChannelData, GuildData, UserData},
 | 
				
			||||||
    FrameworkCtx, PopularTimezones,
 | 
					    FrameworkCtx, PopularTimezones,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
use std::{collections::HashMap, iter};
 | 
					use std::{collections::HashMap, iter};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command]
 | 
					#[command]
 | 
				
			||||||
@@ -46,11 +47,13 @@ async fn blacklist(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let (channel, local) = match capture_opt {
 | 
					    let (channel, local) = match capture_opt {
 | 
				
			||||||
        Some(capture) => (
 | 
					        Some(capture) => (
 | 
				
			||||||
            ChannelId(capture.as_str().parse::<u64>().unwrap()).to_channel_cached(&ctx),
 | 
					            ChannelId(capture.as_str().parse::<u64>().unwrap())
 | 
				
			||||||
 | 
					                .to_channel_cached(&ctx)
 | 
				
			||||||
 | 
					                .await,
 | 
				
			||||||
            false,
 | 
					            false,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        None => (msg.channel(&ctx).await.ok(), true),
 | 
					        None => (msg.channel(&ctx).await, true),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut channel_data = ChannelData::from_channel(channel.unwrap(), &pool)
 | 
					    let mut channel_data = ChannelData::from_channel(channel.unwrap(), &pool)
 | 
				
			||||||
@@ -72,7 +75,8 @@ async fn blacklist(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                .say(&ctx, lm.get(&language, "blacklist/added_from"))
 | 
					                .say(&ctx, lm.get(&language, "blacklist/added_from"))
 | 
				
			||||||
                .await;
 | 
					                .await;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else if local {
 | 
					    } else {
 | 
				
			||||||
 | 
					        if local {
 | 
				
			||||||
            let _ = msg
 | 
					            let _ = msg
 | 
				
			||||||
                .channel_id
 | 
					                .channel_id
 | 
				
			||||||
                .say(&ctx, lm.get(&language, "blacklist/removed"))
 | 
					                .say(&ctx, lm.get(&language, "blacklist/removed"))
 | 
				
			||||||
@@ -83,6 +87,7 @@ async fn blacklist(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                .say(&ctx, lm.get(&language, "blacklist/removed_from"))
 | 
					                .say(&ctx, lm.get(&language, "blacklist/removed_from"))
 | 
				
			||||||
                .await;
 | 
					                .await;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command]
 | 
					#[command]
 | 
				
			||||||
@@ -108,7 +113,11 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                let content = lm
 | 
					                let content = lm
 | 
				
			||||||
                    .get(&user_data.language, "timezone/set_p")
 | 
					                    .get(&user_data.language, "timezone/set_p")
 | 
				
			||||||
                    .replacen("{timezone}", &user_data.timezone, 1)
 | 
					                    .replacen("{timezone}", &user_data.timezone, 1)
 | 
				
			||||||
                    .replacen("{time}", &now.format("%H:%M").to_string(), 1);
 | 
					                    .replacen(
 | 
				
			||||||
 | 
					                        "{time}",
 | 
				
			||||||
 | 
					                        &now.format(user_data.meridian().fmt_str_short()).to_string(),
 | 
				
			||||||
 | 
					                        1,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let _ =
 | 
					                let _ =
 | 
				
			||||||
                    msg.channel_id
 | 
					                    msg.channel_id
 | 
				
			||||||
@@ -145,7 +154,10 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                        tz.to_string(),
 | 
					                        tz.to_string(),
 | 
				
			||||||
                        format!(
 | 
					                        format!(
 | 
				
			||||||
                            "🕗 `{}`",
 | 
					                            "🕗 `{}`",
 | 
				
			||||||
                            Utc::now().with_timezone(tz).format("%H:%M").to_string()
 | 
					                            Utc::now()
 | 
				
			||||||
 | 
					                                .with_timezone(tz)
 | 
				
			||||||
 | 
					                                .format(user_data.meridian().fmt_str_short())
 | 
				
			||||||
 | 
					                                .to_string()
 | 
				
			||||||
                        ),
 | 
					                        ),
 | 
				
			||||||
                        true,
 | 
					                        true,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
@@ -199,7 +211,10 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                t.to_string(),
 | 
					                t.to_string(),
 | 
				
			||||||
                format!(
 | 
					                format!(
 | 
				
			||||||
                    "🕗 `{}`",
 | 
					                    "🕗 `{}`",
 | 
				
			||||||
                    Utc::now().with_timezone(t).format("%H:%M").to_string()
 | 
					                    Utc::now()
 | 
				
			||||||
 | 
					                        .with_timezone(t)
 | 
				
			||||||
 | 
					                        .format(user_data.meridian().fmt_str_short())
 | 
				
			||||||
 | 
					                        .to_string()
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                true,
 | 
					                true,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
@@ -237,6 +252,49 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[command("meridian")]
 | 
				
			||||||
 | 
					async fn change_meridian(ctx: &Context, msg: &Message, args: String) {
 | 
				
			||||||
 | 
					    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if &args == "12" {
 | 
				
			||||||
 | 
					        user_data.meridian_time = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user_data.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let _ = msg
 | 
				
			||||||
 | 
					            .channel_id
 | 
				
			||||||
 | 
					            .send_message(&ctx, |m| {
 | 
				
			||||||
 | 
					                m.embed(|e| {
 | 
				
			||||||
 | 
					                    e.title(lm.get(&user_data.language, "meridian/title"))
 | 
				
			||||||
 | 
					                        .color(*THEME_COLOR)
 | 
				
			||||||
 | 
					                        .description(lm.get(&user_data.language, "meridian/12"))
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await;
 | 
				
			||||||
 | 
					    } else if &args == "24" {
 | 
				
			||||||
 | 
					        user_data.meridian_time = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        user_data.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let _ = msg
 | 
				
			||||||
 | 
					            .channel_id
 | 
				
			||||||
 | 
					            .send_message(&ctx, |m| {
 | 
				
			||||||
 | 
					                m.embed(|e| {
 | 
				
			||||||
 | 
					                    e.title(lm.get(&user_data.language, "meridian/title"))
 | 
				
			||||||
 | 
					                        .color(*THEME_COLOR)
 | 
				
			||||||
 | 
					                        .description(lm.get(&user_data.language, "meridian/24"))
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					            .await;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        let prefix = ctx.prefix(msg.guild_id).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        command_help(ctx, msg, lm, &prefix, &user_data.language, "meridian").await;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command("lang")]
 | 
					#[command("lang")]
 | 
				
			||||||
async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
					async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
				
			||||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
					    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
				
			||||||
@@ -392,7 +450,7 @@ async fn restrict(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
					    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
					    let language = UserData::language_of(&msg.author, &pool).await;
 | 
				
			||||||
    let guild_data = GuildData::from_guild(msg.guild(&ctx).unwrap(), &pool)
 | 
					    let guild_data = GuildData::from_guild(msg.guild(&ctx).await.unwrap(), &pool)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -409,7 +467,7 @@ async fn restrict(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                .unwrap(),
 | 
					                .unwrap(),
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let role_opt = role_id.to_role_cached(&ctx);
 | 
					        let role_opt = role_id.to_role_cached(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(role) = role_opt {
 | 
					        if let Some(role) = role_opt {
 | 
				
			||||||
            let _ = sqlx::query!(
 | 
					            let _ = sqlx::query!(
 | 
				
			||||||
@@ -622,7 +680,7 @@ SELECT command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHER
 | 
				
			|||||||
                                .get::<FrameworkCtx>().cloned().expect("Could not get FrameworkCtx from data");
 | 
					                                .get::<FrameworkCtx>().cloned().expect("Could not get FrameworkCtx from data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let mut new_msg = msg.clone();
 | 
					                            let mut new_msg = msg.clone();
 | 
				
			||||||
                            new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id(), row.command);
 | 
					                            new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id().await, row.command);
 | 
				
			||||||
                            new_msg.id = MessageId(0);
 | 
					                            new_msg.id = MessageId(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            framework.dispatch(ctx.clone(), new_msg).await;
 | 
					                            framework.dispatch(ctx.clone(), new_msg).await;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +1,34 @@
 | 
				
			|||||||
use regex_command_attr::command;
 | 
					use regex_command_attr::command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serenity::{
 | 
					use serenity::{
 | 
				
			||||||
 | 
					    cache::Cache,
 | 
				
			||||||
    client::Context,
 | 
					    client::Context,
 | 
				
			||||||
    http::CacheHttp,
 | 
					    http::CacheHttp,
 | 
				
			||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
 | 
					        channel::GuildChannel,
 | 
				
			||||||
        channel::Message,
 | 
					        channel::Message,
 | 
				
			||||||
        channel::{Channel, GuildChannel},
 | 
					 | 
				
			||||||
        guild::Guild,
 | 
					        guild::Guild,
 | 
				
			||||||
        id::{ChannelId, GuildId, UserId},
 | 
					        id::{ChannelId, GuildId, UserId},
 | 
				
			||||||
 | 
					        misc::Mentionable,
 | 
				
			||||||
        webhook::Webhook,
 | 
					        webhook::Webhook,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    prelude::Mentionable,
 | 
					 | 
				
			||||||
    Result as SerenityResult,
 | 
					    Result as SerenityResult,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    check_subscription_on_message, command_help,
 | 
					    check_subscription_on_message, command_help,
 | 
				
			||||||
    consts::{
 | 
					    consts::{
 | 
				
			||||||
        CHARACTERS, MAX_TIME, MIN_INTERVAL, REGEX_CHANNEL_USER, REGEX_CONTENT_SUBSTITUTION,
 | 
					        CHARACTERS, DAY, HOUR, MAX_TIME, MINUTE, MIN_INTERVAL, REGEX_CHANNEL, REGEX_CHANNEL_USER,
 | 
				
			||||||
        REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2, REGEX_REMIND_COMMAND, THEME_COLOR,
 | 
					        REGEX_CONTENT_SUBSTITUTION, REGEX_NATURAL_COMMAND_1, REGEX_NATURAL_COMMAND_2,
 | 
				
			||||||
 | 
					        REGEX_REMIND_COMMAND, THEME_COLOR,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    framework::SendIterator,
 | 
					    framework::SendIterator,
 | 
				
			||||||
    get_ctx_data,
 | 
					    get_ctx_data,
 | 
				
			||||||
    models::{
 | 
					    models::{ChannelData, GuildData, Timer, UserData},
 | 
				
			||||||
        channel_data::ChannelData,
 | 
					 | 
				
			||||||
        guild_data::GuildData,
 | 
					 | 
				
			||||||
        reminder::{LookFlags, Reminder},
 | 
					 | 
				
			||||||
        timer::Timer,
 | 
					 | 
				
			||||||
        user_data::UserData,
 | 
					 | 
				
			||||||
        CtxGuildData,
 | 
					 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
    time_parser::{natural_parser, TimeParser},
 | 
					    time_parser::{natural_parser, TimeParser},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					use chrono::{offset::TimeZone, NaiveDateTime};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
					use rand::{rngs::OsRng, seq::IteratorRandom};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -45,13 +40,33 @@ use std::{
 | 
				
			|||||||
    collections::HashSet,
 | 
					    collections::HashSet,
 | 
				
			||||||
    convert::TryInto,
 | 
					    convert::TryInto,
 | 
				
			||||||
    default::Default,
 | 
					    default::Default,
 | 
				
			||||||
    env,
 | 
					 | 
				
			||||||
    fmt::Display,
 | 
					    fmt::Display,
 | 
				
			||||||
    string::ToString,
 | 
					    string::ToString,
 | 
				
			||||||
    time::{SystemTime, UNIX_EPOCH},
 | 
					    time::{SystemTime, UNIX_EPOCH},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
use regex::Captures;
 | 
					use regex::Captures;
 | 
				
			||||||
 | 
					use serenity::model::channel::Channel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fn longhand_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 mut sections = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (var, name) in [days, hours, minutes, seconds]
 | 
				
			||||||
 | 
					        .iter()
 | 
				
			||||||
 | 
					        .zip(["days", "hours", "minutes", "seconds"].iter())
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if *var > 0 {
 | 
				
			||||||
 | 
					            sections.push(format!("{} {}", var, name));
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sections.join(", ")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn create_webhook(
 | 
					async fn create_webhook(
 | 
				
			||||||
    ctx: impl CacheHttp,
 | 
					    ctx: impl CacheHttp,
 | 
				
			||||||
@@ -85,6 +100,7 @@ async fn pause(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
					    let language = UserData::language_of(&msg.author, &pool).await;
 | 
				
			||||||
    let timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
					    let timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
				
			||||||
 | 
					    let meridian = UserData::meridian_of(&msg.author, &pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut channel = ChannelData::from_channel(msg.channel(&ctx).await.unwrap(), &pool)
 | 
					    let mut channel = ChannelData::from_channel(msg.channel(&ctx).await.unwrap(), &pool)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
@@ -120,9 +136,13 @@ async fn pause(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                channel.commit_changes(&pool).await;
 | 
					                channel.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let content = lm
 | 
					                let content = lm.get(&language, "pause/paused_until").replace(
 | 
				
			||||||
                    .get(&language, "pause/paused_until")
 | 
					                    "{}",
 | 
				
			||||||
                    .replace("{}", &format!("<t:{}:D>", timestamp));
 | 
					                    &timezone
 | 
				
			||||||
 | 
					                        .timestamp(timestamp, 0)
 | 
				
			||||||
 | 
					                        .format(meridian.fmt_str())
 | 
				
			||||||
 | 
					                        .to_string(),
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let _ = msg.channel_id.say(&ctx, content).await;
 | 
					                let _ = msg.channel_id.say(&ctx, content).await;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -152,7 +172,7 @@ async fn offset(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
        let parser = TimeParser::new(&args, user_data.timezone());
 | 
					        let parser = TimeParser::new(&args, user_data.timezone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Ok(displacement) = parser.displacement() {
 | 
					        if let Ok(displacement) = parser.displacement() {
 | 
				
			||||||
            if let Some(guild) = msg.guild(&ctx) {
 | 
					            if let Some(guild) = msg.guild(&ctx).await {
 | 
				
			||||||
                let guild_data = GuildData::from_guild(guild, &pool).await.unwrap();
 | 
					                let guild_data = GuildData::from_guild(guild, &pool).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                sqlx::query!(
 | 
					                sqlx::query!(
 | 
				
			||||||
@@ -161,7 +181,7 @@ UPDATE reminders
 | 
				
			|||||||
    INNER JOIN `channels`
 | 
					    INNER JOIN `channels`
 | 
				
			||||||
        ON `channels`.id = reminders.channel_id
 | 
					        ON `channels`.id = reminders.channel_id
 | 
				
			||||||
    SET
 | 
					    SET
 | 
				
			||||||
        reminders.`utc_time` = DATE_ADD(reminders.`utc_time`, INTERVAL ? SECOND)
 | 
					        reminders.`utc_time` = reminders.`utc_time` + ?
 | 
				
			||||||
    WHERE channels.guild_id = ?
 | 
					    WHERE channels.guild_id = ?
 | 
				
			||||||
                    ",
 | 
					                    ",
 | 
				
			||||||
                    displacement,
 | 
					                    displacement,
 | 
				
			||||||
@@ -173,7 +193,7 @@ UPDATE reminders
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                sqlx::query!(
 | 
					                sqlx::query!(
 | 
				
			||||||
                    "
 | 
					                    "
 | 
				
			||||||
UPDATE reminders SET `utc_time` = DATE_ADD(`utc_time`, INTERVAL ? SECOND) WHERE reminders.channel_id = ?
 | 
					UPDATE reminders SET `utc_time` = `utc_time` + ? WHERE reminders.channel_id = ?
 | 
				
			||||||
                    ",
 | 
					                    ",
 | 
				
			||||||
                    displacement,
 | 
					                    displacement,
 | 
				
			||||||
                    user_data.dm_channel
 | 
					                    user_data.dm_channel
 | 
				
			||||||
@@ -253,6 +273,108 @@ async fn nudge(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum TimeDisplayType {
 | 
				
			||||||
 | 
					    Absolute,
 | 
				
			||||||
 | 
					    Relative,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LookFlags {
 | 
				
			||||||
 | 
					    pub limit: u16,
 | 
				
			||||||
 | 
					    pub show_disabled: bool,
 | 
				
			||||||
 | 
					    pub channel_id: Option<u64>,
 | 
				
			||||||
 | 
					    time_display: TimeDisplayType,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Default for LookFlags {
 | 
				
			||||||
 | 
					    fn default() -> Self {
 | 
				
			||||||
 | 
					        Self {
 | 
				
			||||||
 | 
					            limit: u16::MAX,
 | 
				
			||||||
 | 
					            show_disabled: true,
 | 
				
			||||||
 | 
					            channel_id: None,
 | 
				
			||||||
 | 
					            time_display: TimeDisplayType::Relative,
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LookFlags {
 | 
				
			||||||
 | 
					    fn from_string(args: &str) -> Self {
 | 
				
			||||||
 | 
					        let mut new_flags: Self = Default::default();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for arg in args.split(' ') {
 | 
				
			||||||
 | 
					            match arg {
 | 
				
			||||||
 | 
					                "enabled" => {
 | 
				
			||||||
 | 
					                    new_flags.show_disabled = false;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                "time" => {
 | 
				
			||||||
 | 
					                    new_flags.time_display = TimeDisplayType::Absolute;
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                param => {
 | 
				
			||||||
 | 
					                    if let Ok(val) = param.parse::<u16>() {
 | 
				
			||||||
 | 
					                        new_flags.limit = val;
 | 
				
			||||||
 | 
					                    } else {
 | 
				
			||||||
 | 
					                        if let Some(channel) = REGEX_CHANNEL
 | 
				
			||||||
 | 
					                            .captures(&arg)
 | 
				
			||||||
 | 
					                            .map(|cap| cap.get(1))
 | 
				
			||||||
 | 
					                            .flatten()
 | 
				
			||||||
 | 
					                            .map(|c| c.as_str().parse::<u64>().unwrap())
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            new_flags.channel_id = Some(channel);
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        new_flags
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct LookReminder {
 | 
				
			||||||
 | 
					    id: u32,
 | 
				
			||||||
 | 
					    time: NaiveDateTime,
 | 
				
			||||||
 | 
					    interval: Option<u32>,
 | 
				
			||||||
 | 
					    channel: u64,
 | 
				
			||||||
 | 
					    content: String,
 | 
				
			||||||
 | 
					    description: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl LookReminder {
 | 
				
			||||||
 | 
					    fn display_content(&self) -> String {
 | 
				
			||||||
 | 
					        if self.content.len() > 0 {
 | 
				
			||||||
 | 
					            self.content.clone()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            self.description.clone()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fn display(&self, flags: &LookFlags, inter: &str) -> String {
 | 
				
			||||||
 | 
					        let time_display = match flags.time_display {
 | 
				
			||||||
 | 
					            TimeDisplayType::Absolute => format!("<t:{}>", self.time.timestamp()),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            TimeDisplayType::Relative => format!("<t:{}:R>", self.time.timestamp()),
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Some(interval) = self.interval {
 | 
				
			||||||
 | 
					            format!(
 | 
				
			||||||
 | 
					                "'{}' *{}* **{}**, repeating every **{}**",
 | 
				
			||||||
 | 
					                self.display_content(),
 | 
				
			||||||
 | 
					                &inter,
 | 
				
			||||||
 | 
					                time_display,
 | 
				
			||||||
 | 
					                longhand_displacement(interval as u64)
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            format!(
 | 
				
			||||||
 | 
					                "'{}' *{}* **{}**",
 | 
				
			||||||
 | 
					                self.display_content(),
 | 
				
			||||||
 | 
					                &inter,
 | 
				
			||||||
 | 
					                time_display
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command("look")]
 | 
					#[command("look")]
 | 
				
			||||||
#[permission_level(Managed)]
 | 
					#[permission_level(Managed)]
 | 
				
			||||||
async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
					async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
				
			||||||
@@ -262,19 +384,53 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let flags = LookFlags::from_string(&args);
 | 
					    let flags = LookFlags::from_string(&args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let channel_opt = msg.channel_id.to_channel_cached(&ctx);
 | 
					    let enabled = if flags.show_disabled { "0,1" } else { "1" };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let channel_opt = msg.channel_id.to_channel_cached(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
 | 
					    let channel_id = if let Some(Channel::Guild(channel)) = channel_opt {
 | 
				
			||||||
        if Some(channel.guild_id) == msg.guild_id {
 | 
					        if Some(channel.guild_id) == msg.guild_id {
 | 
				
			||||||
            flags.channel_id.unwrap_or(msg.channel_id)
 | 
					            flags
 | 
				
			||||||
 | 
					                .channel_id
 | 
				
			||||||
 | 
					                .unwrap_or_else(|| msg.channel_id.as_u64().to_owned())
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            msg.channel_id
 | 
					            msg.channel_id.as_u64().to_owned()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        msg.channel_id
 | 
					        msg.channel_id.as_u64().to_owned()
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let reminders = Reminder::from_channel(ctx, channel_id, &flags).await;
 | 
					    let reminders = sqlx::query_as!(
 | 
				
			||||||
 | 
					        LookReminder,
 | 
				
			||||||
 | 
					        "
 | 
				
			||||||
 | 
					SELECT
 | 
				
			||||||
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
 | 
					FROM
 | 
				
			||||||
 | 
					    reminders
 | 
				
			||||||
 | 
					INNER JOIN
 | 
				
			||||||
 | 
					    channels
 | 
				
			||||||
 | 
					ON
 | 
				
			||||||
 | 
					    reminders.channel_id = channels.id
 | 
				
			||||||
 | 
					WHERE
 | 
				
			||||||
 | 
					    channels.channel = ? AND
 | 
				
			||||||
 | 
					    FIND_IN_SET(reminders.enabled, ?)
 | 
				
			||||||
 | 
					ORDER BY
 | 
				
			||||||
 | 
					    reminders.utc_time
 | 
				
			||||||
 | 
					LIMIT
 | 
				
			||||||
 | 
					    ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					        channel_id,
 | 
				
			||||||
 | 
					        enabled,
 | 
				
			||||||
 | 
					        flags.limit
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .fetch_all(&pool)
 | 
				
			||||||
 | 
					    .await
 | 
				
			||||||
 | 
					    .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if reminders.is_empty() {
 | 
					    if reminders.is_empty() {
 | 
				
			||||||
        let _ = msg
 | 
					        let _ = msg
 | 
				
			||||||
@@ -304,9 +460,94 @@ async fn delete(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
        .say(&ctx, lm.get(&user_data.language, "del/listing"))
 | 
					        .say(&ctx, lm.get(&user_data.language, "del/listing"))
 | 
				
			||||||
        .await;
 | 
					        .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut reminder_ids: Vec<u32> = vec![];
 | 
					    let reminders = if let Some(guild_id) = msg.guild_id {
 | 
				
			||||||
 | 
					        let guild_opt = guild_id.to_guild_cached(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let reminders = Reminder::from_guild(ctx, msg.guild_id, msg.author.id).await;
 | 
					        if let Some(guild) = guild_opt {
 | 
				
			||||||
 | 
					            let channels = guild
 | 
				
			||||||
 | 
					                .channels
 | 
				
			||||||
 | 
					                .keys()
 | 
				
			||||||
 | 
					                .into_iter()
 | 
				
			||||||
 | 
					                .map(|k| k.as_u64().to_string())
 | 
				
			||||||
 | 
					                .collect::<Vec<String>>()
 | 
				
			||||||
 | 
					                .join(",");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					                LookReminder,
 | 
				
			||||||
 | 
					                "
 | 
				
			||||||
 | 
					SELECT
 | 
				
			||||||
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
 | 
					FROM
 | 
				
			||||||
 | 
					    reminders
 | 
				
			||||||
 | 
					LEFT OUTER JOIN
 | 
				
			||||||
 | 
					    channels
 | 
				
			||||||
 | 
					ON
 | 
				
			||||||
 | 
					    channels.id = reminders.channel_id
 | 
				
			||||||
 | 
					WHERE
 | 
				
			||||||
 | 
					    FIND_IN_SET(channels.channel, ?)
 | 
				
			||||||
 | 
					                ",
 | 
				
			||||||
 | 
					                channels
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .fetch_all(&pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					                LookReminder,
 | 
				
			||||||
 | 
					                "
 | 
				
			||||||
 | 
					SELECT
 | 
				
			||||||
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
 | 
					FROM
 | 
				
			||||||
 | 
					    reminders
 | 
				
			||||||
 | 
					LEFT OUTER JOIN
 | 
				
			||||||
 | 
					    channels
 | 
				
			||||||
 | 
					ON
 | 
				
			||||||
 | 
					    channels.id = reminders.channel_id
 | 
				
			||||||
 | 
					WHERE
 | 
				
			||||||
 | 
					    channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
				
			||||||
 | 
					                ",
 | 
				
			||||||
 | 
					                guild_id.as_u64()
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					            .fetch_all(&pool)
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        sqlx::query_as!(
 | 
				
			||||||
 | 
					            LookReminder,
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT
 | 
				
			||||||
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
 | 
					FROM
 | 
				
			||||||
 | 
					    reminders
 | 
				
			||||||
 | 
					INNER JOIN
 | 
				
			||||||
 | 
					    channels
 | 
				
			||||||
 | 
					ON
 | 
				
			||||||
 | 
					    channels.id = reminders.channel_id
 | 
				
			||||||
 | 
					WHERE
 | 
				
			||||||
 | 
					    channels.channel = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            msg.channel_id.as_u64()
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_all(&pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let mut reminder_ids: Vec<u32> = vec![];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let enumerated_reminders = reminders.iter().enumerate().map(|(count, reminder)| {
 | 
					    let enumerated_reminders = reminders.iter().enumerate().map(|(count, reminder)| {
 | 
				
			||||||
        reminder_ids.push(reminder.id);
 | 
					        reminder_ids.push(reminder.id);
 | 
				
			||||||
@@ -316,7 +557,7 @@ async fn delete(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
            count + 1,
 | 
					            count + 1,
 | 
				
			||||||
            reminder.display_content(),
 | 
					            reminder.display_content(),
 | 
				
			||||||
            reminder.channel,
 | 
					            reminder.channel,
 | 
				
			||||||
            reminder.utc_time.timestamp()
 | 
					            reminder.time.timestamp()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -560,6 +801,7 @@ impl ReminderScope {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#[derive(PartialEq, Eq, Hash, Debug)]
 | 
					#[derive(PartialEq, Eq, Hash, Debug)]
 | 
				
			||||||
enum ReminderError {
 | 
					enum ReminderError {
 | 
				
			||||||
 | 
					    LongTime,
 | 
				
			||||||
    LongInterval,
 | 
					    LongInterval,
 | 
				
			||||||
    PastTime,
 | 
					    PastTime,
 | 
				
			||||||
    ShortInterval,
 | 
					    ShortInterval,
 | 
				
			||||||
@@ -586,6 +828,7 @@ trait ToResponse {
 | 
				
			|||||||
impl ToResponse for ReminderError {
 | 
					impl ToResponse for ReminderError {
 | 
				
			||||||
    fn to_response(&self) -> &'static str {
 | 
					    fn to_response(&self) -> &'static str {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::LongTime => "remind/long_time",
 | 
				
			||||||
            Self::LongInterval => "interval/long_interval",
 | 
					            Self::LongInterval => "interval/long_interval",
 | 
				
			||||||
            Self::PastTime => "remind/past_time",
 | 
					            Self::PastTime => "remind/past_time",
 | 
				
			||||||
            Self::ShortInterval => "interval/short_interval",
 | 
					            Self::ShortInterval => "interval/short_interval",
 | 
				
			||||||
@@ -598,6 +841,7 @@ impl ToResponse for ReminderError {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    fn to_response_natural(&self) -> &'static str {
 | 
					    fn to_response_natural(&self) -> &'static str {
 | 
				
			||||||
        match self {
 | 
					        match self {
 | 
				
			||||||
 | 
					            Self::LongTime => "natural/long_time",
 | 
				
			||||||
            Self::InvalidTime => "natural/invalid_time",
 | 
					            Self::InvalidTime => "natural/invalid_time",
 | 
				
			||||||
            _ => self.to_response(),
 | 
					            _ => self.to_response(),
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -681,7 +925,7 @@ impl Content {
 | 
				
			|||||||
                Ok(Self {
 | 
					                Ok(Self {
 | 
				
			||||||
                    content: content.to_string(),
 | 
					                    content: content.to_string(),
 | 
				
			||||||
                    tts: false,
 | 
					                    tts: false,
 | 
				
			||||||
                    attachment: Some(attachment_bytes),
 | 
					                    attachment: Some(attachment_bytes.clone()),
 | 
				
			||||||
                    attachment_name: Some(attachment.filename.clone()),
 | 
					                    attachment_name: Some(attachment.filename.clone()),
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
@@ -770,7 +1014,7 @@ INSERT INTO reminders (
 | 
				
			|||||||
    `embed_color`,
 | 
					    `embed_color`,
 | 
				
			||||||
    `channel_id`,
 | 
					    `channel_id`,
 | 
				
			||||||
    `utc_time`,
 | 
					    `utc_time`,
 | 
				
			||||||
    `interval_seconds`,
 | 
					    `interval`,
 | 
				
			||||||
    `set_by`,
 | 
					    `set_by`,
 | 
				
			||||||
    `expires`
 | 
					    `expires`
 | 
				
			||||||
) VALUES (
 | 
					) VALUES (
 | 
				
			||||||
@@ -884,7 +1128,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
        Some(captures) => {
 | 
					        Some(captures) => {
 | 
				
			||||||
            let parsed = parse_mention_list(captures.name("mentions").unwrap().as_str());
 | 
					            let parsed = parse_mention_list(captures.name("mentions").unwrap().as_str());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let scopes = if parsed.is_empty() {
 | 
					            let scopes = if parsed.len() == 0 {
 | 
				
			||||||
                vec![ReminderScope::Channel(msg.channel_id.into())]
 | 
					                vec![ReminderScope::Channel(msg.channel_id.into())]
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                parsed
 | 
					                parsed
 | 
				
			||||||
@@ -923,7 +1167,6 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
                    match content_res {
 | 
					                    match content_res {
 | 
				
			||||||
                        Ok(mut content) => {
 | 
					                        Ok(mut content) => {
 | 
				
			||||||
                            let mut ok_locations = vec![];
 | 
					                            let mut ok_locations = vec![];
 | 
				
			||||||
                            let mut ok_reminders = vec![];
 | 
					 | 
				
			||||||
                            let mut err_locations = vec![];
 | 
					                            let mut err_locations = vec![];
 | 
				
			||||||
                            let mut err_types = HashSet::new();
 | 
					                            let mut err_types = HashSet::new();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -935,22 +1178,17 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
                                    msg.guild_id,
 | 
					                                    msg.guild_id,
 | 
				
			||||||
                                    &scope,
 | 
					                                    &scope,
 | 
				
			||||||
                                    &time_parser,
 | 
					                                    &time_parser,
 | 
				
			||||||
                                    expires_parser.as_ref(),
 | 
					                                    expires_parser.as_ref().clone(),
 | 
				
			||||||
                                    interval,
 | 
					                                    interval,
 | 
				
			||||||
                                    &mut content,
 | 
					                                    &mut content,
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
                                .await;
 | 
					                                .await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                match res {
 | 
					                                if let Err(e) = res {
 | 
				
			||||||
                                    Err(e) => {
 | 
					 | 
				
			||||||
                                    err_locations.push(scope);
 | 
					                                    err_locations.push(scope);
 | 
				
			||||||
                                    err_types.insert(e);
 | 
					                                    err_types.insert(e);
 | 
				
			||||||
                                    }
 | 
					                                } else {
 | 
				
			||||||
 | 
					 | 
				
			||||||
                                    Ok(id) => {
 | 
					 | 
				
			||||||
                                    ok_locations.push(scope);
 | 
					                                    ok_locations.push(scope);
 | 
				
			||||||
                                        ok_reminders.push(id);
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1159,7 +1397,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                                &scope,
 | 
					                                &scope,
 | 
				
			||||||
                                timestamp,
 | 
					                                timestamp,
 | 
				
			||||||
                                expires,
 | 
					                                expires,
 | 
				
			||||||
                                interval,
 | 
					                                interval.clone(),
 | 
				
			||||||
                                &mut content,
 | 
					                                &mut content,
 | 
				
			||||||
                            )
 | 
					                            )
 | 
				
			||||||
                            .await;
 | 
					                            .await;
 | 
				
			||||||
@@ -1291,7 +1529,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
					async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
				
			||||||
    ctx: &Context,
 | 
					    ctx: impl CacheHttp + AsRef<Cache>,
 | 
				
			||||||
    pool: &MySqlPool,
 | 
					    pool: &MySqlPool,
 | 
				
			||||||
    user_id: U,
 | 
					    user_id: U,
 | 
				
			||||||
    guild_id: Option<GuildId>,
 | 
					    guild_id: Option<GuildId>,
 | 
				
			||||||
@@ -1300,11 +1538,11 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
				
			|||||||
    expires_parser: Option<T>,
 | 
					    expires_parser: Option<T>,
 | 
				
			||||||
    interval: Option<i64>,
 | 
					    interval: Option<i64>,
 | 
				
			||||||
    content: &mut Content,
 | 
					    content: &mut Content,
 | 
				
			||||||
) -> Result<Reminder, ReminderError> {
 | 
					) -> Result<(), ReminderError> {
 | 
				
			||||||
    let user_id = user_id.into();
 | 
					    let user_id = user_id.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if let Some(g_id) = guild_id {
 | 
					    if let Some(g_id) = guild_id {
 | 
				
			||||||
        if let Some(guild) = g_id.to_guild_cached(&ctx) {
 | 
					        if let Some(guild) = g_id.to_guild_cached(&ctx).await {
 | 
				
			||||||
            content.substitute(guild);
 | 
					            content.substitute(guild);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -1313,19 +1551,11 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    let db_channel_id = match scope_id {
 | 
					    let db_channel_id = match scope_id {
 | 
				
			||||||
        ReminderScope::User(user_id) => {
 | 
					        ReminderScope::User(user_id) => {
 | 
				
			||||||
            if let Ok(user) = UserId(*user_id).to_user(&ctx).await {
 | 
					            let user = UserId(*user_id).to_user(&ctx).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let user_data = UserData::from_user(&user, &ctx, &pool).await.unwrap();
 | 
					            let user_data = UserData::from_user(&user, &ctx, &pool).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if let Some(guild_id) = guild_id {
 | 
					 | 
				
			||||||
                    if guild_id.member(&ctx, user).await.is_err() {
 | 
					 | 
				
			||||||
                        return Err(ReminderError::InvalidTag);
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            user_data.dm_channel
 | 
					            user_data.dm_channel
 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return Err(ReminderError::InvalidTag);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        ReminderScope::Channel(channel_id) => {
 | 
					        ReminderScope::Channel(channel_id) => {
 | 
				
			||||||
@@ -1380,8 +1610,9 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
				
			|||||||
                            .as_secs() as i64;
 | 
					                            .as_secs() as i64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        if time >= unix_time - 10 {
 | 
					                        if time >= unix_time - 10 {
 | 
				
			||||||
                            let uid = generate_uid();
 | 
					                            if time > unix_time + *MAX_TIME {
 | 
				
			||||||
 | 
					                                Err(ReminderError::LongTime)
 | 
				
			||||||
 | 
					                            } else {
 | 
				
			||||||
                                sqlx::query!(
 | 
					                                sqlx::query!(
 | 
				
			||||||
                                    "
 | 
					                                    "
 | 
				
			||||||
INSERT INTO reminders (
 | 
					INSERT INTO reminders (
 | 
				
			||||||
@@ -1393,7 +1624,7 @@ INSERT INTO reminders (
 | 
				
			|||||||
    channel_id,
 | 
					    channel_id,
 | 
				
			||||||
    `utc_time`,
 | 
					    `utc_time`,
 | 
				
			||||||
    expires,
 | 
					    expires,
 | 
				
			||||||
    `interval_seconds`,
 | 
					    `interval`,
 | 
				
			||||||
    set_by
 | 
					    set_by
 | 
				
			||||||
) VALUES (
 | 
					) VALUES (
 | 
				
			||||||
    ?,
 | 
					    ?,
 | 
				
			||||||
@@ -1408,13 +1639,13 @@ INSERT INTO reminders (
 | 
				
			|||||||
    (SELECT id FROM users WHERE user = ? LIMIT 1)
 | 
					    (SELECT id FROM users WHERE user = ? LIMIT 1)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
                            ",
 | 
					                            ",
 | 
				
			||||||
                                uid,
 | 
					                                    generate_uid(),
 | 
				
			||||||
                                    content.content,
 | 
					                                    content.content,
 | 
				
			||||||
                                    content.tts,
 | 
					                                    content.tts,
 | 
				
			||||||
                                    content.attachment,
 | 
					                                    content.attachment,
 | 
				
			||||||
                                    content.attachment_name,
 | 
					                                    content.attachment_name,
 | 
				
			||||||
                                    db_channel_id,
 | 
					                                    db_channel_id,
 | 
				
			||||||
                                time,
 | 
					                                    time as u32,
 | 
				
			||||||
                                    expires,
 | 
					                                    expires,
 | 
				
			||||||
                                    interval,
 | 
					                                    interval,
 | 
				
			||||||
                                    user_id
 | 
					                                    user_id
 | 
				
			||||||
@@ -1423,9 +1654,8 @@ INSERT INTO reminders (
 | 
				
			|||||||
                                .await
 | 
					                                .await
 | 
				
			||||||
                                .unwrap();
 | 
					                                .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            let reminder = Reminder::from_uid(ctx, uid).await.unwrap();
 | 
					                                Ok(())
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
                            Ok(reminder)
 | 
					 | 
				
			||||||
                        } else if time < 0 {
 | 
					                        } else if time < 0 {
 | 
				
			||||||
                            // case required for if python returns -1
 | 
					                            // case required for if python returns -1
 | 
				
			||||||
                            Err(ReminderError::InvalidTime)
 | 
					                            Err(ReminderError::InvalidTime)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,10 +12,8 @@ use serenity::{
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::fmt;
 | 
					use std::fmt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
    command_help, get_ctx_data,
 | 
					use crate::{command_help, get_ctx_data, models::UserData};
 | 
				
			||||||
    models::{user_data::UserData, CtxGuildData},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
use sqlx::MySqlPool;
 | 
					use sqlx::MySqlPool;
 | 
				
			||||||
use std::convert::TryFrom;
 | 
					use std::convert::TryFrom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -274,12 +272,7 @@ DELETE FROM todos WHERE user_id = (SELECT id FROM users WHERE user = ?) AND guil
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                self.add(extra, pool).await.unwrap();
 | 
					                self.add(extra, pool).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let _ = msg
 | 
					                let _ = msg.channel_id.say(&ctx, content).await;
 | 
				
			||||||
                    .channel_id
 | 
					 | 
				
			||||||
                    .send_message(&ctx, |m| {
 | 
					 | 
				
			||||||
                        m.content(content).allowed_mentions(|m| m.empty_parse())
 | 
					 | 
				
			||||||
                    })
 | 
					 | 
				
			||||||
                    .await;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            SubCommand::Remove => {
 | 
					            SubCommand::Remove => {
 | 
				
			||||||
@@ -291,12 +284,7 @@ DELETE FROM todos WHERE user_id = (SELECT id FROM users WHERE user = ?) AND guil
 | 
				
			|||||||
                            1,
 | 
					                            1,
 | 
				
			||||||
                        );
 | 
					                        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let _ = msg
 | 
					                        let _ = msg.channel_id.say(&ctx, content).await;
 | 
				
			||||||
                            .channel_id
 | 
					 | 
				
			||||||
                            .send_message(&ctx, |m| {
 | 
					 | 
				
			||||||
                                m.content(content).allowed_mentions(|m| m.empty_parse())
 | 
					 | 
				
			||||||
                            })
 | 
					 | 
				
			||||||
                            .await;
 | 
					 | 
				
			||||||
                    } else {
 | 
					                    } else {
 | 
				
			||||||
                        let _ = msg
 | 
					                        let _ = msg
 | 
				
			||||||
                            .channel_id
 | 
					                            .channel_id
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,7 +51,7 @@ lazy_static! {
 | 
				
			|||||||
                .split(',')
 | 
					                .split(',')
 | 
				
			||||||
                .filter_map(|item| { item.parse::<u64>().ok() })
 | 
					                .filter_map(|item| { item.parse::<u64>().ok() })
 | 
				
			||||||
                .collect::<Vec<u64>>())
 | 
					                .collect::<Vec<u64>>())
 | 
				
			||||||
            .unwrap_or_else(|_| Vec::new())
 | 
					            .unwrap_or_else(|_| vec![])
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    pub static ref CNC_GUILD: Option<u64> = env::var("CNC_GUILD")
 | 
					    pub static ref CNC_GUILD: Option<u64> = env::var("CNC_GUILD")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,7 +8,7 @@ use serenity::{
 | 
				
			|||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        channel::{Channel, GuildChannel, Message},
 | 
					        channel::{Channel, GuildChannel, Message},
 | 
				
			||||||
        guild::{Guild, Member},
 | 
					        guild::{Guild, Member},
 | 
				
			||||||
        id::{ChannelId, MessageId},
 | 
					        id::ChannelId,
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    Result as SerenityResult,
 | 
					    Result as SerenityResult,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -19,11 +19,10 @@ use regex::{Match, Regex, RegexBuilder};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use std::{collections::HashMap, fmt};
 | 
					use std::{collections::HashMap, fmt};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::language_manager::LanguageManager;
 | 
				
			||||||
    language_manager::LanguageManager,
 | 
					use crate::models::{CtxGuildData, GuildData, UserData};
 | 
				
			||||||
    models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData, CtxGuildData},
 | 
					use crate::{models::ChannelData, LimitExecutors, SQLPool};
 | 
				
			||||||
    LimitExecutors, SQLPool,
 | 
					use serenity::model::id::MessageId;
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, String) -> BoxFuture<'fut, ()>;
 | 
					type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, String) -> BoxFuture<'fut, ()>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -240,7 +239,7 @@ impl RegexFramework {
 | 
				
			|||||||
                let mut command_names_vec =
 | 
					                let mut command_names_vec =
 | 
				
			||||||
                    self.commands.keys().map(|k| &k[..]).collect::<Vec<&str>>();
 | 
					                    self.commands.keys().map(|k| &k[..]).collect::<Vec<&str>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                command_names_vec.sort_unstable_by_key(|a| a.len());
 | 
					                command_names_vec.sort_unstable_by(|a, b| b.len().cmp(&a.len()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                command_names = command_names_vec.join("|");
 | 
					                command_names = command_names_vec.join("|");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -276,7 +275,7 @@ impl RegexFramework {
 | 
				
			|||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .collect::<Vec<&str>>();
 | 
					                    .collect::<Vec<&str>>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                command_names_vec.sort_unstable_by_key(|a| a.len());
 | 
					                command_names_vec.sort_unstable_by(|a, b| b.len().cmp(&a.len()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                dm_command_names = command_names_vec.join("|");
 | 
					                dm_command_names = command_names_vec.join("|");
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -312,10 +311,10 @@ impl Framework for RegexFramework {
 | 
				
			|||||||
            guild: &Guild,
 | 
					            guild: &Guild,
 | 
				
			||||||
            channel: &GuildChannel,
 | 
					            channel: &GuildChannel,
 | 
				
			||||||
        ) -> SerenityResult<PermissionCheck> {
 | 
					        ) -> SerenityResult<PermissionCheck> {
 | 
				
			||||||
            let user_id = ctx.cache.current_user_id();
 | 
					            let user_id = ctx.cache.current_user_id().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let guild_perms = guild.member_permissions(&ctx, user_id).await?;
 | 
					            let guild_perms = guild.member_permissions(&ctx, user_id).await?;
 | 
				
			||||||
            let channel_perms = channel.permissions_for_user(ctx, user_id)?;
 | 
					            let channel_perms = channel.permissions_for_user(ctx, user_id).await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let basic_perms = channel_perms.send_messages();
 | 
					            let basic_perms = channel_perms.send_messages();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -347,8 +346,8 @@ impl Framework for RegexFramework {
 | 
				
			|||||||
        if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
 | 
					        if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            // Guild Command
 | 
					            // Guild Command
 | 
				
			||||||
            if let (Some(guild), Ok(Channel::Guild(channel))) =
 | 
					            if let (Some(guild), Some(Channel::Guild(channel))) =
 | 
				
			||||||
                (msg.guild(&ctx), msg.channel(&ctx).await)
 | 
					                (msg.guild(&ctx).await, msg.channel(&ctx).await)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                let data = ctx.data.read().await;
 | 
					                let data = ctx.data.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -399,14 +398,12 @@ impl Framework for RegexFramework {
 | 
				
			|||||||
                                            {
 | 
					                                            {
 | 
				
			||||||
                                                let guild_id = guild.id.as_u64().to_owned();
 | 
					                                                let guild_id = guild.id.as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                                GuildData::from_guild(guild, &pool)
 | 
					                                                GuildData::from_guild(guild, &pool).await.expect(
 | 
				
			||||||
                                                    .await
 | 
					                                                    &format!(
 | 
				
			||||||
                                                    .unwrap_or_else(|_| {
 | 
					 | 
				
			||||||
                                                        panic!(
 | 
					 | 
				
			||||||
                                                        "Failed to create new guild object for {}",
 | 
					                                                        "Failed to create new guild object for {}",
 | 
				
			||||||
                                                        guild_id
 | 
					                                                        guild_id
 | 
				
			||||||
                                                    )
 | 
					                                                    ),
 | 
				
			||||||
                                                    });
 | 
					                                                );
 | 
				
			||||||
                                            }
 | 
					                                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                            if msg.id == MessageId(0)
 | 
					                                            if msg.id == MessageId(0)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ pub struct LanguageManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
impl LanguageManager {
 | 
					impl LanguageManager {
 | 
				
			||||||
    pub fn from_compiled(content: &'static str) -> Result<Self, Box<dyn Error + Send + Sync>> {
 | 
					    pub fn from_compiled(content: &'static str) -> Result<Self, Box<dyn Error + Send + Sync>> {
 | 
				
			||||||
        let new: Self = from_str(content)?;
 | 
					        let new: Self = from_str(content.as_ref())?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        Ok(new)
 | 
					        Ok(new)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -23,13 +23,13 @@ impl LanguageManager {
 | 
				
			|||||||
        self.strings
 | 
					        self.strings
 | 
				
			||||||
            .get(language)
 | 
					            .get(language)
 | 
				
			||||||
            .map(|sm| sm.get(name))
 | 
					            .map(|sm| sm.get(name))
 | 
				
			||||||
            .unwrap_or_else(|| panic!(r#"Language does not exist: "{}""#, language))
 | 
					            .expect(&format!(r#"Language does not exist: "{}""#, language))
 | 
				
			||||||
            .unwrap_or_else(|| {
 | 
					            .unwrap_or_else(|| {
 | 
				
			||||||
                self.strings
 | 
					                self.strings
 | 
				
			||||||
                    .get(&*LOCAL_LANGUAGE)
 | 
					                    .get(&*LOCAL_LANGUAGE)
 | 
				
			||||||
                    .map(|sm| {
 | 
					                    .map(|sm| {
 | 
				
			||||||
                        sm.get(name)
 | 
					                        sm.get(name)
 | 
				
			||||||
                            .unwrap_or_else(|| panic!(r#"String does not exist: "{}""#, name))
 | 
					                            .expect(&format!(r#"String does not exist: "{}""#, name))
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .expect("LOCAL_LANGUAGE is not available")
 | 
					                    .expect("LOCAL_LANGUAGE is not available")
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										53
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										53
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -11,15 +11,15 @@ mod time_parser;
 | 
				
			|||||||
use serenity::{
 | 
					use serenity::{
 | 
				
			||||||
    async_trait,
 | 
					    async_trait,
 | 
				
			||||||
    cache::Cache,
 | 
					    cache::Cache,
 | 
				
			||||||
    client::Client,
 | 
					    client::{bridge::gateway::GatewayIntents, Client},
 | 
				
			||||||
    futures::TryFutureExt,
 | 
					    futures::TryFutureExt,
 | 
				
			||||||
    http::{client::Http, CacheHttp},
 | 
					    http::{client::Http, CacheHttp},
 | 
				
			||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        channel::GuildChannel,
 | 
					        channel::GuildChannel,
 | 
				
			||||||
        channel::Message,
 | 
					        channel::Message,
 | 
				
			||||||
        guild::Guild,
 | 
					        guild::{Guild, GuildUnavailable},
 | 
				
			||||||
        id::{GuildId, UserId},
 | 
					        id::{GuildId, UserId},
 | 
				
			||||||
        interactions::Interaction,
 | 
					        interactions::{Interaction, InteractionData, InteractionType},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    prelude::{Context, EventHandler, TypeMapKey},
 | 
					    prelude::{Context, EventHandler, TypeMapKey},
 | 
				
			||||||
    utils::shard_id,
 | 
					    utils::shard_id,
 | 
				
			||||||
@@ -36,7 +36,7 @@ use crate::{
 | 
				
			|||||||
    consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
 | 
					    consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
 | 
				
			||||||
    framework::RegexFramework,
 | 
					    framework::RegexFramework,
 | 
				
			||||||
    language_manager::LanguageManager,
 | 
					    language_manager::LanguageManager,
 | 
				
			||||||
    models::{guild_data::GuildData, user_data::UserData},
 | 
					    models::GuildData,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use inflector::Inflector;
 | 
					use inflector::Inflector;
 | 
				
			||||||
@@ -46,10 +46,9 @@ use dashmap::DashMap;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use tokio::sync::RwLock;
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::models::UserData;
 | 
				
			||||||
use chrono::Utc;
 | 
					use chrono::Utc;
 | 
				
			||||||
use chrono_tz::Tz;
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
use serenity::model::gateway::GatewayIntents;
 | 
					 | 
				
			||||||
use serenity::model::guild::UnavailableGuild;
 | 
					 | 
				
			||||||
use serenity::model::prelude::{
 | 
					use serenity::model::prelude::{
 | 
				
			||||||
    InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
 | 
					    InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
@@ -180,20 +179,20 @@ DELETE FROM channels WHERE channel = ?
 | 
				
			|||||||
                    .cloned()
 | 
					                    .cloned()
 | 
				
			||||||
                    .expect("Could not get SQLPool from data");
 | 
					                    .expect("Could not get SQLPool from data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                GuildData::from_guild(guild, &pool)
 | 
					                GuildData::from_guild(guild, &pool).await.expect(&format!(
 | 
				
			||||||
                    .await
 | 
					                    "Failed to create new guild object for {}",
 | 
				
			||||||
                    .unwrap_or_else(|_| {
 | 
					                    guild_id
 | 
				
			||||||
                        panic!("Failed to create new guild object for {}", guild_id)
 | 
					                ));
 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
 | 
					            if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
 | 
				
			||||||
                let shard_count = ctx.cache.shard_count();
 | 
					                let shard_count = ctx.cache.shard_count().await;
 | 
				
			||||||
                let current_shard_id = shard_id(guild_id, shard_count);
 | 
					                let current_shard_id = shard_id(guild_id, shard_count);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let guild_count = ctx
 | 
					                let guild_count = ctx
 | 
				
			||||||
                    .cache
 | 
					                    .cache
 | 
				
			||||||
                    .guilds()
 | 
					                    .guilds()
 | 
				
			||||||
 | 
					                    .await
 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
 | 
					                    .filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
 | 
				
			||||||
                    .count() as u64;
 | 
					                    .count() as u64;
 | 
				
			||||||
@@ -215,7 +214,7 @@ DELETE FROM channels WHERE channel = ?
 | 
				
			|||||||
                    .post(
 | 
					                    .post(
 | 
				
			||||||
                        format!(
 | 
					                        format!(
 | 
				
			||||||
                            "https://top.gg/api/bots/{}/stats",
 | 
					                            "https://top.gg/api/bots/{}/stats",
 | 
				
			||||||
                            ctx.cache.current_user_id().as_u64()
 | 
					                            ctx.cache.current_user_id().await.as_u64()
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        .as_str(),
 | 
					                        .as_str(),
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
@@ -231,12 +230,7 @@ DELETE FROM channels WHERE channel = ?
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async fn guild_delete(
 | 
					    async fn guild_delete(&self, ctx: Context, guild: GuildUnavailable, _guild: Option<Guild>) {
 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        ctx: Context,
 | 
					 | 
				
			||||||
        deleted_guild: UnavailableGuild,
 | 
					 | 
				
			||||||
        _guild: Option<Guild>,
 | 
					 | 
				
			||||||
    ) {
 | 
					 | 
				
			||||||
        let pool = ctx
 | 
					        let pool = ctx
 | 
				
			||||||
            .data
 | 
					            .data
 | 
				
			||||||
            .read()
 | 
					            .read()
 | 
				
			||||||
@@ -252,13 +246,13 @@ DELETE FROM channels WHERE channel = ?
 | 
				
			|||||||
            .get::<GuildDataCache>()
 | 
					            .get::<GuildDataCache>()
 | 
				
			||||||
            .cloned()
 | 
					            .cloned()
 | 
				
			||||||
            .unwrap();
 | 
					            .unwrap();
 | 
				
			||||||
        guild_data_cache.remove(&deleted_guild.id);
 | 
					        guild_data_cache.remove(&guild.id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        sqlx::query!(
 | 
					        sqlx::query!(
 | 
				
			||||||
            "
 | 
					            "
 | 
				
			||||||
DELETE FROM guilds WHERE guild = ?
 | 
					DELETE FROM guilds WHERE guild = ?
 | 
				
			||||||
            ",
 | 
					            ",
 | 
				
			||||||
            deleted_guild.id.as_u64()
 | 
					            guild.id.as_u64()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .execute(&pool)
 | 
					        .execute(&pool)
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
@@ -268,10 +262,13 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
 | 
					    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
 | 
				
			||||||
        let (pool, lm) = get_ctx_data(&&ctx).await;
 | 
					        let (pool, lm) = get_ctx_data(&&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        match interaction {
 | 
					        match interaction.kind {
 | 
				
			||||||
            Interaction::MessageComponent(interaction) => {
 | 
					            InteractionType::ApplicationCommand => {}
 | 
				
			||||||
                if let Some(member) = interaction.clone().member {
 | 
					            InteractionType::MessageComponent => {
 | 
				
			||||||
                    let data = interaction.data.clone();
 | 
					                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:") {
 | 
					                    if data.custom_id.starts_with("timezone:") {
 | 
				
			||||||
                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
					                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
				
			||||||
@@ -299,7 +296,7 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                                            .replacen("{timezone}", &user_data.timezone, 1)
 | 
					                                            .replacen("{timezone}", &user_data.timezone, 1)
 | 
				
			||||||
                                            .replacen(
 | 
					                                            .replacen(
 | 
				
			||||||
                                                "{time}",
 | 
					                                                "{time}",
 | 
				
			||||||
                                                &now.format("%H:%M").to_string(),
 | 
					                                                &now.format(user_data.meridian().fmt_str_short()).to_string(),
 | 
				
			||||||
                                                1,
 | 
					                                                1,
 | 
				
			||||||
                                            );
 | 
					                                            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -336,7 +333,6 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
                                                    lm.get(&user_data.language, "lang/set_p"),
 | 
					                                                    lm.get(&user_data.language, "lang/set_p"),
 | 
				
			||||||
                                                )
 | 
					                                                )
 | 
				
			||||||
                                            })
 | 
					                                            })
 | 
				
			||||||
                                            .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
 | 
					 | 
				
			||||||
                                        })
 | 
					                                        })
 | 
				
			||||||
                                })
 | 
					                                })
 | 
				
			||||||
                                .await;
 | 
					                                .await;
 | 
				
			||||||
@@ -405,6 +401,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
				
			|||||||
        .add_command("blacklist", &moderation_cmds::BLACKLIST_COMMAND)
 | 
					        .add_command("blacklist", &moderation_cmds::BLACKLIST_COMMAND)
 | 
				
			||||||
        .add_command("restrict", &moderation_cmds::RESTRICT_COMMAND)
 | 
					        .add_command("restrict", &moderation_cmds::RESTRICT_COMMAND)
 | 
				
			||||||
        .add_command("timezone", &moderation_cmds::TIMEZONE_COMMAND)
 | 
					        .add_command("timezone", &moderation_cmds::TIMEZONE_COMMAND)
 | 
				
			||||||
 | 
					        .add_command("meridian", &moderation_cmds::CHANGE_MERIDIAN_COMMAND)
 | 
				
			||||||
        .add_command("prefix", &moderation_cmds::PREFIX_COMMAND)
 | 
					        .add_command("prefix", &moderation_cmds::PREFIX_COMMAND)
 | 
				
			||||||
        .add_command("lang", &moderation_cmds::LANGUAGE_COMMAND)
 | 
					        .add_command("lang", &moderation_cmds::LANGUAGE_COMMAND)
 | 
				
			||||||
        .add_command("pause", &reminder_cmds::PAUSE_COMMAND)
 | 
					        .add_command("pause", &reminder_cmds::PAUSE_COMMAND)
 | 
				
			||||||
@@ -539,7 +536,7 @@ pub async fn check_subscription_on_message(
 | 
				
			|||||||
    msg: &Message,
 | 
					    msg: &Message,
 | 
				
			||||||
) -> bool {
 | 
					) -> bool {
 | 
				
			||||||
    check_subscription(&cache_http, &msg.author).await
 | 
					    check_subscription(&cache_http, &msg.author).await
 | 
				
			||||||
        || if let Some(guild) = msg.guild(&cache_http) {
 | 
					        || if let Some(guild) = msg.guild(&cache_http).await {
 | 
				
			||||||
            check_subscription(&cache_http, guild.owner_id).await
 | 
					            check_subscription(&cache_http, guild.owner_id).await
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            false
 | 
					            false
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										452
									
								
								src/models.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										452
									
								
								src/models.rs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,452 @@
 | 
				
			|||||||
 | 
					use serenity::{
 | 
				
			||||||
 | 
					    async_trait,
 | 
				
			||||||
 | 
					    http::CacheHttp,
 | 
				
			||||||
 | 
					    model::{
 | 
				
			||||||
 | 
					        channel::Channel,
 | 
				
			||||||
 | 
					        guild::Guild,
 | 
				
			||||||
 | 
					        id::{GuildId, UserId},
 | 
				
			||||||
 | 
					        user::User,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    prelude::Context,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use sqlx::MySqlPool;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use chrono::NaiveDateTime;
 | 
				
			||||||
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use log::error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::{
 | 
				
			||||||
 | 
					    consts::{DEFAULT_PREFIX, LOCAL_LANGUAGE, LOCAL_TIMEZONE},
 | 
				
			||||||
 | 
					    GuildDataCache, SQLPool,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use std::sync::Arc;
 | 
				
			||||||
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					pub trait CtxGuildData {
 | 
				
			||||||
 | 
					    async fn guild_data<G: Into<GuildId> + Send + Sync>(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        guild_id: G,
 | 
				
			||||||
 | 
					    ) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl CtxGuildData for Context {
 | 
				
			||||||
 | 
					    async fn guild_data<G: Into<GuildId> + Send + Sync>(
 | 
				
			||||||
 | 
					        &self,
 | 
				
			||||||
 | 
					        guild_id: G,
 | 
				
			||||||
 | 
					    ) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
 | 
				
			||||||
 | 
					        let guild_id = guild_id.into();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let guild = guild_id.to_guild_cached(&self.cache).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let guild_cache = self
 | 
				
			||||||
 | 
					            .data
 | 
				
			||||||
 | 
					            .read()
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .get::<GuildDataCache>()
 | 
				
			||||||
 | 
					            .cloned()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let x = if let Some(guild_data) = guild_cache.get(&guild_id) {
 | 
				
			||||||
 | 
					            Ok(guild_data.clone())
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            let pool = self.data.read().await.get::<SQLPool>().cloned().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            match GuildData::from_guild(guild, &pool).await {
 | 
				
			||||||
 | 
					                Ok(d) => {
 | 
				
			||||||
 | 
					                    let lock = Arc::new(RwLock::new(d));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    guild_cache.insert(guild_id, lock.clone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    Ok(lock)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Err(e) => Err(e),
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        x
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String {
 | 
				
			||||||
 | 
					        if let Some(guild_id) = guild_id {
 | 
				
			||||||
 | 
					            self.guild_data(guild_id)
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .unwrap()
 | 
				
			||||||
 | 
					                .read()
 | 
				
			||||||
 | 
					                .await
 | 
				
			||||||
 | 
					                .prefix
 | 
				
			||||||
 | 
					                .clone()
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            DEFAULT_PREFIX.clone()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct GuildData {
 | 
				
			||||||
 | 
					    pub id: u32,
 | 
				
			||||||
 | 
					    pub name: Option<String>,
 | 
				
			||||||
 | 
					    pub prefix: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl GuildData {
 | 
				
			||||||
 | 
					    pub async fn from_guild(guild: Guild, pool: &MySqlPool) -> Result<Self, sqlx::Error> {
 | 
				
			||||||
 | 
					        let guild_id = guild.id.as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match sqlx::query_as!(
 | 
				
			||||||
 | 
					            Self,
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT id, name, prefix FROM guilds WHERE guild = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            guild_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Ok(mut g) => {
 | 
				
			||||||
 | 
					                g.name = Some(guild.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(g)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(sqlx::Error::RowNotFound) => {
 | 
				
			||||||
 | 
					                sqlx::query!(
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					INSERT INTO guilds (guild, name, prefix) VALUES (?, ?, ?)
 | 
				
			||||||
 | 
					                    ",
 | 
				
			||||||
 | 
					                    guild_id,
 | 
				
			||||||
 | 
					                    guild.name,
 | 
				
			||||||
 | 
					                    *DEFAULT_PREFIX
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .execute(&pool.clone())
 | 
				
			||||||
 | 
					                .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(sqlx::query_as!(
 | 
				
			||||||
 | 
					                    Self,
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					SELECT id, name, prefix FROM guilds WHERE guild = ?
 | 
				
			||||||
 | 
					                    ",
 | 
				
			||||||
 | 
					                    guild_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .fetch_one(pool)
 | 
				
			||||||
 | 
					                .await?)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                error!("Unexpected error in guild query: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Err(e)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					UPDATE guilds SET name = ?, prefix = ? WHERE id = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            self.name,
 | 
				
			||||||
 | 
					            self.prefix,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct ChannelData {
 | 
				
			||||||
 | 
					    pub id: u32,
 | 
				
			||||||
 | 
					    pub name: Option<String>,
 | 
				
			||||||
 | 
					    pub nudge: i16,
 | 
				
			||||||
 | 
					    pub blacklisted: bool,
 | 
				
			||||||
 | 
					    pub webhook_id: Option<u64>,
 | 
				
			||||||
 | 
					    pub webhook_token: Option<String>,
 | 
				
			||||||
 | 
					    pub paused: bool,
 | 
				
			||||||
 | 
					    pub paused_until: Option<NaiveDateTime>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl ChannelData {
 | 
				
			||||||
 | 
					    pub async fn from_channel(
 | 
				
			||||||
 | 
					        channel: Channel,
 | 
				
			||||||
 | 
					        pool: &MySqlPool,
 | 
				
			||||||
 | 
					    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
 | 
				
			||||||
 | 
					        let channel_id = channel.id().as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if let Ok(c) = sqlx::query_as_unchecked!(Self,
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
 | 
				
			||||||
 | 
					            ", channel_id)
 | 
				
			||||||
 | 
					            .fetch_one(pool)
 | 
				
			||||||
 | 
					            .await {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Ok(c)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            let props = channel.guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            let (guild_id, channel_name) = if let Some((a, b)) = props {
 | 
				
			||||||
 | 
					                (Some(a), Some(b))
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					                (None, None)
 | 
				
			||||||
 | 
					            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            sqlx::query!(
 | 
				
			||||||
 | 
					                "
 | 
				
			||||||
 | 
					INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))
 | 
				
			||||||
 | 
					                ", channel_id, channel_name, guild_id)
 | 
				
			||||||
 | 
					                .execute(&pool.clone())
 | 
				
			||||||
 | 
					                .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Ok(sqlx::query_as_unchecked!(Self,
 | 
				
			||||||
 | 
					                "
 | 
				
			||||||
 | 
					SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
 | 
				
			||||||
 | 
					                ", channel_id)
 | 
				
			||||||
 | 
					                .fetch_one(pool)
 | 
				
			||||||
 | 
					                .await?)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					UPDATE channels SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, paused = ?, paused_until = ? WHERE id = ?
 | 
				
			||||||
 | 
					            ", self.name, self.nudge, self.blacklisted, self.webhook_id, self.webhook_token, self.paused, self.paused_until, self.id)
 | 
				
			||||||
 | 
					            .execute(pool)
 | 
				
			||||||
 | 
					            .await.unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct UserData {
 | 
				
			||||||
 | 
					    pub id: u32,
 | 
				
			||||||
 | 
					    pub user: u64,
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub dm_channel: u32,
 | 
				
			||||||
 | 
					    pub language: String,
 | 
				
			||||||
 | 
					    pub timezone: String,
 | 
				
			||||||
 | 
					    pub meridian_time: bool,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct MeridianType(bool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl MeridianType {
 | 
				
			||||||
 | 
					    pub fn fmt_str(&self) -> &str {
 | 
				
			||||||
 | 
					        if self.0 {
 | 
				
			||||||
 | 
					            "%Y-%m-%d %I:%M:%S %p"
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            "%Y-%m-%d %H:%M:%S"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn fmt_str_short(&self) -> &str {
 | 
				
			||||||
 | 
					        if self.0 {
 | 
				
			||||||
 | 
					            "%I:%M %p"
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					            "%H:%M"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl UserData {
 | 
				
			||||||
 | 
					    pub async fn language_of<U>(user: U, pool: &MySqlPool) -> String
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        U: Into<UserId>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let user_id = user.into().as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT language FROM users WHERE user = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            user_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Ok(r) => r.language,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(_) => LOCAL_LANGUAGE.clone(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn timezone_of<U>(user: U, pool: &MySqlPool) -> Tz
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        U: Into<UserId>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let user_id = user.into().as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT timezone FROM users WHERE user = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            user_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Ok(r) => r.timezone,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(_) => LOCAL_TIMEZONE.clone(),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        .parse()
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn meridian_of<U>(user: U, pool: &MySqlPool) -> MeridianType
 | 
				
			||||||
 | 
					    where
 | 
				
			||||||
 | 
					        U: Into<UserId>,
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        let user_id = user.into().as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT meridian_time FROM users WHERE user = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            user_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Ok(r) => MeridianType(r.meridian_time != 0),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(_) => MeridianType(false),
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn from_user(
 | 
				
			||||||
 | 
					        user: &User,
 | 
				
			||||||
 | 
					        ctx: impl CacheHttp,
 | 
				
			||||||
 | 
					        pool: &MySqlPool,
 | 
				
			||||||
 | 
					    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
 | 
				
			||||||
 | 
					        let user_id = user.id.as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					            Self,
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT id, user, name, dm_channel, IF(language IS NULL, ?, language) AS language, IF(timezone IS NULL, ?, timezone) AS timezone, meridian_time FROM users WHERE user = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            *LOCAL_LANGUAGE, *LOCAL_TIMEZONE, user_id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            Ok(c) => Ok(c),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(sqlx::Error::RowNotFound) => {
 | 
				
			||||||
 | 
					                let dm_channel = user.create_dm_channel(ctx).await?;
 | 
				
			||||||
 | 
					                let dm_id = dm_channel.id.as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                let pool_c = pool.clone();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                sqlx::query!(
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					INSERT IGNORE INTO channels (channel) VALUES (?)
 | 
				
			||||||
 | 
					                    ",
 | 
				
			||||||
 | 
					                    dm_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .execute(&pool_c)
 | 
				
			||||||
 | 
					                .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                sqlx::query!(
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					INSERT INTO users (user, name, dm_channel, language, timezone) VALUES (?, ?, (SELECT id FROM channels WHERE channel = ?), ?, ?)
 | 
				
			||||||
 | 
					                    ", user_id, user.name, dm_id, *LOCAL_LANGUAGE, *LOCAL_TIMEZONE)
 | 
				
			||||||
 | 
					                    .execute(&pool_c)
 | 
				
			||||||
 | 
					                    .await?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Ok(sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					                    Self,
 | 
				
			||||||
 | 
					                    "
 | 
				
			||||||
 | 
					SELECT id, user, name, dm_channel, language, timezone, meridian_time FROM users WHERE user = ?
 | 
				
			||||||
 | 
					                    ",
 | 
				
			||||||
 | 
					                    user_id
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					                .fetch_one(pool)
 | 
				
			||||||
 | 
					                .await?)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            Err(e) => {
 | 
				
			||||||
 | 
					                error!("Error querying for user: {:?}", e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                Err(Box::new(e))
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					UPDATE users SET name = ?, language = ?, timezone = ?, meridian_time = ? WHERE id = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            self.name,
 | 
				
			||||||
 | 
					            self.language,
 | 
				
			||||||
 | 
					            self.timezone,
 | 
				
			||||||
 | 
					            self.meridian_time,
 | 
				
			||||||
 | 
					            self.id
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn timezone(&self) -> Tz {
 | 
				
			||||||
 | 
					        self.timezone.parse().unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub fn meridian(&self) -> MeridianType {
 | 
				
			||||||
 | 
					        MeridianType(self.meridian_time)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct Timer {
 | 
				
			||||||
 | 
					    pub name: String,
 | 
				
			||||||
 | 
					    pub start_time: NaiveDateTime,
 | 
				
			||||||
 | 
					    pub owner: u64,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl Timer {
 | 
				
			||||||
 | 
					    pub async fn from_owner(owner: u64, pool: &MySqlPool) -> Vec<Self> {
 | 
				
			||||||
 | 
					        sqlx::query_as_unchecked!(
 | 
				
			||||||
 | 
					            Timer,
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT name, start_time, owner FROM timers WHERE owner = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            owner
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_all(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn count_from_owner(owner: u64, pool: &MySqlPool) -> u32 {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					SELECT COUNT(1) as count FROM timers WHERE owner = ?
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            owner
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .fetch_one(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap()
 | 
				
			||||||
 | 
					        .count as u32
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    pub async fn create(name: &str, owner: u64, pool: &MySqlPool) {
 | 
				
			||||||
 | 
					        sqlx::query!(
 | 
				
			||||||
 | 
					            "
 | 
				
			||||||
 | 
					INSERT INTO timers (name, owner) VALUES (?, ?)
 | 
				
			||||||
 | 
					            ",
 | 
				
			||||||
 | 
					            name,
 | 
				
			||||||
 | 
					            owner
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .execute(pool)
 | 
				
			||||||
 | 
					        .await
 | 
				
			||||||
 | 
					        .unwrap();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,67 +0,0 @@
 | 
				
			|||||||
use serenity::model::channel::Channel;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use sqlx::MySqlPool;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct ChannelData {
 | 
					 | 
				
			||||||
    pub id: u32,
 | 
					 | 
				
			||||||
    pub name: Option<String>,
 | 
					 | 
				
			||||||
    pub nudge: i16,
 | 
					 | 
				
			||||||
    pub blacklisted: bool,
 | 
					 | 
				
			||||||
    pub webhook_id: Option<u64>,
 | 
					 | 
				
			||||||
    pub webhook_token: Option<String>,
 | 
					 | 
				
			||||||
    pub paused: bool,
 | 
					 | 
				
			||||||
    pub paused_until: Option<NaiveDateTime>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl ChannelData {
 | 
					 | 
				
			||||||
    pub async fn from_channel(
 | 
					 | 
				
			||||||
        channel: Channel,
 | 
					 | 
				
			||||||
        pool: &MySqlPool,
 | 
					 | 
				
			||||||
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
 | 
					 | 
				
			||||||
        let channel_id = channel.id().as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Ok(c) = sqlx::query_as_unchecked!(Self,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
 | 
					 | 
				
			||||||
            ", channel_id)
 | 
					 | 
				
			||||||
            .fetch_one(pool)
 | 
					 | 
				
			||||||
            .await {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Ok(c)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        else {
 | 
					 | 
				
			||||||
            let props = channel.guild().map(|g| (g.guild_id.as_u64().to_owned(), g.name));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            let (guild_id, channel_name) = if let Some((a, b)) = props {
 | 
					 | 
				
			||||||
                (Some(a), Some(b))
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                (None, None)
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            sqlx::query!(
 | 
					 | 
				
			||||||
                "
 | 
					 | 
				
			||||||
INSERT IGNORE INTO channels (channel, name, guild_id) VALUES (?, ?, (SELECT id FROM guilds WHERE guild = ?))
 | 
					 | 
				
			||||||
                ", channel_id, channel_name, guild_id)
 | 
					 | 
				
			||||||
                .execute(&pool.clone())
 | 
					 | 
				
			||||||
                .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Ok(sqlx::query_as_unchecked!(Self,
 | 
					 | 
				
			||||||
                "
 | 
					 | 
				
			||||||
SELECT id, name, nudge, blacklisted, webhook_id, webhook_token, paused, paused_until FROM channels WHERE channel = ?
 | 
					 | 
				
			||||||
                ", channel_id)
 | 
					 | 
				
			||||||
                .fetch_one(pool)
 | 
					 | 
				
			||||||
                .await?)
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
					 | 
				
			||||||
        sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
UPDATE channels SET name = ?, nudge = ?, blacklisted = ?, webhook_id = ?, webhook_token = ?, paused = ?, paused_until = ? WHERE id = ?
 | 
					 | 
				
			||||||
            ", self.name, self.nudge, self.blacklisted, self.webhook_id, self.webhook_token, self.paused, self.paused_until, self.id)
 | 
					 | 
				
			||||||
            .execute(pool)
 | 
					 | 
				
			||||||
            .await.unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,79 +0,0 @@
 | 
				
			|||||||
use serenity::model::guild::Guild;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use sqlx::MySqlPool;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use log::error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::consts::DEFAULT_PREFIX;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct GuildData {
 | 
					 | 
				
			||||||
    pub id: u32,
 | 
					 | 
				
			||||||
    pub name: Option<String>,
 | 
					 | 
				
			||||||
    pub prefix: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl GuildData {
 | 
					 | 
				
			||||||
    pub async fn from_guild(guild: Guild, pool: &MySqlPool) -> Result<Self, sqlx::Error> {
 | 
					 | 
				
			||||||
        let guild_id = guild.id.as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match sqlx::query_as!(
 | 
					 | 
				
			||||||
            Self,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT id, name, prefix FROM guilds WHERE guild = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            guild_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Ok(mut g) => {
 | 
					 | 
				
			||||||
                g.name = Some(guild.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok(g)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(sqlx::Error::RowNotFound) => {
 | 
					 | 
				
			||||||
                sqlx::query!(
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
INSERT INTO guilds (guild, name, prefix) VALUES (?, ?, ?)
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                    guild_id,
 | 
					 | 
				
			||||||
                    guild.name,
 | 
					 | 
				
			||||||
                    *DEFAULT_PREFIX
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .execute(&pool.clone())
 | 
					 | 
				
			||||||
                .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok(sqlx::query_as!(
 | 
					 | 
				
			||||||
                    Self,
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
SELECT id, name, prefix FROM guilds WHERE guild = ?
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                    guild_id
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .fetch_one(pool)
 | 
					 | 
				
			||||||
                .await?)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(e) => {
 | 
					 | 
				
			||||||
                error!("Unexpected error in guild query: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(e)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
					 | 
				
			||||||
        sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
UPDATE guilds SET name = ?, prefix = ? WHERE id = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            self.name,
 | 
					 | 
				
			||||||
            self.prefix,
 | 
					 | 
				
			||||||
            self.id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .execute(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,78 +0,0 @@
 | 
				
			|||||||
pub mod channel_data;
 | 
					 | 
				
			||||||
pub mod guild_data;
 | 
					 | 
				
			||||||
pub mod reminder;
 | 
					 | 
				
			||||||
pub mod timer;
 | 
					 | 
				
			||||||
pub mod user_data;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use serenity::{async_trait, model::id::GuildId, prelude::Context};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::{consts::DEFAULT_PREFIX, GuildDataCache, SQLPool};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use guild_data::GuildData;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use std::sync::Arc;
 | 
					 | 
				
			||||||
use tokio::sync::RwLock;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[async_trait]
 | 
					 | 
				
			||||||
pub trait CtxGuildData {
 | 
					 | 
				
			||||||
    async fn guild_data<G: Into<GuildId> + Send + Sync>(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        guild_id: G,
 | 
					 | 
				
			||||||
    ) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[async_trait]
 | 
					 | 
				
			||||||
impl CtxGuildData for Context {
 | 
					 | 
				
			||||||
    async fn guild_data<G: Into<GuildId> + Send + Sync>(
 | 
					 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        guild_id: G,
 | 
					 | 
				
			||||||
    ) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
 | 
					 | 
				
			||||||
        let guild_id = guild_id.into();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let guild = guild_id.to_guild_cached(&self.cache).unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let guild_cache = self
 | 
					 | 
				
			||||||
            .data
 | 
					 | 
				
			||||||
            .read()
 | 
					 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
            .get::<GuildDataCache>()
 | 
					 | 
				
			||||||
            .cloned()
 | 
					 | 
				
			||||||
            .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let x = if let Some(guild_data) = guild_cache.get(&guild_id) {
 | 
					 | 
				
			||||||
            Ok(guild_data.clone())
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            let pool = self.data.read().await.get::<SQLPool>().cloned().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            match GuildData::from_guild(guild, &pool).await {
 | 
					 | 
				
			||||||
                Ok(d) => {
 | 
					 | 
				
			||||||
                    let lock = Arc::new(RwLock::new(d));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    guild_cache.insert(guild_id, lock.clone());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    Ok(lock)
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(e) => Err(e),
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        x
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String {
 | 
					 | 
				
			||||||
        if let Some(guild_id) = guild_id {
 | 
					 | 
				
			||||||
            self.guild_data(guild_id)
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .unwrap()
 | 
					 | 
				
			||||||
                .read()
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .prefix
 | 
					 | 
				
			||||||
                .clone()
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            DEFAULT_PREFIX.clone()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,346 +0,0 @@
 | 
				
			|||||||
use serenity::{
 | 
					 | 
				
			||||||
    client::Context,
 | 
					 | 
				
			||||||
    model::id::{ChannelId, GuildId, UserId},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::{
 | 
					 | 
				
			||||||
    consts::{DAY, HOUR, MINUTE, REGEX_CHANNEL},
 | 
					 | 
				
			||||||
    SQLPool,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use num_integer::Integer;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn longhand_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 mut sections = vec![];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (var, name) in [days, hours, minutes, seconds]
 | 
					 | 
				
			||||||
        .iter()
 | 
					 | 
				
			||||||
        .zip(["days", "hours", "minutes", "seconds"].iter())
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        if *var > 0 {
 | 
					 | 
				
			||||||
            sections.push(format!("{} {}", var, name));
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    sections.join(", ")
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#[derive(Debug)]
 | 
					 | 
				
			||||||
pub struct Reminder {
 | 
					 | 
				
			||||||
    pub id: u32,
 | 
					 | 
				
			||||||
    pub uid: String,
 | 
					 | 
				
			||||||
    pub channel: u64,
 | 
					 | 
				
			||||||
    pub utc_time: NaiveDateTime,
 | 
					 | 
				
			||||||
    pub interval_seconds: Option<u32>,
 | 
					 | 
				
			||||||
    pub expires: Option<NaiveDateTime>,
 | 
					 | 
				
			||||||
    pub enabled: bool,
 | 
					 | 
				
			||||||
    pub content: String,
 | 
					 | 
				
			||||||
    pub embed_description: String,
 | 
					 | 
				
			||||||
    pub set_by: Option<u64>,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Reminder {
 | 
					 | 
				
			||||||
    pub async fn from_uid(ctx: &Context, uid: String) -> Option<Self> {
 | 
					 | 
				
			||||||
        let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
            Self,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT
 | 
					 | 
				
			||||||
    reminders.id,
 | 
					 | 
				
			||||||
    reminders.uid,
 | 
					 | 
				
			||||||
    channels.channel,
 | 
					 | 
				
			||||||
    reminders.utc_time,
 | 
					 | 
				
			||||||
    reminders.interval_seconds,
 | 
					 | 
				
			||||||
    reminders.expires,
 | 
					 | 
				
			||||||
    reminders.enabled,
 | 
					 | 
				
			||||||
    reminders.content,
 | 
					 | 
				
			||||||
    reminders.embed_description,
 | 
					 | 
				
			||||||
    users.user AS set_by
 | 
					 | 
				
			||||||
FROM
 | 
					 | 
				
			||||||
    reminders
 | 
					 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    channels
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.channel_id = channels.id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    users
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.set_by = users.id
 | 
					 | 
				
			||||||
WHERE
 | 
					 | 
				
			||||||
    reminders.uid = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            uid
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(&pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .ok()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn from_channel<C: Into<ChannelId>>(
 | 
					 | 
				
			||||||
        ctx: &Context,
 | 
					 | 
				
			||||||
        channel_id: C,
 | 
					 | 
				
			||||||
        flags: &LookFlags,
 | 
					 | 
				
			||||||
    ) -> Vec<Self> {
 | 
					 | 
				
			||||||
        let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let enabled = if flags.show_disabled { "0,1" } else { "1" };
 | 
					 | 
				
			||||||
        let channel_id = channel_id.into();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
            Self,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT
 | 
					 | 
				
			||||||
    reminders.id,
 | 
					 | 
				
			||||||
    reminders.uid,
 | 
					 | 
				
			||||||
    channels.channel,
 | 
					 | 
				
			||||||
    reminders.utc_time,
 | 
					 | 
				
			||||||
    reminders.interval_seconds,
 | 
					 | 
				
			||||||
    reminders.expires,
 | 
					 | 
				
			||||||
    reminders.enabled,
 | 
					 | 
				
			||||||
    reminders.content,
 | 
					 | 
				
			||||||
    reminders.embed_description,
 | 
					 | 
				
			||||||
    users.user AS set_by
 | 
					 | 
				
			||||||
FROM
 | 
					 | 
				
			||||||
    reminders
 | 
					 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    channels
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.channel_id = channels.id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    users
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.set_by = users.id
 | 
					 | 
				
			||||||
WHERE
 | 
					 | 
				
			||||||
    channels.channel = ? AND
 | 
					 | 
				
			||||||
    FIND_IN_SET(reminders.enabled, ?)
 | 
					 | 
				
			||||||
ORDER BY
 | 
					 | 
				
			||||||
    reminders.utc_time
 | 
					 | 
				
			||||||
LIMIT
 | 
					 | 
				
			||||||
    ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            channel_id.as_u64(),
 | 
					 | 
				
			||||||
            enabled,
 | 
					 | 
				
			||||||
            flags.limit
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_all(&pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn from_guild(ctx: &Context, guild_id: Option<GuildId>, user: UserId) -> Vec<Self> {
 | 
					 | 
				
			||||||
        let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Some(guild_id) = guild_id {
 | 
					 | 
				
			||||||
            let guild_opt = guild_id.to_guild_cached(&ctx);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(guild) = guild_opt {
 | 
					 | 
				
			||||||
                let channels = guild
 | 
					 | 
				
			||||||
                    .channels
 | 
					 | 
				
			||||||
                    .keys()
 | 
					 | 
				
			||||||
                    .into_iter()
 | 
					 | 
				
			||||||
                    .map(|k| k.as_u64().to_string())
 | 
					 | 
				
			||||||
                    .collect::<Vec<String>>()
 | 
					 | 
				
			||||||
                    .join(",");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
                    Self,
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
SELECT
 | 
					 | 
				
			||||||
    reminders.id,
 | 
					 | 
				
			||||||
    reminders.uid,
 | 
					 | 
				
			||||||
    channels.channel,
 | 
					 | 
				
			||||||
    reminders.utc_time,
 | 
					 | 
				
			||||||
    reminders.interval_seconds,
 | 
					 | 
				
			||||||
    reminders.expires,
 | 
					 | 
				
			||||||
    reminders.enabled,
 | 
					 | 
				
			||||||
    reminders.content,
 | 
					 | 
				
			||||||
    reminders.embed_description,
 | 
					 | 
				
			||||||
    users.user AS set_by
 | 
					 | 
				
			||||||
FROM
 | 
					 | 
				
			||||||
    reminders
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    channels
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    channels.id = reminders.channel_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    users
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.set_by = users.id
 | 
					 | 
				
			||||||
WHERE
 | 
					 | 
				
			||||||
    FIND_IN_SET(channels.channel, ?)
 | 
					 | 
				
			||||||
                ",
 | 
					 | 
				
			||||||
                    channels
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .fetch_all(&pool)
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
                    Self,
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
SELECT
 | 
					 | 
				
			||||||
    reminders.id,
 | 
					 | 
				
			||||||
    reminders.uid,
 | 
					 | 
				
			||||||
    channels.channel,
 | 
					 | 
				
			||||||
    reminders.utc_time,
 | 
					 | 
				
			||||||
    reminders.interval_seconds,
 | 
					 | 
				
			||||||
    reminders.expires,
 | 
					 | 
				
			||||||
    reminders.enabled,
 | 
					 | 
				
			||||||
    reminders.content,
 | 
					 | 
				
			||||||
    reminders.embed_description,
 | 
					 | 
				
			||||||
    users.user AS set_by
 | 
					 | 
				
			||||||
FROM
 | 
					 | 
				
			||||||
    reminders
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    channels
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    channels.id = reminders.channel_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    users
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.set_by = users.id
 | 
					 | 
				
			||||||
WHERE
 | 
					 | 
				
			||||||
    channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
					 | 
				
			||||||
                ",
 | 
					 | 
				
			||||||
                    guild_id.as_u64()
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .fetch_all(&pool)
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
                Self,
 | 
					 | 
				
			||||||
                "
 | 
					 | 
				
			||||||
SELECT
 | 
					 | 
				
			||||||
    reminders.id,
 | 
					 | 
				
			||||||
    reminders.uid,
 | 
					 | 
				
			||||||
    channels.channel,
 | 
					 | 
				
			||||||
    reminders.utc_time,
 | 
					 | 
				
			||||||
    reminders.interval_seconds,
 | 
					 | 
				
			||||||
    reminders.expires,
 | 
					 | 
				
			||||||
    reminders.enabled,
 | 
					 | 
				
			||||||
    reminders.content,
 | 
					 | 
				
			||||||
    reminders.embed_description,
 | 
					 | 
				
			||||||
    users.user AS set_by
 | 
					 | 
				
			||||||
FROM
 | 
					 | 
				
			||||||
    reminders
 | 
					 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    channels
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    channels.id = reminders.channel_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    users
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.set_by = users.id
 | 
					 | 
				
			||||||
WHERE
 | 
					 | 
				
			||||||
    channels.id = (SELECT dm_channel FROM users WHERE user = ?)
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
                user.as_u64()
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
            .fetch_all(&pool)
 | 
					 | 
				
			||||||
            .await
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn display_content(&self) -> &str {
 | 
					 | 
				
			||||||
        if self.content.is_empty() {
 | 
					 | 
				
			||||||
            &self.embed_description
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            &self.content
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn display(&self, flags: &LookFlags, inter: &str) -> String {
 | 
					 | 
				
			||||||
        let time_display = match flags.time_display {
 | 
					 | 
				
			||||||
            TimeDisplayType::Absolute => format!("<t:{}>", self.utc_time.timestamp()),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            TimeDisplayType::Relative => format!("<t:{}:R>", self.utc_time.timestamp()),
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Some(interval) = self.interval_seconds {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "'{}' *{}* **{}**, repeating every **{}** (set by {})",
 | 
					 | 
				
			||||||
                self.display_content(),
 | 
					 | 
				
			||||||
                &inter,
 | 
					 | 
				
			||||||
                time_display,
 | 
					 | 
				
			||||||
                longhand_displacement(interval as u64),
 | 
					 | 
				
			||||||
                self.set_by
 | 
					 | 
				
			||||||
                    .map(|i| format!("<@{}>", i))
 | 
					 | 
				
			||||||
                    .unwrap_or_else(|| "unknown".to_string())
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            format!(
 | 
					 | 
				
			||||||
                "'{}' *{}* **{}** (set by {})",
 | 
					 | 
				
			||||||
                self.display_content(),
 | 
					 | 
				
			||||||
                &inter,
 | 
					 | 
				
			||||||
                time_display,
 | 
					 | 
				
			||||||
                self.set_by
 | 
					 | 
				
			||||||
                    .map(|i| format!("<@{}>", i))
 | 
					 | 
				
			||||||
                    .unwrap_or_else(|| "unknown".to_string())
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum TimeDisplayType {
 | 
					 | 
				
			||||||
    Absolute,
 | 
					 | 
				
			||||||
    Relative,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct LookFlags {
 | 
					 | 
				
			||||||
    pub limit: u16,
 | 
					 | 
				
			||||||
    pub show_disabled: bool,
 | 
					 | 
				
			||||||
    pub channel_id: Option<ChannelId>,
 | 
					 | 
				
			||||||
    time_display: TimeDisplayType,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Default for LookFlags {
 | 
					 | 
				
			||||||
    fn default() -> Self {
 | 
					 | 
				
			||||||
        Self {
 | 
					 | 
				
			||||||
            limit: u16::MAX,
 | 
					 | 
				
			||||||
            show_disabled: true,
 | 
					 | 
				
			||||||
            channel_id: None,
 | 
					 | 
				
			||||||
            time_display: TimeDisplayType::Relative,
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl LookFlags {
 | 
					 | 
				
			||||||
    pub fn from_string(args: &str) -> Self {
 | 
					 | 
				
			||||||
        let mut new_flags: Self = Default::default();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for arg in args.split(' ') {
 | 
					 | 
				
			||||||
            match arg {
 | 
					 | 
				
			||||||
                "enabled" => {
 | 
					 | 
				
			||||||
                    new_flags.show_disabled = false;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                "time" => {
 | 
					 | 
				
			||||||
                    new_flags.time_display = TimeDisplayType::Absolute;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                param => {
 | 
					 | 
				
			||||||
                    if let Ok(val) = param.parse::<u16>() {
 | 
					 | 
				
			||||||
                        new_flags.limit = val;
 | 
					 | 
				
			||||||
                    } else if let Some(channel) = REGEX_CHANNEL
 | 
					 | 
				
			||||||
                        .captures(&arg)
 | 
					 | 
				
			||||||
                        .map(|cap| cap.get(1))
 | 
					 | 
				
			||||||
                        .flatten()
 | 
					 | 
				
			||||||
                        .map(|c| c.as_str().parse::<u64>().unwrap())
 | 
					 | 
				
			||||||
                    {
 | 
					 | 
				
			||||||
                        new_flags.channel_id = Some(ChannelId(channel));
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        new_flags
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,50 +0,0 @@
 | 
				
			|||||||
use sqlx::MySqlPool;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use chrono::NaiveDateTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct Timer {
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    pub start_time: NaiveDateTime,
 | 
					 | 
				
			||||||
    pub owner: u64,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl Timer {
 | 
					 | 
				
			||||||
    pub async fn from_owner(owner: u64, pool: &MySqlPool) -> Vec<Self> {
 | 
					 | 
				
			||||||
        sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
            Timer,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT name, start_time, owner FROM timers WHERE owner = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            owner
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_all(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn count_from_owner(owner: u64, pool: &MySqlPool) -> u32 {
 | 
					 | 
				
			||||||
        sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT COUNT(1) as count FROM timers WHERE owner = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            owner
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
        .count as u32
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn create(name: &str, owner: u64, pool: &MySqlPool) {
 | 
					 | 
				
			||||||
        sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
INSERT INTO timers (name, owner) VALUES (?, ?)
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            name,
 | 
					 | 
				
			||||||
            owner
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .execute(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,146 +0,0 @@
 | 
				
			|||||||
use serenity::{
 | 
					 | 
				
			||||||
    http::CacheHttp,
 | 
					 | 
				
			||||||
    model::{id::UserId, user::User},
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use sqlx::MySqlPool;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use chrono_tz::Tz;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use log::error;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::consts::{LOCAL_LANGUAGE, LOCAL_TIMEZONE};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
pub struct UserData {
 | 
					 | 
				
			||||||
    pub id: u32,
 | 
					 | 
				
			||||||
    pub user: u64,
 | 
					 | 
				
			||||||
    pub name: String,
 | 
					 | 
				
			||||||
    pub dm_channel: u32,
 | 
					 | 
				
			||||||
    pub language: String,
 | 
					 | 
				
			||||||
    pub timezone: String,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
impl UserData {
 | 
					 | 
				
			||||||
    pub async fn language_of<U>(user: U, pool: &MySqlPool) -> String
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        U: Into<UserId>,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        let user_id = user.into().as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT language FROM users WHERE user = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            user_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Ok(r) => r.language,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(_) => LOCAL_LANGUAGE.clone(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn timezone_of<U>(user: U, pool: &MySqlPool) -> Tz
 | 
					 | 
				
			||||||
    where
 | 
					 | 
				
			||||||
        U: Into<UserId>,
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        let user_id = user.into().as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT timezone FROM users WHERE user = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            user_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Ok(r) => r.timezone,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(_) => LOCAL_TIMEZONE.clone(),
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .parse()
 | 
					 | 
				
			||||||
        .unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn from_user(
 | 
					 | 
				
			||||||
        user: &User,
 | 
					 | 
				
			||||||
        ctx: impl CacheHttp,
 | 
					 | 
				
			||||||
        pool: &MySqlPool,
 | 
					 | 
				
			||||||
    ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
 | 
					 | 
				
			||||||
        let user_id = user.id.as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        match sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
            Self,
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
SELECT id, user, name, dm_channel, IF(language IS NULL, ?, language) AS language, IF(timezone IS NULL, ?, timezone) AS timezone FROM users WHERE user = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            *LOCAL_LANGUAGE, *LOCAL_TIMEZONE, user_id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .fetch_one(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        {
 | 
					 | 
				
			||||||
            Ok(c) => Ok(c),
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(sqlx::Error::RowNotFound) => {
 | 
					 | 
				
			||||||
                let dm_channel = user.create_dm_channel(ctx).await?;
 | 
					 | 
				
			||||||
                let dm_id = dm_channel.id.as_u64().to_owned();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                let pool_c = pool.clone();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                sqlx::query!(
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
INSERT IGNORE INTO channels (channel) VALUES (?)
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                    dm_id
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .execute(&pool_c)
 | 
					 | 
				
			||||||
                .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                sqlx::query!(
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
INSERT INTO users (user, name, dm_channel, language, timezone) VALUES (?, ?, (SELECT id FROM channels WHERE channel = ?), ?, ?)
 | 
					 | 
				
			||||||
                    ", user_id, user.name, dm_id, *LOCAL_LANGUAGE, *LOCAL_TIMEZONE)
 | 
					 | 
				
			||||||
                    .execute(&pool_c)
 | 
					 | 
				
			||||||
                    .await?;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Ok(sqlx::query_as_unchecked!(
 | 
					 | 
				
			||||||
                    Self,
 | 
					 | 
				
			||||||
                    "
 | 
					 | 
				
			||||||
SELECT id, user, name, dm_channel, language, timezone FROM users WHERE user = ?
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                    user_id
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
                .fetch_one(pool)
 | 
					 | 
				
			||||||
                .await?)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            Err(e) => {
 | 
					 | 
				
			||||||
                error!("Error querying for user: {:?}", e);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                Err(Box::new(e))
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub async fn commit_changes(&self, pool: &MySqlPool) {
 | 
					 | 
				
			||||||
        sqlx::query!(
 | 
					 | 
				
			||||||
            "
 | 
					 | 
				
			||||||
UPDATE users SET name = ?, language = ?, timezone = ? WHERE id = ?
 | 
					 | 
				
			||||||
            ",
 | 
					 | 
				
			||||||
            self.name,
 | 
					 | 
				
			||||||
            self.language,
 | 
					 | 
				
			||||||
            self.timezone,
 | 
					 | 
				
			||||||
            self.id
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .execute(pool)
 | 
					 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    pub fn timezone(&self) -> Tz {
 | 
					 | 
				
			||||||
        self.timezone.parse().unwrap()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -112,7 +112,7 @@ impl TimeParser {
 | 
				
			|||||||
            DateTime::with_second,
 | 
					            DateTime::with_second,
 | 
				
			||||||
        ]) {
 | 
					        ]) {
 | 
				
			||||||
            time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorHMS)?)
 | 
					            time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorHMS)?)
 | 
				
			||||||
                .map_or_else(|| Err(InvalidTime::ParseErrorHMS), Ok)?;
 | 
					                .map_or_else(|| Err(InvalidTime::ParseErrorHMS), |inner| Ok(inner))?;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(dmy) = segments.next() {
 | 
					        if let Some(dmy) = segments.next() {
 | 
				
			||||||
@@ -128,7 +128,7 @@ impl TimeParser {
 | 
				
			|||||||
            {
 | 
					            {
 | 
				
			||||||
                if let Some(t) = t {
 | 
					                if let Some(t) = t {
 | 
				
			||||||
                    time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
 | 
					                    time = setter(&time, t.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
 | 
				
			||||||
                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?;
 | 
					                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -136,7 +136,7 @@ impl TimeParser {
 | 
				
			|||||||
                if year.len() == 4 {
 | 
					                if year.len() == 4 {
 | 
				
			||||||
                    time = time
 | 
					                    time = time
 | 
				
			||||||
                        .with_year(year.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
 | 
					                        .with_year(year.parse().map_err(|_| InvalidTime::ParseErrorDMY)?)
 | 
				
			||||||
                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?;
 | 
					                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
 | 
				
			||||||
                } else if year.len() == 2 {
 | 
					                } else if year.len() == 2 {
 | 
				
			||||||
                    time = time
 | 
					                    time = time
 | 
				
			||||||
                        .with_year(
 | 
					                        .with_year(
 | 
				
			||||||
@@ -144,9 +144,9 @@ impl TimeParser {
 | 
				
			|||||||
                                .parse()
 | 
					                                .parse()
 | 
				
			||||||
                                .map_err(|_| InvalidTime::ParseErrorDMY)?,
 | 
					                                .map_err(|_| InvalidTime::ParseErrorDMY)?,
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), Ok)?;
 | 
					                        .map_or_else(|| Err(InvalidTime::ParseErrorDMY), |inner| Ok(inner))?;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    return Err(InvalidTime::ParseErrorDMY);
 | 
					                    Err(InvalidTime::ParseErrorDMY)?;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -157,10 +157,10 @@ impl TimeParser {
 | 
				
			|||||||
    fn process_displacement(&self) -> Result<i64, InvalidTime> {
 | 
					    fn process_displacement(&self) -> Result<i64, InvalidTime> {
 | 
				
			||||||
        let mut current_buffer = "0".to_string();
 | 
					        let mut current_buffer = "0".to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let mut seconds = 0_i64;
 | 
					        let mut seconds = 0 as i64;
 | 
				
			||||||
        let mut minutes = 0_i64;
 | 
					        let mut minutes = 0 as i64;
 | 
				
			||||||
        let mut hours = 0_i64;
 | 
					        let mut hours = 0 as i64;
 | 
				
			||||||
        let mut days = 0_i64;
 | 
					        let mut days = 0 as i64;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for character in self.time_string.chars() {
 | 
					        for character in self.time_string.chars() {
 | 
				
			||||||
            match character {
 | 
					            match character {
 | 
				
			||||||
@@ -205,7 +205,7 @@ impl TimeParser {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub async fn natural_parser(time: &str, timezone: &str) -> Option<i64> {
 | 
					pub(crate) async fn natural_parser(time: &str, timezone: &str) -> Option<i64> {
 | 
				
			||||||
    Command::new(&*PYTHON_LOCATION)
 | 
					    Command::new(&*PYTHON_LOCATION)
 | 
				
			||||||
        .arg("-c")
 | 
					        .arg("-c")
 | 
				
			||||||
        .arg(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/dp.py")))
 | 
					        .arg(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/dp.py")))
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user