53 lines
1.3 KiB
Go
53 lines
1.3 KiB
Go
package qq_obv11
|
|
|
|
// This file implements the sliding-window send rate limiter that keeps QQ
|
|
// OneBot proactive/reply traffic from bursting (see DevDocs/design/08 section
|
|
// 3, "注意风控").
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// ErrRateLimited is returned by SendMessage/SendProactive when the configured
|
|
// per-window send quota is exhausted.
|
|
var ErrRateLimited = errors.New("qq-obv11: send rate limit exceeded")
|
|
|
|
// rateLimiter enforces a maximum number of events within a sliding window. It
|
|
// is disabled when max is non-positive.
|
|
type rateLimiter struct {
|
|
max int
|
|
window time.Duration
|
|
mu sync.Mutex
|
|
times []time.Time
|
|
}
|
|
|
|
func newRateLimiter(max int, window time.Duration) *rateLimiter {
|
|
return &rateLimiter{max: max, window: window}
|
|
}
|
|
|
|
// allow records a send at now and reports whether it fits within the window
|
|
// quota. A disabled limiter (max <= 0) always allows. The timestamp is only
|
|
// recorded when the send is accepted.
|
|
func (r *rateLimiter) allow(now time.Time) bool {
|
|
if r == nil || r.max <= 0 {
|
|
return true
|
|
}
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
|
|
cutoff := now.Add(-r.window)
|
|
i := 0
|
|
for i < len(r.times) && !r.times[i].After(cutoff) {
|
|
i++
|
|
}
|
|
r.times = r.times[i:]
|
|
|
|
if len(r.times) >= r.max {
|
|
return false
|
|
}
|
|
r.times = append(r.times, now)
|
|
return true
|
|
}
|