chipa_webhooks/platform/
ntfy.rs1use serde_json::{Map, Value, json};
2
3use super::Platform;
4
5pub struct Ntfy {
6 url: String,
7 topic: String,
8 title: Option<String>,
9 priority: Option<u8>,
10 tags: Vec<String>,
11}
12
13impl Ntfy {
14 pub fn new(url: impl Into<String>, topic: impl Into<String>) -> Self {
17 Self {
18 url: url.into(),
19 topic: topic.into(),
20 title: None,
21 priority: None,
22 tags: Vec::new(),
23 }
24 }
25
26 pub fn with_title(mut self, title: impl Into<String>) -> Self {
27 self.title = Some(title.into());
28 self
29 }
30
31 pub fn with_priority(mut self, priority: u8) -> Self {
33 self.priority = Some(priority.clamp(1, 5));
34 self
35 }
36
37 pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
38 self.tags = tags.into_iter().map(Into::into).collect();
39 self
40 }
41}
42
43impl Platform for Ntfy {
44 fn build_payload(&self, rendered: &str, hints: &Map<String, Value>) -> Value {
45 let title = hints
46 .get("__ntfy_title")
47 .and_then(Value::as_str)
48 .map(str::to_owned)
49 .or_else(|| self.title.clone());
50
51 let priority = hints
52 .get("__ntfy_priority")
53 .and_then(Value::as_u64)
54 .map(|p| (p as u8).clamp(1, 5))
55 .or(self.priority);
56
57 let mut tags = self.tags.clone();
60 if let Some(extra) = hints.get("__ntfy_tags").and_then(Value::as_str) {
61 tags.extend(extra.split(',').map(|t| t.trim().to_owned()));
62 }
63
64 let mut payload = json!({
65 "topic": self.topic,
66 "message": rendered,
67 });
68
69 if let Some(t) = title {
70 payload["title"] = Value::String(t);
71 }
72
73 if let Some(p) = priority {
74 payload["priority"] = Value::Number(p.into());
75 }
76
77 if !tags.is_empty() {
78 payload["tags"] = Value::Array(tags.into_iter().map(Value::String).collect());
79 }
80
81 payload
82 }
83
84 fn endpoint(&self) -> &str {
85 Box::leak(format!("{}/", self.url.trim_end_matches('/')).into_boxed_str())
88 }
89}