Minecraft Update Newsfeed (version_manifest.json2rss app)


#1

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.