hyperbutter-weather
Version:
A plugin to get weather from Yahoo and send it into the Hyper Butter server
177 lines (152 loc) • 5.51 kB
JavaScript
;
const EventEmitter = require('events');
const util = require('util');
const restler = require('restler');
const ENDPOINT = 'https://query.yahooapis.com/v1/public/yql';
const DEFAULT_UPDATE_MINS = 5;
const woeIds = [];
const cache = {};
let updateTimer = undefined;
let lastUpdate = 0;
function Weather(config, settings) {
EventEmitter.call(this);
this.init = () => {
this.emit('subscribe', {
'get-forecast': this.getForecast,
'get-update': this.update,
});
// if we have some pre-defined places, let's go ahead and fetch the forecast
if (config.places) {
let foundCount = 0;
config.places.forEach((place) => {
this.getForecast({ place, watch: true }, (error, forecast) => {
if (error) this.emit('error', error);
else foundCount++;
if (foundCount === config.places.length) this.update();
});
});
}
// create a timer to update the weather
updateTimer = setInterval(this.update, (config.updateMins || DEFAULT_UPDATE_MINS) * 60000);
};
const getWoeidFromName = (name, callback) => {
restler.get(ENDPOINT, {
query: {
q: `select woeid from geo.places(1) where text="${name}"`,
format: 'json',
},
})
.on('complete', (response) => {
const results = response.query.results;
if (results === null) {
const errorString = `Could not find a place with the name "${name}"`;
if (callback) callback (errorString);
this.emit('warn', errorString);
return;
}
if (callback) callback(null, results.place.woeid);
});
};
// makes something a little easier to consume
const createForecastObject = (channel) => {
if (channel === null || channel === undefined || channel.item === undefined) return {};
return {
location: channel.location,
condition: channel.item.condition,
forecast: channel.item.forecast,
lastUpdate: channel.item.pubDate,
units: channel.units,
astronomy: channel.astronomy,
wind: channel.wind,
atmosphere: channel.atmosphere,
};
};
/**
Gets a forecast with the option to also keep watching it
@param args.place {String} - The place you want to get weather on
@param args.watch {Boolean} - True if you want to keep receving updates about this place
**/
this.getForecast = (args, callback) => {
if (args.place === undefined) {
if (callback) callback('getForecast missing "place" in object argument');
return;
}
getWoeidFromName(args.place, (error, woeid) => {
if (woeid !== undefined) {
// go grab the forecast
restler.get(ENDPOINT, {
query: {
q: `select * from weather.forecast where woeid=${woeid}`,
format: 'json',
},
})
.on('complete', (response) => {
const results = response.query.results;
if (results === null) {
const errorString = `Could not get a forecast for "${args.place}"`;
if (callback) callback (errorString);
this.emit('warn', errorString);
return;
}
if (args.watch === true && woeIds.indexOf(woeid) === -1) {
woeIds.push(woeid);
this.emit('status', `Watching ${results.channel.location.city},${results.channel.location.region}`);
}
const forecastObj = createForecastObject(results.channel);
forecastObj.woeid = woeid;
cache[woeid] = forecastObj;
if (callback) callback(null, forecastObj);
this.emit('forecast', forecastObj);
});
}
});
};
this.update = (callback) => {
// make sure we have something to update
if (woeIds.length === 0) return;
// if the last update is sooner than the update minutes, just grab it from cache
const now = new Date().getTime();
if (now - lastUpdate < (config.updateMins || DEFAULT_UPDATE_MINS) * 60000) {
const cacheArr = [];
woeIds.forEach(id => cacheArr.push(cache[id]));
if (callback) callback(null, cacheArr);
this.emit('update', cacheArr);
return;
}
// loop through all of our known woeIds and get the current forecast
restler.get(ENDPOINT, {
query: {
q: `select * from weather.forecast where woeid in (${woeIds.join(',')})`,
format: 'json',
},
})
.on('complete', (response) => {
if (response instanceof Error) this.emit('error', response);
else {
const results = response.query.results;
if (results === null || results.channel === null) {
const errorString = `There was a problem updating the following woeIds: ${woeIds}`;
if (callback) callback (errorString);
this.emit('warn', errorString);
return;
}
// make sure we are working with an array
let channel = results.channel;
if(!Array.isArray(channel)) channel = [channel];
const updateArr = [];
channel.forEach((info, index) => {
const forecastObj = createForecastObject(info);
forecastObj.woeid = woeIds[index];
cache[woeIds[index]] = forecastObj;
updateArr.push(forecastObj);
});
lastUpdate = new Date().getTime();
if (callback) callback(null, updateArr);
this.emit('update', updateArr);
}
});
};
return this;
};
util.inherits(Weather, EventEmitter);
module.exports = Weather;