144 lines
4.1 KiB
Rust
144 lines
4.1 KiB
Rust
use std::sync::atomic::{AtomicBool, Ordering};
|
|
use std::sync::{Arc, Condvar, OnceLock};
|
|
|
|
use crate::commands::{self, COMMANDS};
|
|
use crate::{constants, tasks, CARG_NO_DISCORD};
|
|
use serenity::all::{ActivityData, Context, GuildId};
|
|
use serenity::all::{EventHandler, GatewayIntents, ShardManager};
|
|
use serenity::model::channel::Message;
|
|
use serenity::model::gateway::Ready;
|
|
use serenity::Client;
|
|
use tracing::{error, info, warn};
|
|
|
|
pub static DO_SHUTDOWN: (AtomicBool, Condvar) = (AtomicBool::new(false), Condvar::new()); // atombool and condvar combo to ensure maximum coverage when the bot needs to power off
|
|
|
|
pub static SHARD_MANAGER: OnceLock<Arc<ShardManager>> = OnceLock::new();
|
|
pub struct BotHandler;
|
|
|
|
unsafe impl Sync for BotHandler {}
|
|
unsafe impl Send for BotHandler {}
|
|
|
|
#[serenity::async_trait]
|
|
impl EventHandler for BotHandler {
|
|
async fn message(&self, ctx: Context, msg: Message) {
|
|
// We do not reply to bots https://en.wikipedia.org/wiki/Email_storm
|
|
if msg.author.bot {
|
|
return;
|
|
}
|
|
|
|
let prefix_regex = format!(
|
|
r"^({}|{}|<@{}>)\s?",
|
|
constants::COMMAND_PREFIX,
|
|
"NPO_PFX",
|
|
ctx.cache.current_user().id
|
|
);
|
|
let cmd_regex = format!(r"{}[A-Za-z0-9_\-]+", prefix_regex);
|
|
|
|
let result = match regex::Regex::new(cmd_regex.as_str())
|
|
.unwrap()
|
|
.find(&msg.content)
|
|
{
|
|
Some(result) => result,
|
|
None => return, // silently exit because not every message is meant for the bot
|
|
};
|
|
|
|
if DO_SHUTDOWN.0.load(Ordering::SeqCst) {
|
|
let _ = msg
|
|
.channel_id
|
|
.say(
|
|
&ctx.http,
|
|
"Sorry! Your request was cancelled because the bot is shutting down.",
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
|
|
let target_cmd_name = regex::Regex::new(prefix_regex.as_str())
|
|
.unwrap()
|
|
.replace(result.as_str(), "")
|
|
.to_string();
|
|
|
|
msg.reply(&ctx.http, target_cmd_name.to_string())
|
|
.await
|
|
.unwrap();
|
|
|
|
if let Some(command) = COMMANDS.read().await.get(&target_cmd_name) {
|
|
if let Some(guild_id) = msg.guild_id {
|
|
if let Some(run_guild_command) = &command.run_guild_command {
|
|
match run_guild_command {
|
|
commands::CommandFnKind::Lua(_) => todo!(),
|
|
commands::CommandFnKind::Rust(cmd) => (cmd)(ctx, msg, Some(guild_id)),
|
|
}
|
|
}
|
|
} else {
|
|
if let Some(run_dm_command) = &command.run_dm_command {
|
|
match run_dm_command {
|
|
commands::CommandFnKind::Lua(_) => todo!(),
|
|
commands::CommandFnKind::Rust(cmd) => (cmd)(ctx, msg, None),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Runs once for every shard once its ready
|
|
async fn ready(&self, ctx: Context, ready: Ready) {
|
|
info!("Shart `{}` is connected!", ready.shard.unwrap().id);
|
|
ctx.set_activity(Some(ActivityData::custom("Initializing.")))
|
|
}
|
|
|
|
/// Runs once when all shards are ready
|
|
async fn shards_ready(&self, ctx: Context, total_shards: u32) {
|
|
tasks::start_tasks(ctx).await;
|
|
info!("{total_shards} shards ready.");
|
|
}
|
|
|
|
// Runs once for every shard when its cache is ready
|
|
async fn cache_ready(&self, _ctx: Context, _guild_id_list: Vec<GuildId>) {
|
|
info!("Cache ready.");
|
|
}
|
|
}
|
|
|
|
pub async fn start() {
|
|
// TODO load this at runtime so the key will not be stored in the binary?
|
|
#[cfg(not(debug_assertions))]
|
|
let token = {
|
|
info!("Initializing bot with production token.");
|
|
include_str!("bot_token.prod")
|
|
};
|
|
#[cfg(debug_assertions)]
|
|
let token = {
|
|
info!("Initializing bot with development token.");
|
|
include_str!("bot_token.dev")
|
|
};
|
|
|
|
let intents = GatewayIntents::DIRECT_MESSAGES
|
|
| GatewayIntents::DIRECT_MESSAGE_REACTIONS
|
|
| GatewayIntents::GUILDS
|
|
| GatewayIntents::GUILD_MODERATION
|
|
// | GatewayIntents::GUILD_EMOJIS_AND_STICKERS
|
|
| GatewayIntents::GUILD_MEMBERS
|
|
| GatewayIntents::GUILD_MESSAGE_REACTIONS
|
|
| GatewayIntents::GUILD_MESSAGES
|
|
| GatewayIntents::MESSAGE_CONTENT;
|
|
|
|
if *CARG_NO_DISCORD.get().unwrap() {
|
|
warn!("ABORTING CONNECTING TO DISCORD, BYE BYE!");
|
|
} else {
|
|
let mut client = match Client::builder(&token, intents)
|
|
.event_handler(BotHandler)
|
|
.await
|
|
{
|
|
Ok(client) => client,
|
|
Err(err) => panic!("Error starting client connection: `{err}`"),
|
|
};
|
|
|
|
SHARD_MANAGER.set(client.shard_manager.clone()).unwrap();
|
|
|
|
if let Err(why) = client.start_shards(2).await {
|
|
error!("Client error: {why:?}");
|
|
}
|
|
}
|
|
|
|
warn!("BOT EXITING");
|
|
}
|