use std::collections::HashMap; use std::convert::Infallible; use async_trait::async_trait; use automation_lib::device::{Device, LuaDeviceCreate}; use automation_macro::{Device, LuaDeviceConfig}; use lua_typed::Typed; use mlua::LuaSerdeExt; use serde::{Deserialize, Serialize}; use serde_repr::*; use tracing::{error, trace, warn}; #[derive(Debug, Serialize_repr, Deserialize, Clone, Copy, Typed)] #[repr(u8)] #[serde(rename_all = "snake_case")] pub enum Priority { Min = 1, Low, Default, High, Max, } crate::register_type!(Priority); #[derive(Debug, Serialize, Deserialize, Clone, Typed)] #[serde(rename_all = "snake_case", tag = "action")] pub enum ActionType { Broadcast { #[serde(skip_serializing_if = "HashMap::is_empty")] #[serde(default)] extras: HashMap, }, // View, // Http } #[derive(Debug, Serialize, Deserialize, Clone, Typed)] pub struct Action { #[serde(flatten)] pub action: ActionType, pub label: String, pub clear: Option, } crate::register_type!(Action); #[derive(Serialize, Deserialize, Typed)] struct NotificationFinal { topic: String, #[serde(flatten)] inner: Notification, } #[derive(Debug, Serialize, Clone, Deserialize, Typed)] pub struct Notification { title: String, message: Option, #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] tags: Vec, #[serde(skip_serializing_if = "Option::is_none")] priority: Option, #[serde(skip_serializing_if = "Vec::is_empty", default = "Default::default")] actions: Vec, } crate::register_type!(Notification); impl Notification { fn finalize(self, topic: &str) -> NotificationFinal { NotificationFinal { topic: topic.into(), inner: self, } } } #[derive(Debug, Clone, LuaDeviceConfig, Typed)] #[typed(as = "NtfyConfig")] pub struct Config { #[device_config(default("https://ntfy.sh".into()))] #[serde(default)] pub url: String, pub topic: String, } crate::register_type!(Config); #[derive(Debug, Clone, Device)] #[device(add_methods(Self::add_methods))] pub struct Ntfy { config: Config, } crate::register_device!(Ntfy); impl Ntfy { fn add_methods>(methods: &mut M) { methods.add_async_method( "send_notification", async |lua, this, notification: mlua::Value| { let notification: Notification = lua.from_value(notification)?; this.send(notification).await; Ok(()) }, ); } } // impl Typed for Ntfy { // fn type_name() -> String { // "Ntfy".into() // } // // fn generate_header() -> Option { // let type_name = ::type_name(); // Some(format!("---@class {type_name}\nlocal {type_name}\n")) // } // // fn generate_members() -> Option { // Some(format!( // "---@async\n---@param notification Notification\nfunction {}:send_notification(notification) end\n", // ::type_name(), // )) // } // } #[async_trait] impl LuaDeviceCreate for Ntfy { type Config = Config; type Error = Infallible; async fn create(config: Self::Config) -> Result { trace!(id = "ntfy", "Setting up Ntfy"); Ok(Self { config }) } } impl Device for Ntfy { fn get_id(&self) -> String { "ntfy".to_string() } } impl Ntfy { async fn send(&self, notification: Notification) { let notification = notification.finalize(&self.config.topic); // Create the request let res = reqwest::Client::new() .post(self.config.url.clone()) .json(¬ification) .send() .await; if let Err(err) = res { error!("Something went wrong while sending the notification: {err}"); } else if let Ok(res) = res { let status = res.status(); if !status.is_success() { warn!("Received status {status} when sending notification"); } } } }