Class: Whatsapp::RateLimiter
- Inherits:
-
Object
- Object
- Whatsapp::RateLimiter
- Defined in:
- lib/whatsapp/rate_limiter.rb
Overview
Anti-ban rate limiter with gaussian jitter.
Enforces conservative message rate limits to protect personal WhatsApp accounts from being banned. Uses a gaussian (Box-Muller) jitter to simulate human-like sending patterns.
Protections:
- Multi-level rate limits (per-minute, per-hour, per-day, per-recipient)
- New chat limit per day
- Post-connect delay before first message
- Ramp-up after idle (warmup penalty)
- Progressive account warmup (day 1-N with linearly increasing limits)
Instance Attribute Summary collapse
-
#config ⇒ Configuration
readonly
Current configuration.
Instance Method Summary collapse
-
#acquire!(recipient, unsafe: false) ⇒ Float
Acquire permission to send a message.
-
#initialize(config = Whatsapp.configuration) ⇒ RateLimiter
constructor
A new instance of RateLimiter.
-
#mark_connected! ⇒ void
Signal that the WhatsApp connection was just established.
-
#reset! ⇒ void
Clear all rate limiting state.
-
#safety_status ⇒ Hash
Safety status summary for monitoring and dashboards.
-
#stats ⇒ Hash
Current rate limiting statistics.
Constructor Details
#initialize(config = Whatsapp.configuration) ⇒ RateLimiter
Returns a new instance of RateLimiter.
24 25 26 27 28 29 30 31 32 |
# File 'lib/whatsapp/rate_limiter.rb', line 24 def initialize(config = Whatsapp.configuration) @config = config @mutex = Mutex.new @global_log = [] @per_recipient = Hash.new { |h, k| h[k] = [] } @new_chats_today = [] @connected_at = nil @warmup_remaining = 0 end |
Instance Attribute Details
#config ⇒ Configuration (readonly)
Returns current configuration.
21 22 23 |
# File 'lib/whatsapp/rate_limiter.rb', line 21 def config @config end |
Instance Method Details
#acquire!(recipient, unsafe: false) ⇒ Float
Acquire permission to send a message.
Checks all rate limits, sleeps with human-like jitter, and records the send. Blocks the calling thread.
53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 |
# File 'lib/whatsapp/rate_limiter.rb', line 53 def acquire!(recipient, unsafe: false) @mutex.synchronize do now = Time.now cleanup!(now) if unsafe delay = 0.0 else check_limits!(recipient, now) delay = compute_delay(recipient, now) sleep(delay) if delay > 0 end record!(recipient, now) delay end end |
#mark_connected! ⇒ void
This method returns an undefined value.
Signal that the WhatsApp connection was just established. Starts the post-connect delay window.
38 39 40 41 42 |
# File 'lib/whatsapp/rate_limiter.rb', line 38 def mark_connected! @mutex.synchronize do @connected_at = Time.now end end |
#reset! ⇒ void
This method returns an undefined value.
Clear all rate limiting state.
145 146 147 148 149 150 151 152 153 |
# File 'lib/whatsapp/rate_limiter.rb', line 145 def reset! @mutex.synchronize do @global_log.clear @per_recipient.clear @new_chats_today.clear @connected_at = nil @warmup_remaining = 0 end end |
#safety_status ⇒ Hash
Safety status summary for monitoring and dashboards.
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/whatsapp/rate_limiter.rb', line 95 def safety_status @mutex.synchronize do now = Time.now cleanup!(now) day_count = count_since(@global_log, now - 86_400) hour_count = count_since(@global_log, now - 3600) new_chats = @new_chats_today.size factor = warmup_factor current_day = warmup_day effective_day_limit = (config.rate_limit_per_day * factor).to_i effective_hour_limit = (config.rate_limit_per_hour * factor).to_i effective_new_chats_limit = (config.max_new_chats_per_day * factor).to_i day_usage = (effective_day_limit > 0) ? (day_count.to_f / effective_day_limit * 100).round(1) : 0.0 hour_usage = (effective_hour_limit > 0) ? (hour_count.to_f / effective_hour_limit * 100).round(1) : 0.0 new_chats_usage = (effective_new_chats_limit > 0) ? (new_chats.to_f / effective_new_chats_limit * 100).round(1) : 0.0 risk = if [day_usage, hour_usage, new_chats_usage].max >= 90 :high elsif [day_usage, hour_usage, new_chats_usage].max >= 60 :medium else :low end { risk: risk, preset: config.safety_preset, warmup_day: current_day, warmup_factor: factor, limits: { per_day: {used: day_count, limit: effective_day_limit, usage_pct: day_usage}, per_hour: {used: hour_count, limit: effective_hour_limit, usage_pct: hour_usage}, new_chats: {used: new_chats, limit: effective_new_chats_limit, usage_pct: new_chats_usage} }, features: { typing_simulation: config.typing_simulation, auto_read_receipts: config.auto_read_receipts, ramp_up_enabled: config.ramp_up_enabled, post_connect_delay: config.post_connect_delay } } end end |
#stats ⇒ Hash
Current rate limiting statistics.
74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 |
# File 'lib/whatsapp/rate_limiter.rb', line 74 def stats @mutex.synchronize do now = Time.now cleanup!(now) { last_minute: count_since(@global_log, now - 60), last_hour: count_since(@global_log, now - 3600), last_day: count_since(@global_log, now - 86_400), total: @global_log.size, recipients: @per_recipient.transform_values(&:size), new_chats_today: @new_chats_today.size, warmup_remaining: @warmup_remaining, warmup_day: warmup_day, warmup_factor: warmup_factor } end end |