Compare commits
	
		
			13 Commits
		
	
	
		
			1.5-dead
			...
			discord-ti
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 51d2ac2b92 | |||
| 2e153cffab | |||
| 540f120d7d | |||
| 59ffb505dc | |||
| 2bec2b9e12 | |||
| 507075d9d4 | |||
| 85659f05aa | |||
| eb07ece779 | |||
| 1a09f026c9 | |||
| b31843c478 | |||
| 9109250fe8 | |||
| 2346c2e978 | |||
| a0da4dcf00 | 
							
								
								
									
										543
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										543
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
[package]
 | 
					[package]
 | 
				
			||||||
name = "reminder_rs"
 | 
					name = "reminder_rs"
 | 
				
			||||||
version = "1.4.13"
 | 
					version = "1.5.0-2"
 | 
				
			||||||
authors = ["jellywx <judesouthworth@pm.me>"]
 | 
					authors = ["jellywx <judesouthworth@pm.me>"]
 | 
				
			||||||
edition = "2018"
 | 
					edition = "2018"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -23,7 +23,7 @@ rand = "0.7"
 | 
				
			|||||||
Inflector = "0.11"
 | 
					Inflector = "0.11"
 | 
				
			||||||
levenshtein = "1.0"
 | 
					levenshtein = "1.0"
 | 
				
			||||||
# serenity = { version = "0.10", features = ["collector"] }
 | 
					# serenity = { version = "0.10", features = ["collector"] }
 | 
				
			||||||
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "next", 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,3 +1,5 @@
 | 
				
			|||||||
 | 
					CREATE DATABASE IF NOT EXISTS reminders;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
SET FOREIGN_KEY_CHECKS=0;
 | 
					SET FOREIGN_KEY_CHECKS=0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
USE reminders;
 | 
					USE reminders;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										161
									
								
								migration/reminder_message_embed.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										161
									
								
								migration/reminder_message_embed.sql
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -1,13 +1,13 @@
 | 
				
			|||||||
