uberbot/src/main.rs

223 lines
7.4 KiB
Rust
Raw Permalink Normal View History

2022-07-28 11:39:05 -05:00
#![warn(clippy::pedantic)]
#![allow(clippy::module_name_repetitions, clippy::too_many_lines)]
2022-07-17 13:27:20 -05:00
2022-07-17 13:10:34 -05:00
use fancy_regex::Regex;
use std::str::FromStr;
use std::sync::Arc;
2022-07-20 03:58:33 -05:00
use std::{env, fs};
2022-07-28 06:21:17 -05:00
use std::{process, thread};
2022-01-26 05:58:49 -06:00
use crate::bot::Bot;
2022-07-17 13:10:34 -05:00
use crate::commands::eval::Eval;
use crate::commands::help::Help;
2022-07-17 13:27:20 -05:00
use crate::commands::leek::{Leet, Mock, Owo};
2022-07-19 08:07:55 -05:00
use crate::commands::quotes::{Grab, Quot, Search, SearchNext};
2022-07-17 13:10:34 -05:00
use crate::commands::sed::Sed;
use crate::commands::spotify::Spotify;
use crate::commands::title::Title;
use crate::commands::waifu::Waifu;
2022-08-06 17:49:06 -05:00
use crate::commands::pkg::Pkg;
2022-07-20 16:08:35 -05:00
use crate::web::HttpContext;
2022-01-26 05:58:49 -06:00
use futures_util::stream::StreamExt;
use irc::client::prelude::Config;
use irc::client::{Client, ClientStream};
2022-01-26 05:58:49 -06:00
use irc::proto::{ChannelExt, Command, Prefix};
2022-07-16 17:05:21 -05:00
use rspotify::Credentials;
2022-04-16 18:37:06 -05:00
use tokio::select;
2022-01-27 17:44:50 -06:00
use tokio::sync::mpsc::unbounded_channel;
2022-07-28 06:21:17 -05:00
use tokio::sync::{broadcast, mpsc};
use tracing::Level;
2021-12-27 06:39:01 -06:00
2022-07-16 05:21:23 -05:00
use crate::config::UberConfig;
use crate::database::{DbExecutor, ExecutorConnection};
2022-01-26 05:58:49 -06:00
2022-07-15 10:58:45 -05:00
mod bot;
mod commands;
2022-07-15 10:58:45 -05:00
mod config;
mod database;
2022-07-17 13:10:34 -05:00
mod history;
2022-07-28 06:21:17 -05:00
mod web;
2021-12-27 06:39:01 -06:00
#[cfg(unix)]
async fn terminate_signal() {
use tokio::signal::unix::{signal, SignalKind};
let mut sigterm = signal(SignalKind::terminate()).unwrap();
let mut sigint = signal(SignalKind::interrupt()).unwrap();
2021-12-31 17:51:54 -06:00
tracing::debug!("Installed ctrl+c handler");
2021-12-27 06:39:01 -06:00
select! {
2022-07-16 16:00:43 -05:00
_ = sigterm.recv() => (),
_ = sigint.recv() => ()
2021-12-27 06:39:01 -06:00
}
}
#[cfg(windows)]
async fn terminate_signal() {
use tokio::signal::windows::ctrl_c;
let mut ctrlc = ctrl_c().unwrap();
2021-12-31 17:51:54 -06:00
tracing::debug!("Installed ctrl+c handler");
2021-12-27 06:39:01 -06:00
let _ = ctrlc.recv().await;
}
#[tokio::main]
2021-12-27 06:39:01 -06:00
async fn main() -> anyhow::Result<()> {
let config_var = env::var("UBERBOT_CONFIG");
let config_path = config_var.as_deref().unwrap_or("uberbot.toml");
println!("Loading config from '{}'...", config_path);
let config_str = fs::read_to_string(config_path)?;
let cfg: UberConfig = toml::from_str(&config_str)?;
tracing_subscriber::fmt::fmt()
.with_max_level({
if let Some(o) = cfg.log_level.as_deref() {
Level::from_str(o)?
} else {
Level::INFO
}
})
.init();
2022-07-24 05:22:00 -05:00
if cfg.bot.prefixes.is_empty() {
tracing::error!("You have to specify at least one prefix");
process::exit(1);
}
2022-07-20 03:58:33 -05:00
let (db_exec, db_conn) =
DbExecutor::create(cfg.bot.db_path.as_deref().unwrap_or("uberbot.db3"))?;
2022-07-16 17:05:21 -05:00
let exec_thread = thread::spawn(move || db_exec.run());
2022-01-01 15:35:27 -06:00
2022-01-26 05:58:49 -06:00
let uber_ver = concat!("Überbot ", env!("CARGO_PKG_VERSION"));
let irc_config = Config {
nickname: Some(cfg.irc.nickname.unwrap_or_else(|| cfg.irc.username.clone())),
2022-07-16 05:21:23 -05:00
username: Some(cfg.irc.username.clone()),
realname: Some(cfg.irc.username),
server: Some(cfg.irc.host),
port: Some(cfg.irc.port),
use_tls: Some(cfg.irc.tls),
channels: cfg.irc.channels,
umodes: cfg.irc.mode,
2022-01-26 05:58:49 -06:00
user_info: Some(uber_ver.into()),
version: Some(uber_ver.into()),
..Config::default()
};
let mut client = Client::from_config(irc_config).await?;
let stream = client.stream()?;
2022-01-26 05:58:49 -06:00
client.identify()?;
let client = Arc::new(client);
let (ctx, _) = broadcast::channel(1);
let (etx, mut erx) = unbounded_channel();
2022-07-20 16:08:35 -05:00
let sf = {
2022-07-16 05:21:23 -05:00
let client = client.clone();
move |target, msg| Ok(client.send_privmsg(target, msg)?)
2022-07-20 16:08:35 -05:00
};
let http_task = cfg.web.map(|http| {
let http_ctx = ctx.subscribe();
2022-07-28 06:21:17 -05:00
let context = HttpContext {
cfg: http,
sendmsg: sf.clone(),
};
2022-07-20 16:08:35 -05:00
tokio::spawn(async move {
if let Err(e) = web::run(context, http_ctx).await {
tracing::error!("Fatal error in web service: {}", e);
}
})
2022-07-16 05:21:23 -05:00
});
2022-07-24 05:22:00 -05:00
let mut bot = Bot::new(cfg.bot.prefixes, db_conn, cfg.bot.history_depth, sf);
2022-07-16 05:21:23 -05:00
2022-07-16 16:00:43 -05:00
bot.add_command("help".into(), Help);
2022-07-16 17:40:58 -05:00
bot.add_command("waifu".into(), Waifu::default());
bot.add_command("owo".into(), Owo);
2022-07-17 13:27:20 -05:00
bot.add_command("leet".into(), Leet);
bot.add_command("mock".into(), Mock);
2022-07-16 17:05:21 -05:00
bot.add_command("ev".into(), Eval::default());
2022-07-17 13:10:34 -05:00
bot.add_command("grab".into(), Grab);
bot.add_command("quot".into(), Quot);
2022-07-19 08:07:55 -05:00
let search_limit = cfg.bot.search_limit.unwrap_or(3);
bot.add_command("qsearch".into(), Search::new(search_limit));
bot.add_command("qnext".into(), SearchNext::new(search_limit));
2022-08-06 17:49:06 -05:00
bot.add_command("pkg".into(), Pkg::default());
2022-07-17 13:10:34 -05:00
bot.add_trigger(
Regex::new(r"^(?:(?<u>\S+):\s+)?s/(?<r>[^/]*)/(?<w>[^/]*)(?:/(?<f>[a-z]*))?\s*")?,
Sed,
);
2022-07-16 17:05:21 -05:00
if let Some(spotcfg) = cfg.spotify {
let creds = Credentials::new(&spotcfg.client_id, &spotcfg.client_secret);
let spotify = Spotify::new(creds).await?;
2022-07-17 13:27:20 -05:00
bot.add_trigger(Regex::new(r"(?:https?|spotify):(?://open\.spotify\.com/)?(track|artist|album|playlist)[/:]([a-zA-Z\d]*)")?, spotify);
2022-07-16 17:05:21 -05:00
} else {
2022-07-17 13:27:20 -05:00
tracing::warn!("Spotify module is disabled, because the config is missing");
2022-07-16 17:05:21 -05:00
}
bot.add_trigger(Regex::new(r"https?://[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b[-a-zA-Z0-9()@:%_\+.~#?&//=]*")?, Title::new()?);
2022-07-16 17:40:58 -05:00
#[cfg(feature = "debug")]
{
use commands::debug::*;
bot.add_command("lastmsg".into(), LastMsg);
}
2022-07-16 17:05:21 -05:00
let message_loop_task = tokio::spawn(async move {
if let Err(e) = message_loop(stream, bot).await {
let _err = etx.send(e);
2022-01-15 11:18:26 -06:00
}
});
select! {
2021-12-31 17:51:54 -06:00
_ = terminate_signal() => {
tracing::info!("Received shutdown signal, sending QUIT message");
client.send_quit("überbot shutting down")?;
}
e = erx.recv() => {
if let Some(e) = e {
tracing::error!("An error has occurred, shutting down: {}", e);
} else {
tracing::error!("Error channel has been dropped due to an unknown error, shutting down");
}
2021-12-27 06:39:01 -06:00
}
}
tracing::info!("Closing services...");
let _ = ctx.send(());
2022-07-20 16:08:35 -05:00
message_loop_task.await.unwrap();
2022-07-16 05:21:23 -05:00
tracing::info!("Message loop finished");
2022-07-20 16:08:35 -05:00
if let Some(t) = http_task {
t.await.unwrap();
tracing::info!("Web service finished");
}
exec_thread.join().unwrap();
2022-07-16 17:05:21 -05:00
tracing::info!("DB Executor thread finished");
tracing::info!("Shutdown complete!");
Ok(())
}
2021-12-27 06:39:01 -06:00
2022-07-28 06:21:17 -05:00
async fn message_loop<SF>(mut stream: ClientStream, bot: Bot<SF>) -> anyhow::Result<()>
where
SF: Fn(String, String) -> anyhow::Result<()> + Send + Sync + 'static,
{
2022-07-28 06:18:40 -05:00
let bot = Arc::new(bot);
let (cancelled_send, mut cancelled_recv) = mpsc::channel::<()>(1);
while let Some(message) = stream.next().await.transpose()? {
2022-07-28 06:18:40 -05:00
if let Command::PRIVMSG(origin, content) = message.command {
2022-01-26 05:58:49 -06:00
if origin.is_channel_name() {
2022-07-28 06:18:40 -05:00
if let Some(author) = message.prefix.and_then(|p| match p {
Prefix::Nickname(name, _, _) => Some(name),
2022-07-17 13:27:20 -05:00
Prefix::ServerName(_) => None,
2022-01-26 05:58:49 -06:00
}) {
2022-07-28 06:18:40 -05:00
let bot = bot.clone();
let cancelled_send = cancelled_send.clone();
tokio::spawn(async move {
2022-07-28 06:21:17 -05:00
bot.handle_message(origin, author, content, cancelled_send)
.await;
2022-07-28 06:18:40 -05:00
});
2022-01-26 05:58:49 -06:00
} else {
tracing::warn!("Couldn't get the author for a message");
}
}
}
}
2022-07-28 06:21:05 -05:00
drop(cancelled_send);
2022-07-28 06:18:40 -05:00
let _ = cancelled_recv.recv().await;
2021-12-27 06:39:01 -06:00
Ok(())
}