this basically works
This commit is contained in:
commit
33ec144979
|
@ -0,0 +1 @@
|
||||||
|
/target
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "alert2muc"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
|
futures = "0.3"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
tokio-xmpp = "3"
|
||||||
|
xmpp-parsers = "0.19"
|
||||||
|
axum = "0.6"
|
|
@ -0,0 +1,101 @@
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use futures::{SinkExt, StreamExt};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
use tokio_xmpp::{AsyncClient, Packet, Event};
|
||||||
|
use xmpp_parsers::{Jid, BareJid, FullJid, Element};
|
||||||
|
use xmpp_parsers::message::{Body, Message, MessageType};
|
||||||
|
use xmpp_parsers::presence::{Presence, Show as PresenceShow, Type as PresenceType};
|
||||||
|
use xmpp_parsers::muc::Muc;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Handle {
|
||||||
|
room_jid: BareJid,
|
||||||
|
tx: mpsc::Sender<Packet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Handle {
|
||||||
|
pub async fn send_message(&self, msg: String) {
|
||||||
|
let stanza = make_room_message(self.room_jid.clone(), msg);
|
||||||
|
self.tx.send(Packet::Stanza(stanza)).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn run(jid: String, password: String, muc_jid: String) -> Handle {
|
||||||
|
let muc_jid: FullJid = match FullJid::from_str(&muc_jid) {
|
||||||
|
Ok(jid) => jid,
|
||||||
|
Err(err) => panic!("MUC Jid invalid: {:?}", err),
|
||||||
|
};
|
||||||
|
let (tx, mut rx) = mpsc::channel(1);
|
||||||
|
let handle = Handle {
|
||||||
|
room_jid: muc_jid.clone().into(),
|
||||||
|
tx: tx.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut client = AsyncClient::new(&jid, &password).unwrap();
|
||||||
|
loop {
|
||||||
|
match client.next().await {
|
||||||
|
Some(Event::Online { .. }) => {
|
||||||
|
println!("XMPP client now online at {}", jid);
|
||||||
|
let packet = Packet::Stanza(make_join_presence(muc_jid.clone()));
|
||||||
|
client.send(packet).await
|
||||||
|
.unwrap();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Some(_) => {}
|
||||||
|
None => panic!("XMPP cannot connect"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut sink, mut stream) = client.split();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(event) = stream.next().await {
|
||||||
|
match event {
|
||||||
|
Event::Stanza(el) => {
|
||||||
|
if el.is("presence", "jabber:client") {
|
||||||
|
match Presence::try_from(el) {
|
||||||
|
Ok(presence) => {
|
||||||
|
if presence.from == Some(Jid::Full(muc_jid.clone())) {
|
||||||
|
if presence.type_ == PresenceType::Error {
|
||||||
|
println!("Failed to enter MUC {:?}", muc_jid);
|
||||||
|
} else {
|
||||||
|
println!("Entered MUC {:?}", muc_jid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Err(err) => println!("Received invalid presence: {:?}", err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("XMPP end")
|
||||||
|
});
|
||||||
|
tokio::spawn(async move {
|
||||||
|
while let Some(packet) = rx.recv().await {
|
||||||
|
sink.send(packet).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
panic!("channel end")
|
||||||
|
});
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn make_room_message(room_jid: BareJid, text: String) -> Element {
|
||||||
|
let mut message = Message::new(Some(Jid::Bare(room_jid)));
|
||||||
|
message.type_ = MessageType::Groupchat;
|
||||||
|
message.bodies.insert("de".to_string(), Body(text));
|
||||||
|
message.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_join_presence(muc_jid: FullJid) -> Element {
|
||||||
|
let mut presence = Presence::new(PresenceType::None)
|
||||||
|
.with_to(Jid::Full(muc_jid))
|
||||||
|
.with_show(PresenceShow::Dnd);
|
||||||
|
presence.set_status("de".to_string(), "Augen und Ohren nach oben".to_string());
|
||||||
|
presence.add_payload(Muc::new());
|
||||||
|
presence.into()
|
||||||
|
}
|
|
@ -0,0 +1,91 @@
|
||||||
|
use std::{
|
||||||
|
net::SocketAddr,
|
||||||
|
collections::HashMap,
|
||||||
|
sync::{Arc, Mutex},
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::State,
|
||||||
|
routing::post,
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Json, Router,
|
||||||
|
};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
mod jabber;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct Alert {
|
||||||
|
generatorURL: String,
|
||||||
|
startsAt: String,
|
||||||
|
annotations: AlertAnnotations,
|
||||||
|
labels: HashMap<String, String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Clone, Debug)]
|
||||||
|
struct AlertAnnotations {
|
||||||
|
summary: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct AppState {
|
||||||
|
jabber: jabber::Handle,
|
||||||
|
alerts: Arc<Mutex<HashMap<String, Alert>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn alerts(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(payload): Json<Vec<Alert>>,
|
||||||
|
) -> Response {
|
||||||
|
let mut message = "".to_string();
|
||||||
|
let mut alerts = state.alerts.lock().unwrap();
|
||||||
|
for alert in payload.into_iter() {
|
||||||
|
let is_old = if let Some(old_alert) = alerts.get(&alert.generatorURL) {
|
||||||
|
old_alert.startsAt == alert.startsAt
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
if ! is_old {
|
||||||
|
if message != "" {
|
||||||
|
message += "\n";
|
||||||
|
}
|
||||||
|
message += &format!("{}: {}", alert.annotations.summary, alert.generatorURL);
|
||||||
|
alerts.insert(alert.generatorURL.clone(), alert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(alerts);
|
||||||
|
|
||||||
|
if message != "" {
|
||||||
|
let jabber = state.jabber.clone();
|
||||||
|
tokio::spawn(async move {
|
||||||
|
jabber.send_message(message).await;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusCode::OK.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
// initialize tracing
|
||||||
|
tracing_subscriber::fmt::init();
|
||||||
|
|
||||||
|
let jabber = jabber::run(/*secret*/).await;
|
||||||
|
let state = AppState {
|
||||||
|
jabber,
|
||||||
|
alerts: Arc::new(Mutex::new(HashMap::new())),
|
||||||
|
};
|
||||||
|
|
||||||
|
// build our application with a route
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/", post(alerts))
|
||||||
|
.with_state(state);
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], 9022));
|
||||||
|
tracing::debug!("listening on {}", addr);
|
||||||
|
axum::Server::bind(&addr)
|
||||||
|
.serve(app.into_make_service())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
Loading…
Reference in New Issue