From d09070dae745f8195fca6fcc86fc5a848e6410b5 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Sun, 12 Sep 2021 17:36:18 +0200 Subject: [PATCH] bot: receive events from webhook --- src/bot/mod.rs | 19 ++++++++++-- src/config.rs | 2 +- src/main.rs | 11 +++++-- src/webhooks/github.rs | 67 +++++++++++++++++++++++++++++++++++++----- src/webhooks/mod.rs | 11 ++++++- 5 files changed, 95 insertions(+), 15 deletions(-) diff --git a/src/bot/mod.rs b/src/bot/mod.rs index e48cd90..c009bb8 100644 --- a/src/bot/mod.rs +++ b/src/bot/mod.rs @@ -2,6 +2,7 @@ use std::{ fs::File, io::{BufReader, BufWriter}, path::PathBuf, + sync::mpsc::Receiver, }; use anyhow::Context; @@ -12,7 +13,7 @@ use matrix_sdk::{ }; use tracing::{debug, info}; -use crate::config::ProloloConfig; +use crate::{config::ProloloConfig, webhooks::Event}; mod handlers; use handlers::autojoin::autojoin_authorized_rooms; @@ -61,11 +62,25 @@ impl Prololo { /// /// [`Prololo::init`] **must** be called before this function, otherwise the [`Client`] isn't /// logged in. - pub async fn run(&self) { + pub async fn run(&self, events: Receiver) { debug!("running..."); + + let client = self.client.clone(); + let config = self.config.clone(); + tokio::task::spawn_blocking(move || { + Self::handle_events(events, client, config); + }); + self.client.sync(SyncSettings::default()).await } + fn handle_events(events: Receiver, client: Client, config: ProloloConfig) { + loop { + let event = events.recv().unwrap(); + debug!("received event: {:?}", event); + } + } + /// This loads the session information from an existing file, and tries to login with it. If no such /// file is found, then login using username and password, and save the new session information on /// disk. diff --git a/src/config.rs b/src/config.rs index 8970830..5eeb74c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,7 +4,7 @@ use matrix_sdk::ruma::RoomId; use serde::Deserialize; use url::Url; -#[derive(Debug, Deserialize)] +#[derive(Debug, Deserialize, Clone)] pub struct ProloloConfig { /// The URL for the homeserver we should connect to pub matrix_homeserver: Url, diff --git a/src/main.rs b/src/main.rs index 7a3d1f6..aaf78dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ use std::fs::File; use std::io::BufReader; use std::path::PathBuf; +use std::sync::mpsc::sync_channel; use anyhow::Context; use clap::Clap; @@ -13,7 +14,7 @@ mod config; use config::ProloloConfig; mod webhooks; -use webhooks::github_webhook; +use webhooks::{github_webhook, EventSender}; #[derive(Clap)] #[clap(version = "0.1")] @@ -33,10 +34,14 @@ async fn main() -> anyhow::Result<()> { let config: ProloloConfig = serde_yaml::from_reader(BufReader::new(config_file)) .context("couldn't parse config file")?; + let (sender, receiver) = sync_channel(42); + let prololo = Prololo::new(config).context("failed to create prololo bot")?; prololo.init().await.context("failed to init prololo bot")?; - tokio::spawn(async move { prololo.run().await }); + tokio::spawn(async move { prololo.run(receiver).await }); - let rocket = rocket::build().mount("/", routes![github_webhook]); + let rocket = rocket::build() + .mount("/", routes![github_webhook]) + .manage(EventSender(sender)); rocket.launch().await.map_err(|err| anyhow::anyhow!(err)) } diff --git a/src/webhooks/github.rs b/src/webhooks/github.rs index f1bfcbc..df0e62c 100644 --- a/src/webhooks/github.rs +++ b/src/webhooks/github.rs @@ -1,27 +1,47 @@ -use anyhow::anyhow; +use anyhow::{anyhow, bail}; use rocket::{ http::Status, request::{FromRequest, Outcome}, - Request, + Request, State, }; use serde::Deserialize; mod signing; use signing::SignedGitHubPayload; use tracing::{debug, info, warn}; +use url::Url; + +use crate::webhooks::{Event, EventSender}; const X_GITHUB_EVENT: &str = "X-GitHub-Event"; struct GitHubSecret(String); #[rocket::post("/api/webhooks/github", data = "")] -pub fn github_webhook(event: GitHubEventType, payload: SignedGitHubPayload) -> &'static str { +pub fn github_webhook( + event: GitHubEventType, + payload: SignedGitHubPayload, + sender: &State, +) -> Status { info!( "received event {:?} with signed payload:\n{}", event, payload.0 ); - "OK" + let event = match event.parse_payload(&payload) { + Ok(event) => event, + Err(e) => { + warn!( + "couldn't parse payload for event {:?}: {}\n{}", + event, e, payload.0 + ); + return Status::BadRequest; + } + }; + + sender.0.send(Event::GitHub(event)).unwrap(); + + Status::Ok } #[derive(Debug, Deserialize)] @@ -34,6 +54,16 @@ pub enum GitHubEventType { Unknown, } +impl GitHubEventType { + fn parse_payload(&self, payload: &SignedGitHubPayload) -> anyhow::Result { + Ok(match self { + Self::Create => GitHubEvent::Create(serde_json::from_str(&payload.0)?), + Self::Unknown => bail!("unknown event type"), + _ => unimplemented!(), + }) + } +} + #[rocket::async_trait] impl<'r> FromRequest<'r> for GitHubEventType { type Error = anyhow::Error; @@ -70,16 +100,37 @@ impl<'r> FromRequest<'r> for GitHubEventType { } } -enum GitHubEvent { - Create { ref_type: RefType }, +#[derive(Debug)] +pub enum GitHubEvent { + Create(CreateEvent), Issues, IssueComment, Push, } -#[derive(Deserialize)] +#[derive(Debug, Deserialize)] #[serde(rename_all = "lowercase")] -enum RefType { +pub enum RefType { Branch, Tag, } + +#[derive(Debug, Deserialize)] +pub struct GitHubUser { + login: String, +} + +#[derive(Debug, Deserialize)] +pub struct Repository { + name: String, + full_name: String, + html_url: Url, +} + +#[derive(Debug, Deserialize)] +pub struct CreateEvent { + r#ref: String, + ref_type: RefType, + repository: Repository, + sender: GitHubUser, +} diff --git a/src/webhooks/mod.rs b/src/webhooks/mod.rs index f8096f5..1cf921b 100644 --- a/src/webhooks/mod.rs +++ b/src/webhooks/mod.rs @@ -1,2 +1,11 @@ +use std::sync::{mpsc::SyncSender}; + mod github; -pub use github::github_webhook; +pub use github::{github_webhook, GitHubEvent}; + +pub struct EventSender(pub SyncSender); + +#[derive(Debug)] +pub enum Event { + GitHub(GitHubEvent), +}