removed all guild data related code

This commit is contained in:
jellywx 2021-10-26 21:10:14 +01:00
parent 44debf93c5
commit e36e718f28
11 changed files with 63 additions and 414 deletions

1
Cargo.lock generated
View File

@ -1201,7 +1201,6 @@ dependencies = [
"base64",
"chrono",
"chrono-tz",
"dashmap",
"dotenv",
"env_logger",
"humantime",

View File

@ -5,7 +5,6 @@ authors = ["jellywx <judesouthworth@pm.me>"]
edition = "2018"
[dependencies]
dashmap = "4.0"
dotenv = "0.15"
humantime = "2.1"
tokio = { version = "1", features = ["process", "full"] }

View File

@ -3,7 +3,6 @@ use regex_command_attr::command;
use serenity::{builder::CreateEmbedFooter, client::Context};
use crate::{
consts::DEFAULT_PREFIX,
framework::{CommandInvoke, CreateGenericResponse},
models::CtxData,
THEME_COLOR,
@ -28,8 +27,6 @@ fn footer(ctx: &Context) -> impl FnOnce(&mut CreateEmbedFooter) -> &mut CreateEm
#[description("Get information about the bot")]
#[group("Info")]
async fn info(ctx: &Context, invoke: &mut CommandInvoke) {
let prefix = ctx.prefix(invoke.guild_id()).await;
let current_user = ctx.cache.current_user();
let footer = footer(ctx);
let _ = invoke
@ -38,18 +35,15 @@ async fn info(ctx: &Context, invoke: &mut CommandInvoke) {
CreateGenericResponse::new().embed(|e| {
e.title("Info")
.description(format!(
"Default prefix: `{default_prefix}`
Reset prefix: `@{user} prefix {default_prefix}`
Help: `{prefix}help`**Welcome to Reminder Bot!**
"Help: `/help`
**Welcome to Reminder Bot!**
Developer: <@203532103185465344>
Icon: <@253202252821430272>
Find me on https://discord.jellywx.com and on https://github.com/JellyWX :)
Invite the bot: https://invite.reminder-bot.com/
Use our dashboard: https://reminder-bot.com/",
default_prefix = *DEFAULT_PREFIX,
user = current_user.name,
prefix = prefix
))
.footer(footer)
.color(*THEME_COLOR)

View File

@ -12,7 +12,7 @@ use crate::{
consts::{EMBED_DESCRIPTION_MAX_LENGTH, THEME_COLOR},
framework::{CommandInvoke, CommandOptions, CreateGenericResponse, OptionValue},
hooks::{CHECK_GUILD_PERMISSIONS_HOOK, CHECK_MANAGED_PERMISSIONS_HOOK},
models::{channel_data::ChannelData, command_macro::CommandMacro, CtxData},
models::{command_macro::CommandMacro, CtxData},
PopularTimezones, RecordingMacros, RegexFramework, SQLPool,
};

View File

@ -11,7 +11,6 @@ use regex_command_attr::command;
use serenity::{builder::CreateEmbed, client::Context, model::channel::Channel};
use crate::{
check_subscription_on_message,
component_models::{
pager::{DelPager, LookPager, Pager},
ComponentDataModel, DelSelector,

View File

@ -10,42 +10,22 @@ const THEME_COLOR_FALLBACK: u32 = 0x8fb677;
use std::{collections::HashSet, env, iter::FromIterator};
use regex::{Regex, RegexBuilder};
use regex::Regex;
use serenity::http::AttachmentType;
lazy_static! {
pub static ref DEFAULT_AVATAR: AttachmentType<'static> = (
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/",
env!("WEBHOOK_AVATAR", "WEBHOOK_AVATAR not provided for compilation")
)) as &[u8],
env!("WEBHOOK_AVATAR"),
)
.into();
include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/assets/",
env!("WEBHOOK_AVATAR", "WEBHOOK_AVATAR not provided for compilation")
)) as &[u8],
env!("WEBHOOK_AVATAR"),
)
.into();
pub static ref REGEX_CHANNEL: Regex = Regex::new(r#"^\s*<#(\d+)>\s*$"#).unwrap();
pub static ref REGEX_ROLE: Regex = Regex::new(r#"<@&(\d+)>"#).unwrap();
pub static ref REGEX_CONTENT_SUBSTITUTION: Regex = Regex::new(r#"<<((?P<user>\d+)|(?P<role>.{1,100}))>>"#).unwrap();
pub static ref REGEX_CHANNEL_USER: Regex = Regex::new(r#"\s*<(#|@)(?:!)?(\d+)>\s*"#).unwrap();
pub static ref REGEX_NATURAL_COMMAND_1: Regex = RegexBuilder::new(
r#"(?P<time>.*?)(?:\s+)(?:send|say)(?:\s+)(?P<msg>.*?)(?:(?:\s+)to(?:\s+)(?P<mentions>((?:<@\d+>)|(?:<@!\d+>)|(?:<#\d+>)|(?:\s+))+))?$"#
)
.dot_matches_new_line(true)
.build()
.unwrap();
pub static ref REGEX_NATURAL_COMMAND_2: Regex = RegexBuilder::new(
r#"(?P<msg>.*)(?:\s+)every(?:\s+)(?P<interval>.*?)(?:(?:\s+)(?:until|for)(?:\s+)(?P<expires>.*?))?$"#
)
.dot_matches_new_line(true)
.build()
.unwrap();
pub static ref SUBSCRIPTION_ROLES: HashSet<u64> = HashSet::from_iter(
env::var("SUBSCRIPTION_ROLES")
.map(|var| var
@ -54,35 +34,23 @@ lazy_static! {
.collect::<Vec<u64>>())
.unwrap_or_else(|_| Vec::new())
);
pub static ref CNC_GUILD: Option<u64> = env::var("CNC_GUILD")
.map(|var| var.parse::<u64>().ok())
.ok()
.flatten();
pub static ref CNC_GUILD: Option<u64> =
env::var("CNC_GUILD").map(|var| var.parse::<u64>().ok()).ok().flatten();
pub static ref MIN_INTERVAL: i64 = env::var("MIN_INTERVAL")
.ok()
.map(|inner| inner.parse::<i64>().ok())
.flatten()
.unwrap_or(600);
pub static ref MAX_TIME: i64 = env::var("MAX_TIME")
.ok()
.map(|inner| inner.parse::<i64>().ok())
.flatten()
.unwrap_or(60 * 60 * 24 * 365 * 50);
pub static ref LOCAL_TIMEZONE: String =
env::var("LOCAL_TIMEZONE").unwrap_or_else(|_| "UTC".to_string());
pub static ref DEFAULT_PREFIX: String =
env::var("DEFAULT_PREFIX").unwrap_or_else(|_| "$".to_string());
pub static ref THEME_COLOR: u32 = env::var("THEME_COLOR").map_or(
THEME_COLOR_FALLBACK,
|inner| u32::from_str_radix(&inner, 16).unwrap_or(THEME_COLOR_FALLBACK)
);
pub static ref THEME_COLOR: u32 = env::var("THEME_COLOR")
.map_or(THEME_COLOR_FALLBACK, |inner| u32::from_str_radix(&inner, 16)
.unwrap_or(THEME_COLOR_FALLBACK));
pub static ref PYTHON_LOCATION: String =
env::var("PYTHON_LOCATION").unwrap_or_else(|_| "venv/bin/python3".to_string());
}

View File

@ -7,7 +7,7 @@ use std::{
};
use log::info;
use regex::{Match, Regex, RegexBuilder};
use regex::{Regex, RegexBuilder};
use serde::{Deserialize, Serialize};
use serenity::{
async_trait,
@ -16,11 +16,11 @@ use serenity::{
client::Context,
framework::Framework,
futures::prelude::future::BoxFuture,
http::{CacheHttp, Http},
http::Http,
model::{
channel::Message,
guild::{Guild, Member},
id::{ChannelId, GuildId, MessageId, RoleId, UserId},
id::{ChannelId, GuildId, RoleId, UserId},
interactions::{
application_command::{
ApplicationCommand, ApplicationCommandInteraction, ApplicationCommandOptionType,
@ -34,7 +34,7 @@ use serenity::{
Result as SerenityResult,
};
use crate::{models::CtxData, LimitExecutors};
use crate::LimitExecutors;
pub struct CreateGenericResponse {
content: String,
@ -89,7 +89,6 @@ impl CreateGenericResponse {
enum InvokeModel {
Slash(ApplicationCommandInteraction),
Component(MessageComponentInteraction),
Text(Message),
}
#[derive(Clone)]
@ -108,10 +107,6 @@ impl CommandInvoke {
Self { model: InvokeModel::Slash(interaction), already_responded: false, deferred: false }
}
fn msg(msg: Message) -> Self {
Self { model: InvokeModel::Text(msg), already_responded: false, deferred: false }
}
pub async fn defer(&mut self, http: impl AsRef<Http>) {
if !self.deferred {
match &self.model {
@ -133,7 +128,6 @@ impl CommandInvoke {
self.deferred = true;
}
InvokeModel::Text(_) => (),
}
}
}
@ -142,7 +136,6 @@ impl CommandInvoke {
match &self.model {
InvokeModel::Slash(i) => i.channel_id,
InvokeModel::Component(i) => i.channel_id,
InvokeModel::Text(m) => m.channel_id,
}
}
@ -150,7 +143,6 @@ impl CommandInvoke {
match &self.model {
InvokeModel::Slash(i) => i.guild_id,
InvokeModel::Component(i) => i.guild_id,
InvokeModel::Text(m) => m.guild_id,
}
}
@ -162,15 +154,13 @@ impl CommandInvoke {
match &self.model {
InvokeModel::Slash(i) => i.user.id,
InvokeModel::Component(i) => i.user.id,
InvokeModel::Text(m) => m.author.id,
}
}
pub async fn member(&self, cache_http: impl CacheHttp) -> Option<Member> {
pub fn member(&self) -> Option<Member> {
match &self.model {
InvokeModel::Slash(i) => i.member.clone(),
InvokeModel::Component(i) => i.member.clone(),
InvokeModel::Text(m) => m.member(cache_http).await.ok(),
}
}
@ -264,26 +254,6 @@ impl CommandInvoke {
})
.await
.map(|_| ()),
InvokeModel::Text(m) => m
.channel_id
.send_message(http, |m| {
m.content(generic_response.content);
if let Some(embed) = generic_response.embed {
m.set_embed(embed);
}
if let Some(components) = generic_response.components {
m.components(|c| {
*c = components;
c
});
}
m
})
.await
.map(|_| ()),
}?;
self.already_responded = true;
@ -484,9 +454,6 @@ pub enum HookResult {
type SlashCommandFn =
for<'fut> fn(&'fut Context, &'fut mut CommandInvoke, CommandOptions) -> BoxFuture<'fut, ()>;
type TextCommandFn =
for<'fut> fn(&'fut Context, &'fut mut CommandInvoke, String) -> BoxFuture<'fut, ()>;
type MultiCommandFn = for<'fut> fn(&'fut Context, &'fut mut CommandInvoke) -> BoxFuture<'fut, ()>;
pub type HookFn = for<'fut> fn(
@ -497,16 +464,9 @@ pub type HookFn = for<'fut> fn(
pub enum CommandFnType {
Slash(SlashCommandFn),
Text(TextCommandFn),
Multi(MultiCommandFn),
}
impl CommandFnType {
pub fn is_slash(&self) -> bool {
!matches!(self, CommandFnType::Text(_))
}
}
pub struct Hook {
pub fun: HookFn,
pub uuid: u128,
@ -697,44 +657,42 @@ impl RegexFramework {
commands: &'a mut CreateApplicationCommands,
) -> &'a mut CreateApplicationCommands {
for command in &self.commands {
if command.fun.is_slash() {
commands.create_application_command(|c| {
c.name(command.names[0]).description(command.desc);
commands.create_application_command(|c| {
c.name(command.names[0]).description(command.desc);
for arg in command.args {
c.create_option(|o| {
o.name(arg.name)
.description(arg.description)
.kind(arg.kind)
.required(arg.required);
for arg in command.args {
c.create_option(|o| {
o.name(arg.name)
.description(arg.description)
.kind(arg.kind)
.required(arg.required);
for option in arg.options {
o.create_sub_option(|s| {
s.name(option.name)
.description(option.description)
.kind(option.kind)
.required(option.required);
for option in arg.options {
o.create_sub_option(|s| {
s.name(option.name)
.description(option.description)
.kind(option.kind)
.required(option.required);
for sub_option in option.options {
s.create_sub_option(|ss| {
ss.name(sub_option.name)
.description(sub_option.description)
.kind(sub_option.kind)
.required(sub_option.required)
});
}
for sub_option in option.options {
s.create_sub_option(|ss| {
ss.name(sub_option.name)
.description(sub_option.description)
.kind(sub_option.kind)
.required(sub_option.required)
});
}
s
});
}
s
});
}
o
});
}
o
});
}
c
});
}
c
});
}
commands
@ -798,7 +756,6 @@ impl RegexFramework {
match command.fun {
CommandFnType::Slash(t) => t(&ctx, &mut command_invoke, args).await,
CommandFnType::Multi(m) => m(&ctx, &mut command_invoke).await,
_ => (),
}
ctx.drop_executing(user_id).await;
@ -820,85 +777,11 @@ impl RegexFramework {
match command.fun {
CommandFnType::Slash(t) => t(&ctx, command_invoke, command_options).await,
CommandFnType::Multi(m) => m(&ctx, command_invoke).await,
_ => (),
}
}
}
#[async_trait]
impl Framework for RegexFramework {
async fn dispatch(&self, ctx: Context, msg: Message) {
async fn check_prefix(ctx: &Context, guild: &Guild, prefix_opt: Option<Match<'_>>) -> bool {
if let Some(prefix) = prefix_opt {
let guild_prefix = ctx.prefix(Some(guild.id)).await;
guild_prefix.as_str() == prefix.as_str()
} else {
true
}
}
// gate to prevent analysing messages unnecessarily
if (msg.author.bot && self.ignore_bots) || msg.content.is_empty() {
return;
}
let user_id = msg.author.id;
let mut invoke = CommandInvoke::msg(msg.clone());
// Guild Command
if let Some(guild) = msg.guild(&ctx) {
if let Some(full_match) = self.command_matcher.captures(&msg.content) {
if check_prefix(&ctx, &guild, full_match.name("prefix")).await {
let command = self
.commands_map
.get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
.unwrap();
let channel_data = ctx.channel_data(invoke.channel_id()).await.unwrap();
if !command.can_blacklist || !channel_data.blacklisted {
let args =
full_match.name("args").map(|m| m.as_str()).unwrap_or("").to_string();
if msg.id == MessageId(0) || !ctx.check_executing(user_id).await {
ctx.set_executing(user_id).await;
match command.fun {
CommandFnType::Text(t) => t(&ctx, &mut invoke, args).await,
CommandFnType::Multi(m) => m(&ctx, &mut invoke).await,
_ => {}
};
ctx.drop_executing(user_id).await;
}
}
}
}
}
// DM Command
else if self.dm_enabled {
if let Some(full_match) = self.dm_regex_matcher.captures(&msg.content[..]) {
let command = self
.commands_map
.get(&full_match.name("cmd").unwrap().as_str().to_lowercase())
.unwrap();
let args = full_match.name("args").map(|m| m.as_str()).unwrap_or("").to_string();
let user_id = invoke.author_id();
if msg.id == MessageId(0) || !ctx.check_executing(user_id).await {
ctx.set_executing(user_id).await;
match command.fun {
CommandFnType::Text(t) => t(&ctx, &mut invoke, args).await,
CommandFnType::Multi(m) => m(&ctx, &mut invoke).await,
_ => {}
};
ctx.drop_executing(user_id).await;
}
}
}
}
async fn dispatch(&self, _ctx: Context, _msg: Message) {}
}

View File

@ -107,7 +107,7 @@ pub async fn check_managed_permissions(
return HookResult::Continue;
}
let member = invoke.member(&ctx).await.unwrap();
let member = invoke.member().unwrap();
let pool = ctx
.data

View File

@ -13,19 +13,17 @@ mod time_parser;
use std::{collections::HashMap, env, sync::Arc, time::Instant};
use chrono_tz::Tz;
use dashmap::DashMap;
use dotenv::dotenv;
use log::info;
use serenity::{
async_trait,
cache::Cache,
client::{bridge::gateway::GatewayIntents, Client},
futures::TryFutureExt,
http::{client::Http, CacheHttp},
model::{
channel::{GuildChannel, Message},
channel::GuildChannel,
gateway::{Activity, Ready},
guild::{Guild, GuildUnavailable},
guild::Guild,
id::{GuildId, UserId},
interactions::Interaction,
},
@ -38,17 +36,11 @@ use tokio::sync::RwLock;
use crate::{
commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
component_models::ComponentDataModel,
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
consts::{CNC_GUILD, SUBSCRIPTION_ROLES, THEME_COLOR},
framework::RegexFramework,
models::{command_macro::CommandMacro, guild_data::GuildData},
models::command_macro::CommandMacro,
};
struct GuildDataCache;
impl TypeMapKey for GuildDataCache {
type Value = Arc<DashMap<GuildId, Arc<RwLock<GuildData>>>>;
}
struct SQLPool;
impl TypeMapKey for SQLPool {
@ -156,20 +148,6 @@ DELETE FROM channels WHERE channel = ?
if is_new {
let guild_id = guild.id.as_u64().to_owned();
{
let pool = ctx
.data
.read()
.await
.get::<SQLPool>()
.cloned()
.expect("Could not get SQLPool from data");
GuildData::from_guild(guild, &pool).await.unwrap_or_else(|_| {
panic!("Failed to create new guild object for {}", guild_id)
});
}
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
let shard_count = ctx.cache.shard_count();
let current_shard_id = shard_id(guild_id, shard_count);
@ -214,34 +192,6 @@ DELETE FROM channels WHERE channel = ?
}
}
async fn guild_delete(
&self,
ctx: Context,
deleted_guild: GuildUnavailable,
_guild: Option<Guild>,
) {
let pool = ctx
.data
.read()
.await
.get::<SQLPool>()
.cloned()
.expect("Could not get SQLPool from data");
let guild_data_cache = ctx.data.read().await.get::<GuildDataCache>().cloned().unwrap();
guild_data_cache.remove(&deleted_guild.id);
sqlx::query!(
"
DELETE FROM guilds WHERE guild = ?
",
deleted_guild.id.as_u64()
)
.execute(&pool)
.await
.unwrap();
}
async fn ready(&self, ctx: Context, _: Ready) {
ctx.set_activity(Activity::watching("for /remind")).await;
}
@ -288,7 +238,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1");
let framework = RegexFramework::new(logged_in_id)
.default_prefix(DEFAULT_PREFIX.clone())
.default_prefix("")
.case_insensitive(env::var("CASE_INSENSITIVE").map_or(true, |var| var == "1"))
.ignore_bots(env::var("IGNORE_BOTS").map_or(true, |var| var == "1"))
.debug_guild(env::var("DEBUG_GUILD").map_or(None, |g| {
@ -337,8 +287,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
.expect("Error occurred creating client");
{
let guild_data_cache = dashmap::DashMap::new();
let pool = MySqlPool::connect(
&env::var("DATABASE_URL").expect("Missing DATABASE_URL from environment"),
)
@ -357,7 +305,6 @@ async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let mut data = client.data.write().await;
data.insert::<GuildDataCache>(Arc::new(guild_data_cache));
data.insert::<CurrentlyExecuting>(Arc::new(RwLock::new(HashMap::new())));
data.insert::<SQLPool>(pool);
data.insert::<PopularTimezones>(Arc::new(popular_timezones));
@ -415,15 +362,3 @@ pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<U
true
}
}
pub async fn check_subscription_on_message(
cache_http: impl CacheHttp + AsRef<Cache>,
msg: &Message,
) -> bool {
check_subscription(&cache_http, &msg.author).await
|| if let Some(guild) = msg.guild(&cache_http) {
check_subscription(&cache_http, guild.owner_id).await
} else {
false
}
}

View File

@ -1,77 +0,0 @@
use log::error;
use serenity::model::guild::Guild;
use sqlx::MySqlPool;
use crate::consts::DEFAULT_PREFIX;
pub struct GuildData {
pub id: u32,
pub name: Option<String>,
pub prefix: String,
}
impl GuildData {
pub async fn from_guild(guild: Guild, pool: &MySqlPool) -> Result<Self, sqlx::Error> {
let guild_id = guild.id.as_u64().to_owned();
match sqlx::query_as!(
Self,
"
SELECT id, name, prefix FROM guilds WHERE guild = ?
",
guild_id
)
.fetch_one(pool)
.await
{
Ok(mut g) => {
g.name = Some(guild.name);
Ok(g)
}
Err(sqlx::Error::RowNotFound) => {
sqlx::query!(
"
INSERT INTO guilds (guild, name, prefix) VALUES (?, ?, ?)
",
guild_id,
guild.name,
*DEFAULT_PREFIX
)
.execute(&pool.clone())
.await?;
Ok(sqlx::query_as!(
Self,
"
SELECT id, name, prefix FROM guilds WHERE guild = ?
",
guild_id
)
.fetch_one(pool)
.await?)
}
Err(e) => {
error!("Unexpected error in guild query: {:?}", e);
Err(e)
}
}
}
pub async fn commit_changes(&self, pool: &MySqlPool) {
sqlx::query!(
"
UPDATE guilds SET name = ?, prefix = ? WHERE id = ?
",
self.name,
self.prefix,
self.id
)
.execute(pool)
.await
.unwrap();
}
}

View File

@ -1,35 +1,23 @@
pub mod channel_data;
pub mod command_macro;
pub mod guild_data;
pub mod reminder;
pub mod timer;
pub mod user_data;
use std::sync::Arc;
use chrono_tz::Tz;
use serenity::{
async_trait,
model::id::{ChannelId, GuildId, UserId},
model::id::{ChannelId, UserId},
prelude::Context,
};
use tokio::sync::RwLock;
use crate::{
consts::DEFAULT_PREFIX,
models::{channel_data::ChannelData, guild_data::GuildData, user_data::UserData},
GuildDataCache, SQLPool,
models::{channel_data::ChannelData, user_data::UserData},
SQLPool,
};
#[async_trait]
pub trait CtxData {
async fn guild_data<G: Into<GuildId> + Send + Sync>(
&self,
guild_id: G,
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error>;
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String;
async fn user_data<U: Into<UserId> + Send + Sync>(
&self,
user_id: U,
@ -45,45 +33,6 @@ pub trait CtxData {
#[async_trait]
impl CtxData for Context {
async fn guild_data<G: Into<GuildId> + Send + Sync>(
&self,
guild_id: G,
) -> Result<Arc<RwLock<GuildData>>, sqlx::Error> {
let guild_id = guild_id.into();
let guild = guild_id.to_guild_cached(&self.cache).unwrap();
let guild_cache = self.data.read().await.get::<GuildDataCache>().cloned().unwrap();
let x = if let Some(guild_data) = guild_cache.get(&guild_id) {
Ok(guild_data.clone())
} else {
let pool = self.data.read().await.get::<SQLPool>().cloned().unwrap();
match GuildData::from_guild(guild, &pool).await {
Ok(d) => {
let lock = Arc::new(RwLock::new(d));
guild_cache.insert(guild_id, lock.clone());
Ok(lock)
}
Err(e) => Err(e),
}
};
x
}
async fn prefix<G: Into<GuildId> + Send + Sync>(&self, guild_id: Option<G>) -> String {
if let Some(guild_id) = guild_id {
self.guild_data(guild_id).await.unwrap().read().await.prefix.clone()
} else {
DEFAULT_PREFIX.clone()
}
}
async fn user_data<U: Into<UserId> + Send + Sync>(
&self,
user_id: U,