├── .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 | [](https://MagicMirror.builders)
6 | [](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 |
13 |
14 | 
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