Simple app for downloading and converting version_manifest.json to RSS/ATOM/JSON Feed.
Launch parameters: node app.js [port=3000] [hostname=127.0.0.1]
. Port is a port that will be used, 3000 by default, hostname is hostname that will be used, 127.0.0.1 by default.
Example URL:
http://127.0.0.1:3000/feed?blacklist=old_alpha,old_beta&count=15&format=atom
Parameters:
blacklist
: filter out certain release types, separated by comma. Currently available release types: snapshot
, release
, old_beta
, old_alpha
. Optional, empty by default.
count
: how many entries should be in the newsfeed. For example, 10
means show only 10 latest versions in the newsfeed. To show all versions, use -1
. Optional, 10 by default.
format
: newsfeed’s format. Available formats: rss
, atom
, json
. Optional, rss
by default.
App will not download full version_manifest.json
file if it’s not updated. Though, it’s still not recommended to set your Feed Reader to update every 1 minute or something. Use more reasonable update times.
Requirements: Node.js 10.15.3 (maybe works on older versions, not tested)
Dependencies: feed (2.0.4)
Code:
const http = require('http');
const https = require('https');
const URL = require('url');
const Feed = require('feed').Feed;
const hostname = process.argv[3] || '127.0.0.1';
const port = process.argv[2] || 3000;
const mcJsonUrl = "https://launchermeta.mojang.com/mc/game/version_manifest.json";
let lastData = "";
let lastModified = "";
const feed = new Feed({
title: "Minecraft Update Feed",
description: "Newsfeed with all Minecraft versions, generated from version_manifest.json",
id: "tag:minecraft.net,2019:mc/minecraft_update_feed",
link: "http://minecraft.net/",
language: "en", // optional, used only in RSS 2.0, possible values: http://www.w3.org/TR/REC-html40/struct/dirlang.html#langcodes
favicon: "https://minecraft.net/favicon.ico",
copyright: "All rights reserved 2019, user",
//updated: new Date(2013, 6, 14), // optional, default = today
feedLinks: {
json: `http://${hostname}:${port}/feed?format=json`,
atom: `http://${hostname}:${port}/feed?format=atom`
}
});
function getDefault(val, defaultVal) {
if (val == null) return defaultVal;
else return val;
}
function isNumeric(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}
function getDefaultNum(val, defaultVal) {
let result = getDefault(val, defaultVal);
if (isNumeric(defaultVal) && !isNumeric(val)) result = defaultVal;
return result;
}
function capitalizeFirstLetter(s) {
return s.charAt(0).toUpperCase() + s.slice(1);
}
const server = http.createServer((req, res) => {
let parsedUrl = URL.parse(req.url, true);
if (parsedUrl.pathname === "/feed") {
let entryCount = getDefaultNum(parsedUrl.query.count, 10);
let feedFormat = getDefault(parsedUrl.query.format, 'rss');
let blacklist = new Set(getDefault(parsedUrl.query.blacklist, '').split(','));
//console.log('Starting download...');
let dataRaw=[];
let feedResponce = res;
https.get(mcJsonUrl, lastModified ? {headers: {'If-Modified-Since': lastModified}} : {}, function(mcJsonResponse) {
mcJsonResponse.on("data",chunk=>dataRaw.push(chunk)).on("end",()=>{
let data = Buffer.concat(dataRaw).toString();
if (mcJsonResponse.statusCode === 304) {
//console.log('Not Modified! Using cached version.');
data = lastData;
} else {
lastModified = mcJsonResponse.headers['last-modified'];
//console.log('Last Modified: '+lastModified);
}
//console.log('Ended download!');
feed.options.updated = new Date(lastModified);
feed.items.length = 0;
let mcReleases = JSON.parse(data);
let i = 0;
for (let version of mcReleases.versions) {
if (i>=entryCount && entryCount>0) break;
if (blacklist.has(version.type)) continue;
version.releaseTime = new Date(version.releaseTime);
feed.addItem({
title: capitalizeFirstLetter(version.type)+" "+version.id,
id: `tag:minecraft.net,${version.releaseTime.toISOString().split("T")[0]}:mc/minecraft_update_feed/${encodeURIComponent(version.id)}`,
link: "https://minecraft.gamepedia.com/"+version.id,
date: version.releaseTime
});
i++;
}
feedResponce.statusCode = 200;
switch (feedFormat) {
case 'atom':
feedResponce.setHeader('Content-Type', 'application/atom+xml');
feedResponce.end(feed.atom1());
break;
case 'json':
feedResponce.setHeader('Content-Type', 'application/json');
feedResponce.end(feed.json1());
break;
default://rss
feedResponce.setHeader('Content-Type', 'application/rss+xml');
feedResponce.end(feed.rss2());
break;
}
lastData = data;
}).on('error', (e) => {
console.error(e);
feedResponce.statusCode = 500;
feedResponce.end("Error");
});
});
} else {
res.statusCode = 404;
res.end('Not found');
}
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
Disclaimer:
Standard disclaimer about no warranties about quality, properness, security, or anything else.