--- /dev/null
+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
--- /dev/null
+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
+ }
#!/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)
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") )
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
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")