From 3d3f7d068161a3dc549eb88c0967f0916860580f Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 16 Jan 2019 17:29:49 -0800 Subject: [PATCH 01/50] Check for existing avatar entry to properly update fixes #39 --- pnut-matrix.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 5ff4464..8a1aee0 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -194,8 +194,12 @@ def set_matrix_avatar(user): try: matrix_api.set_avatar_url(matrix_id, ul['content_uri']) - avatar = Avatars(pnut_user=user.username, avatar=user.content.avatar_image.link) - db_session.add(avatar) + avatar = Avatars.query.filter(Avatars.pnut_user == user.username).one_or_none() + if avatar is None: + avatar = Avatars(pnut_user=user.username, avatar=user.content.avatar_image.link) + db_session.add(avatar) + else: + avatar.avatar = user.content.avatar_image.link db_session.commit() except MatrixRequestError: -- 2.45.2 From f6d1b74ad7f650492c275916cd27df539c8ff325 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 16 Jan 2019 17:37:08 -0800 Subject: [PATCH 02/50] adjust log entry for when error is recieved on the websocket --- pnut-matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 8a1aee0..128d6c4 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -258,7 +258,7 @@ def on_message(ws, message): new_message(pmsg) def on_error(ws, error): - logger.debug("on_error: !!! ERROR !!!") + logger.error("on_error: !!! ERROR !!!") logger.error(error) def on_close(ws): -- 2.45.2 From be10ec234696ddf766968f2158719e94cd8d67f0 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 16 Jan 2019 17:41:31 -0800 Subject: [PATCH 03/50] changelog update --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fead47f..52e585d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Fixed +- error checking if avatar changes ## [1.0.1] - 2019-01-09 ### Fixed -- 2.45.2 From febb765f81ccf2c1c088de52bac9b073cab5dc24 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 2 Feb 2019 11:50:56 -0800 Subject: [PATCH 04/50] - improved thread handling - automatic reconnect when closed remotely issue #40 --- pnut-matrix.py | 50 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 128d6c4..a631068 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -18,6 +18,7 @@ from appservice import app logger = logging.getLogger() _shutdown = threading.Event() +_reconnect = threading.Event() def new_message(msg): logger.debug("channel: " + msg.channel_id) @@ -263,33 +264,50 @@ def on_error(ws, error): def on_close(ws): logger.debug("on_close: ### CLOSED ###") - if not _shutdown.set(): - time.sleep(10) - t.start() - else: - time.sleep(2) def on_open(ws): def run(*args): - while not _shutdown.isSet(): + while not _shutdown.isSet() and not _reconnect.isSet(): time.sleep(3) - ws.send(".") + try: + ws.send(".") + except websocket._exceptions.WebSocketConnectionClosedException: + logger.debug('websocket closed exception caught...') + _reconnect.set() + time.sleep(1) - ws.close() - logger.debug("*** terminate ***") - + logger.debug("*** terminate thread ***") + t = threading.Thread(target=run) t.start() +def wsthreader(threadfunc): + + def wrapper(): + + while not _shutdown.isSet(): + _reconnect.clear() + logger.debug('threadfunc start...') + running = threadfunc() + logger.debug('threadfunc end...') + if running: + time.sleep(5) + else: + _shutdown.set() + logger.debug('*** thread stopped ***') + + return wrapper + if __name__ == '__main__': # websocket.enableTrace(True) - logging.basicConfig(level=logging.INFO) + logging.basicConfig(level=logging.DEBUG) with open("config.yaml", "rb") as config_file: config = yaml.load(config_file) - ws_url = 'wss://stream.pnut.io/v0/app?access_token=' + # ws_url = 'wss://stream.pnut.io/v0/app?access_token=' + ws_url = 'ws://192.168.1.200:9001/?' ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY'] ws_url += '&include_raw=1' matrix_url = config['MATRIX_HOST'] + '/_matrix/client/r0' @@ -301,9 +319,8 @@ if __name__ == '__main__': ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close) ws.on_open = on_open - t = threading.Thread(target=ws.run_forever) - t.daemon = True - t.start() + wst = threading.Thread(target=wsthreader(ws.run_forever)) + wst.start() # setup the matrix app service if config['MATRIX_ADMIN_ROOM']: @@ -312,6 +329,7 @@ if __name__ == '__main__': app.config.update(config) app.run(port=config['LISTEN_PORT']) - _shutdown.set() logger.info('!! shutdown initiated !!') + _shutdown.set() + ws.close() time.sleep(2) -- 2.45.2 From dbf3d5e1ae875d2901e81983a75e990a8550e31a Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 2 Feb 2019 15:10:02 -0800 Subject: [PATCH 05/50] remove test url --- pnut-matrix.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index a631068..e59a5e4 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -306,8 +306,7 @@ if __name__ == '__main__': with open("config.yaml", "rb") as config_file: config = yaml.load(config_file) - # ws_url = 'wss://stream.pnut.io/v0/app?access_token=' - ws_url = 'ws://192.168.1.200:9001/?' + ws_url = 'wss://stream.pnut.io/v0/app?access_token=' ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY'] ws_url += '&include_raw=1' matrix_url = config['MATRIX_HOST'] + '/_matrix/client/r0' -- 2.45.2 From 55eb6177fe4ea95595185825ef2b8f7504913962 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 2 Feb 2019 15:14:24 -0800 Subject: [PATCH 06/50] - assume defaults for image that does not contain height or width issue #41 --- appservice.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index e766e8a..ed207c6 100644 --- a/appservice.py +++ b/appservice.py @@ -201,8 +201,14 @@ def raw_from_event(event): value['url'] = url value['title'] = event['content']['body'] if 'info' in event['content']: - value['width'] = event['content']['info']['w'] - value['height'] = event['content']['info']['h'] + if 'w' in event['content']['info']: + value['width'] = event['content']['info']['w'] + else: + value['width'] = 200 + if 'h' in event['content']['info']: + value['height'] = event['content']['info']['h'] + else: + value['height'] = 200 if 'thumbnail_info' in event['content']['info']: value['thumbnail_url'] = app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] -- 2.45.2 From 83c409481d4af371b8c873197adab8edd39250c5 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 2 Feb 2019 15:20:30 -0800 Subject: [PATCH 07/50] avoid delete when user may not be authorized issue #42 --- appservice.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/appservice.py b/appservice.py index ed207c6..6ed189a 100644 --- a/appservice.py +++ b/appservice.py @@ -251,9 +251,13 @@ def delete_message(event, user): logger.debug("- can't find the event to remove -") return - r, meta = pnutpy.api.delete_message(e.pnut_chan_id, e.pnut_msg_id) - e.deleted = True - db_session.commit() + try: + r, meta = pnutpy.api.delete_message(e.pnut_chan_id, e.pnut_msg_id) + e.deleted = True + db_session.commit() + + except pnutpy.errors.PnutPermissionDenied as e: + pass def get_displayname(userid): url = "http://localhost:8008/_matrix/client/r0/profile/" + userid -- 2.45.2 From 327d8fad8cd3d8c77eb9e2aa376ed6e282ec943e Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 2 Feb 2019 15:31:56 -0800 Subject: [PATCH 08/50] update changelog --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52e585d..cab0a13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,13 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [1.0.2] - 2019-02-02 ### Fixed -- error checking if avatar changes +- error checking when avatar changes +- hung thread when websocket is closed remotely +- error checking for image size +- error handling message redaction from moderator ## [1.0.1] - 2019-01-09 ### Fixed @@ -31,7 +36,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - This CHANGELOG file because I can't believe for the last year I wasn't keeping track of releases for this project. :p -[Unreleased]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/compare/1.0.0...HEAD +[Unreleased]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/compare/1.0.2...HEAD +[1.0.2]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/tags/1.0.2 [1.0.1]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/tags/1.0.1 [1.0.0]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/tags/1.0.0 [0.0.1]: https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/tags/v0.0.1 -- 2.45.2 From 264dee0176ed907a483fbe03d4f81306c1ba7526 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 22 Mar 2020 22:00:53 -0700 Subject: [PATCH 09/50] properly handle unlinking rooms, issue #47 --- appservice.py | 29 +++++++++++++++-------------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/appservice.py b/appservice.py index 6ed189a..fb90285 100644 --- a/appservice.py +++ b/appservice.py @@ -409,22 +409,23 @@ def cmd_admin_unlink(id): else: room = Rooms.query.filter(Rooms.pnut_chan == id).one_or_none() - if room.portal: - alias = "#" + app.config['MATRIX_PNUT_PREFIX'] - alias += str(room.pnut_chan) + ":" - alias += app.config['MATRIX_DOMAIN'] - matrix_api.remove_room_alias(alias) + if hasattr(room, 'portal'): + if room.portal: + alias = "#" + app.config['MATRIX_PNUT_PREFIX'] + alias += str(room.pnut_chan) + ":" + alias += app.config['MATRIX_DOMAIN'] + matrix_api.remove_room_alias(alias) - # Kicking users needs at least moderator privs - members = matrix_api.get_room_members(room.room_id) - reason = "Portal room has been unlinked by administrator" - for m in members['chunk']: - if m['membership'] == 'join' and m['sender'] != app.config['MATRIX_AS_ID']: - if room.portal: - matrix_api.kick_user(room.room_id, m['sender'], reason=reason) - else: - if m['sender'].startswith("@" + app.config['MATRIX_PNUT_PREFIX']): + # Kicking users needs at least moderator privs + members = matrix_api.get_room_members(room.room_id) + reason = "Portal room has been unlinked by administrator" + for m in members['chunk']: + if m['content']['membership'] == 'join' and m['sender'] != app.config['MATRIX_AS_ID']: + if room.portal: matrix_api.kick_user(room.room_id, m['sender'], reason=reason) + else: + if m['sender'].startswith("@" + app.config['MATRIX_PNUT_PREFIX']): + matrix_api.kick_user(room.room_id, m['sender'], reason=reason) try: channel, meta = pnutpy.api.unsubscribe_channel(room.pnut_chan) -- 2.45.2 From 8f0d383e20003c4b1d888886349b05d358b4a255 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 22 Mar 2020 22:06:38 -0700 Subject: [PATCH 10/50] handle missing thumbnail info, issue #46 --- appservice.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index fb90285..fc4e4a1 100644 --- a/appservice.py +++ b/appservice.py @@ -211,8 +211,14 @@ def raw_from_event(event): value['height'] = 200 if 'thumbnail_info' in event['content']['info']: value['thumbnail_url'] = app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] - value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] - value['thumbnail_height'] = event['content']['info']['thumbnail_info']['h'] + if 'w' in event['content']['info']['thumbnail_info']: + value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] + else: + value['thumbnail_width'] = 200 + if 'h' in event['content']['info']['thumbnail_info']: + value['thumbnail_height'] = event['content']['info']['thumbnail_info']['h'] + else: + value['thumbnail_height'] = 200 elif event['content']['msgtype'] == 'm.video': # TODO: Need to sort out the oembed for this media type -- 2.45.2 From f10e8bb924edcc9f7df2b54b1b1f562271c3e51d Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Tue, 24 Mar 2020 20:47:10 -0700 Subject: [PATCH 11/50] pick up config file from environment variable --- database.py | 7 +++++-- pnut-matrix.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/database.py b/database.py index c2f9e22..2c05987 100644 --- a/database.py +++ b/database.py @@ -2,9 +2,12 @@ from sqlalchemy import create_engine from sqlalchemy.orm import scoped_session, sessionmaker from sqlalchemy.ext.declarative import declarative_base import yaml +import os -with open("config.yaml", "rb") as config_file: - config = yaml.load(config_file) +configyaml = os.environ.get("CONFIG_FILE") + +with open(configyaml, "rb") as config_file: + config = yaml.load(config_file, Loader=yaml.SafeLoader) engine = create_engine(config['SERVICE_DB']) db_session = scoped_session(sessionmaker(bind=engine)) diff --git a/pnut-matrix.py b/pnut-matrix.py index e59a5e4..c3eb2db 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -7,6 +7,8 @@ import json import pnutpy import requests import magic +import argparse +import os from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError @@ -300,12 +302,33 @@ def wsthreader(threadfunc): return wrapper if __name__ == '__main__': - # websocket.enableTrace(True) - logging.basicConfig(level=logging.DEBUG) - with open("config.yaml", "rb") as config_file: - config = yaml.load(config_file) - + + a_parser = argparse.ArgumentParser() + a_parser.add_argument( + '-d', action='store_true', dest='debug', + help="debug logging" + ) + # a_parser.add_argument( + # '-c', '--config', default="config.yaml", + # help="configuration file" + # ) + args = a_parser.parse_args() + + if args.debug: + # websocket.enableTrace(True) + logging.basicConfig(level=logging.DEBUG) + else: + logging.basicConfig(level=logging.INFO) + + # with open(args.config, 'rb') as config_file: + # config = yaml.load(config_file, Loader=yaml.SafeLoader) + + configyaml = os.environ.get("CONFIG_FILE") + + with open(configyaml, "rb") as config_file: + config = yaml.load(config_file, Loader=yaml.SafeLoader) + ws_url = 'wss://stream.pnut.io/v0/app?access_token=' ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY'] ws_url += '&include_raw=1' @@ -326,7 +349,7 @@ if __name__ == '__main__': logger.debug("- sould join admin room -") join_room(config['MATRIX_ADMIN_ROOM'], config['MATRIX_AS_ID']) app.config.update(config) - app.run(port=config['LISTEN_PORT']) + app.run(host=config['LISTEN_HOST'], port=config['LISTEN_PORT']) logger.info('!! shutdown initiated !!') _shutdown.set() -- 2.45.2 From 9dc5e3eda9bf01f3af12ad9581f3236772b298b9 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Tue, 24 Mar 2020 22:39:09 -0700 Subject: [PATCH 12/50] added direct chat support with bridge removing the need for seperate bot, issue #35 --- appservice.py | 159 +++++++++++++++++++++++++++++++++++++++++++++++++- models.py | 5 ++ 2 files changed, 162 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index fc4e4a1..f4e6b2c 100644 --- a/appservice.py +++ b/appservice.py @@ -6,7 +6,7 @@ import pnutpy from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError -from models import Avatars, Rooms, Events, Users +from models import Avatars, Rooms, Events, Users, DirectRooms from database import db_session from sqlalchemy import and_ from flask import Flask, jsonify, request, abort @@ -105,7 +105,17 @@ def on_receive_events(transaction): delete_message(event, user) elif event['type'] == 'm.room.member': - logger.debug("-room member event") + if 'is_direct' in event['content'] and 'membership' in event['content']: + if event['content']['membership'] == "invite" and event['content']['is_direct']: + return on_admin_invite(event) + + if 'membership' in event['content']: + if event['content']['membership'] == "leave": + return on_leave_event(event) + + logger.debug("----room member event----") + logger.debug(user) + logger.debug(event) return jsonify({}) @@ -115,6 +125,10 @@ def new_message(event, user): logger.debug('-skipping dup event-') return + direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none() + if direct is not None: + return on_direct_message(event) + room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none() if room is None: logger.debug('-room not mapped-') @@ -448,3 +462,144 @@ def cmd_admin_unlink(id): errmsg = "- error while unlinking room -" logger.exception(errmsg) return errmsg + +def on_admin_invite(event): + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + + matrix_api.join_room(event['room_id']) + + direct = DirectRooms(room_id=event['room_id']) + db_session.add(direct) + db_session.commit() + + return jsonify({}) + +def on_leave_event(event): + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + + direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none() + if direct is not None: + db_session.delete(direct) + db_session.commit() + matrix_api.leave_room(event['room_id']) + + return jsonify({}) + +def on_direct_message(event): + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + logger.debug("- direct room event received -") + + if event['type'] != 'm.room.message': + return jsonify({}) + + msg = event['content']['body'].split(' ') + + if msg[0] == '!help' or msg[0] == 'help': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_user_help(msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_user_help()) + + elif msg[0] == '!auth': + matrix_api.send_message(event['room_id'], cmd_user_auth()) + + elif msg[0] == '!save': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_user_save(event['sender'], msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_user_save()) + + elif msg[0] == '!drop': + matrix_api.send_message(event['room_id'], cmd_user_drop(event['sender'])) + + elif msg[0] == '!status': + matrix_api.send_message(event['room_id'], cmd_user_status(event['sender'])) + + return jsonify({}) + +def cmd_user_help(cmd=None): + reply = "This is an admin room for controlling your connection to pnut.io\n" + reply += "The following commands are available.\n\n" + reply += "!auth\t\t\t- Authorize your account on pnut.io\n" + reply += "!save \t- Save your pnut.io auth token\n" + reply += "!drop\t\t\t- Drop your pnut.io auth token\n" + reply += "!status\t\t\t- Status of your pnut.io auth token\n" + + return reply + +def cmd_user_auth(): + reply = "Visit the following URL to authorize your account on pnut.io.\n\n" + reply += "https://pnut.io/oauth/authenticate" + reply += "?client_id=6SeCRCpCZkmZOKFLFGWbcdAeq2fX1M5t" + reply += "&redirect_uri=urn:ietf:wg:oauth:2.0:oob" + reply += "&scope=write_post,presence,messages&response_type=token\n\n" + reply += "You will be provided with a token that you store with the !save command.\n" + + return reply + +def cmd_user_save(sender=None, token=None): + if token is None: + reply = "You must provide a token with this command.\n" + reply += "!save " + return reply + + pnutpy.api.add_authorization_token(token) + try: + response, meta = pnutpy.api.get_user('me') + + user = Users( + matrix_id=sender, + pnut_user_id=response.id, + pnut_user_token=token + ) + db_session.add(user) + db_session.commit() + + reply = "Success! You are now authorized as " + response['username'] + + except pnutpy.errors.PnutAuthAPIException as e: + reply = "Error! Unable to authorize your account." + + except Exception as e: + logging.exception('!save') + reply = "Error! Problem saving your token." + + return reply + +def cmd_user_drop(sender=None): + try: + user = Users.query.filter(Users.matrix_id == sender).one_or_none() + if user is not None: + db_session.delete(user) + db_session.commit() + reply = "Success! Your auth token has been removed." + else: + reply = "You do not appear to be registered." + + except Exception as e: + logging.exception('!drop') + reply = "Error! Problem removing your token." + + return reply + +def cmd_user_status(sender=None): + try: + user = Users.query.filter(Users.matrix_id == sender).one_or_none() + if user is None: + reply = "You are currently not authorized on pnut.io" + else: + pnutpy.api.add_authorization_token(user.pnut_user_token) + response, meta = pnutpy.api.get_user('me') + reply = "You are currently authorized as " + response.username + + except pnutpy.errors.PnutAuthAPIException as e: + reply = "You are currently not authorized on pnut.io" + + except Exception as e: + logging.exception('!status') + reply = "Error! There was a problem checking your account." + + return reply diff --git a/models.py b/models.py index ecd6fb1..a201b60 100644 --- a/models.py +++ b/models.py @@ -14,6 +14,11 @@ class Rooms(Base): pnut_chan = Column(Integer, unique=True) portal = Column(Boolean) +class DirectRooms(Base): + __tablename__ = 'direct' + id = Column(Integer, primary_key=True) + room_id = Column(String(250), unique=True) + class Events(Base): __tablename__ = 'events' id = Column(Integer, primary_key=True) -- 2.45.2 From 38009861bea632580c9cf52522d74fe770775ced Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 25 Mar 2020 22:10:42 -0700 Subject: [PATCH 13/50] docker support, issue #48 --- Dockerfile | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5acd64f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM python:3 + +VOLUME /data + +WORKDIR /usr/src/app + +COPY requirements.txt ./ +RUN pip install --no-cache-dir -r requirements.txt + +COPY . . + +WORKDIR /data + +EXPOSE 5000/tcp + +CMD [ "python", "/usr/src/app/pnut-matrix.py", "-d" ] -- 2.45.2 From d4c4cde685032ffc2e56dc581e4778c04854fbac Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 26 Mar 2020 07:43:58 -0700 Subject: [PATCH 14/50] fix hardcoded url in profile lookup, issue #49 --- appservice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appservice.py b/appservice.py index f4e6b2c..e64ff0d 100644 --- a/appservice.py +++ b/appservice.py @@ -280,7 +280,7 @@ def delete_message(event, user): pass def get_displayname(userid): - url = "http://localhost:8008/_matrix/client/r0/profile/" + userid + url = app.config['MATRIX_HOST'] + "/_matrix/client/r0/profile/" + userid r = requests.get(url) if r.status_code == 200: data = json.loads(r.text) -- 2.45.2 From 6b4b75c89145501996db7745aeb70b8074f5819e Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 26 Mar 2020 13:29:01 -0700 Subject: [PATCH 15/50] cleanup of readme file --- README.md | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 68162f6..0dfe7d7 100644 --- a/README.md +++ b/README.md @@ -7,24 +7,12 @@ This bridge will pass pnut.io channel messages through to Matrix, and Matrix mes ## Usage -To join a channel on pnut.io configured for public use: - -* `/join #pnut_ID:monkeystew.net` where ID is the id number of the channel on pnut.io -* You can get the id number by looking at the URL when viewing the channel in patter.chat - -By default any messages you send will be posted as the matrixbot user on pnut.io with your display name. You can however authorize pnut-matrix to post as your account instead. - -To link your Matrix ID with your pnut.io account: - -* Start a direct chat with `@pnut-matrix-bot:monkeystew.net` -* Type the `!help` command to get started. +Visit the project [wiki](https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/-/wikis/home) for up to date information on using the bridge hosted on dreamfall.space. ## Questions, comments, or issues Join the chat! -* [Matrix] [#pnut-matrix:monkeystew.net](https://matrix.to/#/#pnut-matrix:monkeystew.net) -* [pnut.io] [patter.chat](https://patter.chat/room/999) -* [XMPP] [pnut-matrix@chat.monkeystew.net](xmpp:pnut-matrix@chat.monkeystew.net?join) - +* [#pnut-matrix:dreamfall.space](https://matrix.to/#/#pnut-matrix:dreamfall.space) +* [pnut.io](https://patter.chat/room/999) -- 2.45.2 From 40b54076e0490f9ae6ba7f2d5e0637d2373be368 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 8 May 2020 07:50:43 -0700 Subject: [PATCH 16/50] split out public base url from client connection resolves issue #51 --- appservice.py | 12 ++++++------ config.yaml-sample | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/appservice.py b/appservice.py index e64ff0d..151daf9 100644 --- a/appservice.py +++ b/appservice.py @@ -157,20 +157,20 @@ def new_message(event, user): elif event['content']['msgtype'] == 'm.image': text = event['content']['body'] + "\n" - text += app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] embed = [raw_from_event(event)] elif event['content']['msgtype'] == 'm.video': text = event['content']['body'] + "\n" - text += app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] elif event['content']['msgtype'] == 'm.audio': text = event['content']['body'] + "\n" - text += app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] elif event['content']['msgtype'] == 'm.file': text = event['content']['body'] + "\n" - text += app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] else: logger.debug('-unknown msg type- ' + event['content']['msgtype']) @@ -208,7 +208,7 @@ def new_message(event, user): def raw_from_event(event): - url = app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + url = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] if event['content']['msgtype'] == 'm.image': value = {'type': "photo", 'version': "1.0"} @@ -224,7 +224,7 @@ def raw_from_event(event): else: value['height'] = 200 if 'thumbnail_info' in event['content']['info']: - value['thumbnail_url'] = app.config['MATRIX_HOST'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] + value['thumbnail_url'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] if 'w' in event['content']['info']['thumbnail_info']: value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] else: diff --git a/config.yaml-sample b/config.yaml-sample index 18716e5..d03d148 100644 --- a/config.yaml-sample +++ b/config.yaml-sample @@ -1,6 +1,7 @@ SERVICE_DB: 'sqlite:///store.db' # URL for the service database LISTEN_PORT: 5000 # matrix app service port to listen on -MATRIX_HOST: 'https://localhost:8448' # URL of the matrix server +MATRIX_URL: 'https://' # public base URL of the matrix server +MATRIX_HOST: 'https://localhost:8448' # client URL of the matrix server MATRIX_DOMAIN: '' # domain of the matrix server (right hand side of a matrix ID) MATRIX_AS_ID: '' # matrix ID for the app service user MATRIX_AS_TOKEN: '' # auth token for the app service user -- 2.45.2 From dbfc1b50829058b745c4bf450a9405eb094c0784 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 8 May 2020 08:36:03 -0700 Subject: [PATCH 17/50] support longposts for messages sent to global resovles issue #50 --- appservice.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index 151daf9..23ad6b4 100644 --- a/appservice.py +++ b/appservice.py @@ -3,6 +3,8 @@ import requests import logging import re import pnutpy +import textwrap +import time from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError @@ -193,10 +195,24 @@ def new_message(event, user): if user is not None: cctag = re.search('##$', text) if cctag: + raw = [] cname = get_channel_settings(room.pnut_chan)['name'] text = text[:-2] - text += '\n\n[' + cname + "](https://patter.chat/room.html?channel=" + str(room.pnut_chan) + ")" - r, meta = pnutpy.api.create_post(data={'text': text}) + ftext = '\n\n[' + cname + "](https://patter.chat/room.html?channel=" + str(room.pnut_chan) + ")" + mtext = textwrap.wrap(text + ftext, 254) + if len(mtext) > 1: + longpost = { + 'title': "", + 'body': text, + 'tstamp': time.time() * 1000 + } + pretext = textwrap.wrap(text, 100) + text = pretext[0] + text += "... - https://longpo.st/p/{object_id} - #longpost" + raw.append({'type':"nl.chimpnut.blog.post", 'value': longpost}) + + text += ftext + r, meta = pnutpy.api.create_post(data={'text': text, 'raw': raw}) except pnutpy.errors.PnutAuthAPIException: logger.exception('-unable to post to pnut channel-') -- 2.45.2 From 25ac47d049e8b758bbbe669c2d0f1e9f3d076df4 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 9 May 2020 21:55:45 -0700 Subject: [PATCH 18/50] include crosspost detail in messages to pnut resolves issue #52 --- appservice.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/appservice.py b/appservice.py index 23ad6b4..8ffcfc8 100644 --- a/appservice.py +++ b/appservice.py @@ -140,16 +140,34 @@ def new_message(event, user): logger.debug('-unknown message type-') return + cross_profile = {'username': event['user_id']} + matrix_profile = get_profile(event['user_id']) + if "avatar_url" in matrix_profile: + cross_profile['avatar_image'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + matrix_profile['avatar_url'][6:] + if user is not None: token = user.pnut_user_token prefix = "" else: token = app.config['MATRIX_PNUT_TOKEN'] - prefix = "[" + get_displayname(event['user_id']) + "] (" + event['user_id'] + ")\n" + if "displayname" in matrix_profile: + prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n" + else: + prefix = "(" + event['user_id'] + ")\n" pnutpy.api.add_authorization_token(token) text = None - embed = None + embed = [{ + 'type': "io.pnut.core.crosspost", + 'value': { + 'canonical_url': "https://matrix.to/#/" + event['room_id'] + "/" + event['event_id'] + ":" + app.config['MATRIX_DOMAIN'], + 'source': { + 'name': "matrix", + 'url': "https://matrix.org" + }, + 'user': cross_profile + } + }] if event['content']['msgtype'] == 'm.text' or event['content']['msgtype'] == 'm.notice': text = event['content']['body'] @@ -160,7 +178,7 @@ def new_message(event, user): elif event['content']['msgtype'] == 'm.image': text = event['content']['body'] + "\n" text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] - embed = [raw_from_event(event)] + embed.append(raw_from_event(event)) elif event['content']['msgtype'] == 'm.video': text = event['content']['body'] + "\n" @@ -295,13 +313,11 @@ def delete_message(event, user): except pnutpy.errors.PnutPermissionDenied as e: pass -def get_displayname(userid): +def get_profile(userid): url = app.config['MATRIX_HOST'] + "/_matrix/client/r0/profile/" + userid r = requests.get(url) if r.status_code == 200: - data = json.loads(r.text) - if 'displayname' in data: - return data["displayname"] + return json.loads(r.text) return userid def get_channel_settings(channel_id): -- 2.45.2 From b65b040a1561b5b1f8fc895f45a1d2fb9f2f089d Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 17 Dec 2020 20:38:27 -0800 Subject: [PATCH 19/50] ignore incomplete thumbnail info --- appservice.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/appservice.py b/appservice.py index 8ffcfc8..59050f5 100644 --- a/appservice.py +++ b/appservice.py @@ -258,15 +258,16 @@ def raw_from_event(event): else: value['height'] = 200 if 'thumbnail_info' in event['content']['info']: - value['thumbnail_url'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] - if 'w' in event['content']['info']['thumbnail_info']: - value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] - else: - value['thumbnail_width'] = 200 - if 'h' in event['content']['info']['thumbnail_info']: - value['thumbnail_height'] = event['content']['info']['thumbnail_info']['h'] - else: - value['thumbnail_height'] = 200 + if 'thumbnail_url' in event['content']['info']: + value['thumbnail_url'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['info']['thumbnail_url'][6:] + if 'w' in event['content']['info']['thumbnail_info']: + value['thumbnail_width'] = event['content']['info']['thumbnail_info']['w'] + else: + value['thumbnail_width'] = 200 + if 'h' in event['content']['info']['thumbnail_info']: + value['thumbnail_height'] = event['content']['info']['thumbnail_info']['h'] + else: + value['thumbnail_height'] = 200 elif event['content']['msgtype'] == 'm.video': # TODO: Need to sort out the oembed for this media type -- 2.45.2 From 2ce39ea06fb0d2c1a9e92a3ae4735d8e9615338c Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 17 Jan 2021 21:45:50 -0800 Subject: [PATCH 20/50] fix link references for api v1 --- pnut-matrix.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index c3eb2db..997a2b8 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -58,7 +58,7 @@ def new_message(msg): logger.debug('-set_display-') avatar = Avatars.query.filter(Avatars.pnut_user == msg.user.username).one_or_none() - if avatar is None or avatar.avatar != msg.user.content.avatar_image.link: + if avatar is None or avatar.avatar != msg.user.content.avatar_image.url: set_matrix_avatar(msg.user) logger.debug('-set_avatar-') @@ -76,8 +76,8 @@ def new_message(msg): for link in msg.content.entities.links: if 'title' in link: lnktext += link.title + "\n" - if 'link' in link: - lnktext += link.link + "\n" + if 'url' in link: + lnktext += link.url + "\n" if len(lnktext) > 0: text += "\n" + lnktext @@ -189,7 +189,7 @@ def set_matrix_avatar(user): token=config['MATRIX_AS_TOKEN'], identity=matrix_id) - dl = requests.get(user.content.avatar_image.link, stream=True) + dl = requests.get(user.content.avatar_image.url, stream=True) dl.raise_for_status() with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m: mtype = m.id_buffer(dl.content) @@ -199,10 +199,10 @@ def set_matrix_avatar(user): matrix_api.set_avatar_url(matrix_id, ul['content_uri']) avatar = Avatars.query.filter(Avatars.pnut_user == user.username).one_or_none() if avatar is None: - avatar = Avatars(pnut_user=user.username, avatar=user.content.avatar_image.link) + avatar = Avatars(pnut_user=user.username, avatar=user.content.avatar_image.url) db_session.add(avatar) else: - avatar.avatar = user.content.avatar_image.link + avatar.avatar = user.content.avatar_image.url db_session.commit() except MatrixRequestError: @@ -309,6 +309,7 @@ if __name__ == '__main__': '-d', action='store_true', dest='debug', help="debug logging" ) + # TODO: solve the database.py problem and enable this # a_parser.add_argument( # '-c', '--config', default="config.yaml", # help="configuration file" @@ -321,9 +322,6 @@ if __name__ == '__main__': else: logging.basicConfig(level=logging.INFO) - # with open(args.config, 'rb') as config_file: - # config = yaml.load(config_file, Loader=yaml.SafeLoader) - configyaml = os.environ.get("CONFIG_FILE") with open(configyaml, "rb") as config_file: -- 2.45.2 From 49fd79e8822c28913fc710fcfd9f31c89e8d9946 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 17 Jan 2021 21:47:31 -0800 Subject: [PATCH 21/50] fix raw object handling for api v1 --- pnut-matrix.py | 59 +++++++++++++++++++++++++++++--------------------- 1 file changed, 34 insertions(+), 25 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 997a2b8..378ebe4 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -93,7 +93,7 @@ def new_message(msg): db_session.add(event) db_session.commit() - if len(msg.raw) > 0: + if 'raw' in msg: logger.debug('-handle media uploads-') new_media(room.room_id, msg) @@ -104,36 +104,45 @@ def new_media(room_id, msg): identity=matrix_id) ts = int(msg.created_at.strftime('%s')) * 1000 - for item in msg.raw: - if item.type == 'io.pnut.core.oembed' and 'url' in item.value: + if 'io.pnut.core.oembed' in msg.raw: - dl = requests.get(item.value.url, stream=True) + for oembed in msg.raw['io.pnut.core.oembed']: + info = {} + + if oembed.type == 'photo': + msgtype = 'm.image' + dl_url = oembed.url + info['h'] = oembed.height + info['w'] = oembed.width + elif oembed.type == 'audio': + logger.debug("* recieved audio attachment") + continue + elif oembed.type == 'video': + logger.debug("* recieved video attachment") + continue + elif oembed.type == 'html5video': + logger.debug("* recieved html5 video attachment") + continue + elif oembed.type == 'rich': + logger.debug("* recieved video attachment") + continue + else: + logger.debug("* recieved unknown attachment") + continue + + dl = requests.get(dl_url, stream=True) dl.raise_for_status() with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m: - mtype = m.id_buffer(dl.content) - info = {'mimetype': mtype} - + info['mimetype'] = m.id_buffer(dl.content) + info['size'] = dl.content ul = matrix_api.media_upload(dl.content, mtype) - if item.value.type == 'photo': - msgtype = 'm.image' - info['h'] = item.value.height - info['w'] = item.value.width - info['size'] = len(dl.content) - elif item.value.type == 'video' or item.value.type == 'html5video': - msgtype = 'm.video' - info['h'] = item.value.height - info['w'] = item.value.width - info['size'] = len(dl.content) - elif item.value.type == 'audio': - msgtype = 'm.audio' - info['duration'] = int(item.value.duration) * 1000 - info['size'] = len(dl.content) + if 'title' in oembed: + title = oembed.title else: - msgtype = 'm.file' - info['size'] = len(dl.content) + title = "" - r = matrix_api.send_content(room_id, ul['content_uri'], item.value.title, msgtype, extra_information=info, timestamp=ts) + r = matrix_api.send_content(room_id, ul['content_uri'], title, msgtype, extra_information=info, timestamp=ts) event = Events( event_id=r['event_id'], room_id=room_id, @@ -327,7 +336,7 @@ if __name__ == '__main__': with open(configyaml, "rb") as config_file: config = yaml.load(config_file, Loader=yaml.SafeLoader) - ws_url = 'wss://stream.pnut.io/v0/app?access_token=' + ws_url = 'wss://stream.pnut.io/v1/app?access_token=' ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY'] ws_url += '&include_raw=1' matrix_url = config['MATRIX_HOST'] + '/_matrix/client/r0' -- 2.45.2 From 07ea679ecd01b3a080c60ded0efa84c125db331d Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 27 Feb 2021 10:08:59 -0800 Subject: [PATCH 22/50] fix channel type check and parsing payload --- pnut-matrix.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 378ebe4..e12f15e 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -252,22 +252,23 @@ def on_message(ws, message): if 'data' in msg: - if msg['meta']['type'] == "message": + if 'channel_type' in msg['meta']: # TODO: bypassed other channel types for now if msg['meta']['channel_type'] != 'io.pnut.core.chat': return - pmsg = pnutpy.models.Message.from_response_data(msg['data']) + for d_item in msg['data']: + pmsg = pnutpy.models.Message.from_response_data(d_item) - if 'is_deleted' in msg['meta']: - if msg['meta']['is_deleted']: - logger.debug("message: delete") - delete_message(pmsg) + if 'is_deleted' in msg['meta']: + if msg['meta']['is_deleted']: + logger.debug("message: delete") + delete_message(pmsg) + else: + logger.debug("uh whut?") else: - logger.debug("uh whut?") - else: - new_message(pmsg) + new_message(pmsg) def on_error(ws, error): logger.error("on_error: !!! ERROR !!!") -- 2.45.2 From fc617a2c670f3ace16b8427d566e9abc59bc5152 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 5 Mar 2021 21:14:11 -0800 Subject: [PATCH 23/50] get size of image to pass to info, not image Resolves issue #54 --- pnut-matrix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index e12f15e..85c634f 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -134,8 +134,8 @@ def new_media(room_id, msg): dl.raise_for_status() with magic.Magic(flags=magic.MAGIC_MIME_TYPE) as m: info['mimetype'] = m.id_buffer(dl.content) - info['size'] = dl.content - ul = matrix_api.media_upload(dl.content, mtype) + info['size'] = len(dl.content) + ul = matrix_api.media_upload(dl.content, info['mimetype']) if 'title' in oembed: title = oembed.title -- 2.45.2 From e06a3fbf12e768ea46c630b5c8c1f8bbab49d7da Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 20 Mar 2021 13:26:33 +0000 Subject: [PATCH 24/50] initial private channel support issue #11 --- appservice.py | 397 +++++++++++++++++++++++++++++++++++-------------- models.py | 7 + pnut-matrix.py | 58 +++++++- 3 files changed, 346 insertions(+), 116 deletions(-) diff --git a/appservice.py b/appservice.py index 59050f5..8c14bb9 100644 --- a/appservice.py +++ b/appservice.py @@ -8,7 +8,7 @@ import time from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError -from models import Avatars, Rooms, Events, Users, DirectRooms +from models import Avatars, Rooms, Events, Users, DirectRooms, ControlRooms from database import db_session from sqlalchemy import and_ from flask import Flask, jsonify, request, abort @@ -77,6 +77,9 @@ def query_alias(alias): db_session.add(rr) db_session.commit() + except pnutpy.errors.PnutPermissionDenied: + abort(401) + except Exception: logger.exception("-couldn't get the pnut channel-") abort(404) @@ -85,7 +88,7 @@ def query_alias(alias): @app.route("/transactions/", methods=["PUT"]) def on_receive_events(transaction): - + access_token = request.args.get('access_token', '') if access_token != app.config['MATRIX_HS_TOKEN']: abort(403) @@ -94,7 +97,6 @@ def on_receive_events(transaction): for event in events: logger.debug(event) - # TODO: route event if it's in the control room if app.config['MATRIX_ADMIN_ROOM'] and app.config['MATRIX_ADMIN_ROOM'] == event['room_id']: return on_admin_event(event) @@ -109,8 +111,8 @@ def on_receive_events(transaction): elif event['type'] == 'm.room.member': if 'is_direct' in event['content'] and 'membership' in event['content']: if event['content']['membership'] == "invite" and event['content']['is_direct']: - return on_admin_invite(event) - + return on_direct_invite(event) + if 'membership' in event['content']: if event['content']['membership'] == "leave": return on_leave_event(event) @@ -122,81 +124,44 @@ def on_receive_events(transaction): return jsonify({}) def new_message(event, user): - if app.config['MATRIX_PNUT_PREFIX'] in event['user_id'] or 'pnut-bridge' in event['user_id']: logger.debug('-skipping dup event-') return + if 'msgtype' not in event['content']: + logger.debug('-unknown message type-') + return + + control = ControlRooms.query.filter(ControlRooms.room_id == event['room_id']).one_or_none() + if control is not None: + return on_control_message(event) + direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none() if direct is not None: - return on_direct_message(event) + return on_direct_message(event, user, direct) room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none() if room is None: logger.debug('-room not mapped-') return - if 'msgtype' not in event['content']: - logger.debug('-unknown message type-') - return - - cross_profile = {'username': event['user_id']} - matrix_profile = get_profile(event['user_id']) - if "avatar_url" in matrix_profile: - cross_profile['avatar_image'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + matrix_profile['avatar_url'][6:] - if user is not None: token = user.pnut_user_token prefix = "" else: token = app.config['MATRIX_PNUT_TOKEN'] + matrix_profile = get_profile(event['user_id']) if "displayname" in matrix_profile: prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n" else: prefix = "(" + event['user_id'] + ")\n" - pnutpy.api.add_authorization_token(token) - text = None - embed = [{ - 'type': "io.pnut.core.crosspost", - 'value': { - 'canonical_url': "https://matrix.to/#/" + event['room_id'] + "/" + event['event_id'] + ":" + app.config['MATRIX_DOMAIN'], - 'source': { - 'name': "matrix", - 'url': "https://matrix.org" - }, - 'user': cross_profile - } - }] - if event['content']['msgtype'] == 'm.text' or event['content']['msgtype'] == 'm.notice': - text = event['content']['body'] - - elif event['content']['msgtype'] == 'm.emote': - text = "* " + event['content']['body'] - - elif event['content']['msgtype'] == 'm.image': - text = event['content']['body'] + "\n" - text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] - embed.append(raw_from_event(event)) - - elif event['content']['msgtype'] == 'm.video': - text = event['content']['body'] + "\n" - text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] - - elif event['content']['msgtype'] == 'm.audio': - text = event['content']['body'] + "\n" - text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] - - elif event['content']['msgtype'] == 'm.file': - text = event['content']['body'] + "\n" - text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] - - else: - logger.debug('-unknown msg type- ' + event['content']['msgtype']) - return - - text = prefix + text + embed = [crosspost_raw(event)] + evtext, evraw = msg_from_event(event) + text = prefix + evtext + if len(evraw) > 0: + embed.append(evraw) try: msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': embed}) revent = Events( @@ -235,13 +200,63 @@ def new_message(event, user): except pnutpy.errors.PnutAuthAPIException: logger.exception('-unable to post to pnut channel-') return - + except Exception: logger.exception('-something bad happened here-') return -def raw_from_event(event): +def msg_from_event(event): + text = None + raw = {} + if event['content']['msgtype'] == 'm.text' or event['content']['msgtype'] == 'm.notice': + text = event['content']['body'] + elif event['content']['msgtype'] == 'm.emote': + text = "* " + event['content']['body'] + + elif event['content']['msgtype'] == 'm.image': + text = event['content']['body'] + "\n" + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + raw = raw_from_event(event) + + elif event['content']['msgtype'] == 'm.video': + text = event['content']['body'] + "\n" + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + + elif event['content']['msgtype'] == 'm.audio': + text = event['content']['body'] + "\n" + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + + elif event['content']['msgtype'] == 'm.file': + text = event['content']['body'] + "\n" + text += app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + + else: + logger.debug('-unknown msg type- ' + event['content']['msgtype']) + return + + return text, raw + +def crosspost_raw(event): + cross_profile = {'username': event['user_id']} + matrix_profile = get_profile(event['user_id']) + if "avatar_url" in matrix_profile: + cross_profile['avatar_image'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + matrix_profile['avatar_url'][6:] + + embed = { + 'type': "io.pnut.core.crosspost", + 'value': { + 'canonical_url': "https://matrix.to/#/" + event['room_id'] + "/" + event['event_id'] + ":" + app.config['MATRIX_DOMAIN'], + 'source': { + 'name': "matrix", + 'url': "https://matrix.org" + }, + 'user': cross_profile + } + } + return embed + +def raw_from_event(event): url = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] if event['content']['msgtype'] == 'm.image': @@ -335,6 +350,46 @@ def get_channel_settings(channel_id): return channel_settings +def create_room(channel, invite): + channel_settings = {} + for item in channel.raw: + if item.type == 'io.pnut.core.chat-settings': + channel_settings = item.value + + # Matrix sdk doesn't include all details in a single call + room = {'room_alias_name': app.config['MATRIX_PNUT_PREFIX'] + channel.id} + logger.debug(invite) + logger.debug(room) + room['invite'] = [invite] + if 'name' in channel_settings: + room['name'] = channel_settings['name'] + if 'description' in channel_settings: + room['topic'] = channel_settings['description'] + if channel.acl.read.any_user: + room['preset'] = 'public_chat' + room['visibility'] = 'public' + else: + room['preset'] = 'private_chat' + room['visibility'] = 'private' + + url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom?access_token=' + url += app.config['MATRIX_AS_TOKEN'] + headers = {"Content-Type":"application/json"} + r = requests.post(url, headers=headers, data=json.dumps(room)) + + if r.status_code == 200: + #pnutpy.api.subscribe_channel(channel.id) + rdata = r.json() + rr = Rooms( + room_id=rdata['room_id'], + pnut_chan=channel.id, + portal=True + ) + db_session.add(rr) + db_session.commit() + logger.debug(r.status_code) + logger.debug(r) + def on_admin_event(event): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) @@ -345,26 +400,31 @@ def on_admin_event(event): msg = event['content']['body'].split(' ') - if msg[0] == 'help': - if len(msg) > 1: - matrix_api.send_message(event['room_id'], cmd_admin_help(msg[1])) - else: - matrix_api.send_message(event['room_id'], cmd_admin_help()) + try: + if msg[0] == 'help': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_admin_help(msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_admin_help()) - elif msg[0] == 'list': - matrix_api.send_message(event['room_id'], cmd_admin_list()) + elif msg[0] == 'list': + matrix_api.send_message(event['room_id'], cmd_admin_list()) - elif msg[0] == 'unlink': - if len(msg) > 1: - matrix_api.send_message(event['room_id'], cmd_admin_unlink(msg[1])) - else: - matrix_api.send_message(event['room_id'], cmd_admin_help('unlink')) + elif msg[0] == 'unlink': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_admin_unlink(msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_admin_help('unlink')) - elif msg[0] == 'link': - if len(msg) > 2: - matrix_api.send_message(event['room_id'], cmd_admin_link(msg[1], msg[2])) - else: - matrix_api.send_message(event['room_id'], cmd_admin_help('link')) + elif msg[0] == 'link': + if len(msg) > 2: + matrix_api.send_message(event['room_id'], cmd_admin_link(msg[1], msg[2])) + else: + matrix_api.send_message(event['room_id'], cmd_admin_help('link')) + + except Exception: + errmsg = "- on_admin_event -" + logger.exception(errmsg) return jsonify({}) @@ -496,31 +556,111 @@ def cmd_admin_unlink(id): logger.exception(errmsg) return errmsg -def on_admin_invite(event): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], - token=app.config['MATRIX_AS_TOKEN']) - - matrix_api.join_room(event['room_id']) +def on_direct_invite(event): + if event['state_key'] == app.config['MATRIX_AS_ID']: + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + dm = ControlRooms(room_id=event['room_id']) - direct = DirectRooms(room_id=event['room_id']) - db_session.add(direct) - db_session.commit() + else: + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + identity=event['state_key'], + token=app.config['MATRIX_AS_TOKEN']) + bridge_user = event['state_key'] + pnut_user = bridge_user.replace(app.config['MATRIX_PNUT_PREFIX'],'').split(':')[0] + + user = Users.query.filter(Users.matrix_id == event['sender']).one_or_none() + if user is not None: + # TODO: need to handle if the user isn't registered + pnutpy.api.add_authorization_token(user.pnut_user_token) + channel, meta = pnutpy.api.existing_pm(ids=pnut_user) + + dm = DirectRooms(room_id=event['room_id'], + bridge_user=bridge_user, pnut_chan=channel.id) + + try: + matrix_api.join_room(event['room_id']) + + db_session.add(dm) + db_session.commit() + + except Exception: + errmsg = "- on_direct_invite -" + logger.exception(errmsg) return jsonify({}) def on_leave_event(event): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], - token=app.config['MATRIX_AS_TOKEN']) - direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none() if direct is not None: - db_session.delete(direct) - db_session.commit() - matrix_api.leave_room(event['room_id']) - + + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + identity=direct.bridge_user, + token=app.config['MATRIX_AS_TOKEN']) + try: + matrix_api.leave_room(event['room_id']) + db_session.delete(direct) + db_session.commit() + + except Exception: + errmsg = "- on_leave_event -" + logger.exception(errmsg) + + control = ControlRooms.query.filter(ControlRooms.room_id == event['room_id']).one_or_none() + if control is not None: + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + try: + matrix_api.leave_room(event['room_id']) + db_session.delete(control) + db_session.commit() + + except Exception: + errmsg = "- on_leave_event -" + logger.exception(errmsg) + return jsonify({}) -def on_direct_message(event): +def on_direct_message(event, user, room): + if user is not None: + token = user.pnut_user_token + prefix = "" + else: + token = app.config['MATRIX_PNUT_TOKEN'] + matrix_profile = get_profile(event['user_id']) + if "displayname" in matrix_profile: + prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n" + else: + prefix = "(" + event['user_id'] + ")\n" + pnutpy.api.add_authorization_token(token) + + embed = [crosspost_raw(event)] + evtext, evraw = msg_from_event(event) + text = prefix + evtext + if len(evraw) > 0: + embed.append(evraw) + try: + msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': embed}) + revent = Events( + event_id=event['event_id'], + room_id=event['room_id'], + pnut_msg_id=msg.id, + pnut_user_id=msg.user.id, + pnut_chan_id=room.pnut_chan, + deleted=False + ) + db_session.add(revent) + db_session.commit() + + except pnutpy.errors.PnutAuthAPIException: + logger.exception('-unable to post to pnut channel-') + + except Exception: + logger.exception('-something bad happened here-') + + return jsonify({}) + +def on_control_message(event): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) logger.debug("- direct room event received -") @@ -530,26 +670,38 @@ def on_direct_message(event): msg = event['content']['body'].split(' ') - if msg[0] == '!help' or msg[0] == 'help': - if len(msg) > 1: - matrix_api.send_message(event['room_id'], cmd_user_help(msg[1])) - else: - matrix_api.send_message(event['room_id'], cmd_user_help()) + try: + if msg[0] == '!help' or msg[0] == 'help': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_user_help(msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_user_help()) - elif msg[0] == '!auth': - matrix_api.send_message(event['room_id'], cmd_user_auth()) + elif msg[0] == '!auth': + matrix_api.send_message(event['room_id'], cmd_user_auth()) - elif msg[0] == '!save': - if len(msg) > 1: - matrix_api.send_message(event['room_id'], cmd_user_save(event['sender'], msg[1])) - else: - matrix_api.send_message(event['room_id'], cmd_user_save()) + elif msg[0] == '!save': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_user_save(event['sender'], msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_user_save()) - elif msg[0] == '!drop': - matrix_api.send_message(event['room_id'], cmd_user_drop(event['sender'])) + elif msg[0] == '!drop': + matrix_api.send_message(event['room_id'], cmd_user_drop(event['sender'])) - elif msg[0] == '!status': - matrix_api.send_message(event['room_id'], cmd_user_status(event['sender'])) + elif msg[0] == '!status': + matrix_api.send_message(event['room_id'], cmd_user_status(event['sender'])) + + elif msg[0] == '!join': + if len(msg) > 1: + matrix_api.send_message(event['room_id'], cmd_user_join(event['sender'], msg[1])) + else: + matrix_api.send_message(event['room_id'], cmd_user_join(event['sender'])) + + + except Exception: + errmsg = "- on_direct_message -" + logger.exception(errmsg) return jsonify({}) @@ -560,6 +712,7 @@ def cmd_user_help(cmd=None): reply += "!save \t- Save your pnut.io auth token\n" reply += "!drop\t\t\t- Drop your pnut.io auth token\n" reply += "!status\t\t\t- Status of your pnut.io auth token\n" + reply += "!join \t- Join a private channel on pnut.io\n" return reply @@ -636,3 +789,31 @@ def cmd_user_status(sender=None): reply = "Error! There was a problem checking your account." return reply + +def cmd_user_join(sender=None, channel_id=None): + if channel_id is None: + reply = "You must provide a channel id number with this command.\n" + reply += "!join " + return reply + + try: + user = Users.query.filter(Users.matrix_id == sender).one_or_none() + if user is None: + reply = "You are currently not authorized on pnut.io" + else: + pnutpy.api.add_authorization_token(user.pnut_user_token) + channel, meta = pnutpy.api.get_channel(channel_id, include_raw=1) + create_room(channel, user.matrix_id) + reply = "is working?" + + except pnutpy.errors.PnutAuthAPIException as e: + reply = "You are currently not authorized on pnut.io" + + except pnutpy.errors.PnutPermissionDenied: + reply = "You are not authorized for this channel" + + except Exception: + logging.exception('!join') + reply = "Error! There was a problem joining the channel." + + return reply diff --git a/models.py b/models.py index a201b60..247da58 100644 --- a/models.py +++ b/models.py @@ -18,6 +18,13 @@ class DirectRooms(Base): __tablename__ = 'direct' id = Column(Integer, primary_key=True) room_id = Column(String(250), unique=True) + pnut_chan = Column(Integer, unique=True) + bridge_user = Column(String(250)) + +class ControlRooms(Base): + __tablename__ = 'control' + id = Column(Integer, primary_key=True) + room_id = Column(String(250), unique=True) class Events(Base): __tablename__ = 'events' diff --git a/pnut-matrix.py b/pnut-matrix.py index 85c634f..19eb9ce 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -12,7 +12,7 @@ import os from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError -from models import Avatars, Rooms, Events +from models import Avatars, Rooms, Events, DirectRooms, Users from database import db_session, init_db from sqlalchemy import and_ from appservice import app @@ -22,7 +22,7 @@ logger = logging.getLogger() _shutdown = threading.Event() _reconnect = threading.Event() -def new_message(msg): +def new_message(msg, meta): logger.debug("channel: " + msg.channel_id) logger.debug("username: " + msg.user.username) if 'name' in msg.user: @@ -36,7 +36,30 @@ def new_message(msg): if msg.source.id == config['PNUTCLIENT_ID']: return - room = Rooms.query.filter(Rooms.pnut_chan == msg.channel_id).one_or_none() + if meta['channel_type'] == 'io.pnut.core.chat': + room = Rooms.query.filter(Rooms.pnut_chan == msg.channel_id).one_or_none() + elif meta['channel_type'] == 'io.pnut.core.pm': + room = DirectRooms.query.filter(DirectRooms.pnut_chan == msg.channel_id).one_or_none() + if room is None: + # Do do an invite from the bridge user? + logger.debug('new invite?') + # create room and included matrix recpient + # subscribed_user_ids from meta + logger.debug(meta['subscribed_user_ids']) + pnut_user = matrix_id_from_pnut(msg.user.username) + profile = get_matrix_profile(pnut_user) + if not profile: + new_matrix_user(msg.user.username) + invitees=[] + for pm_user in meta['subscribed_user_ids']: + user = Users.query.filter(Users.pnut_user_id == pm_user).one_or_none() + if user is not None: + invitees.append(user.matrix_id) + if len(invitees) > 0: + room = new_room(pnut_user, invitees, msg.channel_id) + else: + room = None + logger.debug(room) if room is None: logger.debug('-not_mapped-') @@ -245,6 +268,27 @@ def join_room(room_id, matrix_id): logger.debug('-room_join-') +def new_room(pnut_user, invitees, chan): + dr = None + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + token=config['MATRIX_AS_TOKEN'], + identity=pnut_user) + url = matrix_url + '/createRoom' + params = {"access_token": config['MATRIX_AS_TOKEN'], "user_id": pnut_user} + content = {"visibility": "private", "is_direct": True, "invite": invitees} + headers = {"Content-Type": "application/json"} + r = requests.post(url, headers=headers, params=params, + data=json.dumps(content)) + response = r.json() + for bridge_user in invitees: + dr = DirectRooms(room_id=response['room_id'], + bridge_user=pnut_user, pnut_chan=chan) + logger.debug(dr) + db_session.add(dr) + db_session.commit() + + return dr + def on_message(ws, message): # logger.debug("on_message: " + message) msg = json.loads(message) @@ -254,8 +298,8 @@ def on_message(ws, message): if 'channel_type' in msg['meta']: - # TODO: bypassed other channel types for now - if msg['meta']['channel_type'] != 'io.pnut.core.chat': + if msg['meta']['channel_type'] not in ['io.pnut.core.chat', + 'io.pnut.core.pm']: return for d_item in msg['data']: @@ -265,10 +309,8 @@ def on_message(ws, message): if msg['meta']['is_deleted']: logger.debug("message: delete") delete_message(pmsg) - else: - logger.debug("uh whut?") else: - new_message(pmsg) + new_message(pmsg, msg['meta']) def on_error(ws, error): logger.error("on_error: !!! ERROR !!!") -- 2.45.2 From f5fd082ec2ab5c6de2acaf5b325e50934dafe52b Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 20 Mar 2021 08:14:57 -0700 Subject: [PATCH 25/50] update to changelog --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cab0a13..7aaa1a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [1.2.0] - 2021-03-20 +### Added +- Support of private messaging and private channels + +## [1.1.1] - 2021-03-05 +### Fixed +- Pass correct image size info for image attachments + +## [1.1.0] - 2021-02-27 +### Added +- Support for pnut v1 app streams + ## [1.0.2] - 2019-02-02 ### Fixed - error checking when avatar changes -- 2.45.2 From eb78c95ec6649703f630f28a4ddab71d740a4d9c Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 3 Jul 2021 07:57:55 -0700 Subject: [PATCH 26/50] Removed public bridge details --- README.md | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0dfe7d7..0b6075a 100644 --- a/README.md +++ b/README.md @@ -7,12 +7,9 @@ This bridge will pass pnut.io channel messages through to Matrix, and Matrix mes ## Usage -Visit the project [wiki](https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/-/wikis/home) for up to date information on using the bridge hosted on dreamfall.space. +The public bridge has been retired and is no longer available. You can get future updates on social media or public chat. +- [Social](https://thrrgilag.net/social) +- [pnut.io chat](https://patter.chat/room/999) +- [thrrgilag's chat](https://thrrgilag.net/chat/dev) -## Questions, comments, or issues - -Join the chat! - -* [#pnut-matrix:dreamfall.space](https://matrix.to/#/#pnut-matrix:dreamfall.space) -* [pnut.io](https://patter.chat/room/999) -- 2.45.2 From 9571238043405b09ee5cd8d1373759b222a1fe40 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 26 May 2022 18:47:51 -0700 Subject: [PATCH 27/50] Updated for the new public bridge --- README.md | 9 ++++----- requirements.txt | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 0b6075a..673d7ca 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,13 @@ This is a pnut.io channel bridge for Matrix using the Application Services (AS) API. -This bridge will pass pnut.io channel messages through to Matrix, and Matrix messages through to pnut.io channels. Currently only public chat channels on pnut.io are supported but work is in progress to support private messages. +This bridge will pass pnut.io channel messages through to Matrix, and Matrix messages through to pnut.io channels. ## Usage -The public bridge has been retired and is no longer available. You can get future updates on social media or public chat. +The public bridge is once again online! + +See [Using-the-public-bridge](https://gitlab.com/thrrgilag/pnut-matrix/-/wikis/Using-the-public-bridge) for details. -- [Social](https://thrrgilag.net/social) -- [pnut.io chat](https://patter.chat/room/999) -- [thrrgilag's chat](https://thrrgilag.net/chat/dev) diff --git a/requirements.txt b/requirements.txt index 2a0d308..ae1f4cf 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,8 @@ pyyaml requests -matrix-client +matrix-client==0.3.2 Flask pnutpy sqlalchemy websocket-client filemagic -git+https://github.com/shawnanastasio/python-matrix-bot-api \ No newline at end of file -- 2.45.2 From 03949ff67b0219a6039c20983c00eae9ab68e849 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 11 Jun 2022 21:47:27 -0700 Subject: [PATCH 28/50] use local timestamp when creating events --- pnut-matrix.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 19eb9ce..0792431 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -93,7 +93,7 @@ def new_message(msg, meta): if 'content' in msg: text = msg.content.text + "\n" - ts = int(msg.created_at.strftime('%s')) * 1000 + ts = int(time.time()) * 1000 lnktext = "" for link in msg.content.entities.links: @@ -125,7 +125,7 @@ def new_media(room_id, msg): matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) - ts = int(msg.created_at.strftime('%s')) * 1000 + ts = int(time.time()) * 1000 if 'io.pnut.core.oembed' in msg.raw: -- 2.45.2 From 937ad8cea16c86e6b291f396e1aa23d9cba86ff6 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 11 Jun 2022 21:55:06 -0700 Subject: [PATCH 29/50] attempt to generate the pnut matrix user on event --- appservice.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/appservice.py b/appservice.py index 8c14bb9..62dee59 100644 --- a/appservice.py +++ b/appservice.py @@ -390,6 +390,17 @@ def create_room(channel, invite): logger.debug(r.status_code) logger.debug(r) +def new_matrix_user(username): + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + data = {'type': 'm.login.application_service','user': app.config['MATRIX_PNUT_PREFIX'] + username} + try: + matrix_api.register(content=data) + + except Exception: + errmsg = "- new_matrix_user user already exists -" + logger.warning(errmsg) + def on_admin_event(event): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) @@ -574,6 +585,7 @@ def on_direct_invite(event): # TODO: need to handle if the user isn't registered pnutpy.api.add_authorization_token(user.pnut_user_token) channel, meta = pnutpy.api.existing_pm(ids=pnut_user) + new_matrix_user(pnut_user) dm = DirectRooms(room_id=event['room_id'], bridge_user=bridge_user, pnut_chan=channel.id) -- 2.45.2 From 904140320d1d8620b5e8cf48ad6a54ff02a5b1a2 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 25 Jun 2022 10:26:49 -0700 Subject: [PATCH 30/50] README: added contribution details --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index 673d7ca..07b42ef 100644 --- a/README.md +++ b/README.md @@ -12,3 +12,23 @@ The public bridge is once again online! See [Using-the-public-bridge](https://gitlab.com/thrrgilag/pnut-matrix/-/wikis/Using-the-public-bridge) for details. +## Contributing and support + +You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches to the [mailing list] or directly to [morgan@mcmillian.dev]. + +Join my public chat room for development discussion. + +- [pnut-matrix on pnut.io] +- [#pnut_999:pnut-matrix.dreamfall.space] + + +## License + +GPLv3, see [LICENSE]. + +[GitLab]: https://gitlab.com/thrrgilag/pnut-matrix/ +[mailing list]: https://lists.sr.ht/~thrrgilag/pnut-matrix +[morgan@mcmillian.dev]: mailto:morgan@mcmillian.dev +[pnut-matrix on pnut.io]: https://patter.chat/999 +[#pnut_999:pnut-matrix.dreamfall.space]: https://matrix.to/#/#pnut_999:pnut-matrix.dreamfall.space +[LICENSE]: LICENSE -- 2.45.2 From f04ec6fef968769d16536b4b4528d8ab5adcafde Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 25 Jun 2022 12:00:10 -0700 Subject: [PATCH 31/50] README: added installation and configuration Started with the bare minimum instructions needed for getting the bridge installed and running from source. Related to #34 --- README.md | 23 +++++++++++++++++++++++ appservice.yaml-sample | 13 +++++++++++++ config.yaml-sample | 6 +++--- 3 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 appservice.yaml-sample diff --git a/README.md b/README.md index 07b42ef..435f636 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,26 @@ The public bridge is once again online! See [Using-the-public-bridge](https://gitlab.com/thrrgilag/pnut-matrix/-/wikis/Using-the-public-bridge) for details. +## Installation + +Currently pnut-matrix has been only tested with and confirmed to work with [synapse]. Please refer to the [synapse installation instructions] for details on how to setup your homeserver. + +To install the latest version of pnut-matrix from source: + +```sh +git clone https://gitlab.com/thrrgilag/pnut-matrix.git +cd pnut-matrix +python3 -m venv env +source env/bin/activate +pip3 install -r requirements.txt +``` + + +## Configuration + +Copy `config.yaml-sample` to `config.yaml` and edit for your setup. Likewise copy `appservice.yaml-sample` to `appservice.yaml` and edit to match the tokens, prefix, and port listed in `config.yaml`. This is the configuration that synapse will need to reference so that it can connect the bridge. Make sure you modify your [syanpse configuration] accordingly. + + ## Contributing and support You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches to the [mailing list] or directly to [morgan@mcmillian.dev]. @@ -26,6 +46,9 @@ Join my public chat room for development discussion. GPLv3, see [LICENSE]. +[synapse]: https://github.com/matrix-org/synapse +[synapse installation instructions]: https://matrix-org.github.io/synapse/latest/setup/installation.html +[syanpse configuration]: https://matrix-org.github.io/synapse/latest/application_services.html [GitLab]: https://gitlab.com/thrrgilag/pnut-matrix/ [mailing list]: https://lists.sr.ht/~thrrgilag/pnut-matrix [morgan@mcmillian.dev]: mailto:morgan@mcmillian.dev diff --git a/appservice.yaml-sample b/appservice.yaml-sample new file mode 100644 index 0000000..9a81904 --- /dev/null +++ b/appservice.yaml-sample @@ -0,0 +1,13 @@ +id: "pnut" +url: "http://127.0.0.1:5000" +as_token: "" +hs_token: "" +sender_localpart: pnut +namespaces: + users: + - exclusive: true + regex: "@pnut_.*" + rooms: [] + aliases: + - exclusive: true + regex: "#pnut_.*" diff --git a/config.yaml-sample b/config.yaml-sample index d03d148..922e123 100644 --- a/config.yaml-sample +++ b/config.yaml-sample @@ -4,9 +4,9 @@ MATRIX_URL: 'https://' # public base URL of the matrix server MATRIX_HOST: 'https://localhost:8448' # client URL of the matrix server MATRIX_DOMAIN: '' # domain of the matrix server (right hand side of a matrix ID) MATRIX_AS_ID: '' # matrix ID for the app service user -MATRIX_AS_TOKEN: '' # auth token for the app service user -MATRIX_HS_TOKEN: '' # auth token for the matrix server -MATRIX_PNUT_PREFIX: '' # prefix used for reserving matrix IDs and room aliases +MATRIX_AS_TOKEN: '' # auth token for the app service user +MATRIX_HS_TOKEN: '' # auth token for the matrix server +MATRIX_PNUT_PREFIX: 'pnut_' # prefix used for reserving matrix IDs and room aliases MATRIX_ADMIN_ROOM: '' # Administrator control room ID MATRIX_PNUT_USER: '' # pnut.io username for the matrix bot MATRIX_PNUT_TOKEN: '' # pnut.io auth token for the matrix bot -- 2.45.2 From c75b7517ca8f3e0f863749bf72028887744f44e1 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 30 Jun 2022 12:47:31 -0700 Subject: [PATCH 32/50] use correct pnut acl attribute for public When creating of the matrix room is triggered, the pnut attribute channel.acl.read.public flag should be used to determine if the matrix room preset should be set to public or private. Issue #58 --- appservice.py | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/appservice.py b/appservice.py index 62dee59..3c66a33 100644 --- a/appservice.py +++ b/appservice.py @@ -58,9 +58,11 @@ def query_alias(alias): room['name'] = channel_settings['name'] if 'description' in channel_settings: room['topic'] = channel_settings['description'] - if channel.acl.read.any_user: + if channel.acl.read.public: room['preset'] = 'public_chat' room['visibility'] = 'public' + else: + abort(401) url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom?access_token=' url += app.config['MATRIX_AS_TOKEN'] @@ -350,7 +352,7 @@ def get_channel_settings(channel_id): return channel_settings -def create_room(channel, invite): +def create_room(channel, user): channel_settings = {} for item in channel.raw: if item.type == 'io.pnut.core.chat-settings': @@ -358,19 +360,21 @@ def create_room(channel, invite): # Matrix sdk doesn't include all details in a single call room = {'room_alias_name': app.config['MATRIX_PNUT_PREFIX'] + channel.id} - logger.debug(invite) + logger.debug(user) logger.debug(room) - room['invite'] = [invite] + room['invite'] = [user.matrix_id] if 'name' in channel_settings: room['name'] = channel_settings['name'] if 'description' in channel_settings: room['topic'] = channel_settings['description'] - if channel.acl.read.any_user: + if channel.acl.read.public: room['preset'] = 'public_chat' room['visibility'] = 'public' - else: + elif channel.acl.read.any_user or channel.acl.read.you: room['preset'] = 'private_chat' room['visibility'] = 'private' + else: + abort(401) url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom?access_token=' url += app.config['MATRIX_AS_TOKEN'] @@ -523,15 +527,15 @@ def cmd_admin_link(room_id, pnut_chan_id): logger.exception(errmsg) return errmsg -def cmd_admin_unlink(id): +def cmd_admin_unlink(rid): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN']) - if id.startswith('!'): - room = Rooms.query.filter(Rooms.room_id == id).one_or_none() + if rid.startswith('!'): + room = Rooms.query.filter(Rooms.room_id == rid).one_or_none() else: - room = Rooms.query.filter(Rooms.pnut_chan == id).one_or_none() + room = Rooms.query.filter(Rooms.pnut_chan == rid).one_or_none() if hasattr(room, 'portal'): if room.portal: @@ -815,8 +819,14 @@ def cmd_user_join(sender=None, channel_id=None): else: pnutpy.api.add_authorization_token(user.pnut_user_token) channel, meta = pnutpy.api.get_channel(channel_id, include_raw=1) - create_room(channel, user.matrix_id) - reply = "is working?" + room = Rooms.query.filter(Rooms.pnut_chan == channel_id).one_or_none() + if room is None: + create_room(channel, user) + else: + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + token=app.config['MATRIX_AS_TOKEN']) + matrix_api.invite_user(room.room_id, sender) + reply = "ok" except pnutpy.errors.PnutAuthAPIException as e: reply = "You are currently not authorized on pnut.io" -- 2.45.2 From 2d4476b9a331a025f17071f91f4a402218663469 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Thu, 30 Jun 2022 13:27:51 -0700 Subject: [PATCH 33/50] added workaround for invite in README Resolves issue #57 --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 435f636..6848777 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,15 @@ pip3 install -r requirements.txt Copy `config.yaml-sample` to `config.yaml` and edit for your setup. Likewise copy `appservice.yaml-sample` to `appservice.yaml` and edit to match the tokens, prefix, and port listed in `config.yaml`. This is the configuration that synapse will need to reference so that it can connect the bridge. Make sure you modify your [syanpse configuration] accordingly. +## Tweaks + +There exists a bug in synapse which prevents the bridge user from accepting DM invites from users on other homeservers. To work around the issue you can create a profile for the bot user.[^1] + +```sh +curl --data '{"type": "m.login.application_service", "username": "your_sender_localpart"}' 'http://yourhomeserver/_matrix/client/r0/register?access_token=your_as_token' +``` + + ## Contributing and support You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches to the [mailing list] or directly to [morgan@mcmillian.dev]. @@ -55,3 +64,4 @@ GPLv3, see [LICENSE]. [pnut-matrix on pnut.io]: https://patter.chat/999 [#pnut_999:pnut-matrix.dreamfall.space]: https://matrix.to/#/#pnut_999:pnut-matrix.dreamfall.space [LICENSE]: LICENSE +[^1]: https://github.com/matrix-org/matrix-appservice-irc/issues/1270#issuecomment-849765090 -- 2.45.2 From ad49c3a3541fcd01b828a65180bf99b260f5f304 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 1 Jul 2022 10:57:16 -0700 Subject: [PATCH 34/50] add config and filters to logging Basic logging configuration can now be done in the config.yaml file and filter has been added to redact the access tokens from the log output. Closes issue #38 --- config.yaml-sample | 22 ++++++++++++++++++++++ pnut-matrix.py | 29 +++++++++++++++++++++++------ 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/config.yaml-sample b/config.yaml-sample index 922e123..e97295a 100644 --- a/config.yaml-sample +++ b/config.yaml-sample @@ -13,3 +13,25 @@ MATRIX_PNUT_TOKEN: '' # pnut.io auth token for the matrix bot PNUTCLIENT_ID: '' # pnut.io app client ID PNUT_APPTOKEN: '' # pnut.io app token PNUT_APPKEY: '' # pnut.io app stream key + +logging: + version: 1 + formatters: + precise: + format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s' + normal: + format: '%(name)s - %(levelname)s - %(message)s' + handlers: + console: + class: logging.StreamHandler + formatter: normal + loggers: + werkzeug: + level: DEBUG + appservice: + level: DEBUG + urllib3.connectionpool: + level: DEBUG + root: + level: DEBUG + handlers: [console] diff --git a/pnut-matrix.py b/pnut-matrix.py index 0792431..3212f82 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -2,6 +2,7 @@ import websocket import threading import time import logging +import logging.config import yaml import json import pnutpy @@ -9,6 +10,7 @@ import requests import magic import argparse import os +import re from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixError, MatrixRequestError @@ -22,6 +24,21 @@ logger = logging.getLogger() _shutdown = threading.Event() _reconnect = threading.Event() +class MLogFilter(logging.Filter): + + ACCESS_TOKEN_RE = re.compile(r"(\?.*access(_|%5[Ff])token=)[^&]*(\s.*)$") + ACCESS_TOKEN_RE2 = re.compile(r"(\?.*access(_|%5[Ff])token=)[^&]*(.*)$") + + def filter(self, record): + if record.name == "werkzeug" and len(record.args) > 0: + redacted_uri = MLogFilter.ACCESS_TOKEN_RE.sub(r"\1\3", record.args[0]) + record.args = (redacted_uri, ) + record.args[1:] + elif record.name == "urllib3.connectionpool" and len(record.args) > 3: + redacted_uri = MLogFilter.ACCESS_TOKEN_RE2.sub(r"\1\3", record.args[4]) + record.args = record.args[:4] + (redacted_uri,) + record.args[5:] + + return True + def new_message(msg, meta): logger.debug("channel: " + msg.channel_id) logger.debug("username: " + msg.user.username) @@ -368,17 +385,17 @@ if __name__ == '__main__': # ) args = a_parser.parse_args() - if args.debug: - # websocket.enableTrace(True) - logging.basicConfig(level=logging.DEBUG) - else: - logging.basicConfig(level=logging.INFO) - configyaml = os.environ.get("CONFIG_FILE") with open(configyaml, "rb") as config_file: config = yaml.load(config_file, Loader=yaml.SafeLoader) + # websocket.enableTrace(True) + logging.config.dictConfig(config['logging']) + redact_filter = MLogFilter() + logging.getLogger("werkzeug").addFilter(redact_filter) + logging.getLogger("urllib3.connectionpool").addFilter(redact_filter) + ws_url = 'wss://stream.pnut.io/v1/app?access_token=' ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY'] ws_url += '&include_raw=1' -- 2.45.2 From 05893172f8e72c3715372e82c628cf5907c835bd Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 1 Jul 2022 14:58:32 -0700 Subject: [PATCH 35/50] sticker support closes issue #43 --- appservice.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/appservice.py b/appservice.py index 3c66a33..76f02a3 100644 --- a/appservice.py +++ b/appservice.py @@ -107,6 +107,9 @@ def on_receive_events(transaction): if event['type'] == 'm.room.message': new_message(event, user) + elif event['type'] == 'm.sticker': + new_sticker(event, user) + elif event['type'] == 'm.room.redaction': delete_message(event, user) @@ -125,6 +128,60 @@ def on_receive_events(transaction): return jsonify({}) +def new_sticker(event, user): + if app.config['MATRIX_PNUT_PREFIX'] in event['user_id'] or 'pnut-bridge' in event['user_id']: + logger.debug('-skipping dup event-') + return + + room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none() + if room is None: + logger.debug('-room not mapped-') + return + + if user is not None: + token = user.pnut_user_token + prefix = "" + else: + token = app.config['MATRIX_PNUT_TOKEN'] + matrix_profile = get_profile(event['user_id']) + if "displayname" in matrix_profile: + prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n" + else: + prefix = "(" + event['user_id'] + ")\n" + + pnutpy.api.add_authorization_token(token) + # evtext, evraw = msg_from_event(event) + text = "sticker::" + event['content']['body'] + "\n" + value = {'type': "photo", 'version': "1.0"} + value['url'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] + value['title'] = event['content']['body'] + value['width'] = event['content']['info']['thumbnail_info']['w'] + value['height'] = event['content']['info']['thumbnail_info']['h'] + raw = {'type': "io.pnut.core.oembed", 'value': value} + embed = [raw] + text = prefix + text + + try: + msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': embed}) + revent = Events( + event_id=event['event_id'], + room_id=event['room_id'], + pnut_msg_id=msg.id, + pnut_user_id=msg.user.id, + pnut_chan_id=room.pnut_chan, + deleted=False + ) + db_session.add(revent) + db_session.commit() + + except pnutpy.errors.PnutAuthAPIException: + logger.exception('-unable to post to pnut channel-') + return + + except Exception: + logger.exception('-something bad happened here-') + return + def new_message(event, user): if app.config['MATRIX_PNUT_PREFIX'] in event['user_id'] or 'pnut-bridge' in event['user_id']: logger.debug('-skipping dup event-') -- 2.45.2 From 24d12582654b54d3a875bd0a5b46aa0711651ac4 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 20 Jul 2022 16:29:53 -0700 Subject: [PATCH 36/50] clean up display name for pnut user --- pnut-matrix.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pnut-matrix.py b/pnut-matrix.py index 3212f82..7d83e0a 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -210,7 +210,7 @@ def matrix_id_from_pnut(username): def matrix_display_from_pnut(user): if 'name' in user: - display = user.name + " <@" + user.username + "> (pnut)" + display = user.name + " (@" + user.username + ")" else: display = "@" + user.username return display -- 2.45.2 From ee6baaa579a5c45cd6702f90a1d7ff9e346e45a5 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 13 Aug 2022 08:16:59 -0700 Subject: [PATCH 37/50] ignore malformed sticker messages resolves issue #60 --- appservice.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index 76f02a3..13bec39 100644 --- a/appservice.py +++ b/appservice.py @@ -155,8 +155,11 @@ def new_sticker(event, user): value = {'type': "photo", 'version': "1.0"} value['url'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:] value['title'] = event['content']['body'] - value['width'] = event['content']['info']['thumbnail_info']['w'] - value['height'] = event['content']['info']['thumbnail_info']['h'] + if 'h' in event['content']['info'] and 'w' in event['content']['info']: + value['height'] = event['content']['info']['h'] + value['width'] = event['content']['info']['h'] + else: + return raw = {'type': "io.pnut.core.oembed", 'value': value} embed = [raw] text = prefix + text -- 2.45.2 From 38cd7b347eb84bddd186b5975f5ceaec7887e548 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 20 Aug 2022 07:07:05 -0700 Subject: [PATCH 38/50] updated changelog for 1.3.0 release --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aaa1a8..3fe8a0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ## [Unreleased] +## [1.3.0] - 2022-08-20 +### Added +- Support for matrix stickers + +### Fixed +- External matrix users unable to DM appservice +- Joining a non-public channels on pnut makes matrix room public +- Timestamps on generated events + +### Changed +- Improved display name for pnut users in matrix rooms +- Access tokens redacted from appservice logs + ## [1.2.0] - 2021-03-20 ### Added - Support of private messaging and private channels -- 2.45.2 From 82c2ab105a9c4ef1cfd107743821631da1a6d48a Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sat, 10 Dec 2022 07:21:37 -0800 Subject: [PATCH 39/50] fixup docker build work towards issue #48 --- .dockerignore | 15 ++++++++++++++ .gitlab-ci.yml | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 14 ++++++------- 3 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitlab-ci.yml diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..5026a5e --- /dev/null +++ b/.dockerignore @@ -0,0 +1,15 @@ +__pycache__/ +*.py[cod] +*.yaml +*.db +*.sublime-project +*.sublime-workspace +.vscode/ +.git/ +.gitignore +Dockerfile +.dockerignore +snap/ +Jenkinsfile +.gitlab-ci.yml +contrib/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..67b0c82 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,56 @@ +# This file is a template, and might need editing before it works on your project. +# To contribute improvements to CI/CD templates, please follow the Development guide at: +# https://docs.gitlab.com/ee/development/cicd/templates.html +# This specific template is located at: +# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Docker.gitlab-ci.yml + +# Build a Docker image with CI/CD and push to the GitLab registry. +# Docker-in-Docker documentation: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html +# +# This template uses one generic job with conditional builds +# for the default branch and all other (MR) branches. +stages: + - build + +docker-build: + # Use the official docker image. + image: docker:latest + stage: build + services: + - docker:dind + before_script: + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + # Default branch leaves tag empty (= latest tag) + # All other branches are tagged with the escaped branch name (commit ref slug) + script: + - | + if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then + tag="" + echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'" + else + tag=":$CI_COMMIT_REF_SLUG" + echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag" + fi + - docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" . + - docker push "$CI_REGISTRY_IMAGE${tag}" + # Run this job in a branch where a Dockerfile exists + rules: + - if: $CI_COMMIT_BRANCH + exists: + - Dockerfile + +# snap-build-arm64: +# stage: build +# tags: +# - snap-arm64 +# script: +# - snapcraft +# - snapcraft upload --release=edge *.snap +# +# snap-build-amd64: +# stage: build +# tags: +# - snap-amd64 +# script: +# - snapcraft +# - snapcraft upload --release=edge *.snap diff --git a/Dockerfile b/Dockerfile index 5acd64f..307c7ee 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,14 @@ -FROM python:3 - -VOLUME /data +FROM python:3.10-slim-bullseye WORKDIR /usr/src/app -COPY requirements.txt ./ -RUN pip install --no-cache-dir -r requirements.txt +RUN apt-get update && apt-get install libmagic-dev -y COPY . . +RUN pip install --no-cache-dir -r requirements.txt +ENV CONFIG_FILE=/data/config.yaml +VOLUME /data WORKDIR /data - -EXPOSE 5000/tcp - +EXPOSE 5000 CMD [ "python", "/usr/src/app/pnut-matrix.py", "-d" ] -- 2.45.2 From edd1ef62127dcf8aa791c83a637a925dd45cb3fc Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 11 Dec 2022 06:29:12 -0800 Subject: [PATCH 40/50] add wrapper script for a clean startup in container --- wait-for-synapse.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100755 wait-for-synapse.sh diff --git a/wait-for-synapse.sh b/wait-for-synapse.sh new file mode 100755 index 0000000..afb3a79 --- /dev/null +++ b/wait-for-synapse.sh @@ -0,0 +1,26 @@ +#!/bin/sh +# wait-for-synapse.sh +# +# based on an exmaple from https://docs.docker.com/compose/startup-order/ +# +# command: ["./wait-for-synapse.sh", "synapse", "python", "/usr/src/app/pnut-matrix.py", "-d"] + +set -e + +host="$1" +# Shift arguments with mapping: +# - $0 => $0 +# - $1 => +# - $2 => $1 +# - $3 => $2 +# - ... +# This is done for `exec "$@"` below to work correctly +shift + +until [ "200" == $(curl -s -o /dev/null --head -w "%{http_code}" https://${host}/_matrix/client/versions) ]; do + >&2 echo "synapse is unavailable - sleeping" + sleep 3 +done + +>&2 echo "synapse is up - executing command" +exec "$@" -- 2.45.2 From 45f621f9af7c6483daee2a1fa0d3367129d32651 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 11 Dec 2022 07:01:55 -0800 Subject: [PATCH 41/50] added curl to image for wrapper script work towards issue #48 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 307c7ee..f15df4d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.10-slim-bullseye WORKDIR /usr/src/app -RUN apt-get update && apt-get install libmagic-dev -y +RUN apt-get update && apt-get install libmagic-dev curl -y COPY . . RUN pip install --no-cache-dir -r requirements.txt -- 2.45.2 From 32d38bc0055a0894c3f9a006dc2d7c4c4ee58076 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 11 Dec 2022 07:02:56 -0800 Subject: [PATCH 42/50] fix conditional in until loop work towards issue #48 --- wait-for-synapse.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wait-for-synapse.sh b/wait-for-synapse.sh index afb3a79..1c07041 100755 --- a/wait-for-synapse.sh +++ b/wait-for-synapse.sh @@ -17,7 +17,7 @@ host="$1" # This is done for `exec "$@"` below to work correctly shift -until [ "200" == $(curl -s -o /dev/null --head -w "%{http_code}" https://${host}/_matrix/client/versions) ]; do +until [ "200" -eq $(curl -s -o /dev/null --head -w "%{http_code}" https://${host}/_matrix/client/versions) ]; do >&2 echo "synapse is unavailable - sleeping" sleep 3 done -- 2.45.2 From de7f4f5c358a644c9ed1ce991e8d1f1de9d6b47f Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Sun, 11 Dec 2022 07:13:28 -0800 Subject: [PATCH 43/50] allow protocol to be specified in argument work towards issue #48 --- Dockerfile | 2 +- wait-for-synapse.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index f15df4d..e138d8f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,4 +11,4 @@ ENV CONFIG_FILE=/data/config.yaml VOLUME /data WORKDIR /data EXPOSE 5000 -CMD [ "python", "/usr/src/app/pnut-matrix.py", "-d" ] +CMD [ "python", "/usr/src/app/pnut-matrix.py" ] diff --git a/wait-for-synapse.sh b/wait-for-synapse.sh index 1c07041..1e484d8 100755 --- a/wait-for-synapse.sh +++ b/wait-for-synapse.sh @@ -3,7 +3,7 @@ # # based on an exmaple from https://docs.docker.com/compose/startup-order/ # -# command: ["./wait-for-synapse.sh", "synapse", "python", "/usr/src/app/pnut-matrix.py", "-d"] +# command: ["/usr/src/app/wait-for-synapse.sh", "http://synapse:8008", "python", "/usr/src/app/pnut-matrix.py", "-d"] set -e @@ -17,7 +17,7 @@ host="$1" # This is done for `exec "$@"` below to work correctly shift -until [ "200" -eq $(curl -s -o /dev/null --head -w "%{http_code}" https://${host}/_matrix/client/versions) ]; do +until [ "200" -eq $(curl -s -o /dev/null --head -w "%{http_code}" ${host}/_matrix/client/versions) ]; do >&2 echo "synapse is unavailable - sleeping" sleep 3 done -- 2.45.2 From 03ba94ecb9514489fc9152c026351e1bc433be28 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 16 Dec 2022 12:07:16 -0800 Subject: [PATCH 44/50] Avoid inviting self when sending a PM from a different pnut client Fixes #63 --- CHANGELOG.md | 5 +++++ pnut-matrix.py | 2 ++ 2 files changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3fe8a0b..0724d22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Docker image + +### Fixed +- Self-invite when sending a PM from a pnut app ## [1.3.0] - 2022-08-20 ### Added diff --git a/pnut-matrix.py b/pnut-matrix.py index 7d83e0a..8ec3bcc 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -70,6 +70,8 @@ def new_message(msg, meta): invitees=[] for pm_user in meta['subscribed_user_ids']: user = Users.query.filter(Users.pnut_user_id == pm_user).one_or_none() + if int(pm_user) == msg.user.id: + continue if user is not None: invitees.append(user.matrix_id) if len(invitees) > 0: -- 2.45.2 From 037fee7796ad70bd07a7de04560aad2ffa5e4e45 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Fri, 10 Feb 2023 08:00:54 -0800 Subject: [PATCH 45/50] removed mailing list link from README --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index 6848777..ab8203b 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ curl --data '{"type": "m.login.application_service", "username": "your_sender_lo ## Contributing and support -You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches to the [mailing list] or directly to [morgan@mcmillian.dev]. +You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches directly to [morgan@mcmillian.dev]. Join my public chat room for development discussion. @@ -59,7 +59,6 @@ GPLv3, see [LICENSE]. [synapse installation instructions]: https://matrix-org.github.io/synapse/latest/setup/installation.html [syanpse configuration]: https://matrix-org.github.io/synapse/latest/application_services.html [GitLab]: https://gitlab.com/thrrgilag/pnut-matrix/ -[mailing list]: https://lists.sr.ht/~thrrgilag/pnut-matrix [morgan@mcmillian.dev]: mailto:morgan@mcmillian.dev [pnut-matrix on pnut.io]: https://patter.chat/999 [#pnut_999:pnut-matrix.dreamfall.space]: https://matrix.to/#/#pnut_999:pnut-matrix.dreamfall.space -- 2.45.2 From 782c3d070bb03b468c58ccbd24e2a4822e934e96 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Wed, 15 Feb 2023 18:08:51 -0800 Subject: [PATCH 46/50] whitespace trim --- appservice.py | 20 ++++++++++---------- pnut-matrix.py | 46 +++++++++++++++++++++++----------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/appservice.py b/appservice.py index 13bec39..fa548ed 100644 --- a/appservice.py +++ b/appservice.py @@ -382,7 +382,7 @@ def delete_message(event, user): if e is None: logger.debug("- can't find the event to remove -") return - + try: r, meta = pnutpy.api.delete_message(e.pnut_chan_id, e.pnut_msg_id) e.deleted = True @@ -466,13 +466,13 @@ def new_matrix_user(username): logger.warning(errmsg) def on_admin_event(event): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) logger.debug("- admin room event recieved -") if event['type'] != 'm.room.message': return jsonify({}) - + msg = event['content']['body'].split(' ') try: @@ -555,7 +555,7 @@ def cmd_admin_list(): return text def cmd_admin_link(room_id, pnut_chan_id): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN']) @@ -568,7 +568,7 @@ def cmd_admin_link(room_id, pnut_chan_id): try: channel, meta = pnutpy.api.subscribe_channel(pnut_chan_id) r = matrix_api.join_room(room_id) - + rec = Rooms( room_id=room_id, pnut_chan=channel.id, @@ -588,7 +588,7 @@ def cmd_admin_link(room_id, pnut_chan_id): return errmsg def cmd_admin_unlink(rid): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN']) @@ -730,20 +730,20 @@ def on_direct_message(event, user, room): except pnutpy.errors.PnutAuthAPIException: logger.exception('-unable to post to pnut channel-') - + except Exception: logger.exception('-something bad happened here-') return jsonify({}) def on_control_message(event): - matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) logger.debug("- direct room event received -") if event['type'] != 'm.room.message': return jsonify({}) - + msg = event['content']['body'].split(' ') try: @@ -839,7 +839,7 @@ def cmd_user_drop(sender=None): db_session.commit() reply = "Success! Your auth token has been removed." else: - reply = "You do not appear to be registered." + reply = "You do not appear to be registered." except Exception as e: logging.exception('!drop') diff --git a/pnut-matrix.py b/pnut-matrix.py index 8ec3bcc..6690c06 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -85,7 +85,7 @@ def new_message(msg, meta): return matrix_id = matrix_id_from_pnut(msg.user.username) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) @@ -94,11 +94,11 @@ def new_message(msg, meta): new_matrix_user(msg.user.username) logger.debug('-new_user-') profile = {'displayname': None} - + if profile['displayname'] != matrix_display_from_pnut(msg.user): set_matrix_display(msg.user) logger.debug('-set_display-') - + avatar = Avatars.query.filter(Avatars.pnut_user == msg.user.username).one_or_none() if avatar is None or avatar.avatar != msg.user.content.avatar_image.url: set_matrix_avatar(msg.user) @@ -126,10 +126,10 @@ def new_message(msg, meta): r = matrix_api.send_message(room.room_id, text, timestamp=ts) event = Events( - event_id=r['event_id'], - room_id=room.room_id, - pnut_msg_id=msg.id, - pnut_user_id=msg.user.id, + event_id=r['event_id'], + room_id=room.room_id, + pnut_msg_id=msg.id, + pnut_user_id=msg.user.id, pnut_chan_id=msg.channel_id, deleted=False) db_session.add(event) @@ -141,7 +141,7 @@ def new_message(msg, meta): def new_media(room_id, msg): matrix_id = matrix_id_from_pnut(msg.user.username) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) ts = int(time.time()) * 1000 @@ -186,10 +186,10 @@ def new_media(room_id, msg): r = matrix_api.send_content(room_id, ul['content_uri'], title, msgtype, extra_information=info, timestamp=ts) event = Events( - event_id=r['event_id'], - room_id=room_id, - pnut_msg_id=msg.id, - pnut_user_id=msg.user.id, + event_id=r['event_id'], + room_id=room_id, + pnut_msg_id=msg.id, + pnut_user_id=msg.user.id, pnut_chan_id=msg.channel_id, deleted=False) db_session.add(event) @@ -197,7 +197,7 @@ def new_media(room_id, msg): def delete_message(msg): matrix_id = matrix_id_from_pnut(msg.user.username) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) @@ -229,14 +229,14 @@ def get_matrix_profile(matrix_id): def set_matrix_display(user): matrix_id = matrix_id_from_pnut(user.username) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) matrix_api.set_display_name(matrix_id, matrix_display_from_pnut(user)) def set_matrix_avatar(user): matrix_id = matrix_id_from_pnut(user.username) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) @@ -260,7 +260,7 @@ def set_matrix_avatar(user): logger.exception('failed to set user avatar') def new_matrix_user(username): - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN']) data = { 'type': 'm.login.application_service', @@ -269,15 +269,15 @@ def new_matrix_user(username): matrix_api.register(content=data) def join_room(room_id, matrix_id): - matrix_api_as = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api_as = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN']) - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=matrix_id) - + try: matrix_api.join_room(room_id) - + except MatrixRequestError as e: if e.code == 403: matrix_api_as.invite_user(room_id, matrix_id) @@ -289,7 +289,7 @@ def join_room(room_id, matrix_id): def new_room(pnut_user, invitees, chan): dr = None - matrix_api = MatrixHttpApi(config['MATRIX_HOST'], + matrix_api = MatrixHttpApi(config['MATRIX_HOST'], token=config['MATRIX_AS_TOKEN'], identity=pnut_user) url = matrix_url + '/createRoom' @@ -312,7 +312,7 @@ def on_message(ws, message): # logger.debug("on_message: " + message) msg = json.loads(message) logger.debug(msg['meta']) - + if 'data' in msg: if 'channel_type' in msg['meta']: @@ -407,7 +407,7 @@ if __name__ == '__main__': init_db() # setup the websocket connection - ws = websocket.WebSocketApp(ws_url, on_message=on_message, + ws = websocket.WebSocketApp(ws_url, on_message=on_message, on_error=on_error, on_close=on_close) ws.on_open = on_open wst = threading.Thread(target=wsthreader(ws.run_forever)) -- 2.45.2 From a26759bb7b626587f0311aba9c9543d1102cb2f1 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Mon, 1 May 2023 14:18:53 -0700 Subject: [PATCH 47/50] Add versioned appservice paths to existing routes Synapse v1.81 changed to attempt using version paths when calling an appservice before falling back to the legacy paths. However the appservice was generating a 404 and the server wouldn't fallback so this adds the versioned paths to the existing routes. Resolves #64 --- appservice.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/appservice.py b/appservice.py index fa548ed..781e0d0 100644 --- a/appservice.py +++ b/appservice.py @@ -29,6 +29,7 @@ def forbidden(error): def shutdown_session(exception=None): db_session.remove() +@app.route("/_matrix/app/v1/rooms/") @app.route("/rooms/") def query_alias(alias): alias_localpart = alias.split(":")[0][1:] @@ -88,6 +89,7 @@ def query_alias(alias): return jsonify({}) +@app.route("/_matrix/app/v1/transactions/", methods=["PUT"]) @app.route("/transactions/", methods=["PUT"]) def on_receive_events(transaction): -- 2.45.2 From 1a5c9d84b0a9f849e7162aa5355f8b042f3eda94 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Tue, 4 Jul 2023 09:31:18 -0700 Subject: [PATCH 48/50] fix username parameter for /register endpoint resolves #65 --- appservice.py | 2 +- pnut-matrix.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/appservice.py b/appservice.py index 781e0d0..a78a19c 100644 --- a/appservice.py +++ b/appservice.py @@ -459,7 +459,7 @@ def create_room(channel, user): def new_matrix_user(username): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) - data = {'type': 'm.login.application_service','user': app.config['MATRIX_PNUT_PREFIX'] + username} + data = {'type': 'm.login.application_service','username': app.config['MATRIX_PNUT_PREFIX'] + username} try: matrix_api.register(content=data) diff --git a/pnut-matrix.py b/pnut-matrix.py index 6690c06..993f821 100644 --- a/pnut-matrix.py +++ b/pnut-matrix.py @@ -264,7 +264,7 @@ def new_matrix_user(username): token=config['MATRIX_AS_TOKEN']) data = { 'type': 'm.login.application_service', - 'user': config['MATRIX_PNUT_PREFIX'] + username + 'username': config['MATRIX_PNUT_PREFIX'] + username } matrix_api.register(content=data) -- 2.45.2 From 84f3882466a5d808c81e857e5fd7336df08bd0b1 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Tue, 4 Jul 2023 09:33:24 -0700 Subject: [PATCH 49/50] replace access_token url parameter with authorization header issue #66 --- appservice.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/appservice.py b/appservice.py index a78a19c..e9f80d6 100644 --- a/appservice.py +++ b/appservice.py @@ -65,9 +65,11 @@ def query_alias(alias): else: abort(401) - url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom?access_token=' - url += app.config['MATRIX_AS_TOKEN'] - headers = {"Content-Type":"application/json"} + url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom' + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer " + app.config['MATRIX_AS_TOKEN'] + } r = requests.post(url, headers=headers, data=json.dumps(room)) if r.status_code == 200: pnutpy.api.subscribe_channel(channel_id) @@ -80,6 +82,12 @@ def query_alias(alias): db_session.add(rr) db_session.commit() + else: + logger.error("Unable to create room") + logger.error(r.status_code) + logger.error(r.text) + abort(400) + except pnutpy.errors.PnutPermissionDenied: abort(401) @@ -438,9 +446,11 @@ def create_room(channel, user): else: abort(401) - url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom?access_token=' - url += app.config['MATRIX_AS_TOKEN'] - headers = {"Content-Type":"application/json"} + url = app.config['MATRIX_HOST'] + '/_matrix/client/api/v1/createRoom' + headers = { + "Content-Type": "application/json", + "Authorization": "Bearer " + app.config['MATRIX_AS_TOKEN'] + } r = requests.post(url, headers=headers, data=json.dumps(room)) if r.status_code == 200: @@ -453,8 +463,12 @@ def create_room(channel, user): ) db_session.add(rr) db_session.commit() - logger.debug(r.status_code) - logger.debug(r) + + else: + logger.error("Unable to create room") + logger.error(r.status_code) + logger.error(r.text) + abort(400) def new_matrix_user(username): matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], -- 2.45.2 From 409b2a5a3cc219fccd3ec10067840d68215c4946 Mon Sep 17 00:00:00 2001 From: Morgan McMillian Date: Tue, 4 Jul 2023 09:55:43 -0700 Subject: [PATCH 50/50] update base docker image --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e138d8f..642b5ef 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.10-slim-bullseye +FROM python:3.11-slim-bookworm WORKDIR /usr/src/app -- 2.45.2