namespace RegexBot.Common; /// /// Helper class for managing rate limit data. /// Specifically, this class holds entries and does not allow the same entry to be held more than once until a specified /// amount of time has passed since the entry was originally tracked; useful for a rate limit system. /// public class RateLimit where T : notnull { private const int DefaultTimeout = 20; // Skeeter's a cool guy and you can't convince me otherwise. /// /// Time until an entry within this instance expires, in seconds. /// public int Timeout { get; } private Dictionary Entries { get; } = new Dictionary(); /// /// Creates a new instance with the default timeout value. /// public RateLimit() : this(DefaultTimeout) { } /// /// Creates a new instance with the given timeout value. /// /// Time until an entry within this instance will expire, in seconds. public RateLimit(int timeout) { if (timeout < 0) throw new ArgumentOutOfRangeException(nameof(timeout), "Timeout valie cannot be negative."); Timeout = timeout; } /// /// Checks if the given value is permitted through the rate limit. /// Executing this method may create a rate limit entry for the given value. /// /// True if the given value is permitted by the rate limiter. public bool IsPermitted(T value) { if (Timeout == 0) return true; // Take a moment to clean out expired entries var now = DateTime.Now; var expired = Entries.Where(x => x.Value.AddSeconds(Timeout) <= now).Select(x => x.Key).ToList(); foreach (var item in expired) Entries.Remove(item); if (Entries.ContainsKey(value)) return false; else { Entries.Add(value, DateTime.Now); return true; } } }