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 +1 -0
- package/README.md +4 -2
- package/handlers/RSS.js +4 -8
- package/handlers/Twitter.js +38 -50
- package/handlers/Worldstate.js +64 -72
- package/handlers/events/arrayLike.js +18 -12
- package/handlers/events/checkOverrides.js +2 -4
- package/handlers/events/cycleLike.js +14 -3
- package/handlers/events/eKeyOverrides.js +27 -15
- package/handlers/events/kuva.js +5 -6
- package/handlers/events/nightwave.js +12 -9
- package/handlers/events/objectLike.js +2 -4
- package/handlers/events/parse.js +23 -14
- package/index.js +32 -22
- package/package.json +66 -34
- package/resources/rssFeeds.json +0 -47
- package/utilities/Cache.js +59 -0
- package/utilities/WSCache.js +32 -37
- package/utilities/env.js +10 -0
- package/utilities/index.js +23 -30
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
|
+
[](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
|
[](https://discord.gg/jGZxH9f)
|
package/handlers/RSS.js
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
import RssFeedEmitter from 'rss-feed-emitter';
|
|
2
|
+
import feeds from '../resources/rssFeeds.json' assert { type: 'json' };
|
|
2
3
|
|
|
3
|
-
|
|
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;
|
package/handlers/Twitter.js
CHANGED
|
@@ -1,21 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
|
8
|
+
return 'reply';
|
|
11
9
|
}
|
|
12
10
|
if (tweet.quoted_status_id) {
|
|
13
|
-
return
|
|
11
|
+
return 'quote';
|
|
14
12
|
}
|
|
15
13
|
if (tweet.retweeted_status) {
|
|
16
|
-
return
|
|
14
|
+
return 'retweet';
|
|
17
15
|
}
|
|
18
|
-
return
|
|
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) =>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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 =
|
|
66
|
-
this.
|
|
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 =
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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;
|
package/handlers/Worldstate.js
CHANGED
|
@@ -1,86 +1,77 @@
|
|
|
1
|
-
|
|
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
|
|
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
|
|
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,
|
|
33
|
-
this
|
|
34
|
-
this
|
|
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
|
-
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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
|
|
83
|
-
worldStates[
|
|
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
|
|
84
|
+
this.#emitter.on('ws:update:parsed', ({ language, platform, data }) => {
|
|
94
85
|
const packet = { platform, worldstate: data, language };
|
|
95
|
-
this.parseEvents(packet
|
|
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']
|
|
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],
|
|
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(...
|
|
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
|
|
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
|
|
137
|
+
this.#emitter.emit(id, packet);
|
|
143
138
|
}
|
|
144
139
|
|
|
145
140
|
/**
|
|
146
141
|
* get a specific worldstate version
|
|
147
|
-
* @param {string} [
|
|
148
|
-
* @
|
|
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
|
-
|
|
153
|
-
|
|
154
|
-
if (worldStates[
|
|
155
|
-
return worldStates[
|
|
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(`
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
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
|
-
|
|
9
|
+
export default (key, data) => {
|
|
12
10
|
if (typeof eKeyOverrides[key] === 'string') {
|
|
13
11
|
return eKeyOverrides[key];
|
|
14
12
|
}
|
|
@@ -1,8 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { between, lastUpdated, fromNow } from '../../utilities/index.js';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {Object} CycleLike
|
|
5
|
+
* @property {string} state
|
|
6
|
+
* @property {string} key
|
|
7
|
+
* @property {Date} activation
|
|
8
|
+
*/
|
|
4
9
|
|
|
5
|
-
|
|
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
|
-
|
|
1
|
+
import { logger } from '../../utilities/index.js';
|
|
2
2
|
|
|
3
|
-
|
|
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
|
|
6
|
-
const
|
|
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
|
-
|
|
11
|
-
|
|
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.${
|
|
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(
|
|
34
|
+
logger.error(`Unable to parse arbitraion: ${JSON.stringify(arbi)}\n${e}`);
|
|
18
35
|
}
|
|
19
36
|
return k;
|
|
20
37
|
};
|
|
21
38
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
persistentEnemies: 'enemies',
|
|
25
|
-
fissures: fissureKey,
|
|
26
|
-
enemies: acolyteKey,
|
|
27
|
-
arbitration: arbiKey,
|
|
28
|
-
};
|
|
39
|
+
export const events = 'operations';
|
|
40
|
+
export const persistentEnemies = 'enemies';
|
package/handlers/events/kuva.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
|
|
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 {
|
|
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
|
-
|
|
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 =
|
|
24
|
+
const p = objectLike(deps.data, deps);
|
|
26
25
|
if (p) {
|
|
27
26
|
packets.push(p);
|
|
28
27
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
import objectLike from './objectLike.js';
|
|
2
2
|
|
|
3
|
-
|
|
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 =
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
1
|
+
import { between, lastUpdated } from '../../utilities/index.js';
|
|
2
2
|
|
|
3
|
-
|
|
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);
|
package/handlers/events/parse.js
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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
|
|
12
|
-
* @param {
|
|
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 {
|
|
31
|
+
* @param {Deps} deps dependencies to parse out events
|
|
23
32
|
* @returns {Packet|Packet[]} packet(s) to emit
|
|
24
33
|
*/
|
|
25
|
-
|
|
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(
|
|
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(
|
|
73
|
+
packets.push(objectLike(deps.data, deps));
|
|
65
74
|
case 'nightwave':
|
|
66
|
-
packets.push(
|
|
75
|
+
packets.push(nightwave(deps.data, deps));
|
|
67
76
|
default:
|
|
68
77
|
break;
|
|
69
78
|
}
|
package/index.js
CHANGED
|
@@ -1,27 +1,38 @@
|
|
|
1
|
-
|
|
1
|
+
import EventEmitter from 'node:events';
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import RSS from './handlers/RSS.js';
|
|
4
|
+
import Worldstate from './handlers/Worldstate.js';
|
|
5
|
+
import Twitter from './handlers/Twitter.js';
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
const Worldstate = require('./handlers/Worldstate');
|
|
7
|
-
const Twitter = require('./handlers/Twitter');
|
|
7
|
+
import { logger } from './utilities/index.js';
|
|
8
8
|
|
|
9
|
-
|
|
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}
|
|
23
|
+
* @param {string} locale language to restrict events to
|
|
15
24
|
*/
|
|
16
|
-
constructor({
|
|
25
|
+
constructor({ locale } = { locale: undefined }) {
|
|
17
26
|
super();
|
|
18
27
|
|
|
19
|
-
this
|
|
20
|
-
|
|
28
|
+
this.#locale = locale;
|
|
29
|
+
}
|
|
21
30
|
|
|
22
|
-
|
|
23
|
-
this
|
|
24
|
-
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) =>
|
|
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
|
|
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(
|
|
59
|
-
return this
|
|
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
|
|
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": "
|
|
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": "
|
|
8
|
-
"lint": "eslint
|
|
9
|
-
"lint:fix": "eslint
|
|
10
|
-
"coverage": "npm test &&
|
|
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.
|
|
34
|
+
"homepage": "https://wfcd.github.io/worldstate-emitter/",
|
|
33
35
|
"dependencies": {
|
|
34
|
-
"
|
|
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
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"
|
|
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": ">=
|
|
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": "
|
|
67
|
+
"extends": "@wfcd/eslint-config/esm",
|
|
68
|
+
"parser": "@babel/eslint-parser",
|
|
64
69
|
"parserOptions": {
|
|
65
|
-
"sourceType": "
|
|
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
|
-
"
|
|
97
|
+
"import/no-extraneous-dependencies": [
|
|
89
98
|
"error",
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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
|
}
|
package/resources/rssFeeds.json
CHANGED
|
@@ -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
|
+
}
|
package/utilities/WSCache.js
CHANGED
|
@@ -1,35 +1,31 @@
|
|
|
1
|
-
|
|
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 {
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
this
|
|
21
|
-
|
|
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
|
|
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
|
|
51
|
-
kuvaData: await this
|
|
52
|
-
sentientData: await this
|
|
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
|
|
57
|
-
this
|
|
58
|
-
language: this
|
|
59
|
-
platform: this
|
|
60
|
-
data:
|
|
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
|
-
},
|
|
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
|
|
68
|
+
this.#inner.twitter = newTwitter;
|
|
72
69
|
}
|
|
73
70
|
}
|
|
74
|
-
|
|
75
|
-
module.exports = WSCache;
|
package/utilities/env.js
ADDED
|
@@ -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;
|
package/utilities/index.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import 'colors';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { transports, createLogger, format } from 'winston';
|
|
4
|
+
import { LOG_LEVEL } from './env.js';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
21
|
-
|
|
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()) =>
|
|
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
|
-
};
|