feedsub 0.7.4 → 0.7.8

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 CHANGED
@@ -73,8 +73,8 @@ Creates a new instance of FeedSub. `options` defaults to.
73
73
  ### FeedSub#read([callback(err, items)])
74
74
  Reads the feed. Calls `callback` with possible error or new items discovered if provided. Causes `reader` to emit new item events.
75
75
 
76
- ### FeedSub#start()
77
- Starts checking the feed for any new items. It checks right away, and then, every `options.interval` minutes.
76
+ ### FeedSub#start(readOnStart)
77
+ Starts checking the feed for any new items every `options.interval` minutes. If `readOnStart` is true, it checks right away.
78
78
 
79
79
  ### FeedSub#options
80
80
  Options that were passed to the constructor along with any defaults are kept here.
@@ -88,7 +88,7 @@ Stops the reader from automatically reading the feed.
88
88
  Emitted whenever there is a new item.
89
89
 
90
90
  ### Event: items
91
- * `Array.Object` - List of items.
91
+ * `Array.<Object>` - List of items.
92
92
 
93
93
  Emits all new items from one request in one array.
94
94
 
@@ -0,0 +1,62 @@
1
+ import { TypedEmitter } from 'tiny-typed-emitter';
2
+ import { FeedItem } from 'feedme';
3
+ import NewsEmitter from 'newsemitter';
4
+ import miniget from 'miniget';
5
+ export interface Options {
6
+ interval?: number;
7
+ forceInterval?: boolean;
8
+ autoStart?: boolean;
9
+ emitOnStart?: boolean;
10
+ lastDate?: null | string;
11
+ history?: string[];
12
+ maxHistory?: number;
13
+ skipHours?: boolean;
14
+ hoursToSkip?: number[];
15
+ skipDays?: boolean;
16
+ daysToSkip?: string[];
17
+ requestOpts?: miniget.Options;
18
+ }
19
+ export declare type DefaultOptions = Required<Options>;
20
+ interface FeedSubEvents {
21
+ 'error': (err: Error) => void;
22
+ 'item': (item: FeedItem) => void;
23
+ 'items': (items: FeedItem[]) => void;
24
+ }
25
+ export { FeedItem } from 'feedme';
26
+ export default class FeedSub extends TypedEmitter<FeedSubEvents> {
27
+ feed: string;
28
+ options: DefaultOptions;
29
+ news: NewsEmitter;
30
+ getOpts: miniget.Options & {
31
+ headers: Record<string, string>;
32
+ };
33
+ private _first;
34
+ private _intervalid;
35
+ /**
36
+ * @constructor
37
+ * @param {string} feed
38
+ * @param {!Object} options
39
+ */
40
+ constructor(feed: string, options?: Options);
41
+ /**
42
+ * Start calling the read function on interval.
43
+ *
44
+ * @param {boolean} readOnStart
45
+ */
46
+ start(readOnStart?: boolean): void;
47
+ /**
48
+ * Stop interval if any
49
+ */
50
+ stop(): void;
51
+ /**
52
+ * Reads feed and determines if there are any new items
53
+ * emits new items through event `item`
54
+ * if there are new items, emits `items` event at end with all items
55
+ *
56
+ * if callback is given, calls callback with all new items
57
+ * even when there are 0.
58
+ *
59
+ * @param {!Function(!Error, Array.<Object>)} callback
60
+ */
61
+ read(callback?: (err: null | Error, items?: FeedItem[]) => void): void;
62
+ }
@@ -0,0 +1,280 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const tiny_typed_emitter_1 = require("tiny-typed-emitter");
7
+ const feedme_1 = __importDefault(require("feedme"));
8
+ const newsemitter_1 = __importDefault(require("newsemitter"));
9
+ const miniget_1 = __importDefault(require("miniget"));
10
+ const zlib_1 = __importDefault(require("zlib"));
11
+ // Used for the skipdays tag.
12
+ const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday',
13
+ 'thursday', 'friday', 'saturday'];
14
+ class FeedSub extends tiny_typed_emitter_1.TypedEmitter {
15
+ /**
16
+ * @constructor
17
+ * @param {string} feed
18
+ * @param {!Object} options
19
+ */
20
+ constructor(feed, options) {
21
+ super();
22
+ this.feed = feed;
23
+ this.options = Object.assign({
24
+ interval: 10,
25
+ forceInterval: false,
26
+ autoStart: false,
27
+ emitOnStart: false,
28
+ lastDate: null,
29
+ history: [],
30
+ maxHistory: 10,
31
+ skipHours: false,
32
+ hoursToSkip: null,
33
+ skipDays: false,
34
+ daysToSkip: null,
35
+ requestOpts: {},
36
+ }, options);
37
+ // Create news emitter.
38
+ this.news = new newsemitter_1.default({
39
+ maxHistory: this.options.maxHistory,
40
+ manageHistory: true,
41
+ identifier: (item) => {
42
+ let feedItem = item[0];
43
+ return [
44
+ feedItem.title,
45
+ feedItem.link,
46
+ feedItem.pubdate,
47
+ feedItem.published,
48
+ feedItem.updated
49
+ ].join(',');
50
+ }
51
+ });
52
+ this.news.addHistory('item', this.options.history);
53
+ this._first = this.news.history.get('item').size === 0;
54
+ this.getOpts = Object.assign({
55
+ headers: {},
56
+ acceptEncoding: {
57
+ gzip: () => zlib_1.default.createGunzip(),
58
+ deflate: () => zlib_1.default.createInflate(),
59
+ },
60
+ }, this.options.requestOpts);
61
+ if (this.options.autoStart) {
62
+ this.start(true);
63
+ }
64
+ }
65
+ /**
66
+ * Start calling the read function on interval.
67
+ *
68
+ * @param {boolean} readOnStart
69
+ */
70
+ start(readOnStart) {
71
+ this.stop();
72
+ let ms = this.options.interval * 60000;
73
+ this._intervalid = setInterval(this.read.bind(this), ms);
74
+ if (readOnStart) {
75
+ this.read();
76
+ }
77
+ }
78
+ /**
79
+ * Stop interval if any
80
+ */
81
+ stop() {
82
+ clearInterval(this._intervalid);
83
+ }
84
+ /**
85
+ * Reads feed and determines if there are any new items
86
+ * emits new items through event `item`
87
+ * if there are new items, emits `items` event at end with all items
88
+ *
89
+ * if callback is given, calls callback with all new items
90
+ * even when there are 0.
91
+ *
92
+ * @param {!Function(!Error, Array.<Object>)} callback
93
+ */
94
+ read(callback) {
95
+ let ended = false;
96
+ let req;
97
+ let items = [];
98
+ let newItems = [];
99
+ let sortOrder = 0;
100
+ const error = (err) => {
101
+ ended = true;
102
+ this._first = false;
103
+ if (typeof callback === 'function') {
104
+ callback(err);
105
+ }
106
+ else {
107
+ this.emit('error', err);
108
+ }
109
+ req.destroy();
110
+ };
111
+ const success = () => {
112
+ if (ended) {
113
+ return;
114
+ }
115
+ ended = true;
116
+ if (sortOrder <= 0) {
117
+ newItems.reverse();
118
+ }
119
+ this.news.addHistory('item', newItems.map((item) => [item]));
120
+ if (this._first && !this.options.emitOnStart) {
121
+ newItems = [];
122
+ }
123
+ newItems.forEach(item => this.emit('item', item));
124
+ this.emit('items', newItems);
125
+ if (req) {
126
+ req.destroy();
127
+ }
128
+ if (typeof callback === 'function') {
129
+ callback(null, newItems);
130
+ }
131
+ };
132
+ // If skipHours or skipDays are enabled and feed provides hours/days
133
+ // to skip and it's currently one of those hours/days, abort.
134
+ const now = new Date();
135
+ const shouldSkip = () => {
136
+ return ((!this._first || !this.options.emitOnStart) &&
137
+ (this.options.hoursToSkip || this.options.daysToSkip)) && ((this.options.hoursToSkip &&
138
+ this.options.hoursToSkip.indexOf(now.getHours()) !== -1) ||
139
+ (this.options.daysToSkip &&
140
+ this.options.daysToSkip.some((day) => {
141
+ return day.toLowerCase() === DAYS[now.getDay()];
142
+ })));
143
+ };
144
+ if (shouldSkip()) {
145
+ return success();
146
+ }
147
+ req = miniget_1.default(this.feed, this.getOpts);
148
+ req.on('response', (res) => {
149
+ // Check if not modified code is sent back
150
+ // in this case, the body will be empty.
151
+ if (res.statusCode === 304) {
152
+ return success();
153
+ }
154
+ // Check headers for conditional get.
155
+ if (res.headers['last-modified']) {
156
+ this.getOpts.headers['If-Modified-Since'] = res.headers['last-modified'];
157
+ }
158
+ if (res.headers.etag) {
159
+ this.getOpts.headers['If-None-Match'] = res.headers.etag;
160
+ }
161
+ // Save date.
162
+ let date;
163
+ let getdate = (text) => date = text;
164
+ // Create feed parser.
165
+ const parser = new feedme_1.default();
166
+ parser.on('error', error);
167
+ // Try to get date from one of the fields.
168
+ parser.once('pubdate', getdate);
169
+ parser.once('lastbuilddate', getdate);
170
+ parser.once('updated', getdate);
171
+ // Change interval time if ttl available.
172
+ if (!this.options.forceInterval) {
173
+ parser.once('ttl', (minutes) => {
174
+ minutes = parseInt(minutes, 10);
175
+ // Only update if ttl is longer than requested interval.
176
+ if (minutes > this.options.interval) {
177
+ this.options.interval = minutes;
178
+ if (this.options.autoStart) {
179
+ this.start(false);
180
+ }
181
+ }
182
+ });
183
+ }
184
+ // Listen for skipHours if enabled.
185
+ if (this.options.skipHours && !this.options.hoursToSkip) {
186
+ parser.once('skiphours', (data) => {
187
+ this.options.hoursToSkip =
188
+ [].concat(data.hour).map(h => parseInt(h, 10));
189
+ });
190
+ }
191
+ // Listen for skipDays if enabled.
192
+ if (this.options.skipDays !== false && !this.options.daysToSkip) {
193
+ parser.once('skipdays', (data) => {
194
+ this.options.daysToSkip = [].concat(data.day);
195
+ });
196
+ }
197
+ // Compare date when first item is encountered.
198
+ const firstitem = (item) => {
199
+ // If date is the same as last, abort.
200
+ if (date && this.options.lastDate === date) {
201
+ return success();
202
+ }
203
+ if (shouldSkip()) {
204
+ return success();
205
+ }
206
+ // Continue if dates differ.
207
+ if (date) {
208
+ this.options.lastDate = date;
209
+ }
210
+ parser.on('item', getItem);
211
+ getItem(item);
212
+ };
213
+ parser.once('item', firstitem);
214
+ const getItemDate = (item) => +new Date(item.pubdate || item.published || 0);
215
+ const getItem = (item) => {
216
+ if (sortOrder === 0) {
217
+ items.push(item);
218
+ sortOrder = getItemDate(item) - getItemDate(items[0]);
219
+ if (sortOrder < 0) {
220
+ items.forEach(getOlderItem);
221
+ }
222
+ else if (sortOrder > 0) {
223
+ items.forEach(getNewerItem);
224
+ }
225
+ }
226
+ else if (sortOrder < 0) {
227
+ getOlderItem(item);
228
+ }
229
+ else {
230
+ getNewerItem(item);
231
+ }
232
+ };
233
+ const getOlderItem = (item) => {
234
+ if (this._first) {
235
+ newItems.push(item);
236
+ }
237
+ else if (!ended) {
238
+ let emitted = this.news.emit('item', item);
239
+ if (emitted) {
240
+ newItems.push(item);
241
+ }
242
+ else {
243
+ // Check if this item has already been read in previous requests
244
+ // if it has, then stop parsing the rest of the document.
245
+ parser.removeListener('item', getItem);
246
+ success();
247
+ }
248
+ }
249
+ };
250
+ let foundPrevItem = false;
251
+ const getNewerItem = (item) => {
252
+ if (this._first) {
253
+ newItems.push(item);
254
+ }
255
+ else if (!foundPrevItem && !this.news.emit('item', item)) {
256
+ foundPrevItem = true;
257
+ }
258
+ else if (foundPrevItem && this.news.emit('item', item)) {
259
+ newItems.push(item);
260
+ }
261
+ };
262
+ req.pipe(parser);
263
+ parser.on('finish', () => {
264
+ if (ended) {
265
+ return;
266
+ }
267
+ // Process items in descending order if no order found at end.
268
+ if (sortOrder === 0 && newItems.length === 0) {
269
+ items.forEach(getOlderItem);
270
+ }
271
+ success();
272
+ this._first = false;
273
+ });
274
+ });
275
+ req.on('error', error);
276
+ }
277
+ }
278
+ exports.default = FeedSub;
279
+ module.exports = FeedSub;
280
+ //# sourceMappingURL=feedsub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feedsub.js","sourceRoot":"","sources":["../src/feedsub.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -8,30 +8,39 @@
8
8
  "json",
