@commandkit/ratelimit 0.0.0-dev.20260317060555
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/LICENSE +21 -0
- package/README.md +801 -0
- package/dist/api.d.ts +79 -0
- package/dist/api.js +266 -0
- package/dist/augmentation.d.ts +11 -0
- package/dist/augmentation.js +8 -0
- package/dist/configure.d.ts +28 -0
- package/dist/configure.js +85 -0
- package/dist/constants.d.ts +17 -0
- package/dist/constants.js +21 -0
- package/dist/directive/use-ratelimit-directive.d.ts +22 -0
- package/dist/directive/use-ratelimit-directive.js +38 -0
- package/dist/directive/use-ratelimit.d.ts +14 -0
- package/dist/directive/use-ratelimit.js +169 -0
- package/dist/engine/RateLimitEngine.d.ts +48 -0
- package/dist/engine/RateLimitEngine.js +137 -0
- package/dist/engine/algorithms/fixed-window.d.ts +44 -0
- package/dist/engine/algorithms/fixed-window.js +198 -0
- package/dist/engine/algorithms/leaky-bucket.d.ts +48 -0
- package/dist/engine/algorithms/leaky-bucket.js +119 -0
- package/dist/engine/algorithms/sliding-window.d.ts +45 -0
- package/dist/engine/algorithms/sliding-window.js +127 -0
- package/dist/engine/algorithms/token-bucket.d.ts +47 -0
- package/dist/engine/algorithms/token-bucket.js +118 -0
- package/dist/engine/violations.d.ts +55 -0
- package/dist/engine/violations.js +106 -0
- package/dist/errors.d.ts +21 -0
- package/dist/errors.js +28 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +53 -0
- package/dist/plugin.d.ts +140 -0
- package/dist/plugin.js +796 -0
- package/dist/providers/fallback.d.ts +7 -0
- package/dist/providers/fallback.js +11 -0
- package/dist/providers/memory.d.ts +6 -0
- package/dist/providers/memory.js +11 -0
- package/dist/providers/redis.d.ts +7 -0
- package/dist/providers/redis.js +11 -0
- package/dist/runtime.d.ts +45 -0
- package/dist/runtime.js +67 -0
- package/dist/storage/fallback.d.ts +180 -0
- package/dist/storage/fallback.js +261 -0
- package/dist/storage/memory.d.ts +146 -0
- package/dist/storage/memory.js +304 -0
- package/dist/storage/redis.d.ts +130 -0
- package/dist/storage/redis.js +243 -0
- package/dist/types.d.ts +296 -0
- package/dist/types.js +40 -0
- package/dist/utils/config.d.ts +34 -0
- package/dist/utils/config.js +105 -0
- package/dist/utils/keys.d.ts +102 -0
- package/dist/utils/keys.js +304 -0
- package/dist/utils/locking.d.ts +17 -0
- package/dist/utils/locking.js +60 -0
- package/dist/utils/time.d.ts +23 -0
- package/dist/utils/time.js +72 -0
- package/package.json +65 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,796 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RateLimitPlugin = void 0;
|
|
4
|
+
const commandkit_1 = require("commandkit");
|
|
5
|
+
const async_queue_1 = require("commandkit/async-queue");
|
|
6
|
+
const discord_js_1 = require("discord.js");
|
|
7
|
+
const RateLimitEngine_1 = require("./engine/RateLimitEngine");
|
|
8
|
+
const config_1 = require("./utils/config");
|
|
9
|
+
const keys_1 = require("./utils/keys");
|
|
10
|
+
const constants_1 = require("./constants");
|
|
11
|
+
const memory_1 = require("./storage/memory");
|
|
12
|
+
const runtime_1 = require("./runtime");
|
|
13
|
+
const configure_1 = require("./configure");
|
|
14
|
+
const time_1 = require("./utils/time");
|
|
15
|
+
const ANALYTICS_EVENTS = {
|
|
16
|
+
HIT: 'ratelimit_hit',
|
|
17
|
+
ALLOWED: 'ratelimit_allowed',
|
|
18
|
+
RESET: 'ratelimit_reset',
|
|
19
|
+
VIOLATION: 'ratelimit_violation',
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Runtime plugin that enforces rate limits for CommandKit commands so handlers stay lean.
|
|
23
|
+
*
|
|
24
|
+
* @extends RuntimePlugin<RateLimitPluginOptions>
|
|
25
|
+
*/
|
|
26
|
+
class RateLimitPlugin extends commandkit_1.RuntimePlugin {
|
|
27
|
+
constructor(options) {
|
|
28
|
+
super(options);
|
|
29
|
+
this.name = 'RateLimitPlugin';
|
|
30
|
+
this.engines = new WeakMap();
|
|
31
|
+
this.memoryStorage = new memory_1.MemoryRateLimitStorage();
|
|
32
|
+
this.queues = new Map();
|
|
33
|
+
this.hasLoggedMissingStorage = false;
|
|
34
|
+
this.preload.add('ratelimit.js');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Initialize runtime storage and defaults for this plugin instance.
|
|
38
|
+
*
|
|
39
|
+
* @param ctx - CommandKit runtime for the active application.
|
|
40
|
+
* @returns Resolves when runtime storage has been initialized.
|
|
41
|
+
* @throws Error when the plugin has not been configured.
|
|
42
|
+
*/
|
|
43
|
+
async activate(ctx) {
|
|
44
|
+
if (!(0, configure_1.isRateLimitConfigured)()) {
|
|
45
|
+
throw new Error('RateLimit is not configured. Call configureRatelimit() during startup (for example in src/ratelimit.ts).');
|
|
46
|
+
}
|
|
47
|
+
const runtimeStorage = this.resolveDefaultStorage();
|
|
48
|
+
if (!runtimeStorage) {
|
|
49
|
+
this.logMissingStorage();
|
|
50
|
+
(0, runtime_1.setRateLimitRuntime)(null);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (!(0, runtime_1.getRateLimitStorage)()) {
|
|
54
|
+
(0, runtime_1.setRateLimitStorage)(runtimeStorage);
|
|
55
|
+
}
|
|
56
|
+
(0, runtime_1.setRateLimitRuntime)({
|
|
57
|
+
storage: runtimeStorage,
|
|
58
|
+
keyPrefix: this.options.keyPrefix,
|
|
59
|
+
defaultLimiter: this.options.defaultLimiter ?? config_1.DEFAULT_LIMITER,
|
|
60
|
+
limiters: this.options.limiters,
|
|
61
|
+
hooks: this.options.hooks,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Dispose queues and clear shared runtime state.
|
|
66
|
+
*
|
|
67
|
+
* @returns Resolves after queues are aborted and runtime state is cleared.
|
|
68
|
+
*/
|
|
69
|
+
async deactivate() {
|
|
70
|
+
for (const queue of this.queues.values()) {
|
|
71
|
+
queue.abort();
|
|
72
|
+
}
|
|
73
|
+
this.queues.clear();
|
|
74
|
+
(0, runtime_1.setRateLimitRuntime)(null);
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Evaluate rate limits and optionally queue execution to avoid dropping commands.
|
|
78
|
+
*
|
|
79
|
+
* @param ctx - CommandKit runtime for the active application.
|
|
80
|
+
* @param env - Command execution environment.
|
|
81
|
+
* @param source - Interaction or message triggering the command.
|
|
82
|
+
* @param prepared - Prepared command execution data.
|
|
83
|
+
* @param execute - Callback that executes the command handler.
|
|
84
|
+
* @returns True when execution is deferred or handled, otherwise false to continue.
|
|
85
|
+
*/
|
|
86
|
+
async executeCommand(ctx, env, source, prepared, execute) {
|
|
87
|
+
const metadata = prepared.command.metadata;
|
|
88
|
+
const rateLimitSetting = metadata?.ratelimit;
|
|
89
|
+
if (rateLimitSetting == null || rateLimitSetting === false) {
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (!env.context) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
if (await this.shouldBypass(source)) {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
const commandConfig = typeof rateLimitSetting === 'object' ? rateLimitSetting : {};
|
|
99
|
+
const { limiter: limiterName, ...commandOverrides } = commandConfig;
|
|
100
|
+
const namedLimiter = limiterName
|
|
101
|
+
? this.options.limiters?.[limiterName]
|
|
102
|
+
: undefined;
|
|
103
|
+
const mergedLimiter = (0, config_1.mergeLimiterConfigs)(config_1.DEFAULT_LIMITER, this.options.defaultLimiter, namedLimiter, commandOverrides);
|
|
104
|
+
const roleLimits = mergeRoleLimits(this.options.roleLimits, this.options.defaultLimiter?.roleLimits, namedLimiter?.roleLimits, commandOverrides.roleLimits);
|
|
105
|
+
const roleStrategy = commandOverrides.roleLimitStrategy ??
|
|
106
|
+
namedLimiter?.roleLimitStrategy ??
|
|
107
|
+
this.options.defaultLimiter?.roleLimitStrategy ??
|
|
108
|
+
this.options.roleLimitStrategy;
|
|
109
|
+
const roleOverride = resolveRoleLimit(roleLimits, roleStrategy, source);
|
|
110
|
+
const effectiveLimiter = roleOverride
|
|
111
|
+
? (0, config_1.mergeLimiterConfigs)(mergedLimiter, roleOverride)
|
|
112
|
+
: mergedLimiter;
|
|
113
|
+
const queueConfig = resolveQueueOptions(this.options.queue, this.options.defaultLimiter?.queue, namedLimiter?.queue, commandOverrides.queue, roleOverride?.queue);
|
|
114
|
+
const scopes = normalizeScopes(effectiveLimiter.scope);
|
|
115
|
+
const keyResolver = effectiveLimiter.keyResolver ?? this.options.keyResolver;
|
|
116
|
+
const keyPrefix = effectiveLimiter.keyPrefix ?? this.options.keyPrefix;
|
|
117
|
+
const storage = this.resolveStorage(effectiveLimiter.storage) ??
|
|
118
|
+
this.resolveDefaultStorage();
|
|
119
|
+
if (!storage) {
|
|
120
|
+
this.logMissingStorage();
|
|
121
|
+
env.store.set(constants_1.RATELIMIT_STORE_KEY, createEmptyStoreValue());
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
const engine = this.getEngine(storage);
|
|
125
|
+
const resolvedKeys = (0, keys_1.resolveScopeKeys)({
|
|
126
|
+
ctx: env.context,
|
|
127
|
+
source,
|
|
128
|
+
command: prepared.command,
|
|
129
|
+
scopes,
|
|
130
|
+
keyPrefix,
|
|
131
|
+
keyResolver,
|
|
132
|
+
});
|
|
133
|
+
if (!resolvedKeys.length) {
|
|
134
|
+
env.store.set(constants_1.RATELIMIT_STORE_KEY, createEmptyStoreValue());
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const results = [];
|
|
138
|
+
let violationCount;
|
|
139
|
+
for (const resolved of resolvedKeys) {
|
|
140
|
+
const resolvedConfigs = (0, config_1.resolveLimiterConfigs)(effectiveLimiter, resolved.scope);
|
|
141
|
+
for (const resolvedConfig of resolvedConfigs) {
|
|
142
|
+
const resolvedKey = withWindowSuffix(resolved.key, resolvedConfig.windowId);
|
|
143
|
+
let output;
|
|
144
|
+
try {
|
|
145
|
+
output = await engine.consume(resolvedKey, resolvedConfig);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
if (this.options.hooks?.onStorageError) {
|
|
149
|
+
await this.options.hooks.onStorageError(error, false);
|
|
150
|
+
}
|
|
151
|
+
commandkit_1.Logger.error `[ratelimit] Storage error during consume: ${error}`;
|
|
152
|
+
env.store.set(constants_1.RATELIMIT_STORE_KEY, createEmptyStoreValue());
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
const { result, violationCount: count } = output;
|
|
156
|
+
results.push(result);
|
|
157
|
+
if (typeof count === 'number') {
|
|
158
|
+
violationCount =
|
|
159
|
+
violationCount == null ? count : Math.max(violationCount, count);
|
|
160
|
+
}
|
|
161
|
+
if (result.limited) {
|
|
162
|
+
(0, commandkit_1.defer)(() => ctx.commandkit.analytics.track({
|
|
163
|
+
name: ANALYTICS_EVENTS.HIT,
|
|
164
|
+
id: prepared.command.command.name,
|
|
165
|
+
data: {
|
|
166
|
+
key: result.key,
|
|
167
|
+
scope: result.scope,
|
|
168
|
+
algorithm: result.algorithm,
|
|
169
|
+
resetAt: result.resetAt,
|
|
170
|
+
remaining: result.remaining,
|
|
171
|
+
},
|
|
172
|
+
}));
|
|
173
|
+
if (violationCount != null) {
|
|
174
|
+
(0, commandkit_1.defer)(() => ctx.commandkit.analytics.track({
|
|
175
|
+
name: ANALYTICS_EVENTS.VIOLATION,
|
|
176
|
+
id: prepared.command.command.name,
|
|
177
|
+
data: {
|
|
178
|
+
key: result.key,
|
|
179
|
+
count: violationCount,
|
|
180
|
+
},
|
|
181
|
+
}));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
(0, commandkit_1.defer)(() => ctx.commandkit.analytics.track({
|
|
186
|
+
name: ANALYTICS_EVENTS.ALLOWED,
|
|
187
|
+
id: prepared.command.command.name,
|
|
188
|
+
data: {
|
|
189
|
+
key: result.key,
|
|
190
|
+
scope: result.scope,
|
|
191
|
+
algorithm: result.algorithm,
|
|
192
|
+
remaining: result.remaining,
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Aggregate across all scopes/windows so callers see a single response.
|
|
200
|
+
*/
|
|
201
|
+
const aggregate = aggregateResults(results);
|
|
202
|
+
env.store.set(constants_1.RATELIMIT_STORE_KEY, aggregate);
|
|
203
|
+
if (aggregate.limited) {
|
|
204
|
+
const firstLimited = results.find((r) => r.limited) ?? results[0];
|
|
205
|
+
if (!firstLimited) {
|
|
206
|
+
return false;
|
|
207
|
+
}
|
|
208
|
+
if (queueConfig.enabled &&
|
|
209
|
+
(await this.enqueueExecution({
|
|
210
|
+
queueKey: selectQueueKey(results),
|
|
211
|
+
queue: queueConfig,
|
|
212
|
+
initialDelayMs: aggregate.retryAfter,
|
|
213
|
+
source,
|
|
214
|
+
execute,
|
|
215
|
+
engine,
|
|
216
|
+
resolvedKeys,
|
|
217
|
+
limiter: effectiveLimiter,
|
|
218
|
+
}))) {
|
|
219
|
+
commandkit_1.Logger.info(`[ratelimit] Queued command /${prepared.command.command.name} for retry in ${Math.ceil(aggregate.retryAfter / 1000)}s`);
|
|
220
|
+
ctx.capture();
|
|
221
|
+
if (this.options.hooks?.onRateLimited) {
|
|
222
|
+
await this.options.hooks.onRateLimited({
|
|
223
|
+
key: firstLimited.key,
|
|
224
|
+
result: firstLimited,
|
|
225
|
+
source,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
if (violationCount != null && this.options.hooks?.onViolation) {
|
|
229
|
+
await this.options.hooks.onViolation(firstLimited.key, violationCount);
|
|
230
|
+
}
|
|
231
|
+
this.emitRateLimited(ctx, {
|
|
232
|
+
key: firstLimited.key,
|
|
233
|
+
result: firstLimited,
|
|
234
|
+
source,
|
|
235
|
+
aggregate,
|
|
236
|
+
commandName: prepared.command.command.name,
|
|
237
|
+
queued: true,
|
|
238
|
+
});
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
commandkit_1.Logger.warn(`[ratelimit] User hit rate limit on /${prepared.command.command.name} - retry in ${Math.ceil(aggregate.retryAfter / 1000)}s`);
|
|
242
|
+
await this.respondRateLimited(env, source, aggregate);
|
|
243
|
+
if (this.options.hooks?.onRateLimited) {
|
|
244
|
+
await this.options.hooks.onRateLimited({
|
|
245
|
+
key: firstLimited.key,
|
|
246
|
+
result: firstLimited,
|
|
247
|
+
source,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
if (violationCount != null && this.options.hooks?.onViolation) {
|
|
251
|
+
await this.options.hooks.onViolation(firstLimited.key, violationCount);
|
|
252
|
+
}
|
|
253
|
+
ctx.capture();
|
|
254
|
+
this.emitRateLimited(ctx, {
|
|
255
|
+
key: firstLimited.key,
|
|
256
|
+
result: firstLimited,
|
|
257
|
+
source,
|
|
258
|
+
aggregate,
|
|
259
|
+
commandName: prepared.command.command.name,
|
|
260
|
+
queued: false,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
else if (this.options.hooks?.onAllowed) {
|
|
264
|
+
const first = results[0];
|
|
265
|
+
if (first) {
|
|
266
|
+
await this.options.hooks.onAllowed({
|
|
267
|
+
key: first.key,
|
|
268
|
+
result: first,
|
|
269
|
+
source,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Clear matching keys when a command is hot-reloaded to avoid stale state.
|
|
277
|
+
*
|
|
278
|
+
* @param ctx - CommandKit runtime for the active application.
|
|
279
|
+
* @param event - HMR event describing the changed file.
|
|
280
|
+
* @returns Resolves after matching keys are cleared and the event is handled.
|
|
281
|
+
*/
|
|
282
|
+
async performHMR(ctx, event) {
|
|
283
|
+
if (!event.path)
|
|
284
|
+
return;
|
|
285
|
+
const normalized = normalizePath(event.path);
|
|
286
|
+
const commands = ctx.commandkit.commandHandler.getCommandsArray();
|
|
287
|
+
const matched = commands.filter((cmd) => cmd.command.path ? normalizePath(cmd.command.path) === normalized : false);
|
|
288
|
+
if (!matched.length)
|
|
289
|
+
return;
|
|
290
|
+
const storage = this.resolveDefaultStorage();
|
|
291
|
+
if (!storage) {
|
|
292
|
+
this.logMissingStorage();
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
for (const cmd of matched) {
|
|
296
|
+
await resetByCommand(storage, this.options.keyPrefix, cmd.command.name);
|
|
297
|
+
}
|
|
298
|
+
event.accept();
|
|
299
|
+
event.preventDefault();
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Resolve a cached engine instance for a storage backend.
|
|
303
|
+
*
|
|
304
|
+
* @param storage - Storage backend to associate with the engine.
|
|
305
|
+
* @returns Cached engine instance for the storage.
|
|
306
|
+
*/
|
|
307
|
+
getEngine(storage) {
|
|
308
|
+
const existing = this.engines.get(storage);
|
|
309
|
+
if (existing)
|
|
310
|
+
return existing;
|
|
311
|
+
const engine = new RateLimitEngine_1.RateLimitEngine(storage);
|
|
312
|
+
this.engines.set(storage, engine);
|
|
313
|
+
return engine;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Normalize a storage config into a storage driver instance.
|
|
317
|
+
*
|
|
318
|
+
* @param config - Storage config or driver.
|
|
319
|
+
* @returns Storage driver instance or null when not configured.
|
|
320
|
+
*/
|
|
321
|
+
resolveStorage(config) {
|
|
322
|
+
if (!config)
|
|
323
|
+
return null;
|
|
324
|
+
if ('driver' in config)
|
|
325
|
+
return config.driver;
|
|
326
|
+
return config;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Resolve the default storage, falling back to memory when enabled.
|
|
330
|
+
*
|
|
331
|
+
* @returns Resolved storage instance or null when disabled.
|
|
332
|
+
*/
|
|
333
|
+
resolveDefaultStorage() {
|
|
334
|
+
const resolved = this.resolveStorage(this.options.storage) ?? (0, runtime_1.getRateLimitStorage)();
|
|
335
|
+
if (resolved)
|
|
336
|
+
return resolved;
|
|
337
|
+
if (this.options.initializeDefaultStorage === false ||
|
|
338
|
+
this.options.initializeDefaultDriver === false) {
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
return this.memoryStorage;
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Log a one-time error when storage is missing.
|
|
345
|
+
*
|
|
346
|
+
* @returns Nothing; logs at most once per process.
|
|
347
|
+
*/
|
|
348
|
+
logMissingStorage() {
|
|
349
|
+
if (this.hasLoggedMissingStorage)
|
|
350
|
+
return;
|
|
351
|
+
this.hasLoggedMissingStorage = true;
|
|
352
|
+
commandkit_1.Logger.error('[ratelimit] No storage configured. Set storage via configureRatelimit({ storage }), setRateLimitStorage(), or enable initializeDefaultStorage.');
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Emit a ratelimited event through CommandKit's event bus.
|
|
356
|
+
*
|
|
357
|
+
* @param ctx - CommandKit runtime for the active application.
|
|
358
|
+
* @param payload - Rate-limit event payload to emit.
|
|
359
|
+
* @returns Nothing; emits the event when available.
|
|
360
|
+
*/
|
|
361
|
+
emitRateLimited(ctx, payload) {
|
|
362
|
+
ctx.commandkit.events?.to('ratelimits').emit('ratelimited', payload);
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Determine whether a source should bypass rate limits.
|
|
366
|
+
*
|
|
367
|
+
* @param source - Interaction or message to evaluate.
|
|
368
|
+
* @returns True when the source should bypass rate limiting.
|
|
369
|
+
*/
|
|
370
|
+
async shouldBypass(source) {
|
|
371
|
+
const bypass = this.options.bypass;
|
|
372
|
+
if (bypass) {
|
|
373
|
+
/**
|
|
374
|
+
* Check permanent allowlists first to avoid storage lookups.
|
|
375
|
+
*/
|
|
376
|
+
const userId = source instanceof discord_js_1.Message ? source.author.id : source.user?.id;
|
|
377
|
+
if (userId && bypass.userIds?.includes(userId))
|
|
378
|
+
return true;
|
|
379
|
+
const guildId = source.guildId ?? null;
|
|
380
|
+
if (guildId && bypass.guildIds?.includes(guildId))
|
|
381
|
+
return true;
|
|
382
|
+
const roleIds = (0, keys_1.getRoleIds)(source);
|
|
383
|
+
if (roleIds.length && bypass.roleIds?.length) {
|
|
384
|
+
if (roleIds.some((roleId) => bypass.roleIds.includes(roleId)))
|
|
385
|
+
return true;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Check temporary exemptions stored in the rate limit storage next.
|
|
390
|
+
*/
|
|
391
|
+
if (await this.hasTemporaryBypass(source)) {
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Run custom predicate last so it can override previous checks.
|
|
396
|
+
*/
|
|
397
|
+
if (bypass?.check) {
|
|
398
|
+
return Boolean(await bypass.check(source));
|
|
399
|
+
}
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Check for temporary exemptions in storage for the source.
|
|
404
|
+
*
|
|
405
|
+
* @param source - Interaction or message to evaluate.
|
|
406
|
+
* @returns True when a temporary exemption is found.
|
|
407
|
+
*/
|
|
408
|
+
async hasTemporaryBypass(source) {
|
|
409
|
+
const storage = this.resolveDefaultStorage();
|
|
410
|
+
if (!storage)
|
|
411
|
+
return false;
|
|
412
|
+
const keys = (0, keys_1.resolveExemptionKeys)(source, this.options.keyPrefix);
|
|
413
|
+
if (!keys.length)
|
|
414
|
+
return false;
|
|
415
|
+
try {
|
|
416
|
+
for (const key of keys) {
|
|
417
|
+
if (await storage.get(key))
|
|
418
|
+
return true;
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
if (this.options.hooks?.onStorageError) {
|
|
423
|
+
await this.options.hooks.onStorageError(error, false);
|
|
424
|
+
}
|
|
425
|
+
commandkit_1.Logger.error `[ratelimit] Storage error during exemption check: ${error}`;
|
|
426
|
+
}
|
|
427
|
+
return false;
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Send the default rate-limited response when no custom handler is set.
|
|
431
|
+
*
|
|
432
|
+
* @param env - Command execution environment.
|
|
433
|
+
* @param source - Interaction or message that was limited.
|
|
434
|
+
* @param info - Aggregated rate-limit info for the response.
|
|
435
|
+
* @returns Resolves after the response is sent.
|
|
436
|
+
*/
|
|
437
|
+
async respondRateLimited(env, source, info) {
|
|
438
|
+
const ctx = env.context;
|
|
439
|
+
if (this.options.onRateLimited && ctx) {
|
|
440
|
+
await this.options.onRateLimited(ctx, info);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
const retrySeconds = Math.ceil(info.retryAfter / 1000);
|
|
444
|
+
const embed = new discord_js_1.EmbedBuilder()
|
|
445
|
+
.setTitle(':hourglass_flowing_sand: You are on cooldown')
|
|
446
|
+
.setDescription(`Try again <t:${Math.floor(info.resetAt / 1000)}:R> (in ${retrySeconds}s).`)
|
|
447
|
+
.setColor('Red');
|
|
448
|
+
if (source instanceof discord_js_1.Message) {
|
|
449
|
+
if (source.channel?.isSendable()) {
|
|
450
|
+
try {
|
|
451
|
+
await source.reply({ embeds: [embed] });
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
commandkit_1.Logger.error `[ratelimit] Failed to reply with rate limit embed: ${error}`;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (!source.isRepliable())
|
|
460
|
+
return;
|
|
461
|
+
if (source.replied || source.deferred) {
|
|
462
|
+
try {
|
|
463
|
+
await source.followUp({
|
|
464
|
+
embeds: [embed],
|
|
465
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
catch (error) {
|
|
469
|
+
commandkit_1.Logger.error `[ratelimit] Failed to follow up with rate limit embed: ${error}`;
|
|
470
|
+
}
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
await source.reply({
|
|
475
|
+
embeds: [embed],
|
|
476
|
+
flags: discord_js_1.MessageFlags.Ephemeral,
|
|
477
|
+
});
|
|
478
|
+
}
|
|
479
|
+
catch (error) {
|
|
480
|
+
commandkit_1.Logger.error `[ratelimit] Failed to reply with rate limit embed: ${error}`;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Enqueue a command execution for later retry under queue rules.
|
|
485
|
+
*
|
|
486
|
+
* @param params - Queue execution parameters.
|
|
487
|
+
* @returns True when the execution was queued.
|
|
488
|
+
*/
|
|
489
|
+
async enqueueExecution(params) {
|
|
490
|
+
if (!params.queue.enabled)
|
|
491
|
+
return false;
|
|
492
|
+
const queue = this.getQueue(params.queueKey, params.queue);
|
|
493
|
+
const size = queue.getPending() + queue.getRunning();
|
|
494
|
+
if (size >= params.queue.maxSize) {
|
|
495
|
+
/**
|
|
496
|
+
* Queue full: fall back to immediate rate-limit handling to avoid unbounded growth.
|
|
497
|
+
*/
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
await this.deferInteractionIfNeeded(params.source, params.queue);
|
|
501
|
+
const queuedAt = Date.now();
|
|
502
|
+
const timeoutAt = queuedAt + params.queue.timeoutMs;
|
|
503
|
+
const initialDelay = Math.max(0, params.initialDelayMs);
|
|
504
|
+
void queue
|
|
505
|
+
.add(async () => {
|
|
506
|
+
let delayMs = initialDelay;
|
|
507
|
+
while (true) {
|
|
508
|
+
if (delayMs > 0) {
|
|
509
|
+
await sleep(delayMs);
|
|
510
|
+
}
|
|
511
|
+
if (Date.now() > timeoutAt) {
|
|
512
|
+
commandkit_1.Logger.warn(`[ratelimit] Queue timeout exceeded for key ${params.queueKey}`);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const aggregate = await this.consumeForQueue(params.engine, params.limiter, params.resolvedKeys).catch(async (error) => {
|
|
516
|
+
if (this.options.hooks?.onStorageError) {
|
|
517
|
+
await this.options.hooks.onStorageError(error, false);
|
|
518
|
+
}
|
|
519
|
+
commandkit_1.Logger.error `[ratelimit] Storage error during queued consume: ${error}`;
|
|
520
|
+
return null;
|
|
521
|
+
});
|
|
522
|
+
if (!aggregate) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
if (!aggregate.limited) {
|
|
526
|
+
await params.execute();
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
delayMs = Math.max(aggregate.retryAfter, 250);
|
|
530
|
+
}
|
|
531
|
+
})
|
|
532
|
+
.catch((error) => {
|
|
533
|
+
commandkit_1.Logger.error `[ratelimit] Queue task failed: ${error}`;
|
|
534
|
+
});
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Get or create an async queue for the given key.
|
|
539
|
+
*
|
|
540
|
+
* @param key - Queue identifier.
|
|
541
|
+
* @param options - Normalized queue settings.
|
|
542
|
+
* @returns Async queue instance.
|
|
543
|
+
*/
|
|
544
|
+
getQueue(key, options) {
|
|
545
|
+
const existing = this.queues.get(key);
|
|
546
|
+
if (existing)
|
|
547
|
+
return existing;
|
|
548
|
+
const queue = (0, async_queue_1.createAsyncQueue)({ concurrency: options.concurrency });
|
|
549
|
+
this.queues.set(key, queue);
|
|
550
|
+
return queue;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Consume limits for queued execution to decide whether to run now.
|
|
554
|
+
*
|
|
555
|
+
* @param engine - Rate limit engine.
|
|
556
|
+
* @param limiter - Resolved limiter configuration.
|
|
557
|
+
* @param resolvedKeys - Scope keys to consume.
|
|
558
|
+
* @returns Aggregated rate-limit info for the queue check.
|
|
559
|
+
*/
|
|
560
|
+
async consumeForQueue(engine, limiter, resolvedKeys) {
|
|
561
|
+
const results = [];
|
|
562
|
+
for (const resolved of resolvedKeys) {
|
|
563
|
+
const resolvedConfigs = (0, config_1.resolveLimiterConfigs)(limiter, resolved.scope);
|
|
564
|
+
for (const resolvedConfig of resolvedConfigs) {
|
|
565
|
+
const resolvedKey = withWindowSuffix(resolved.key, resolvedConfig.windowId);
|
|
566
|
+
const output = await engine.consume(resolvedKey, resolvedConfig);
|
|
567
|
+
results.push(output.result);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
return aggregateResults(results);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Defer interaction replies when queueing and the source is repliable.
|
|
574
|
+
*
|
|
575
|
+
* @param source - Interaction or message that may be deferred.
|
|
576
|
+
* @param queue - Normalized queue settings.
|
|
577
|
+
* @returns Resolves after attempting to defer the interaction.
|
|
578
|
+
*/
|
|
579
|
+
async deferInteractionIfNeeded(source, queue) {
|
|
580
|
+
if (!queue.deferInteraction)
|
|
581
|
+
return;
|
|
582
|
+
if (source instanceof discord_js_1.Message)
|
|
583
|
+
return;
|
|
584
|
+
if (!source.isRepliable())
|
|
585
|
+
return;
|
|
586
|
+
if (source.deferred || source.replied)
|
|
587
|
+
return;
|
|
588
|
+
try {
|
|
589
|
+
await source.deferReply({
|
|
590
|
+
flags: queue.ephemeral ? discord_js_1.MessageFlags.Ephemeral : undefined,
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
catch (error) {
|
|
594
|
+
commandkit_1.Logger.debug(`[ratelimit] Failed to defer interaction for queued command: ${error}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
exports.RateLimitPlugin = RateLimitPlugin;
|
|
599
|
+
/**
|
|
600
|
+
* Normalize scope input into a de-duplicated scope array.
|
|
601
|
+
*
|
|
602
|
+
* @param scope - Scope config value.
|
|
603
|
+
* @returns Array of scopes to enforce.
|
|
604
|
+
*/
|
|
605
|
+
function normalizeScopes(scope) {
|
|
606
|
+
if (!scope)
|
|
607
|
+
return ['user'];
|
|
608
|
+
if (Array.isArray(scope))
|
|
609
|
+
return Array.from(new Set(scope));
|
|
610
|
+
return [scope];
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Aggregate multiple rate-limit results into a single summary object.
|
|
614
|
+
*
|
|
615
|
+
* @param results - Individual limiter/window results.
|
|
616
|
+
* @returns Aggregated rate-limit store value.
|
|
617
|
+
*/
|
|
618
|
+
function aggregateResults(results) {
|
|
619
|
+
if (!results.length) {
|
|
620
|
+
return createEmptyStoreValue();
|
|
621
|
+
}
|
|
622
|
+
const limitedResults = results.filter((r) => r.limited);
|
|
623
|
+
const limited = limitedResults.length > 0;
|
|
624
|
+
const remaining = Math.min(...results.map((r) => r.remaining));
|
|
625
|
+
const resetAt = Math.max(...results.map((r) => r.resetAt));
|
|
626
|
+
const retryAfter = limited
|
|
627
|
+
? Math.max(...limitedResults.map((r) => r.retryAfter))
|
|
628
|
+
: 0;
|
|
629
|
+
return {
|
|
630
|
+
limited,
|
|
631
|
+
remaining,
|
|
632
|
+
resetAt,
|
|
633
|
+
retryAfter,
|
|
634
|
+
results,
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Append a window suffix to a key when a window id is present.
|
|
639
|
+
*
|
|
640
|
+
* @param key - Base storage key.
|
|
641
|
+
* @param windowId - Optional window identifier.
|
|
642
|
+
* @returns Key with window suffix when provided.
|
|
643
|
+
*/
|
|
644
|
+
function withWindowSuffix(key, windowId) {
|
|
645
|
+
if (!windowId)
|
|
646
|
+
return key;
|
|
647
|
+
return `${key}:w:${windowId}`;
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Create an empty aggregate result for cases with no limiter results.
|
|
651
|
+
*
|
|
652
|
+
* @returns Empty rate-limit store value.
|
|
653
|
+
*/
|
|
654
|
+
function createEmptyStoreValue() {
|
|
655
|
+
return {
|
|
656
|
+
limited: false,
|
|
657
|
+
remaining: 0,
|
|
658
|
+
resetAt: 0,
|
|
659
|
+
retryAfter: 0,
|
|
660
|
+
results: [],
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Merge multiple role limit maps, with later maps overriding earlier ones.
|
|
665
|
+
*
|
|
666
|
+
* @param limits - Role limit maps ordered from lowest to highest priority.
|
|
667
|
+
* @returns Merged role limits or undefined when empty.
|
|
668
|
+
*/
|
|
669
|
+
function mergeRoleLimits(...limits) {
|
|
670
|
+
const merged = {};
|
|
671
|
+
for (const limit of limits) {
|
|
672
|
+
if (!limit)
|
|
673
|
+
continue;
|
|
674
|
+
Object.assign(merged, limit);
|
|
675
|
+
}
|
|
676
|
+
return Object.keys(merged).length ? merged : undefined;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* Resolve a role-specific limiter for a source using a strategy.
|
|
680
|
+
*
|
|
681
|
+
* @param limits - Role limit map keyed by role id.
|
|
682
|
+
* @param strategy - Role limit strategy to apply.
|
|
683
|
+
* @param source - Interaction or message to resolve roles from.
|
|
684
|
+
* @returns Resolved role limiter or null when none match.
|
|
685
|
+
*/
|
|
686
|
+
function resolveRoleLimit(limits, strategy, source) {
|
|
687
|
+
if (!limits)
|
|
688
|
+
return null;
|
|
689
|
+
const roleIds = (0, keys_1.getRoleIds)(source);
|
|
690
|
+
if (!roleIds.length)
|
|
691
|
+
return null;
|
|
692
|
+
const entries = Object.entries(limits).filter(([roleId]) => roleIds.includes(roleId));
|
|
693
|
+
if (!entries.length)
|
|
694
|
+
return null;
|
|
695
|
+
const resolvedStrategy = strategy ?? 'highest';
|
|
696
|
+
if (resolvedStrategy === 'first') {
|
|
697
|
+
return entries[0]?.[1] ?? null;
|
|
698
|
+
}
|
|
699
|
+
const scored = entries.map(([, limiter]) => ({
|
|
700
|
+
limiter,
|
|
701
|
+
score: computeLimiterScore(limiter),
|
|
702
|
+
}));
|
|
703
|
+
scored.sort((a, b) => {
|
|
704
|
+
if (resolvedStrategy === 'lowest') {
|
|
705
|
+
return a.score - b.score;
|
|
706
|
+
}
|
|
707
|
+
return b.score - a.score;
|
|
708
|
+
});
|
|
709
|
+
return scored[0]?.limiter ?? null;
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Compute a comparable score for a limiter for role-strategy sorting.
|
|
713
|
+
*
|
|
714
|
+
* @param limiter - Limiter configuration to score.
|
|
715
|
+
* @returns Minimum request rate across windows.
|
|
716
|
+
*/
|
|
717
|
+
function computeLimiterScore(limiter) {
|
|
718
|
+
const resolvedConfigs = (0, config_1.resolveLimiterConfigs)(limiter, 'user');
|
|
719
|
+
if (!resolvedConfigs.length)
|
|
720
|
+
return 0;
|
|
721
|
+
const scores = resolvedConfigs.map((resolved) => resolved.maxRequests / resolved.intervalMs);
|
|
722
|
+
return Math.min(...scores);
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Merge and normalize queue options across config layers.
|
|
726
|
+
*
|
|
727
|
+
* @param options - Queue option layers ordered from lowest to highest priority.
|
|
728
|
+
* @returns Normalized queue options.
|
|
729
|
+
*/
|
|
730
|
+
function resolveQueueOptions(...options) {
|
|
731
|
+
const merged = options.reduce((acc, opt) => ({ ...acc, ...(opt ?? {}) }), {});
|
|
732
|
+
const hasConfig = options.some((opt) => opt != null);
|
|
733
|
+
const enabled = merged.enabled ?? hasConfig;
|
|
734
|
+
return {
|
|
735
|
+
enabled,
|
|
736
|
+
maxSize: (0, time_1.clampAtLeast)(merged.maxSize ?? 3, 1),
|
|
737
|
+
timeoutMs: (0, time_1.clampAtLeast)((0, time_1.resolveDuration)(merged.timeout, 30000), 1),
|
|
738
|
+
deferInteraction: merged.deferInteraction !== false,
|
|
739
|
+
ephemeral: merged.ephemeral !== false,
|
|
740
|
+
concurrency: (0, time_1.clampAtLeast)(merged.concurrency ?? 1, 1),
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
/**
|
|
744
|
+
* Select the queue key from the result with the longest retry delay.
|
|
745
|
+
*
|
|
746
|
+
* @param results - Rate limit results for the command.
|
|
747
|
+
* @returns Queue key to use for serialization.
|
|
748
|
+
*/
|
|
749
|
+
function selectQueueKey(results) {
|
|
750
|
+
let target;
|
|
751
|
+
for (const result of results) {
|
|
752
|
+
if (!result.limited)
|
|
753
|
+
continue;
|
|
754
|
+
if (!target || result.retryAfter > target.retryAfter) {
|
|
755
|
+
target = result;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
return (target ?? results[0])?.key ?? 'ratelimit:queue';
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Delay execution for a given duration.
|
|
762
|
+
*
|
|
763
|
+
* @param ms - Delay duration in milliseconds.
|
|
764
|
+
* @returns Promise that resolves after the delay.
|
|
765
|
+
*/
|
|
766
|
+
function sleep(ms) {
|
|
767
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Reset all rate-limit keys for a specific command name.
|
|
771
|
+
*
|
|
772
|
+
* @param storage - Storage backend to delete from.
|
|
773
|
+
* @param keyPrefix - Optional prefix to prepend to the key.
|
|
774
|
+
* @param commandName - Command name to reset.
|
|
775
|
+
* @returns Resolves after matching keys are deleted.
|
|
776
|
+
*/
|
|
777
|
+
async function resetByCommand(storage, keyPrefix, commandName) {
|
|
778
|
+
if (!storage.deleteByPattern)
|
|
779
|
+
return;
|
|
780
|
+
const prefix = keyPrefix ?? '';
|
|
781
|
+
const pattern = `${prefix}*:${commandName}`;
|
|
782
|
+
await storage.deleteByPattern(pattern);
|
|
783
|
+
await storage.deleteByPattern(`violation:${pattern}`);
|
|
784
|
+
await storage.deleteByPattern(`${pattern}:w:*`);
|
|
785
|
+
await storage.deleteByPattern(`violation:${pattern}:w:*`);
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Normalize path separators to forward slashes for comparisons.
|
|
789
|
+
*
|
|
790
|
+
* @param path - Path to normalize.
|
|
791
|
+
* @returns Normalized path string.
|
|
792
|
+
*/
|
|
793
|
+
function normalizePath(path) {
|
|
794
|
+
return path.replace(/\\/g, '/');
|
|
795
|
+
}
|
|
796
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL3BsdWdpbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSwyQ0FBMEQ7QUFPMUQsd0RBQTJFO0FBQzNFLDJDQUFpRTtBQUVqRSw4REFBMkQ7QUFhM0QsMkNBSXdCO0FBQ3hCLHVDQUlzQjtBQUV0QiwyQ0FBa0Q7QUFDbEQsNkNBQTBEO0FBQzFELHVDQUltQjtBQUNuQiwyQ0FBb0Q7QUFDcEQsdUNBQTZEO0FBRTdELE1BQU0sZ0JBQWdCLEdBQUc7SUFDdkIsR0FBRyxFQUFFLGVBQWU7SUFDcEIsT0FBTyxFQUFFLG1CQUFtQjtJQUM1QixLQUFLLEVBQUUsaUJBQWlCO0lBQ3hCLFNBQVMsRUFBRSxxQkFBcUI7Q0FDeEIsQ0FBQztBQVdYOzs7O0dBSUc7QUFDSCxNQUFhLGVBQWdCLFNBQVEsMEJBQXFDO0lBT3hFLFlBQW1CLE9BQStCO1FBQ2hELEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQVBELFNBQUksR0FBRyxpQkFBaUIsQ0FBQztRQUN4QixZQUFPLEdBQUcsSUFBSSxPQUFPLEVBQXFDLENBQUM7UUFDM0Qsa0JBQWEsR0FBRyxJQUFJLCtCQUFzQixFQUFFLENBQUM7UUFDN0MsV0FBTSxHQUFHLElBQUksR0FBRyxFQUFzQixDQUFDO1FBQ2hELDRCQUF1QixHQUFHLEtBQUssQ0FBQztRQUl0QyxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxjQUFjLENBQUMsQ0FBQztJQUNuQyxDQUFDO0lBRUQ7Ozs7OztPQU1HO0lBQ0ksS0FBSyxDQUFDLFFBQVEsQ0FBQyxHQUE0QjtRQUNoRCxJQUFJLENBQUMsSUFBQSxpQ0FBcUIsR0FBRSxFQUFFLENBQUM7WUFDN0IsTUFBTSxJQUFJLEtBQUssQ0FDYiwwR0FBMEcsQ0FDM0csQ0FBQztRQUNKLENBQUM7UUFFRCxNQUFNLGNBQWMsR0FBRyxJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztRQUVwRCxJQUFJLENBQUMsY0FB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
|