2020-08-26 17:26:28 +00:00
|
|
|
#[macro_use]
|
|
|
|
extern crate lazy_static;
|
|
|
|
|
2020-08-09 22:59:31 +00:00
|
|
|
mod commands;
|
2020-09-28 12:42:20 +00:00
|
|
|
mod consts;
|
2020-10-12 20:01:27 +00:00
|
|
|
mod framework;
|
2020-11-21 16:48:20 +00:00
|
|
|
mod language_manager;
|
2020-10-12 20:01:27 +00:00
|
|
|
mod models;
|
|
|
|
mod time_parser;
|
2020-08-06 14:22:13 +00:00
|
|
|
|
|
|
|
use serenity::{
|
2020-10-18 16:26:07 +00:00
|
|
|
async_trait,
|
2020-10-03 16:31:23 +00:00
|
|
|
cache::Cache,
|
2020-10-12 20:01:27 +00:00
|
|
|
client::{bridge::gateway::GatewayIntents, Client},
|
2021-06-27 15:31:54 +00:00
|
|
|
futures::TryFutureExt,
|
2020-10-12 20:01:27 +00:00
|
|
|
http::{client::Http, CacheHttp},
|
2020-10-03 16:31:23 +00:00
|
|
|
model::{
|
2020-10-18 16:26:07 +00:00
|
|
|
channel::GuildChannel,
|
2020-10-03 16:31:23 +00:00
|
|
|
channel::Message,
|
2020-10-25 11:19:21 +00:00
|
|
|
guild::{Guild, GuildUnavailable},
|
2020-10-12 20:01:27 +00:00
|
|
|
id::{GuildId, UserId},
|
2021-06-27 15:31:54 +00:00
|
|
|
interactions::{Interaction, InteractionData, InteractionType},
|
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;
|
2020-08-06 14:22:13 +00:00
|
|
|
|
|
|
|
use dotenv::dotenv;
|
|
|
|
|
2021-06-27 15:31:54 +00:00
|
|
|
use std::{collections::HashMap, env, sync::Arc, time::Instant};
|
2020-08-06 14:22:13 +00:00
|
|
|
|
2020-09-28 12:42:20 +00:00
|
|
|
use crate::{
|
2020-10-12 20:01:27 +00:00
|
|
|
commands::{info_cmds, moderation_cmds, reminder_cmds, todo_cmds},
|
2020-10-13 13:35:13 +00:00
|
|
|
consts::{CNC_GUILD, DEFAULT_PREFIX, SUBSCRIPTION_ROLES, THEME_COLOR},
|
2020-09-28 12:42:20 +00:00
|
|
|
framework::RegexFramework,
|
2020-11-21 16:48:20 +00:00
|
|
|
language_manager::LanguageManager,
|
2021-07-16 20:28:51 +00:00
|
|
|
models::{guild_data::GuildData, user_data::UserData},
|
2020-08-09 22:59:31 +00:00
|
|
|
};
|
2020-10-11 16:41:26 +00:00
|
|
|
|
2020-12-18 12:37:05 +00:00
|
|
|
use inflector::Inflector;
|
2020-10-17 22:56:19 +00:00
|
|
|
use log::info;
|
|
|
|
|
2021-04-12 21:33:02 +00:00
|
|
|
use dashmap::DashMap;
|
|
|
|
|
|
|
|
use tokio::sync::RwLock;
|
|
|
|
|
2021-07-17 21:53:00 +00:00
|
|
|
use crate::models::reminder::{Reminder, ReminderAction};
|
2021-06-27 15:31:54 +00:00
|
|
|
use chrono::Utc;
|
2021-01-19 12:19:20 +00:00
|
|
|
use chrono_tz::Tz;
|
2021-06-27 15:31:54 +00:00
|
|
|
use serenity::model::prelude::{
|
|
|
|
InteractionApplicationCommandCallbackDataFlags, InteractionResponseType,
|
|
|
|
};
|
2021-01-19 12:19:20 +00:00
|
|
|
|
2021-04-12 21:33:02 +00:00
|
|
|
struct GuildDataCache;
|
|
|
|
|
|
|
|
impl TypeMapKey for GuildDataCache {
|
|
|
|
type Value = Arc<DashMap<GuildId, Arc<RwLock<GuildData>>>>;
|
2021-03-24 13:10:57 +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>;
|
|
|
|
}
|
|
|
|
|
2020-09-03 23:29:19 +00:00
|
|
|
struct FrameworkCtx;
|
|
|
|
|
|
|
|
impl TypeMapKey for FrameworkCtx {
|
2021-03-14 10:27:30 +00:00
|
|
|
type Value = Arc<RegexFramework>;
|
2020-09-03 23:29:19 +00:00
|
|
|
}
|
|
|
|
|
2021-01-19 12:19:20 +00:00
|
|
|
struct PopularTimezones;
|
|
|
|
|
|
|
|
impl TypeMapKey for PopularTimezones {
|
|
|
|
type Value = Arc<Vec<Tz>>;
|
|
|
|
}
|
|
|
|
|
2021-05-13 17:50:22 +00:00
|
|
|
struct CurrentlyExecuting;
|
|
|
|
|
|
|
|
impl TypeMapKey for CurrentlyExecuting {
|
2021-05-25 21:43:35 +00:00
|
|
|
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);
|
|
|
|
}
|
2021-05-13 17:50:22 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 16:26:07 +00:00
|
|
|
struct Handler;
|
|
|
|
|
|
|
|
#[async_trait]
|
|
|
|
impl EventHandler for Handler {
|
|
|
|
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();
|
|
|
|
|
|
|
|
{
|
|
|
|
let pool = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<SQLPool>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get SQLPool from data");
|
|
|
|
|
2021-07-17 16:00:47 +00:00
|
|
|
GuildData::from_guild(guild, &pool)
|
|
|
|
.await
|
|
|
|
.unwrap_or_else(|_| {
|
|
|
|
panic!("Failed to create new guild object for {}", guild_id)
|
|
|
|
});
|
2021-02-14 14:10:01 +00:00
|
|
|
}
|
|
|
|
|
2020-10-18 16:26:07 +00:00
|
|
|
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
|
2020-10-23 11:14:29 +00:00
|
|
|
let shard_count = ctx.cache.shard_count().await;
|
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()
|
|
|
|
.await
|
|
|
|
.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",
|
|
|
|
ctx.cache.current_user_id().await.as_u64()
|
|
|
|
)
|
|
|
|
.as_str(),
|
|
|
|
)
|
|
|
|
.header("Authorization", token)
|
|
|
|
.json(&hm)
|
|
|
|
.send()
|
|
|
|
.await;
|
|
|
|
|
|
|
|
if let Err(res) = response {
|
|
|
|
println!("DiscordBots Response: {:?}", res);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-07-17 16:00:47 +00:00
|
|
|
async fn guild_delete(
|
|
|
|
&self,
|
|
|
|
ctx: Context,
|
|
|
|
deleted_guild: GuildUnavailable,
|
|
|
|
_guild: Option<Guild>,
|
|
|
|
) {
|
2020-10-18 16:26:07 +00:00
|
|
|
let pool = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<SQLPool>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get SQLPool from data");
|
|
|
|
|
2021-04-12 21:33:02 +00:00
|
|
|
let guild_data_cache = ctx
|
|
|
|
.data
|
|
|
|
.read()
|
|
|
|
.await
|
|
|
|
.get::<GuildDataCache>()
|
|
|
|
.cloned()
|
|
|
|
.unwrap();
|
2021-07-17 16:00:47 +00:00
|
|
|
guild_data_cache.remove(&deleted_guild.id);
|
2021-03-24 13:10:57 +00:00
|
|
|
|
2020-10-18 16:26:07 +00:00
|
|
|
sqlx::query!(
|
|
|
|
"
|
|
|
|
DELETE FROM guilds WHERE guild = ?
|
|
|
|
",
|
2021-07-17 16:00:47 +00:00
|
|
|
deleted_guild.id.as_u64()
|
2020-10-18 16:26:07 +00:00
|
|
|
)
|
|
|
|
.execute(&pool)
|
|
|
|
.await
|
|
|
|
.unwrap();
|
|
|
|
}
|
2021-06-27 15:31:54 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
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}",
|
2021-07-16 20:28:51 +00:00
|
|
|
&now.format("%H:%M").to_string(),
|
2021-06-27 15:31:54 +00:00
|
|
|
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;
|
|
|
|
}
|
2021-06-27 15:55:59 +00:00
|
|
|
} 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"),
|
|
|
|
)
|
|
|
|
})
|
2021-07-17 21:53:00 +00:00
|
|
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
2021-06-27 15:55:59 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
2021-07-17 21:53:00 +00:00
|
|
|
} else {
|
|
|
|
match Reminder::from_interaction(&ctx, member.user.id, data.custom_id).await
|
|
|
|
{
|
|
|
|
Ok((reminder, action)) => {
|
|
|
|
let response = match action {
|
|
|
|
ReminderAction::Delete => {
|
|
|
|
reminder.delete(&ctx).await;
|
|
|
|
"Reminder has been deleted"
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
let _ = interaction
|
|
|
|
.create_interaction_response(&ctx, |r| {
|
|
|
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
|
|
|
.interaction_response_data(|d| d
|
|
|
|
.content(response)
|
|
|
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(ie) => {
|
|
|
|
let _ = interaction
|
|
|
|
.create_interaction_response(&ctx, |r| {
|
|
|
|
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
|
|
|
.interaction_response_data(|d| d
|
|
|
|
.content(ie.to_string())
|
|
|
|
.flags(InteractionApplicationCommandCallbackDataFlags::EPHEMERAL)
|
|
|
|
)
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|
|
|
|
}
|
2021-06-27 15:31:54 +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");
|
|
|
|
|
|
|
|
let http = Http::new_with_token(&token);
|
|
|
|
|
2020-10-12 20:01:27 +00:00
|
|
|
let logged_in_id = http
|
|
|
|
.get_current_user()
|
|
|
|
.map_ok(|user| user.id.as_u64().to_owned())
|
|
|
|
.await?;
|
2021-06-27 15:31:54 +00:00
|
|
|
let application_id = 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");
|
|
|
|
|
2020-10-12 17:37:14 +00:00
|
|
|
let framework = RegexFramework::new(logged_in_id)
|
2020-10-13 13:35:13 +00:00
|
|
|
.default_prefix(DEFAULT_PREFIX.clone())
|
2020-10-22 09:31:47 +00:00
|
|
|
.case_insensitive(env::var("CASE_INSENSITIVE").map_or(true, |var| var == "1"))
|
2020-10-12 18:12:33 +00:00
|
|
|
.ignore_bots(env::var("IGNORE_BOTS").map_or(true, |var| var == "1"))
|
2021-01-19 12:01:14 +00:00
|
|
|
.dm_enabled(dm_enabled)
|
2020-10-17 14:21:00 +00:00
|
|
|
// info commands
|
2020-09-01 17:48:40 +00:00
|
|
|
.add_command("ping", &info_cmds::PING_COMMAND)
|
2020-08-09 22:59:31 +00:00
|
|
|
.add_command("help", &info_cmds::HELP_COMMAND)
|
|
|
|
.add_command("info", &info_cmds::INFO_COMMAND)
|
2020-10-11 00:42:19 +00:00
|
|
|
.add_command("invite", &info_cmds::INFO_COMMAND)
|
2020-08-09 22:59:31 +00:00
|
|
|
.add_command("donate", &info_cmds::DONATE_COMMAND)
|
2020-09-01 16:07:51 +00:00
|
|
|
.add_command("dashboard", &info_cmds::DASHBOARD_COMMAND)
|
2020-08-27 20:37:44 +00:00
|
|
|
.add_command("clock", &info_cmds::CLOCK_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// reminder commands
|
2020-09-08 22:08:02 +00:00
|
|
|
.add_command("timer", &reminder_cmds::TIMER_COMMAND)
|
2020-09-11 16:41:15 +00:00
|
|
|
.add_command("remind", &reminder_cmds::REMIND_COMMAND)
|
|
|
|
.add_command("r", &reminder_cmds::REMIND_COMMAND)
|
|
|
|
.add_command("interval", &reminder_cmds::INTERVAL_COMMAND)
|
|
|
|
.add_command("i", &reminder_cmds::INTERVAL_COMMAND)
|
2020-09-19 14:20:43 +00:00
|
|
|
.add_command("natural", &reminder_cmds::NATURAL_COMMAND)
|
|
|
|
.add_command("n", &reminder_cmds::NATURAL_COMMAND)
|
|
|
|
.add_command("", &reminder_cmds::NATURAL_COMMAND)
|
2021-03-21 14:18:35 +00:00
|
|
|
.add_command("countdown", &reminder_cmds::COUNTDOWN_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// management commands
|
2020-09-05 20:17:45 +00:00
|
|
|
.add_command("look", &reminder_cmds::LOOK_COMMAND)
|
2020-09-08 22:08:02 +00:00
|
|
|
.add_command("del", &reminder_cmds::DELETE_COMMAND)
|
2020-10-17 14:21:00 +00:00
|
|
|
// to-do commands
|
|
|
|
.add_command("todo", &todo_cmds::TODO_USER_COMMAND)
|
|
|
|
.add_command("todo user", &todo_cmds::TODO_USER_COMMAND)
|
|
|
|
.add_command("todoc", &todo_cmds::TODO_CHANNEL_COMMAND)
|
|
|
|
.add_command("todo channel", &todo_cmds::TODO_CHANNEL_COMMAND)
|
|
|
|
.add_command("todos", &todo_cmds::TODO_GUILD_COMMAND)
|
|
|
|
.add_command("todo server", &todo_cmds::TODO_GUILD_COMMAND)
|
|
|
|
.add_command("todo guild", &todo_cmds::TODO_GUILD_COMMAND)
|
|
|
|
// moderation commands
|
2020-08-18 19:09:21 +00:00
|
|
|
.add_command("blacklist", &moderation_cmds::BLACKLIST_COMMAND)
|
2020-09-02 16:13:17 +00:00
|
|
|
.add_command("restrict", &moderation_cmds::RESTRICT_COMMAND)
|
2020-08-27 20:37:44 +00:00
|
|
|
.add_command("timezone", &moderation_cmds::TIMEZONE_COMMAND)
|
2020-09-01 16:07:51 +00:00
|
|
|
.add_command("prefix", &moderation_cmds::PREFIX_COMMAND)
|
2020-09-01 14:34:50 +00:00
|
|
|
.add_command("lang", &moderation_cmds::LANGUAGE_COMMAND)
|
|
|
|
.add_command("pause", &reminder_cmds::PAUSE_COMMAND)
|
2020-09-01 17:18:45 +00:00
|
|
|
.add_command("offset", &reminder_cmds::OFFSET_COMMAND)
|
2020-09-01 17:37:43 +00:00
|
|
|
.add_command("nudge", &reminder_cmds::NUDGE_COMMAND)
|
2020-09-04 20:21:47 +00:00
|
|
|
.add_command("alias", &moderation_cmds::ALIAS_COMMAND)
|
2020-09-05 20:17:45 +00:00
|
|
|
.add_command("a", &moderation_cmds::ALIAS_COMMAND)
|
2020-08-06 14:22:13 +00:00
|
|
|
.build();
|
|
|
|
|
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-01-19 12:01:14 +00:00
|
|
|
.intents(if dm_enabled {
|
2020-10-12 20:01:27 +00:00
|
|
|
GatewayIntents::GUILD_MESSAGES
|
|
|
|
| GatewayIntents::GUILDS
|
2020-11-23 14:11:57 +00:00
|
|
|
| GatewayIntents::GUILD_MESSAGE_REACTIONS
|
|
|
|
| GatewayIntents::DIRECT_MESSAGES
|
2021-01-19 12:01:14 +00:00
|
|
|
| GatewayIntents::DIRECT_MESSAGE_REACTIONS
|
|
|
|
} else {
|
|
|
|
GatewayIntents::GUILD_MESSAGES
|
|
|
|
| GatewayIntents::GUILDS
|
|
|
|
| GatewayIntents::GUILD_MESSAGE_REACTIONS
|
|
|
|
})
|
2021-06-27 15:31:54 +00:00
|
|
|
.application_id(application_id.0)
|
2020-10-18 16:26:07 +00:00
|
|
|
.event_handler(Handler)
|
2020-09-03 23:29:19 +00:00
|
|
|
.framework_arc(framework_arc.clone())
|
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
|
|
|
{
|
2021-04-12 21:33:02 +00:00
|
|
|
let guild_data_cache = dashmap::DashMap::new();
|
2021-03-24 13:10:57 +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
|
|
|
|
2020-11-22 01:31:50 +00:00
|
|
|
let language_manager = LanguageManager::from_compiled(include_str!(concat!(
|
|
|
|
env!("CARGO_MANIFEST_DIR"),
|
|
|
|
"/assets/",
|
|
|
|
env!("STRINGS_FILE")
|
2021-01-13 19:19:55 +00:00
|
|
|
)))
|
|
|
|
.unwrap();
|
2020-11-21 16:48:20 +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;
|
|
|
|
|
2021-04-12 21:33:02 +00:00
|
|
|
data.insert::<GuildDataCache>(Arc::new(guild_data_cache));
|
2021-05-25 21:43:35 +00:00
|
|
|
data.insert::<CurrentlyExecuting>(Arc::new(RwLock::new(HashMap::new())));
|
2020-08-07 15:45:19 +00:00
|
|
|
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-02-23 13:45:25 +00:00
|
|
|
data.insert::<FrameworkCtx>(framework_arc.clone());
|
2020-11-22 01:31:50 +00:00
|
|
|
data.insert::<LanguageManager>(Arc::new(language_manager))
|
2020-08-07 15:45:19 +00:00
|
|
|
}
|
|
|
|
|
2020-10-17 22:56:19 +00:00
|
|
|
if let Ok((Some(lower), Some(upper))) = env::var("SHARD_RANGE").map(|sr| {
|
|
|
|
let mut split = sr
|
|
|
|
.split(',')
|
|
|
|
.map(|val| val.parse::<u64>().expect("SHARD_RANGE not an integer"));
|
|
|
|
|
|
|
|
(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");
|
|
|
|
|
|
|
|
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")
|
|
|
|
}) {
|
|
|
|
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 {
|
2020-10-12 20:01:27 +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
|
|
|
|
}
|
|
|
|
}
|
2020-10-03 16:31:23 +00:00
|
|
|
|
2020-10-12 20:01:27 +00:00
|
|
|
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).await {
|
|
|
|
check_subscription(&cache_http, guild.owner_id).await
|
|
|
|
} else {
|
|
|
|
false
|
|
|
|
}
|
2020-10-03 16:31:23 +00:00
|
|
|
}
|
2020-12-18 11:46:22 +00:00
|
|
|
|
|
|
|
pub async fn get_ctx_data(ctx: &&Context) -> (MySqlPool, Arc<LanguageManager>) {
|
|
|
|
let pool;
|
|
|
|
let lm;
|
|
|
|
|
|
|
|
{
|
|
|
|
let data = ctx.data.read().await;
|
|
|
|
|
|
|
|
pool = data
|
|
|
|
.get::<SQLPool>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get SQLPool");
|
|
|
|
|
|
|
|
lm = data
|
|
|
|
.get::<LanguageManager>()
|
|
|
|
.cloned()
|
|
|
|
.expect("Could not get LanguageManager");
|
|
|
|
}
|
|
|
|
|
|
|
|
(pool, lm)
|
|
|
|
}
|
2020-12-18 12:37:05 +00:00
|
|
|
|
|
|
|
async fn command_help(
|
|
|
|
ctx: &Context,
|
|
|
|
msg: &Message,
|
|
|
|
lm: Arc<LanguageManager>,
|
|
|
|
prefix: &str,
|
|
|
|
language: &str,
|
|
|
|
command_name: &str,
|
|
|
|
) {
|
|
|
|
let _ = msg
|
|
|
|
.channel_id
|
|
|
|
.send_message(ctx, |m| {
|
|
|
|
m.embed(move |e| {
|
|
|
|
e.title(format!("{} Help", command_name.to_title_case()))
|
|
|
|
.description(
|
|
|
|
lm.get(&language, &format!("help/{}", command_name))
|
|
|
|
.replace("{prefix}", &prefix),
|
|
|
|
)
|
|
|
|
.footer(|f| {
|
|
|
|
f.text(concat!(
|
|
|
|
env!("CARGO_PKG_NAME"),
|
|
|
|
" ver ",
|
|
|
|
env!("CARGO_PKG_VERSION")
|
|
|
|
))
|
|
|
|
})
|
|
|
|
.color(*THEME_COLOR)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.await;
|
|
|
|
}
|