Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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')
})

};

79 changes: 79 additions & 0 deletions lib/cli/gfx/server-util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/** 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', ''); //not clear from where in blessed this code comes from. It forces the terminal to clear and loose existing content.
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,
}
145 changes: 145 additions & 0 deletions lib/cli/gfx/template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
const
contrib = require('blessed-contrib'),
chalk = require('chalk');

// <Grid>:new contrib.grid({rows: 14, cols: 14, screen: screen});
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