├── .gitignore ├── LICENSE ├── MMM-NewsAPI.css ├── MMM-NewsAPI.js ├── README.md ├── headlines.html ├── node_helper.js ├── package-lock.json ├── package.json ├── screenshot1.png └── template.html /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Bernard Mumble 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MMM-NewsAPI.css: -------------------------------------------------------------------------------- 1 | 2 | @keyframes hide { 3 | 0% { opacity:1; } 4 | 100% { opacity:0; } 5 | } 6 | 7 | @keyframes show { 8 | 0% { opacity:0; } 9 | 100% { opacity:1; } 10 | } 11 | 12 | .untouchable .touchable { 13 | display:none; 14 | } 15 | 16 | .showArticle { 17 | animation: show linear 1s; 18 | } 19 | 20 | .hideArticle { 21 | animation: hide linear 1s; 22 | } 23 | 24 | #NEWSAPI { 25 | line-height:1.1em; 26 | } 27 | 28 | #NEWSAPI .header .title { 29 | display:block; 30 | color:#FFF; 31 | font-size:99%; 32 | 33 | } 34 | 35 | #NEWSAPI .content { 36 | padding:10px; 37 | font-size:90%; 38 | } 39 | 40 | #NEWSAPI .footer { 41 | text-align:right; 42 | } 43 | #NEWSAPI .footer .sourceName, 44 | #NEWSAPI .footer .publishedAt, 45 | #NEWSAPI .footer .author { 46 | font-size:80%; 47 | text-align:right; 48 | display:inline-block; 49 | } 50 | 51 | #NEWSAPI .footer .sourceName { 52 | text-transform: uppercase; 53 | } 54 | 55 | /* custom className */ 56 | #NEWSAPI .redTitle .title { 57 | color:#F99; 58 | } 59 | 60 | /* Type: Horizontal */ 61 | #NEWSAPI.horizontal { 62 | width:100%; 63 | } 64 | 65 | #NEWSAPI.horizontal .content { 66 | text-align:center; 67 | } 68 | 69 | #NEWSAPI.horizontal .content .articleImage { 70 | margin-right:10px; 71 | float:left; 72 | max-width:15vw; 73 | max-height:15vh; 74 | } 75 | 76 | /* Type: Vertical */ 77 | #NEWSAPI.vertical { 78 | font-size: calc(2vh); 79 | width:400px; 80 | } 81 | 82 | #NEWSAPI.vertical .content .articleImage { 83 | max-width:400px; 84 | max-height:300px; 85 | margin-left:auto; 86 | margin-right:auto; 87 | display:block; 88 | } 89 | 90 | #NEWSAPI.vertical #NEWSAPI_CONTENT { 91 | padding-bottom:5px; 92 | } 93 | 94 | #NEWSAPI .article #NEWSAPI_QRCODE { 95 | float: right; 96 | margin-left: 20px; 97 | } 98 | -------------------------------------------------------------------------------- /MMM-NewsAPI.js: -------------------------------------------------------------------------------- 1 | /* global Log Module QRious */ 2 | Module.register("MMM-NewsAPI", { 3 | // Declare default inputs 4 | defaults: { 5 | apiKey: "", 6 | type: "horizontal", 7 | choice: "headlines", 8 | pageSize: 20, 9 | sortBy: "publishedAt", 10 | timeFormat: "relative", 11 | // className: "NEWSAPI", 12 | templateFile: "template.html", 13 | drawInterval: 1000 * 30, 14 | fetchInterval: 1000 * 60 * 60, 15 | debug: false, 16 | QRCode: false, 17 | query: { 18 | country: "us", 19 | category: "", 20 | q: "", 21 | qInTitle: "", 22 | sources: "", 23 | domains: "", 24 | excludeDomains: "", 25 | language: "en" 26 | } 27 | }, 28 | 29 | // Get the Stylesheet 30 | getStyles () { 31 | return [this.file("MMM-NewsAPI.css")] 32 | }, 33 | 34 | // Import QR code script file 35 | getScripts () { 36 | return [this.file("node_modules/qrious/dist/qrious.min.js")] 37 | }, 38 | 39 | // Start process 40 | start () { 41 | this.firstUpdate = 0 42 | this.index = 0 43 | this.timer = null 44 | this.template = "" 45 | this.newsArticles = [] 46 | if (this.config.debug) { Log.log("config: ", JSON.stringify(this.config)) } 47 | // Start function call to node_helper 48 | this.getInfo() 49 | // Schedule the next update 50 | this.scheduleUpdate() 51 | }, 52 | 53 | stop () { 54 | Log.info(`Stopping module ${this.name}`) 55 | }, 56 | 57 | getDom () { 58 | const wrapper = document.createElement("div") 59 | wrapper.id = "NEWSAPI" 60 | wrapper.className = this.config.type 61 | const newsContent = document.createElement("div") 62 | newsContent.id = "NEWS_CONTENT" 63 | wrapper.appendChild(newsContent) 64 | wrapper.classList.add("untouchable") 65 | return wrapper 66 | }, 67 | 68 | notificationReceived (notification) { 69 | switch (notification) { 70 | case "DOM_OBJECTS_CREATED": 71 | this.readTemplate() 72 | this.sendSocketNotification("START") 73 | break 74 | } 75 | }, 76 | 77 | // Schedule the next update 78 | scheduleUpdate (delay) { 79 | if (this.config.debug) { Log.log("Fetch Interval: ", this.config.fetchInterval) } 80 | let nextLoad = this.config.fetchInterval 81 | if (typeof delay !== "undefined" && delay >= 0) { 82 | nextLoad = delay 83 | } 84 | const self = this 85 | setInterval(() => { 86 | Log.debug("getting the next batch of data") 87 | self.getInfo() 88 | }, nextLoad) 89 | }, 90 | 91 | // Send Socket Notification and start node_helper 92 | getInfo () { 93 | if (this.config.debug) { Log.log("selected choice: ", this.config.choice) } 94 | if (this.config.choice === "headlines") { 95 | this.sendSocketNotification("headlines", this.config) 96 | } else if (this.config.choice === "everything") { 97 | this.sendSocketNotification("everything", this.config) 98 | } else { 99 | Log.log("NewsAPI: Invalid choice defined in config/config.js. Use 'headlines' or 'everything'") 100 | return true 101 | } 102 | }, 103 | 104 | // Receive Socket Notification 105 | socketNotificationReceived (notification, payload) { 106 | if (this.config.debug) { Log.log("payload received: ", JSON.stringify(payload)) } 107 | if (notification === "NEWSAPI_UPDATE") { 108 | this.newsArticles = payload 109 | if (this.firstUpdate === 0) { 110 | this.firstUpdate = 1 111 | this.index = 0 112 | this.draw() 113 | } 114 | } 115 | }, 116 | 117 | async readTemplate () { 118 | const file = this.config.templateFile 119 | const url = `modules/MMM-NewsAPI/${file}` 120 | try { 121 | const response = await fetch(url) 122 | if (!response.ok) { 123 | throw new Error(`A Problem has been encountered retrieving the Template File (${response.statusText})`) 124 | } 125 | this.template = await response.text() 126 | } catch (error) { 127 | Log.error(error.message) 128 | } 129 | }, 130 | 131 | draw () { 132 | clearTimeout(this.timer) 133 | this.timer = null 134 | const tag = [ 135 | "sourceId", "author", "content", "description", "articleId", 136 | "sourceName", "title", "url", "urlToImage", "publishedAt" 137 | ] 138 | const article = this.newsArticles[this.index] 139 | let template = this.template 140 | 141 | for (const i in tag) { 142 | const t = tag[i] 143 | const tu = `%${t.toUpperCase()}%` 144 | template = template.replace(tu, article[t]) 145 | } 146 | 147 | const imgtag = article.urlToImage ? `` : "" 148 | template = template.replace("%ARTICLEIMAGE%", imgtag) 149 | template = template.replace("%CLASSNAME%", "NEWSAPI") // "NEWS" 150 | template = template.replace("%AUTHOR%", article.author) 151 | const news = document.getElementById("NEWSAPI") 152 | 153 | const newsContent = document.getElementById("NEWS_CONTENT") 154 | news.classList.add("hideArticle") 155 | news.classList.remove("showArticle") 156 | for (const j in article) { news.dataset[j] = article[j] } 157 | 158 | setTimeout(() => { 159 | newsContent.innerHTML = "" 160 | news.classList.remove("hideArticle") 161 | news.classList.add("showArticle") 162 | newsContent.innerHTML = template 163 | if (this.config.QRCode) { 164 | const qr = new QRious({ 165 | element: document.getElementById("NEWSAPI_QRCODE"), 166 | value: article.url 167 | }) 168 | } else { 169 | const qrCodeElement = document.getElementById("NEWSAPI_QRCODE") 170 | if (qrCodeElement) { 171 | qrCodeElement.parentNode.removeChild(qrCodeElement) 172 | } 173 | } 174 | }, 900) 175 | this.timer = setTimeout(() => { 176 | this.index++ 177 | if (this.index >= this.newsArticles.length) { this.index = 0 } 178 | this.draw() 179 | }, this.config.drawInterval) 180 | } 181 | }) 182 | 183 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MMM-NewsAPI 2 | 3 | A [MagicMirror²](https://magicmirror.builders) module to to get news from [NewsAPi.org](https://newsapi.org/). 4 | 5 | [![Platform](https://img.shields.io/badge/platform-MagicMirror-informational)](https://MagicMirror.builders) 6 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE) 7 | 8 | ## Support 9 | 10 | If you like my module you can support my work by giving me a star ir buy me a coffee. 11 | 12 | Buy Me A Coffee 13 | 14 | ![Example](screenshot1.png) 15 | 16 | ## Updates 17 | 18 | - [x] Issue raise that null's were displayed for certain information. Update to rather return a blank string. 19 | - [X] Make sortBy an optional parameter for both `headlines` and `everything` 20 | - [X] Add DEBUG Handling. 21 | - - [X] Only add `debug: true` to the config section if you are experiencing issues and require more information to find issues. 22 | - [X] Replaced moment with luxon 23 | - [X] Replaced request with internal fetch 24 | - [X] Remove the following line from your config `className: "NEWS",`. It is no longer required and module will stop working if not removed. 25 | - [X] Optional QR code via QRious 26 | - [X] Remove QRCode element when option is not selected. This was causing display issues in vertical mode. When selected will display 27 | 28 | ## Dependencies 29 | 30 | - luxon@3.5.0 31 | 32 | ## Installation 33 | 34 | In your terminal, go to your MagicMirror's Module folder: 35 | 36 | ```bash 37 | cd ~/MagicMirror/modules 38 | ``` 39 | 40 | Clone this repository: 41 | 42 | ```bash 43 | git clone https://github.com/mumblebaj/MMM-NewsAPI 44 | ``` 45 | 46 | Add the module to the modules array in the `config/config.js` file: 47 | 48 | ```javascript 49 | { 50 | module: "MMM-NewsAPI", 51 | header: "NEWS", 52 | position: "bottom_bar", 53 | }, 54 | ``` 55 | 56 | ## Configuration options 57 | 58 | The following properties can be configured: 59 | 60 | | Option | Description 61 | | ---------------------------- | ----------- 62 | | `header` | The header text

