Browse Source

Add option to truncate overlong messages instead of splitting them

master
JustAnotherArchivist 4 years ago
parent
commit
98f8821fda
2 changed files with 25 additions and 9 deletions
  1. +2
    -0
      config.example.toml
  2. +23
    -9
      http2irc.py

+ 2
- 0
config.example.toml View File

@@ -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'

+ 23
- 9
http2irc.py View File

@@ -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):


Loading…
Cancel
Save