diff options
| author | 2026-06-27 03:40:13 +0000 | |
|---|---|---|
| committer | 2026-06-27 03:40:13 +0000 | |
| commit | dd466410791a947e2acfb2858b4e7e45f63c1580 (patch) | |
| tree | c291b1d7501567cf4093aad2c6379f1b9f3a3200 | |
| parent | Trim spaces from CAP responses (diff) | |
Allow setting global message sending rate limit settable-global-ratelimit
| -rw-r--r-- | config.example.toml | 2 | ||||
| -rw-r--r-- | http2irc.py | 19 |
2 files changed, 16 insertions, 5 deletions
diff --git a/config.example.toml b/config.example.toml index 59a9f09..219c1bd 100644 --- a/config.example.toml +++ b/config.example.toml @@ -11,6 +11,8 @@ #family = #nick = 'h2ibot' #real = 'I am an http2irc bot.' + #sendratelimit = 10 + # Set to the number of lines per second you want the bot to be able to send globally. # Certificate and key for SASL EXTERNAL authentication with NickServ; certfile is a string containing the path to a .pem file which has the certificate and the key, certkeyfile similarly for one containing only the key; default values are empty (None in Python) to disable authentication; the connection is terminated if authentication fails #certfile = #certkeyfile = diff --git a/http2irc.py b/http2irc.py index da47dc2..5294923 100644 --- a/http2irc.py +++ b/http2irc.py @@ -104,7 +104,7 @@ class Config(dict): except (ValueError, AssertionError) as e: raise InvalidConfig('Invalid log format: parsing failed') from e if 'irc' in obj: - if any(x not in ('host', 'port', 'ssl', 'family', 'nick', 'real', 'certfile', 'certkeyfile') for x in obj['irc']): + if any(x not in ('host', 'port', 'ssl', 'family', 'nick', 'real', 'certfile', 'certkeyfile', 'sendratelimit') for x in obj['irc']): raise InvalidConfig('Unknown key found in irc section') if 'host' in obj['irc'] and not isinstance(obj['irc']['host'], str): #TODO: Check whether it's a valid hostname raise InvalidConfig('Invalid IRC host') @@ -126,6 +126,9 @@ class Config(dict): raise InvalidConfig('Invalid IRC nick: NICK command too long') if 'real' in obj['irc'] and not isinstance(obj['irc']['real'], str): raise InvalidConfig('Invalid IRC realname') + if 'sendratelimit' in obj['irc']: + if not isinstance(obj['irc']['sendratelimit'], (int, float)): + raise InvalidConfig('Invalid ratelimit: must be numeric') if len(IRCClientProtocol.user_command(obj['irc']['nick'], obj['irc']['real'])) > 510: raise InvalidConfig('Invalid IRC nick/realname combination: USER command too long') if ('certfile' in obj['irc']) != ('certkeyfile' in obj['irc']): @@ -225,7 +228,7 @@ class Config(dict): # Default values finalObj = { 'logging': {'level': 'INFO', 'format': '{asctime} {levelname} {name} {message}'}, - 'irc': {'host': 'irc.hackint.org', 'port': 6697, 'ssl': 'yes', 'family': 0, 'nick': 'h2ibot', 'real': 'I am an http2irc bot.', 'certfile': None, 'certkeyfile': None}, + 'irc': {'host': 'irc.hackint.org', 'port': 6697, 'ssl': 'yes', 'family': 0, 'nick': 'h2ibot', 'real': 'I am an http2irc bot.', 'certfile': None, 'certkeyfile': None, 'sendratelimit': 1 }, 'web': {'host': '127.0.0.1', 'port': 8080, 'maxrequestsize': 1048576}, 'maps': {} } @@ -484,9 +487,10 @@ class IRCClientProtocol(asyncio.Protocol): data = t.result() self.logger.debug(f'Got {data!r} from send queue') now = time.time() - if self.lastSentTime is not None and now - self.lastSentTime < 1: + sendTimeLimit = 1 / self.config['irc']['sendratelimit'] + if self.lastSentTime is not None and now - self.lastSentTime < sendTimeLimit: self.logger.debug(f'Rate limited') - await wait_cancel_pending({asyncio.create_task(self.connectionClosedEvent.wait())}, timeout = self.lastSentTime + 1 - now) + await wait_cancel_pending({asyncio.create_task(self.connectionClosedEvent.wait())}, timeout = self.lastSentTime + sendTimeLimit - now) if self.connectionClosedEvent.is_set(): break time_ = self._direct_send(data) @@ -871,7 +875,11 @@ class IRCClient: self._protocol = None def update_config(self, config): - needReconnect = self.config['irc'] != config['irc'] + ircReconnectNeededKeys = [ + 'host', 'port', 'ssl', 'family', + 'nick', 'real', 'certfile', 'certkeyfile', + ] + needReconnect = True in [self.config['irc'][key] != config['irc'][key] for key in ircReconnectNeededKeys] self.config = config if self._transport: # if currently connected: if needReconnect: @@ -879,6 +887,7 @@ class IRCClient: else: self.channels = {map_['ircchannel'] for map_ in config['maps'].values()} self._protocol.update_channels(self.channels) + self._protocol.config['irc'] = config['irc'] def _get_ssl_context(self): ctx = SSL_CONTEXTS[self.config['irc']['ssl']] |
