Add basic discord webhook emulator

This commit is contained in:
lemonsh 2022-07-22 18:28:29 +02:00
parent c7ce19275a
commit 8c898caabd
5 changed files with 183 additions and 50 deletions

View File

@ -27,6 +27,7 @@ irc = { version = "0.15", default-features = false, features = ["tls-rust"] }
async-trait = "0.1"
regex = "1.6.0"
hyper = { version = "0.14", features = ["server"] }
ellipse = "0.2.0"
[features]
# debug IRC commands

View File

@ -1,4 +1,4 @@
use std::net::SocketAddr;
use std::{net::SocketAddr, collections::HashMap};
use serde::Deserialize;
@ -39,4 +39,5 @@ pub struct BotConfig {
#[derive(Deserialize)]
pub struct HttpConfig {
pub listen: SocketAddr,
pub webhooks: HashMap<String, String>
}

View File

@ -1,49 +0,0 @@
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<SF>
where
SF: Fn(String, String) -> anyhow::Result<()>,
{
pub cfg: HttpConfig,
pub sendmsg: SF,
}
async fn handle<SF>(_ctx: Arc<HttpContext<SF>>, _req: Request<Body>) -> anyhow::Result<Response<Body>>
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<SF>(context: HttpContext<SF>, 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
}

83
src/web/mod.rs Normal file
View File

@ -0,0 +1,83 @@
use std::{convert::Infallible, sync::Arc};
use hyper::{
header::HeaderValue,
service::{make_service_fn, service_fn},
Body, Request, Response, Server, StatusCode, body::to_bytes,
};
use tokio::sync::broadcast;
use crate::config::HttpConfig;
mod parser;
pub struct HttpContext<SF>
where
SF: Fn(String, String) -> anyhow::Result<()>,
{
pub cfg: HttpConfig,
pub sendmsg: SF,
}
async fn handle<SF>(ctx: Arc<HttpContext<SF>>, req: Request<Body>) -> anyhow::Result<Response<Body>>
where
SF: Fn(String, String) -> anyhow::Result<()> + Send + Sync + 'static,
{
let mime = req
.headers()
.get("Content-Type")
.map(HeaderValue::to_str)
.transpose()?;
if let Some(mime) = mime {
if mime != "application/json" {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("wrong content-type"))?);
}
} else {
return Ok(Response::builder()
.status(StatusCode::BAD_REQUEST)
.body(Body::from("no content-type"))?);
}
let webhook = (&req.uri().path()[1..]).to_string();
let channel = if let Some(c) = ctx.cfg.webhooks.get(&webhook) {
c
} else {
return Ok(Response::builder()
.status(StatusCode::NOT_FOUND)
.body(Body::from("webhook path not registered"))?);
};
let body_bytes = to_bytes(req.into_body()).await?;
let body = String::from_utf8_lossy(&body_bytes);
let response = parser::textify(&body, &webhook)?;
(ctx.sendmsg)(channel.to_string(), response)?;
let resp = Response::builder()
.status(StatusCode::OK)
.body(Body::empty())?;
Ok(resp)
}
pub async fn run<SF>(
context: HttpContext<SF>,
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
}

97
src/web/parser.rs Normal file
View File

@ -0,0 +1,97 @@
use ellipse::Ellipse;
use serde::Deserialize;
use std::fmt::Write;
#[derive(Deserialize)]
struct WebhookData {
content: Option<String>,
username: Option<String>,
embeds: Vec<Embed>
}
#[derive(Deserialize)]
struct Embed {
title: Option<String>,
description: Option<String>,
url: Option<String>,
timestamp: Option<String>,
footer: Option<EmbedFooter>,
image: Option<UrlObject>,
thumbnail: Option<UrlObject>,
video: Option<UrlObject>,
author: Option<EmbedAuthor>,
fields: Vec<EmbedField>,
}
#[derive(Deserialize)]
struct UrlObject {
url: String
}
#[derive(Deserialize)]
struct EmbedAuthor {
name: String
}
#[derive(Deserialize)]
struct EmbedFooter {
text: String
}
#[derive(Deserialize)]
struct EmbedField {
name: String,
value: String
}
pub fn textify(json: &str, webhook_name: &str) -> anyhow::Result<String> {
let wh: WebhookData = serde_json::from_str(json)?;
let mut buf = format!("-- [Webhook: {}]\r\n", wh.username.as_deref().unwrap_or(webhook_name));
if let Some(content) = wh.content {
let content = content.trim().truncate_ellipse(450);
for line in content.lines() {
write!(&mut buf, " {}\r\n", line)?;
}
}
for embed in wh.embeds {
write!(&mut buf, "-> {}\r\n", embed.title.as_deref().unwrap_or("Embed"))?;
if let Some(description) = embed.description {
let description = description.trim().truncate_ellipse(450);
for line in description.lines() {
write!(&mut buf, " {}\r\n", line)?;
}
}
for field in embed.fields {
write!(&mut buf, " + {}\r\n", field.name)?;
let value = field.value.trim().truncate_ellipse(450);
for line in value.lines() {
write!(&mut buf, " {}\r\n", line)?;
}
}
if let Some(url) = embed.url {
write!(&mut buf, " url: {}\r\n", url)?;
}
if let Some(image) = embed.image {
write!(&mut buf, " img: {}\r\n", image.url)?;
}
if let Some(thumbnail) = embed.thumbnail {
write!(&mut buf, " thumb: {}\r\n", thumbnail.url)?;
}
if let Some(video) = embed.video {
write!(&mut buf, " vid: {}\r\n", video.url)?;
}
if let Some(author) = embed.author {
write!(&mut buf, " by: {}\r\n", author.name)?;
}
if let Some(footer) = embed.footer {
write!(&mut buf, " - {}\r\n", footer.text)?;
}
if let Some(timestamp) = embed.timestamp {
write!(&mut buf, " - {}\r\n", timestamp)?;
}
}
buf.push_str("-- end of webhook");
Ok(buf)
}