]> git.treefish.org Git - mtxbot.git/commitdiff
support html/markdown/plain messages of arbitrary length
authorAlexander Schmidt <alex@treefish.org>
Sun, 4 Oct 2020 22:21:51 +0000 (00:21 +0200)
committerAlexander Schmidt <alex@treefish.org>
Sun, 4 Oct 2020 22:21:51 +0000 (00:21 +0200)
src/inputparser.py [new file with mode: 0644]
src/message.py [new file with mode: 0644]
src/mtxbot-post.py
src/presence.py

diff --git a/src/inputparser.py b/src/inputparser.py
new file mode 100644 (file)
index 0000000..9adbbc6
--- /dev/null
@@ -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 (file)
index 0000000..d66b6ef
--- /dev/null
@@ -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
+            }
index 3e97ef2043f68675abe6929881002f13f0afab5f..f7e536550ecc9432ced3f80b68b1ed9635b55485 100755 (executable)
@@ -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 <channel>" % 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") )
index 7430ee814f4a5e02b2138fed820473964ba16454..d9b65294ff68d397a5f5250c3a35fc73c8e39ba1 100644 (file)
@@ -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")