Skip to content

Commit

Permalink
config, bot: Configurable message rate
Browse files Browse the repository at this point in the history
Original commit message:
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.
  • Loading branch information
larsks authored and kwaaak committed Mar 25, 2019
1 parent f3f27e3 commit 8ec333c
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 @@ -307,18 +307,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 = 0.8 + penalty
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 = self.config.core.bucket_empty_wait + penalty
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 @@ -329,8 +339,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 @@ -216,3 +216,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 8ec333c

Please sign in to comment.