9
9
  "subscribe"
10
10
  ],
11
- "version": "0.7.4",
11
+ "version": "0.7.8",
12
12
  "repository": {
13
13
  "type": "git",
14
14
  "url": "git://github.com/fent/node-feedsub.git"
15
15
  },
16
16
  "author": "fent <fentbox@gmail.com> (https://github.com/fent)",
17
- "main": "./lib/feedsub.js",
17
+ "main": "./dist/feedsub.js",
18
18
  "files": [
19
- "lib"
19
+ "dist"
20
20
  ],
21
21
  "scripts": {
22
- "test": "nyc --reporter=lcov --reporter=text-summary mocha -- -t 4000 test/*-test.js"
22
+ "prepare": "tsc -p tsconfig.build.json",
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "test": "nyc --extension .ts --reporter=lcov --reporter=text-summary npm run test:unit",
25
+ "test:unit": "mocha --require ts-node/register test/*-test.ts"
23
26
  },
24
27
  "dependencies": {
25
- "feedme": "^1.2.0",
26
- "miniget": "^3.0.0",
27
- "newsemitter": "^1.0.0"
28
+ "feedme": "^2.0.2",
29
+ "miniget": "^4.2.2",
30
+ "newsemitter": "^1.0.2",
31
+ "tiny-typed-emitter": "^2.0.3"
28
32
  },