use regex_command_attr::command;
 | 
					use regex_command_attr::command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serenity::{
 | 
					use serenity::{
 | 
				
			||||||
 | 
					    builder::CreateActionRow,
 | 
				
			||||||
    client::Context,
 | 
					    client::Context,
 | 
				
			||||||
    framework::Framework,
 | 
					    framework::Framework,
 | 
				
			||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        channel::ReactionType,
 | 
					        channel::Message,
 | 
				
			||||||
        channel::{Channel, Message},
 | 
					        id::{ChannelId, MessageId, RoleId},
 | 
				
			||||||
        id::ChannelId,
 | 
					        interactions::ButtonStyle,
 | 
				
			||||||
        id::RoleId,
 | 
					 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,11 +28,8 @@ use crate::{
 | 
				
			|||||||
    FrameworkCtx, PopularTimezones,
 | 
					    FrameworkCtx, PopularTimezones,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[cfg(feature = "prefix-cache")]
 | 
					 | 
				
			||||||
use crate::PrefixCache;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use crate::models::CtxGuildData;
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
use std::{collections::HashMap, iter, time::Duration};
 | 
					use std::{collections::HashMap, iter};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[command]
 | 
					#[command]
 | 
				
			||||||
#[supports_dm(false)]
 | 
					#[supports_dm(false)]
 | 
				
			||||||
@@ -143,22 +140,28 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
            Err(_) => {
 | 
					            Err(_) => {
 | 
				
			||||||
                let filtered_tz = TZ_VARIANTS
 | 
					                let filtered_tz = TZ_VARIANTS
 | 
				
			||||||
                    .iter()
 | 
					                    .iter()
 | 
				
			||||||
                    .map(|tz| (tz, tz.to_string(), levenshtein(&tz.to_string(), &args)))
 | 
					                    .filter(|tz| {
 | 
				
			||||||
                    .filter(|(_, tz, dist)| args.contains(tz) || tz.contains(&args) || dist < &4)
 | 
					                        args.contains(&tz.to_string())
 | 
				
			||||||
 | 
					                            || tz.to_string().contains(&args)
 | 
				
			||||||
 | 
					                            || levenshtein(&tz.to_string(), &args) < 4
 | 
				
			||||||
 | 
					                    })
 | 
				
			||||||
                    .take(25)
 | 
					                    .take(25)
 | 
				
			||||||
                    .map(|(tz, tz_s, _)| {
 | 
					                    .map(|t| t.to_owned())
 | 
				
			||||||
                        (
 | 
					                    .collect::<Vec<Tz>>();
 | 
				
			||||||
                            tz_s,
 | 
					
 | 
				
			||||||
                            format!(
 | 
					                let fields = filtered_tz.iter().map(|tz| {
 | 
				
			||||||
                                "🕗 `{}`",
 | 
					                    (
 | 
				
			||||||
                                Utc::now()
 | 
					                        tz.to_string(),
 | 
				
			||||||
                                    .with_timezone(tz)
 | 
					                        format!(
 | 
				
			||||||
                                    .format(user_data.meridian().fmt_str_short())
 | 
					                            "🕗 `{}`",
 | 
				
			||||||
                                    .to_string()
 | 
					                            Utc::now()
 | 
				
			||||||
                            ),
 | 
					                                .with_timezone(tz)
 | 
				
			||||||
                            true,
 | 
					                                .format(user_data.meridian().fmt_str_short())
 | 
				
			||||||
                        )
 | 
					                                .to_string()
 | 
				
			||||||
                    });
 | 
					                        ),
 | 
				
			||||||
 | 
					                        true,
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                let _ = msg
 | 
					                let _ = msg
 | 
				
			||||||
                    .channel_id
 | 
					                    .channel_id
 | 
				
			||||||
@@ -167,9 +170,24 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                            e.title(lm.get(&user_data.language, "timezone/no_timezone_title"))
 | 
					                            e.title(lm.get(&user_data.language, "timezone/no_timezone_title"))
 | 
				
			||||||
                                .description(lm.get(&user_data.language, "timezone/no_timezone"))
 | 
					                                .description(lm.get(&user_data.language, "timezone/no_timezone"))
 | 
				
			||||||
                                .color(*THEME_COLOR)
 | 
					                                .color(*THEME_COLOR)
 | 
				
			||||||
                                .fields(filtered_tz)
 | 
					                                .fields(fields)
 | 
				
			||||||
                                .footer(|f| f.text(footer_text))
 | 
					                                .footer(|f| f.text(footer_text))
 | 
				
			||||||
                                .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
					                                .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
				
			||||||
 | 
					                        }).components(|c| {
 | 
				
			||||||
 | 
					                            for row in filtered_tz.as_slice().chunks(5) {
 | 
				
			||||||
 | 
					                                let mut action_row = CreateActionRow::default();
 | 
				
			||||||
 | 
					                                for timezone in row {
 | 
				
			||||||
 | 
					                                    action_row.create_button(|b| {
 | 
				
			||||||
 | 
					                                        b.style(ButtonStyle::Secondary)
 | 
				
			||||||
 | 
					                                            .label(timezone.to_string())
 | 
				
			||||||
 | 
					                                            .custom_id(format!("timezone:{}", timezone.to_string()))
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                c.add_action_row(action_row);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            c
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .await;
 | 
					                    .await;
 | 
				
			||||||
@@ -213,6 +231,22 @@ async fn timezone(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                        .footer(|f| f.text(footer_text))
 | 
					                        .footer(|f| f.text(footer_text))
 | 
				
			||||||
                        .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
					                        .url("https://gist.github.com/JellyWX/913dfc8b63d45192ad6cb54c829324ee")
 | 
				
			||||||
                })
 | 
					                })
 | 
				
			||||||
 | 
					                .components(|c| {
 | 
				
			||||||
 | 
					                    for row in popular_timezones.as_slice().chunks(5) {
 | 
				
			||||||
 | 
					                        let mut action_row = CreateActionRow::default();
 | 
				
			||||||
 | 
					                        for timezone in row {
 | 
				
			||||||
 | 
					                            action_row.create_button(|b| {
 | 
				
			||||||
 | 
					                                b.style(ButtonStyle::Secondary)
 | 
				
			||||||
 | 
					                                    .label(timezone.to_string())
 | 
				
			||||||
 | 
					                                    .custom_id(format!("timezone:{}", timezone.to_string()))
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        c.add_action_row(action_row);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    c
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .await;
 | 
					            .await;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -304,6 +338,28 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                                .description(lm.get(&user_data.language, "lang/invalid"))
 | 
					                                .description(lm.get(&user_data.language, "lang/invalid"))
 | 
				
			||||||
                                .fields(language_codes)
 | 
					                                .fields(language_codes)
 | 
				
			||||||
                        })
 | 
					                        })
 | 
				
			||||||
 | 
					                        .components(|c| {
 | 
				
			||||||
 | 
					                            for row in lm
 | 
				
			||||||
 | 
					                                .all_languages()
 | 
				
			||||||
 | 
					                                .map(|(k, v)| (k.to_string(), v.to_string()))
 | 
				
			||||||
 | 
					                                .collect::<Vec<(String, String)>>()
 | 
				
			||||||
 | 
					                                .as_slice()
 | 
				
			||||||
 | 
					                                .chunks(5)
 | 
				
			||||||
 | 
					                            {
 | 
				
			||||||
 | 
					                                let mut action_row = CreateActionRow::default();
 | 
				
			||||||
 | 
					                                for (code, name) in row {
 | 
				
			||||||
 | 
					                                    action_row.create_button(|b| {
 | 
				
			||||||
 | 
					                                        b.style(ButtonStyle::Primary)
 | 
				
			||||||
 | 
					                                            .label(name.to_title_case())
 | 
				
			||||||
 | 
					                                            .custom_id(format!("lang:{}", code.to_uppercase()))
 | 
				
			||||||
 | 
					                                    });
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                c.add_action_row(action_row);
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            c
 | 
				
			||||||
 | 
					                        })
 | 
				
			||||||
                    })
 | 
					                    })
 | 
				
			||||||
                    .await;
 | 
					                    .await;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -317,21 +373,7 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let flags = lm
 | 
					        let _ = msg
 | 
				
			||||||
            .all_languages()
 | 
					 | 
				
			||||||
            .map(|(k, _)| ReactionType::Unicode(lm.get(k, "flag").to_string()));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let can_react = if let Some(Channel::Guild(channel)) = msg.channel(&ctx).await {
 | 
					 | 
				
			||||||
            channel
 | 
					 | 
				
			||||||
                .permissions_for_user(&ctx, ctx.cache.current_user().await)
 | 
					 | 
				
			||||||
                .await
 | 
					 | 
				
			||||||
                .map(|p| p.add_reactions())
 | 
					 | 
				
			||||||
                .unwrap_or(false)
 | 
					 | 
				
			||||||
        } else {
 | 
					 | 
				
			||||||
            true
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let reactor = msg
 | 
					 | 
				
			||||||
            .channel_id
 | 
					            .channel_id
 | 
				
			||||||
            .send_message(&ctx, |m| {
 | 
					            .send_message(&ctx, |m| {
 | 
				
			||||||
                m.embed(|e| {
 | 
					                m.embed(|e| {
 | 
				
			||||||
@@ -339,57 +381,31 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                        .color(*THEME_COLOR)
 | 
					                        .color(*THEME_COLOR)
 | 
				
			||||||
                        .description(lm.get(&user_data.language, "lang/select"))
 | 
					                        .description(lm.get(&user_data.language, "lang/select"))
 | 
				
			||||||
                        .fields(language_codes)
 | 
					                        .fields(language_codes)
 | 
				
			||||||
                });
 | 
					                })
 | 
				
			||||||
 | 
					                .components(|c| {
 | 
				
			||||||
 | 
					                    for row in lm
 | 
				
			||||||
 | 
					                        .all_languages()
 | 
				
			||||||
 | 
					                        .map(|(k, v)| (k.to_string(), v.to_string()))
 | 
				
			||||||
 | 
					                        .collect::<Vec<(String, String)>>()
 | 
				
			||||||
 | 
					                        .as_slice()
 | 
				
			||||||
 | 
					                        .chunks(5)
 | 
				
			||||||
 | 
					                    {
 | 
				
			||||||
 | 
					                        let mut action_row = CreateActionRow::default();
 | 
				
			||||||
 | 
					                        for (code, name) in row {
 | 
				
			||||||
 | 
					                            action_row.create_button(|b| {
 | 
				
			||||||
 | 
					                                b.style(ButtonStyle::Primary)
 | 
				
			||||||
 | 
					                                    .label(name.to_title_case())
 | 
				
			||||||
 | 
					                                    .custom_id(format!("lang:{}", code.to_uppercase()))
 | 
				
			||||||
 | 
					                            });
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if can_react {
 | 
					                        c.add_action_row(action_row);
 | 
				
			||||||
                    m.reactions(flags);
 | 
					                    }
 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                m
 | 
					                    c
 | 
				
			||||||
 | 
					                })
 | 
				
			||||||
            })
 | 
					            })
 | 
				
			||||||
            .await;
 | 
					            .await;
 | 
				
			||||||
 | 
					 | 
				
			||||||
        if let Ok(sent_msg) = reactor {
 | 
					 | 
				
			||||||
            let reaction_reply = sent_msg
 | 
					 | 
				
			||||||
                .await_reaction(&ctx)
 | 
					 | 
				
			||||||
                .timeout(Duration::from_secs(45))
 | 
					 | 
				
			||||||
                .await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(reaction_action) = reaction_reply {
 | 
					 | 
				
			||||||
                if reaction_action.is_added() {
 | 
					 | 
				
			||||||
                    if let ReactionType::Unicode(emoji) = &reaction_action.as_inner_ref().emoji {
 | 
					 | 
				
			||||||
                        if let Some(lang) = lm.get_language_by_flag(emoji) {
 | 
					 | 
				
			||||||
                            user_data.language = lang.to_string();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            user_data.commit_changes(&pool).await;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            let _ = msg
 | 
					 | 
				
			||||||
                                .channel_id
 | 
					 | 
				
			||||||
                                .send_message(&ctx, |m| {
 | 
					 | 
				
			||||||
                                    m.embed(|e| {
 | 
					 | 
				
			||||||
                                        e.title(lm.get(&user_data.language, "lang/set_p_title"))
 | 
					 | 
				
			||||||
                                            .color(*THEME_COLOR)
 | 
					 | 
				
			||||||
                                            .description(lm.get(&user_data.language, "lang/set_p"))
 | 
					 | 
				
			||||||
                                    })
 | 
					 | 
				
			||||||
                                })
 | 
					 | 
				
			||||||
                                .await;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if let Some(Channel::Guild(channel)) = msg.channel(&ctx).await {
 | 
					 | 
				
			||||||
                let has_perms = channel
 | 
					 | 
				
			||||||
                    .permissions_for_user(&ctx, ctx.cache.current_user().await)
 | 
					 | 
				
			||||||
                    .await
 | 
					 | 
				
			||||||
                    .map(|p| p.manage_messages())
 | 
					 | 
				
			||||||
                    .unwrap_or(false);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if has_perms {
 | 
					 | 
				
			||||||
                    let _ = sent_msg.delete_reactions(&ctx).await;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -399,9 +415,7 @@ async fn language(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
					async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
				
			||||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
					    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let mut guild_data = GuildData::from_guild(msg.guild(&ctx).await.unwrap(), &pool)
 | 
					    let guild_data = ctx.guild_data(msg.guild_id.unwrap()).await.unwrap();
 | 
				
			||||||
        .await
 | 
					 | 
				
			||||||
        .unwrap();
 | 
					 | 
				
			||||||
    let language = UserData::language_of(&msg.author, &pool).await;
 | 
					    let language = UserData::language_of(&msg.author, &pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if args.len() > 5 {
 | 
					    if args.len() > 5 {
 | 
				
			||||||
@@ -415,18 +429,15 @@ async fn prefix(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
            .say(&ctx, lm.get(&language, "prefix/no_argument"))
 | 
					            .say(&ctx, lm.get(&language, "prefix/no_argument"))
 | 
				
			||||||
            .await;
 | 
					            .await;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
        guild_data.prefix = args;
 | 
					        guild_data.write().await.prefix = args;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        #[cfg(feature = "prefix-cache")]
 | 
					        guild_data.read().await.commit_changes(&pool).await;
 | 
				
			||||||
        let prefix_cache = ctx.data.read().await.get::<PrefixCache>().cloned().unwrap();
 | 
					 | 
				
			||||||
        #[cfg(feature = "prefix-cache")]
 | 
					 | 
				
			||||||
        prefix_cache.insert(msg.guild_id.unwrap(), guild_data.prefix.clone());
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        guild_data.commit_changes(&pool).await;
 | 
					        let content = lm.get(&language, "prefix/success").replacen(
 | 
				
			||||||
 | 
					            "{prefix}",
 | 
				
			||||||
        let content =
 | 
					            &guild_data.read().await.prefix,
 | 
				
			||||||
            lm.get(&language, "prefix/success")
 | 
					            1,
 | 
				
			||||||
                .replacen("{prefix}", &guild_data.prefix, 1);
 | 
					        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let _ = msg.channel_id.say(&ctx, content).await;
 | 
					        let _ = msg.channel_id.say(&ctx, content).await;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -670,6 +681,7 @@ SELECT command FROM command_aliases WHERE guild_id = (SELECT id FROM guilds WHER
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                            let mut new_msg = msg.clone();
 | 
					                            let mut new_msg = msg.clone();
 | 
				
			||||||
                            new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id().await, row.command);
 | 
					                            new_msg.content = format!("<@{}> {}", &ctx.cache.current_user_id().await, row.command);
 | 
				
			||||||
 | 
					                            new_msg.id = MessageId(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                            framework.dispatch(ctx.clone(), new_msg).await;
 | 
					                            framework.dispatch(ctx.clone(), new_msg).await;
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
use regex_command_attr::command;
 | 
					use regex_command_attr::command;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use chrono_tz::Tz;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use serenity::{
 | 
					use serenity::{
 | 
				
			||||||
    cache::Cache,
 | 
					    cache::Cache,
 | 
				
			||||||
    client::Context,
 | 
					    client::Context,
 | 
				
			||||||
@@ -47,20 +45,10 @@ use std::{
 | 
				
			|||||||
    time::{SystemTime, UNIX_EPOCH},
 | 
					    time::{SystemTime, UNIX_EPOCH},
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::models::{CtxGuildData, MeridianType};
 | 
					use crate::models::CtxGuildData;
 | 
				
			||||||
use regex::Captures;
 | 
					use regex::Captures;
 | 
				
			||||||
use serenity::model::channel::Channel;
 | 
					use serenity::model::channel::Channel;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fn shorthand_displacement(seconds: u64) -> String {
 | 
					 | 
				
			||||||
    let (days, seconds) = seconds.div_rem(&DAY);
 | 
					 | 
				
			||||||
    let (hours, seconds) = seconds.div_rem(&HOUR);
 | 
					 | 
				
			||||||
    let (minutes, seconds) = seconds.div_rem(&MINUTE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let time_repr = format!("{:02}:{:02}:{:02}", hours, minutes, seconds);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    format!("{} days, {}", days, time_repr)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fn longhand_displacement(seconds: u64) -> String {
 | 
					fn longhand_displacement(seconds: u64) -> String {
 | 
				
			||||||
    let (days, seconds) = seconds.div_rem(&DAY);
 | 
					    let (days, seconds) = seconds.div_rem(&DAY);
 | 
				
			||||||
    let (hours, seconds) = seconds.div_rem(&HOUR);
 | 
					    let (hours, seconds) = seconds.div_rem(&HOUR);
 | 
				
			||||||
@@ -193,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.`time` = reminders.`time` + ?
 | 
					        reminders.`utc_time` = reminders.`utc_time` + ?
 | 
				
			||||||
    WHERE channels.guild_id = ?
 | 
					    WHERE channels.guild_id = ?
 | 
				
			||||||
                    ",
 | 
					                    ",
 | 
				
			||||||
                    displacement,
 | 
					                    displacement,
 | 
				
			||||||
@@ -205,7 +193,7 @@ UPDATE reminders
 | 
				
			|||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                sqlx::query!(
 | 
					                sqlx::query!(
 | 
				
			||||||
                    "
 | 
					                    "
 | 
				
			||||||
UPDATE reminders SET `time` = `time` + ? 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
 | 
				
			||||||
@@ -345,11 +333,11 @@ impl LookFlags {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
struct LookReminder {
 | 
					struct LookReminder {
 | 
				
			||||||
    id: u32,
 | 
					    id: u32,
 | 
				
			||||||
    time: u32,
 | 
					    time: NaiveDateTime,
 | 
				
			||||||
    interval: Option<u32>,
 | 
					    interval: Option<u32>,
 | 
				
			||||||
    channel: u64,
 | 
					    channel: u64,
 | 
				
			||||||
    content: String,
 | 
					    content: String,
 | 
				
			||||||
    description: Option<String>,
 | 
					    description: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
impl LookReminder {
 | 
					impl LookReminder {
 | 
				
			||||||
@@ -357,31 +345,15 @@ impl LookReminder {
 | 
				
			|||||||
        if self.content.len() > 0 {
 | 
					        if self.content.len() > 0 {
 | 
				
			||||||
            self.content.clone()
 | 
					            self.content.clone()
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            self.description.clone().unwrap_or(String::from(""))
 | 
					            self.description.clone()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fn display(
 | 
					    fn display(&self, flags: &LookFlags, inter: &str) -> String {
 | 
				
			||||||
        &self,
 | 
					 | 
				
			||||||
        flags: &LookFlags,
 | 
					 | 
				
			||||||
        meridian: &MeridianType,
 | 
					 | 
				
			||||||
        timezone: &Tz,
 | 
					 | 
				
			||||||
        inter: &str,
 | 
					 | 
				
			||||||
    ) -> String {
 | 
					 | 
				
			||||||
        let time_display = match flags.time_display {
 | 
					        let time_display = match flags.time_display {
 | 
				
			||||||
            TimeDisplayType::Absolute => timezone
 | 
					            TimeDisplayType::Absolute => format!("<t:{}>", self.time.timestamp()),
 | 
				
			||||||
                .timestamp(self.time as i64, 0)
 | 
					 | 
				
			||||||
                .format(meridian.fmt_str())
 | 
					 | 
				
			||||||
                .to_string(),
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            TimeDisplayType::Relative => {
 | 
					            TimeDisplayType::Relative => format!("<t:{}:R>", self.time.timestamp()),
 | 
				
			||||||
                let now = SystemTime::now()
 | 
					 | 
				
			||||||
                    .duration_since(UNIX_EPOCH)
 | 
					 | 
				
			||||||
                    .unwrap()
 | 
					 | 
				
			||||||
                    .as_secs();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                longhand_displacement((self.time as u64).checked_sub(now).unwrap_or(1))
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        };
 | 
					        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if let Some(interval) = self.interval {
 | 
					        if let Some(interval) = self.interval {
 | 
				
			||||||
@@ -409,8 +381,6 @@ async fn look(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 timezone = UserData::timezone_of(&msg.author, &pool).await;
 | 
					 | 
				
			||||||
    let meridian = UserData::meridian_of(&msg.author, &pool).await;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let flags = LookFlags::from_string(&args);
 | 
					    let flags = LookFlags::from_string(&args);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -434,26 +404,23 @@ async fn look(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
        LookReminder,
 | 
					        LookReminder,
 | 
				
			||||||
        "
 | 
					        "
 | 
				
			||||||
SELECT
 | 
					SELECT
 | 
				
			||||||
    reminders.id, reminders.time, reminders.interval, channels.channel, messages.content, embeds.description
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
FROM
 | 
					FROM
 | 
				
			||||||
    reminders
 | 
					    reminders
 | 
				
			||||||
INNER JOIN
 | 
					INNER JOIN
 | 
				
			||||||
    channels
 | 
					    channels
 | 
				
			||||||
ON
 | 
					ON
 | 
				
			||||||
    reminders.channel_id = channels.id
 | 
					    reminders.channel_id = channels.id
 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    messages
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    messages.id = reminders.message_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    embeds
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    embeds.id = messages.embed_id
 | 
					 | 
				
			||||||
WHERE
 | 
					WHERE
 | 
				
			||||||
    channels.channel = ? AND
 | 
					    channels.channel = ? AND
 | 
				
			||||||
    FIND_IN_SET(reminders.enabled, ?)
 | 
					    FIND_IN_SET(reminders.enabled, ?)
 | 
				
			||||||
ORDER BY
 | 
					ORDER BY
 | 
				
			||||||
    reminders.time
 | 
					    reminders.utc_time
 | 
				
			||||||
LIMIT
 | 
					LIMIT
 | 
				
			||||||
    ?
 | 
					    ?
 | 
				
			||||||
            ",
 | 
					            ",
 | 
				
			||||||
@@ -475,7 +442,7 @@ LIMIT
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let display = reminders
 | 
					        let display = reminders
 | 
				
			||||||
            .iter()
 | 
					            .iter()
 | 
				
			||||||
            .map(|reminder| reminder.display(&flags, &meridian, &timezone, &inter));
 | 
					            .map(|reminder| reminder.display(&flags, &inter));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let _ = msg.channel_id.say_lines(&ctx, display).await;
 | 
					        let _ = msg.channel_id.say_lines(&ctx, display).await;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -509,21 +476,18 @@ async fn delete(ctx: &Context, msg: &Message, _args: String) {
 | 
				
			|||||||
                LookReminder,
 | 
					                LookReminder,
 | 
				
			||||||
                "
 | 
					                "
 | 
				
			||||||
SELECT
 | 
					SELECT
 | 
				
			||||||
    reminders.id, reminders.time, reminders.interval, channels.channel, messages.content, embeds.description
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
FROM
 | 
					FROM
 | 
				
			||||||
    reminders
 | 
					    reminders
 | 
				
			||||||
LEFT OUTER JOIN
 | 
					LEFT OUTER JOIN
 | 
				
			||||||
    channels
 | 
					    channels
 | 
				
			||||||
ON
 | 
					ON
 | 
				
			||||||
    channels.id = reminders.channel_id
 | 
					    channels.id = reminders.channel_id
 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    messages
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    messages.id = reminders.message_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    embeds
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    embeds.id = messages.embed_id
 | 
					 | 
				
			||||||
WHERE
 | 
					WHERE
 | 
				
			||||||
    FIND_IN_SET(channels.channel, ?)
 | 
					    FIND_IN_SET(channels.channel, ?)
 | 
				
			||||||
                ",
 | 
					                ",
 | 
				
			||||||
@@ -536,21 +500,18 @@ WHERE
 | 
				
			|||||||
                LookReminder,
 | 
					                LookReminder,
 | 
				
			||||||
                "
 | 
					                "
 | 
				
			||||||
SELECT
 | 
					SELECT
 | 
				
			||||||
    reminders.id, reminders.time, reminders.interval, channels.channel, messages.content, embeds.description
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
FROM
 | 
					FROM
 | 
				
			||||||
    reminders
 | 
					    reminders
 | 
				
			||||||
LEFT OUTER JOIN
 | 
					LEFT OUTER JOIN
 | 
				
			||||||
    channels
 | 
					    channels
 | 
				
			||||||
ON
 | 
					ON
 | 
				
			||||||
    channels.id = reminders.channel_id
 | 
					    channels.id = reminders.channel_id
 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    messages
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    messages.id = reminders.message_id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    embeds
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    embeds.id = messages.embed_id
 | 
					 | 
				
			||||||
WHERE
 | 
					WHERE
 | 
				
			||||||
    channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
					    channels.guild_id = (SELECT id FROM guilds WHERE guild = ?)
 | 
				
			||||||
                ",
 | 
					                ",
 | 
				
			||||||
@@ -564,17 +525,14 @@ WHERE
 | 
				
			|||||||
            LookReminder,
 | 
					            LookReminder,
 | 
				
			||||||
            "
 | 
					            "
 | 
				
			||||||
SELECT
 | 
					SELECT
 | 
				
			||||||
    reminders.id, reminders.time, reminders.interval, channels.channel, messages.content, embeds.description
 | 
					    reminders.id,
 | 
				
			||||||
 | 
					    reminders.utc_time AS time,
 | 
				
			||||||
 | 
					    reminders.interval,
 | 
				
			||||||
 | 
					    channels.channel,
 | 
				
			||||||
 | 
					    reminders.content,
 | 
				
			||||||
 | 
					    reminders.embed_description AS description
 | 
				
			||||||
FROM
 | 
					FROM
 | 
				
			||||||
    reminders
 | 
					    reminders
 | 
				
			||||||
INNER JOIN
 | 
					 | 
				
			||||||
    messages
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    reminders.message_id = messages.id
 | 
					 | 
				
			||||||
LEFT JOIN
 | 
					 | 
				
			||||||
    embeds
 | 
					 | 
				
			||||||
ON
 | 
					 | 
				
			||||||
    embeds.id = messages.embed_id
 | 
					 | 
				
			||||||
INNER JOIN
 | 
					INNER JOIN
 | 
				
			||||||
    channels
 | 
					    channels
 | 
				
			||||||
ON
 | 
					ON
 | 
				
			||||||
@@ -593,14 +551,13 @@ WHERE
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    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);
 | 
				
			||||||
        let time = user_data.timezone().timestamp(reminder.time as i64, 0);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        format!(
 | 
					        format!(
 | 
				
			||||||
            "**{}**: '{}' *<#{}>* at {}",
 | 
					            "**{}**: '{}' *<#{}>* at <t:{}>",
 | 
				
			||||||
            count + 1,
 | 
					            count + 1,
 | 
				
			||||||
            reminder.display_content(),
 | 
					            reminder.display_content(),
 | 
				
			||||||
            reminder.channel,
 | 
					            reminder.channel,
 | 
				
			||||||
            time.format(user_data.meridian().fmt_str())
 | 
					            reminder.time.timestamp()
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1047,65 +1004,36 @@ async fn countdown(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                        event_name, target_ts
 | 
					                        event_name, target_ts
 | 
				
			||||||
                    );
 | 
					                    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    sqlx::query!(
 | 
					 | 
				
			||||||
                        "
 | 
					 | 
				
			||||||
INSERT INTO embeds (title, description, color) VALUES (?, ?, ?)
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                        event_name,
 | 
					 | 
				
			||||||
                        description,
 | 
					 | 
				
			||||||
                        *THEME_COLOR
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    .execute(&pool)
 | 
					 | 
				
			||||||
                    .await
 | 
					 | 
				
			||||||
                    .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    let embed_id = sqlx::query!(
 | 
					 | 
				
			||||||
                        "
 | 
					 | 
				
			||||||
SELECT id FROM embeds WHERE title = ? AND description = ?
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                        event_name,
 | 
					 | 
				
			||||||
                        description
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    .fetch_one(&pool)
 | 
					 | 
				
			||||||
                    .await
 | 
					 | 
				
			||||||
                    .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    sqlx::query!(
 | 
					 | 
				
			||||||
                        "
 | 
					 | 
				
			||||||
INSERT INTO messages (embed_id) VALUES (?)
 | 
					 | 
				
			||||||
                    ",
 | 
					 | 
				
			||||||
                        embed_id.id
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                    .execute(&pool)
 | 
					 | 
				
			||||||
                    .await
 | 
					 | 
				
			||||||
                    .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    sqlx::query!(
 | 
					                    sqlx::query!(
 | 
				
			||||||
                        "
 | 
					                        "
 | 
				
			||||||
INSERT INTO reminders (
 | 
					INSERT INTO reminders (
 | 
				
			||||||
    `uid`,
 | 
					    `uid`,
 | 
				
			||||||
    `name`,
 | 
					    `name`,
 | 
				
			||||||
    `message_id`,
 | 
					    `embed_title`,
 | 
				
			||||||
 | 
					    `embed_description`,
 | 
				
			||||||
 | 
					    `embed_color`,
 | 
				
			||||||
    `channel_id`,
 | 
					    `channel_id`,
 | 
				
			||||||
    `time`,
 | 
					    `utc_time`,
 | 
				
			||||||
    `interval`,
 | 
					    `interval`,
 | 
				
			||||||
    `method`,
 | 
					 | 
				
			||||||
    `set_by`,
 | 
					    `set_by`,
 | 
				
			||||||
    `expires`
 | 
					    `expires`
 | 
				
			||||||
) VALUES (
 | 
					) VALUES (
 | 
				
			||||||
    ?,
 | 
					    ?,
 | 
				
			||||||
    'Countdown',
 | 
					    'Countdown',
 | 
				
			||||||
    (SELECT id FROM messages WHERE embed_id = ?),
 | 
					 | 
				
			||||||
    (SELECT id FROM channels WHERE channel = ?),
 | 
					 | 
				
			||||||
    ?,
 | 
					    ?,
 | 
				
			||||||
    ?,
 | 
					    ?,
 | 
				
			||||||
    'countdown',
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
    (SELECT id FROM users WHERE user = ?),
 | 
					    (SELECT id FROM users WHERE user = ?),
 | 
				
			||||||
    FROM_UNIXTIME(?)
 | 
					    FROM_UNIXTIME(?)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
                    ",
 | 
					                    ",
 | 
				
			||||||
                        generate_uid(),
 | 
					                        generate_uid(),
 | 
				
			||||||
                        embed_id.id,
 | 
					                        event_name,
 | 
				
			||||||
 | 
					                        description,
 | 
				
			||||||
 | 
					                        *THEME_COLOR,
 | 
				
			||||||
                        msg.channel_id.as_u64(),
 | 
					                        msg.channel_id.as_u64(),
 | 
				
			||||||
                        first_time,
 | 
					                        first_time,
 | 
				
			||||||
                        interval,
 | 
					                        interval,
 | 
				
			||||||
@@ -1271,9 +1199,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
                                    .replace("{location}", &ok_locations[0].mention())
 | 
					                                    .replace("{location}", &ok_locations[0].mention())
 | 
				
			||||||
                                    .replace(
 | 
					                                    .replace(
 | 
				
			||||||
                                        "{offset}",
 | 
					                                        "{offset}",
 | 
				
			||||||
                                        &shorthand_displacement(
 | 
					                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
				
			||||||
                                            time_parser.displacement().unwrap() as u64,
 | 
					 | 
				
			||||||
                                        ),
 | 
					 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                                n => lm
 | 
					                                n => lm
 | 
				
			||||||
                                    .get(&language, "remind/success_bulk")
 | 
					                                    .get(&language, "remind/success_bulk")
 | 
				
			||||||
@@ -1288,9 +1214,7 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
                                    )
 | 
					                                    )
 | 
				
			||||||
                                    .replace(
 | 
					                                    .replace(
 | 
				
			||||||
                                        "{offset}",
 | 
					                                        "{offset}",
 | 
				
			||||||
                                        &shorthand_displacement(
 | 
					                                        &format!("<t:{}:R>", time_parser.timestamp().unwrap()),
 | 
				
			||||||
                                            time_parser.displacement().unwrap() as u64,
 | 
					 | 
				
			||||||
                                        ),
 | 
					 | 
				
			||||||
                                    ),
 | 
					                                    ),
 | 
				
			||||||
                            };
 | 
					                            };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1397,11 +1321,6 @@ async fn remind_command(ctx: &Context, msg: &Message, args: String, command: Rem
 | 
				
			|||||||
async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
					async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			||||||
    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
					    let (pool, lm) = get_ctx_data(&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let now = SystemTime::now();
 | 
					 | 
				
			||||||
    let since_epoch = now
 | 
					 | 
				
			||||||
        .duration_since(UNIX_EPOCH)
 | 
					 | 
				
			||||||
        .expect("Time calculated as going backwards. Very bad");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap();
 | 
					    let user_data = UserData::from_user(&msg.author, &ctx, &pool).await.unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    match REGEX_NATURAL_COMMAND_1.captures(&args) {
 | 
					    match REGEX_NATURAL_COMMAND_1.captures(&args) {
 | 
				
			||||||
@@ -1465,8 +1384,6 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
                match content_res {
 | 
					                match content_res {
 | 
				
			||||||
                    Ok(mut content) => {
 | 
					                    Ok(mut content) => {
 | 
				
			||||||
                        let offset = timestamp as u64 - since_epoch.as_secs();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        let mut ok_locations = vec![];
 | 
					                        let mut ok_locations = vec![];
 | 
				
			||||||
                        let mut err_locations = vec![];
 | 
					                        let mut err_locations = vec![];
 | 
				
			||||||
                        let mut err_types = HashSet::new();
 | 
					                        let mut err_types = HashSet::new();
 | 
				
			||||||
@@ -1498,7 +1415,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                            1 => lm
 | 
					                            1 => lm
 | 
				
			||||||
                                .get(&user_data.language, "remind/success")
 | 
					                                .get(&user_data.language, "remind/success")
 | 
				
			||||||
                                .replace("{location}", &ok_locations[0].mention())
 | 
					                                .replace("{location}", &ok_locations[0].mention())
 | 
				
			||||||
                                .replace("{offset}", &shorthand_displacement(offset)),
 | 
					                                .replace("{offset}", &format!("<t:{}:R>", timestamp)),
 | 
				
			||||||
                            n => lm
 | 
					                            n => lm
 | 
				
			||||||
                                .get(&user_data.language, "remind/success_bulk")
 | 
					                                .get(&user_data.language, "remind/success_bulk")
 | 
				
			||||||
                                .replace("{number}", &n.to_string())
 | 
					                                .replace("{number}", &n.to_string())
 | 
				
			||||||
@@ -1510,7 +1427,7 @@ async fn natural(ctx: &Context, msg: &Message, args: String) {
 | 
				
			|||||||
                                        .collect::<Vec<String>>()
 | 
					                                        .collect::<Vec<String>>()
 | 
				
			||||||
                                        .join(", "),
 | 
					                                        .join(", "),
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
                                .replace("{offset}", &shorthand_displacement(offset)),
 | 
					                                .replace("{offset}", &format!("<t:{}:R>", timestamp)),
 | 
				
			||||||
                        };
 | 
					                        };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                        let error_part = format!(
 | 
					                        let error_part = format!(
 | 
				
			||||||
@@ -1698,37 +1615,45 @@ async fn create_reminder<'a, U: Into<u64>, T: TryInto<i64>>(
 | 
				
			|||||||
                            } else {
 | 
					                            } else {
 | 
				
			||||||
                                sqlx::query!(
 | 
					                                sqlx::query!(
 | 
				
			||||||
                                    "
 | 
					                                    "
 | 
				
			||||||
INSERT INTO messages (content, tts, attachment, attachment_name) VALUES (?, ?, ?, ?)
 | 
					INSERT INTO reminders (
 | 
				
			||||||
 | 
					    uid,
 | 
				
			||||||
 | 
					    content,
 | 
				
			||||||
 | 
					    tts,
 | 
				
			||||||
 | 
					    attachment,
 | 
				
			||||||
 | 
					    attachment_name,
 | 
				
			||||||
 | 
					    channel_id,
 | 
				
			||||||
 | 
					    `utc_time`,
 | 
				
			||||||
 | 
					    expires,
 | 
				
			||||||
 | 
					    `interval`,
 | 
				
			||||||
 | 
					    set_by
 | 
				
			||||||
 | 
					) VALUES (
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
 | 
				
			||||||
 | 
					    DATE_ADD(FROM_UNIXTIME(0), INTERVAL ? SECOND),
 | 
				
			||||||
 | 
					    ?,
 | 
				
			||||||
 | 
					    (SELECT id FROM users WHERE user = ? LIMIT 1)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
                            ",
 | 
					                            ",
 | 
				
			||||||
 | 
					                                    generate_uid(),
 | 
				
			||||||
                                    content.content,
 | 
					                                    content.content,
 | 
				
			||||||
                                    content.tts,
 | 
					                                    content.tts,
 | 
				
			||||||
                                    content.attachment,
 | 
					                                    content.attachment,
 | 
				
			||||||
                                    content.attachment_name,
 | 
					                                    content.attachment_name,
 | 
				
			||||||
 | 
					                                    db_channel_id,
 | 
				
			||||||
 | 
					                                    time as u32,
 | 
				
			||||||
 | 
					                                    expires,
 | 
				
			||||||
 | 
					                                    interval,
 | 
				
			||||||
 | 
					                                    user_id
 | 
				
			||||||
                                )
 | 
					                                )
 | 
				
			||||||
                                .execute(&pool.clone())
 | 
					                                .execute(pool)
 | 
				
			||||||
                                .await
 | 
					                                .await
 | 
				
			||||||
                                .unwrap();
 | 
					                                .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                sqlx::query!(
 | 
					 | 
				
			||||||
                            "
 | 
					 | 
				
			||||||
INSERT INTO reminders (uid, message_id, channel_id, time, expires, `interval`, method, set_by) VALUES
 | 
					 | 
				
			||||||
    (?,
 | 
					 | 
				
			||||||
    (SELECT id FROM messages WHERE content = ? ORDER BY id DESC LIMIT 1),
 | 
					 | 
				
			||||||
    ?, ?, FROM_UNIXTIME(?), ?, 'remind',
 | 
					 | 
				
			||||||
    (SELECT id FROM users WHERE user = ? LIMIT 1))
 | 
					 | 
				
			||||||
                            ",
 | 
					 | 
				
			||||||
                            generate_uid(),
 | 
					 | 
				
			||||||
                            content.content,
 | 
					 | 
				
			||||||
                            db_channel_id,
 | 
					 | 
				
			||||||
                            time as u32,
 | 
					 | 
				
			||||||
                            expires,
 | 
					 | 
				
			||||||
                            interval,
 | 
					 | 
				
			||||||
                            user_id
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                        .execute(pool)
 | 
					 | 
				
			||||||
                        .await
 | 
					 | 
				
			||||||
                        .unwrap();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                Ok(())
 | 
					                                Ok(())
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        } else if time < 0 {
 | 
					                        } else if time < 0 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										256
									
								
								src/framework.rs
									
									
									
									
									
								
							
							
						
						
									
										256
									
								
								src/framework.rs
									
									
									
									
									
								
							@@ -21,7 +21,8 @@ use std::{collections::HashMap, fmt};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use crate::language_manager::LanguageManager;
 | 
					use crate::language_manager::LanguageManager;
 | 
				
			||||||
use crate::models::{CtxGuildData, GuildData, UserData};
 | 
					use crate::models::{CtxGuildData, GuildData, UserData};
 | 
				
			||||||
use crate::{models::ChannelData, SQLPool};
 | 
					use crate::{models::ChannelData, LimitExecutors, SQLPool};
 | 
				
			||||||
 | 
					use serenity::model::id::MessageId;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, String) -> BoxFuture<'fut, ()>;
 | 
					type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, String) -> BoxFuture<'fut, ()>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -297,9 +298,9 @@ impl RegexFramework {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
enum PermissionCheck {
 | 
					enum PermissionCheck {
 | 
				
			||||||
    None,                          // No permissions
 | 
					    None,              // No permissions
 | 
				
			||||||
    Basic(bool, bool, bool, bool), // Send + Embed permissions (sufficient to reply)
 | 
					    Basic(bool, bool), // Send + Embed permissions (sufficient to reply)
 | 
				
			||||||
    All,                           // Above + Manage Webhooks (sufficient to operate)
 | 
					    All,               // Above + Manage Webhooks (sufficient to operate)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
@@ -324,8 +325,6 @@ impl Framework for RegexFramework {
 | 
				
			|||||||
                    PermissionCheck::Basic(
 | 
					                    PermissionCheck::Basic(
 | 
				
			||||||
                        guild_perms.manage_webhooks(),
 | 
					                        guild_perms.manage_webhooks(),
 | 
				
			||||||
                        channel_perms.embed_links(),
 | 
					                        channel_perms.embed_links(),
 | 
				
			||||||
                        channel_perms.add_reactions(),
 | 
					 | 
				
			||||||
                        channel_perms.manage_messages(),
 | 
					 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    PermissionCheck::None
 | 
					                    PermissionCheck::None
 | 
				
			||||||
@@ -345,144 +344,153 @@ impl Framework for RegexFramework {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        // gate to prevent analysing messages unnecessarily
 | 
					        // gate to prevent analysing messages unnecessarily
 | 
				
			||||||
        if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
 | 
					        if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
 | 
				
			||||||
        }
 | 
					        } else {
 | 
				
			||||||
        // Guild Command
 | 
					            // Guild Command
 | 
				
			||||||
        else if let (Some(guild), Some(Channel::Guild(channel))) =
 | 
					            if let (Some(guild), Some(Channel::Guild(channel))) =
 | 
				
			||||||
            (msg.guild(&ctx).await, msg.channel(&ctx).await)
 | 
					                (msg.guild(&ctx).await, msg.channel(&ctx).await)
 | 
				
			||||||
        {
 | 
					            {
 | 
				
			||||||
            let data = ctx.data.read().await;
 | 
					                let data = ctx.data.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let pool = data
 | 
					                let pool = data
 | 
				
			||||||
                .get::<SQLPool>()
 | 
					                    .get::<SQLPool>()
 | 
				
			||||||
                .cloned()
 | 
					                    .cloned()
 | 
				
			||||||
                .expect("Could not get SQLPool from data");
 | 
					                    .expect("Could not get SQLPool from data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if let Some(full_match) = self.command_matcher.captures(&msg.content) {
 | 
					                if let Some(full_match) = self.command_matcher.captures(&msg.content) {
 | 
				
			||||||
                if check_prefix(&ctx, &guild, full_match.name("prefix")).await {
 | 
					                    if check_prefix(&ctx, &guild, full_match.name("prefix")).await {
 | 
				
			||||||
                    let lm = data.get::<LanguageManager>().unwrap();
 | 
					                        let lm = data.get::<LanguageManager>().unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    let language = UserData::language_of(&msg.author, &pool);
 | 
					                        let language = UserData::language_of(&msg.author, &pool);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    match check_self_permissions(&ctx, &guild, &channel).await {
 | 
					                        match check_self_permissions(&ctx, &guild, &channel).await {
 | 
				
			||||||
                        Ok(perms) => match perms {
 | 
					                            Ok(perms) => match perms {
 | 
				
			||||||
                            PermissionCheck::All => {
 | 
					                                PermissionCheck::All => {
 | 
				
			||||||
                                let command = self
 | 
					                                    let command = self
 | 
				
			||||||
                                    .commands
 | 
					                                        .commands
 | 
				
			||||||
                                    .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
					                                        .get(
 | 
				
			||||||
 | 
					                                            &full_match
 | 
				
			||||||
 | 
					                                                .name("cmd")
 | 
				
			||||||
 | 
					                                                .unwrap()
 | 
				
			||||||
 | 
					                                                .as_str()
 | 
				
			||||||
 | 
					                                                .to_lowercase(),
 | 
				
			||||||
 | 
					                                        )
 | 
				
			||||||
 | 
					                                        .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    let channel_data = ChannelData::from_channel(
 | 
				
			||||||
 | 
					                                        msg.channel(&ctx).await.unwrap(),
 | 
				
			||||||
 | 
					                                        &pool,
 | 
				
			||||||
 | 
					                                    )
 | 
				
			||||||
 | 
					                                    .await
 | 
				
			||||||
                                    .unwrap();
 | 
					                                    .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                let channel_data = ChannelData::from_channel(
 | 
					                                    if !command.can_blacklist || !channel_data.blacklisted {
 | 
				
			||||||
                                    msg.channel(&ctx).await.unwrap(),
 | 
					                                        let args = full_match
 | 
				
			||||||
                                    &pool,
 | 
					                                            .name("args")
 | 
				
			||||||
                                )
 | 
					                                            .map(|m| m.as_str())
 | 
				
			||||||
                                .await
 | 
					                                            .unwrap_or("")
 | 
				
			||||||
                                .unwrap();
 | 
					                                            .to_string();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                if !command.can_blacklist || !channel_data.blacklisted {
 | 
					                                        let member = guild.member(&ctx, &msg.author).await.unwrap();
 | 
				
			||||||
                                    let args = full_match
 | 
					 | 
				
			||||||
                                        .name("args")
 | 
					 | 
				
			||||||
                                        .map(|m| m.as_str())
 | 
					 | 
				
			||||||
                                        .unwrap_or("")
 | 
					 | 
				
			||||||
                                        .to_string();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    let member = guild.member(&ctx, &msg.author).await.unwrap();
 | 
					                                        if command.check_permissions(&ctx, &guild, &member).await {
 | 
				
			||||||
 | 
					                                            dbg!(command.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                    if command.check_permissions(&ctx, &guild, &member).await {
 | 
					                                            {
 | 
				
			||||||
                                        dbg!(command.name);
 | 
					                                                let guild_id = guild.id.as_u64().to_owned();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                                        {
 | 
					                                                GuildData::from_guild(guild, &pool).await.expect(
 | 
				
			||||||
                                            let guild_id = guild.id.as_u64().to_owned();
 | 
					                                                    &format!(
 | 
				
			||||||
 | 
					                                                        "Failed to create new guild object for {}",
 | 
				
			||||||
                                            GuildData::from_guild(guild, &pool).await.expect(
 | 
					                                                        guild_id
 | 
				
			||||||
                                                &format!(
 | 
					 | 
				
			||||||
                                                    "Failed to create new guild object for {}",
 | 
					 | 
				
			||||||
                                                    guild_id
 | 
					 | 
				
			||||||
                                                ),
 | 
					 | 
				
			||||||
                                            );
 | 
					 | 
				
			||||||
                                        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                        (command.func)(&ctx, &msg, args).await;
 | 
					 | 
				
			||||||
                                    } else if command.required_perms == PermissionLevel::Restricted
 | 
					 | 
				
			||||||
                                    {
 | 
					 | 
				
			||||||
                                        let _ = msg
 | 
					 | 
				
			||||||
                                            .channel_id
 | 
					 | 
				
			||||||
                                            .say(
 | 
					 | 
				
			||||||
                                                &ctx,
 | 
					 | 
				
			||||||
                                                lm.get(&language.await, "no_perms_restricted"),
 | 
					 | 
				
			||||||
                                            )
 | 
					 | 
				
			||||||
                                            .await;
 | 
					 | 
				
			||||||
                                    } else if command.required_perms == PermissionLevel::Managed {
 | 
					 | 
				
			||||||
                                        let _ = msg
 | 
					 | 
				
			||||||
                                            .channel_id
 | 
					 | 
				
			||||||
                                            .say(
 | 
					 | 
				
			||||||
                                                &ctx,
 | 
					 | 
				
			||||||
                                                lm.get(&language.await, "no_perms_managed")
 | 
					 | 
				
			||||||
                                                    .replace(
 | 
					 | 
				
			||||||
                                                        "{prefix}",
 | 
					 | 
				
			||||||
                                                        &ctx.prefix(msg.guild_id).await,
 | 
					 | 
				
			||||||
                                                    ),
 | 
					                                                    ),
 | 
				
			||||||
                                            )
 | 
					                                                );
 | 
				
			||||||
                                            .await;
 | 
					                                            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                            if msg.id == MessageId(0)
 | 
				
			||||||
 | 
					                                                || !ctx.check_executing(msg.author.id).await
 | 
				
			||||||
 | 
					                                            {
 | 
				
			||||||
 | 
					                                                ctx.set_executing(msg.author.id).await;
 | 
				
			||||||
 | 
					                                                (command.func)(&ctx, &msg, args).await;
 | 
				
			||||||
 | 
					                                                ctx.drop_executing(msg.author.id).await;
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                        } else if command.required_perms
 | 
				
			||||||
 | 
					                                            == PermissionLevel::Restricted
 | 
				
			||||||
 | 
					                                        {
 | 
				
			||||||
 | 
					                                            let _ = msg
 | 
				
			||||||
 | 
					                                                .channel_id
 | 
				
			||||||
 | 
					                                                .say(
 | 
				
			||||||
 | 
					                                                    &ctx,
 | 
				
			||||||
 | 
					                                                    lm.get(&language.await, "no_perms_restricted"),
 | 
				
			||||||
 | 
					                                                )
 | 
				
			||||||
 | 
					                                                .await;
 | 
				
			||||||
 | 
					                                        } else if command.required_perms == PermissionLevel::Managed
 | 
				
			||||||
 | 
					                                        {
 | 
				
			||||||
 | 
					                                            let _ = msg
 | 
				
			||||||
 | 
					                                                .channel_id
 | 
				
			||||||
 | 
					                                                .say(
 | 
				
			||||||
 | 
					                                                    &ctx,
 | 
				
			||||||
 | 
					                                                    lm.get(&language.await, "no_perms_managed")
 | 
				
			||||||
 | 
					                                                        .replace(
 | 
				
			||||||
 | 
					                                                            "{prefix}",
 | 
				
			||||||
 | 
					                                                            &ctx.prefix(msg.guild_id).await,
 | 
				
			||||||
 | 
					                                                        ),
 | 
				
			||||||
 | 
					                                                )
 | 
				
			||||||
 | 
					                                                .await;
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
                                    }
 | 
					                                    }
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                PermissionCheck::Basic(manage_webhooks, embed_links) => {
 | 
				
			||||||
 | 
					                                    let response = lm
 | 
				
			||||||
 | 
					                                        .get(&language.await, "no_perms_general")
 | 
				
			||||||
 | 
					                                        .replace(
 | 
				
			||||||
 | 
					                                            "{manage_webhooks}",
 | 
				
			||||||
 | 
					                                            if manage_webhooks { "✅" } else { "❌" },
 | 
				
			||||||
 | 
					                                        )
 | 
				
			||||||
 | 
					                                        .replace(
 | 
				
			||||||
 | 
					                                            "{embed_links}",
 | 
				
			||||||
 | 
					                                            if embed_links { "✅" } else { "❌" },
 | 
				
			||||||
 | 
					                                        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                    let _ = msg.channel_id.say(&ctx, response).await;
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                PermissionCheck::None => {
 | 
				
			||||||
 | 
					                                    warn!("Missing enough permissions for guild {}", guild.id);
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            Err(e) => {
 | 
				
			||||||
 | 
					                                error!(
 | 
				
			||||||
 | 
					                                    "Error occurred getting permissions in guild {}: {:?}",
 | 
				
			||||||
 | 
					                                    guild.id, e
 | 
				
			||||||
 | 
					                                );
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                            PermissionCheck::Basic(
 | 
					 | 
				
			||||||
                                manage_webhooks,
 | 
					 | 
				
			||||||
                                embed_links,
 | 
					 | 
				
			||||||
                                add_reactions,
 | 
					 | 
				
			||||||
                                manage_messages,
 | 
					 | 
				
			||||||
                            ) => {
 | 
					 | 
				
			||||||
                                let response = lm
 | 
					 | 
				
			||||||
                                    .get(&language.await, "no_perms_general")
 | 
					 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{manage_webhooks}",
 | 
					 | 
				
			||||||
                                        if manage_webhooks { "✅" } else { "❌" },
 | 
					 | 
				
			||||||
                                    )
 | 
					 | 
				
			||||||
                                    .replace("{embed_links}", if embed_links { "✅" } else { "❌" })
 | 
					 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{add_reactions}",
 | 
					 | 
				
			||||||
                                        if add_reactions { "✅" } else { "❌" },
 | 
					 | 
				
			||||||
                                    )
 | 
					 | 
				
			||||||
                                    .replace(
 | 
					 | 
				
			||||||
                                        "{manage_messages}",
 | 
					 | 
				
			||||||
                                        if manage_messages { "✅" } else { "❌" },
 | 
					 | 
				
			||||||
                                    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                                let _ = msg.channel_id.say(&ctx, response).await;
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            PermissionCheck::None => {
 | 
					 | 
				
			||||||
                                warn!("Missing enough permissions for guild {}", guild.id);
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                        Err(e) => {
 | 
					 | 
				
			||||||
                            error!(
 | 
					 | 
				
			||||||
                                "Error occurred getting permissions in guild {}: {:?}",
 | 
					 | 
				
			||||||
                                guild.id, e
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					            // DM Command
 | 
				
			||||||
        // DM Command
 | 
					            else if self.dm_enabled {
 | 
				
			||||||
        else if self.dm_enabled {
 | 
					                if let Some(full_match) = self.dm_regex_matcher.captures(&msg.content[..]) {
 | 
				
			||||||
            if let Some(full_match) = self.dm_regex_matcher.captures(&msg.content[..]) {
 | 
					                    let command = self
 | 
				
			||||||
                let command = self
 | 
					                        .commands
 | 
				
			||||||
                    .commands
 | 
					                        .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
				
			||||||
                    .get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
 | 
					                        .unwrap();
 | 
				
			||||||
                    .unwrap();
 | 
					                    let args = full_match
 | 
				
			||||||
                let args = full_match
 | 
					                        .name("args")
 | 
				
			||||||
                    .name("args")
 | 
					                        .map(|m| m.as_str())
 | 
				
			||||||
                    .map(|m| m.as_str())
 | 
					                        .unwrap_or("")
 | 
				
			||||||
                    .unwrap_or("")
 | 
					                        .to_string();
 | 
				
			||||||
                    .to_string();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                dbg!(command.name);
 | 
					                    dbg!(command.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                (command.func)(&ctx, &msg, args).await;
 | 
					                    if msg.id == MessageId(0) || !ctx.check_executing(msg.author.id).await {
 | 
				
			||||||
 | 
					                        ctx.set_executing(msg.author.id).await;
 | 
				
			||||||
 | 
					                        (command.func)(&ctx, &msg, args).await;
 | 
				
			||||||
 | 
					                        ctx.drop_executing(msg.author.id).await;
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										159
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										159
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -12,12 +12,14 @@ use serenity::{
 | 
				
			|||||||
    async_trait,
 | 
					    async_trait,
 | 
				
			||||||
    cache::Cache,
 | 
					    cache::Cache,
 | 
				
			||||||
    client::{bridge::gateway::GatewayIntents, Client},
 | 
					    client::{bridge::gateway::GatewayIntents, Client},
 | 
				
			||||||
 | 
					    futures::TryFutureExt,
 | 
				
			||||||
    http::{client::Http, CacheHttp},
 | 
					    http::{client::Http, CacheHttp},
 | 
				
			||||||
    model::{
 | 
					    model::{
 | 
				
			||||||
        channel::GuildChannel,
 | 
					        channel::GuildChannel,
 | 
				
			||||||
        channel::Message,
 | 
					        channel::Message,
 | 
				
			||||||
        guild::{Guild, GuildUnavailable},
 | 
					        guild::{Guild, GuildUnavailable},
 | 
				
			||||||
        id::{GuildId, UserId},
 | 
					        id::{GuildId, UserId},
 | 
				
			||||||
 | 
					        interactions::{Interaction, InteractionData, InteractionType},
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    prelude::{Context, EventHandler, TypeMapKey},
 | 
					    prelude::{Context, EventHandler, TypeMapKey},
 | 
				
			||||||
    utils::shard_id,
 | 
					    utils::shard_id,
 | 
				
			||||||
@@ -27,7 +29,7 @@ use sqlx::mysql::MySqlPool;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use dotenv::dotenv;
 | 
					use dotenv::dotenv;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use std::{collections::HashMap, env, sync::Arc};
 | 
					use std::{collections::HashMap, env, sync::Arc, time::Instant};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use crate::{
 | 
					use crate::{
 | 
				
			||||||
    commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
 | 
					    commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
 | 
				
			||||||
@@ -37,8 +39,6 @@ use crate::{
 | 
				
			|||||||
    models::GuildData,
 | 
					    models::GuildData,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
use serenity::futures::TryFutureExt;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
use inflector::Inflector;
 | 
					use inflector::Inflector;
 | 
				
			||||||
use log::info;
 | 
					use log::info;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -46,7 +46,12 @@ use dashmap::DashMap;
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
use tokio::sync::RwLock;
 | 
					use tokio::sync::RwLock;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					use crate::models::UserData;
 | 
				
			||||||
 | 
					use chrono::Utc;
 | 
				
			||||||
use chrono_tz::Tz;
 | 
					use chrono_tz::Tz;
 | 
				
			||||||
 | 
					use serenity::model::prelude::{
 | 
				
			||||||
 | 
					    InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct GuildDataCache;
 | 
					struct GuildDataCache;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,6 +83,65 @@ impl TypeMapKey for PopularTimezones {
 | 
				
			|||||||
    type Value = Arc<Vec<Tz>>;
 | 
					    type Value = Arc<Vec<Tz>>;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					struct CurrentlyExecuting;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					impl TypeMapKey for CurrentlyExecuting {
 | 
				
			||||||
 | 
					    type Value = Arc<RwLock<HashMap<UserId, Instant>>>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					trait LimitExecutors {
 | 
				
			||||||
 | 
					    async fn check_executing(&self, user: UserId) -> bool;
 | 
				
			||||||
 | 
					    async fn set_executing(&self, user: UserId);
 | 
				
			||||||
 | 
					    async fn drop_executing(&self, user: UserId);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[async_trait]
 | 
				
			||||||
 | 
					impl LimitExecutors for Context {
 | 
				
			||||||
 | 
					    async fn check_executing(&self, user: UserId) -> bool {
 | 
				
			||||||
 | 
					        let currently_executing = self
 | 
				
			||||||
 | 
					            .data
 | 
				
			||||||
 | 
					            .read()
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .get::<CurrentlyExecuting>()
 | 
				
			||||||
 | 
					            .cloned()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let lock = currently_executing.read().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lock.get(&user)
 | 
				
			||||||
 | 
					            .map_or(false, |now| now.elapsed().as_secs() < 4)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn set_executing(&self, user: UserId) {
 | 
				
			||||||
 | 
					        let currently_executing = self
 | 
				
			||||||
 | 
					            .data
 | 
				
			||||||
 | 
					            .read()
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .get::<CurrentlyExecuting>()
 | 
				
			||||||
 | 
					            .cloned()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut lock = currently_executing.write().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lock.insert(user, Instant::now());
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn drop_executing(&self, user: UserId) {
 | 
				
			||||||
 | 
					        let currently_executing = self
 | 
				
			||||||
 | 
					            .data
 | 
				
			||||||
 | 
					            .read()
 | 
				
			||||||
 | 
					            .await
 | 
				
			||||||
 | 
					            .get::<CurrentlyExecuting>()
 | 
				
			||||||
 | 
					            .cloned()
 | 
				
			||||||
 | 
					            .unwrap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let mut lock = currently_executing.write().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lock.remove(&user);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
struct Handler;
 | 
					struct Handler;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[async_trait]
 | 
					#[async_trait]
 | 
				
			||||||
@@ -194,6 +258,91 @@ DELETE FROM guilds WHERE guild = ?
 | 
				
			|||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .unwrap();
 | 
					        .unwrap();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
 | 
				
			||||||
 | 
					        let (pool, lm) = get_ctx_data(&&ctx).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        match interaction.kind {
 | 
				
			||||||
 | 
					            InteractionType::ApplicationCommand => {}
 | 
				
			||||||
 | 
					            InteractionType::MessageComponent => {
 | 
				
			||||||
 | 
					                if let (Some(InteractionData::MessageComponent(data)), Some(member)) =
 | 
				
			||||||
 | 
					                    (interaction.clone().data, interaction.clone().member)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    println!("{}", data.custom_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if data.custom_id.starts_with("timezone:") {
 | 
				
			||||||
 | 
					                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
				
			||||||
 | 
					                            .await
 | 
				
			||||||
 | 
					                            .unwrap();
 | 
				
			||||||
 | 
					                        let new_timezone = data.custom_id.replace("timezone:", "").parse::<Tz>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if let Ok(timezone) = new_timezone {
 | 
				
			||||||
 | 
					                            user_data.timezone = timezone.to_string();
 | 
				
			||||||
 | 
					                            user_data.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            let _ = interaction.create_interaction_response(&ctx, |r| {
 | 
				
			||||||
 | 
					                                r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
 | 
					                                    .interaction_response_data(|d| {
 | 
				
			||||||
 | 
					                                        let footer_text = lm.get(&user_data.language, "timezone/footer").replacen(
 | 
				
			||||||
 | 
					                                            "{timezone}",
 | 
				
			||||||
 | 
					                                            &user_data.timezone,
 | 
				
			||||||
 | 
					                                            1,
 | 
				
			||||||
 | 
					                                        );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        let now = Utc::now().with_timezone(&user_data.timezone());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        let content = lm
 | 
				
			||||||
 | 
					                                            .get(&user_data.language, "timezone/set_p")
 | 
				
			||||||
 | 
					                                            .replacen("{timezone}", &user_data.timezone, 1)
 | 
				
			||||||
 | 
					                                            .replacen(
 | 
				
			||||||
 | 
					                                                "{time}",
 | 
				
			||||||
 | 
					                                                &now.format(user_data.meridian().fmt_str_short()).to_string(),
 | 
				
			||||||
 | 
					                                                1,
 | 
				
			||||||
 | 
					                                            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        d.create_embed(|e| e.title(lm.get(&user_data.language, "timezone/set_p_title"))
 | 
				
			||||||
 | 
					                                            .color(*THEME_COLOR)
 | 
				
			||||||
 | 
					                                            .description(content)
 | 
				
			||||||
 | 
					                                            .footer(|f| f.text(footer_text)))
 | 
				
			||||||
 | 
					                                            .flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                                        d
 | 
				
			||||||
 | 
					                                    })
 | 
				
			||||||
 | 
					                            }).await;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    } else if data.custom_id.starts_with("lang:") {
 | 
				
			||||||
 | 
					                        let mut user_data = UserData::from_user(&member.user, &ctx, &pool)
 | 
				
			||||||
 | 
					                            .await
 | 
				
			||||||
 | 
					                            .unwrap();
 | 
				
			||||||
 | 
					                        let lang_code = data.custom_id.replace("lang:", "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        if let Some(lang) = lm.get_language(&lang_code) {
 | 
				
			||||||
 | 
					                            user_data.language = lang.to_string();
 | 
				
			||||||
 | 
					                            user_data.commit_changes(&pool).await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                            let _ = interaction
 | 
				
			||||||
 | 
					                                .create_interaction_response(&ctx, |r| {
 | 
				
			||||||
 | 
					                                    r.kind(InteractionResponseType::ChannelMessageWithSource)
 | 
				
			||||||
 | 
					                                        .interaction_response_data(|d| {
 | 
				
			||||||
 | 
					                                            d.create_embed(|e| {
 | 
				
			||||||
 | 
					                                                e.title(
 | 
				
			||||||
 | 
					                                                    lm.get(&user_data.language, "lang/set_p_title"),
 | 
				
			||||||
 | 
					                                                )
 | 
				
			||||||
 | 
					                                                .color(*THEME_COLOR)
 | 
				
			||||||
 | 
					                                                .description(
 | 
				
			||||||
 | 
					                                                    lm.get(&user_data.language, "lang/set_p"),
 | 
				
			||||||
 | 
					                                                )
 | 
				
			||||||
 | 
					                                            })
 | 
				
			||||||
 | 
					                                        })
 | 
				
			||||||
 | 
					                                })
 | 
				
			||||||
 | 
					                                .await;
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            _ => {}
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[tokio::main]
 | 
					#[tokio::main]
 | 
				
			||||||
@@ -210,6 +359,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
				
			|||||||
        .get_current_user()
 | 
					        .get_current_user()
 | 
				
			||||||
        .map_ok(|user| user.id.as_u64().to_owned())
 | 
					        .map_ok(|user| user.id.as_u64().to_owned())
 | 
				
			||||||
        .await?;
 | 
					        .await?;
 | 
				
			||||||
 | 
					    let application_id = http.get_current_application_info().await?.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1");
 | 
					    let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -275,6 +425,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
				
			|||||||
                | GatewayIntents::GUILDS
 | 
					                | GatewayIntents::GUILDS
 | 
				
			||||||
                | GatewayIntents::GUILD_MESSAGE_REACTIONS
 | 
					                | GatewayIntents::GUILD_MESSAGE_REACTIONS
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
 | 
					        .application_id(application_id.0)
 | 
				
			||||||
        .event_handler(Handler)
 | 
					        .event_handler(Handler)
 | 
				
			||||||
        .framework_arc(framework_arc.clone())
 | 
					        .framework_arc(framework_arc.clone())
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
@@ -309,7 +460,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
 | 
				
			|||||||
        let mut data = client.data.write().await;
 | 
					        let mut data = client.data.write().await;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        data.insert::<GuildDataCache>(Arc::new(guild_data_cache));
 | 
					        data.insert::<GuildDataCache>(Arc::new(guild_data_cache));
 | 
				
			||||||
 | 
					        data.insert::<CurrentlyExecuting>(Arc::new(RwLock::new(HashMap::new())));
 | 
				
			||||||
        data.insert::<SQLPool>(pool);
 | 
					        data.insert::<SQLPool>(pool);
 | 
				
			||||||
        data.insert::<PopularTimezones>(Arc::new(popular_timezones));
 | 
					        data.insert::<PopularTimezones>(Arc::new(popular_timezones));
 | 
				
			||||||
        data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new()));
 | 
					        data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new()));
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user