commit 19cb052930e69305ce57d0423218f2b6b305d360 Author: ChrisChrome Date: Tue Sep 23 18:41:25 2025 -0600 Bwug diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ccb8df --- /dev/null +++ b/.gitignore @@ -0,0 +1,141 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.* +!.env.example + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist +.output + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Sveltekit cache directory +.svelte-kit/ + +# vitepress build output +**/.vitepress/dist + +# vitepress cache directory +**/.vitepress/cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# Firebase cache directory +.firebase/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v3 +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/sdks +!.yarn/versions + +# Vite files +vite.config.js.timestamp-* +vite.config.ts.timestamp-* +.vite/ diff --git a/index.js b/index.js new file mode 100644 index 0000000..116744c --- /dev/null +++ b/index.js @@ -0,0 +1,150 @@ +require("dotenv").config(); +const ws = require("ws") + +/* Old NTFY Body from old system +ntfyBody = { + "topic": `${config.ntfy.prefix}${fromChannel}`, + "message": bodyData.string, + "tags": [`Timestamp: ${product_id.timestamp}`, `Station: ${product_id.station}`, `WMO: ${product_id.wmo}`, `PIL: ${product_id.pil}`, `Channel: ${fromChannel}`], + "priority": evt.priority, + "actions": [{ "action": "view", "label": "Product", "url": bodyData.url }, { "action": "view", "label": "Product Text", "url": `https://mesonet.agron.iastate.edu/api/1/nwstext/${product_id_raw}` }] + } + +// Message body from websocket +{ + "type": "iem-message", + "data": { + "channel": { + "location": "", + "room": "" + }, + "event": { + "name": "", + "priority": , + "code": "" + }, + "body": "", + "timestamp": "", + "wmo": "", + "pil": "", + "station": "", + "raw": "", + "rawBody": "", + "image": "", // Optional + "productText": "" // Optional + } +} + +*/ + +let retryQueue = []; + +function handleIEMMessage(msg) { + if (!msg.data) return; + const data = msg.data; + ntfyBody = { + topic: `${data.channel.room}`, + message: data.body.string, + tags: [ + `Timestamp: ${data.timestamp}`, + `Station: ${data.station}`, + `WMO: ${data.wmo}`, + `PIL: ${data.pil}`, + `Channel: ${data.channel.room}`, + ], + priority: data.event.priority, + actions: [ + { + action: "view", + label: "Product", + url: data.body.url, + }, + { + action: "view", + label: "Product Text", + url: `https://mesonet.agron.iastate.edu/api/1/nwstext/${data.raw}`, + }, + ], + }; + + if (data.image) { + ntfyBody.attach = data.image; + } + + // Send to NTFY + fetch(process.env.NTFY_HOST, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${process.env.NTFY_TOKEN}`, + }, + body: JSON.stringify(ntfyBody), + }).then((res) => { + if (!res.ok) { + retryQueue.push(ntfyBody); + console.error(`Error sending to NTFY: ${res.statusText}. Added to retry queue.`); + } else { + console.log(`Sent to NTFY topic ${ntfyBody.topic}`); + } + }).catch((error) => { + retryQueue.push(ntfyBody); + console.error("Error sending to NTFY:", error); + }); +}; + +function processRetryQueue() { + if (retryQueue.length === 0) return; + + const body = retryQueue.shift(); + fetch(process.env.NTFY_HOST, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Authorization": `Bearer ${process.env.NTFY_TOKEN}`, + }, + body: JSON.stringify(body), + }).then((res) => { + if (!res.ok) { + retryQueue.push(body); + console.error(`Error retrying to NTFY: ${res.statusText}. Re-added to retry queue.`); + } else { + console.log(`Retried and sent to NTFY topic ${body.topic}`); + } + }).catch((error) => { + retryQueue.push(body); + console.error("Error retrying to NTFY:", error); + }); +}; + +// Process retry queue every second +setInterval(processRetryQueue, 1000); + + +// Connect to process.env.IEM_WS_SERVER +const wsClient = new ws(process.env.IEM_WS_SERVER, { + headers: { + "User-Agent": "ntfy-pusher/1.0", + }, +}); + +wsClient.on("open", function open() { + console.log("Connected to IEM WebSocket server"); + wsClient.send(JSON.stringify({ type: "subscribe", channel: "*" })); +}); + +wsClient.on("message", function incoming(data) { + let msg; + try { + msg = JSON.parse(data); + } catch (error) { + console.error("Error parsing message:", error); + } + + if (!msg) return; // Ignore invalid messages + + switch (msg.type) { + case "iem-message": + handleIEMMessage(msg); + break; + } +}); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..b8c096f --- /dev/null +++ b/package-lock.json @@ -0,0 +1,50 @@ +{ + "name": "ntfy-pusher", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "ntfy-pusher", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "dotenv": "^17.2.2", + "ws": "^8.18.3" + } + }, + "node_modules/dotenv": { + "version": "17.2.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.2.tgz", + "integrity": "sha512-Sf2LSQP+bOlhKWWyhFsn0UsfdK/kCWRv1iuA2gXAwt3dyNabr6QSj00I2V10pidqz69soatm9ZwZvpQMTIOd5Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..23e52a6 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "name": "ntfy-pusher", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "type": "commonjs", + "dependencies": { + "dotenv": "^17.2.2", + "ws": "^8.18.3" + } +}