pnut-matrix/appservice.py

956 lines
34 KiB
Python
Raw Normal View History

2017-03-04 21:45:09 +00:00
import json
import yaml
2017-03-04 21:45:09 +00:00
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 *
2017-03-04 21:45:09 +00:00
from models import Avatars, Rooms, Events, Users, DirectRooms, ControlRooms
2019-01-04 03:49:38 +00:00
from database import db_session
from sqlalchemy import and_
2017-03-04 21:45:09 +00:00
from flask import Flask, jsonify, request, abort
2019-01-04 03:49:38 +00:00
logger = logging.getLogger(__name__)
2017-03-04 21:45:09 +00:00
app = Flask(__name__)
2019-01-04 03:49:38 +00:00
@app.errorhandler(404)
def not_found(error):
return jsonify({'errcode':'PNUT_NOT_FOUND'}), 404
2019-01-04 03:49:38 +00:00
@app.errorhandler(403)
def forbidden(error):
return jsonify({'errcode':'PNUT_FORBIDDEN'}), 403
2019-01-04 03:49:38 +00:00
@app.teardown_appcontext
def shutdown_session(exception=None):
db_session.remove()
@app.route("/_matrix/app/v1/rooms/<alias>")
2019-01-04 03:49:38 +00:00
@app.route("/rooms/<alias>")
async def query_alias(alias):
logging.debug("--- query alias ---")
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
if 'is_active' in channel and channel.is_active == False:
logging.debug("-channel isn't active-")
2019-01-04 03:49:38 +00:00
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'])
2019-01-04 03:49:38 +00:00
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:
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
except Exception:
logging.exception("-couldn't get the pnut channel-")
2019-01-04 03:49:38 +00:00
abort(404)
2017-05-05 03:23:02 +00:00
2019-01-04 03:49:38 +00:00
return jsonify({})
2017-03-04 21:45:09 +00:00
@app.route("/_matrix/app/v1/transactions/<transaction>", methods=["PUT"])
2017-03-04 21:45:09 +00:00
@app.route("/transactions/<transaction>", methods=["PUT"])
async def on_receive_events(transaction):
2019-01-04 03:49:38 +00:00
access_token = request.args.get('access_token', '')
if access_token != app.config['MATRIX_HS_TOKEN']:
abort(403)
2017-03-04 21:45:09 +00:00
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({})
2017-03-04 21:45:09 +00:00
user = Users.query.filter(Users.matrix_id ==
event['sender']).one_or_none()
2017-03-04 21:45:09 +00:00
2019-01-04 03:49:38 +00:00
if event['type'] == 'm.room.message':
await new_message(event, user)
2017-03-04 21:45:09 +00:00
# elif event['type'] == 'm.sticker':
# new_sticker(event, user)
2022-07-01 21:58:32 +00:00
2019-01-04 03:49:38 +00:00
elif event['type'] == 'm.room.redaction':
delete_message(event, user)
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
return jsonify({})
2018-05-09 00:58:43 +00:00
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-')
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none()
if room is None:
logging.debug('-room not mapped-')
2019-01-04 03:49:38 +00:00
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"
2019-01-04 03:49:38 +00:00
pnutpy.api.add_authorization_token(token)
2017-05-04 06:01:51 +00:00
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)
2019-01-04 03:49:38 +00:00
try:
msg, meta = pnutpy.api.create_message(room.pnut_chan,
data={'text': text, 'raw': raw})
2019-01-04 03:49:38 +00:00
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})
2019-01-04 03:49:38 +00:00
except pnutpy.errors.PnutAuthAPIException:
logging.exception('-unable to post to pnut channel-')
2019-01-04 03:49:38 +00:00
return
2019-01-04 03:49:38 +00:00
except Exception:
logging.exception('-something bad happened here-')
2019-01-04 03:49:38 +00:00
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']})")
2019-01-04 03:49:38 +00:00
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 = {}
2019-01-04 03:49:38 +00:00
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
2019-01-04 03:49:38 +00:00
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']
2019-01-04 03:49:38 +00:00
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
2019-01-04 03:49:38 +00:00
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']
2017-03-04 21:45:09 +00:00
2019-01-04 03:49:38 +00:00
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
2019-01-04 03:49:38 +00:00
def delete_message(event, user):
2017-03-04 21:45:09 +00:00
2019-01-04 03:49:38 +00:00
# TODO: should there be moderator handled redactions?
2017-03-04 21:45:09 +00:00
2019-01-04 03:49:38 +00:00
if user is not None:
token = user.pnut_user_token
else:
token = app.config['MATRIX_PNUT_TOKEN']
pnutpy.api.add_authorization_token(token)
2017-03-04 21:45:09 +00:00
e = Events.query.filter(and_(Events.event_id == event['redacts'],
Events.deleted == False)).one_or_none()
2019-01-04 03:49:38 +00:00
if e is None:
logging.debug("- can't find the event to remove -")
2019-01-04 03:49:38 +00:00
return
2023-02-16 02:08:51 +00:00
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
2017-03-04 21:45:09 +00:00
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)
2017-04-25 02:05:17 +00:00
return userid
def get_channel_settings(channel_id):
2019-01-04 03:49:38 +00:00
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-')
2019-01-04 03:49:38 +00:00
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 -")
2019-01-04 03:49:38 +00:00
if event['type'] != 'm.room.message':
return jsonify({})
2023-02-16 02:08:51 +00:00
2019-01-04 03:49:38 +00:00
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())
2019-01-04 03:49:38 +00:00
elif msg[0] == 'list':
await matrix_api.send_message(event['room_id'], cmd_admin_list())
2019-01-04 03:49:38 +00:00
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'))
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
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 <room_id> | <pnut_channel_id>"
unlink_desc = "Unlink a room between Matrix and pnut.io."
link_usage = "link <room_id> <pnut_channel_id>"
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)
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
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'])
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
try:
channel, meta = pnutpy.api.subscribe_channel(pnut_chan_id)
await matrix_api.join_room(room_id)
2023-02-16 02:08:51 +00:00
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
except Exception:
errmsg = "- unable to link room for some reason -"
logging.exception(errmsg)
return TextMessageEventContent(msgtype='m.text', body=errmsg)
2019-01-04 03:49:38 +00:00
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'])
2019-01-04 03:49:38 +00:00
pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN'])
if rid.startswith('!'):
room = Rooms.query.filter(Rooms.room_id == rid).one_or_none()
2019-01-04 03:49:38 +00:00
else:
room = Rooms.query.filter(Rooms.pnut_chan == rid).one_or_none()
2019-01-04 03:49:38 +00:00
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)
2019-01-04 03:49:38 +00:00
try:
channel, meta = pnutpy.api.unsubscribe_channel(room.pnut_chan)
await matrix_api.leave_room(room.room_id)
2019-01-04 03:49:38 +00:00
if room is not None:
db_session.delete(room)
db_session.commit()
text = "- room has been unlinked -"
2019-01-04 03:49:38 +00:00
else:
text = "- unable to locate room to unlink -"
return TextMessageEventContent(msgtype='m.text', body=text)
2019-01-04 03:49:38 +00:00
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-')
2023-02-16 02:08:51 +00:00
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({})
2023-02-16 02:08:51 +00:00
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 <token>\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 <channel #>\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 <token>"
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:
2023-02-16 02:08:51 +00:00
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 <channel #>"
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<redacted>\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<redacted>\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'])