Ruslan_F

Discord.Net precondition throttle

May 13th, 2022 (edited)
462
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 8.80 KB | None | 0 0
  1.     public enum ThrottleBy
  2.     {
  3.         User,
  4.         Guild
  5.     }
  6.     [AttributeUsage(AttributeTargets.Class)]
  7.     public class ThrottleAttribute : PreconditionAttribute
  8.     {
  9.         private readonly ThrottleBy _throttleBy;
  10.         private readonly int _limit;
  11.         private readonly int _intervalSeconds;
  12.  
  13.         public ThrottleAttribute(ThrottleBy throttleBy, int limit, int intervalSeconds)
  14.         {
  15.             _throttleBy = throttleBy;
  16.             _limit = limit;
  17.             _intervalSeconds = intervalSeconds;
  18.         }
  19.  
  20.         public override async Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context,
  21.             ICommandInfo commandInfo, IServiceProvider services)
  22.         {
  23.             var throttleService = services.GetRequiredService<IThrottleService>();
  24.            
  25.             if (throttleService.CheckThrottle(_throttleBy, _limit, _intervalSeconds, context.User, context.Guild))
  26.                 return PreconditionResult.FromSuccess();
  27.             else
  28.             {
  29.                 var reset = throttleService.GetThrottleReset(_throttleBy, _limit, _intervalSeconds, context.User, context.Guild);
  30.                 await context.Interaction.RespondAsync($"I'm busy right now. Retry in {reset.TotalSeconds:F0} seconds", ephemeral: true);
  31.                 return PreconditionResult.FromError("Throttle exceeded");
  32.             }
  33.         }
  34.     }
  35.    
  36.     [AttributeUsage(AttributeTargets.Method)]
  37.     public class ThrottleCommandAttribute : PreconditionAttribute
  38.     {
  39.         private readonly ThrottleBy _throttleBy;
  40.         private readonly int _limit;
  41.         private readonly int _intervalSeconds;
  42.  
  43.         public ThrottleCommandAttribute(ThrottleBy throttleBy, int limit, int intervalSeconds)
  44.         {
  45.             _throttleBy = throttleBy;
  46.             _limit = limit;
  47.             _intervalSeconds = intervalSeconds;
  48.         }
  49.  
  50.         public override async Task<PreconditionResult> CheckRequirementsAsync(IInteractionContext context,
  51.             ICommandInfo commandInfo, IServiceProvider services)
  52.         {
  53.             var throttleService = services.GetRequiredService<IThrottleService>();
  54.             var command = commandInfo.Name;
  55.             if (throttleService.CheckThrottle(_throttleBy, _limit, _intervalSeconds, context.User, context.Guild, command))
  56.                 return PreconditionResult.FromSuccess();
  57.             else
  58.             {
  59.                 var reset = throttleService.GetThrottleReset(_throttleBy, _limit, _intervalSeconds, context.User, context.Guild, command);
  60.                 await context.Interaction.RespondAsync($"I'm busy right now. Retry in {reset.TotalSeconds:F0} seconds", ephemeral: true);
  61.                 return PreconditionResult.FromError("Throttle exceeded");
  62.             }
  63.         }
  64.     }
  65.  
  66.     public interface IThrottleService
  67.     {
  68.         TimeSpan GetThrottleReset(ThrottleBy throttleBy, int requestsLimit, int intervalSeconds, IUser us, IGuild guild,
  69.             string command = null);
  70.         bool CheckThrottle(ThrottleBy throttleBy, int limit, int intervalSeconds, IUser user,
  71.             IGuild guild, string command = null);
  72.     }
  73.  
  74.     public class ThrottleService : IThrottleService
  75.     {
  76.         class ThrottleInfo
  77.         {
  78.             public DateTime FirstRequestTime { get; set; }
  79.             public int RequestCount { get; set; }
  80.         }
  81.  
  82.         class ThrottleKey
  83.         {
  84.             public ulong ObjectId { get; }
  85.             public string Command { get; }
  86.  
  87.             public ThrottleKey(ulong objectId, string command)
  88.             {
  89.                 ObjectId = objectId;
  90.                 Command = command;
  91.             }
  92.  
  93.             protected bool Equals(ThrottleKey other)
  94.             {
  95.                 return ObjectId == other.ObjectId && Command == other.Command;
  96.             }
  97.  
  98.             public override bool Equals(object obj)
  99.             {
  100.                 if (ReferenceEquals(null, obj)) return false;
  101.                 if (ReferenceEquals(this, obj)) return true;
  102.                 if (obj.GetType() != this.GetType()) return false;
  103.                 return Equals((ThrottleKey)obj);
  104.             }
  105.  
  106.             public override int GetHashCode()
  107.             {
  108.                 return HashCode.Combine(ObjectId, Command);
  109.             }
  110.         }
  111.         private ConcurrentDictionary<ThrottleKey, ThrottleInfo> UserThrottles { get; set; } = new();
  112.         private ConcurrentDictionary<ThrottleKey, ThrottleInfo> GuildThrottles { get; set; } = new();
  113.         public TimeSpan GetThrottleReset(ThrottleBy throttleBy, int requestsLimit, int intervalSeconds, IUser user,
  114.             IGuild guild, string command)
  115.         {
  116.             ConcurrentDictionary<ThrottleKey, ThrottleInfo> throttles = null;
  117.             var interval = TimeSpan.FromSeconds(intervalSeconds);
  118.             ulong throttleObjectId = 0ul;
  119.             switch (throttleBy)
  120.             {
  121.                 case ThrottleBy.User:
  122.                     throttles = UserThrottles;
  123.                     throttleObjectId = user.Id;
  124.                     break;
  125.                 case ThrottleBy.Guild:
  126.                     throttles = GuildThrottles;
  127.                     throttleObjectId = guild.Id;
  128.                     break;
  129.                 default:
  130.                     throw new ArgumentOutOfRangeException(nameof(throttleBy), throttleBy, null);
  131.             }
  132.  
  133.             var throttleKey = new ThrottleKey(throttleObjectId, command);
  134.             if (throttles.TryGetValue(throttleKey, out var throttleInfo))
  135.             {
  136.                 return interval - (DateTime.Now - throttleInfo.FirstRequestTime);
  137.             }
  138.             else
  139.             {
  140.                 return TimeSpan.Zero;
  141.             }
  142.         }
  143.  
  144.         public bool CheckThrottle(ThrottleBy throttleBy, int requestsLimit, int intervalSeconds,
  145.             IUser user, IGuild guild, string command)
  146.         {
  147.             ConcurrentDictionary<ThrottleKey, ThrottleInfo> throttles = null;
  148.             var interval = TimeSpan.FromSeconds(intervalSeconds);
  149.             ulong throttleObjectId = 0ul;
  150.             switch (throttleBy)
  151.             {
  152.                 case ThrottleBy.User:
  153.                     throttles = UserThrottles;
  154.                     throttleObjectId = user.Id;
  155.                     break;
  156.                 case ThrottleBy.Guild:
  157.                     throttles = GuildThrottles;
  158.                     throttleObjectId = guild.Id;
  159.                     break;
  160.                 default:
  161.                     throw new ArgumentOutOfRangeException(nameof(throttleBy), throttleBy, null);
  162.             }
  163.             var throttleKey = new ThrottleKey(throttleObjectId, command);
  164.             if (throttles.TryGetValue(throttleKey, out var throttleInfo))
  165.             {
  166.                 var checkThrottle = ValidateThrottle(requestsLimit, throttleInfo, interval, throttles, throttleKey);
  167.                
  168.                 return checkThrottle;
  169.             }
  170.             else
  171.             {
  172.                 if (!throttles.TryAdd(throttleKey, new ThrottleInfo()
  173.                 {
  174.                     RequestCount = 1,
  175.                     FirstRequestTime = DateTime.Now
  176.                 }))
  177.                 {
  178.                     throttleInfo = throttles[throttleKey];
  179.                     return ValidateThrottle(requestsLimit, throttleInfo, interval, throttles, throttleKey);
  180.                 }
  181.  
  182.                 return true;
  183.             }
  184.         }
  185.  
  186.         private bool ValidateThrottle(int requestsLimit, ThrottleInfo throttleInfo, TimeSpan interval,
  187.             ConcurrentDictionary<ThrottleKey, ThrottleInfo> throttles, ThrottleKey throttleKey)
  188.         {
  189.             if (DateTime.Now - throttleInfo.FirstRequestTime > interval)
  190.             {
  191.                 if (!throttles.TryUpdate(throttleKey, new ThrottleInfo()
  192.                 {
  193.                     FirstRequestTime = DateTime.Now,
  194.                     RequestCount = 1
  195.                 }, throttleInfo))
  196.                 {
  197.                     return ValidateThrottle(requestsLimit, throttles[throttleKey], interval, throttles,
  198.                         throttleKey);
  199.                 }
  200.                 return true;
  201.             }
  202.             else
  203.             {
  204.                 if (throttleInfo.RequestCount + 1 <= requestsLimit)
  205.                 {
  206.                     if (!throttles.TryUpdate(throttleKey, new ThrottleInfo
  207.                     {
  208.                         FirstRequestTime = throttleInfo.FirstRequestTime,
  209.                         RequestCount = throttleInfo.RequestCount + 1
  210.                     }, throttleInfo))
  211.                     {
  212.                         return ValidateThrottle(requestsLimit, throttles[throttleKey], interval, throttles,
  213.                             throttleKey);
  214.                     }
  215.                     return true;
  216.                 }
  217.  
  218.                 return false;
  219.             }
  220.         }
  221.     }
Add Comment
Please, Sign In to add comment