use rand::Rng;
use std::time::Duration;
use tokio::time::sleep;
#[derive(Debug, Clone)]
pub struct IdsFriendlyConfig {
pub min_delay_ms: u64,
pub max_delay_ms: u64,
pub randomize: bool,
pub max_requests_per_minute: usize,
}
impl Default for IdsFriendlyConfig {
fn default() -> Self {
Self {
min_delay_ms: 1000, max_delay_ms: 3000, randomize: true,
max_requests_per_minute: 20,
}
}
}
impl IdsFriendlyConfig {
pub fn new(min_delay_ms: u64, max_delay_ms: u64) -> Self {
Self {
min_delay_ms,
max_delay_ms,
randomize: true,
max_requests_per_minute: 20,
}
}
pub fn conservative() -> Self {
Self {
min_delay_ms: 3000, max_delay_ms: 10000, randomize: true,
max_requests_per_minute: 6,
}
}
pub fn moderate() -> Self {
Self {
min_delay_ms: 500, max_delay_ms: 2000, randomize: true,
max_requests_per_minute: 30,
}
}
pub fn aggressive() -> Self {
Self {
min_delay_ms: 100, max_delay_ms: 500, randomize: true,
max_requests_per_minute: 60,
}
}
}
pub struct IdsFriendlyLimiter {
config: IdsFriendlyConfig,
request_count: usize,
last_reset: std::time::Instant,
}
impl IdsFriendlyLimiter {
pub fn new(config: IdsFriendlyConfig) -> Self {
Self {
config,
request_count: 0,
last_reset: std::time::Instant::now(),
}
}
pub async fn wait(&mut self) {
if self.last_reset.elapsed() >= Duration::from_secs(60) {
self.request_count = 0;
self.last_reset = std::time::Instant::now();
}
if self.request_count >= self.config.max_requests_per_minute {
let elapsed = self.last_reset.elapsed();
if elapsed < Duration::from_secs(60) {
let wait_time = Duration::from_secs(60) - elapsed;
sleep(wait_time).await;
}
self.request_count = 0;
self.last_reset = std::time::Instant::now();
}
let delay_ms = if self.config.randomize {
let mut rng = rand::thread_rng();
rng.gen_range(self.config.min_delay_ms..=self.config.max_delay_ms)
} else {
self.config.min_delay_ms
};
sleep(Duration::from_millis(delay_ms)).await;
self.request_count += 1;
}
pub fn remaining_requests(&self) -> usize {
self.config
.max_requests_per_minute
.saturating_sub(self.request_count)
}
pub fn would_exceed_limit(&self) -> bool {
self.request_count >= self.config.max_requests_per_minute
&& self.last_reset.elapsed() < Duration::from_secs(60)
}
}
pub async fn random_delay(min_ms: u64, max_ms: u64) {
let mut rng = rand::thread_rng();
let delay_ms = rng.gen_range(min_ms..=max_ms);
sleep(Duration::from_millis(delay_ms)).await;
}
pub async fn fixed_delay(ms: u64) {
sleep(Duration::from_millis(ms)).await;
}
pub fn randomize_order<T>(items: &mut [T]) {
use rand::seq::SliceRandom;
let mut rng = rand::thread_rng();
items.shuffle(&mut rng);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_presets() {
let conservative = IdsFriendlyConfig::conservative();
assert!(conservative.min_delay_ms >= 3000);
let moderate = IdsFriendlyConfig::moderate();
assert!(moderate.min_delay_ms >= 500);
let aggressive = IdsFriendlyConfig::aggressive();
assert!(aggressive.min_delay_ms >= 100);
}
#[test]
fn test_limiter_tracking() {
let config = IdsFriendlyConfig::default();
let limiter = IdsFriendlyLimiter::new(config);
assert_eq!(limiter.request_count, 0);
assert_eq!(limiter.remaining_requests(), 20);
}
#[test]
fn test_randomize_order() {
let mut items = vec![1, 2, 3, 4, 5];
let original = items.clone();
randomize_order(&mut items);
assert_eq!(items.len(), original.len());
for item in &original {
assert!(items.contains(item));
}
}
}