diff options
| author | 2024-05-30 18:03:25 +0200 | |
|---|---|---|
| committer | 2024-05-30 18:03:25 +0200 | |
| commit | fc090f165d6413c1eeaff94d696e1f141e8f481b (patch) | |
| tree | e164257adf4020bae5b0ec661a337b9cebe1a72d | |
| parent | 6c516a51ba0ad45ea17fdc616e4d1963798d818e (diff) | |
| -rw-r--r-- | Cargo.lock | 1 | ||||
| -rw-r--r-- | Cargo.toml | 7 | ||||
| -rw-r--r-- | README.md | 4 | ||||
| -rw-r--r-- | src/main.rs | 59 |
4 files changed, 53 insertions, 18 deletions
@@ -874,6 +874,7 @@ dependencies = [ "serde_json", "tokio", "tokio-tungstenite", + "url", ] [[package]] @@ -2,8 +2,15 @@ name = "ntfyd" version = "0.1.0" edition = "2021" +authors = [ "alemi <[email protected]>" ] +description = "ntfy.sh background notifications daemon" +license = "MIT" +keywords = ["cli", "daemon", "notifications", "ntfy.sh"] +repository = "https://git.alemi.dev/ntfyd.git" +readme = "README.md" [dependencies] +url = "2.5" clap = { version = "4.5", features = ["derive"] } tokio = { version = "1.37", features = ["rt", "macros", "io-util"] } tokio-tungstenite = "0.21" @@ -9,8 +9,8 @@ it will spawn a single thread tokio runtime, with one worker per topic, and send ```sh # listen from a single topic ntfyd Qw9jRVoQDRC62ZoK -# listen from multiple topics on a custom server -ntfyd --server ntfy.alemi.dev Qw9jRVoQDRC62ZoK VHesmyx3W7I7pvcB Gk1KtDtpSrVqaU6y +# listen from multiple topics on a custom server with authentication +ntfyd --server ntfy.alemi.dev --token tk_asdasdasdasd topic1 topic2 topic3 ``` ### notes diff --git a/src/main.rs b/src/main.rs index 12b8b8b..23f6ab8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,5 @@ use clap::Parser; -use tokio_tungstenite::tungstenite::Message; +use tokio_tungstenite::tungstenite::{client::IntoClientRequest, http::HeaderValue, Message}; use notify_rust::Notification; // TODO im so angry tokio-tungstenite makes me import this! 60 more crates????? i dont care about @@ -13,18 +13,23 @@ struct Args { topics: Vec<String>, /// ntfy.sh server to use - #[arg(short, long, default_value = "ntfy.alemi.dev")] + #[arg(short, long, default_value = "ntfy.sh")] server: String, + + /// auth token to use for accessing topics + #[arg(short, long)] + token: Option<String>, } -#[derive(serde::Deserialize)] +#[allow(unused)] +#[derive(Debug, serde::Deserialize)] struct Event { /// Randomly chosen message identifier pub id: String, /// Message date time, as Unix time stamp pub time: u64, /// Unix time stamp indicating when the message will be deleted, not set if Cache: no is sent - pub expires: u64, + pub expires: Option<u64>, /// Message type, typically you'd be only interested in message // TODO make enum for this field pub event: String, @@ -55,28 +60,50 @@ async fn main() { let mut tasks = tokio::task::JoinSet::new(); for topic in args.topics { - println!("connecting to 'wss://{}/{topic}/ws'", args.server); - match tokio_tungstenite::connect_async(format!("wss://{}/{topic}/ws", args.server)).await { + let url = match url::Url::parse(&format!("wss://{}/{topic}/ws", args.server)) { + Err(e) => { eprintln!("[!] invalid url for topic {topic}: {e}"); continue; }, + Ok(u) => u, + }; + println!("connecting to {url}"); + let mut req = match url.into_client_request() { + Err(e) => { eprintln!("[!] error creating request for topic {topic}: {e}"); continue; }, + Ok(r) => r, + }; + + if let Some(ref auth) = args.token { + match HeaderValue::from_str(&format!("Bearer {auth}")) { + Ok(value) => { req.headers_mut().insert("Authorization", value); }, + Err(e) => eprintln!("[!] error inserting auth header: {e}"), + } + } + + match tokio_tungstenite::connect_async(req).await { Err(e) => eprintln!("failed connecting to topic {topic}: {e}"), - Ok((mut socket, response)) => { - println!("connected to topic {topic}: {:?}", response); + Ok((mut socket, _response)) => { + eprintln!("connected to topic {topic}"); tasks.spawn(async move { while let Some(Ok(msg)) = socket.next().await { match msg { - Message::Close(x) => { eprintln!("topic '{topic}' closed: {x:?}"); break; }, + Message::Close(x) => { println!("topic {topic} closed: {x:?}"); break; }, Message::Ping(_x) => {}, // TODO do we need to pong back? Message::Pong(_x) => {}, - Message::Frame(_x) => panic!("should never receive raw frames"), + Message::Frame(x) => eprintln!("[?] received unexpected raw frame: {x:?}"), Message::Binary(x) => eprintln!("[!] unexpected binary blob on '{topic}': {x:?}"), Message::Text(payload) => match serde_json::from_str::<Event>(&payload) { Err(e) => eprintln!("[!] error parsing event: {e}"), Ok(event) => { - Notification::new() - .summary(event.title.as_deref().unwrap_or(event.topic.as_str())) - .body(event.message.as_deref().unwrap_or("")) - .show_async() - .await - .unwrap(); + if event.event == "message" { + if let Err(e) = Notification::new() + .summary(event.title.as_deref().unwrap_or(event.topic.as_str())) + .body(event.message.as_deref().unwrap_or("")) + .show_async() + .await + { + eprintln!("[!] error sending notification: {e}"); + } + } else { + println!("[{topic}] {event:?}"); + } }, }, } |
