aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
author alemi <[email protected]>2024-05-30 18:03:25 +0200
committer alemi <[email protected]>2024-05-30 18:03:25 +0200
commitfc090f165d6413c1eeaff94d696e1f141e8f481b (patch)
treee164257adf4020bae5b0ec661a337b9cebe1a72d
parent6c516a51ba0ad45ea17fdc616e4d1963798d818e (diff)
feat: added auth token support, catch more errorsHEADdev
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml7
-rw-r--r--README.md4
-rw-r--r--src/main.rs59
4 files changed, 53 insertions, 18 deletions
diff --git a/Cargo.lock b/Cargo.lock
index a0001f3..a35ccf5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -874,6 +874,7 @@ dependencies = [
"serde_json",
"tokio",
"tokio-tungstenite",
+ "url",
]
[[package]]
diff --git a/Cargo.toml b/Cargo.toml
index ef1d92b..ff85717 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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"
diff --git a/README.md b/README.md
index d6adae6..afa112d 100644
--- a/README.md
+++ b/README.md
@@ -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:?}");
+ }
},
},
}