diff --git a/Cargo.toml b/Cargo.toml index eef3f4a..c6faf0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,11 +21,11 @@ serde = "1.0" arrayvec = "0.7" rand = "0.8" meval = "0.2" -async-circe = { git = "https://git.karx.xyz/circe/async-circe", default-features = false } +async-circe = { git = "https://git.karx.xyz/circe/async-circe" } lazy_static = "1.4" sedregex = "0.2" rusqlite = { version = "0.26", features = ["bundled"] } -hyper = { version = "0.14", features = ["server"] } +warp = "0.3" [features] tls = ["async-circe/tls"] diff --git a/sample_uberbot.toml b/sample_uberbot.toml index de3c2cd..5784662 100644 --- a/sample_uberbot.toml +++ b/sample_uberbot.toml @@ -14,3 +14,6 @@ prefix = "!" # Web service config http_listen = "127.0.0.1:8080" + +# Git webhook config +git_channel = "#main" diff --git a/src/bots/git.rs b/src/bots/git.rs new file mode 100644 index 0000000..5d2df41 --- /dev/null +++ b/src/bots/git.rs @@ -0,0 +1,35 @@ +use serde_json::Value::Null; +use tokio::sync::mpsc::Sender; + +pub async fn handle_post( + json: serde_json::Value, + tx: Sender, +) -> Result { + if json["commits"] != Null { + let commits = json["commits"].as_array().unwrap(); + let repo = &json["repository"]["full_name"].as_str().unwrap().trim(); + if commits.len() != 1 { + tx.send(format!("{} new commits on {}:", commits.len(), repo)) + .await + .expect("Failed to send string to main thread"); + for commit in commits { + let author = &commit["author"]["name"].as_str().unwrap().trim(); + let message = &commit["message"].as_str().unwrap().trim(); + tx.send(format!("{} - {}", author, message)) + .await + .expect("Failed to send string to main thread"); + } + } else { + let author = &json["commits"][0]["author"]["name"] + .as_str() + .unwrap() + .trim(); + let message = &json["commits"][0]["message"].as_str().unwrap().trim(); + tx.send(format!("New commit on {}: {} - {}", repo, message, author)) + .await + .expect("Failed to send string to main thread"); + } + } + + Ok(warp::reply::with_status("Ok", warp::http::StatusCode::OK)) +} diff --git a/src/bots/mod.rs b/src/bots/mod.rs index 99c5cd8..6a3a58d 100644 --- a/src/bots/mod.rs +++ b/src/bots/mod.rs @@ -1,3 +1,4 @@ +pub mod git; pub mod leek; pub mod misc; pub mod sed; diff --git a/src/main.rs b/src/main.rs index 893540b..9ae9762 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,10 +12,11 @@ use serde::Deserialize; use std::fmt::Write; use std::fs::File; use std::io::Read; +use std::net::SocketAddr; use std::thread; use std::{collections::HashMap, env}; -use std::net::SocketAddr; use tokio::select; +use tokio::sync::mpsc::{channel, Receiver, Sender}; use tracing_subscriber::EnvFilter; // this will be displayed when the help command is used @@ -54,6 +55,7 @@ pub struct AppState { last_eval: HashMap, titlebot: Titlebot, db: ExecutorConnection, + git_channel: String, } #[derive(Deserialize)] @@ -68,7 +70,8 @@ struct ClientConf { spotify_client_secret: String, prefix: String, db_path: Option, - http_listen: Option + http_listen: Option, + git_channel: String, } #[tokio::main(flavor = "current_thread")] @@ -96,6 +99,10 @@ async fn main() -> anyhow::Result<()> { &client_config.spotify_client_secret, ); + let http_listen = client_config + .http_listen + .unwrap_or_else(|| SocketAddr::from(([127, 0, 0, 1], 5000))); + let config = Config::runtime_config( client_config.channels, client_config.host, @@ -115,10 +122,12 @@ async fn main() -> anyhow::Result<()> { last_eval: HashMap::new(), titlebot: Titlebot::create(spotify_creds).await?, db: db_conn, + git_channel: client_config.git_channel, }; - let http_listen = client_config.http_listen.unwrap_or_else(|| SocketAddr::from(([127, 0, 0, 1], 5000))); - if let Err(e) = executor(state, http_listen).await { + let (git_tx, git_recv) = channel(512); + + if let Err(e) = executor(state, git_tx, git_recv, http_listen).await { tracing::error!("Error in message loop: {}", e); } @@ -130,11 +139,22 @@ async fn main() -> anyhow::Result<()> { Ok(()) } -async fn executor(mut state: AppState, http_listen: SocketAddr) -> anyhow::Result<()> { +async fn executor( + mut state: AppState, + git_tx: Sender, + mut git_recv: Receiver, + http_listen: SocketAddr, +) -> anyhow::Result<()> { let web_db = state.db.clone(); + let git_channel = state.git_channel.clone(); select! { - r = web_service::run(web_db, http_listen) => r?, + r = web_service::run(web_db, git_tx, http_listen) => r?, r = message_loop(&mut state) => r?, + r = git_recv.recv() => { + if let Some(message) = r { + state.client.privmsg(&git_channel, &message).await?; + } + } _ = terminate_signal() => { tracing::info!("Sending QUIT message"); state.client.quit(Some("überbot shutting down")).await?; diff --git a/src/web_service.rs b/src/web_service.rs index ce172ed..7b2fd8b 100644 --- a/src/web_service.rs +++ b/src/web_service.rs @@ -1,23 +1,38 @@ -use std::net::SocketAddr; use crate::ExecutorConnection; -use std::convert::Infallible; -use std::sync::Arc; -use hyper::{Body, Request, Response, Server}; -use hyper::service::{make_service_fn, service_fn}; +use std::net::SocketAddr; +use tokio::sync::mpsc::Sender; +use warp::Filter; -pub async fn run(db: ExecutorConnection, listen: SocketAddr) -> anyhow::Result<()> { - let db = Arc::new(db); +pub async fn run( + db: ExecutorConnection, + tx: Sender, + listen: SocketAddr, +) -> anyhow::Result<()> { + let db_filter = warp::any().map(move || db.clone()); + let db_filter = warp::get().and(db_filter).and_then(handle); - Server::bind(&listen).serve(make_service_fn(|_| { - let db = Arc::clone(&db); - async move { - Ok::<_, Infallible>(service_fn(move |r| handle(r, Arc::clone(&db)))) - } - })).await?; + let tx_filter = warp::any().map(move || tx.clone()); + let tx_filter = warp::path("webhook") + .and(warp::post()) + .and(warp::body::json()) + .and(tx_filter) + .and_then(crate::bots::git::handle_post); + let filter = db_filter.or(tx_filter); + warp::serve(filter).run(listen).await; Ok(()) } -async fn handle(req: Request, db: Arc) -> Result, Infallible> { - Ok(Response::new(Body::from(format!("{:?}", db.get_quote(None).await)))) +async fn handle(db: ExecutorConnection) -> Result { + if let Some((a, b)) = db.get_quote(None).await { + Ok(warp::reply::with_status( + format!("{} {}", a, b), + warp::http::StatusCode::OK, + )) + } else { + Ok(warp::reply::with_status( + "None".into(), + warp::http::StatusCode::NO_CONTENT, + )) + } }