Last active
November 8, 2018 13:38
-
-
Save bojan88/21e54decfd5961cf91d71e54921deee8 to your computer and use it in GitHub Desktop.
Get the air quality data for your current location.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env node | |
const { get } = require('http'); | |
const debug = process.argv.includes('--debug'); | |
const output = (function() { | |
const outputArgInd = process.argv.indexOf('--output'); | |
if(outputArgInd === -1) { | |
return '䷸{pm25}|{pm10}@{time}'; | |
} | |
if(process.argv.length - 1 === outputArgInd) { | |
throw new Error('Invalid arguments'); | |
} | |
return process.argv[outputArgInd + 1]; | |
})(); | |
(async function() { | |
const airApiToken = process.env.AQI_TOKEN; | |
try { | |
if(!airApiToken) { | |
throw new Error('Missing AQ Token. Go to http://aqicn.org/data-platform/token/ to get it'); | |
} | |
const ipApiUrl = 'http://ip-api.com/json'; | |
log(`Fetching location data from '${ipApiUrl}'`); | |
const ipApiResponse = await fetch(ipApiUrl); | |
log('Location Data response'); | |
log(ipApiResponse); | |
const { lat, lon, city } = ipApiResponse; | |
const airApiLocationUrl = `http://api.waqi.info/feed/geo:${lat};${lon}/?token=${airApiToken}`; | |
const airApiCityUrl = `http://api.waqi.info/feed/${city}/?token=${airApiToken}`; | |
log(`\nFetching air data from '${airApiLocationUrl}'`); | |
log(`\nFetching air data from '${airApiCityUrl}'`); | |
const [airApiLocationRes, airApiCityRes] = await Promise.all([ | |
fetch(airApiLocationUrl), | |
fetch(airApiCityUrl) | |
]); | |
log('Location Data response'); | |
log(airApiLocationRes); | |
log('City Data response'); | |
log(airApiCityRes); | |
const airApiRresponse = getCloserLocation([airApiLocationRes, airApiCityRes], { lat, lon }); | |
if(airApiRresponse.status === 'error') { | |
throw new Error(airApiRresponse.data); | |
} | |
const { | |
data: { | |
aqi, | |
iaqi, | |
time: { | |
s: measuredDateStr | |
} | |
} | |
} = airApiRresponse; | |
const measuredDate = new Date(measuredDateStr); | |
const time = getTime(measuredDate); | |
// flatten the response | |
const flattenedIaqi = {}; | |
for(let key in iaqi) { | |
flattenedIaqi[key] = iaqi[key]['v']; | |
} | |
const outStr = outputWithValues({ | |
aqi, | |
time, | |
...flattenedIaqi | |
}); | |
console.log(outStr); | |
} catch(err) { | |
log(err); | |
console.log('\u4DF8', '-'); | |
} | |
})(); | |
function outputWithValues(values) { | |
return output.replace(/{(.+?)}/g, (_, name) => values[name] || name); | |
} | |
function fetch(url) { | |
return new Promise((resolve, reject) => { | |
get(encodeURI(url), (res) => { | |
const { statusCode } = res; | |
if(statusCode !== 200) { | |
return reject(new Error(`Request for ${url} faild with status code ${statusCode}`)); | |
} | |
res.setEncoding('utf8'); | |
let rawData = ''; | |
res.on('data', (chunk) => { rawData += chunk; }); | |
res.on('end', () => { | |
try { | |
resolve(JSON.parse(rawData)) | |
} catch (err) { | |
reject(err); | |
} | |
}); | |
}).on('error', (err) => { | |
log(err); | |
setTimeout(() => { | |
fetch(url).then(resolve).catch(reject); | |
}, 1000); | |
}); | |
}); | |
} | |
function log(err) { | |
if(!debug) { | |
return; | |
} | |
if(err instanceof Error) { | |
return console.error(err); | |
} | |
console.log(typeof err === 'string' ? err : JSON.stringify(err, null, 2)); | |
} | |
function padTime(val) { | |
return val.toString().length === 1 ? `0${val}` : val; | |
} | |
function getTime(date) { | |
let hours = padTime(date.getHours()); | |
let minutes = padTime(date.getMinutes()); | |
const diff = Date.now() - date.getTime(); | |
const daysDiff = Math.floor(diff / (1000 * 60 * 60 * 24)); | |
const daysDiffStr = daysDiff ? `${daysDiff}d` : ''; | |
return `${daysDiffStr}${hours}:${minutes}`; | |
} | |
function getCloserLocation(locations, { lat, lon }) { | |
let closest; | |
let minDistance = Infinity; | |
for(let location of locations) { | |
const { data: { city: { geo } } } = location; | |
const distance = Math.abs(geo[0] - lat) + Math.abs(geo[1] - lon); | |
if(distance < minDistance) { | |
minDistance = distance; | |
closest = location; | |
} | |
} | |
return closest; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment