Skip to content

Commit

Permalink
config, bot: Configurable message rate
Browse files Browse the repository at this point in the history
Original change description by @larsks (from #908, slightly edited):

This adds several knobs that control how fast the bot sends multiple
messages to the same recipient:

- bucket_burst_tokens -- this controls how many messages may be sent in
  "burst mode", i.e., without any inter-message delay.
- bucket_refill_rate -- once exhausted, burst tokens are refilled at a
  rate of bucket_refill_rate tokens/second.
- bucket_empty_wait -- how long to wait between sending messages when
  not in burst mode.

This permits the bot to return a few lines of information quickly and
not trigger flood protection.

How it works:

When sending to a new recipient, we initialize a token counter to
bucket_burst_tokens. Every time we send a message, we decrement this by
1. If the token counter reaches 0, we engage the rate limiting behavior
(which is identical to the existing code, except that the minimum wait
of 0.7 seconds is now configurable).

When sending a new message to an existing recipient, we check if the
token counter is exhausted. If it is, we refill it based on the elapsed
time since the last message and the value of bucket_refill_rate, and
then proceed as described above.

----

Adapted to current Sopel code and rebased by these users, respectively:

Co-authored-by: kwaaak <kwaaak@users.noreply.github.com>
Co-authored-by: dgw <dgw@technobabbl.es>
  • Loading branch information
3 people committed May 13, 2019
1 parent 54d451f commit 282d332
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 12 deletions.
35 changes: 23 additions & 12 deletions sopel/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,18 +397,28 @@ def say(self, text, recipient, max_messages=1):

recipient_id = Identifier(recipient)

if recipient_id not in self.stack:
self.stack[recipient_id] = []
elif self.stack[recipient_id]:
elapsed = time.time() - self.stack[recipient_id][-1][0]
if elapsed < 3:
penalty = float(max(0, len(text) - 40)) / 70
wait = min(0.8 + penalty, 2) # Never wait more than 2 seconds
if elapsed < wait:
time.sleep(wait - elapsed)
reciprec = self.stack.get(recipient_id)
if not reciprec:
reciprec = self.stack[recipient_id] = {
'messages': [],
'burst': self.config.core.bucket_burst_tokens,
}

if not reciprec['burst']:
elapsed = time.time() - reciprec['messages'][-1][0]
reciprec['burst'] = min(
self.config.core.bucket_burst_tokens,
int(elapsed) * self.config.core.bucket_refill_rate)

if not reciprec['burst']:
elapsed = time.time() - reciprec['messages'][-1][0]
penalty = float(max(0, len(text) - 50)) / 70
wait = min(self.config.core.flood_empty_wait + penalty, 2) # Maximum wait time is 2 sec
if elapsed < wait:
time.sleep(wait - elapsed)

# Loop detection
messages = [m[1] for m in self.stack[recipient_id][-8:]]
messages = [m[1] for m in reciprec['messages'][-8:]]

# If what we about to send repeated at least 5 times in the
# last 2 minutes, replace with '...'
Expand All @@ -419,8 +429,9 @@ def say(self, text, recipient, max_messages=1):
return

self.write(('PRIVMSG', recipient), text)
self.stack[recipient_id].append((time.time(), self.safe(text)))
self.stack[recipient_id] = self.stack[recipient_id][-10:]
reciprec['burst'] = max(0, reciprec['burst'] - 1)
reciprec['messages'].append((time.time(), self.safe(text)))
reciprec['messages'] = reciprec['messages'][-10:]
finally:
self.sending.release()
# Now that we've sent the first part, we need to send the rest. Doing
Expand Down
13 changes: 13 additions & 0 deletions sopel/config/core_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,16 @@ def homedir(self):

verify_ssl = ValidatedAttribute('verify_ssl', bool, default=True)
"""Whether to require a trusted SSL certificate for SSL connections."""

bucket_burst_tokens = ValidatedAttribute('bucket_burst_tokens', int,
default=4)
"""How many messages can be sent in burst mode."""

bucket_refill_rate = ValidatedAttribute('bucket_refill_rate', int,
default=1)
"""How many tokens/second to add to the token bucket."""

bucket_empty_wait = ValidatedAttribute('bucket_empty_wait', float,
default=0.7)
"""How long to wait before sending a messaging when not in burst
mode."""

0 comments on commit 282d332

Please sign in to comment.