@sockethub/platform-feeds 3.0.0-alpha.3 → 4.0.0-alpha.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -7
- package/package.json +20 -17
- package/src/index.test.data.ts +299 -0
- package/src/index.test.ts +139 -0
- package/src/index.ts +281 -0
- package/src/schema.ts +22 -0
- package/src/types.ts +53 -0
- package/API.md +0 -197
- package/index.js +0 -333
package/index.js
DELETED
|
@@ -1,333 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* This is a platform for Sockethub implementing Atom/RSS fetching functionality.
|
|
3
|
-
*
|
|
4
|
-
* Developed by Nick Jennings (https://github.com/silverbucket)
|
|
5
|
-
*
|
|
6
|
-
* Sockethub is licensed under the LGPLv3.
|
|
7
|
-
* See the LICENSE file for details.
|
|
8
|
-
*
|
|
9
|
-
* The latest version of this module can be found here:
|
|
10
|
-
* git://github.com/sockethub/sockethub.git
|
|
11
|
-
*
|
|
12
|
-
* For more information about Sockethub visit http://sockethub.org/.
|
|
13
|
-
*
|
|
14
|
-
* This program is distributed in the hope that it will be useful,
|
|
15
|
-
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
16
|
-
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const FeedParser = require('feedparser');
|
|
20
|
-
const request = require('request');
|
|
21
|
-
|
|
22
|
-
const PlatformSchema = {
|
|
23
|
-
"name": "feeds",
|
|
24
|
-
"version": require('./package.json').version,
|
|
25
|
-
"messages": {
|
|
26
|
-
"required": [ "type" ],
|
|
27
|
-
"properties": {
|
|
28
|
-
"type": {
|
|
29
|
-
"type": "string",
|
|
30
|
-
"enum": [ "fetch" ]
|
|
31
|
-
},
|
|
32
|
-
"object": {
|
|
33
|
-
"type": "object",
|
|
34
|
-
"oneOf": [
|
|
35
|
-
{ "$ref": "#/definitions/objectTypes/feed-parameters-date" },
|
|
36
|
-
{ "$ref": "#/definitions/objectTypes/feed-parameters-url" },
|
|
37
|
-
]
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"definitions": {
|
|
41
|
-
"objectTypes": {
|
|
42
|
-
"feed-parameters-date": {
|
|
43
|
-
"additionalProperties": false,
|
|
44
|
-
"required": [ "objectType" ],
|
|
45
|
-
"properties": {
|
|
46
|
-
"objectType": {
|
|
47
|
-
"enum": [ "parameters" ]
|
|
48
|
-
},
|
|
49
|
-
"limit": {
|
|
50
|
-
"type": "number",
|
|
51
|
-
},
|
|
52
|
-
"property": {
|
|
53
|
-
"enum": [ "date" ]
|
|
54
|
-
},
|
|
55
|
-
"after": {
|
|
56
|
-
"type": "date"
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
},
|
|
60
|
-
"feed-parameters-url": {
|
|
61
|
-
"additionalProperties": false,
|
|
62
|
-
"required": [ "objectType" ],
|
|
63
|
-
"properties": {
|
|
64
|
-
"objectType": {
|
|
65
|
-
"enum": [ "parameters" ]
|
|
66
|
-
},
|
|
67
|
-
"limit": {
|
|
68
|
-
"type": "number",
|
|
69
|
-
},
|
|
70
|
-
"property": {
|
|
71
|
-
"enum": [ "url" ]
|
|
72
|
-
},
|
|
73
|
-
"after": {
|
|
74
|
-
"type": "string"
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Class: Feeds
|
|
85
|
-
*
|
|
86
|
-
* Handles all actions related to fetching feeds.
|
|
87
|
-
*
|
|
88
|
-
* Current supported feed types:
|
|
89
|
-
*
|
|
90
|
-
* - RSS (1 & 2)
|
|
91
|
-
*
|
|
92
|
-
* - Atom
|
|
93
|
-
*
|
|
94
|
-
* Uses the `node-feedparser` module as a base tool fetching feeds.
|
|
95
|
-
*
|
|
96
|
-
* https://github.com/danmactough/node-feedparser
|
|
97
|
-
*
|
|
98
|
-
* @constructor
|
|
99
|
-
* @param {object} cfg a unique config object for this instance
|
|
100
|
-
*/
|
|
101
|
-
class Feeds {
|
|
102
|
-
constructor(cfg) {
|
|
103
|
-
cfg = (typeof cfg === 'object') ? cfg : {};
|
|
104
|
-
this.id = cfg.id; // actor
|
|
105
|
-
this.debug = cfg.debug;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
get schema() {
|
|
109
|
-
return PlatformSchema;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
get config() {
|
|
113
|
-
return {
|
|
114
|
-
persist: false,
|
|
115
|
-
requireCredentials: []
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Function: fetch
|
|
121
|
-
*
|
|
122
|
-
* Fetches feeds from specified source. Upon completion it will send back a
|
|
123
|
-
* response to the original request with a complete list of URLs in the feed
|
|
124
|
-
* and total count.
|
|
125
|
-
*
|
|
126
|
-
* @param {object} job Activity streams object containing job data.
|
|
127
|
-
* @param {object} cb
|
|
128
|
-
*
|
|
129
|
-
* @example
|
|
130
|
-
*
|
|
131
|
-
* {
|
|
132
|
-
* context: "feeds",
|
|
133
|
-
* type: "fetch",
|
|
134
|
-
* actor: {
|
|
135
|
-
* id: 'https://dogfeed.com/user/nick@silverbucket',
|
|
136
|
-
* type: "person",
|
|
137
|
-
* name: "nick@silverbucket.net"
|
|
138
|
-
* },
|
|
139
|
-
* target: {
|
|
140
|
-
* id: 'http://blog.example.com/rss',
|
|
141
|
-
* type: "feed"
|
|
142
|
-
* },
|
|
143
|
-
* object: {
|
|
144
|
-
* type: "parameters",
|
|
145
|
-
* limit: 10, // default 10
|
|
146
|
-
* property: 'date'
|
|
147
|
-
* after: 'Tue Nov 26 2013 02:11:59 GMT+0100 (CET)',
|
|
148
|
-
*
|
|
149
|
-
* // ... OR ...
|
|
150
|
-
*
|
|
151
|
-
* property: 'link',
|
|
152
|
-
* after: 'http://www.news.com/articles/man-eats-car',
|
|
153
|
-
* }
|
|
154
|
-
* }
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* // Without any parameters specified, the platform will return most
|
|
158
|
-
* // recent 10 articles fetched from the feed.
|
|
159
|
-
*
|
|
160
|
-
* // Example of the resulting JSON AS Object:
|
|
161
|
-
*
|
|
162
|
-
* {
|
|
163
|
-
* context: 'feeds',
|
|
164
|
-
* type: 'post',
|
|
165
|
-
* actor: {
|
|
166
|
-
* type: 'feed',
|
|
167
|
-
* name: 'Best Feed Inc.',
|
|
168
|
-
* id: 'http://blog.example.com/rss',
|
|
169
|
-
* description: 'Where the best feed comes to be the best',
|
|
170
|
-
* image: {
|
|
171
|
-
* width: '144',
|
|
172
|
-
* height: '144',
|
|
173
|
-
* url: 'http://example.com/images/bestfeed.jpg',
|
|
174
|
-
* }
|
|
175
|
-
* favicon: 'http://example.com/favicon.ico',
|
|
176
|
-
* categories: ['best', 'feed', 'aminals'],
|
|
177
|
-
* language: 'en',
|
|
178
|
-
* author: 'John Doe'
|
|
179
|
-
* },
|
|
180
|
-
* target: {
|
|
181
|
-
* id: 'https://dogfeed.com/user/nick@silverbucket',
|
|
182
|
-
* type: "person",
|
|
183
|
-
* name: "nick@silverbucket.net"
|
|
184
|
-
* },
|
|
185
|
-
* object: {
|
|
186
|
-
* id: "http://example.com/articles/about-stuff"
|
|
187
|
-
* type: 'post',
|
|
188
|
-
* title: 'About stuff...',
|
|
189
|
-
* url: "http://example.com/articles/about-stuff"
|
|
190
|
-
* date: "2013-05-28T12:00:00.000Z",
|
|
191
|
-
* datenum: 1369742400000,
|
|
192
|
-
* brief_html: "Brief synopsis of stuff...",
|
|
193
|
-
* brief_text: "Brief synopsis of stuff...",
|
|
194
|
-
* html: "Once upon a time...",
|
|
195
|
-
* text: "Once upon a time..."
|
|
196
|
-
* media: [
|
|
197
|
-
* {
|
|
198
|
-
* length: '13908973',
|
|
199
|
-
* type: 'audio/mpeg',
|
|
200
|
-
* url: 'http://example.com/media/thing.mpg'
|
|
201
|
-
* }
|
|
202
|
-
* ]
|
|
203
|
-
* tags: ['foo', 'bar']
|
|
204
|
-
* }
|
|
205
|
-
* }
|
|
206
|
-
*
|
|
207
|
-
*/
|
|
208
|
-
fetch(job, cb) {
|
|
209
|
-
// ready to execute job
|
|
210
|
-
this.fetchFeed(job.target.id, job.object)
|
|
211
|
-
.then((results) => {
|
|
212
|
-
// result.target = job.actor;
|
|
213
|
-
return cb(null, results);
|
|
214
|
-
}).catch(cb);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
cleanup(cb) {
|
|
218
|
-
cb();
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
//
|
|
222
|
-
// fetches the articles from a feed, adding them to an array
|
|
223
|
-
// for processing
|
|
224
|
-
fetchFeed(url, options) {
|
|
225
|
-
let articles = [],
|
|
226
|
-
actor; // queue of articles to buffer and filter before sending out.
|
|
227
|
-
let cfg = parseConfig(options);
|
|
228
|
-
this.debug('FEED URL: ' + url);
|
|
229
|
-
return new Promise((resolve, reject) => {
|
|
230
|
-
request(url)
|
|
231
|
-
.on('error', reject)
|
|
232
|
-
.pipe(new FeedParser(cfg))
|
|
233
|
-
.on('error', reject)
|
|
234
|
-
.on('meta', (meta) => {
|
|
235
|
-
this.debug('fetched feed: ' + meta.title);
|
|
236
|
-
actor = buildFeedChannel(url, meta);
|
|
237
|
-
}).on('readable', function() {
|
|
238
|
-
const stream = this;
|
|
239
|
-
let item;
|
|
240
|
-
while (item = stream.read()) {
|
|
241
|
-
let article = buildFeedEntry(actor);
|
|
242
|
-
article.object = buildFeedObject(Date.parse(item.date) || 0, item);
|
|
243
|
-
articles.push(article); // add to articles stack
|
|
244
|
-
}
|
|
245
|
-
}).on('end', () => {
|
|
246
|
-
return resolve(articles);
|
|
247
|
-
});
|
|
248
|
-
});
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function buildFeedObject(dateNum, item) {
|
|
253
|
-
return {
|
|
254
|
-
type: 'feedEntry',
|
|
255
|
-
name: item.title,
|
|
256
|
-
title: item.title,
|
|
257
|
-
date: item.date,
|
|
258
|
-
datenum: dateNum,
|
|
259
|
-
tags: item.categories,
|
|
260
|
-
text: item.summary,
|
|
261
|
-
html: item.summary,
|
|
262
|
-
brief_text: item.description,
|
|
263
|
-
brief_html: item.description,
|
|
264
|
-
url: item.origlink || item.link || item.meta.link,
|
|
265
|
-
id: item.origlink || item.link || item.meta.link + '#' + dateNum,
|
|
266
|
-
media: item.enclosures,
|
|
267
|
-
source: item.source
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function buildFeedEntry(actor) {
|
|
272
|
-
return {
|
|
273
|
-
actor: {
|
|
274
|
-
type: 'feed',
|
|
275
|
-
name: actor.name,
|
|
276
|
-
id: actor.address,
|
|
277
|
-
description: actor.description,
|
|
278
|
-
image: actor.image,
|
|
279
|
-
favicon: actor.favicon,
|
|
280
|
-
categories: actor.categories,
|
|
281
|
-
language: actor.language,
|
|
282
|
-
author: actor.author
|
|
283
|
-
},
|
|
284
|
-
status: true,
|
|
285
|
-
type: "post",
|
|
286
|
-
object: {}
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function buildFeedChannel(url, meta) {
|
|
291
|
-
return {
|
|
292
|
-
type: 'feedChannel',
|
|
293
|
-
name: (meta.title) ? meta.title : (meta.link) ? meta.link : url,
|
|
294
|
-
address: url,
|
|
295
|
-
description: (meta.description) ? meta.description : '',
|
|
296
|
-
image: (meta.image) ? meta.image : {},
|
|
297
|
-
favicon: (meta.favicon) ? meta.favicon : '',
|
|
298
|
-
categories: (meta.categories) ? meta.categories : [],
|
|
299
|
-
language: (meta.language) ? meta.language : '',
|
|
300
|
-
author: (meta.author) ? meta.author : ''
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
function extractDate(prop) {
|
|
305
|
-
let date;
|
|
306
|
-
try {
|
|
307
|
-
date = (typeof prop === 'string') ? Date.parse(prop) : (typeof prop === 'number') ? prop : 0;
|
|
308
|
-
} catch (e) {
|
|
309
|
-
return 'invalid date string passed: ' + prop + ' - ' + e;
|
|
310
|
-
}
|
|
311
|
-
return date;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/*
|
|
315
|
-
* setting defaults and normalizing
|
|
316
|
-
*/
|
|
317
|
-
function parseConfig(options) {
|
|
318
|
-
let cfg = {};
|
|
319
|
-
cfg.limit = (options.limit) ? options.limit : 10;
|
|
320
|
-
cfg.datenum = 0;
|
|
321
|
-
if ((!cfg.property) || (cfg.property === 'date')) {
|
|
322
|
-
cfg.after_datenum = extractDate(options.after);
|
|
323
|
-
cfg.before_datenum = extractDate(options.before);
|
|
324
|
-
}
|
|
325
|
-
cfg.url = (options.url) ? options.url : null;
|
|
326
|
-
cfg.from = 'after';
|
|
327
|
-
if ((options.from) && (options.from === 'before')) {
|
|
328
|
-
cfg.from = 'before';
|
|
329
|
-
}
|
|
330
|
-
return cfg;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
module.exports = Feeds;
|