aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar JustAnotherArchivist2020-05-13 15:00:54 +0000
committerGravatar JustAnotherArchivist2020-05-13 15:00:54 +0000
commit98f8821fda0488685d6e36d1b70510444487abec (patch)
tree4fe67a2fc16d692c80055db76658f7b178a2b158
parentAdd Grafana module (diff)
signature
Add option to truncate overlong messages instead of splitting them
-rw-r--r--config.example.toml2
-rw-r--r--http2irc.py32
2 files changed, 25 insertions, 9 deletions
diff --git a/config.example.toml b/config.example.toml
index 6bfa3a1..9d6c483 100644
--- a/config.example.toml
+++ b/config.example.toml
@@ -26,3 +26,5 @@
#module =
# moduleargs are additional arguments to be passed into the module's process function after the request object. Example use: Gitea webhook secret key
#moduleargs = []
+ # overlongmode determines what happens to messages that are too long to be sent to the channel. The value may be 'split' (split into multiple messages on spaces or codepoints) or 'truncate' (truncate everything exceeding the limit).
+ #overlongmode = 'split'
diff --git a/http2irc.py b/http2irc.py
index 60c0a5c..16e02e3 100644
--- a/http2irc.py
+++ b/http2irc.py
@@ -121,7 +121,7 @@ class Config(dict):
raise InvalidConfig(f'Invalid map key {key!r}')
if not isinstance(map_, collections.abc.Mapping):
raise InvalidConfig(f'Invalid map for {key!r}')
- if any(x not in ('webpath', 'ircchannel', 'auth', 'module', 'moduleargs') for x in map_):
+ if any(x not in ('webpath', 'ircchannel', 'auth', 'module', 'moduleargs', 'overlongmode') for x in map_):
raise InvalidConfig(f'Unknown key(s) found in map {key!r}')
if 'webpath' not in map_:
@@ -158,6 +158,11 @@ class Config(dict):
raise InvalidConfig(f'Invalid module args for {key!r}: not an array')
if 'module' not in map_:
raise InvalidConfig(f'Module args cannot be specified without a module for {key!r}')
+ if 'overlongmode' in map_:
+ if not isinstance(map_['overlongmode'], str):
+ raise InvalidConfig(f'Invalid map {key!r} overlongmode: not a string')
+ if map_['overlongmode'] not in ('split', 'truncate'):
+ raise InvalidConfig(f'Invalid map {key!r} overlongmode: unsupported value')
# Default values
finalObj = {'logging': {'level': 'INFO', 'format': '{asctime} {levelname} {name} {message}'}, 'irc': {'host': 'irc.hackint.org', 'port': 6697, 'ssl': 'yes', 'nick': 'h2ibot', 'real': 'I am an http2irc bot.', 'certfile': None, 'certkeyfile': None}, 'web': {'host': '127.0.0.1', 'port': 8080}, 'maps': {}}
@@ -172,6 +177,8 @@ class Config(dict):
map_['module'] = None
if 'moduleargs' not in map_:
map_['moduleargs'] = []
+ if 'overlongmode' not in map_:
+ map_['overlongmode'] = 'split'
# Load modules
modulePaths = {} # path: str -> (extraargs: int, key: str)
@@ -386,7 +393,7 @@ class IRCClientProtocol(asyncio.Protocol):
async def send_messages(self):
while self.connected:
self.logger.debug(f'Trying to get a message')
- channel, message = await self._get_message()
+ channel, message, overlongmode = await self._get_message()
self.logger.debug(f'Got message: {message!r}')
if message is None:
break
@@ -394,13 +401,17 @@ class IRCClientProtocol(asyncio.Protocol):
messageB = message.encode('utf-8')
usermaskPrefixLength = 1 + (len(self.usermask) if self.usermask else 100) + 1
if usermaskPrefixLength + len(b'PRIVMSG ' + channelB + b' :' + messageB) > 510:
- self.logger.debug(f'Splitting up into smaller messages')
- # Message too long, need to split. First try to split on spaces, then on codepoints. Ideally, would use graphemes between, but that's too complicated.
+ # Message too long, need to split or truncate. First try to split on spaces, then on codepoints. Ideally, would use graphemes between, but that's too complicated.
+ self.logger.debug(f'Message too long, overlongmode = {overlongmode}')
prefix = b'PRIVMSG ' + channelB + b' :'
prefixLength = usermaskPrefixLength + len(prefix) # Need to account for the origin prefix included by the ircd when sending to others
maxMessageLength = 510 - prefixLength # maximum length of the message part within each line
+ if overlongmode == 'truncate':
+ maxMessageLength -= 3 # Make room for an ellipsis at the end
messages = []
while message:
+ if overlongmode == 'truncate' and messages:
+ break # Only need the first message on truncation
if len(messageB) <= maxMessageLength:
messages.append(message)
break
@@ -425,8 +436,11 @@ class IRCClientProtocol(asyncio.Protocol):
messages.append(message[:cutoffIndex])
message = message[cutoffIndex:]
messageB = message.encode('utf-8')
- for msg in reversed(messages):
- self.messageQueue.putleft_nowait((channel, msg))
+ if overlongmode == 'split':
+ for msg in reversed(messages):
+ self.messageQueue.putleft_nowait((channel, msg, overlongmode))
+ elif overlongmode == 'truncate':
+ self.messageQueue.putleft_nowait((channel, messages[0] + '…', overlongmode))
else:
self.logger.info(f'Sending {message!r} to {channel!r}')
self.unconfirmedMessages.append((channel, message))
@@ -633,7 +647,7 @@ class WebServer:
self._configChanged = asyncio.Event()
def update_config(self, config):
- self._paths = {map_['webpath']: (map_['ircchannel'], f'Basic {base64.b64encode(map_["auth"].encode("utf-8")).decode("utf-8")}' if map_['auth'] else False, map_['module'], map_['moduleargs']) for map_ in config['maps'].values()}
+ self._paths = {map_['webpath']: (map_['ircchannel'], f'Basic {base64.b64encode(map_["auth"].encode("utf-8")).decode("utf-8")}' if map_['auth'] else False, map_['module'], map_['moduleargs'], map_['overlongmode']) for map_ in config['maps'].values()}
needRebind = self.config['web'] != config['web']
self.config = config
if needRebind:
@@ -654,7 +668,7 @@ class WebServer:
async def post(self, request):
self.logger.info(f'Received request {id(request)} from {request.remote!r} for {request.path!r} with body {(await request.read())!r}')
try:
- channel, auth, module, moduleargs = self._paths[request.path]
+ channel, auth, module, moduleargs, overlongmode = self._paths[request.path]
except KeyError:
self.logger.info(f'Bad request {id(request)}: no path {request.path!r}')
raise aiohttp.web.HTTPNotFound()
@@ -679,7 +693,7 @@ class WebServer:
self.logger.debug(f'Processing request {id(request)} using default processor')
message = await self._default_process(request)
self.logger.info(f'Accepted request {id(request)}, putting message {message!r} for {channel} into message queue')
- self.messageQueue.put_nowait((channel, message))
+ self.messageQueue.put_nowait((channel, message, overlongmode))
raise aiohttp.web.HTTPOk()
async def _default_process(self, request):