191 lines
6.3 KiB
JavaScript
191 lines
6.3 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": "/var/www/weather",
|
|
"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 " + defaultConfig["icaos"]);
|
|
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 (metars[i] != undefined)
|
|
items.push(newMetar(metars[i]));
|
|
}
|
|
|
|
let outputPath = path.join(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 = new URL(rssBaseURL);
|
|
feedURL.pathname = path.join(feedURL.pathname, outputPath, "rss.xml");
|
|
|
|
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) {
|
|
if (loglevel >= 3)console.error(err);
|
|
}
|
|
} |