dark-sky-app
Version:
Dark-Sky Cli App
437 lines (386 loc) • 10 kB
JavaScript
#!/usr/bin/env node
'use strict';
const Configstore = require('configstore');
const meow = require('meow');
const axios = require('axios');
const opn = require('opn');
const inquirer = require('inquirer');
const Table = require('cli-table3');
const grid = new Table();
const ora = require('ora');
const chalk = require('chalk');
const boxen = require('boxen');
const checkForUpdate = require('update-check');
const pkg = require('./package.json');
const conf = new Configstore(pkg.name, {
apikey: '',
lang: 'en',
units: 'si',
current: '',
cities: {},
});
const weekday = [
'Sunday',
'Monday',
'Tuesday',
'Wednesday',
'Thursday',
'Friday',
'Saturday',
];
const cli = meow(
`
Usage
$ ds <input>
Options
--current -c Show current weather
--today -t Show todays weather
--week -w Show weekly weather
--add -a Add new location
--get -g Get saved location
--delete -d Delete saved location
--settings -s Show Settings JSON
--version -v Show version
--help -h Show help
`,
{
flags: {
current: { alias: 'c' },
today: { alias: 't' },
week: { alias: 'w' },
add: { alias: 'a' },
get: { alias: 'g' },
delete: { alias: 'd' },
settings: { alias: 's' },
version: { alias: 'v' },
help: { alias: 'h' },
},
autoVersion: false,
}
);
/* CHECK API KEY AND INPUT */
if (conf.get('apikey') === '') {
addAPIKey();
} else {
checkInput();
}
/* API KEY SET */
function addAPIKey() {
ora('You need DarkSky API Key to continue').fail();
ora('You can get one free from https://darksky.net/dev\n').info();
inquirer
.prompt([
{
message: 'DarkSky API Key',
type: 'input',
name: 'key',
},
])
.then(answers => {
conf.set('apikey', answers.key);
});
}
/* INPUT FLAGS */
function checkInput() {
if (cli.flags.a) {
addCity();
} else if (cli.flags.g) {
const cities = conf.get('cities');
const cityCount = Object.keys(cities).length;
if (cityCount === 0) {
ora('No localiton registered. Please add a localiton first').fail();
return;
}
getCities(cities);
} else if (cli.flags.d) {
const cities = conf.get('cities');
const cityCount = Object.keys(cities).length;
if (cityCount === 0) {
ora('No location registered to delete').fail();
return;
}
removeCity(cities);
} else if (cli.flags.s) {
console.log('Opening settings file: ' + chalk.magenta.bold(conf.path));
opn(conf.path, { wait: false });
} else if (cli.flags.c || cli.flags.t || cli.flags.w) {
if (conf.get('current') === '') {
ora('Please select a location first').fail();
return;
}
fecthWeather();
} else if (cli.flags.v) {
checkUpdates();
} else {
cli.showHelp();
}
}
/* CHECK UPDATES */
async function checkUpdates() {
let update = null;
try {
update = await checkForUpdate(pkg);
} catch (error) {
console.log(chalk.red('\nFailed to check for updates:'));
console.error(error);
}
let updateText;
let commandText;
if (update) {
updateText =
'Update available ' +
chalk.gray(pkg.version) +
' → ' +
chalk.green(update.latest);
commandText =
'Run ' + chalk.cyan('npm i -g ' + pkg.name) + ' to update';
} else {
updateText = 'You are using the latest version';
commandText = pkg.name + ' v' + pkg.version;
}
console.log(
boxen(updateText + '\n' + commandText, {
padding: 1,
margin: 1,
align: 'center',
})
);
}
/* SETTINGS */
function addCity() {
inquirer
.prompt([
{
message: 'City Name:',
type: 'input',
name: 'name',
},
{
message: 'Latitude:',
type: 'input',
name: 'lat',
},
{
message: 'Longitude',
type: 'input',
name: 'lon',
},
{
message: 'Are your choices correct?',
type: 'list',
name: 'isConfirmed',
choices: ['Yes', 'No'],
},
])
.then(answers => {
if (answers.isConfirmed === 'No') {
addCity();
} else {
conf.set('cities.' + answers.name + '.lat', answers.lat);
conf.set('cities.' + answers.name + '.lon', answers.lon);
conf.set('current', answers.name);
}
});
}
function getCities(cities) {
inquirer
.prompt([
{
message: 'Switch to a city',
type: 'list',
name: 'cityName',
choices: Object.keys(cities),
},
])
.then(answers => {
conf.set('current', answers.cityName);
});
}
function removeCity(cities) {
inquirer
.prompt([
{
message: 'Delete a city',
type: 'list',
name: 'cityName',
choices: Object.keys(cities),
},
])
.then(answers => {
conf.set('current', '');
conf.delete('cities.' + answers.cityName);
});
}
/* FETCH AND WRITE WEATHER */
function fecthWeather() {
let exclude = '';
let spinner = ora();
if (cli.flags.c) {
exclude = '&exclude=hourly,minutely,daily,weekly,flags,alerts';
spinner = ora('Fetching DarkSky For Current Weather').start();
} else if (cli.flags.t) {
exclude = '&exclude=daily,minutely,flags,alerts';
spinner = ora('Fetching DarkSky For Daily Weather').start();
} else if (cli.flags.w) {
exclude = '&exclude=hourly,minutely,currently,flags,alerts';
spinner = ora('Fetching DarkSky For Weekly Weather').start();
}
const cityName = conf.get('current');
const cityData = conf.get('cities.' + cityName);
const request =
'https://api.darksky.net/forecast/' +
conf.get('apikey') +
'/' +
cityData.lat +
',' +
cityData.lon +
'?lang=' +
conf.get('lang') +
'&units=' +
conf.get('units') +
exclude;
axios
.get(request)
.then(response => {
spinner.succeed('Fetching DarkSky Data Succeeded');
writeWeather(response.data, cityName);
})
.catch(error => {
spinner.fail('Error Fetching DarkSky Data');
console.log(error);
});
}
function writeWeather(response, cityName) {
// Set Title
grid.push([
chalk.green.bold(cityName),
chalk.green.bold('Temperature'),
chalk.green.bold('Precipation'),
chalk.green.bold('Pressure'),
chalk.green.bold('Wind'),
chalk.green.bold('Clouds'),
chalk.green.bold('UV'),
chalk.green.bold('Summary'),
]);
if (cli.flags.c) {
pushData(response.currently, response.currently);
} else if (cli.flags.t) {
for (let index = 0; index < 25; index++) {
const day = response.hourly.data[index];
const previousDay =
index === 0 ? day : response.hourly.data[index - 1];
pushData(day, previousDay);
}
} else if (cli.flags.w) {
for (let index = 0; index < response.daily.data.length; index++) {
const day = response.daily.data[index];
const previousDay =
index === 0 ? day : response.daily.data[index - 1];
pushData(day, previousDay);
}
}
console.log(grid.toString());
}
function pushData(day, previousDay) {
grid.push([
getDayName(day.time),
getTemperature(day),
getPrecip(day),
getPressure(day.pressure, previousDay.pressure),
getWind(day.windSpeed, day.windBearing),
getClouds(day.cloudCover),
getUV(day.uvIndex),
day.summary,
]);
}
/* GET FUNCTIONS */
function getDayName(time) {
const d = new Date(time * 1000);
if (cli.flags.c) {
return chalk.green.bold('Current');
}
if (cli.flags.t) {
return chalk.green.bold(
pad(d.getHours(), '0') + ':' + pad(d.getMinutes(), '0')
);
}
return chalk.green.bold(weekday[d.getDay()]);
}
function getTemperature(day) {
if (cli.flags.w) {
return (
formatTemperature(day.temperatureMin) +
' - ' +
formatTemperature(day.temperatureMax)
);
}
return formatTemperature(day.temperature);
}
function getPrecip(day) {
if (day.precipProbability === 0) return '';
let precipType;
const precipProbability = pad(round(day.precipProbability * 100), ' ');
const shouldColorize = precipProbability >= 25;
if (day.precipType === 'rain') {
precipType = shouldColorize
? (precipType = chalk.blue('(Rain)'))
: '(Rain)';
} else if (day.precipType === 'sleet') {
precipType = shouldColorize
? (precipType = chalk.blue('(Sleet)'))
: '(Sleet)';
} else if (day.precipType === 'snow') {
precipType = shouldColorize
? (precipType = chalk.blue('(Snow)'))
: '(Snow)';
}
return precipProbability + '% ' + precipType;
}
function getPressure(pressure, prevPressure) {
if (pressure > prevPressure)
return chalk.red('↑ ') + round(pressure) + ' hPa';
if (pressure < prevPressure)
return chalk.blue('↓ ') + round(pressure) + ' hPa';
return '• ' + round(pressure) + ' hPa';
}
function getWind(speed, bearing) {
if (bearing >= 292.5 && bearing < 337.5) return '↘ ' + speed + ' kph';
if (bearing >= 247.5 && bearing < 292.5) return '→ ' + speed + ' kph';
if (bearing >= 202.5 && bearing < 247.5) return '↗ ' + speed + ' kph';
if (bearing >= 157.5 && bearing < 202.5) return '↑ ' + speed + ' kph';
if (bearing >= 112.5 && bearing < 157.5) return '↖ ' + speed + ' kph';
if (bearing >= 67.5 && bearing < 112.5) return '← ' + speed + ' kph';
if (bearing >= 22.5 && bearing < 67.5) return '↙ ' + speed + ' kph';
if (bearing > 337.5 && bearing < 22.5) return '↓ ' + speed + ' kph';
return '';
}
function getClouds(cloudCoverage) {
return pad(round(cloudCoverage * 100), ' ') + '%';
}
function getUV(uv) {
if (uv <= 2) return chalk.hex('#6bbf30')(uv);
if (uv >= 3 && uv < 5) return chalk.hex('#ffd208')(uv);
if (uv >= 5 && uv < 7) return chalk.hex('#ffae00')(uv);
if (uv >= 7 && uv < 10) return chalk.hex('#ea3447')(uv);
if (uv >= 10) return chalk.hex('#a44c6f')(uv);
return uv;
}
/* FORMAT HELPERS */
function formatTemperature(temp) {
temp = round(temp);
if (temp <= 0) return chalk.cyan(temp + '°c');
if (temp >= 1 && temp < 10) return chalk.cyan(temp + '°c');
if (temp >= 10 && temp < 15) return chalk.blue(temp + '°c');
if (temp >= 15 && temp < 20) return chalk.white(temp + '°c');
if (temp >= 20 && temp < 25) return chalk.white(temp + '°c');
if (temp >= 25 && temp < 30) return chalk.yellow(temp + '°c');
if (temp >= 30) return chalk.red(temp + '°c');
return temp + '°c';
}
function round(number) {
const value = (number * 2).toFixed() / 2;
return value.toFixed();
}
function pad(num, add) {
return (add + num).slice(-2);
}