|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393 |
- /* Magic Mirror
- * Module: NewsFeed
- *
- * By Michael Teeuw https://michaelteeuw.nl
- * MIT Licensed.
- */
- Module.register("newsfeed", {
- // Default module config.
- defaults: {
- feeds: [
- {
- title: "New York Times",
- url: "https://rss.nytimes.com/services/xml/rss/nyt/HomePage.xml",
- encoding: "UTF-8" //ISO-8859-1
- }
- ],
- showAsList: false,
- showSourceTitle: true,
- showPublishDate: true,
- broadcastNewsFeeds: true,
- broadcastNewsUpdates: true,
- showDescription: false,
- wrapTitle: true,
- wrapDescription: true,
- truncDescription: true,
- lengthDescription: 400,
- hideLoading: false,
- reloadInterval: 5 * 60 * 1000, // every 5 minutes
- updateInterval: 10 * 1000,
- animationSpeed: 2.5 * 1000,
- maxNewsItems: 0, // 0 for unlimited
- ignoreOldItems: false,
- ignoreOlderThan: 24 * 60 * 60 * 1000, // 1 day
- removeStartTags: "",
- removeEndTags: "",
- startTags: [],
- endTags: [],
- prohibitedWords: [],
- scrollLength: 500,
- logFeedWarnings: false
- },
-
- // Define required scripts.
- getScripts: function () {
- return ["moment.js"];
- },
-
- //Define required styles.
- getStyles: function () {
- return ["newsfeed.css"];
- },
-
- // Define required translations.
- getTranslations: function () {
- // The translations for the default modules are defined in the core translation files.
- // Therefor we can just return false. Otherwise we should have returned a dictionary.
- // If you're trying to build your own module including translations, check out the documentation.
- return false;
- },
-
- // Define start sequence.
- start: function () {
- Log.info("Starting module: " + this.name);
-
- // Set locale.
- moment.locale(config.language);
-
- this.newsItems = [];
- this.loaded = false;
- this.error = null;
- this.activeItem = 0;
- this.scrollPosition = 0;
-
- this.registerFeeds();
-
- this.isShowingDescription = this.config.showDescription;
- },
-
- // Override socket notification handler.
- socketNotificationReceived: function (notification, payload) {
- if (notification === "NEWS_ITEMS") {
- this.generateFeed(payload);
-
- if (!this.loaded) {
- if (this.config.hideLoading) {
- this.show();
- }
- this.scheduleUpdateInterval();
- }
-
- this.loaded = true;
- this.error = null;
- } else if (notification === "NEWSFEED_ERROR") {
- this.error = this.translate(payload.error_type);
- this.scheduleUpdateInterval();
- }
- },
-
- //Override fetching of template name
- getTemplate: function () {
- if (this.config.feedUrl) {
- return "oldconfig.njk";
- } else if (this.config.showFullArticle) {
- return "fullarticle.njk";
- }
- return "newsfeed.njk";
- },
-
- //Override template data and return whats used for the current template
- getTemplateData: function () {
- // this.config.showFullArticle is a run-time configuration, triggered by optional notifications
- if (this.config.showFullArticle) {
- return {
- url: this.getActiveItemURL()
- };
- }
- if (this.error) {
- return {
- error: this.error
- };
- }
- if (this.newsItems.length === 0) {
- return {
- loaded: false
- };
- }
- if (this.activeItem >= this.newsItems.length) {
- this.activeItem = 0;
- }
-
- const item = this.newsItems[this.activeItem];
- const items = this.newsItems.map(function (item) {
- item.publishDate = moment(new Date(item.pubdate)).fromNow();
- return item;
- });
-
- return {
- loaded: true,
- config: this.config,
- sourceTitle: item.sourceTitle,
- publishDate: moment(new Date(item.pubdate)).fromNow(),
- title: item.title,
- description: item.description,
- items: items
- };
- },
-
- getActiveItemURL: function () {
- return typeof this.newsItems[this.activeItem].url === "string" ? this.newsItems[this.activeItem].url : this.newsItems[this.activeItem].url.href;
- },
-
- /**
- * Registers the feeds to be used by the backend.
- */
- registerFeeds: function () {
- for (let feed of this.config.feeds) {
- this.sendSocketNotification("ADD_FEED", {
- feed: feed,
- config: this.config
- });
- }
- },
-
- /**
- * Generate an ordered list of items for this configured module.
- *
- * @param {object} feeds An object with feeds returned by the node helper.
- */
- generateFeed: function (feeds) {
- let newsItems = [];
- for (let feed in feeds) {
- const feedItems = feeds[feed];
- if (this.subscribedToFeed(feed)) {
- for (let item of feedItems) {
- item.sourceTitle = this.titleForFeed(feed);
- if (!(this.config.ignoreOldItems && Date.now() - new Date(item.pubdate) > this.config.ignoreOlderThan)) {
- newsItems.push(item);
- }
- }
- }
- }
- newsItems.sort(function (a, b) {
- const dateA = new Date(a.pubdate);
- const dateB = new Date(b.pubdate);
- return dateB - dateA;
- });
- if (this.config.maxNewsItems > 0) {
- newsItems = newsItems.slice(0, this.config.maxNewsItems);
- }
-
- if (this.config.prohibitedWords.length > 0) {
- newsItems = newsItems.filter(function (item) {
- for (let word of this.config.prohibitedWords) {
- if (item.title.toLowerCase().indexOf(word.toLowerCase()) > -1) {
- return false;
- }
- }
- return true;
- }, this);
- }
- newsItems.forEach((item) => {
- //Remove selected tags from the beginning of rss feed items (title or description)
- if (this.config.removeStartTags === "title" || this.config.removeStartTags === "both") {
- for (let startTag of this.config.startTags) {
- if (item.title.slice(0, startTag.length) === startTag) {
- item.title = item.title.slice(startTag.length, item.title.length);
- }
- }
- }
-
- if (this.config.removeStartTags === "description" || this.config.removeStartTags === "both") {
- if (this.isShowingDescription) {
- for (let startTag of this.config.startTags) {
- if (item.description.slice(0, startTag.length) === startTag) {
- item.description = item.description.slice(startTag.length, item.description.length);
- }
- }
- }
- }
-
- //Remove selected tags from the end of rss feed items (title or description)
-
- if (this.config.removeEndTags) {
- for (let endTag of this.config.endTags) {
- if (item.title.slice(-endTag.length) === endTag) {
- item.title = item.title.slice(0, -endTag.length);
- }
- }
-
- if (this.isShowingDescription) {
- for (let endTag of this.config.endTags) {
- if (item.description.slice(-endTag.length) === endTag) {
- item.description = item.description.slice(0, -endTag.length);
- }
- }
- }
- }
- });
-
- // get updated news items and broadcast them
- const updatedItems = [];
- newsItems.forEach((value) => {
- if (this.newsItems.findIndex((value1) => value1 === value) === -1) {
- // Add item to updated items list
- updatedItems.push(value);
- }
- });
-
- // check if updated items exist, if so and if we should broadcast these updates, then lets do so
- if (this.config.broadcastNewsUpdates && updatedItems.length > 0) {
- this.sendNotification("NEWS_FEED_UPDATE", { items: updatedItems });
- }
-
- this.newsItems = newsItems;
- },
-
- /**
- * Check if this module is configured to show this feed.
- *
- * @param {string} feedUrl Url of the feed to check.
- * @returns {boolean} True if it is subscribed, false otherwise
- */
- subscribedToFeed: function (feedUrl) {
- for (let feed of this.config.feeds) {
- if (feed.url === feedUrl) {
- return true;
- }
- }
- return false;
- },
-
- /**
- * Returns title for the specific feed url.
- *
- * @param {string} feedUrl Url of the feed
- * @returns {string} The title of the feed
- */
- titleForFeed: function (feedUrl) {
- for (let feed of this.config.feeds) {
- if (feed.url === feedUrl) {
- return feed.title || "";
- }
- }
- return "";
- },
-
- /**
- * Schedule visual update.
- */
- scheduleUpdateInterval: function () {
- this.updateDom(this.config.animationSpeed);
-
- // Broadcast NewsFeed if needed
- if (this.config.broadcastNewsFeeds) {
- this.sendNotification("NEWS_FEED", { items: this.newsItems });
- }
-
- this.timer = setInterval(() => {
- this.activeItem++;
- this.updateDom(this.config.animationSpeed);
-
- // Broadcast NewsFeed if needed
- if (this.config.broadcastNewsFeeds) {
- this.sendNotification("NEWS_FEED", { items: this.newsItems });
- }
- }, this.config.updateInterval);
- },
-
- resetDescrOrFullArticleAndTimer: function () {
- this.isShowingDescription = this.config.showDescription;
- this.config.showFullArticle = false;
- this.scrollPosition = 0;
- // reset bottom bar alignment
- document.getElementsByClassName("region bottom bar")[0].classList.remove("newsfeed-fullarticle");
- if (!this.timer) {
- this.scheduleUpdateInterval();
- }
- },
-
- notificationReceived: function (notification, payload, sender) {
- const before = this.activeItem;
- if (notification === "MODULE_DOM_CREATED" && this.config.hideLoading) {
- this.hide();
- } else if (notification === "ARTICLE_NEXT") {
- this.activeItem++;
- if (this.activeItem >= this.newsItems.length) {
- this.activeItem = 0;
- }
- this.resetDescrOrFullArticleAndTimer();
- Log.debug(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")");
- this.updateDom(100);
- } else if (notification === "ARTICLE_PREVIOUS") {
- this.activeItem--;
- if (this.activeItem < 0) {
- this.activeItem = this.newsItems.length - 1;
- }
- this.resetDescrOrFullArticleAndTimer();
- Log.debug(this.name + " - going from article #" + before + " to #" + this.activeItem + " (of " + this.newsItems.length + ")");
- this.updateDom(100);
- }
- // if "more details" is received the first time: show article summary, on second time show full article
- else if (notification === "ARTICLE_MORE_DETAILS") {
- // full article is already showing, so scrolling down
- if (this.config.showFullArticle === true) {
- this.scrollPosition += this.config.scrollLength;
- window.scrollTo(0, this.scrollPosition);
- Log.debug(this.name + " - scrolling down");
- Log.debug(this.name + " - ARTICLE_MORE_DETAILS, scroll position: " + this.config.scrollLength);
- } else {
- this.showFullArticle();
- }
- } else if (notification === "ARTICLE_SCROLL_UP") {
- if (this.config.showFullArticle === true) {
- this.scrollPosition -= this.config.scrollLength;
- window.scrollTo(0, this.scrollPosition);
- Log.debug(this.name + " - scrolling up");
- Log.debug(this.name + " - ARTICLE_SCROLL_UP, scroll position: " + this.config.scrollLength);
- }
- } else if (notification === "ARTICLE_LESS_DETAILS") {
- this.resetDescrOrFullArticleAndTimer();
- Log.debug(this.name + " - showing only article titles again");
- this.updateDom(100);
- } else if (notification === "ARTICLE_TOGGLE_FULL") {
- if (this.config.showFullArticle) {
- this.activeItem++;
- this.resetDescrOrFullArticleAndTimer();
- } else {
- this.showFullArticle();
- }
- } else if (notification === "ARTICLE_INFO_REQUEST") {
- this.sendNotification("ARTICLE_INFO_RESPONSE", {
- title: this.newsItems[this.activeItem].title,
- source: this.newsItems[this.activeItem].sourceTitle,
- date: this.newsItems[this.activeItem].pubdate,
- desc: this.newsItems[this.activeItem].description,
- url: this.getActiveItemURL()
- });
- }
- },
-
- showFullArticle: function () {
- this.isShowingDescription = !this.isShowingDescription;
- this.config.showFullArticle = !this.isShowingDescription;
- // make bottom bar align to top to allow scrolling
- if (this.config.showFullArticle === true) {
- document.getElementsByClassName("region bottom bar")[0].classList.add("newsfeed-fullarticle");
- }
- clearInterval(this.timer);
- this.timer = null;
- Log.debug(this.name + " - showing " + this.isShowingDescription ? "article description" : "full article");
- this.updateDom(100);
- }
- });
|