Add news feed module.

This commit is contained in:
Michael Teeuw 2016-03-29 15:44:43 +02:00
parent 4fb7f099de
commit c09f8e97f7
5 changed files with 262 additions and 32 deletions

View File

@ -19,5 +19,6 @@ Things that still have to be implemented or changed.
####Helper scripts ####Helper scripts
- Only start helper scripts of modules that are actually loaded in the UI (config.js) - Only start helper scripts of modules that are actually loaded in the UI (config.js)
- Notification system, so that not every helper scripts needs it's own socket to the UI. - Notification system, so that not every helper scripts needs it's own socket to the UI.
- `modules/newsfeed/node_helper.js` now spawns it's own epxress webserver on port 8080. We need to create a solution for every module that needs a server side url.

View File

@ -38,10 +38,11 @@ var config = {
} }
}, },
{ {
module: 'helloworld', module: 'newsfeed',
position: 'bottom_bar', position: 'bottom_bar',
config: { config: {
text: 'Magic Mirror V2' feedUrl: 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml',
showPublishDate: true,
} }
}, },
] ]

View File

@ -0,0 +1,150 @@
/* global Module */
/* Magic Mirror
* Module: NewsFeed
*
* By Michael Teeuw http://michaelteeuw.nl
* MIT Licensed.
*/
Module.create({
// Default module config.
defaults: {
feedUrl: 'http://www.nytimes.com/services/xml/rss/nyt/HomePage.xml',
showPublishDate: true,
reloadInterval: 10 * 60 * 1000, // every 10 minutes
updateInterval: 7.5 * 1000,
animationSpeed: 2.5 * 1000,
proxyUrl: 'http://localhost:8080/?url=',
initialLoadDelay: 0, // 5 seconds delay. This delay is used to keep the OpenWeather API happy.
retryDelay: 2500,
},
// Define required scripts.
getScripts: function() {
return ['moment.js'];
},
// Define start sequence.
start: function() {
Log.info('Starting module: ' + this.name);
// Set locale.
moment.locale(config.language);
this.newsItems = [];
this.loaded = false;
this.scheduleFetch(this.config.initialLoadDelay);
this.fetchTimer = null;
this.activeItem = 0;
},
// Override dom generator.
getDom: function() {
var wrapper = document.createElement("div");
if (this.activeItem >= this.newsItems.length) {
this.activeItem = 0;
}
if (this.newsItems.length > 0) {
if (this.config.showPublishDate) {
var timestamp = document.createElement("div");
timestamp.className = "light small dimmed";
timestamp.innerHTML = this.capitalizeFirstLetter(moment(new Date(this.newsItems[this.activeItem].pubdate)).fromNow() + ':');
wrapper.appendChild(timestamp);
}
var title = document.createElement("div");
title.className = "bright medium light";
title.innerHTML = this.newsItems[this.activeItem].title;
wrapper.appendChild(title);
} else {
wrapper.innerHTML = "Loading news ...";
wrapper.className = "small dimmed";
}
return wrapper;
},
/* fetchNews(compliments)
* Requests new data from news proxy.
*/
fetchNews: function() {
var url = this.config.proxyUrl + encodeURIComponent(this.config.feedUrl);
var self = this;
var newsRequest = new XMLHttpRequest();
newsRequest.open("GET", url, true);
newsRequest.onreadystatechange = function() {
if(this.readyState === 4) {
if(this.status === 200) {
self.newsItems = JSON.parse(this.response);
if (!self.loaded) {
self.scheduleUpdateInterval();
}
self.loaded = true;
} else {
Log.error(self.name + ": Could not load news.");
}
self.scheduleFetch((self.loaded) ? -1 : self.config.retryDelay);
}
};
newsRequest.send();
},
/* scheduleUpdateInterval()
* Schedule visual update.
*/
scheduleUpdateInterval: function() {
var self = this;
self.updateDom(self.config.animationSpeed);
setInterval(function() {
self.activeItem++;
self.updateDom(self.config.animationSpeed);
}, this.config.updateInterval);
},
/* scheduleFetch()
* Schedule next news fetch.
*
* argument delay number - Milliseconds before next update. If empty, this.config.reloadInterval is used.
*/
scheduleFetch: function(delay) {
var nextLoad = this.config.reloadInterval;
if (typeof delay !== 'undefined' && delay >= 0) {
nextLoad = delay;
}
var self = this;
clearTimeout(this.fetchTimer);
this.fetchTimer = setTimeout(function() {
self.fetchNews();
}, nextLoad);
},
/* capitalizeFirstLetter(string)
* Capitalizes the first character of a string.
*
* argument string string - Input string.
*
* return string - Capitalized output string.
*/
capitalizeFirstLetter: function(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
});

View File

@ -0,0 +1,74 @@
// Configuration.
var config = {
port: 8080
};
// Load modules.
var express = require('express');
var request = require('request');
var FeedMe = require('feedme');
var validUrl = require('valid-url');
var app = express();
// Create NewsFetcher.
var NewsFetcher = (function() {
var self = this;
self.successCallback = function(){};
self.errorCallback = function(){};
self.items = [];
var parser = new FeedMe();
parser.on('item', function(item) {
//console.log(item);
self.items.push({
title: item.title,
pubdate: item.pubdate,
});
});
parser.on('end', function(item) {
self.successCallback(self.items);
});
parser.on('error', function(item) {
self.errorCallback();
});
return {
fetchNews: function(url, success, error) {
self.successCallback = success;
self.errorCallback = error;
request(url).pipe(parser);
}
};
})();
// Create route for fetcher.
app.get('/', function (req, res) {
if (!validUrl.isUri(req.query.url)){
res.status(404).send('No valid feed URL.');
return;
}
NewsFetcher.fetchNews(req.query.url, function(items) {
res.send(items);
}, function() {
res.status(400).send('Could not parse feed.');
});
});
// Listen on port.
app.listen(config.port, function () {
console.log('Feed proxy is running on port: ' + config.port);
});
console.log('Starting feed proxy on port: ' + config.port);

View File

@ -27,6 +27,10 @@
"electron-prebuilt": "latest" "electron-prebuilt": "latest"
}, },
"dependencies": { "dependencies": {
"walk": "latest" "express":"latest",
"request":"latest",
"walk": "latest",
"feedme": "latest",
"valid-url": "latest"
} }
} }