import { Collection, User } from 'discord.js'; import { RateLimit } from './RateLimit'; import { Time } from '../util/Time'; import { Message } from '../types/Message'; /** * Handles assigning ratelimits to guildmembers and users * @param {string} limit Ratelimit string matching the regex `\d+\/\d+[s|m|h|d]`
* **Example:** `1/10m` to limit a command to one use per 10 minutes * @param {boolean} global Whether or not this RateLimiter handles global ratelimits */ export class RateLimiter { private readonly _limit: [number, number]; private readonly _global: boolean; private readonly _rateLimits: Collection>; private readonly _globalLimits: Collection; public constructor(limit: string, global: boolean) { this._limit = this._parseLimit(limit); this._global = global; this._rateLimits = new Collection>(); this._globalLimits = new Collection(); } /** * Returns the RateLimit object for the message author if global * or message member if message is in a guild. If a userOverride * is given then the RateLimit or global RateLimit will be * retrieved for that user based on the message location * @param {external:Message} message Discord.js Message object * @param {external:User} userOverride User object to use in place of Message author * @returns {RateLimit} */ public get(message: Message, userOverride?: User): RateLimit { const user: User = userOverride ? userOverride : message.author; if (this._isGlobal(message)) { if (!this._globalLimits.has(user.id)) this._globalLimits.set(user.id, new RateLimit(this._limit)); return this._globalLimits.get(user.id); } else { if (!this._rateLimits.has(message.guild.id)) this._rateLimits.set(message.guild.id, new Collection()); if (!this._rateLimits.get(message.guild.id).has(user.id)) this._rateLimits.get(message.guild.id).set(user.id, new RateLimit(this._limit)); return this._rateLimits.get(message.guild.id).get(user.id); } } /** * Parse the ratelimit from the given input string * @private */ private _parseLimit(limitString: string): [number, number] { const limitRegex: RegExp = /^(\d+)\/(\d+)(s|m|h|d)?$/; if (!limitRegex.test(limitString)) throw new Error(`Failed to parse a ratelimit from '${limitString}'`); let [limit, duration, post]: (string | number)[] = limitRegex.exec(limitString).slice(1, 4); if (post) duration = Time.parseShorthand(duration + post); else duration = parseInt(duration); limit = parseInt(limit); return [limit, duration]; } /** * Determine whether or not to use the global rate limit collection * @private */ private _isGlobal(message?: Message): boolean { return message ? message.channel.type !== 'text' || this._global : this._global; } }