1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::client::Context;
use crate::model::id::{ChannelId, GuildId, UserId};
use futures::future::BoxFuture;
use std::collections::HashMap;
use std::time::{Duration, Instant};

type Check =
    for<'fut> fn(&'fut Context, Option<GuildId>, ChannelId, UserId) -> BoxFuture<'fut, bool>;

pub(crate) struct Ratelimit {
    pub delay: Duration,
    pub limit: Option<(Duration, u32)>,
}

#[derive(Default)]
pub(crate) struct MemberRatelimit {
    pub last_time: Option<Instant>,
    pub set_time: Option<Instant>,
    pub tickets: u32,
}

pub(crate) struct Bucket {
    pub ratelimit: Ratelimit,
    pub users: HashMap<u64, MemberRatelimit>,
    pub check: Option<Check>,
}

impl Bucket {
    pub fn take(&mut self, user_id: u64) -> Option<Duration> {
        let now = Instant::now();
        let Self {
            users, ratelimit, ..
        } = self;
        let user = users.entry(user_id).or_default();

        if let Some((timespan, limit)) = ratelimit.limit {
            if (user.tickets + 1) > limit {
                if let Some(res) = user
                    .set_time
                    .and_then(|x| (x + timespan).checked_duration_since(now))
                {
                    return Some(res);
                } else {
                    user.tickets = 0;
                    user.set_time = Some(now);
                }
            }
        }

        if let Some(res) = user
            .last_time
            .and_then(|x| (x + ratelimit.delay).checked_duration_since(now))
        {
            return Some(res);
        } else {
            user.tickets += 1;
            user.last_time = Some(now);
        }

        None
    }
}

#[derive(Default)]
pub struct BucketBuilder {
    pub(crate) delay: Duration,
    pub(crate) time_span: Duration,
    pub(crate) limit: u32,
    pub(crate) check: Option<Check>,
}

impl BucketBuilder {
    /// The "break" time between invocations of a command.
    ///
    /// Expressed in seconds.
    #[inline]
    pub fn delay(&mut self, secs: u64) -> &mut Self {
        self.delay = Duration::from_secs(secs);

        self
    }

    /// How long the bucket will apply for.
    ///
    /// Expressed in seconds.
    #[inline]
    pub fn time_span(&mut self, secs: u64) -> &mut Self {
        self.time_span = Duration::from_secs(secs);

        self
    }

    /// Number of invocations allowed per [`time_span`].
    ///
    /// Expressed in seconds.
    ///
    /// [`time_span`]: #method.time_span
    #[inline]
    pub fn limit(&mut self, n: u32) -> &mut Self {
        self.limit = n;

        self
    }

    /// Middleware confirming (or denying) that the bucket is eligible to apply.
    /// For instance, to limit the bucket to just one user.
    #[inline]
    pub fn check(&mut self, f: Check) -> &mut Self {
        self.check = Some(f);

        self
    }
}