29
33
  "devDependencies": {
34
+ "@types/mocha": "^8.2.0",
35
+ "@types/nock": "^11.1.0",
36
+ "@types/node": "^14.14.11",
37
+ "@types/sinon": "^9.0.9",
30
38
  "mocha": "^6.0.0",
31
- "muk-prop": "^1.2.1",
32
39
  "nock": "^11.1.0",
33
40
  "nyc": "^15.1.0",
34
- "sinon": "^8.0.0"
41
+ "sinon": "^8.0.0",
42
+ "ts-node": "^9.1.1",
43
+ "typescript": "^4.1.2"
35
44
  },
36
45
  "engines": {
37
46
  "node": ">=10"
package/lib/feedsub.js DELETED
@@ -1,302 +0,0 @@
1
- const EventEmitter = require('events').EventEmitter;
2
- const FeedMe = require('feedme');
3
- const NewsEmitter = require('newsemitter');
4
- const miniget = require('miniget');
5
- const zlib = require('zlib');
6
-
7
-
8
- // Used for the skipdays tag.
9
- const DAYS = ['sunday', 'monday', 'tuesday', 'wednesday',
10
- 'thursday', 'friday', 'saturday'];
11
-
12
-
13
- module.exports = class FeedReader extends EventEmitter {
14
- /**
15
- * @constructor
16
- * @param {string} feed
17
- * @param {!Object} options
18
- */
19
- constructor(feed, options) {
20
- super();
21
- this.feed = feed;
22
- this.options = Object.assign({
23
- interval : 10,
24
- forceInterval : false,
25
- autoStart : false,
26
- emitOnStart : false,
27
- lastDate : null,
28
- history : [],
29
- maxHistory : 10,
30
- skipHours : false,
31
- skipDays : false,
32
- }, options);
33
-
34
- // Create news emitter.
35
- this.news = new NewsEmitter({
36
- maxHistory: this.options.maxHistory,
37
- manageHistory: true,
38
- identifier: (item) => {
39
- item = item[0];
40
- return [
41
- item.title,
42
- item.link,
43
- item.pubdate,
44
- item.published,
45
- item.updated
46
- ].join(',');
47
- }
48
- });
49
-
50
- this.news.addHistory('item', this.options.history);
51
- this.first = this.news.history.get('item').size === 0;
52
- this.getOpts = Object.assign({
53
- headers: {},
54
- acceptEncoding: {
55
- gzip: () => zlib.createGunzip(),
56
- deflate: () => zlib.createInflate(),
57
- },
58
- }, this.options.requestOpts);
59
- if (this.options.autoStart) {
60
- this.start(true);
61
- }
62
- }
63
-
64
-
65
- /**
66
- * Start calling the read function on interval.
67
- *
68
- * @param {boolean} readOnStart
69
- */
70
- start(readOnStart) {
71
- this.stop();
72
- let ms = this.options.interval * 60000;
73
- this._intervalid = setInterval(this.read.bind(this), ms);
74
- if (readOnStart) {
75
- this.read();
76
- }
77
- }
78
-
79
-
80
- /**
81
- * Stop interval if any
82
- */
83
- stop() {
84
- clearInterval(this._intervalid);
85
- }
86
-
87
-
88
- /**
89
- * Reads feed and determines if there are any new items
90
- * emits new items through event `item`
91
- * if there are new items, emits `items` event at end with all items
92
- *
93
- * if callback is given, calls callback with all new items
94
- * even when there are 0.
95
- *
96
- * @param {!Function(!Error, Array.<Object>)} callback
97
- */
98
- read(callback) {
99
- let ended = false;
100
- let req;
101
- let items = [];
102
- let newItems = [];
103
- let sortOrder = 0;
104
-
105
- const error = (err) => {
106
- ended = true;
107
- this.first = false;
108
- if (typeof callback === 'function') {
109
- callback(err);
110
- } else {
111
- this.emit('error', err);
112
- }
113
- req.destroy();
114
- };
115
-
116
- const success = () => {
117
- if (ended) { return; }
118
- ended = true;
119
- if (sortOrder <= 0) {
120
- newItems.reverse();
121
- }
122
- this.news.addHistory('item', newItems.map((item) => [item]));
123
- if (this.first && !this.options.emitOnStart) {
124
- newItems = [];
125
- }
126
- newItems.forEach(this.emit.bind(this, 'item'));
127
- this.emit('items', newItems);
128
- if (req) {
129
- req.destroy();
130
- }
131
- if (typeof callback === 'function') {
132
- callback(null, newItems);
133
- }
134
- };
135
-
136
- // If skipHours or skipDays are enabled and feed provides hours/days
137
- // to skip and it's currently one of those hours/days, abort.
138
- const now = new Date();
139
- const shouldSkip = () => {
140
- return (
141
- (!this.first || !this.options.emitOnStart) &&
142
- (this.options.hoursToSkip || this.options.daysToSkip)
143
- ) && (
144
- (this.options.hoursToSkip &&
145
- this.options.hoursToSkip.indexOf(now.getHours()) !== -1) ||
146
- (this.options.daysToSkip &&
147
- this.options.daysToSkip.some((day) => {
148
- return day.toLowerCase() === DAYS[now.getDay()];
149
- })
150
- )
151
- );
152
- };
153
-
154
- if (shouldSkip()) {
155
- return success();
156
- }
157
-
158
- req = miniget(this.feed, this.getOpts);
159
- req.on('response', (res) => {
160
- // Check if not modified code is sent back
161
- // in this case, the body will be empty.
162
- if (res.statusCode === 304) {
163
- return success();
164
- }
165
-
166
- // Check headers for conditional get.
167
- if (res.headers['last-modified']) {
168
- this.getOpts.headers['If-Modified-Since'] = res.headers['last-modified'];
169
- }
170
- if (res.headers.etag) {
171
- this.getOpts.headers['If-None-Match'] = res.headers.etag;
172
- }
173
-
174
- // Save date.
175
- let date;
176
- let getdate = text => date = text;
177
-
178
- // Create feed parser.
179
- const parser = new FeedMe();
180
- parser.on('error', error);
181
-
182
- // Try to get date from one of the fields.
183
- parser.once('pubdate', getdate);
184
- parser.once('lastbuilddate', getdate);
185
- parser.once('updated', getdate);
186
-
187
- // Change interval time if ttl available.
188
- if (!this.options.forceInterval) {
189
- parser.once('ttl', (minutes) => {
190
- minutes = parseInt(minutes, 10);
191
-
192
- // Only update if ttl is longer than requested interval.
193
- if (minutes > this.options.interval) {
194
- this.options.interval = minutes;
195
- if (this.options.autoStart) {
196
- this.start(false);
197
- }
198
- }
199
- });
200
- }
201
-
202
- // Listen for skipHours if enabled.
203
- if (this.options.skipHours && !this.options.hoursToSkip) {
204
- parser.once('skiphours', (data) => {
205
- this.options.hoursToSkip =
206
- [].concat(data.hour).map(h => parseInt(h, 10));
207
- });
208
- }
209
-
210
- // Listen for skipDays if enabled.
211
- if (this.options.skipDays !== false && !this.options.daysToSkip) {
212
- parser.once('skipdays', (data) => {
213
- this.options.daysToSkip = [].concat(data.day);
214
- });
215
- }
216
-
217
-
218
- // Compare date when first item is encountered.
219
- const firstitem = (item) => {
220
- // If date is the same as last, abort.
221
- if (date && this.options.lastDate === date) {
222
- return success();
223
- }
224
-
225
- if (shouldSkip()) {
226
- return success();
227
- }
228
-
229
- // Continue if dates differ.
230
- if (date) {
231
- this.options.lastDate = date;
232
- }
233
- parser.on('item', getItem);
234
- getItem(item);
235
- };
236
-
237
- parser.once('item', firstitem);
238
-
239
- const getItemDate = (item) => new Date(item.pubdate || item.published || 0);
240
- const getItem = (item) => {
241
- if (sortOrder === 0) {
242
- items.push(item);
243
- sortOrder = getItemDate(item) - getItemDate(items[0]);
244
- if (sortOrder < 0) {
245
- items.forEach(getOlderItem);
246
- } else if (sortOrder > 0) {
247
- items.forEach(getNewerItem);
248
- }
249
- } else if (sortOrder < 0) {
250
- getOlderItem(item);
251
-
252
- } else {
253
- getNewerItem(item);
254
- }
255
- };
256
-
257
- const getOlderItem = (item) => {
258
- if (this.first) {
259
- newItems.push(item);
260
- } else if (!ended) {
261
- let emitted = this.news.emit('item', item);
262
- if (emitted) {
263
- newItems.push(item);
264
- } else {
265
- // Check if this item has already been read in previous requests
266
- // if it has, then stop parsing the rest of the document.
267
- parser.removeListener('item', getItem);
268
- success();
269
- }
270
- }
271
- };
272
-
273
- let foundPrevItem = false;
274
- const getNewerItem = (item) => {
275
- if (this.first) {
276
- newItems.push(item);
277
- } else if (!foundPrevItem && !this.news.emit('item', item)) {
278
- foundPrevItem = true;
279
- } else if (foundPrevItem && this.news.emit('item', item)) {
280
- newItems.push(item);
281
- }
282
- };
283
-
284
- req.pipe(parser);
285
-
286
- const onEnd = () => {
287
- if (ended) { return; }
288
- // Process items in descending order if no order found at end.
289
- if (sortOrder === 0 && newItems.length === 0) {
290
- items.forEach(getOlderItem);
291
- }
292
- success();
293
- this.first = false;
294
- };
295
-
296
- req.on('close', onEnd);
297
- req.on('end', onEnd);
298
- });
299
-
300
- req.on('error', error);
301
- }
302
- };