From 774f6d56362dbbca11662e8cea4950f8c554b5dd Mon Sep 17 00:00:00 2001 From: Lars Kellogg-Stedman Date: Sun, 4 Oct 2015 21:30:10 -0400 Subject: [PATCH] config, bot: Configurable message rate 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 Co-authored-by: dgw --- sopel/bot.py | 35 +++++++++++++++++++++++------------ sopel/config/core_section.py | 13 +++++++++++++ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/sopel/bot.py b/sopel/bot.py index 7e0fefc866..7872c220ae 100644 --- a/sopel/bot.py +++ b/sopel/bot.py @@ -305,18 +305,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 '...' @@ -327,8 +337,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 diff --git a/sopel/config/core_section.py b/sopel/config/core_section.py index b08fdbcf13..ec96b871f1 100644 --- a/sopel/config/core_section.py +++ b/sopel/config/core_section.py @@ -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."""