Added config file import and checking and reworked to support multiple airports

This commit is contained in:
2026-04-13 15:04:00 -05:00
parent 2b8a16a277
commit 9a926a80ff
3 changed files with 144 additions and 32 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
node_modules node_modules
*-metars.json

8
config.json Normal file
View File

@@ -0,0 +1,8 @@
{
"outputdir": "/var/www/weather",
"icaos": ["CYWG", "KJFK"],
"siteurl": "https://example.com",
"rss-baseurl": "https://example.com/weather",
"check-interval": 10,
"loglevel": 4
}

161
index.js
View File

@@ -1,24 +1,71 @@
import RSS from "rss"; import RSS from "rss";
import axios from "axios"; import axios from "axios";
import fs from 'fs'; import fs from 'fs';
import path from 'path';
const API_URL = "https://aviationweather.gov/api/data/"; const API_URL = "https://aviationweather.gov/api/data/";
const METAR_SERVICE = "metar"; const METAR_SERVICE = "metar";
const feedOutput = process.env.METAR_FEED_OUTPUT; const generatorURL = "https://git.entropic.pro/Aiden/metar-rss";
const icao = process.env.METAR_ICAO;
const rssMetarOptions = { /** Load config file */
title: icao + " METAR", const configFile = "./config.json";
description: "METAR feed for " + icao + " Airport", let configData;
feed_url: process.env.METAR_FEED_URL, try {
site_url: process.env.METAR_SITE_URL, configData = JSON.parse(fs.readFileSync(configFile));
generator: "https://git.entropic.pro/Aiden/metar-rss" } catch (err) {
console.error(err);
console.warn("Unable to read config file, using defaults");
} }
const checkInterval = 10 * 1000 * 60; //10 Minute Check Interval
/** Verify config data **/
if (Number(configData["loglevel"]) == NaN) {
console.warn("loglevel is not a number, using 4");
configData["loglevel"] = 4;
}
const loglevel = Number(configData["loglevel"]);
try {
if (!fs.lstatSync(configData["outputdir"]).isDirectory()) {
if (loglevel >= 4) console.warn("outputdir is not a directory, using current folder");
configData["outputdir"] = __dirname;
}
} catch (err) {
if (loglevel >= 3) console.error(err);
if (loglevel >= 4) console.warn("error finding outputdir, using current folder");
configData["outputdir"] = __dirname;
}
const outputDir = configData["outputdir"]
if (!Array.isArray(configData["icaos"])) {
if (loglevel >= 4) console.warn("icao config must be an array of ICAO codes, using default");
configData["icaos"] = ["KJFK"];
}
const icaos = configData["icaos"];
try {
new URL(configData["siteurl"]);
} catch (err) {
if (loglevel >= 4) console.warn("siteurl is not a valid URL, using example.com");
configData["siteurl"] = "https://example.com"
}
const siteURL = new URL(configData["siteurl"]);
try {
new URL(configData["rss-baseurl"]);
} catch (err) {
if (loglevel >= 4) console.warn("rss-baseurl is not a valid URL, using example.com");
configData["rss-baseurl"] = "https://example.com"
}
const rssBaseURL = new URL(configData["rss-baseurl"]);
if (Number(configData["check-interval"]) == NaN) {
if (loglevel >= 4) console.warn("check-interval is NaN, using 10 minutes");
configData["check-interval"] = 10;
}
const checkInterval = Number(configData["check-interval"]) * 1000 * 60;
let lastMetar; /** Start Service **/
let metarRSS = new RSS(rssMetarOptions);
checkMetar(); //Inital check run checkMetar(); //Inital check run
const metarCheckJob = setInterval(checkMetar, checkInterval); //Run the check every interval const metarCheckJob = setInterval(checkMetar, checkInterval); //Run the check every interval
@@ -26,53 +73,109 @@ const metarCheckJob = setInterval(checkMetar, checkInterval); //Run the check ev
* Pulls the current metar and adds it to the feed if it is new * Pulls the current metar and adds it to the feed if it is new
*/ */
function checkMetar() { function checkMetar() {
console.log("Checking METAR for " + icao); for (let i = 0; i < icaos.length; i++) {
getMetar().then(res => { let icao = icaos[i];
if (res.data != lastMetar) { if (loglevel >= 7) console.log("Checking METAR for " + icao);
console.log("New METAR: " + res.data); getMetar(icao).then(res => {
newMetar(metarRSS, res.data); let metarFile = icao+"-metars.json";
writeFeed(metarRSS); let metars = [];
lastMetar = res.data; if (fs.existsSync(metarFile))
metars = JSON.parse(fs.readFileSync(icao+"-metars.json"))["data"];
if (res.data != metars[0]) {
if (loglevel >= 6) console.log("New METAR: " + res.data);
metars.unshift(res.data);
if (metars.length > 20) metars.pop();
fs.writeFileSync(metarFile, JSON.stringify({"data": metars}));
let items = [];
for (let i = 0; i < metars.length; i++) {
if (newMetar(metars[i]) != undefined)
items.push(newMetar(metars[i]));
}
let outputPath = METAR_SERVICE + "/" + icao;
writeFeed(generateFeed(METAR_SERVICE, icao, items, outputPath), outputPath);
} }
}); });
}
} }
/** /**
* Adds a new METAR to the given RSS Feed * Adds a new METAR to the given RSS Feed
* @param {*} rss - RSS Feed to add the entry to * @param {RSS} rss - RSS Feed to add the entry to
* @param {*} metar - metar data to add * @param {String} metar - metar data to add
* @returns {Object} - Object of RSS Item Data
*/ */
function newMetar(rss, metar) { function newMetar(metar) {
let metarSplit = metar.split(" "); let metarSplit = metar.split(" ");
let date = new Date(Date.now()); let date = new Date(Date.now());
let eventDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), Number(metarSplit[2].substring(0,2)), Number(metarSplit[2].substring(2,4)), Number(metarSplit[2].substring(4,6)))); let eventDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), Number(metarSplit[2].substring(0,2)), Number(metarSplit[2].substring(2,4)), Number(metarSplit[2].substring(4,6))));
rss.item({ let item = {
title: metarSplit[0] + " " + metarSplit[1] + " " + metarSplit[2], title: metarSplit[0] + " " + metarSplit[1] + " " + metarSplit[2],
description: metar, description: metar,
url: API_URL + METAR_SERVICE, url: API_URL + METAR_SERVICE,
guid: metarSplit[1] + metarSplit[2], guid: metarSplit[1] + metarSplit[2],
date: eventDate date: eventDate
}); };
return item
} }
/** /**
* Pulls METAR data from the API * Pulls METAR data from the API
* @param {String} icao ICAO Airport Code
* @returns Axios generated GET Request Response * @returns Axios generated GET Request Response
*/ */
function getMetar() { function getMetar(icao) {
let requestURL = API_URL + METAR_SERVICE + "?ids=" + icao; let requestURL = API_URL + METAR_SERVICE + "?ids=" + icao;
return axios.get(requestURL); return axios.get(requestURL);
} }
/** /**
* Writes the given RSS feed to the specified file * Generates and populates a feed for the given details
* @param {*} rss - RSS Feed to Write * @param {String} service eg. "metar"
* @param {String} icao ICAO Airpot Code
* @param {Array} items Array of Objects containing RSS item details
* @param {String} outputPath path for this Service + ICAO
* @returns {RSS} RSS Feed with populated items
*/ */
function writeFeed(rss) { function generateFeed(service, icao, items, outputPath) {
let feedURL = rssBaseURL;
feedURL.pathname = feedURL.pathname.concat(outputPath);
let feedOptions = {
title: icao + " " + service,
description: service + " feed for " + icao + " Airport",
feed_url: feedURL,
site_url: siteURL,
generator: generatorURL
}
let feed = new RSS(feedOptions);
for (let i = 0; i < items.length; i++) {
feed.item(items[i]);
}
return feed;
}
/**
* Writes the given RSS feed to the specified file
* @param {RSS} rss - RSS Feed to Write
* @param {String} outputPath path for this Service + ICAO
*/
function writeFeed(rss, outputPath) {
let xmlFeed = rss.xml({indent: true}) let xmlFeed = rss.xml({indent: true})
let fullPath = path.join(outputDir, outputPath);
let fullPathFile = path.join(fullPath, "rss.xml");
if (!fs.existsSync(fullPath)){
fs.mkdirSync(fullPath, { recursive: true });
}
try { try {
fs.writeFileSync(feedOutput, xmlFeed); fs.writeFileSync(fullPathFile, xmlFeed);
} catch(err) { } catch(err) {
console.error(err); console.error(err);
} }