spiffbot/src/bot.rs
2024-10-11 01:36:28 -03:00

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");
}