bot: receive events from webhook

This commit is contained in:
Antoine Martin 2021-09-12 17:36:18 +02:00
parent e6926d5ba2
commit d09070dae7
5 changed files with 95 additions and 15 deletions

View file

@ -2,6 +2,7 @@ use std::{
fs::File, fs::File,
io::{BufReader, BufWriter}, io::{BufReader, BufWriter},
path::PathBuf, path::PathBuf,
sync::mpsc::Receiver,
}; };
use anyhow::Context; use anyhow::Context;
@ -12,7 +13,7 @@ use matrix_sdk::{
}; };
use tracing::{debug, info}; use tracing::{debug, info};
use crate::config::ProloloConfig; use crate::{config::ProloloConfig, webhooks::Event};
mod handlers; mod handlers;
use handlers::autojoin::autojoin_authorized_rooms; 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 /// [`Prololo::init`] **must** be called before this function, otherwise the [`Client`] isn't
/// logged in. /// logged in.
pub async fn run(&self) { pub async fn run(&self, events: Receiver<Event>) {
debug!("running..."); 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 self.client.sync(SyncSettings::default()).await
} }
fn handle_events(events: Receiver<Event>, 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 /// 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 /// file is found, then login using username and password, and save the new session information on
/// disk. /// disk.

View file

@ -4,7 +4,7 @@ use matrix_sdk::ruma::RoomId;
use serde::Deserialize; use serde::Deserialize;
use url::Url; use url::Url;
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize, Clone)]
pub struct ProloloConfig { pub struct ProloloConfig {
/// The URL for the homeserver we should connect to /// The URL for the homeserver we should connect to
pub matrix_homeserver: Url, pub matrix_homeserver: Url,

View file

@ -1,6 +1,7 @@
use std::fs::File; use std::fs::File;
use std::io::BufReader; use std::io::BufReader;
use std::path::PathBuf; use std::path::PathBuf;
use std::sync::mpsc::sync_channel;
use anyhow::Context; use anyhow::Context;
use clap::Clap; use clap::Clap;
@ -13,7 +14,7 @@ mod config;
use config::ProloloConfig; use config::ProloloConfig;
mod webhooks; mod webhooks;
use webhooks::github_webhook; use webhooks::{github_webhook, EventSender};
#[derive(Clap)] #[derive(Clap)]
#[clap(version = "0.1")] #[clap(version = "0.1")]
@ -33,10 +34,14 @@ async fn main() -> anyhow::Result<()> {
let config: ProloloConfig = serde_yaml::from_reader(BufReader::new(config_file)) let config: ProloloConfig = serde_yaml::from_reader(BufReader::new(config_file))
.context("couldn't parse 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")?; let prololo = Prololo::new(config).context("failed to create prololo bot")?;
prololo.init().await.context("failed to init 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)) rocket.launch().await.map_err(|err| anyhow::anyhow!(err))
} }

View file

@ -1,27 +1,47 @@
use anyhow::anyhow; use anyhow::{anyhow, bail};
use rocket::{ use rocket::{
http::Status, http::Status,
request::{FromRequest, Outcome}, request::{FromRequest, Outcome},
Request, Request, State,
}; };
use serde::Deserialize; use serde::Deserialize;
mod signing; mod signing;
use signing::SignedGitHubPayload; use signing::SignedGitHubPayload;
use tracing::{debug, info, warn}; use tracing::{debug, info, warn};
use url::Url;
use crate::webhooks::{Event, EventSender};
const X_GITHUB_EVENT: &str = "X-GitHub-Event"; const X_GITHUB_EVENT: &str = "X-GitHub-Event";
struct GitHubSecret(String); struct GitHubSecret(String);
#[rocket::post("/api/webhooks/github", data = "<payload>")] #[rocket::post("/api/webhooks/github", data = "<payload>")]
pub fn github_webhook(event: GitHubEventType, payload: SignedGitHubPayload) -> &'static str { pub fn github_webhook(
event: GitHubEventType,
payload: SignedGitHubPayload,
sender: &State<EventSender>,
) -> Status {
info!( info!(
"received event {:?} with signed payload:\n{}", "received event {:?} with signed payload:\n{}",
event, payload.0 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)] #[derive(Debug, Deserialize)]
@ -34,6 +54,16 @@ pub enum GitHubEventType {
Unknown, Unknown,
} }
impl GitHubEventType {
fn parse_payload(&self, payload: &SignedGitHubPayload) -> anyhow::Result<GitHubEvent> {
Ok(match self {
Self::Create => GitHubEvent::Create(serde_json::from_str(&payload.0)?),
Self::Unknown => bail!("unknown event type"),
_ => unimplemented!(),
})
}
}
#[rocket::async_trait] #[rocket::async_trait]
impl<'r> FromRequest<'r> for GitHubEventType { impl<'r> FromRequest<'r> for GitHubEventType {
type Error = anyhow::Error; type Error = anyhow::Error;
@ -70,16 +100,37 @@ impl<'r> FromRequest<'r> for GitHubEventType {
} }
} }
enum GitHubEvent { #[derive(Debug)]
Create { ref_type: RefType }, pub enum GitHubEvent {
Create(CreateEvent),
Issues, Issues,
IssueComment, IssueComment,
Push, Push,
} }
#[derive(Deserialize)] #[derive(Debug, Deserialize)]
#[serde(rename_all = "lowercase")] #[serde(rename_all = "lowercase")]
enum RefType { pub enum RefType {
Branch, Branch,
Tag, 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,
}

View file

@ -1,2 +1,11 @@
use std::sync::{mpsc::SyncSender};
mod github; mod github;
pub use github::github_webhook; pub use github::{github_webhook, GitHubEvent};
pub struct EventSender(pub SyncSender<Event>);
#[derive(Debug)]
pub enum Event {
GitHub(GitHubEvent),
}