import json import yaml import requests import logging import logging.config import re import pnutpy import textwrap import time import os from mautrix.client import ClientAPI from mautrix.types import * 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 logger = logging.getLogger(__name__) app = Flask(__name__) @app.errorhandler(404) def not_found(error): return jsonify({'errcode':'PNUT_NOT_FOUND'}), 404 @app.errorhandler(403) def forbidden(error): return jsonify({'errcode':'PNUT_FORBIDDEN'}), 403 @app.teardown_appcontext def shutdown_session(exception=None): db_session.remove() @app.route("/_matrix/app/v1/rooms/") @app.route("/rooms/") async def query_alias(alias): logging.debug("--- query alias ---") alias_localpart = alias.split(":")[0][1:] channel_id = int(alias_localpart.split('_')[1]) room = Rooms.query.filter(Rooms.pnut_chan == channel_id).one_or_none() if room is not None: abort(404) token = app.config['MATRIX_PNUT_TOKEN'] pnutpy.api.add_authorization_token(token) try: logging.debug("---- getting the channel ----") channel, meta = pnutpy.api.get_channel(channel_id, include_channel_raw=1) if 'is_active' in channel and channel.is_active == False: logging.debug("-channel isn't active-") abort(404) if 'io.pnut.core.chat-settings' in channel.raw: for setting in channel.raw['io.pnut.core.chat-settings']: if 'name' in setting: name = setting['name'] else: name = None if 'description' in setting: topic = setting['description']['text'] else: topic = None matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) if channel.acl.read.public: visibility = RoomDirectoryVisibility.PUBLIC preset = RoomCreatePreset.PUBLIC else: visibility = RoomDirectoryVisibility.PRIVATE preset = RoomCreatePreset.PRIVATE room_id = await matrix_api.create_room(alias_localpart, visibility=visibility, preset=preset, name=name, topic=topic) if not channel.you_subscribed: pnutpy.api.subscribe_channel(channel_id) rr = Rooms( room_id=room_id, pnut_chan=channel_id, portal=True ) logging.debug(rr.room_id) logging.debug(rr.pnut_chan) db_session.add(rr) db_session.commit() except pnutpy.errors.PnutPermissionDenied: logging.debug("-permission denied-") abort(401) except Exception: logging.exception("-couldn't get the pnut channel-") abort(404) return jsonify({}) @app.route("/_matrix/app/v1/transactions/", methods=["PUT"]) @app.route("/transactions/", methods=["PUT"]) async def on_receive_events(transaction): access_token = request.args.get('access_token', '') if access_token != app.config['MATRIX_HS_TOKEN']: abort(403) events = request.get_json()["events"] for event in events: logging.debug(event) if (app.config['MATRIX_ADMIN_ROOM'] and app.config['MATRIX_ADMIN_ROOM'] == event['room_id']): await on_admin_event(event) return jsonify({}) user = Users.query.filter(Users.matrix_id == event['sender']).one_or_none() if event['type'] == 'm.room.message': await new_message(event, user) # elif event['type'] == 'm.sticker': # new_sticker(event, user) elif event['type'] == 'm.room.redaction': delete_message(event, user) 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']): logging.debug('----> direct invite <----') await on_direct_invite(event) return jsonify({}) if 'membership' in event['content']: if event['content']['membership'] == "leave": logging.debug('----> leave event <----') await on_leave_event(event) return jsonify({}) logging.debug("----room member event----") logging.debug(user) logging.debug(event) return jsonify({}) async def new_message(event, user): if (app.config['MATRIX_PNUT_PREFIX'] in event['sender'] or 'pnut-bridge' in event['sender']): logging.debug('-skipping dup event-') return if 'msgtype' not in event['content']: logging.debug('-unknown message type-') return control = ControlRooms.query.filter(ControlRooms.room_id == event['room_id']).one_or_none() if control is not None: await on_control_message(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, user, direct) room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none() if room is None: logging.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['sender']) if "displayname" in matrix_profile: prefix = (f"[{matrix_profile['displayname']}]" f" ({event['sender']})\n") else: prefix = "(" + event['sender'] + ")\n" pnutpy.api.add_authorization_token(token) raw = {} raw['io.pnut.core.crosspost'] = [crosspost_raw(event)] text, oembed = msg_from_event(event) text = prefix + text if oembed: raw['io.pnut.core.oembed'] = [oembed] logging.debug(oembed) try: msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': raw}) 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() # TODO: need to redo this for global message # if user is not None: # cctag = re.search('##$', text) # if cctag: # raw = [] # cname = get_channel_settings(room.pnut_chan)['name'] # text = text[:-2] # 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: logging.exception('-unable to post to pnut channel-') return except Exception: logging.exception('-something bad happened here-') return def msg_from_event(event): text = None oembed = None 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' or event['content']['msgtype'] == 'm.video' or event['content']['msgtype'] == 'm.audio'): oembed = oembed_from_event(event) if ('title' in oembed and 'url' in oembed): text = (f"[{oembed['title']}]" f"({oembed['url']})") elif event['content']['msgtype'] == 'm.file': file_url = event['content']['url'][6:] file_name = event['content']['body'] dl_url = (f"{app.config['MATRIX_URL']}" f"/_matrix/client/v1/media/download/{file_url}" f"/{file_name}") text = (f"[{file_name}]" f"({dl_url})") else: logging.debug('-unknown msg type- ' + event['content']['msgtype']) return return text, oembed def crosspost_raw(event): cross_profile = {'username': event['sender']} matrix_profile = get_profile(event['sender']) if "avatar_url" in matrix_profile: cross_profile['avatar_image'] = (f"{app.config['MATRIX_URL']}" f"/_matrix/media/r0/download/" f"{matrix_profile['avatar_url'][6:]}") crosspost = {} crosspost['canonical_url'] = (f"https://matrix.to/#/{event['room_id']}" f"/{event['event_id']}" f":{app.config['MATRIX_DOMAIN']}") crosspost['source'] = {'name': "matrix.", 'url': "https://matrix.org"} crosspost['user'] = cross_profile return crosspost # TODO: This could be used for uploading the media to pnut, maybe # async def media_from_event(event): # matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], # base_url=app.config['MATRIX_HOST'], # token=app.config['MATRIX_AS_TOKEN']) # # mxc_url = event['content']['url'] # media_file = await matrix_api.download_media(mxc_url) def oembed_from_event(event): media_url = event['content']['url'][6:] file_name = event['content']['body'] dl_url = (f"{app.config['MATRIX_URL']}" f"/_matrix/client/v1/media/download/{media_url}" f"/{file_name}") oembed = {} if event['content']['msgtype'] == 'm.image': oembed['provider_name'] = "matrix" oembed['provider_url'] = "https://matrix.org" oembed['version'] = "1.0" oembed['type'] = "photo" oembed['title'] = file_name oembed['url'] = dl_url if 'info' in event['content']: if 'h' in event['content']['info']: oembed['height'] = event['content']['info']['h'] if 'w' in event['content']['info']: oembed['width'] = event['content']['info']['w'] elif event['content']['msgtype'] == 'm.video': oembed['provider_name'] = "matrix" oembed['provider_url'] = "https://matrix.org" oembed['version'] = "1.0" oembed['type'] = "video" oembed['title'] = file_name oembed['url'] = dl_url if 'info' in event['content']: if 'h' in event['content']['info']: oembed['height'] = event['content']['info']['h'] if 'w' in event['content']['info']: oembed['width'] = event['content']['info']['w'] elif event['content']['msgtype'] == 'm.audio': oembed['provider_name'] = "matrix" oembed['provider_url'] = "https://matrix.org" oembed['version'] = "1.0" oembed['type'] = "audio" oembed['title'] = file_name oembed['url'] = dl_url if 'info' in event['content']: if 'duration' in event['content']['info']: oembed['duration'] = event['content']['info']['duration'] return oembed def delete_message(event, user): # TODO: should there be moderator handled redactions? if user is not None: token = user.pnut_user_token else: token = app.config['MATRIX_PNUT_TOKEN'] pnutpy.api.add_authorization_token(token) e = Events.query.filter(and_(Events.event_id == event['redacts'], Events.deleted == False)).one_or_none() if e is None: logging.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 db_session.commit() except pnutpy.errors.PnutPermissionDenied as e: pass def get_profile(userid): url = app.config['MATRIX_HOST'] + "/_matrix/client/r0/profile/" + userid r = requests.get(url) if r.status_code == 200: return json.loads(r.text) return userid def get_channel_settings(channel_id): channel_settings = {} try: channel, meta = pnutpy.api.get_channel(channel_id, include_raw=1) for item in channel.raw: if item.type == 'io.pnut.core.chat-settings': channel_settings = item.value except Exception: logging.exception('-unable to get channel settings-') return channel_settings async def create_pnut_matrix_room(channel, user): name = None topic = None alias_localpart = f"{app.config['MATRIX_PNUT_PREFIX']}{channel.id}" invitees = [user.matrix_id] if channel.acl.read.public: visibility = RoomDirectoryVisibility.PUBLIC preset = RoomCreatePreset.PUBLIC else: visibility = RoomDirectoryVisibility.PRIVATE preset = RoomCreatePreset.PRIVATE if 'io.pnut.core.chat-settings' in channel.raw: for setting in channel.raw['io.pnut.core.chat-settings']: if 'name' in setting: name = setting['name'] if 'description' in setting: topic = setting['description']['text'] matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) room_id = await matrix_api.create_room(alias_localpart, invitees=invitees, visibility=visibility, preset=preset, name=name, topic=topic) rr = Rooms(room_id=room_id, pnut_chan=channel.id, portal=True) db_session.add(rr) db_session.commit() def new_matrix_user(username): endpoint = "/_matrix/client/v3/register" url = app.config['MATRIX_HOST'] + endpoint params = {'kind': 'user'} data = { 'type': 'm.login.application_service', 'username': app.config['MATRIX_PNUT_PREFIX'] + username } headers = { "Content-Type": "application/json", "Authorization": "Bearer " + app.config['MATRIX_AS_TOKEN'] } r = requests.post(url, headers=headers, json=data, params=params) if r.status_code == 200: return else: errmsg = f"- unable to register {username} -" logging.warning(errmsg) logging.debug(r.status_code) logging.debug(r.text) return async def on_admin_event(event): matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) logging.debug("- admin room event recieved -") if event['type'] != 'm.room.message': return jsonify({}) msg = event['content']['body'].split(' ') try: if msg[0] == 'help': if len(msg) > 1: await matrix_api.send_message(event['room_id'], cmd_admin_help(msg[1])) else: await matrix_api.send_message(event['room_id'], cmd_admin_help()) elif msg[0] == 'list': await matrix_api.send_message(event['room_id'], cmd_admin_list()) elif msg[0] == 'unlink': if len(msg) > 1: await matrix_api.send_message(event['room_id'], cmd_admin_unlink(msg[1])) else: await matrix_api.send_message(event['room_id'], cmd_admin_help('unlink')) elif msg[0] == 'link': if len(msg) > 2: await matrix_api.send_message(event['room_id'], cmd_admin_link(msg[1], msg[2])) else: await matrix_api.send_message(event['room_id'], cmd_admin_help('link')) except Exception: errmsg = "- on_admin_event -" logging.exception(errmsg) def cmd_admin_help(cmd=None): help_usage = "help [command]" help_desc = "Show information about available commands." list_usage = "list" list_desc = "List the rooms currently linked with pnut.io." unlink_usage = "unlink | " unlink_desc = "Unlink a room between Matrix and pnut.io." link_usage = "link " link_desc = "Link a room between Matrix and pnut.io." if cmd == 'help': text = "usage: " + help_usage + "\n\n" text += help_desc if cmd == 'list': text = "usage: " + list_usage + "\n\n" text += list_desc elif cmd == 'unlink': text = "usage: " + unlink_usage + "\n\n" text += unlink_desc elif cmd == 'link': text = "usage: " + link_usage + "\n\n" text += link_desc else: text = "The following commands are available:\n\n" text += help_usage + "\n" text += list_usage + "\n" text += unlink_usage + "\n" text += link_usage + "\n" return TextMessageEventContent(msgtype='m.text', body=text) def cmd_admin_list(): text = "" rooms = Rooms.query.all() if len(rooms) > 0: text = "ID\tMATRIX ID\tPNUT CHANNEL\n" else: text = " - no rooms are currently linked - \n" for room in rooms: text += str(room.id) + '\t' text += room.room_id + '\t\t\t\t\t' text += str(room.pnut_chan) + '\t' if room.portal: text += "(portal)" text += '\n' return TextMessageEventContent(msgtype='m.text', body=text) async def cmd_admin_link(room_id, pnut_chan_id): matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN']) mrcheck = Rooms.query.filter(Rooms.room_id == room_id).one_or_none() pncheck = Rooms.query.filter(Rooms.pnut_chan == pnut_chan_id).one_or_none() if mrcheck is not None or pncheck is not None: text = "- room may already be linked -" return TextMessageEventContent(msgtype='m.text', body=text) try: channel, meta = pnutpy.api.subscribe_channel(pnut_chan_id) await matrix_api.join_room(room_id) rec = Rooms( room_id=room_id, pnut_chan=channel.id, portal=False ) db_session.add(rec) db_session.commit() except pnutpy.errors.PnutAuthAPIException: errmsg = "- unable to subscribe to channel -" logging.exception(errmsg) return TextMessageEventContent(msgtype='m.text', body=errmsg) except Exception: errmsg = "- unable to link room for some reason -" logging.exception(errmsg) return TextMessageEventContent(msgtype='m.text', body=errmsg) async def cmd_admin_unlink(rid): matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN']) if rid.startswith('!'): room = Rooms.query.filter(Rooms.room_id == rid).one_or_none() else: room = Rooms.query.filter(Rooms.pnut_chan == rid).one_or_none() if hasattr(room, 'portal'): if room.portal: alias = "#" + app.config['MATRIX_PNUT_PREFIX'] alias += str(room.pnut_chan) + ":" alias += app.config['MATRIX_DOMAIN'] await matrix_api.remove_room_alias(alias) # Kicking users needs at least moderator privs members = await matrix_api.get_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: await matrix_api.kick_user(room.room_id, m['sender'], reason=reason) else: prefix = f"@{app.config['MATRIX_PNUT_PREFIX']}" if m['sender'].startswith(prefix): await matrix_api.kick_user(room.room_id, m['sender'], reason=reason) try: channel, meta = pnutpy.api.unsubscribe_channel(room.pnut_chan) await matrix_api.leave_room(room.room_id) if room is not None: db_session.delete(room) db_session.commit() text = "- room has been unlinked -" else: text = "- unable to locate room to unlink -" return TextMessageEventContent(msgtype='m.text', body=text) except Exception: errmsg = "- error while unlinking room -" logging.exception(errmsg) return TextMessageEventContent(msgtype='m.text', body=errmsg) async def on_direct_invite(event): if event['state_key'] == app.config['MATRIX_AS_ID']: matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) dm = ControlRooms(room_id=event['room_id']) else: matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN'], as_user_id=event['state_key']) 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) new_matrix_user(pnut_user) dm = DirectRooms(room_id=event['room_id'], bridge_user=bridge_user, pnut_chan=channel.id) try: logging.debug('--> trying to join room <--') await matrix_api.join_room_by_id(event['room_id']) db_session.add(dm) db_session.commit() except Exception: errmsg = "- on_direct_invite -" logging.exception(errmsg) async def on_leave_event(event): direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none() if direct is not None: matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN'], as_user_id=direct.bridge_user.lower()) try: await matrix_api.leave_room(event['room_id']) db_session.delete(direct) db_session.commit() except Exception: errmsg = "- on_leave_event -" logging.exception(errmsg) control = ControlRooms.query.filter(ControlRooms.room_id == event['room_id']).one_or_none() if control is not None: matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) try: await matrix_api.leave_room(event['room_id']) db_session.delete(control) db_session.commit() except Exception: errmsg = "- on_leave_event -" logging.exception(errmsg) 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['sender']) if "displayname" in matrix_profile: prefix = (f"[{matrix_profile['displayname']}]" f" ({event['sender']})\n") else: prefix = "(" + event['sender'] + ")\n" pnutpy.api.add_authorization_token(token) raw = {} raw['io.pnut.core.crosspost'] = [crosspost_raw(event)] evtext, evraw = msg_from_event(event) text = prefix + evtext try: msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': raw}) 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: logging.exception('-unable to post to pnut channel-') except Exception: logging.exception('-something bad happened here-') return jsonify({}) async def on_control_message(event): matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) logging.debug("- direct room event received -") if event['type'] != 'm.room.message': return jsonify({}) msg = event['content']['body'].split(' ') try: if msg[0] == '!help' or msg[0] == 'help': if len(msg) > 1: await matrix_api.send_message(event['room_id'], cmd_user_help(msg[1])) else: await matrix_api.send_message(event['room_id'], cmd_user_help()) elif msg[0] == '!auth': await matrix_api.send_message(event['room_id'], cmd_user_auth()) elif msg[0] == '!save': if len(msg) > 1: await matrix_api.send_message(event['room_id'], cmd_user_save(event['sender'], msg[1])) else: await matrix_api.send_message(event['room_id'], cmd_user_save()) elif msg[0] == '!drop': await matrix_api.send_message(event['room_id'], cmd_user_drop(event['sender'])) elif msg[0] == '!status': await matrix_api.send_message(event['room_id'], cmd_user_status(event['sender'])) elif msg[0] == '!join': if len(msg) > 1: r = await cmd_user_join(event['sender'], msg[1]) await matrix_api.send_message(event['room_id'], r) else: r = await cmd_user_join(event['sender']) await matrix_api.send_message(event['room_id'], r) except Exception: errmsg = "- on_direct_message -" logging.exception(errmsg) 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" reply += "!join \t- Join a private channel on pnut.io\n" return TextMessageEventContent(msgtype='m.text', body=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 TextMessageEventContent(msgtype='m.text', body=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 TextMessageEventContent(msgtype='m.text', body=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 TextMessageEventContent(msgtype='m.text', body=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 TextMessageEventContent(msgtype='m.text', body=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 TextMessageEventContent(msgtype='m.text', body=reply) async 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 TextMessageEventContent(msgtype='m.text', body=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) room = Rooms.query.filter(Rooms.pnut_chan == channel_id).one_or_none() if room is None: await create_pnut_matrix_room(channel, user) else: matrix_api = ClientAPI(app.config['MATRIX_AS_ID'], base_url=app.config['MATRIX_HOST'], token=app.config['MATRIX_AS_TOKEN']) await 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" 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 TextMessageEventContent(msgtype='m.text', body=reply) 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 if __name__ == '__main__': configyaml = os.environ.get("CONFIG_FILE") with open(configyaml, "rb") as config_file: config = yaml.load(config_file, Loader=yaml.SafeLoader) logging.config.dictConfig(config['logging']) redact_filter = MLogFilter() logging.getLogger("werkzeug").addFilter(redact_filter) logging.getLogger("urllib3.connectionpool").addFilter(redact_filter) app.config.update(config) logging.basicConfig(level=logging.DEBUG) app.run(host=config['LISTEN_HOST'], port=config['LISTEN_PORT'])