Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules
.now
.now
.DS_Store
25 changes: 25 additions & 0 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const express = require('express'),
util = require('./bin/util'),
axios = require('axios'),
covid19 = require('./lib/cli'),
covid19GFX = require('./lib/cli/gfx'),
pkg = require('./package.json'), // package.json info
apiBaseURL = "https://corona.lmao.ninja", // NovelCOVID API
port = process.env.port || 7070; // set port
Expand Down Expand Up @@ -128,6 +129,30 @@ app.get('/history/:country/:chartType(cases|deaths)?', async (req, res, next) =>
return next();
});


// historical chart by country
app.get('/history/charts/:country', async (req, res, next) => {
const userAgent = req.headers['user-agent'],
countryData = req.params.country,
chartType = req.params.chartType || 'cases',
summary = await axios.get(`${apiBaseURL}/countries/${countryData}`),
history = await axios.get(`${apiBaseURL}/v2/historical/${summary.data.country}?lastdays=all`),
s = summary.data,
h = history.data;

if (util.isCommandline(userAgent)) {
covid19GFX.historyCountryTracker(
req, res,
s.country, s.cases, s.todayCases,
s.deaths, s.todayDeaths, s.recovered,
s.active, s.critical, s.casesPerOneMillion,
s.updated, h, chartType, s.countryInfo
)
return null;
}
return next();
});

