reminder-bot/src/main.rs

344 lines
11 KiB
Rust
Raw Normal View History

2020-08-26 17:26:28 +00:00
#[macro_use]
extern crate lazy_static;
2020-08-09 22:59:31 +00:00
mod commands;
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
mod component_models;
mod consts;
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
mod event_handlers;
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
mod hooks;
2022-02-01 23:04:31 +00:00
mod interval_parser;
2024-03-24 20:23:16 +00:00
mod metrics;
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
mod models;
2024-03-24 20:23:16 +00:00
mod postman;
2024-02-24 15:01:54 +00:00
#[cfg(test)]
mod test;
mod time_parser;
mod utils;
2024-03-24 20:23:16 +00:00
mod web;
2020-08-06 14:22:13 +00:00
2022-03-21 23:11:52 +00:00
use std::{
collections::HashMap,
env,
error::Error as StdError,
fmt::{Debug, Display, Formatter},
path::Path,
2022-03-21 23:11:52 +00:00
};
use chrono_tz::Tz;
2024-03-24 20:23:16 +00:00
use log::warn;
2024-01-06 19:48:17 +00:00
use poise::serenity_prelude::{
model::{
gateway::GatewayIntents,
id::{GuildId, UserId},
},
ClientBuilder,
2020-08-06 14:22:13 +00:00
};
2024-07-07 15:31:23 +00:00
use serenity::all::ActivityData;
2022-02-19 14:32:03 +00:00
use sqlx::{MySql, Pool};
2022-03-21 23:11:52 +00:00
use tokio::sync::{broadcast, broadcast::Sender, RwLock};
2020-08-06 14:22:13 +00:00
2024-03-24 20:23:16 +00:00
use crate::metrics::init_metrics;
2024-02-24 15:01:54 +00:00
#[cfg(test)]
use crate::test::TestContext;
#[cfg(not(test))]
use crate::{
2024-02-17 18:55:16 +00:00
commands::{
allowed_dm, clock, clock_context_menu::clock_context_menu, command_macro, dashboard,
delete, donate, help, info, look, multiline, nudge, offset, pause, remind, settings, timer,
timezone, todo, webhook,
2024-02-17 18:55:16 +00:00
},
consts::THEME_COLOR,
2022-02-19 14:32:03 +00:00
event_handlers::listener,
hooks::all_checks,
2021-10-26 20:10:14 +00:00
models::command_macro::CommandMacro,
2020-08-09 22:59:31 +00:00
};
2022-02-19 14:32:03 +00:00
type Database = MySql;
2020-08-06 14:22:13 +00:00
2022-02-19 14:32:03 +00:00
type Error = Box<dyn std::error::Error + Send + Sync>;
2024-02-24 15:01:54 +00:00
#[cfg(test)]
type Context<'a> = TestContext<'a>;
#[cfg(not(test))]
2022-02-19 14:32:03 +00:00
type Context<'a> = poise::Context<'a, Data, Error>;
2024-02-24 15:01:54 +00:00
2022-09-07 17:27:13 +00:00
type ApplicationContext<'a> = poise::ApplicationContext<'a, Data, Error>;
2020-08-06 14:22:13 +00:00
2022-02-19 14:32:03 +00:00
pub struct Data {
database: Pool<Database>,
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
2024-02-06 20:08:59 +00:00
recording_macros: RwLock<HashMap<(GuildId, UserId), CommandMacro>>,
2022-02-19 14:32:03 +00:00
popular_timezones: Vec<Tz>,
2022-09-07 17:27:13 +00:00
_broadcast: Sender<()>,
2021-12-20 13:48:18 +00:00
}
impl Debug for Data {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "Data {{ .. }}")
}
}
2022-03-21 23:11:52 +00:00
struct Ended;
impl Debug for Ended {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("Process ended.")
}
}
impl Display for Ended {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("Process ended.")
}
}
impl StdError for Ended {}
2022-12-10 16:21:43 +00:00
#[tokio::main(flavor = "multi_thread")]
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
2022-03-21 23:11:52 +00:00
async fn main() -> Result<(), Box<dyn StdError + Send + Sync>> {
let (tx, mut rx) = broadcast::channel(16);
tokio::select! {
output = _main(tx) => output,
_ = rx.recv() => Err(Box::new(Ended) as Box<dyn StdError + Send + Sync>)
}
}
2024-02-24 15:01:54 +00:00
#[cfg(not(test))]
2022-03-21 23:11:52 +00:00
async fn _main(tx: Sender<()>) -> Result<(), Box<dyn StdError + Send + Sync>> {
2020-10-17 22:56:19 +00:00
env_logger::init();
if Path::new("/etc/reminder-rs/config.env").exists() {
dotenv::from_path("/etc/reminder-rs/config.env")?;
} else {
let _ = dotenv::dotenv();
}
2020-08-06 14:22:13 +00:00
2024-10-13 18:01:37 +00:00
let args = env::args().collect::<Vec<_>>();
let cmd_word = args.last().map(|w| w.as_str());
let database =
Pool::connect(&env::var("DATABASE_URL").expect("No database URL provided")).await.unwrap();
match cmd_word {
Some("clean") => {
let sent_clean_age = env::var("SENT_CLEAN_AGE")?;
if sent_clean_age.is_empty() {
panic!("No SENT_CLEAN_AGE")
}
sqlx::query!(
"
DELETE FROM reminders
WHERE `utc_time` < NOW() - INTERVAL ? DAY
AND status != 'pending'
ORDER BY `utc_time`
LIMIT 1000
",
sent_clean_age
)
.execute(&database)
.await?;
let total_clean_age = env::var("TOTAL_CLEAN_AGE");
if let Ok(total_clean_age) = total_clean_age {
sqlx::query!(
"
DELETE FROM reminders
WHERE `utc_time` < NOW() - INTERVAL ? DAY
ORDER BY `utc_time`
LIMIT 1000
",
total_clean_age
)
.execute(&database)
.await?;
}
Ok(())
}
_ => {
let discord_token =
env::var("DISCORD_TOKEN").expect("Missing DISCORD_TOKEN from environment");
let options = poise::FrameworkOptions {
commands: vec![
help::command(),
info::command(),
clock::command(),
donate::command(),
clock_context_menu(),
dashboard::command(),
timezone::command(),
poise::Command {
subcommands: vec![allowed_dm::set::set(), allowed_dm::unset::unset()],
..allowed_dm::allowed_dm()
},
poise::Command {
subcommands: vec![poise::Command {
subcommands: vec![
settings::ephemeral_confirmations::set::set(),
settings::ephemeral_confirmations::unset::unset(),
],
..settings::ephemeral_confirmations::ephemeral_confirmations()
}],
..settings::settings()
},
webhook::command(),
poise::Command {
2024-10-13 18:01:37 +00:00
subcommands: vec![
command_macro::delete_macro::delete_macro(),
command_macro::finish_macro::finish_macro(),
command_macro::list_macro::list_macro(),
command_macro::record_macro::record_macro(),
command_macro::run_macro::run_macro(),
],
..command_macro::command_macro()
},
2024-10-13 18:01:37 +00:00
pause::command(),
offset::command(),
nudge::command(),
look::command(),
delete::command(),
poise::Command {
2024-10-13 18:01:37 +00:00
subcommands: vec![
timer::list::list(),
timer::start::start(),
timer::delete::delete(),
],
..timer::timer()
},
2024-10-13 18:01:37 +00:00
multiline::command(),
remind::command(),
poise::Command {
2024-10-13 18:01:37 +00:00
subcommands: vec![
poise::Command {
subcommands: vec![
todo::guild::add::add(),
todo::guild::view::view(),
],
..todo::guild::guild()
},
poise::Command {
subcommands: vec![
todo::channel::add::add(),
todo::channel::view::view(),
],
..todo::channel::channel()
},
poise::Command {
subcommands: vec![todo::user::add::add(), todo::user::view::view()],
..todo::user::user()
},
],
..todo::todo()
},
],
2024-10-13 18:01:37 +00:00
allowed_mentions: None,
command_check: Some(|ctx| Box::pin(all_checks(ctx))),
event_handler: |ctx, event, _framework, data| Box::pin(listener(ctx, event, data)),
on_error: |error| {
Box::pin(async move {
match error {
poise::FrameworkError::CommandCheckFailed { .. } => {
// suppress error
}
error => {
if let Err(e) = poise::builtins::on_error(error).await {
log::error!("Error while handling error: {}", e);
}
}
}
})
},
..Default::default()
};
// Start metrics
init_metrics();
sqlx::migrate!().run(&database).await?;
let popular_timezones = sqlx::query!(
"
SELECT IFNULL(timezone, 'UTC') AS timezone
FROM users
WHERE timezone IS NOT NULL
GROUP BY timezone
ORDER BY COUNT(timezone) DESC
LIMIT 21
"
)
.fetch_all(&database)
.await
.unwrap()
.iter()
.map(|t| t.timezone.parse::<Tz>().unwrap())
.collect::<Vec<Tz>>();
let framework = poise::Framework::builder()
.setup(move |ctx, _bot, framework| {
Box::pin(async move {
poise::builtins::register_globally(ctx, &framework.options().commands)
.await?;
let kill_tx = tx.clone();
let kill_recv = tx.subscribe();
let ctx1 = ctx.clone();
let ctx2 = ctx.clone();
let pool1 = database.clone();
let pool2 = database.clone();
let run_settings = env::var("DONTRUN").unwrap_or_else(|_| "".to_string());
if !run_settings.contains("postman") {
tokio::spawn(async move {
match postman::initialize(kill_recv, ctx1, &pool1).await {
Ok(_) => {}
Err(e) => {
panic!("postman exiting: {}", e);
}
};
});
} else {
warn!("Not running postman");
2023-07-31 16:59:38 +00:00
}
2024-03-24 20:23:16 +00:00
2024-10-13 18:01:37 +00:00
if !run_settings.contains("web") {
tokio::spawn(async move {
web::initialize(kill_tx, ctx2, pool2).await.unwrap();
});
} else {
warn!("Not running web");
}
2022-02-19 14:32:03 +00:00
2024-10-13 18:01:37 +00:00
Ok(Data {
database,
popular_timezones,
recording_macros: Default::default(),
_broadcast: tx,
})
})
2022-02-19 14:32:03 +00:00
})
2024-10-13 18:01:37 +00:00
.options(options)
.build();
2024-01-06 19:48:17 +00:00
2024-10-13 18:01:37 +00:00
let mut client = ClientBuilder::new(&discord_token, GatewayIntents::GUILDS)
.framework(framework)
.activity(ActivityData::watching("for /remind"))
.await?;
2024-01-06 19:48:17 +00:00
2024-10-13 18:01:37 +00:00
client.start_autosharded().await?;
2020-08-06 14:22:13 +00:00
2024-10-13 18:01:37 +00:00
Ok(())
}
}
2020-08-06 14:22:13 +00:00
}