From: Alexander Schmidt Date: Sun, 4 Oct 2020 22:21:51 +0000 (+0200) Subject: support html/markdown/plain messages of arbitrary length X-Git-Url: http://git.treefish.org/~alex/mtxbot.git/commitdiff_plain/732dbf2c4afa69b93f9e7e05d3d46d334059d00e?ds=inline;hp=6c4dbc2edd8bf5cac128c2fa48728d8abe52432e support html/markdown/plain messages of arbitrary length --- diff --git a/src/inputparser.py b/src/inputparser.py new file mode 100644 index 0000000..9adbbc6 --- /dev/null +++ b/src/inputparser.py @@ -0,0 +1,32 @@ +from message import Message + +class InputParser: + def __init__(self): + self._messages = {} + self._decoded = [] + + def feed_line(self, line): + try: + msg_id, msg_fragment = line.rstrip("\n").split(' ', 1) + except: + raise Exception("Invalid fragment!") + + try: + if msg_id in self._messages: + self._messages[msg_id].feed_fragment(msg_fragment) + else: + self._messages[msg_id] = Message(msg_fragment) + + if self._messages[msg_id].is_complete(): + self._decoded.append( self._messages[msg_id].get_content() ) + del self._messages[msg_id] + + except Exception as e: + if msg_id in self._messages: + del self._messages[msg_id] + raise e + + def fetch_decoded(self): + decoded = self._decoded + self._decoded = [] + return decoded diff --git a/src/message.py b/src/message.py new file mode 100644 index 0000000..d66b6ef --- /dev/null +++ b/src/message.py @@ -0,0 +1,74 @@ +import base64 +import html2text +from enum import Enum + +class Message: + class Format(Enum): + PLAIN = 0 + HTML = 1 + + def __init__(self, fragment): + self._next_fragment = 0 + self._raw_content = "" + + parts = fragment.split() + + if len(parts) != 2: + raise Exception("Invalid fragment!") + + if parts[0] == "PLAIN": + self._msg_fmt = Message.Format.PLAIN + elif parts[0] == "HTML": + self._msg_fmt = Message.Format.HTML + else: + raise Exception("Unknown message type %s!" % msg_fmt) + + try: + self._msg_len = int(parts[1]) + except: + raise Exception("Invalid message length %s!" % parts[1]) + + if self._msg_len < 1 or self._msg_len > 1000: + raise Exception("Invalid message length %d!" % self._msg_len) + + def feed_fragment(self, fragment): + parts = fragment.split() + + if len(parts) != 2: + raise Exception("Invalid fragment!") + + try: + fragment_number = int(parts[0]) + except: + raise Exception("Invalid fragment number %s!" % parts[0]) + + if fragment_number == self._next_fragment and fragment_number < self._msg_len: + self._next_fragment += 1 + else: + raise Exception("Unexpected fragment!") + + self._raw_content += parts[1] + + def is_complete(self): + return self._next_fragment == self._msg_len + + def get_content(self): + try: + msgB64Bytes = self._raw_content.encode("UTF-8") + msgBytes = base64.b64decode(msgB64Bytes) + msgStr = msgBytes.decode("UTF-8") + except: + raise Exception("Message decoding error!") + + if self._msg_fmt == Message.Format.PLAIN: + return { + "msgtype": "m.text", + "body": msgStr, + } + elif self._msg_fmt == Message.Format.HTML: + return { + "format": "org.matrix.custom.html", + "msgtype": "m.text", + "body": html2text.html2text(msgStr), + "formatted_body": msgStr + } diff --git a/src/mtxbot-post.py b/src/mtxbot-post.py index 3e97ef2..f7e5365 100755 --- a/src/mtxbot-post.py +++ b/src/mtxbot-post.py @@ -1,21 +1,47 @@ #!/usr/bin/env python3 +import argparse import base64 import errno +import markdown2 import os import posix +import random import stat +import string import sys import time assert sys.version_info >= (3, 5) -if len(sys.argv) != 2: - print("Usage: %s " % sys.argv[0]) - sys.exit(1) +FRAG_LEN = 1024 + +def send_line(line): + for i in range(0, 10): + fifo = -1 + try: + fifo = posix.open(fifo_path, posix.O_WRONLY | posix.O_NONBLOCK) + posix.write(fifo, line) + return + except OSError as e: + if e.errno == errno.ENXIO: + time.sleep(1.0) + finally: + if fifo != -1: + posix.close(fifo) + raise Exception("Error sending line!") + +parser = argparse.ArgumentParser(description='Post message using Matrix Bot.') +parser.add_argument('channel', type=str, help='channel to be used for posting') +parser.add_argument('--html', action='store_true', dest='is_html', + default=False, help='post html message') +parser.add_argument('--markdown', action='store_true', dest='is_markdown', + default=False, help='post markdown message') + +args = parser.parse_args() fifo_dir = os.getenv('MTXBOT_FIFO_DIR', '/run/mtxbot') -fifo_path = "%s/%s" % (fifo_dir, sys.argv[1]) +fifo_path = "%s/%s" % (fifo_dir, args.channel) if not os.path.isdir(fifo_dir): print("Fifo directory %s does not exist!" % fifo_dir, file=sys.stderr) @@ -23,24 +49,21 @@ if not os.path.isdir(fifo_dir): if not ( os.path.exists(fifo_path) and stat.S_ISFIFO(os.stat(fifo_path).st_mode) ): - print("Channel %s does not exist!" % sys.argv[1], file=sys.stderr) + print("Channel %s does not exist!" % args.channel, file=sys.stderr) sys.exit(3) -inBytes = sys.stdin.read().encode("UTF-8") -inB64 = base64.b64encode(inBytes) - -for i in range(0, 10): - fifo = -1 - try: - fifo = posix.open(fifo_path, posix.O_WRONLY | posix.O_NONBLOCK) - posix.write(fifo, inB64) - sys.exit(0) - except OSError as e: - if e.errno == errno.ENXIO: - time.sleep(1.0) - finally: - if fifo != -1: - posix.close(fifo) - -print("Error posting to channel %s!" % sys.argv[1], file=sys.stderr) -sys.exit(4) +in_raw = sys.stdin.read() + +if args.is_markdown: + in_raw = markdown2.markdown(in_raw) + +in_bytes = in_raw.encode("UTF-8") +in_b64 = base64.b64encode(in_bytes).decode("UTF-8") + +msg_id = ''.join(random.choices(string.ascii_uppercase + string.digits, k=5)) +msg_fmt = 'HTML' if (args.is_html or args.is_markdown) else 'PLAIN' +msg_len = len(in_b64) // FRAG_LEN + (0 if len(in_b64) % FRAG_LEN == 0 else 1) + +send_line( ("%s %s %d\n" % (msg_id, msg_fmt, msg_len)).encode("UTF-8") ) +for i in range(0, len(in_b64), FRAG_LEN): + send_line( ("%s %d %s\n" % (msg_id, i//FRAG_LEN, in_b64[i:i+FRAG_LEN])).encode("UTF-8") ) diff --git a/src/presence.py b/src/presence.py index 7430ee8..d9b6529 100644 --- a/src/presence.py +++ b/src/presence.py @@ -1,12 +1,14 @@ import aiofiles import asyncio -import base64 import errno import logging import posix +from inputparser import InputParser + class Presence: def __init__(self, room_name, fifo_path): + self._input_parser = InputParser() self._room_name = room_name self._fifo_path = fifo_path self._joined_room_id = None @@ -70,20 +72,16 @@ class Presence: break if self._joined_room_id != None: try: - msgB64Bytes = line.rstrip("\n").encode("UTF-8") - msgBytes = base64.b64decode(msgB64Bytes) - msgStr = msgBytes.decode("UTF-8") - except: - self._log(logging.WARNING, "Error decoding message") + self._input_parser.feed_line(line) + except Exception as e: + self._log(logging.WARNING, "Error parsing input: %s" % str(e)) continue - await client.room_send( - room_id=self._joined_room_id, - message_type="m.room.message", - content={ - "msgtype": "m.text", - "body": msgStr - } - ) + for content in self._input_parser.fetch_decoded(): + await client.room_send( + room_id=self._joined_room_id, + message_type="m.room.message", + content=content + ) else: self._log(logging.WARNING, "Dropping message cause no room joined")