**Default value:** `'NEWS'` 63 | | `choice` | Type of query to be instantiated

**Possible values:** `headlines` or `everything`
**Default value:** `headlines` 64 | | `type` | Orientation

**Possible values:** `horizontal` or `vertical`
**Default value:** `horizontal`. Module does currently not cater for vertical 65 | | `pageSize` | The number of articles to be returned. Max = 100
**Default value:** `20` 66 | | `sortBy` | The order to sort the articles in.
**Possible values:** `relevancy`, `popularity`, `publishedAt`
only available for `choice: "everything"` 67 | | `drawInterval` | The amount of time each article is displayed
**Default value:** `30` seconds 68 | | `templateFile` | The template file to use. You can create your own template file and reference here. For now use `template.html` 69 | | `fetchInterval` | The time interval between fetching new articles. There is a daily limit of 100 calls per apiKey. Best to set this to `100 * 60 * 60` 70 | | `apiKey` | You can obtain an API Key from [NewsAPi.org](https://newsapi.org/) 71 | | `QRCode` | Boolean true/false value to display QR code for article URL. Default is false. 72 | 73 | ## Query Options 74 | 75 | **The following query options can be defined:** 76 | 77 | When specifying the query options take note of the following:
78 | When using `headlines`, `country` and `sources` cannot be used together.
The `domains` option cannot be used on it's own, you have to specify any of the following parameters with it: `sources`, `q`, `language`, `country`, `category`.
When using `everything`, you cannot use the `country` option.
To try more combinations you can visit [NewsAPi.org](https://newsapi.org/) 79 | 80 | | Option | Description 81 | | ---------------------------- | ----------- 82 | | `country` | The 2-letter ISO 3166-1 code of the country you want to get headlines for.
Possible options:
`ae` `ar` `at` `au` `be` `bg` `br` `ca` `ch` `cn`
`co` `cu` `cz` `de` `eg` `fr` `gb` `gr` `hk` `hu` `id` `ie` `ve` `za`
`il` `in` `it` `jp` `kr` `lt` `lv` `ma` `mx` `my` `ng` `nl`
`no` `nz` `ph` `pl` `pt` `ro` `rs` `ru` `sa` `se` `sg` `si`
`sk` `th` `tr` `tw` `ua` `us`.
**Note: you can't mix this param with the sources param** 83 | | `category` | The category you want to get headlines for.
Possible options: `business` `entertainment` `general` `health` `science` `sports` `technology`.
**Note: you can't mix this param with the sources param.** 84 | | `q` | Keywords or phrases to search for in the article title and body 85 | | `qInTitle` | Keywords or phrases to search for in the article title only 86 | | `sources` | A comma-seperated string of identifiers (maximum 20) for the news sources or blogs you want headlines from
TO obtain a list of sources or sources for your country you can put the following in your browser. `https://newsapi.org/v2/top-headlines/sources?apiKey=your-api-key-here&country=your-country-code-here` 87 | | `domains` | A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to restrict the search to. 88 | | `excludeDomains` | A comma-seperated string of domains (eg bbc.co.uk, techcrunch.com, engadget.com) to remove from the results. 89 | | `language` | The 2-letter ISO-639-1 code of the language you want to get headlines for. Possible options:
`ar` `de` `en` `es` `fr` `he` `it` `nl` `no` `pt` `ru` `se` `ud` `zh` 90 | 91 | ## Config Example 92 | 93 | **everything** Example 94 | 95 | ```javascript 96 | { 97 | module: "MMM-NewsAPI", 98 | header: "news", 99 | position: "bottom_bar", 100 | config: { 101 | apiKey: "", 102 | type: "horizontal", 103 | choice: "everything", 104 | pageSize: 10, 105 | sortBy: "publishedAt", 106 | drawInterval: 1000*30, 107 | templateFile: "template.html", 108 | fetchInterval: 1000*60*60, 109 | QRCode: true, 110 | query: { 111 | country: "", 112 | category: "", 113 | q: "", 114 | qInTitle: "", 115 | sources: "", 116 | domains: "cnn.com,nytimes.com,news24.com", 117 | excludeDomains: "", 118 | language: "" 119 | } 120 | } 121 | }, 122 | ``` 123 | 124 | **headlines** Example 125 | 126 | ```javascript 127 | { 128 | module: "MMM-NewsAPI", 129 | header: "news", 130 | position: "bottom_bar", 131 | config: { 132 | apiKey: "", 133 | type: "horizontal", 134 | choice: "headlines", 135 | pageSize: 10, 136 | sortBy: "relevance", 137 | drawInterval: 1000*30, 138 | templateFile: "template.html", 139 | fetchInterval: 1000*60*60, 140 | query: { 141 | country: "us", 142 | category: "", 143 | q: "covid", 144 | qInTitle: "", 145 | sources: "", 146 | domains: "nytimes.com", 147 | excludeDomains: "", 148 | language: "" 149 | } 150 | } 151 | }, 152 | ``` 153 | 154 | **Notes** 155 | 156 | - `apiKey` is **required**. You should first create an account on 157 | 158 | ## Updating 159 | 160 | To update the module to the latest version, use your terminal to go to your MMM-NewsAPI module folder and type the following command: 161 | 162 | ```bash 163 | cd ~/MagicMirror/modules/MMM-NewsAPI 164 | git pull 165 | npm install 166 | ``` 167 | -------------------------------------------------------------------------------- /headlines.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
%TITLE%
5 |
6 | 11 |
12 | 13 | -------------------------------------------------------------------------------- /node_helper.js: -------------------------------------------------------------------------------- 1 | // Imports 2 | const Log = require("logger") 3 | const NodeHelper = require("node_helper") 4 | const luxon = require("luxon") 5 | const querystring = require("querystring") // querystring has been deprecated and need to be replaced by URLSearchParams possibly 6 | const DateTime = luxon.DateTime 7 | 8 | // Any declarations 9 | 10 | module.exports = NodeHelper.create({ 11 | requiresVersion: "2.21.0", 12 | 13 | start () { 14 | Log.log(`Starting node_helper for module: ${this.name}`) 15 | 16 | // Declare any defaults 17 | this.config = null 18 | this.articles = [] 19 | this.endPoint1 = "https://newsapi.org/v2/top-headlines?" 20 | this.endPoint2 = "https://newsapi.org/v2/everything?" 21 | }, 22 | 23 | deconHeadlines (payload) { 24 | const query = payload 25 | for (const i in query) { 26 | const q = query[i] 27 | let qs = {} 28 | if (Object.hasOwn(q, "sources") && q.sources !== "") { 29 | const t = q.sources.replace(/\s/gu, "") 30 | qs = {...qs, sources: t} 31 | } 32 | if (Object.hasOwn(q, "q") && q.q !== "") { qs = {...qs, q: q.q} } 33 | if (Object.hasOwn(q, "qInTitle") && q.qInTitle !== "") { qs = {...qs, qInTitle: q.qInTitle} } 34 | if (Object.hasOwn(q, "category") && q.category !== "") { qs = {...qs, category: q.category} } 35 | if (Object.hasOwn(q, "domains") && q.domains !== "") { 36 | const d = q.domains.replace(/\s/gu, "") 37 | qs = {...qs, domains: d} 38 | } 39 | if (Object.hasOwn(q, "excludeDomains") && q.excludeDomains !== "") { 40 | const ed = q.domains.replace(/\s/g, "") 41 | qs = {...qs, excludeDomains: ed} 42 | } 43 | if (Object.hasOwn(q, "language") && q.language !== "") { qs = {...qs, language: q.language} } 44 | if (Object.hasOwn(q, "country") && q.country !== "") { 45 | if (q.sources === "") { 46 | qs = {...qs, country: q.country} 47 | } else if (!q.sources) { 48 | qs = {...qs, country: q.country} 49 | } 50 | } 51 | qs = {...qs, pageSize: payload.pageSize} 52 | if (Object.hasOwn(q, "sortBy") && q.sortBy !== "") { qs = {...qs, sortBy: payload.sortBy} } 53 | qs = {...qs, apiKey: payload.apiKey} 54 | const qp = querystring.stringify(qs) 55 | var callScript = this.endPoint1 + qp 56 | } 57 | if (payload.debug) { Log.log("headlines callscript: ", callScript) } 58 | this.getData(callScript, payload) 59 | }, 60 | 61 | deconEverything (payload) { 62 | const query = payload 63 | for (const i in query) { 64 | const q = query[i] 65 | let qs = {} 66 | if (Object.hasOwn(q, "country") && q.country !== "") { 67 | Log.log("[MMM-NEWSAPI] Invalid Option specified. Country not allowed with 'everything'!") 68 | } 69 | if (Object.hasOwn(q, "category") && q.category !== "") { 70 | Log.log("[MMM-NEWSAPI] Invalid Option specified. Country not allowed with 'everything'!") 71 | } 72 | if (Object.hasOwn(q, "q") && q.q !== "") { qs = {...qs, q: q.q} } 73 | if (Object.hasOwn(q, "qInTitle") && q.qInTitle !== "") { qs = {...qs, qInTitle: q.qInTitle} } 74 | if (Object.hasOwn(q, "domains") && q.domains !== "") { 75 | const d = q.domains.replace(/\s/gu, "") 76 | qs = {...qs, domains: d} 77 | } 78 | if (Object.hasOwn(q, "excludeDomains") && q.excludeDomains !== "") { 79 | const ed = q.excludeDomains.replace(/\s/gu, "") 80 | qs = {...qs, excludeDomains: ed} 81 | } 82 | if (Object.hasOwn(q, "sources") && q.sources !== "") { 83 | const t = q.sources.replace(/\s/gu, "") 84 | qs = {...qs, sources: t} 85 | } 86 | if (Object.hasOwn(q, "language") && q.language !== "") { qs = {...qs, language: q.language} } 87 | qs = {...qs, pageSize: payload.pageSize} 88 | if (Object.hasOwn(q, "sortBy") && q.sortBy !== "") { qs = {...qs, sortBy: payload.sortBy} } 89 | qs = {...qs, apiKey: payload.apiKey} 90 | const qp = querystring.stringify(qs) 91 | var callScript = this.endPoint2 + qp 92 | } 93 | if (payload.debug) { Log.log("everything callscript: ", callScript) } 94 | this.getData(callScript, payload) 95 | }, 96 | 97 | formatResults (ret, payload) { 98 | const results = [] 99 | for (const j in ret.articles) { 100 | const article = ret.articles[j] 101 | article.sourceName = article.source.name 102 | article.sourceId = article.source.Id 103 | const luxTime = DateTime.fromISO(article.publishedAt).toRelative() 104 | article.publishedAt = luxTime 105 | article.content = article.content ? article.content : "" 106 | article.author = article.author ? article.author : "" 107 | article.description = article.description ? article.description : "" 108 | results.push(article) 109 | } 110 | if (results.length > 0) { this.articles = this.articles.concat(results) } 111 | if (payload.debug) { Log.log("sending articles: ", JSON.stringify(this.articles)) } 112 | this.sendSocketNotification("NEWSAPI_UPDATE", this.articles) 113 | }, 114 | 115 | async getData (callScript, payload) { 116 | const response = await fetch(callScript) 117 | 118 | if (!response.status === 200) { 119 | Log.error(`Error retrieving NewsAPI data: ${response.statusCode} ${response.statusText}`) 120 | return; 121 | } 122 | const parsedResponse = await response.json() 123 | if (payload.debug) { Log.log("response received: ", JSON.stringify(parsedResponse)) } 124 | this.formatResults(parsedResponse, payload) 125 | }, 126 | 127 | // Socket Notification Received 128 | socketNotificationReceived (notification, payload) { 129 | if (notification === "headlines") { 130 | this.deconHeadlines(payload) 131 | } else if (notification === "everything") { 132 | this.deconEverything(payload) 133 | } 134 | } 135 | }) 136 | 137 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-newsapi", 3 | "version": "1.1.4", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "mmm-newsapi", 9 | "version": "1.1.4", 10 | "license": "MIT", 11 | "dependencies": { 12 | "luxon": "^3.5.0", 13 | "qrious": "^4.0.2" 14 | } 15 | }, 16 | "node_modules/luxon": { 17 | "version": "3.5.0", 18 | "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", 19 | "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==", 20 | "license": "MIT", 21 | "engines": { 22 | "node": ">=12" 23 | } 24 | }, 25 | "node_modules/qrious": { 26 | "version": "4.0.2", 27 | "resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz", 28 | "integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g==" 29 | } 30 | }, 31 | "dependencies": { 32 | "luxon": { 33 | "version": "3.5.0", 34 | "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.5.0.tgz", 35 | "integrity": "sha512-rh+Zjr6DNfUYR3bPwJEnuwDdqMbxZW7LOQfUN4B54+Cl+0o5zaU9RJ6bcidfDtC1cWCZXQ+nvX8bf6bAji37QQ==" 36 | }, 37 | "qrious": { 38 | "version": "4.0.2", 39 | "resolved": "https://registry.npmjs.org/qrious/-/qrious-4.0.2.tgz", 40 | "integrity": "sha512-xWPJIrK1zu5Ypn898fBp8RHkT/9ibquV2Kv24S/JY9VYEhMBMKur1gHVsOiNUh7PHP9uCgejjpZUHUIXXKoU/g==" 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mmm-newsapi", 3 | "version": "1.1.4", 4 | "description": "News from NewsApi.org", 5 | "main": "MMM-NewsAPI.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mumblebaj/MMM-NewsAPI.git" 12 | }, 13 | "keywords": [ 14 | "news" 15 | ], 16 | "author": "Mumblebaj", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/mumblebaj/MMM-NewsAPI/issues" 20 | }, 21 | "homepage": "https://github.com/mumblebaj/MMM-NewsAPI#readme", 22 | "dependencies": { 23 | "luxon": "^3.5.0", 24 | "qrious": "^4.0.2" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mumblebaj/MMM-NewsAPI/f1342b705894e5888f1825f3582e6c832dc902b4/screenshot1.png -------------------------------------------------------------------------------- /template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
%TITLE%
5 |
6 |
7 | %ARTICLEIMAGE% 8 | %DESCRIPTION% 9 |
10 | 15 |
16 | 17 | --------------------------------------------------------------------------------