Added weather alerts
This commit is contained in:
parent
326ca1005b
commit
9f13a3e391
9
package-lock.json
generated
9
package-lock.json
generated
@ -9588,6 +9588,15 @@
|
|||||||
"error": "7.0.2"
|
"error": "7.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"request-json": {
|
||||||
|
"version": "0.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/request-json/-/request-json-0.6.3.tgz",
|
||||||
|
"integrity": "sha512-5TVnMD3LaeK0GRCyFlsNgJf5Fjg8J8j7VEfsoJESSWZlWRgPIf7IojsBLbTHcg2798JrrRkJ6L3k1+wj4sglgw==",
|
||||||
|
"requires": {
|
||||||
|
"depd": "1.1.2",
|
||||||
|
"request": "2.83.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"request-promise": {
|
"request-promise": {
|
||||||
"version": "4.2.2",
|
"version": "4.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz",
|
||||||
|
@ -34,6 +34,7 @@
|
|||||||
"muicss": "^0.9.36",
|
"muicss": "^0.9.36",
|
||||||
"node-foursquare-venues": "^1.1.0",
|
"node-foursquare-venues": "^1.1.0",
|
||||||
"openweather-apis": "^3.3.5",
|
"openweather-apis": "^3.3.5",
|
||||||
|
"request-json": "^0.6.3",
|
||||||
"twitter": "^1.7.1",
|
"twitter": "^1.7.1",
|
||||||
"uglifyify": "^4.0.5",
|
"uglifyify": "^4.0.5",
|
||||||
"underscore": "^1.8.3",
|
"underscore": "^1.8.3",
|
||||||
|
20
server.js
20
server.js
@ -21,6 +21,7 @@ app.use(express.static(path.join(__dirname, sitePath)));
|
|||||||
|
|
||||||
app.get('/weather', cache('45 minutes'), (req, res) => {
|
app.get('/weather', cache('45 minutes'), (req, res) => {
|
||||||
if (req.query.hasOwnProperty('ll'))
|
if (req.query.hasOwnProperty('ll'))
|
||||||
|
|
||||||
weather.doGetOpenWeather(req.query.ll)
|
weather.doGetOpenWeather(req.query.ll)
|
||||||
.then((d) => {
|
.then((d) => {
|
||||||
res.send(d);
|
res.send(d);
|
||||||
@ -36,6 +37,25 @@ app.get('/weather', cache('45 minutes'), (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
app.get('/weatheralert', cache('45 minutes'), (req, res) => {
|
||||||
|
if (req.query.hasOwnProperty('ll'))
|
||||||
|
// weather.doGetOpenWeather(req.query.ll)
|
||||||
|
// doGetDarkSkyWeather
|
||||||
|
weather.doGetDarkSkyWeather(req.query.ll)
|
||||||
|
.then((d) => {
|
||||||
|
res.send(d);
|
||||||
|
}).catch((e) => {
|
||||||
|
logger.error(e);
|
||||||
|
res.status(500).send('There was an error!');
|
||||||
|
});
|
||||||
|
|
||||||
|
else {
|
||||||
|
// throw new Error('Weather: LL missing');
|
||||||
|
logger.warn('Weather: LL missing');
|
||||||
|
res.status(500).send('LL Missing');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/fsexplore', cache('30 minutes'), (req, res) => {
|
app.get('/fsexplore', cache('30 minutes'), (req, res) => {
|
||||||
if (req.query.hasOwnProperty('ll'))
|
if (req.query.hasOwnProperty('ll'))
|
||||||
foursquare.doGetFourSquareExplore(req.query.ll)
|
foursquare.doGetFourSquareExplore(req.query.ll)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
|
const Client = require('request-json');
|
||||||
const logger = require('log4js').getLogger('Weather');
|
const logger = require('log4js').getLogger('Weather');
|
||||||
const weather = require('openweather-apis');
|
const weather = require('openweather-apis');
|
||||||
|
|
||||||
@ -6,6 +6,9 @@ logger.level = 'debug';
|
|||||||
|
|
||||||
const openWeatherApiKey = process.env.openweatherAPI || '936a0ed9eb23b95cf08fc9f693c24264';
|
const openWeatherApiKey = process.env.openweatherAPI || '936a0ed9eb23b95cf08fc9f693c24264';
|
||||||
|
|
||||||
|
const darkskyApiKey = process.env.darkskyApiKey || '9ad2a41d420f3cf4960571bb886f710c';
|
||||||
|
const DSclient = Client.createClient(`https://api.darksky.net/forecast/${ darkskyApiKey }/`);
|
||||||
|
|
||||||
weather.setAPPID(openWeatherApiKey);
|
weather.setAPPID(openWeatherApiKey);
|
||||||
weather.setLang('en');
|
weather.setLang('en');
|
||||||
// weather.setCity('Glasgow City');
|
// weather.setCity('Glasgow City');
|
||||||
@ -38,5 +41,17 @@ function doGetOpenWeatherForecast(ll) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function doGetDarkSkyWeather(ll) {
|
||||||
|
const query = `${ll}?units=uk2&exclude=daily,flags,minutely,hourly`;
|
||||||
|
logger.debug(query);
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
DSclient.get(query, function(err, res, body) {
|
||||||
|
if (err || !body || !body.currently)
|
||||||
|
return reject(err);
|
||||||
|
|
||||||
module.exports = { doGetOpenWeather, doGetOpenWeatherForecast };
|
return resolve(body);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { doGetOpenWeather, doGetOpenWeatherForecast, doGetDarkSkyWeather };
|
||||||
|
@ -25,10 +25,16 @@
|
|||||||
<div class="mui-container">
|
<div class="mui-container">
|
||||||
<div id="greet"></div>
|
<div id="greet"></div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="mui-container" id="viewFrame">
|
||||||
|
<div id="weatherAlertShell" class="mui-panel" style="display: none;">
|
||||||
|
<div id="weatherAlertTitle" class="mui--text-title cardTitle">Weather Alert</div>
|
||||||
|
<div id="weatherAlert"></div>
|
||||||
|
</div>
|
||||||
<div id="bymeShell" class="mui-panel" style="display: none;">
|
<div id="bymeShell" class="mui-panel" style="display: none;">
|
||||||
<div id="byMeTitle" class="mui--text-title cardTitle">By me</div>
|
<div id="byMeTitle" class="mui--text-title cardTitle">By me</div>
|
||||||
<div id="byme"></div>
|
<div id="byme"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="nearbyShell" class="mui-panel" style="display: none;">
|
<div id="nearbyShell" class="mui-panel" style="display: none;">
|
||||||
@ -37,14 +43,14 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="newsShell" class="mui-panel" style="display: none;">
|
<div id="newsShell" class="mui-panel" style="display: none;">
|
||||||
<div class="mui--text-title cardTitle">Latest news</div>
|
<div class="mui--text-title cardTitle">Latest news</div>
|
||||||
<div id="news" class="scrolling-wrapper-flexbox"></div>
|
<div id="news" class="scrolling-wrapper-flexbox"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="weatherShell" class="mui-panel" style="display: none;">
|
<div id="weatherShell" class="mui-panel" style="display: none;">
|
||||||
<div class="mui--text-title cardTitle">Weather</div>
|
<div class="mui--text-title cardTitle">Weather</div>
|
||||||
<div id="weather"></div>
|
<div id="weather"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
@ -113,7 +113,6 @@ const LocationModel = Backbone.Model.extend({
|
|||||||
console.error(err);
|
console.error(err);
|
||||||
this.set('location', newLocation);
|
this.set('location', newLocation);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
newLocation.city = current.city;
|
newLocation.city = current.city;
|
||||||
@ -126,16 +125,16 @@ const LocationModel = Backbone.Model.extend({
|
|||||||
// console.log('>> distanceFromLastGeocode', distanceFromLastGeocode, TimeFormat.fromMs(timestamp - lastGeocode.timestamp, 'hh:mm:ss'));
|
// console.log('>> distanceFromLastGeocode', distanceFromLastGeocode, TimeFormat.fromMs(timestamp - lastGeocode.timestamp, 'hh:mm:ss'));
|
||||||
console.log('>> distanceFromLastGeocode', distanceFromLastGeocode, TimeFormat.fromMs(currentTime - lastGeocode.timestamp, 'hh:mm:ss'));
|
console.log('>> distanceFromLastGeocode', distanceFromLastGeocode, TimeFormat.fromMs(currentTime - lastGeocode.timestamp, 'hh:mm:ss'));
|
||||||
console.log(`(currentTime:${currentTime}, timestamp:${timestamp}, lastGeocode.timestamp:${lastGeocode.timestamp})`);
|
console.log(`(currentTime:${currentTime}, timestamp:${timestamp}, lastGeocode.timestamp:${lastGeocode.timestamp})`);
|
||||||
console.log('(currentTime - current.timestamp > 900000) ' , (currentTime - current.timestamp > 900000));
|
console.log('(currentTime - current.timestamp > 900000) ', (currentTime - current.timestamp > 900000));
|
||||||
//if ((distanceFromLast > 0.5 && distanceFromLast < 2.0) || (timestamp - current.timestamp > 900000)) {
|
// if ((distanceFromLast > 0.5 && distanceFromLast < 2.0) || (timestamp - current.timestamp > 900000)) {
|
||||||
if ((distanceFromLast > 0.5 && distanceFromLast < 2.0) || (currentTime - current.timestamp > 900000)) {
|
if ((distanceFromLast > 0.5 && distanceFromLast < 2.0) || ((currentTime - current.timestamp > 900000) && (currentTime - lastGeocode.timestamp < 1.8e+6))) {
|
||||||
// dont bother re geocoding
|
// dont bother re geocoding
|
||||||
console.log('Slightly moved from previous');
|
console.log('Slightly moved from previous');
|
||||||
this.set('location', newLocation);
|
this.set('location', newLocation);
|
||||||
this.set('moving', moving);
|
this.set('moving', moving);
|
||||||
}
|
}
|
||||||
else if (distanceFromLastGeocode >= 2.0 || (timestamp - lastGeocode.timestamp > 1.8e+6) ) {
|
else if (distanceFromLastGeocode >= 2.0 || (currentTime - lastGeocode.timestamp >= 1.8e+6) ) {
|
||||||
console.log('Moved from previous', (timestamp - lastGeocode.timestamp > 1.8e+6));
|
console.log('Moved from previous', (currentTime - lastGeocode.timestamp >= 1.8e+6));
|
||||||
console.info('>> Location:geocoder request');
|
console.info('>> Location:geocoder request');
|
||||||
geocoder.reverse(latlong)
|
geocoder.reverse(latlong)
|
||||||
.then(function(res) {
|
.then(function(res) {
|
||||||
|
@ -92,7 +92,7 @@ const WeatherModel = Backbone.Model.extend({
|
|||||||
},
|
},
|
||||||
'getWeather': function() {
|
'getWeather': function() {
|
||||||
// const ll = this.get('llShort');
|
// const ll = this.get('llShort');
|
||||||
console.info('>> Weather:request');
|
console.log('>> Weather request');
|
||||||
const llFixed = this.get('llFixed');
|
const llFixed = this.get('llFixed');
|
||||||
request({
|
request({
|
||||||
'url': `${window.loc}/weather`,
|
'url': `${window.loc}/weather`,
|
||||||
@ -175,7 +175,7 @@ const WeatherView = Backbone.View.extend({
|
|||||||
console.log('>> Weather No location yet');
|
console.log('>> Weather No location yet');
|
||||||
},
|
},
|
||||||
'render': function() {
|
'render': function() {
|
||||||
console.info('>> Weather:Render');
|
console.log('>> Weather:Render');
|
||||||
|
|
||||||
this.$el.empty();
|
this.$el.empty();
|
||||||
const item = this.wCollection.first();
|
const item = this.wCollection.first();
|
||||||
|
161
src/v1/js/WeatherAlert.js
Normal file
161
src/v1/js/WeatherAlert.js
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
const $ = require('jquery');
|
||||||
|
const _ = require('underscore');
|
||||||
|
const Backbone = require('backbone');
|
||||||
|
const request = require('request');
|
||||||
|
const fecha = require('fecha');
|
||||||
|
const { get } = require('lodash');
|
||||||
|
const { reduceOpenWeather } = require('./reducers');
|
||||||
|
const { distance, toHour } = require('./utils');
|
||||||
|
|
||||||
|
const WeatherAlertModel = Backbone.Model.extend({
|
||||||
|
'defaults': function (obj) {
|
||||||
|
// return a new object
|
||||||
|
return {
|
||||||
|
'update': new Date().getTime()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'initialize': function () {
|
||||||
|
this.run = false;
|
||||||
|
this.listenTo(this, 'change:ll', this.onChange);
|
||||||
|
this.listenTo(this, 'change:update', this.onChange);
|
||||||
|
},
|
||||||
|
'onChange': function () {
|
||||||
|
console.log('Weather LL has changed');
|
||||||
|
// if distance change > 10km
|
||||||
|
// if its been an hour since last update
|
||||||
|
|
||||||
|
if (!this.has('log')) {
|
||||||
|
console.info('First run');
|
||||||
|
this.getWeatherAlert();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const log = this.get('log');
|
||||||
|
const timeDiff = new Date().getTime() - log.time;
|
||||||
|
const ll = this.get('ll').split(',');
|
||||||
|
|
||||||
|
const dist = distance(log.lat, log.long, ll[0], ll[1]);
|
||||||
|
console.log('Weather distance:', dist);
|
||||||
|
|
||||||
|
if ((dist > 5.0) && (timeDiff > 1.8e+6))
|
||||||
|
this.getWeatherAlert();
|
||||||
|
else if (timeDiff > 3.6e+6) {
|
||||||
|
console.log('WeatherAlert hourly update');
|
||||||
|
this.getWeatherAlert();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'getWeatherAlert': function () {
|
||||||
|
// const ll = this.get('llShort');
|
||||||
|
console.log('>> WeatherAlert request');
|
||||||
|
const llFixed = this.get('ll');
|
||||||
|
request({
|
||||||
|
'url': `${window.loc}/weatheralert`,
|
||||||
|
'method': 'GET',
|
||||||
|
'qs': {
|
||||||
|
'll': llFixed
|
||||||
|
}
|
||||||
|
}, function (err, res, body) {
|
||||||
|
if (err)
|
||||||
|
console.error(err);
|
||||||
|
else {
|
||||||
|
// console.log(body);
|
||||||
|
const fsJSON = JSON.parse(body);
|
||||||
|
|
||||||
|
const alerts = get(fsJSON, 'alerts', []);
|
||||||
|
const item = alerts.slice(0, 1);
|
||||||
|
|
||||||
|
this.set('alert', item);
|
||||||
|
|
||||||
|
console.log(fsJSON);
|
||||||
|
|
||||||
|
this.logUpdate();
|
||||||
|
}
|
||||||
|
}.bind(this));
|
||||||
|
},
|
||||||
|
'logUpdate': function () {
|
||||||
|
console.log('WeatherAlert logging:');
|
||||||
|
|
||||||
|
const ll = this.get('ll').split(',');
|
||||||
|
|
||||||
|
const log = { 'lat': ll[0], 'long': ll[1], 'time': new Date().getTime() };
|
||||||
|
|
||||||
|
console.log('>>WeatherAlert log', log);
|
||||||
|
this.set('log', log);
|
||||||
|
|
||||||
|
this.timerID = setTimeout(
|
||||||
|
() => this.tick(),
|
||||||
|
toHour
|
||||||
|
);
|
||||||
|
|
||||||
|
// console.log(this);
|
||||||
|
},
|
||||||
|
'tick': function () {
|
||||||
|
console.log('Set update');
|
||||||
|
this.set('update', new Date().getTime());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const WeatherAlertView = Backbone.View.extend({
|
||||||
|
'className': 'mui--align-middle',
|
||||||
|
|
||||||
|
'initialize': function(options) {
|
||||||
|
this.eventBus = options.eventBus;
|
||||||
|
this.location = options.location;
|
||||||
|
|
||||||
|
this.$parent = this.$el.parent();
|
||||||
|
this.$title = $('#weatherAlertTitle');
|
||||||
|
|
||||||
|
// this.model.bind('change', this.render, this);
|
||||||
|
this.location.bind('change', this.updateLocation, this);
|
||||||
|
this.model.bind('change:alert', this.render, this);
|
||||||
|
},
|
||||||
|
'template': _.template(`
|
||||||
|
<div>
|
||||||
|
<div class="mui--text-body1 %>"><%=description%></div>
|
||||||
|
|
||||||
|
<div class="mui--text-dark-secondary mui--text-caption mui--text-right">
|
||||||
|
Expires <%= readdate %>
|
||||||
|
</div>
|
||||||
|
`),
|
||||||
|
'attributes': function() {
|
||||||
|
return {
|
||||||
|
'class': 'mui--align-middle'
|
||||||
|
};
|
||||||
|
},
|
||||||
|
'events': {
|
||||||
|
'click': 'doClick'
|
||||||
|
},
|
||||||
|
'updateLocation': function(l) {
|
||||||
|
console.log('>> WeatherAlert Location has changed...');
|
||||||
|
|
||||||
|
if (l.has('location')) {
|
||||||
|
const location = l.get('location');
|
||||||
|
if (location.hasOwnProperty('atHome'))
|
||||||
|
this.model.set('ll', location.llFixed);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
console.log('>> Weather No location yet');
|
||||||
|
},
|
||||||
|
'render': function() {
|
||||||
|
console.log('>> WeatherAlert:Render');
|
||||||
|
|
||||||
|
this.$el.empty();
|
||||||
|
|
||||||
|
const alert = this.model.get('alert');
|
||||||
|
|
||||||
|
if (alert.length === 0)
|
||||||
|
// nothing to see, hide the alert.
|
||||||
|
this.$parent.hide();
|
||||||
|
else {
|
||||||
|
const fts = new Date(alert[0].expires * 1000);
|
||||||
|
alert[0].readdate = fecha.format(fts, 'default');
|
||||||
|
this.$title.html(alert[0].title);
|
||||||
|
this.$el.html(this.template(alert[0]));
|
||||||
|
this.$el.parent().show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { WeatherAlertModel, WeatherAlertView };
|
@ -7,6 +7,7 @@ const { LocationModel } = require('./Location');
|
|||||||
const { GreetModel, GreetView } = require('./Greet');
|
const { GreetModel, GreetView } = require('./Greet');
|
||||||
const { NearbyModel, NearbyView } = require('./Nearby');
|
const { NearbyModel, NearbyView } = require('./Nearby');
|
||||||
const { WeatherModel, WeatherView } = require('./Weather');
|
const { WeatherModel, WeatherView } = require('./Weather');
|
||||||
|
const { WeatherAlertModel, WeatherAlertView } = require('./WeatherAlert');
|
||||||
const { NewsModel, NewsView } = require('./News');
|
const { NewsModel, NewsView } = require('./News');
|
||||||
const { ByMeModel, ByMeView } = require('./RightByMe');
|
const { ByMeModel, ByMeView } = require('./RightByMe');
|
||||||
|
|
||||||
@ -56,7 +57,9 @@ else
|
|||||||
|
|
||||||
app.nearby = new NearbyView({ 'model': new NearbyModel(), 'eventBus': app.eventBus, 'location': app.locationModel, 'el':'#nearby' });
|
app.nearby = new NearbyView({ 'model': new NearbyModel(), 'eventBus': app.eventBus, 'location': app.locationModel, 'el':'#nearby' });
|
||||||
|
|
||||||
app.weather = new WeatherView({ 'model': new WeatherModel(), 'eventBus': app.eventBus, 'location': app.locationModel, 'el':'#weather' });
|
app.weather = new WeatherView({ 'model': new WeatherModel(), 'eventBus': app.eventBus, 'location': app.locationModel, 'el':'#weather', 'viewFrame':'#viewFrame' });
|
||||||
|
|
||||||
|
app.weatherAlert = new WeatherAlertView({ 'model': new WeatherAlertModel(), 'eventBus': app.eventBus, 'location': app.locationModel, 'el':'#weatherAlert', 'viewFrame':'#viewFrame' });
|
||||||
|
|
||||||
app.news = new NewsView({ 'model': new NewsModel(), 'eventBus': app.eventBus, 'el':'#news' });
|
app.news = new NewsView({ 'model': new NewsModel(), 'eventBus': app.eventBus, 'el':'#news' });
|
||||||
|
|
||||||
|
@ -1,6 +1,36 @@
|
|||||||
const fecha = require('fecha');
|
const fecha = require('fecha');
|
||||||
const { get } = require('lodash');
|
const { get } = require('lodash');
|
||||||
|
|
||||||
|
function reduceDarksky(item, city) {
|
||||||
|
// Openweather returns timestamps in seconds. Moment requires them in milliseconds.
|
||||||
|
// Replaced Moment with Fecha.
|
||||||
|
// Too many issues trying to get Moment packaged into the bundle.
|
||||||
|
|
||||||
|
if (!item || item === null) return {};
|
||||||
|
const currently = get(item, 'currently');
|
||||||
|
const alerts = get(item, 'alerts', []);
|
||||||
|
const fts = new Date(currently.time * 1000);
|
||||||
|
|
||||||
|
const newItem = { 'timestamp': fts,
|
||||||
|
'icon': `wi-wu-${currently.icon}`,
|
||||||
|
'summary': currently.summary,
|
||||||
|
'temp': parseInt(currently.temperature, 10),
|
||||||
|
'feels': parseFloat(currently.apparentTemperature, 10),
|
||||||
|
'datelong': fecha.format(fts, 'YYYY-MM-DDTHH:mm:ss.SSSZZ'),
|
||||||
|
'readdate': fecha.format(fts, 'default'),
|
||||||
|
'time': fts,
|
||||||
|
'date': fecha.format(fts, 'D/M'),
|
||||||
|
'day': fecha.format(fts, 'ddd'),
|
||||||
|
'city': city
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
'item' : newItem,
|
||||||
|
'alerts': (alerts.length === 0) ? alerts : alerts.slice(0, 1)
|
||||||
|
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function reduceOpenWeather(item, city) {
|
function reduceOpenWeather(item, city) {
|
||||||
// Openweather returns timestamps in seconds. Moment requires them in milliseconds.
|
// Openweather returns timestamps in seconds. Moment requires them in milliseconds.
|
||||||
// Replaced Moment with Fecha.
|
// Replaced Moment with Fecha.
|
||||||
@ -88,4 +118,4 @@ const pubdateSrc = fecha.parse(item.pubdate, 'ddd, DD MMM YYYY HH:mm:SS ZZ');
|
|||||||
</article>`;
|
</article>`;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = { reduceOpenWeather, reduceNearby, reduceEuronews };
|
module.exports = { reduceOpenWeather, reduceNearby, reduceEuronews, reduceDarksky };
|
||||||
|
Loading…
Reference in New Issue
Block a user