app.get('*', (req, res) => res.send(`
Welcome to COVID-19 Tracker CLI v${pkg.version} by Waren Gonzaga\n
Please visit: https://warengonza.ga/covid19-tracker-cli
Expand Down
83 changes: 83 additions & 0 deletions lib/cli/gfx/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
const
blessed = require('blessed'),
pkg = require('../../../package.json'),
say = require('../../sayings/threads.json'),
serverUtil = require('./server-util')
template = require ('./template.js'),
ts = Date.now(),
date_ob = new Date(ts),
date = date_ob.getDate(),
month = date_ob.getMonth() + 1,
year = date_ob.getFullYear(),
currentdate = month + "/" + date + "/" + year,
space = ' ',
br = '\n',
header = '`COVID19-TRACKER-CLI (v'+pkg.version+')`',
tagline = '*A curl-based command line tracker for Novel Coronavirus or COVID-19 pandemic.*',
source = 'Source: https://www.worldometers.info/coronavirus/',
repo = 'Code: https://github.com/warengonzaga/covid19-tracker-cli',
bmcurl = 'warengonza.ga/coffee4dev',
twitterhandle = '@warengonzaga',
twitterhashtag = '#covid19trackercli',
gcashNum = '+639176462753',
ansiBMC = '`(Buy Me A Coffee)` '+bmcurl,
ansiTwitter = twitterhandle+space+twitterhashtag,
ansiGCash = '(GCash) '+gcashNum;


// random sayings
const randomSay = () => {
let random = Math.floor(Math.random() * say.length);
return say[random];
};

const patchBlessed = () => {
blessed.Program.prototype.listem = function() {}
process.on = function() {}
};

patchBlessed();

exports.historyCountryTracker = (req, res, n, c, tC, d, tD, r, a, cl, cPOM, u, h, chartType, countryInfo) => {
const name = n, cases = c, todayCases = tC,
deaths = d, todayDeaths = tD, recovered = r,
active = a, critical = cl, casesPerOneMillion = cPOM,
mortalityPercentage = (d/c)*100, recoveredPercentage = (r/c)*100,
asof = new Date(u),
dates = Object.keys(h.timeline[chartType]),
from = dates[0],
to = dates[dates.length - 1],
tableFooter = randomSay(),
defaultfooter = ansiBMC+ansiTwitter+br+br,
specialfooter = ansiGCash+br+ansiBMC+ansiTwitter+br+br,
defaultHeader = header+br+tagline,
footer = (n.toLowerCase() == 'philippines') ? tableFooter+br+specialfooter+br+source+br+repo : tableFooter+br+defaultfooter+br+source+br+repo;

// load template with data
// serverUtil.loadTemplate(template, {jsonData}, callback)
serverUtil.loadTemplate(template, {
name,
cases,
deaths,
recovered,
active,
casesPerOneMillion,
todayCases,
todayDeaths,
critical,
mortalityPercentage,
recoveredPercentage,
countryInfo,
from,
to,
currentdate,
asof,
h,
defaultHeader,
footer,
}, (screen) => {
res.send(screen+'\r\n'+'\033[?25h')
})

};

76 changes: 76 additions & 0 deletions lib/cli/gfx/server-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/** Modified script from blessed-contrib server-util */
const blessed = require('blessed'),
contrib = require('blessed-contrib'),
Stream = require('stream');

function OutputBuffer(options) {
this.isTTY = true;
this.columns = options.cols;
this.rows = options.rows;
this.write = function(s) {
s = s.replace('\x1b8', '');
options.stream.write(s)
};
this.on = function() {};
}

function InputBuffer() {
this.isTTY = true;
this.isRaw = true;
this.emit = function() {};
this.setRawMode = function() {};
this.resume = function() {};
this.pause = function() {};
this.on = function() {};
}

createScreen = (opt = {}) => {
const output = new OutputBuffer({stream: opt.stream, cols: 250, rows: 50});
const input = new InputBuffer(); //required to run under forever since it replaces stdin to non-tty
const program = blessed.program({output: output, input: input});

let screen = blessed.screen({program: program});
return screen
}

loadTemplate = (gridTemplateClass, json, callback) => {

blessed.Screen.global = null
blessed.Program.global = null

const
customStream = new Stream.Transform()
screen = createScreen({stream: customStream}),
grid = new contrib.grid({rows: 12, cols: 14, screen: screen});

let result = []

if(!gridTemplateClass) throw new Error('No template loaded')

// parse template
gridTemplateClass.load(grid, json)

customStream._transform = function (chunk,encoding,done) {
result.push(chunk.toString())
done()
}

customStream._final = () => {
callback(result.join(''))
}

customStream.on('error', (e) => {
// do nothing here
})

screen.render()

setTimeout(() => {
customStream.end()
}, 1000)
}

module.exports = {
createScreen,
loadTemplate,
}
141 changes: 141 additions & 0 deletions lib/cli/gfx/template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
const
contrib = require('blessed-contrib'),
chalk = require('chalk');

exports.load = (grid, data) => {

let markdown = grid.set(0, 0, 2, 2, contrib.markdown,{
style: {
border: {
fg: 'black'
}
}
})
markdown.setMarkdown(data.defaultHeader)

let table = grid.set(0, 2, 2, 7, contrib.table,
{ keys: true
, fg: 'white'
, selectedFg: 'white'
, selectedBg: 'blue'
, interactive: false
, label: 'Historical data as of '+data.asof.toLocaleString()+' [Date:'+data.currentdate+']'
, width: '30%'
, height: '30%'
, border: {type: "line", fg: "cyan"}
, columnSpacing: 10 //in chars
, columnWidth: [1,16, 12, 12, 12, 18],
style: {
border: {
fg: 'white'
}
}})

table.setData(
{ headers: ['','Cases', 'Deaths', 'Recovered', 'Active', 'Cases/Million']
, data:
[
['',data.cases, data.deaths, data.recovered, data.active, data.casesPerOneMillion],
['','Today Cases', 'Today Deaths', 'Critical', 'Mortality %', 'Recovery %'],
['',data.todayCases, data.todayDeaths, data.critical, data.mortalityPercentage, data.recoveredPercentage]
]
})

let map = grid.set(2, 0, 3, 4, contrib.map,{
style: {
border: {
fg: 'black'
},
shapeColor: 'yellow'
},
})

map.addMarker({"lon" : data.countryInfo.long, "lat" : data.countryInfo.lat, color: 'magenta', char: '\u24E7'+` ${data.name}`})

let bar = grid.set(2, 4, 3, 3, contrib.bar,{
barBgColor: 'red',
barWidth: 7,
barSpacing: 5,
xOffset: 3,
maxHeight: 9,
style: {
border: {
fg: 'white'
}
}})

screen.append(bar) //must append before setting data
bar.setData({ titles: ['Cases', 'Deaths', 'Recovered', 'Active'], data: [data.cases, data.deaths, data.recovered, data.active]})

let donut = grid.set(2, 7, 3, 2, contrib.donut,{
label: 'Mortality Rate',
radius: 8,
arcWidth: 3,
remainColor: 'black',
yPadding: 2,
style: {
border: {
fg: 'red'
}
},
data: [
{percent: data.mortalityPercentage, label: 'Mortality', color: 'red'},
{percent: data.recoveredPercentage, label: 'Recovery', color: 'green'}
]
});

let line = grid.set(5, 0, 4, 9, contrib.line,
{ style:
{ line: "yellow"
, text: "green"
, baseline: "black",
border: {
fg: 'black'
}
}
, xLabelPadding: 3
, xPadding: 5
, showLegend: true
, wholeNumbersOnly: true //true=do not show fraction in y axis
, label: `Cases from ${data.from} to ${data.to}`})

let series1 = {
title: 'Cases',
x: Object.keys(data.h.timeline.cases),
y: Object.values(data.h.timeline.cases),
style: {
line: 'blue'
}
}
let series2 = {
title: 'Deaths',
x: Object.keys(data.h.timeline.deaths),
y: Object.values(data.h.timeline.deaths),
style: {
line: 'red'
}
}

let series3 = {
title: 'Recovered',
x: Object.keys(data.h.timeline.recovered),
y: Object.values(data.h.timeline.recovered),
style: {
line: 'green'
}
}

screen.append(line) //must append before setting data
line.setData([series1, series2, series3])

let markdownFooter = grid.set(8, 0, 2, 9, contrib.markdown,{
style: {
border: {
fg: 'black'
}
}
})
markdownFooter.setMarkdown(data.footer)

return grid
}
Loading