diff --git a/Cargo.toml b/Cargo.toml index 5d0e85e..4f772af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,7 @@ futures-util = "0.3" irc = { version = "0.15", default-features = false, features = ["tls-rust"] } async-trait = "0.1" regex = "1.6.0" +hyper = { version = "0.14", features = ["server"] } [features] # debug IRC commands diff --git a/sample_uberbot.toml b/sample_uberbot.toml index f98bc41..82261b7 100644 --- a/sample_uberbot.toml +++ b/sample_uberbot.toml @@ -19,3 +19,5 @@ search_limit = 5 # optional, default: 3 client_id = "" client_secret = "" +[web] # optional, web service disabled if missing +listen = "127.0.0.1:8080" \ No newline at end of file diff --git a/src/config.rs b/src/config.rs index 375b06b..b504bcc 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,3 +1,5 @@ +use std::net::SocketAddr; + use serde::Deserialize; #[derive(Deserialize)] @@ -6,6 +8,7 @@ pub struct UberConfig { pub irc: IrcConfig, pub spotify: Option, pub bot: BotConfig, + pub web: Option } #[derive(Deserialize)] @@ -32,3 +35,8 @@ pub struct BotConfig { pub history_depth: usize, pub search_limit: Option, } + +#[derive(Deserialize)] +pub struct HttpConfig { + pub listen: SocketAddr, +} diff --git a/src/main.rs b/src/main.rs index 6c70de7..8374e17 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,6 +15,7 @@ use crate::commands::sed::Sed; use crate::commands::spotify::Spotify; use crate::commands::title::Title; use crate::commands::waifu::Waifu; +use crate::web::HttpContext; use futures_util::stream::StreamExt; use irc::client::prelude::Config; use irc::client::{Client, ClientStream}; @@ -28,6 +29,7 @@ use tracing::Level; use crate::config::UberConfig; use crate::database::{DbExecutor, ExecutorConnection}; +mod web; mod bot; mod commands; mod config; @@ -98,10 +100,21 @@ async fn main() -> anyhow::Result<()> { let (ctx, _) = broadcast::channel(1); let (etx, mut erx) = unbounded_channel(); - let mut bot = Bot::new(cfg.irc.prefix, db_conn, cfg.bot.history_depth, { + let sf = { let client = client.clone(); move |target, msg| Ok(client.send_privmsg(target, msg)?) + }; + + let http_task = cfg.web.map(|http| { + let http_ctx = ctx.subscribe(); + let context = HttpContext { cfg: http, sendmsg: sf.clone() }; + tokio::spawn(async move { + if let Err(e) = web::run(context, http_ctx).await { + tracing::error!("Fatal error in web service: {}", e); + } + }) }); + let mut bot = Bot::new(cfg.irc.prefix, db_conn, cfg.bot.history_depth, sf); bot.add_command("help".into(), Help); bot.add_command("waifu".into(), Waifu::default()); @@ -154,13 +167,13 @@ async fn main() -> anyhow::Result<()> { tracing::info!("Closing services..."); let _ = ctx.send(()); - message_loop_task - .await - .unwrap_or_else(|e| tracing::warn!("Couldn't join the web service: {:?}", e)); + message_loop_task.await.unwrap(); tracing::info!("Message loop finished"); - exec_thread - .join() - .unwrap_or_else(|e| tracing::warn!("Couldn't join the database: {:?}", e)); + if let Some(t) = http_task { + t.await.unwrap(); + tracing::info!("Web service finished"); + } + exec_thread.join().unwrap(); tracing::info!("DB Executor thread finished"); tracing::info!("Shutdown complete!"); diff --git a/src/web.rs b/src/web.rs new file mode 100644 index 0000000..3b411dc --- /dev/null +++ b/src/web.rs @@ -0,0 +1,49 @@ +use std::{convert::Infallible, sync::Arc}; + +use hyper::{ + service::{make_service_fn, service_fn}, + Body, Request, Response, Server, StatusCode, +}; +use tokio::sync::broadcast; + +use crate::config::HttpConfig; + +pub struct HttpContext +where + SF: Fn(String, String) -> anyhow::Result<()>, +{ + pub cfg: HttpConfig, + pub sendmsg: SF, +} + +async fn handle(_ctx: Arc>, _req: Request) -> anyhow::Result> +where + SF: Fn(String, String) -> anyhow::Result<()> + Send + Sync + 'static, +{ + let resp = Response::builder() + .status(StatusCode::OK) + .body(Body::empty())?; + Ok(resp) +} + +pub async fn run(context: HttpContext, mut shutdown: broadcast::Receiver<()>) -> hyper::Result<()> +where + SF: Fn(String, String) -> anyhow::Result<()> + Send + Sync + 'static, +{ + let ctx = Arc::new(context); + let make_service = make_service_fn({ + let ctx = ctx.clone(); + move |_conn| { + let ctx = ctx.clone(); + let service = service_fn(move |req| handle(ctx.clone(), req)); + async move { Ok::<_, Infallible>(service) } + } + }); + + let server = Server::bind(&ctx.cfg.listen).serve(make_service); + server + .with_graceful_shutdown(async { + shutdown.recv().await.unwrap(); + }) + .await +}