Instantly share code, notes, and snippets.

Embed
What would you like to do?
Get the air quality data for your current location.
#!/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