2020-08-26 17:26:28 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
|
2020-08-09 22:59:31 +00:00
|
|
|
mod commands;
|
2021-09-11 19:40:58 +00:00
|
|
|
mod component_models;
|
2020-09-28 12:42:20 +00:00
|
|
|
mod consts;
|
2020-10-12 20:01:27 +00:00
|
|
|
mod framework;
|
2021-09-22 20:12:29 +00:00
|
|
|
mod hooks;
|
2022-02-01 23:04:31 +00:00
|
|
|
mod interval_parser;
|
2020-10-12 20:01:27 +00:00
|
|
|
mod models;
|
|
|
|
mod time_parser;
|
2020-08-06 14:22:13 +00:00
|
|
|
|
2021-12-20 13:48:18 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
env,
|
|
|
|
sync::{
|
|
|
|
atomic::{AtomicBool, Ordering},
|
|
|
|
Arc,
|
|
|
|
},
|
|
|
|
};
|
2021-09-06 12:46:16 +00:00
|
|
|
|
|
|
|
use chrono_tz::Tz;
|
|
|
|
use dotenv::dotenv;
|
2022-02-19 12:45:33 +00:00
|
|
|
use log::{info, warn};
|
2020-08-06 14:22:13 +00:00
|
|
|
use serenity::{
|
2020-10-18 16:26:07 +00:00
|
|
|
async_trait,
|
2022-02-01 23:04:31 +00:00
|
|
|
client::Client,
|
2020-10-12 20:01:27 +00:00
|
|
|
http::{client::Http, CacheHttp},
|
2020-10-03 16:31:23 +00:00
|
|
|
model::{
|
2021-10-26 20:10:14 +00:00
|
|
|
channel::GuildChannel,
|
2022-02-01 23:04:31 +00:00
|
|
|
gateway::{Activity, GatewayIntents, Ready},
|
|
|
|
guild::{Guild, UnavailableGuild},
|
2020-10-12 20:01:27 +00:00
|
|
|
id::{GuildId, UserId},
|
2021-09-10 17:09:25 +00:00
|
|
|
interactions::Interaction,
|
2020-09-15 13:43:49 +00:00
|
|
|
},
|
2020-10-18 16:26:07 +00:00
|
|
|
prelude::{Context, EventHandler, TypeMapKey},
|
2020-10-23 11:14:29 +00:00
|
|
|
utils::shard_id,
|
2020-08-06 14:22:13 +00:00
|
|
|
};
|
2020-11-20 16:37:39 +00:00
|
|
|
use sqlx::mysql::MySqlPool;
|
2022-02-06 15:47:59 +00:00
|
|
|
use tokio::sync::RwLock;
|
2020-08-06 14:22:13 +00:00
|
|
|
|
2020-09-28 12:42:20 +00:00
|
|
|
use crate::{
|
2021-09-24 11:55:35 +00:00
|
|
|
commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
|
2021-09-11 19:40:58 +00:00
|
|
|
component_models::ComponentDataModel,
|
2022-02-06 15:47:59 +00:00
|
|
|
consts::{CNC_GUILD, SUBSCRIPTION_ROLES, THEME_COLOR},
|
2020-09-28 12:42:20 +00:00
|
|
|
framework::RegexFramework,
|
2021-10-26 20:10:14 +00:00
|
|
|
models::command_macro::CommandMacro,
|
2020-08-09 22:59:31 +00:00
|
|
|
};
|
2020-10-11 16:41:26 +00:00
|
|
|
|
2020-08-06 14:22:13 +00:00
|
|
|
struct SQLPool;
|
|
|
|
|
|
|
|
impl TypeMapKey for SQLPool {
|
2020-11-20 16:37:39 +00:00
|
|
|
type Value = MySqlPool;
|
2020-08-06 14:22:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
struct ReqwestClient;
|
|
|
|
|
|
|
|
impl TypeMapKey for ReqwestClient {
|
|
|
|
type Value = Arc<reqwest::Client>;
|
|
|
|
}
|
|
|
|
|
2021-01-19 12:19:20 +00:00
|
|
|
struct PopularTimezones;
|
|
|
|
|
|
|
|
impl TypeMapKey for PopularTimezones {
|
|
|
|
type Value = Arc<Vec<Tz>>;
|
|
|
|
}
|
|
|
|
|
2021-09-22 20:12:29 +00:00
|
|
|
struct RecordingMacros;
|
|
|
|
|
|
|
|
impl TypeMapKey for RecordingMacros {
|
|
|
|
type Value = Arc<RwLock<HashMap<(GuildId, UserId), CommandMacro>>>;
|
|
|
|
}
|
|
|
|
|
2021-12-20 13:48:18 +00:00
|
|
|
struct Handler {
|
|
|
|
is_loop_running: AtomicBool,
|
|
|
|
}
|
2020-10-18 16:26:07 +00:00
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl EventHandler for Handler {
|
2021-12-20 13:48:18 +00:00
|
|
|
async fn cache_ready(&self, ctx_base: Context, _guilds: Vec<GuildId>) {
|
|
|
|
info!("Cache Ready!");
|
|
|
|
info!("Preparing to send reminders");
|
|
|
|
|
|
|
|
if !self.is_loop_running.load(Ordering::Relaxed) {
|
2022-02-11 17:44:08 +00:00
|
|
|
let ctx1 = ctx_base.clone();
|
|
|
|
let ctx2 = ctx_base.clone();
|
|
|
|
|
|
|
|
let pool1 = ctx1.data.read().await.get::<SQLPool>().cloned().unwrap();
|
|
|
|
let pool2 = ctx2.data.read().await.get::<SQLPool>().cloned().unwrap();
|
|
|
|
|
2022-02-19 12:45:33 +00:00
|
|
|
let run_settings = env::var("DONTRUN").unwrap_or_else(|_| "".to_string());
|
|
|
|
|
|
|
|
if !run_settings.contains("postman") {
|
|
|
|
tokio::spawn(async move {
|
|
|
|
postman::initialize(ctx1, &pool1).await;
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
warn!("Not running postman")
|
|
|
|
}
|
|
|
|
|
|
|
|
if !run_settings.contains("web") {
|
|
|
|
tokio::spawn(async move {
|
|
|
|
reminder_web::initialize(ctx2, pool2).await.unwrap();
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
warn!("Not running web")
|
|
|
|
}
|
2021-12-20 13:48:18 +00:00
|
|
|
|
|
|
|
self.is_loop_running.swap(true, Ordering::Relaxed);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-18 16:26:07 +00:00
|
|
|
async fn channel_delete(&self, ctx: Context, channel: &GuildChannel) {
|
|
|
|
let pool = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<SQLPool>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get SQLPool from data");
|
|
|
|
|
|
|
|
sqlx::query!(
|
|
|
|
"
|
|
|
|
DELETE FROM channels WHERE channel = ?
|
|
|
|
",
|
|
|
|
channel.id.as_u64()
|
|
|
|
)
|
|
|
|
.execute(&pool)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
|
|
|
|
2020-10-23 11:14:29 +00:00
|
|
|
async fn guild_create(&self, ctx: Context, guild: Guild, is_new: bool) {
|
2020-10-18 16:26:07 +00:00
|
|
|
if is_new {
|
2021-02-14 14:10:01 +00:00
|
|
|
let guild_id = guild.id.as_u64().to_owned();
|
|
|
|
|
2021-10-30 19:57:33 +00:00
|
|
|
{
|
|
|
|
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
|
|
|
|
|
|
|
let _ = sqlx::query!("INSERT INTO guilds (guild) VALUES (?)", guild_id)
|
|
|
|
.execute(&pool)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2020-10-18 16:26:07 +00:00
|
|
|
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
|
2021-09-02 22:38:12 +00:00
|
|
|
let shard_count = ctx.cache.shard_count();
|
2021-02-14 14:10:01 +00:00
|
|
|
let current_shard_id = shard_id(guild_id, shard_count);
|
2020-10-24 11:52:49 +00:00
|
|
|
|
|
|
|
let guild_count = ctx
|
|
|
|
.cache
|
|
|
|
.guilds()
|
|
|
|
.iter()
|
|
|
|
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
|
|
|
|
.count() as u64;
|
2020-10-18 16:26:07 +00:00
|
|
|
|
|
|
|
let mut hm = HashMap::new();
|
|
|
|
hm.insert("server_count", guild_count);
|
2020-10-24 11:52:49 +00:00
|
|
|
hm.insert("shard_id", current_shard_id);
|
2020-10-23 11:14:29 +00:00
|
|
|
hm.insert("shard_count", shard_count);
|
2020-10-18 16:26:07 +00:00
|
|
|
|
|
|
|
let client = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<ReqwestClient>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get ReqwestClient from data");
|
|
|
|
|
|
|
|
let response = client
|
|
|
|
.post(
|
|
|
|
format!(
|
|
|
|
"https://top.gg/api/bots/{}/stats",
|
2021-09-02 22:38:12 +00:00
|
|
|
ctx.cache.current_user_id().as_u64()
|
2020-10-18 16:26:07 +00:00
|
|
|
)
|
|
|
|
.as_str(),
|
|
|
|
)
|
|
|
|
.header("Authorization", token)
|
|
|
|
.json(&hm)
|
|
|
|
.send()
|
|
|
|
.await;
|
|
|
|
|
|
|
|
if let Err(res) = response {
|
|
|
|
println!("DiscordBots Response: {:?}", res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-01 23:04:31 +00:00
|
|
|
async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild, _full: Option<Guild>) {
|
2021-10-30 19:57:33 +00:00
|
|
|
let pool = ctx.data.read().await.get::<SQLPool>().cloned().unwrap();
|
|
|
|
let _ = sqlx::query!("DELETE FROM guilds WHERE guild = ?", incomplete.id.0)
|
|
|
|
.execute(&pool)
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
2021-09-10 17:09:25 +00:00
|
|
|
async fn ready(&self, ctx: Context, _: Ready) {
|
|
|
|
ctx.set_activity(Activity::watching("for /remind")).await;
|
|
|
|
}
|
|
|
|
|
|
|
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
|
|
|
match interaction {
|
|
|
|
Interaction::ApplicationCommand(application_command) => {
|
|
|
|
let framework = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<RegexFramework>()
|
|
|
|
.cloned()
|
|
|
|
.expect("RegexFramework not found in context");
|
|
|
|
|
|
|
|
framework.execute(ctx, application_command).await;
|
|
|
|
}
|
2021-09-11 19:40:58 +00:00
|
|
|
Interaction::MessageComponent(component) => {
|
|
|
|
let component_model = ComponentDataModel::from_custom_id(&component.data.custom_id);
|
2021-09-16 13:48:29 +00:00
|
|
|
component_model.act(&ctx, component).await;
|
2021-09-11 19:40:58 +00:00
|
|
|
}
|
2021-09-10 17:09:25 +00:00
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
2020-10-18 16:26:07 +00:00
|
|
|
}
|
|
|
|
|
2020-08-06 14:22:13 +00:00
|
|
|
#[tokio::main]
|
|
|
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
2020-10-17 22:56:19 +00:00
|
|
|
env_logger::init();
|
|
|
|
|
2020-08-06 14:22:13 +00:00
|
|
|
dotenv()?;
|
|
|
|
|
2020-10-11 16:41:26 +00:00
|
|
|
let token = env::var("DISCORD_TOKEN").expect("Missing DISCORD_TOKEN from environment");
|
|
|
|
|
2021-12-20 13:48:18 +00:00
|
|
|
let application_id = {
|
|
|
|
let http = Http::new_with_token(&token);
|
2020-10-11 16:41:26 +00:00
|
|
|
|
2021-12-20 13:48:18 +00:00
|
|
|
http.get_current_application_info().await?.id
|
|
|
|
};
|
2020-10-12 17:37:14 +00:00
|
|
|
|
2021-01-19 12:01:14 +00:00
|
|
|
let dm_enabled = env::var("DM_ENABLED").map_or(true, |var| var == "1");
|
|
|
|
|
2021-10-30 19:57:33 +00:00
|
|
|
let framework = RegexFramework::new()
|
2020-10-12 18:12:33 +00:00
|
|
|
.ignore_bots(env::var("IGNORE_BOTS").map_or(true, |var| var == "1"))
|
2021-09-10 17:09:25 +00:00
|
|
|
.debug_guild(env::var("DEBUG_GUILD").map_or(None, |g| {
|
2021-09-10 23:14:23 +00:00
|
|
|
Some(GuildId(g.parse::<u64>().expect("DEBUG_GUILD must be a guild ID")))
|
2021-09-10 17:09:25 +00:00
|
|
|
}))
|
2021-01-19 12:01:14 +00:00
|
|
|
.dm_enabled(dm_enabled)
|
2020-10-17 14:21:00 +00:00
|
|
|
// info commands
|
2021-11-13 14:12:37 +00:00
|
|
|
.add_command(&info_cmds::HELP_COMMAND)
|
2021-09-06 12:46:16 +00:00
|
|
|
.add_command(&info_cmds::INFO_COMMAND)
|
|
|
|
.add_command(&info_cmds::DONATE_COMMAND)
|
2021-09-10 17:09:25 +00:00
|
|
|
.add_command(&info_cmds::DASHBOARD_COMMAND)
|
|
|
|
.add_command(&info_cmds::CLOCK_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// reminder commands
|
2021-09-12 15:09:57 +00:00
|
|
|
.add_command(&reminder_cmds::TIMER_COMMAND)
|
2021-09-18 12:40:30 +00:00
|
|
|
.add_command(&reminder_cmds::REMIND_COMMAND)
|
2021-09-16 17:30:16 +00:00
|
|
|
// management commands
|
|
|
|
.add_command(&reminder_cmds::DELETE_COMMAND)
|
2021-09-11 19:40:58 +00:00
|
|
|
.add_command(&reminder_cmds::LOOK_COMMAND)
|
2021-09-10 23:14:23 +00:00
|
|
|
.add_command(&reminder_cmds::PAUSE_COMMAND)
|
2021-09-11 19:40:58 +00:00
|
|
|
.add_command(&reminder_cmds::OFFSET_COMMAND)
|
|
|
|
.add_command(&reminder_cmds::NUDGE_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// to-do commands
|
2021-09-24 11:55:35 +00:00
|
|
|
.add_command(&todo_cmds::TODO_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// moderation commands
|
2021-09-10 23:14:23 +00:00
|
|
|
.add_command(&moderation_cmds::TIMEZONE_COMMAND)
|
2021-09-22 20:12:29 +00:00
|
|
|
.add_command(&moderation_cmds::MACRO_CMD_COMMAND)
|
|
|
|
.add_hook(&hooks::CHECK_SELF_PERMISSIONS_HOOK)
|
2021-10-30 19:57:33 +00:00
|
|
|
.add_hook(&hooks::MACRO_CHECK_HOOK);
|
2020-08-06 14:22:13 +00:00
|
|
|
|
2021-03-14 10:27:30 +00:00
|
|
|
let framework_arc = Arc::new(framework);
|
2020-09-03 23:29:19 +00:00
|
|
|
|
2020-10-25 11:19:21 +00:00
|
|
|
let mut client = Client::builder(&token)
|
2021-11-07 13:23:41 +00:00
|
|
|
.intents(GatewayIntents::GUILDS)
|
2021-06-27 15:31:54 +00:00
|
|
|
.application_id(application_id.0)
|
2021-12-20 13:48:18 +00:00
|
|
|
.event_handler(Handler { is_loop_running: AtomicBool::from(false) })
|
2020-10-12 20:01:27 +00:00
|
|
|
.await
|
|
|
|
.expect("Error occurred creating client");
|
2020-08-06 14:22:13 +00:00
|
|
|
|
2020-08-07 15:45:19 +00:00
|
|
|
{
|
2020-11-20 16:37:39 +00:00
|
|
|
let pool = MySqlPool::connect(
|
2020-10-12 20:01:27 +00:00
|
|
|
&env::var("DATABASE_URL").expect("Missing DATABASE_URL from environment"),
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
2020-08-07 15:45:19 +00:00
|
|
|
|
2021-01-19 12:19:20 +00:00
|
|
|
let popular_timezones = sqlx::query!(
|
|
|
|
"SELECT timezone FROM users GROUP BY timezone ORDER BY COUNT(timezone) DESC LIMIT 21"
|
|
|
|
)
|
|
|
|
.fetch_all(&pool)
|
|
|
|
.await
|
|
|
|
.unwrap()
|
|
|
|
.iter()
|
|
|
|
.map(|t| t.timezone.parse::<Tz>().unwrap())
|
|
|
|
.collect::<Vec<Tz>>();
|
|
|
|
|
2020-08-07 15:45:19 +00:00
|
|
|
let mut data = client.data.write().await;
|
|
|
|
|
|
|
|
data.insert::<SQLPool>(pool);
|
2021-01-19 12:19:20 +00:00
|
|
|
data.insert::<PopularTimezones>(Arc::new(popular_timezones));
|
2020-08-07 15:45:19 +00:00
|
|
|
data.insert::<ReqwestClient>(Arc::new(reqwest::Client::new()));
|
2021-09-10 17:09:25 +00:00
|
|
|
data.insert::<RegexFramework>(framework_arc.clone());
|
2021-09-22 20:12:29 +00:00
|
|
|
data.insert::<RecordingMacros>(Arc::new(RwLock::new(HashMap::new())));
|
2020-08-07 15:45:19 +00:00
|
|
|
}
|
|
|
|
|
2021-11-13 14:12:37 +00:00
|
|
|
framework_arc.build_slash(&client.cache_and_http.http).await;
|
|
|
|
|
2020-10-17 22:56:19 +00:00
|
|
|
if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| {
|
2021-09-10 23:14:23 +00:00
|
|
|
let mut split =
|
|
|
|
sr.split(',').map(|val| val.parse::<u64>().expect("SHARD_RANGE not an integer"));
|
2020-10-17 22:56:19 +00:00
|
|
|
|
|
|
|
(split.next(), split.next())
|
|
|
|
}) {
|
|
|
|
let total_shards = env::var("SHARD_COUNT")
|
|
|
|
.map(|shard_count| shard_count.parse::<u64>().ok())
|
|
|
|
.ok()
|
|
|
|
.flatten()
|
|
|
|
.expect("No SHARD_COUNT provided, but SHARD_RANGE was provided");
|
|
|
|
|
2021-09-10 23:14:23 +00:00
|
|
|
assert!(lower < upper, "SHARD_RANGE lower limit is not less than the upper limit");
|
|
|
|
|
|
|
|
info!("Starting client fragment with shards {}-{}/{}", lower, upper, total_shards);
|
|
|
|
|
|
|
|
client.start_shard_range([lower, upper], total_shards).await?;
|
|
|
|
} else if let Ok(total_shards) = env::var("SHARD_COUNT")
|
|
|
|
.map(|shard_count| shard_count.parse::<u64>().expect("SHARD_COUNT not an integer"))
|
|
|
|
{
|
2020-10-17 22:56:19 +00:00
|
|
|
info!("Starting client with {} shards", total_shards);
|
|
|
|
|
|
|
|
client.start_shards(total_shards).await?;
|
|
|
|
} else {
|
|
|
|
info!("Starting client as autosharded");
|
|
|
|
|
|
|
|
client.start_autosharded().await?;
|
|
|
|
}
|
2020-08-06 14:22:13 +00:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
2020-09-15 13:43:49 +00:00
|
|
|
|
|
|
|
pub async fn check_subscription(cache_http: impl CacheHttp, user_id: impl Into<UserId>) -> bool {
|
2020-10-11 16:41:26 +00:00
|
|
|
if let Some(subscription_guild) = *CNC_GUILD {
|
2021-09-10 23:14:23 +00:00
|
|
|
let guild_member = GuildId(subscription_guild).member(cache_http, user_id).await;
|
2020-10-11 16:41:26 +00:00
|
|
|
|
|
|
|
if let Ok(member) = guild_member {
|
|
|
|
for role in member.roles {
|
|
|
|
if SUBSCRIPTION_ROLES.contains(role.as_u64()) {
|
2020-10-12 20:01:27 +00:00
|
|
|
return true;
|
2020-09-15 13:43:49 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
false
|
2020-10-12 20:01:27 +00:00
|
|
|
} else {
|
2020-09-15 13:43:49 +00:00
|
|
|
true
|
|
|
|
}
|
|
|
|
}
|
2021-11-13 14:12:37 +00:00
|
|
|
|
|
|
|
pub async fn check_guild_subscription(
|
|
|
|
cache_http: impl CacheHttp,
|
|
|
|
guild_id: impl Into<GuildId>,
|
|
|
|
) -> bool {
|
|
|
|
if let Some(guild) = cache_http.cache().unwrap().guild(guild_id) {
|
|
|
|
let owner = guild.owner_id;
|
|
|
|
|
|
|
|
check_subscription(&cache_http, owner).await
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
|
|
|
}
|