diff --git a/app.js b/app.js index 93a0f90..378c940 100644 --- a/app.js +++ b/app.js @@ -5,6 +5,7 @@ const { getCountryTable, getJSONData, getJSONDataForCountry } = require('./lib/b const { getCompleteTable } = require('./lib/corona'); const { lookupCountry } = require('./lib/helpers'); const { getLiveUpdates } = require('./lib/reddit.js'); +const { getWorldoMetersTable } = require('./lib/worldoMeters.js'); const app = express(); const port = process.env.PORT || 3001; @@ -32,6 +33,14 @@ app.get('/', (req, res) => { const minimal = req.query.minimal === 'true'; const emojis = req.query.emojis === 'true'; const top = req.query.top ? Number(req.query.top) : 1000; + const source = req.query.source ? Number(req.query.source) : 1; + + if (source === 2) { + return getWorldoMetersTable({ isCurl, emojis, minimal, top }) + .then(result => { + return res.send(result); + }).catch(error => errorHandler(error, res)); + } if (format.toLowerCase() === 'json') { return getJSONData().then(result => { @@ -66,6 +75,7 @@ app.get('/:country', (req, res) => { const format = req.query.format ? req.query.format : ''; const minimal = req.query.minimal === 'true'; const emojis = req.query.emojis === 'true'; + const source = req.query.source ? Number(req.query.source) : 1; if (!country || country.toUpperCase() === 'ALL') { if (format.toLowerCase() === 'json') { @@ -93,8 +103,16 @@ app.get('/:country', (req, res) => { `); } + const { iso2 } = lookupObj; + if (source === 2) { + return getWorldoMetersTable({ countryCode: iso2, isCurl, emojis, minimal }) + .then(result => { + return res.send(result); + }).catch(error => errorHandler(error, res)); + } + if (format.toLowerCase() === 'json') { return getJSONDataForCountry(iso2).then(result => { return res.json(result); diff --git a/bin/index.js b/bin/index.js index f6c7d8a..4aaab8b 100755 --- a/bin/index.js +++ b/bin/index.js @@ -5,6 +5,7 @@ const yargs = require('yargs'); const chalk = require('chalk'); const { getCompleteTable } = require('../lib/corona'); const { getCountryTable } = require('../lib/byCountry'); +const { getWorldoMetersTable } = require('../lib/worldoMeters'); const { lookupCountry } = require('../lib/helpers'); const { argv } = yargs @@ -34,6 +35,12 @@ const { argv } = yargs }) ) .options({ + s: { + alias: 'source', + describe: 'fetch data from other source', + default: 1, + type: 'int' + }, e: { alias: 'emojis', describe: 'Show emojis in table', @@ -60,11 +67,17 @@ const { argv } = yargs .strict() .help('help'); -const { emojis, country, minimal, top } = argv; -( - country === 'ALL' - ? getCompleteTable({ emojis, minimal, top }) - : getCountryTable({ countryCode: country, emojis, minimal }) -) - .then(console.log) - .catch(console.error); +argv.countryCode = argv.country; +if (argv.source === 2) { + getWorldoMetersTable(argv) + .then(console.log) + .catch(console.error); +} else { + ( + argv.country === 'ALL' + ? getCompleteTable(argv) + : getCountryTable(argv) + ) + .then(console.log) + .catch(console.error); +} diff --git a/changelog.md b/changelog.md index 04e3ae6..c0d352a 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,10 @@ # Changelog +## Version 0.7.0 + +* Added new source to fetch realtime data. ``corona --source=2`` +* Code refactored and some bug fixes. + ## Version 0.6.0 * Added filter to show top N countries. ``corona --top=20`` diff --git a/lib/api.js b/lib/api.js index c814652..33f7cfb 100644 --- a/lib/api.js +++ b/lib/api.js @@ -1,23 +1,68 @@ const NodeCache = require('node-cache'); const axios = require('axios'); +const { countryNameMap } = require('./constants'); const myCache = new NodeCache({ stdTTL: 100, checkperiod: 600 }); -const CORONA_ALL_KEY = 'coronaAll'; exports.getCoronaData = async () => { - const coronaCache = myCache.get(CORONA_ALL_KEY); + const CORONA_ALL_KEY = 'coronaAll'; + const cache = myCache.get(CORONA_ALL_KEY); - if (coronaCache) { - return coronaCache; + if (cache) { + return cache; } - const result = await axios('https://coronavirus-tracker-api.herokuapp.com/all'); if (!result || !result.data) { throw new Error('Source API failure.'); } - myCache.set(CORONA_ALL_KEY, result.data, 60 * 15); - return result.data; }; + +/** Fetch Worldometers Data */ +exports.getWorldoMetersData = async (countryCode = 'ALL') => { + const key = `worldMetersData_${countryCode}`; + const cache = myCache.get(key); + + if (cache) { + console.log('cache', key); + return cache; + } + const result = await axios('https://corona.lmao.ninja/countries'); + if (!result || !result.data) { + throw new Error('WorldoMeters Source API failure'); + } + + const worldStats = result.data.reduce((acc, countryObj) => { + acc.cases += countryObj.cases; + acc.todayCases += countryObj.todayCases; + acc.deaths += countryObj.deaths; + acc.todayDeaths += countryObj.todayDeaths; + acc.recovered += countryObj.recovered; + acc.active += countryObj.active; + acc.critical += countryObj.critical; + return acc; + }, { + countryName: 'World', + cases: 0, + todayCases: 0, + deaths: 0, + todayDeaths: 0, + recovered: 0, + active: 0, + critical: 0, + }); + + result.data.forEach(obj => obj.countryCode = countryNameMap[obj.country]); + worldStats.casesPerOneMillion = (worldStats.cases / 7794).toFixed(2); + let finalData = result.data; + console.log(countryCode); + if (countryCode && countryCode !== 'ALL') { + finalData = finalData.filter(obj => obj.countryCode === countryCode); + } + const returnObj = { data: finalData, worldStats }; + + myCache.set(key, returnObj, 60 * 15); + return returnObj; +}; diff --git a/lib/constants.js b/lib/constants.js new file mode 100644 index 0000000..6d9d756 --- /dev/null +++ b/lib/constants.js @@ -0,0 +1,183 @@ +exports.countryNameMap = { + China: 'CN', + UK: 'GB', + Martinique: 'MQ', + Liechtenstein: 'LI', + 'RΓ©union': 'RE', + Ukraine: 'UA', + Honduras: 'HN', + Afghanistan: 'AF', + Bangladesh: 'BD', + Macao: 'MO', + Bolivia: 'BO', + Cuba: 'CU', + Netherlands: 'NL', + Jamaica: 'JM', + 'French Guiana': 'GF', + DRC: 'CD', + Cameroon: 'CM', + Maldives: 'MV', + Montenegro: 'ME', + Paraguay: 'PY', + Nigeria: 'NG', + Guam: 'GU', + 'French Polynesia': 'PF', + Austria: 'AT', + Ghana: 'GH', + Rwanda: 'RW', + Monaco: 'MC', + Gibraltar: 'GI', + Guatemala: 'GT', + 'Ivory Coast': 'CI', + Ethiopia: 'ET', + Togo: 'TG', + 'Trinidad and Tobago': 'TT', + Kenya: 'KE', + Belgium: 'BE', + Mauritius: 'MU', + 'Equatorial Guinea': 'GQ', + Kyrgyzstan: 'KG', + Mongolia: 'MN', + 'Puerto Rico': 'PR', + Seychelles: 'SC', + Tanzania: 'TZ', + Guyana: 'GY', + Aruba: 'AW', + Barbados: 'BB', + Norway: 'NO', + Mayotte: 'YT', + 'Cayman Islands': 'KY', + 'CuraΓ§ao': 'CW', + Bahamas: 'BS', + Congo: 'CD', + Gabon: 'GA', + Namibia: 'NA', + 'St. Barth': 'BL', + 'Saint Martin': 'MF', + 'U.S. Virgin Islands': 'VI', + Sweden: 'SE', + Sudan: 'SD', + Benin: 'BJ', + Bermuda: 'BM', + Bhutan: 'BT', + CAR: 'CF', + Greenland: 'GL', + Haiti: 'HT', + Liberia: 'LR', + Mauritania: 'MR', + 'New Caledonia': 'NC', + Denmark: 'DK', + 'Saint Lucia': 'LC', + Zambia: 'ZM', + Nepal: 'NP', + Angola: 'AO', + 'Antigua and Barbuda': 'AG', + 'Cabo Verde': 'CV', + Chad: 'TD', + Djibouti: 'DJ', + 'El Salvador': 'SV', + Fiji: 'FJ', + Japan: 'JP', + Gambia: 'GM', + Guinea: 'GN', + 'Vatican City': 'VA', + 'Isle of Man': 'IM', + Montserrat: 'MS', + Nicaragua: 'NI', + Niger: 'NE', + 'St. Vincent Grenadines': 'VC', + 'Sint Maarten': 'SX', + Somalia: 'SO', + Malaysia: 'MY', + Suriname: 'SR', + Eswatini: 'SZ', + Australia: 'AU', + Italy: 'IT', + Canada: 'CA', + Portugal: 'PT', + Czechia: 'CZ', + Israel: 'IL', + Brazil: 'BR', + Luxembourg: 'LU', + Ireland: 'IE', + Greece: 'GR', + Qatar: 'QA', + Pakistan: 'PK', + Iran: 'IR', + Finland: 'FI', + Poland: 'PL', + Turkey: 'TR', + Singapore: 'SG', + Chile: 'CL', + Iceland: 'IS', + Thailand: 'TH', + Slovenia: 'SI', + Indonesia: 'ID', + Bahrain: 'BH', + Spain: 'ES', + Romania: 'RO', + 'Saudi Arabia': 'SA', + Estonia: 'EE', + Ecuador: 'EC', + Egypt: 'EG', + Peru: 'PE', + Philippines: 'PH', + 'Hong Kong': 'HK', + India: 'IN', + Russia: 'RU', + Germany: 'DE', + Iraq: 'IQ', + Mexico: 'MX', + Lebanon: 'LB', + 'South Africa': 'ZA', + Kuwait: 'KW', + 'San Marino': 'SM', + UAE: 'AE', + Panama: 'PA', + Armenia: 'AM', + Taiwan: 'TW', + USA: 'US', + Argentina: 'AR', + Colombia: 'CO', + Slovakia: 'SK', + Serbia: 'RS', + Croatia: 'HR', + Bulgaria: 'BG', + Uruguay: 'UY', + Algeria: 'DZ', + 'Costa Rica': 'CR', + Latvia: 'LV', + France: 'FR', + Hungary: 'HU', + Vietnam: 'VN', + 'Faeroe Islands': 'FO', + Andorra: 'AD', + Brunei: 'BN', + Belarus: 'BY', + Jordan: 'JO', + Cyprus: 'CY', + 'Sri Lanka': 'LK', + Albania: 'AL', + 'S. Korea': 'KR', + 'Bosnia and Herzegovina': 'BA', + Morocco: 'MA', + Malta: 'MT', + 'North Macedonia': 'MK', + Moldova: 'MD', + Kazakhstan: 'KZ', + Lithuania: 'LT', + Oman: 'OM', + Cambodia: 'KH', + Palestine: 'PS', + Switzerland: 'CH', + Guadeloupe: 'GP', + Azerbaijan: 'AZ', + Georgia: 'GE', + Venezuela: 'VE', + Tunisia: 'TN', + 'New Zealand': 'NZ', + Senegal: 'SN', + 'Dominican Republic': 'DO', + 'Burkina Faso': 'BF', + Uzbekistan: 'UZ', +}; \ No newline at end of file diff --git a/lib/corona.js b/lib/corona.js index 48c3ece..1581125 100644 --- a/lib/corona.js +++ b/lib/corona.js @@ -81,20 +81,20 @@ exports.getCompleteTable = async ({ const { confirmed, deaths, recovered } = data; const countryData = getDataByCountry(confirmed, deaths, recovered); const worldStats = getTotalStats(countryData); - table.push({ - '': [ - 'World', - getConfirmed(worldStats.confirmed), - getRecovered(worldStats.recovered), - getDeaths(worldStats.deaths), - getActive(worldStats.active), - getMortalityPer(worldStats.mortalityPer), - getRecoveredPer(worldStats.recoveredPer), - getOneDayChange(worldStats), - getOneWeekChange(worldStats), - ...(emojis ? ['🌎'] : []) - ] - }); + const worldRow = [ + 'World', + getConfirmed(worldStats.confirmed), + getRecovered(worldStats.recovered), + getDeaths(worldStats.deaths), + getActive(worldStats.active), + getMortalityPer(worldStats.mortalityPer), + getRecoveredPer(worldStats.recoveredPer), + getOneDayChange(worldStats), + getOneWeekChange(worldStats), + ...(emojis ? ['🌎'] : []) + ]; + + table.push({ '': worldRow }); let rank = 1; countryData.some(cd => { const countryEmoji = getEmoji(cd.countryCode); @@ -113,20 +113,7 @@ exports.getCompleteTable = async ({ table.push({ [rank++]: values }); return rank === top + 1; }); - table.push({ - '': [ - 'World', - getConfirmed(worldStats.confirmed), - getRecovered(worldStats.recovered), - getDeaths(worldStats.deaths), - getActive(worldStats.active), - getMortalityPer(worldStats.mortalityPer), - getRecoveredPer(worldStats.recoveredPer), - getOneDayChange(worldStats), - getOneWeekChange(worldStats), - ...(emojis ? ['🌎'] : []) - ] - }); + table.push({ '': worldRow }); const { lastUpdated } = countryData[0]; const ret = table.toString() + footer(lastUpdated); diff --git a/lib/helpers.js b/lib/helpers.js index 50cd703..c5a5295 100644 --- a/lib/helpers.js +++ b/lib/helpers.js @@ -15,7 +15,6 @@ e.getState = (state) => { if (state) { return chalk.red(state); } - return chalk.red('ALL'); }; @@ -61,7 +60,6 @@ e.getEmoji = (countryCode) => { if (countryCode && emojiFlags.countryCode(countryCode)) { return emojiFlags.countryCode(countryCode).emoji; } - return ''; }; @@ -174,13 +172,14 @@ e.lookupCountry = country => { e.footer = (lastUpdated) => ` -Stay safe. Stay inside. +⚠️ ${chalk.cyanBright('Stay safe. Stay inside.')} -Code: https://github.com/sagarkarira/coronavirus-tracker-cli -Twitter: https://twitter.com/ekrysis +πŸ’» ${chalk.greenBright('Code')}: ${chalk.blueBright('https://github.com/sagarkarira/coronavirus-tracker-cli')} +➑️ ${chalk.greenBright('Twitter')}: ${chalk.blueBright('https://twitter.com/ekrysis')} -Last Updated on: ${moment(lastUpdated).utc().format('DD-MMM-YYYY HH:MM')} UTC +⏳ ${chalk.magentaBright('Last Updated on:')} ${moment(lastUpdated).utc().format('DD-MMM-YYYY HH:MM')} UTC +${chalk.red.bold.underline('NEW UPDATE (REALTIME STATS)')}: ${chalk.blueBright('curl https://corona-stats.online?source=2')} `; e.getTableBorders = minimal => { @@ -224,6 +223,23 @@ e.getTableHeaders = (emojis, secondColumnName) => { return head; }; +e.getTableHeadersV2 = (emojis, secondColumnName) => { + const head = [ + 'Rank', + secondColumnName, + `Total Cases ${emojis ? ' βœ…' : ''}`, + 'New Cases β–²', + `Total Deaths${emojis ? ' 😞' : ''}`, + `New Deaths β–²${emojis ? ' 😞' : ''}`, + `Recovered${emojis ? ' πŸ˜€' : ''}`, + `Active${emojis ? ' 😷' : ''}`, + 'Critical', + 'Cases / 1M pop', + ...(emojis ? ['🏳'] : []), + ]; + return head; +}; + e.extraStats = (dataArr) => { return dataArr.map(obj => { return { @@ -261,3 +277,22 @@ e.htmlTemplate = (body) => { return stripAnsi(template); }; + +exports.cFormatter = (content, chalkFn, alignment, humanize, extra = '') => { + if (!content) { + return ''; + } + if (humanize) { + content = h(content); + } + if (chalkFn) { + content = chalkFn(content + extra); + } + if (alignment) { + return { + content: content, + hAlign: alignment, + }; + } + return content; +}; \ No newline at end of file diff --git a/lib/worldoMeters.js b/lib/worldoMeters.js new file mode 100644 index 0000000..05e5ae3 --- /dev/null +++ b/lib/worldoMeters.js @@ -0,0 +1,59 @@ +const Table = require('cli-table3'); +const helpers = require('./helpers'); +const api = require('./api'); +const chalk = require('chalk'); +const { getEmoji, cFormatter } = helpers; + +exports.getWorldoMetersTable = async ({ + countryCode = null, + isCurl = true, + emojis = false, + minimal = false, + top = 1000 +}) => { + const secondColumnName = countryCode ? 'Country': 'World'; + const table = new Table({ + head: helpers.getTableHeadersV2(emojis, secondColumnName), + chars: helpers.getTableBorders(minimal), + style: helpers.getTableStyles(minimal), + }); + const { data, worldStats } = await api.getWorldoMetersData(countryCode); + + let rank = 1; + data.some(cd => { + const countryEmoji = getEmoji(cd.countryCode) || '🏳️'; + const values = [ + cFormatter(cd.country, chalk.cyanBright), + cFormatter(cd.cases, chalk.green, 'right', true), + cFormatter(cd.todayCases, chalk.cyanBright, 'right', true, ' β–²'), + cFormatter(cd.deaths, chalk.whiteBright, 'right', true), + cFormatter(cd.todayDeaths, chalk.redBright, 'right', true, ' β–²'), + cFormatter(cd.recovered, chalk.greenBright, 'right', true), + cFormatter(cd.active, chalk.blueBright , 'right', true), + cFormatter(cd.critical, chalk.magenta, 'right', true), + cFormatter(cd.casesPerOneMillion, chalk.yellow, 'right', true), + ...(emojis ? [countryEmoji] : []) + ]; + table.push({ [rank++]: values }); + return rank === top + 1; + }); + table.push({ + '': [ + 'World', + cFormatter(worldStats.cases, chalk.green, 'right', true), + cFormatter(worldStats.todayCases, chalk.cyanBright, 'right', true, ' β–²'), + cFormatter(worldStats.deaths, chalk.whiteBright, 'right', true), + cFormatter(worldStats.todayDeaths, chalk.redBright, 'right', true, ' β–²'), + cFormatter(worldStats.recovered, chalk.greenBright, 'right', true), + cFormatter(worldStats.active, chalk.blueBright , 'right', true), + cFormatter(worldStats.critical, chalk.magenta, 'right', true), + cFormatter(worldStats.casesPerOneMillion, chalk.yellow, 'right', true), + ...(emojis ? ['🌎'] : []) + ] + }); + const lastUpdated = new Date(); + const ret = table.toString() + helpers.footer(lastUpdated); + return isCurl ? ret : helpers.htmlTemplate(ret); +}; + + diff --git a/readme.md b/readme.md index 1aef20c..d6ca4a3 100644 --- a/readme.md +++ b/readme.md @@ -37,6 +37,11 @@ curl https://corona-stats.online?minimal=true ```sh curl https://corona-stats.online?top=20 ``` +### Get realtime stats (NEW) + +```sh +curl https://corona-stats.online?source=2 +``` ### Latest News (Work in Progress) @@ -74,6 +79,12 @@ corona corona italy ``` +### Get realtime stats (NEW) + +```sh +corona --source=2 +``` + ### Top N countries ```sh @@ -138,6 +149,7 @@ corona --color=false * [CSSEGISandData](https://github.com/CSSEGISandData/COVID-19) for the data. * [ExpDev07](https://github.com/ExpDev07/coronavirus-tracker-api) for the API. * [Zeit Now](https://github.com/zeit/now) for hosting. +* [https://github.com/NovelCOVID/API/](https://github.com/NovelCOVID/API/) for realtime stats API. ## Related Projects @@ -145,7 +157,7 @@ corona --color=false * * * -* +* ## License