worldstate-emitter 1.0.8 → 2.0.1

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/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ lts/iron
package/README.md CHANGED
@@ -2,7 +2,9 @@
2
2
 
3
3
  Suuuper simple emitter for worldstate events.
4
4
 
5
- Very opinionated decisions on what events and event names, as well as
5
+ Very opinionated decisions on what events and event names, as well as.... everything else
6
+
7
+ [![semantic-release: angular](https://img.shields.io/badge/semantic--release-angular-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
6
8
 
7
9
  ## Emitter Events
8
10
 
@@ -91,5 +93,5 @@ Method | Params | Output
91
93
  </details>
92
94
  <br />
93
95
 
94
- Help & Contact
96
+ ## Help & Contact
95
97
  [![Discord](https://img.shields.io/discord/256087517353213954.svg?logo=discord)](https://discord.gg/jGZxH9f)
package/handlers/RSS.js CHANGED
@@ -1,14 +1,12 @@
1
- 'use strict';
1
+ import RssFeedEmitter from 'rss-feed-emitter';
2
+ import feeds from '../resources/rssFeeds.json' assert { type: 'json' };
2
3
 
3
- const RssFeedEmitter = require('rss-feed-emitter');
4
- const feeds = require('../resources/rssFeeds.json');
5
-
6
- const { logger } = require('../utilities');
4
+ import { logger } from '../utilities/index.js';
7
5
 
8
6
  /**
9
7
  * RSS Emitter, leverages [rss-feed-emitter](https://npmjs.org/package/rss-feed-emitter)
10
8
  */
11
- class RSS {
9
+ export default class RSS {
12
10
  /**
13
11
  * Set up emitting events for warframe forum entries
14
12
  * @param {EventEmitter} eventEmitter Emitter to send events from
@@ -66,5 +64,3 @@ class RSS {
66
64
  }
67
65
  }
68
66
  }
69
-
70
- module.exports = RSS;
@@ -1,21 +1,19 @@
1
- 'use strict';
2
-
3
- const Twitter = require('twitter');
4
- const toWatch = require('../resources/tweeters.json');
5
-
6
- const { logger } = require('../utilities');
1
+ import Twitter from 'twitter';
2
+ import toWatch from '../resources/tweeters.json' assert { type: 'json' };
3
+ import { logger } from '../utilities/index.js';
4
+ import { twiClientInfo, TWITTER_TIMEOUT } from '../utilities/env.js';
7
5
 
8
6
  const determineTweetType = (tweet) => {
9
7
  if (tweet.in_reply_to_status_id) {
10
- return ('reply');
8
+ return 'reply';
11
9
  }
12
10
  if (tweet.quoted_status_id) {
13
- return ('quote');
11
+ return 'quote';
14
12
  }
15
13
  if (tweet.retweeted_status) {
16
- return ('retweet');
14
+ return 'retweet';
17
15
  }
18
- return ('tweet');
16
+ return 'tweet';
19
17
  };
20
18
 
21
19
  const parseAuthor = (tweet) => ({
@@ -25,15 +23,16 @@ const parseAuthor = (tweet) => ({
25
23
  avatar: `${tweet.user.profile_image_url.replace('_normal.jpg', '.jpg')}`,
26
24
  });
27
25
 
28
- const parseQuoted = (tweet, type) => (tweet[type]
29
- ? {
30
- text: tweet[type].full_text,
31
- author: {
32
- name: tweet[type].user.name,
33
- handle: tweet[type].user.screen_name,
34
- },
35
- }
36
- : undefined);
26
+ const parseQuoted = (tweet, type) =>
27
+ tweet[type]
28
+ ? {
29
+ text: tweet[type].full_text,
30
+ author: {
31
+ name: tweet[type].user.name,
32
+ handle: tweet[type].user.screen_name,
33
+ },
34
+ }
35
+ : undefined;
37
36
 
38
37
  const parseTweet = (tweets, watchable) => {
39
38
  const [tweet] = tweets;
@@ -55,27 +54,16 @@ const parseTweet = (tweets, watchable) => {
55
54
  /**
56
55
  * Twitter event handler
57
56
  */
58
- class TwitterCache {
57
+ export default class TwitterCache {
59
58
  /**
60
59
  * Create a new Twitter self-updating cache
61
60
  * @param {EventEmitter} eventEmitter emitter to push new tweets to
62
61
  */
63
62
  constructor(eventEmitter) {
64
63
  this.emitter = eventEmitter;
65
- this.timeout = process.env.TWITTER_TIMEOUT || 60000;
66
- this.initTime = Date.now();
67
-
68
- const clientInfo = {
69
- consumer_key: process.env.TWITTER_KEY,
70
- consumer_secret: process.env.TWITTER_SECRET,
71
- bearer_token: process.env.TWITTER_BEARER_TOKEN,
72
- };
73
-
74
- this.clientInfoValid = clientInfo.consumer_key
75
- && clientInfo.consumer_secret
76
- && clientInfo.bearer_token;
77
-
78
- this.initClient(clientInfo);
64
+ this.timeout = TWITTER_TIMEOUT;
65
+ this.clientInfoValid = twiClientInfo.consumer_key && twiClientInfo.consumer_secret && twiClientInfo.bearer_token;
66
+ this.initClient(twiClientInfo);
79
67
  }
80
68
 
81
69
  initClient(clientInfo) {
@@ -85,7 +73,7 @@ class TwitterCache {
85
73
 
86
74
  // don't attempt anything else if authentication fails
87
75
  this.toWatch = toWatch;
88
- this.currentData = null;
76
+ this.currentData = undefined;
89
77
  this.lastUpdated = Date.now() - 60000;
90
78
  this.updateInterval = setInterval(() => this.update(), this.timeout);
91
79
  this.update();
@@ -129,19 +117,21 @@ class TwitterCache {
129
117
  logger.silly('Starting Twitter update...');
130
118
  const parsedData = [];
131
119
  try {
132
- for (const watchable of this.toWatch) {
133
- const tweets = await this.client.get('statuses/user_timeline', {
134
- screen_name: watchable.acc_name,
135
- tweet_mode: 'extended',
136
- count: 1,
137
- });
138
- const tweet = parseTweet(tweets, watchable);
139
- parsedData.push(tweet);
140
-
141
- if (tweet.createdAt.getTime() > this.lastUpdated) {
142
- this.emitter.emit('tweet', tweet);
143
- }
144
- }
120
+ await Promise.all(
121
+ this.toWatch.map(async (watchable) => {
122
+ const tweets = await this.client.get('statuses/user_timeline', {
123
+ screen_name: watchable.acc_name,
124
+ tweet_mode: 'extended',
125
+ count: 1,
126
+ });
127
+ const tweet = parseTweet(tweets, watchable);
128
+ parsedData.push(tweet);
129
+
130
+ if (tweet.createdAt.getTime() > this.lastUpdated) {
131
+ this.emitter.emit('tweet', tweet);
132
+ }
133
+ })
134
+ );
145
135
  } catch (error) {
146
136
  this.onError(error);
147
137
  }
@@ -176,5 +166,3 @@ class TwitterCache {
176
166
  return this.currentData;
177
167
  }
178
168
  }
179
-
180
- module.exports = TwitterCache;
@@ -1,86 +1,77 @@
1
- 'use strict';
1
+ import wsData from 'warframe-worldstate-data';
2
+ import WSCache from '../utilities/WSCache.js';
3
+ import { logger, lastUpdated } from '../utilities/index.js';
4
+ import parseNew from './events/parse.js';
5
+ import Cache from '../utilities/Cache.js';
2
6
 
3
- const Cache = require('json-fetch-cache');
4
- const { locales } = require('warframe-worldstate-data');
5
-
6
- const WSCache = require('../utilities/WSCache');
7
-
8
- const { logger, lastUpdated } = require('../utilities');
9
-
10
- const parseNew = require('./events/parse');
11
-
12
- const wsTimeout = process.env.CACHE_TIMEOUT || 60000;
13
- const platforms = ['pc', 'ps4', 'xb1', 'swi'];
14
- const worldStates = {};
15
- const wsRawCaches = {};
7
+ const { locales } = wsData;
16
8
 
17
9
  const debugEvents = ['arbitration', 'kuva', 'nightwave'];
18
- const smTimeout = process.env.SEMLAR_TIMEOUT || 300000;
19
- const kuvaCache = new Cache('https://10o.io/arbitrations.json', smTimeout, { logger, maxRetry: 0 });
20
- const sentientCache = new Cache('https://semlar.com/anomaly.json', smTimeout, { logger });
10
+ const smCron = `0 */10 * * * *`;
21
11
 
22
12
  /**
23
13
  * Handler for worldstate data
24
14
  */
25
- class Worldstate {
15
+ export default class Worldstate {
16
+ #emitter;
17
+ #locale;
18
+ #worldStates = {};
19
+ #wsRawCache;
20
+ #kuvaCache;
21
+ #sentientCache;
22
+
26
23
  /**
27
24
  * Set up listening for specific platform and locale if provided.
28
25
  * @param {EventEmitter} eventEmitter Emitter to push new worldstate events to
29
- * @param {string} platform Platform to watch (optional)
30
26
  * @param {string} locale Locale (actually just language) to watch
31
27
  */
32
- constructor(eventEmitter, platform, locale) {
33
- this.emitter = eventEmitter;
34
- this.platform = platform;
35
- this.locale = locale;
28
+ constructor(eventEmitter, locale) {
29
+ this.#emitter = eventEmitter;
30
+ this.#locale = locale;
36
31
  logger.silly('starting up worldstate listener...');
37
- if (platform) {
38
- logger.debug(`only listening for ${platform}...`);
39
- }
40
32
  if (locale) {
41
33
  logger.debug(`only listening for ${locale}...`);
42
34
  }
35
+ }
43
36
 
44
- this.setUpRawEmitters();
37
+ async init() {
38
+ this.#wsRawCache = await Cache.make('https://content.warframe.com/dynamic/worldState.php', '*/10 * * * * *');
39
+ this.#kuvaCache = await Cache.make('https://10o.io/arbitrations.json', smCron);
40
+ this.#sentientCache = await Cache.make('https://semlar.com/anomaly.json', smCron);
41
+
42
+ await this.setUpRawEmitters();
45
43
  this.setupParsedEvents();
46
44
  }
47
45
 
48
46
  /**
49
47
  * Set up emitting raw worldstate data
50
48
  */
51
- setUpRawEmitters() {
52
- platforms.forEach((p) => {
53
- if (this.platform && this.platform !== p) return;
54
-
55
- const url = `https://content${p === 'pc' ? '' : `.${p}`}.warframe.com/dynamic/worldState.php`;
56
- worldStates[p] = {};
57
-
58
- locales.forEach(async (locale) => {
59
- if (!this.locale || this.locale === locale) {
60
- worldStates[p][locale] = new WSCache({
61
- platform: p, language: locale, kuvaCache, sentientCache, eventEmitter: this.emitter,
62
- });
63
- }
64
- });
65
-
66
- wsRawCaches[p] = new Cache(url, wsTimeout, {
67
- delayStart: false,
68
- parser: (str) => str,
69
- useEmitter: true,
70
- logger,
71
- });
49
+ async setUpRawEmitters() {
50
+ this.#worldStates = {};
51
+
52
+ // eslint-disable-next-line no-restricted-syntax
53
+ for await (const locale of locales) {
54
+ if (!this.#locale || this.#locale === locale) {
55
+ this.#worldStates[locale] = new WSCache({
56
+ language: locale,
57
+ kuvaCache: this.#kuvaCache,
58
+ sentientCache: this.#sentientCache,
59
+ eventEmitter: this.#emitter,
60
+ });
61
+ }
62
+ }
72
63
 
73
- /* listen for the raw cache updates so we can emit them from the super emitter */
74
- wsRawCaches[p].on('update', (dataStr) => {
75
- this.emitter.emit('ws:update:raw', { platform: p, data: dataStr });
76
- });
64
+ /* listen for the raw cache updates so we can emit them from the super emitter */
65
+ this.#wsRawCache.on('update', (dataStr) => {
66
+ this.#emitter.emit('ws:update:raw', { platform: 'pc', data: dataStr });
77
67
  });
78
68
 
79
69
  /* when the raw emits happen, parse them and store them on parsed worldstate caches */
80
- this.emitter.on('ws:update:raw', ({ platform, data }) => {
70
+ this.#emitter.on('ws:update:raw', ({ data }) => {
71
+ logger.debug('ws:update:raw - updating locales data');
81
72
  locales.forEach((locale) => {
82
- if (!this.locale || this.locale === locale) {
83
- worldStates[platform][locale].data = data;
73
+ if (!this.#locale || this.#locale === locale) {
74
+ this.#worldStates[locale].data = data;
84
75
  }
85
76
  });
86
77
  });
@@ -90,9 +81,9 @@ class Worldstate {
90
81
  * Set up listeners for the parsed worldstate updates
91
82
  */
92
83
  setupParsedEvents() {
93
- this.emitter.on('ws:update:parsed', ({ language, platform, data }) => {
84
+ this.#emitter.on('ws:update:parsed', ({ language, platform, data }) => {
94
85
  const packet = { platform, worldstate: data, language };
95
- this.parseEvents(packet, this.emitter);
86
+ this.parseEvents(packet);
96
87
  });
97
88
  }
98
89
 
@@ -100,7 +91,7 @@ class Worldstate {
100
91
  * Parse new worldstate events
101
92
  * @param {Object} worldstate worldstate to find packets from
102
93
  * @param {string} platform platform the worldstate corresponds to
103
- * @param {string} [language='en'] langauge of the worldstate (defaults to 'en')
94
+ * @param {string} [language='en'] language of the worldstate (defaults to 'en')
104
95
  */
105
96
  parseEvents({ worldstate, platform, language = 'en' }) {
106
97
  const cycleStart = Date.now();
@@ -108,12 +99,16 @@ class Worldstate {
108
99
  Object.keys(worldstate).forEach(async (key) => {
109
100
  if (worldstate && worldstate[key]) {
110
101
  const packet = parseNew({
111
- data: worldstate[key], key, language, platform, cycleStart,
102
+ data: worldstate[key],
103
+ key,
104
+ language,
105
+ platform,
106
+ cycleStart,
112
107
  });
113
108
 
114
109
  if (Array.isArray(packet)) {
115
110
  if (packet.length) {
116
- packets.push(...(packet.filter((p) => p && p !== null)));
111
+ packets.push(...packet.filter((p) => p && p));
117
112
  }
118
113
  } else if (packet) {
119
114
  packets.push(packet);
@@ -123,7 +118,7 @@ class Worldstate {
123
118
 
124
119
  lastUpdated[platform][language] = Date.now();
125
120
  packets
126
- .filter((p) => p && p.id && packets !== null)
121
+ .filter((p) => p && p.id && packets)
127
122
  .forEach((packet) => {
128
123
  this.emit('ws:update:event', packet);
129
124
  });
@@ -139,23 +134,20 @@ class Worldstate {
139
134
 
140
135
  logger.silly(`ws:update:event - emitting ${packet.id}`);
141
136
  delete packet.cycleStart;
142
- this.emitter.emit(id, packet);
137
+ this.#emitter.emit(id, packet);
143
138
  }
144
139
 
145
140
  /**
146
141
  * get a specific worldstate version
147
- * @param {string} [platform='pc'] Platform of the worldstate
148
- * @param {string} [locale='en'] Locale of the worldsttate
149
- * @returns {Object} Worldstate corresponding to provided data
142
+ * @param {string} [language='en'] Locale of the worldsttate
143
+ * @returns {Object} Worldstate corresponding to provided data
150
144
  * @throws {Error} when the platform or locale aren't tracked and aren't updated
151
145
  */
152
- // eslint-disable-next-line class-methods-use-this
153
- get(platform = 'pc', language = 'en') {
154
- if (worldStates[platform] && worldStates[platform][language]) {
155
- return worldStates[platform][language].data;
146
+ get(language = 'en') {
147
+ logger.warn(`getting worldstate ${language}...`);
148
+ if (this.#worldStates?.[language]) {
149
+ return this.#worldStates?.[language]?.data;
156
150
  }
157
- throw new Error(`Platform (${platform}) or language (${language}) not tracked.\nEnsure that the parameters passed are correct`);
151
+ throw new Error(`Language (${language}) not tracked.\nEnsure that the parameters passed are correct`);
158
152
  }
159
153
  }
160
-
161
- module.exports = Worldstate;
@@ -1,6 +1,6 @@
1
- 'use strict';
2
-
3
- const checkOverrides = require('./checkOverrides');
1
+ import checkOverrides from './checkOverrides.js';
2
+ import objectLike from './objectLike.js';
3
+ import { logger } from '../../utilities/index.js';
4
4
 
5
5
  /**
6
6
  * arrayLike are all just arrays of objectLike
@@ -8,13 +8,19 @@ const checkOverrides = require('./checkOverrides');
8
8
  * @param {Object[]} packets packets to emit
9
9
  * @returns {Object|Object[]} object(s) to emit from arrayLike processing
10
10
  */
11
- module.exports = (deps, packets) => {
12
- deps.data.forEach((arrayItem) => {
13
- const k = checkOverrides(deps.key, arrayItem);
14
- packets.push(require('./objectLike')(arrayItem, {
15
- ...deps,
16
- id: k,
17
- }));
18
- });
19
- return packets;
11
+ export default (deps, packets) => {
12
+ try {
13
+ deps.data.forEach((arrayItem) => {
14
+ const k = checkOverrides(deps.key, arrayItem);
15
+ packets.push(
16
+ objectLike(arrayItem, {
17
+ ...deps,
18
+ id: k,
19
+ })
20
+ );
21
+ });
22
+ return packets;
23
+ } catch (err) {
24
+ logger.error(err);
25
+ }
20
26
  };
@@ -1,6 +1,4 @@
1
- 'use strict';
2
-
3
- const eKeyOverrides = require('./eKeyOverrides');
1
+ import * as eKeyOverrides from './eKeyOverrides.js';
4
2
 
5
3
  /**
6
4
  * Find overrides for the provided key
@@ -8,7 +6,7 @@ const eKeyOverrides = require('./eKeyOverrides');
8
6
  * @param {Object} data data corresponding to the key from provided worldstate
9
7
  * @returns {string} overrided key
10
8
  */
11
- module.exports = (key, data) => {
9
+ export default (key, data) => {
12
10
  if (typeof eKeyOverrides[key] === 'string') {
13
11
  return eKeyOverrides[key];
14
12
  }
@@ -1,8 +1,19 @@
1
- 'use strict';
1
+ import { between, lastUpdated, fromNow } from '../../utilities/index.js';
2
2
 
3
- const { between, lastUpdated, fromNow } = require('../../utilities');
3
+ /**
4
+ * @typedef {Object} CycleLike
5
+ * @property {string} state
6
+ * @property {string} key
7
+ * @property {Date} activation
8
+ */
4
9
 
5
- module.exports = (cycleData, deps) => {
10
+ /**
11
+ * CylceData parser
12
+ * @param {CycleLike} cycleData data for parsing all cycles like this
13
+ * @param {Deps} deps dependencies for processing
14
+ * @returns {Object[]}
15
+ */
16
+ export default (cycleData, deps) => {
6
17
  const packet = {
7
18
  ...deps,
8
19
  data: cycleData,
@@ -1,28 +1,40 @@
1
- 'use strict';
1
+ import { logger } from '../../utilities/index.js';
2
2
 
3
- const { logger } = require('../../utilities');
3
+ /**
4
+ * External mission data retrieved from https://10o.io/kuvalog.json
5
+ * @typedef {Object} ExternalMission
6
+ * @property {Date} activation start time
7
+ * @property {Date} expiry end timer
8
+ * @property {string} node formatted node name with planet
9
+ * @property {string} enemy Enemy on tile
10
+ * @property {string} type Mission type of node
11
+ * @property {boolean} archwing whether or not the tile requires archwing
12
+ * @property {boolean} sharkwing whether or not the tile requires
13
+ * sumbersible archwing
14
+ */
4
15
 
5
- const fissureKey = (fissure) => `fissures.t${fissure.tierNum}.${(fissure.missionType || '').toLowerCase()}`;
6
- const acolyteKey = (acolyte) => ({
16
+ export const fissures = (fissure) => `fissures.t${fissure.tierNum}.${(fissure.missionType || '').toLowerCase()}`;
17
+ export const enemies = (acolyte) => ({
7
18
  eventKey: `enemies${acolyte.isDiscovered ? '' : '.departed'}`,
8
19
  activation: acolyte.lastDiscoveredAt,
9
20
  });
10
- const arbiKey = (arbitration) => {
11
- if (!(arbitration && arbitration.enemy)) return '';
21
+
22
+ /**
23
+ * Parse an arbitration for its key
24
+ * @param {ExternalMission} arbi arbitration data to parse
25
+ * @returns {string}
26
+ */
27
+ export const arbitration = (arbi) => {
28
+ if (!arbi?.enemy) return '';
12
29
 
13
30
  let k;
14
31
  try {
15
- k = `arbitration.${arbitration.enemy.toLowerCase()}.${arbitration.type.replace(/\s/g, '').toLowerCase()}`;
32
+ k = `arbitration.${arbi.enemy.toLowerCase()}.${arbi.type.replace(/\s/g, '').toLowerCase()}`;
16
33
  } catch (e) {
17
- logger.error(`Unable to parse arbitraion: ${JSON.stringify(arbitration)}\n${e}`);
34
+ logger.error(`Unable to parse arbitraion: ${JSON.stringify(arbi)}\n${e}`);
18
35
  }
19
36
  return k;
20
37
  };
21
38
 
22
- module.exports = {
23
- events: 'operations',
24
- persistentEnemies: 'enemies',
25
- fissures: fissureKey,
26
- enemies: acolyteKey,
27
- arbitration: arbiKey,
28
- };
39
+ export const events = 'operations';
40
+ export const persistentEnemies = 'enemies';
@@ -1,14 +1,13 @@
1
- 'use strict';
2
-
3
- const { logger, groupBy } = require('../../utilities');
1
+ import { logger, groupBy } from '../../utilities/index.js';
2
+ import objectLike from './objectLike.js';
4
3
 
5
4
  /**
6
5
  * Process kuva fields
7
- * @param {Object} deps dependencies for processing
6
+ * @param {Deps} deps dependencies for processing
8
7
  * @param {Object[]} packets packets to emit
9
8
  * @returns {Object|Object[]} object(s) to emit from kuva stuff
10
9
  */
11
- module.exports = (deps, packets) => {
10
+ export default (deps, packets) => {
12
11
  if (!deps.data) {
13
12
  logger.error('no kuva data');
14
13
  return undefined;
@@ -22,7 +21,7 @@ module.exports = (deps, packets) => {
22
21
  activation: data[type][0].activation,
23
22
  expiry: data[type][0].expiry,
24
23
  };
25
- const p = require('./objectLike')(deps.data, deps);
24
+ const p = objectLike(deps.data, deps);
26
25
  if (p) {
27
26
  packets.push(p);
28
27
  }
@@ -1,6 +1,6 @@
1
- 'use strict';
1
+ import objectLike from './objectLike.js';
2
2
 
3
- module.exports = (nightwave, deps) => {
3
+ export default (nightwave, deps) => {
4
4
  const groups = {
5
5
  daily: [],
6
6
  weekly: [],
@@ -19,13 +19,16 @@ module.exports = (nightwave, deps) => {
19
19
 
20
20
  const packets = [];
21
21
  Object.keys(groups).forEach((group) => {
22
- const p = require('./objectLike')({
23
- ...nightwave,
24
- activeChallenges: groups[group],
25
- }, {
26
- ...deps,
27
- id: `nightwave.${group}`,
28
- });
22
+ const p = objectLike(
23
+ {
24
+ ...nightwave,
25
+ activeChallenges: groups[group],
26
+ },
27
+ {
28
+ ...deps,
29
+ id: `nightwave.${group}`,
30
+ }
31
+ );
29
32
  packets.push(p);
30
33
  });
31
34
  return packets;
@@ -1,8 +1,6 @@
1
- 'use strict';
1
+ import { between, lastUpdated } from '../../utilities/index.js';
2
2
 
3
- const { between, lastUpdated } = require('../../utilities');
4
-
5
- module.exports = (data, deps) => {
3
+ export default (data, deps) => {
6
4
  if (!data) return undefined;
7
5
  const last = new Date(lastUpdated[deps.platform][deps.language]);
8
6
  const activation = new Date(data.activation);
@@ -1,15 +1,24 @@
1
- 'use strict';
1
+ import checkOverrides from './checkOverrides.js';
2
+ import kuvaProcessing from './kuva.js';
3
+ import arrayLike from './arrayLike.js';
4
+ import objectLike from './objectLike.js';
5
+ import nightwave from './nightwave.js';
6
+ import cycleLike from './cycleLike.js';
7
+ import * as eKeyOverrides from './eKeyOverrides.js';
8
+ import { lastUpdated } from '../../utilities/index.js';
2
9
 
3
- const checkOverrides = require('./checkOverrides');
4
- const kuvaProcessing = require('./kuva');
5
- const arrayLike = require('./arrayLike');
6
- const eKeyOverrides = require('./eKeyOverrides');
7
-
8
- const { lastUpdated } = require('../../utilities');
10
+ /**
11
+ * @typedef {Object} Deps
12
+ * @property {string} key event key being parsed
13
+ * @property {string} platform platform the event is on
14
+ * @property {string} language language the event is in
15
+ * @property {Date} cycleStart start of the current cycle
16
+ * @property {Object|Array} data data to parse
17
+ */
9
18
 
10
19
  /**
11
- * Set up current cycle start if it's not been intiated
12
- * @param {Object} deps dependencies for processing
20
+ * Set up current cycle start if it's not been initiated
21
+ * @param {Deps} deps dependencies for processing
13
22
  */
14
23
  const initCycleStart = (deps) => {
15
24
  if (!lastUpdated[deps.platform][deps.language]) {
@@ -19,10 +28,10 @@ const initCycleStart = (deps) => {
19
28
 
20
29
  /**
21
30
  * Parse new events from the provided worldstate
22
- * @param {Object} deps dependencies to parse out events
31
+ * @param {Deps} deps dependencies to parse out events
23
32
  * @returns {Packet|Packet[]} packet(s) to emit
24
33
  */
25
- module.exports = (deps) => {
34
+ export default (deps) => {
26
35
  initCycleStart(deps);
27
36
 
28
37
  // anything in the eKeyOverrides goes first, then anything uniform
@@ -49,7 +58,7 @@ module.exports = (deps) => {
49
58
  case 'cetusCycle':
50
59
  case 'earthCycle':
51
60
  case 'vallisCycle':
52
- packets.push(require('./cycleLike')(deps.data, deps));
61
+ packets.push(cycleLike(deps.data, deps));
53
62
  break;
54
63
  case 'persistentEnemies':
55
64
  deps = {
@@ -61,9 +70,9 @@ module.exports = (deps) => {
61
70
  case 'arbitration':
62
71
  case 'sentientOutposts':
63
72
  deps.id = checkOverrides(deps.key, deps.data);
64
- packets.push(require('./objectLike')(deps.data, deps));
73
+ packets.push(objectLike(deps.data, deps));
65
74
  case 'nightwave':
66
- packets.push(require('./nightwave')(deps.data, deps));
75
+ packets.push(nightwave(deps.data, deps));
67
76
  default:
68
77
  break;
69
78
  }
package/index.js CHANGED
@@ -1,27 +1,38 @@
1
- 'use strict';
1
+ import EventEmitter from 'node:events';
2
2
 
3
- const EventEmitter = require('events');
3
+ import RSS from './handlers/RSS.js';
4
+ import Worldstate from './handlers/Worldstate.js';
5
+ import Twitter from './handlers/Twitter.js';
4
6
 
5
- const RSS = require('./handlers/RSS');
6
- const Worldstate = require('./handlers/Worldstate');
7
- const Twitter = require('./handlers/Twitter');
7
+ import { logger } from './utilities/index.js';
8
8
 
9
- const { logger } = require('./utilities');
9
+ export default class WorldstateEmitter extends EventEmitter {
10
+ #locale;
11
+ #worldstate;
12
+ #twitter;
13
+ #rss;
14
+
15
+ static async make({ locale = 'en' } = { locale: 'en' }) {
16
+ const emitter = new WorldstateEmitter({ locale });
17
+ await emitter.#init();
18
+ return emitter;
19
+ }
10
20
 
11
- class WorldstateEmitter extends EventEmitter {
12
21
  /**
13
22
  * Pull in and instantiate emitters
14
- * @param {string} platform platform to restrict events to
23
+ * @param {string} locale language to restrict events to
15
24
  */
16
- constructor({ platform, locale } = { platform: undefined, locale: undefined }) {
25
+ constructor({ locale } = { locale: undefined }) {
17
26
  super();
18
27
 
19
- this.platform = platform;
20
- this.locale = locale;
28
+ this.#locale = locale;
29
+ }
21
30
 
22
- this.rss = new RSS(this);
23
- this.worldstate = new Worldstate(this, platform, locale);
24
- this.twitter = new Twitter(this);
31
+ async #init() {
32
+ this.#rss = new RSS(this);
33
+ this.#worldstate = new Worldstate(this, this.#locale);
34
+ await this.#worldstate.init();
35
+ this.#twitter = new Twitter(this);
25
36
 
26
37
  logger.silly('hey look, i started up...');
27
38
  this.setupLogging();
@@ -37,7 +48,9 @@ class WorldstateEmitter extends EventEmitter {
37
48
  this.on('rss', (body) => logger.silly(`emitted: ${body.id}`));
38
49
  this.on('ws:update:raw', (body) => logger.silly(`emitted raw: ${body.platform}`));
39
50
  this.on('ws:update:parsed', (body) => logger.silly(`emitted parsed: ${body.platform} in ${body.language}`));
40
- this.on('ws:update:event', (body) => logger.silly(`emitted event: ${body.id} ${body.platform} in ${body.language}`));
51
+ this.on('ws:update:event', (body) =>
52
+ logger.silly(`emitted event: ${body.id} ${body.platform} in ${body.language}`)
53
+ );
41
54
  this.on('tweet', (body) => logger.silly(`emitted: ${body.id}`));
42
55
  }
43
56
 
@@ -46,17 +59,16 @@ class WorldstateEmitter extends EventEmitter {
46
59
  * @returns {Object} [description]
47
60
  */
48
61
  getRss() {
49
- return this.rss.feeder.list.map((i) => ({ url: i.url, items: i.items }));
62
+ return this.#rss.feeder.list.map((i) => ({ url: i.url, items: i.items }));
50
63
  }
51
64
 
52
65
  /**
53
66
  * Get a specific worldstate, defaulting to 'pc' for the platform and 'en' for the language
54
- * @param {string} [platform='pc'] platform to get
55
67
  * @param {string} [language='en'] locale/languate to fetch
56
68
  * @returns {Object} Requested worldstate
57
69
  */
58
- getWorldstate(platform = 'pc', language = 'en') {
59
- return this.worldstate.get(platform, language);
70
+ getWorldstate(language = 'en') {
71
+ return this.#worldstate?.get(language);
60
72
  }
61
73
 
62
74
  /**
@@ -64,8 +76,6 @@ class WorldstateEmitter extends EventEmitter {
64
76
  * @returns {Promise} promised twitter data
65
77
  */
66
78
  async getTwitter() {
67
- return this.twitter.clientInfoValid ? this.twitter.getData() : undefined;
79
+ return this.#twitter?.clientInfoValid ? this.twitter.getData() : undefined;
68
80
  }
69
81
  }
70
-
71
- module.exports = WorldstateEmitter;
package/package.json CHANGED
@@ -1,15 +1,17 @@
1
1
  {
2
2
  "name": "worldstate-emitter",
3
- "version": "1.0.8",
3
+ "version": "2.0.1",
4
4
  "description": "Event emitter for worldstate & other warframe events",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
- "test": "nyc --reporter=lcov mocha test/specs --exit",
8
- "lint": "eslint index.js handlers/**.js utilities/**.js",
9
- "lint:fix": "eslint index.js handlers/**.js utilities/**.js --fix",
10
- "coverage": "npm test && nyc report --reporter=text-lcov | coveralls",
7
+ "test": "c8 mocha",
8
+ "lint": "eslint .",
9
+ "lint:fix": "eslint . --fix",
10
+ "coverage": "npm test && npm run report | coveralls",
11
11
  "build-docs": "jsdoc -c jsdoc-config.json -d docs",
12
- "dev": "nodemon test/tester.js"
12
+ "dev": "nodemon test/tester.js",
13
+ "postinstall": "install-peerdeps @wfcd/eslint-config@^1.3.3 -S",
14
+ "report": "c8 report --reporter=text-lcov"
13
15
  },
14
16
  "directories": {
15
17
  "test": "test/specs"
@@ -29,29 +31,31 @@
29
31
  "bugs": {
30
32
  "url": "https://github.com/wfcd/worldstate-emitter/issues"
31
33
  },
32
- "homepage": "https://github.com/wfcd/worldstate-emitter#readme",
34
+ "homepage": "https://wfcd.github.io/worldstate-emitter/",
33
35
  "dependencies": {
34
- "@sentry/node": "^6.9.0",
35
- "colors": "^1.4.0",
36
- "json-fetch-cache": "^1.2.6",
36
+ "cron": "^3.1.6",
37
37
  "rss-feed-emitter": "^3.2.2",
38
- "twitter": "^1.7.1",
39
- "warframe-worldstate-data": "^1.18.15",
40
- "warframe-worldstate-parser": "^2.19.0",
41
- "winston": "^3.3.3"
38
+ "twitter": "^1.7.1"
42
39
  },
43
40
  "devDependencies": {
41
+ "@wfcd/eslint-config": "^1.3.3",
42
+ "c8": "^9.1.0",
44
43
  "chai": "^4.3.4",
45
44
  "coveralls": "^3.1.0",
46
- "eslint": "^8.2.0",
47
- "eslint-config-airbnb-base": "^15.0.0",
48
- "eslint-plugin-import": "^2.22.1",
49
- "mocha": "^9.0.2",
50
- "nodemon": "^2.0.7",
51
- "nyc": "^15.1.0"
45
+ "install-peerdeps": "^2.0.3",
46
+ "mocha": "^10.0.0",
47
+ "nodemon": "^3.0.3"
48
+ },
49
+ "peerDependencies": {
50
+ "warframe-worldstate-data": "^1.x",
51
+ "warframe-worldstate-parser": "^2.x"
52
+ },
53
+ "optionalDependencies": {
54
+ "colors": "^1.4.0",
55
+ "winston": "^3.3.3"
52
56
  },
53
57
  "engines": {
54
- "node": ">=10.19.0"
58
+ "node": ">= 18.19.0"
55
59
  },
56
60
  "eslintIgnore": [
57
61
  ".github/**",
@@ -60,9 +64,14 @@
60
64
  "types/**"
61
65
  ],
62
66
  "eslintConfig": {
63
- "extends": "airbnb-base",
67
+ "extends": "@wfcd/eslint-config/esm",
68
+ "parser": "@babel/eslint-parser",
64
69
  "parserOptions": {
65
- "sourceType": "script"
70
+ "sourceType": "module",
71
+ "ecmaVersion": 6,
72
+ "ecmaFeatures": {
73
+ "modules": true
74
+ }
66
75
  },
67
76
  "rules": {
68
77
  "valid-jsdoc": [
@@ -85,19 +94,29 @@
85
94
  }
86
95
  ],
87
96
  "no-underscore-dangle": "off",
88
- "strict": [
97
+ "import/no-extraneous-dependencies": [
89
98
  "error",
90
- "safe"
91
- ],
92
- "linebreak-style": "off",
93
- "no-restricted-syntax": "off",
94
- "no-await-in-loop": "off",
95
- "global-require": "off",
96
- "no-fallthrough": "off",
97
- "no-param-reassign": "off",
98
- "no-case-declarations": "off"
99
+ {
100
+ "peerDependencies": true
101
+ }
102
+ ]
99
103
  }
100
104
  },
105
+ "babel": {
106
+ "presets": [
107
+ "@babel/preset-env"
108
+ ],
109
+ "plugins": [
110
+ "@babel/plugin-proposal-class-properties",
111
+ "@babel/plugin-proposal-private-methods"
112
+ ]
113
+ },
114
+ "pre-commit": [
115
+ "lint",
116
+ "test",
117
+ "validate"
118
+ ],
119
+ "prettier": "@wfcd/eslint-config/prettier",
101
120
  "nodemonConfig": {
102
121
  "delay": 5000,
103
122
  "env": {
@@ -105,5 +124,18 @@
105
124
  "CACHE_TIMEOUT": 60000,
106
125
  "SEMLAR_TIMEOUT": 300000
107
126
  }
108
- }
127
+ },
128
+ "mocha": {
129
+ "timeout": 20000,
130
+ "exit": true,
131
+ "spec": "test/specs/**/*.spec.js"
132
+ },
133
+ "c8": {
134
+ "reporter": [
135
+ "lcov",
136
+ "text"
137
+ ],
138
+ "skip-full": true
139
+ },
140
+ "type": "module"
109
141
  }
@@ -24,26 +24,6 @@
24
24
  "key": "forum.updates.switch",
25
25
  "defaultAttach": "https://i.imgur.com/eY1NkzO.png"
26
26
  },
27
- {
28
- "url": "https://forums.warframe.com/forum/2-pc-announcements.xml",
29
- "defaultAttach": "https://i.imgur.com/CNrsc7V.png",
30
- "key": "forum.announcements.pc"
31
- },
32
- {
33
- "url": "https://forums.warframe.com/forum/151-ps4-announcements.xml",
34
- "defaultAttach": "https://i.imgur.com/CNrsc7V.png",
35
- "key": "forum.announcements.ps4"
36
- },
37
- {
38
- "url": "https://forums.warframe.com/forum/252-xbox-one-announcements.xml",
39
- "defaultAttach": "https://i.imgur.com/CNrsc7V.png",
40
- "key": "forum.announcements.xb1"
41
- },
42
- {
43
- "url": "https://forums.warframe.com/forum/1198-nintendo-switch-announcements.xml",
44
- "defaultAttach": "https://i.imgur.com/CNrsc7V.png",
45
- "key": "forum.announcements.switch"
46
- },
47
27
  {
48
28
  "url": "https://forums.warframe.com/forum/170-announcements-events.xml",
49
29
  "key": "forum.news",
@@ -152,15 +132,6 @@
152
132
  "icon_url": "https://content.invisioncic.com/Mwarframe/monthly_2016_05/Pablo.thumb.png.35bb0384ef7b88e807d55ffc31af0896.png"
153
133
  }
154
134
  },
155
- {
156
- "url": "https://forums.warframe.com/discover/1778.xml",
157
- "key": "forum.staff.connor",
158
- "author": {
159
- "name": "[DE]Connor",
160
- "url": "https://forums.warframe.com/profile/710227-deconnor/",
161
- "icon_url": "https://content.invisioncic.com/Mwarframe/monthly_2017_02/nezha_watermarked_120_by_kevin_glint-d9zkt5n.thumb.jpg.c9519505f40a304bece8eb435eb99ddd.jpg"
162
- }
163
- },
164
135
  {
165
136
  "url": "https://forums.warframe.com/discover/1779.xml",
166
137
  "key": "forum.staff.marcus",
@@ -169,23 +140,5 @@
169
140
  "url": "https://forums.warframe.com/profile/3443485-demarcus/",
170
141
  "icon_url": "https://content.invisioncic.com/Mwarframe/monthly_2018_01/5a55278d71caa_MarcusIIPrint.thumb.jpg.16cb689d778112333cffb6fe546b89ec.jpg"
171
142
  }
172
- },
173
- {
174
- "url": "https://forums.warframe.com/discover/1780.xml",
175
- "key": "forum.staff.george",
176
- "author": {
177
- "name": "[DE]George",
178
- "url": "https://forums.warframe.com/profile/138665-degeorge/",
179
- "icon_url": "https://content.invisioncic.com/Mwarframe/monthly_2016_02/George_Forum_Photo.thumb.png.62acd07b2b2e40831eca57a0e230a2b2.png"
180
- }
181
- },
182
- {
183
- "url": "https://forums.warframe.com/discover/1781.xml",
184
- "key": "forum.staff.bear",
185
- "author": {
186
- "name": "[DE]Bear",
187
- "url": "https://forums.warframe.com/profile/1589-debear/",
188
- "icon_url": "https://content.invisioncic.com/Mwarframe/monthly_2018_02/juggerdaddy.thumb.gif.85061b940143c7310801ce00337636ac.gif"
189
- }
190
143
  }
191
144
  ]
@@ -0,0 +1,59 @@
1
+ import { EventEmitter } from 'node:events';
2
+ import { CronJob } from 'cron';
3
+ import { logger } from './index.js';
4
+
5
+ export default class CronCache extends EventEmitter {
6
+ #url = undefined;
7
+ #pattern = '0 */10 * * * *'; // default: every 10 minutes
8
+ #job /** @type {CronJob} */ = undefined;
9
+ /** @type string */ #data = '';
10
+ #updating = undefined;
11
+ #logger = logger;
12
+
13
+ static async make(url, pattern) {
14
+ const cache = new CronCache(url, pattern);
15
+ await cache.#update();
16
+ return cache;
17
+ }
18
+
19
+ constructor(/** @type string */ url, /** @type string */ pattern) {
20
+ super();
21
+ this.#url = url;
22
+ if (pattern) this.#pattern = pattern;
23
+ this.#job = new CronJob(this.#pattern, this.#update.bind(this), undefined, true);
24
+ this.#job.start();
25
+ }
26
+
27
+ async #update() {
28
+ this.#updating = this.#fetch();
29
+ this.#logger.debug(`update starting for ${this.#url}`);
30
+ try {
31
+ this.#data = await this.#updating;
32
+ return this.#updating;
33
+ } finally {
34
+ this.emit('update', this.#data);
35
+ this.#logger.debug(`update done for ${this.#url}`);
36
+ this.#updating = undefined;
37
+ }
38
+ }
39
+
40
+ async #fetch() {
41
+ logger.silly(`fetching... ${this.#url}`);
42
+ const updated = await fetch(this.#url);
43
+ this.#data = await updated.text();
44
+ return this.#data;
45
+ }
46
+
47
+ async get() {
48
+ if (this.#updating) {
49
+ logger.silly('returning in-progress update promise');
50
+ return this.#updating;
51
+ }
52
+ if (!this.#data) {
53
+ logger.silly('returning new update promise');
54
+ return this.#update();
55
+ }
56
+ logger.silly('returning cached data');
57
+ return this.#data;
58
+ }
59
+ }
@@ -1,35 +1,31 @@
1
- 'use strict';
2
-
3
- const Worldstate = require('warframe-worldstate-parser');
1
+ import Worldstate from 'warframe-worldstate-parser';
2
+ import { logger } from './index.js';
4
3
 
5
4
  /**
6
5
  * Warframe WorldState Cache - store and retrieve current worldstate data
7
6
  */
8
- class WSCache {
7
+ export default class WSCache {
8
+ #inner;
9
+ #kuvaCache;
10
+ #sentientCache;
11
+ #logger = logger;
12
+ #emitter;
13
+ /** @type string */ #platform = 'pc';
14
+ /** @type string */ #language;
15
+
9
16
  /**
10
17
  * Set up a cache checking for data and updates to a specific worldstate set
11
- * @param {string} platform Platform to track
12
18
  * @param {string} language Langauge/translation to track
13
- * @param {JSONCache} kuvaCache Cache of kuva data, provided by Semlar
14
- * @param {JSONCache} sentientCache Cache of sentient outpost data, provided by Semlar
15
- * @param {Eventemitter} eventEmitter Emitter to push new worldstate updates to
19
+ * @param {Cache} kuvaCache Cache of kuva data, provided by Semlar
20
+ * @param {Cache} sentientCache Cache of sentient outpost data, provided by Semlar
21
+ * @param {EventEmitter} eventEmitter Emitter to push new worldstate updates to
16
22
  */
17
- constructor({
18
- platform, language, kuvaCache, sentientCache, eventEmitter,
19
- }) {
20
- this.inner = null;
21
- Object.defineProperty(this, 'inner', { enumerable: false, configurable: false });
22
-
23
- this.kuvaCache = kuvaCache;
24
- Object.defineProperty(this, 'kuvaCache', { enumerable: false, configurable: false });
25
-
26
- this.sentientCache = sentientCache;
27
- Object.defineProperty(this, 'sentientCache', { enumerable: false, configurable: false });
28
-
29
- this.platform = platform;
30
- this.language = language;
31
-
32
- this.emitter = eventEmitter;
23
+ constructor({ language, kuvaCache, sentientCache, eventEmitter }) {
24
+ this.#inner = undefined;
25
+ this.#kuvaCache = kuvaCache;
26
+ this.#sentientCache = sentientCache;
27
+ this.#language = language;
28
+ this.#emitter = eventEmitter;
33
29
  }
34
30
 
35
31
  /**
@@ -37,7 +33,7 @@ class WSCache {
37
33
  * @returns {Object} Current worldstate data
38
34
  */
39
35
  get data() {
40
- return this.inner;
36
+ return this.#inner;
41
37
  }
42
38
 
43
39
  /**
@@ -45,21 +41,22 @@ class WSCache {
45
41
  * @param {string} newData New string data to parse
46
42
  */
47
43
  set data(newData) {
44
+ logger.debug(`got new data for ${this.#language}, parsing...`);
48
45
  setTimeout(async () => {
49
46
  const t = new Worldstate(newData, {
50
- locale: this.language,
51
- kuvaData: await this.kuvaCache.getData(),
52
- sentientData: await this.sentientCache.getData(),
47
+ locale: this.#language,
48
+ kuvaData: JSON.parse(await this.#kuvaCache.get()),
49
+ sentientData: JSON.parse(await this.#sentientCache.get()),
53
50
  });
54
51
  if (!t.timestamp) return;
55
52
 
56
- this.inner = t;
57
- this.emitter.emit('ws:update:parsed', {
58
- language: this.language,
59
- platform: this.platform,
60
- data: this.inner,
53
+ this.#inner = t;
54
+ this.#emitter.emit('ws:update:parsed', {
55
+ language: this.#language,
56
+ platform: this.#platform,
57
+ data: t,
61
58
  });
62
- }, 1000);
59
+ }, 0);
63
60
  }
64
61
 
65
62
  /**
@@ -68,8 +65,6 @@ class WSCache {
68
65
  */
69
66
  set twitter(newTwitter) {
70
67
  if (!(newTwitter && newTwitter.length)) return;
71
- this.inner.twitter = newTwitter;
68
+ this.#inner.twitter = newTwitter;
72
69
  }
73
70
  }
74
-
75
- module.exports = WSCache;
@@ -0,0 +1,10 @@
1
+ // eslint-disable-next-line import/prefer-default-export
2
+ export const LOG_LEVEL = process?.env?.LOG_LEVEL || 'error';
3
+
4
+ export const twiClientInfo = {
5
+ consumer_key: process?.env?.TWITTER_KEY,
6
+ consumer_secret: process?.env?.TWITTER_SECRET,
7
+ bearer_token: process?.env?.TWITTER_BEARER_TOKEN,
8
+ };
9
+
10
+ export const TWITTER_TIMEOUT = process?.env?.TWITTER_TIMEOUT || 60000;
@@ -1,24 +1,25 @@
1
- 'use strict';
1
+ import 'colors';
2
2
 
3
- require('colors');
4
- const { transports, createLogger, format } = require('winston');
3
+ import { transports, createLogger, format } from 'winston';
4
+ import { LOG_LEVEL } from './env.js';
5
5
 
6
- const {
7
- combine, label, printf, colorize,
8
- } = format;
6
+ let tempLogger;
7
+ try {
8
+ const { combine, label, printf, colorize } = format;
9
9
 
10
- /* Logger setup */
11
- const transport = new transports.Console({ colorize: true });
12
- const logFormat = printf((info) => `[${info.label}] ${info.level}: ${info.message}`);
13
- const logger = createLogger({
14
- format: combine(
15
- colorize(),
16
- label({ label: 'WS'.brightBlue }),
17
- logFormat,
18
- ),
19
- transports: [transport],
20
- });
21
- logger.level = process.env.LOG_LEVEL || 'error';
10
+ /* Logger setup */
11
+ const transport = new transports.Console({ colorize: true });
12
+ const logFormat = printf((info) => `[${info.label}] ${info.level}: ${info.message}`);
13
+ tempLogger = createLogger({
14
+ format: combine(colorize(), label({ label: 'WS'.brightBlue }), logFormat),
15
+ transports: [transport],
16
+ });
17
+ tempLogger.level = LOG_LEVEL;
18
+ } catch (e) {
19
+ tempLogger = console;
20
+ }
21
+
22
+ export const logger = tempLogger;
22
23
 
23
24
  /**
24
25
  * Group an array by a field value
@@ -26,7 +27,7 @@ logger.level = process.env.LOG_LEVEL || 'error';
26
27
  * @param {string} field field to group by
27
28
  * @returns {Object} [description]
28
29
  */
29
- const groupBy = (array, field) => {
30
+ export const groupBy = (array, field) => {
30
31
  const grouped = {};
31
32
  if (!array) return undefined;
32
33
  array.forEach((item) => {
@@ -47,7 +48,7 @@ const allowedDeviation = 30000;
47
48
  * @param {Date} c The third Date, should be the start time of this update cycle
48
49
  * @returns {boolean} if the event date is between the server start time and the last update time
49
50
  */
50
- const between = (a, b, c = new Date()) => (b + allowedDeviation > a) && (b - allowedDeviation < c);
51
+ export const between = (a, b, c = new Date()) => b + allowedDeviation > a && b - allowedDeviation < c;
51
52
 
52
53
  /**
53
54
  * Returns the number of milliseconds between now and a given date
@@ -55,7 +56,7 @@ const between = (a, b, c = new Date()) => (b + allowedDeviation > a) && (b - all
55
56
  * @param {function} [now] A function that returns the current UNIX time in milliseconds
56
57
  * @returns {number}
57
58
  */
58
- function fromNow(d, now = Date.now) {
59
+ export function fromNow(d, now = Date.now) {
59
60
  return new Date(d).getTime() - now();
60
61
  }
61
62
 
@@ -63,7 +64,7 @@ function fromNow(d, now = Date.now) {
63
64
  * Map of last updated dates/times
64
65
  * @type {Object}
65
66
  */
66
- const lastUpdated = {
67
+ export const lastUpdated = {
67
68
  pc: {
68
69
  en: 0, // Date.now(),
69
70
  },
@@ -77,11 +78,3 @@ const lastUpdated = {
77
78
  en: Date.now(),
78
79
  },
79
80
  };
80
-
81
- module.exports = {
82
- logger,
83
- groupBy,
84
- fromNow,
85
- between,
86
- lastUpdated,
87
- };