Files
metar-rss/index.js

191 lines
6.2 KiB
JavaScript

import RSS from "rss";
import axios from "axios";
import fs from 'fs';
import path from 'path';
const API_URL = "https://aviationweather.gov/api/data/";
const METAR_SERVICE = "metar";
const generatorURL = "https://git.entropic.pro/Aiden/metar-rss";
let defaultConfig = {
"outputdir": __dirname,
"icaos": ["KJFK"],
"siteurl": "https://example.com",
"rss-baseurl": "https://example.com/weather",
"check-interval": 10,
"loglevel": 4
};
/** Load config file */
const configFile = "./config.json";
let configData;
try {
configData = JSON.parse(fs.readFileSync(configFile));
} catch (err) {
console.error(err);
console.warn("Unable to read config file, using defaults");
configData = defaultConfig;
}
/** Verify config data **/
if (Number(configData["loglevel"]) == NaN) {
console.warn("loglevel is not a number, using " + defaultConfig["loglevel"]);
configData["loglevel"] = defaultConfig["loglevel"];
}
const loglevel = Number(configData["loglevel"]);
try {
if (!fs.lstatSync(configData["outputdir"]).isDirectory()) {
if (loglevel >= 4) console.warn("outputdir is not a directory, using " + defaultConfig["outputdir"]);
configData["outputdir"] = defaultConfig["outputdir"];
}
} catch (err) {
if (loglevel >= 3) console.error(err);
if (loglevel >= 4) console.warn("error finding outputdir, using " + defaultConfig["outputdir"]);
configData["outputdir"] = defaultConfig["outputdir"];
}
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"] = defaultConfig["icaos"];
}
const icaos = configData["icaos"];
try {
new URL(configData["siteurl"]);
} catch (err) {
if (loglevel >= 4) console.warn("siteurl is not a valid URL, using " + defaultConfig["siteurl"]);
configData["siteurl"] = defaultConfig["siteurl"];
}
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 " + defaultConfig["rss-baseurl"]);
configData["rss-baseurl"] = defaultConfig["rss-baseurl"];
}
const rssBaseURL = new URL(configData["rss-baseurl"]);
if (Number(configData["check-interval"]) == NaN) {
if (loglevel >= 4) console.warn("check-interval is NaN, using " + defaultConfig["check-interval"] + " minutes.");
configData["check-interval"] = defaultConfig["check-interval"];
}
const checkInterval = Number(configData["check-interval"]) * 1000 * 60;
/** Start Service **/
checkMetar(); //Inital check run
const metarCheckJob = setInterval(checkMetar, checkInterval); //Run the check every interval
/**
* Pulls the current metar and adds it to the feed if it is new
*/
function checkMetar() {
for (let i = 0; i < icaos.length; i++) {
let icao = icaos[i];
if (loglevel >= 7) console.log("Checking METAR for " + icao);
getMetar(icao).then(res => {
let metarFile = icao+"-metars.json";
let metars = [];
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
* @param {RSS} rss - RSS Feed to add the entry to
* @param {String} metar - metar data to add
* @returns {Object} - Object of RSS Item Data
*/
function newMetar(metar) {
let metarSplit = metar.split(" ");
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 item = {
title: metarSplit[0] + " " + metarSplit[1] + " " + metarSplit[2],
description: metar,
url: API_URL + METAR_SERVICE,
guid: metarSplit[1] + metarSplit[2],
date: eventDate
};
return item
}
/**
* Pulls METAR data from the API
* @param {String} icao ICAO Airport Code
* @returns Axios generated GET Request Response
*/
function getMetar(icao) {
let requestURL = API_URL + METAR_SERVICE + "?ids=" + icao;
return axios.get(requestURL);
}
/**
* Generates and populates a feed for the given details
* @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 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 fullPath = path.join(outputDir, outputPath);
let fullPathFile = path.join(fullPath, "rss.xml");
if (!fs.existsSync(fullPath)){
fs.mkdirSync(fullPath, { recursive: true });
}
try {
fs.writeFileSync(fullPathFile, xmlFeed);
} catch(err) {
console.error(err);
}
}