917 lines
31 KiB
Python
917 lines
31 KiB
Python
import json
|
|
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
|
|
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':'COM.MONKEYSTEW.PNUT_NOT_FOUND'}), 404
|
|
|
|
@app.errorhandler(403)
|
|
def forbidden(error):
|
|
return jsonify({'errcode':'COM.MONKEYSTEW.PNUT_FORBIDDEN'}), 403
|
|
|
|
@app.teardown_appcontext
|
|
def shutdown_session(exception=None):
|
|
db_session.remove()
|
|
|
|
@app.route("/_matrix/app/v1/rooms/<alias>")
|
|
@app.route("/rooms/<alias>")
|
|
def query_alias(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:
|
|
channel, meta = pnutpy.api.get_channel(channel_id, include_raw=1)
|
|
|
|
if 'is_active' in channel and channel.is_active == False:
|
|
logger.debug("-channel isn't active-")
|
|
abort(404)
|
|
|
|
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': alias_localpart}
|
|
if 'name' in channel_settings:
|
|
room['name'] = channel_settings['name']
|
|
if 'description' in channel_settings:
|
|
room['topic'] = channel_settings['description']
|
|
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'
|
|
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)
|
|
rdata = r.json()
|
|
rr = Rooms(
|
|
room_id=rdata['room_id'],
|
|
pnut_chan=channel_id,
|
|
portal=True
|
|
)
|
|
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)
|
|
|
|
except Exception:
|
|
logger.exception("-couldn't get the pnut channel-")
|
|
abort(404)
|
|
|
|
return jsonify({})
|
|
|
|
@app.route("/_matrix/app/v1/transactions/<transaction>", methods=["PUT"])
|
|
@app.route("/transactions/<transaction>", methods=["PUT"])
|
|
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:
|
|
logger.debug(event)
|
|
|
|
if app.config['MATRIX_ADMIN_ROOM'] and app.config['MATRIX_ADMIN_ROOM'] == event['room_id']:
|
|
return on_admin_event(event)
|
|
|
|
user = Users.query.filter(Users.matrix_id == event['user_id']).one_or_none()
|
|
|
|
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)
|
|
|
|
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_direct_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({})
|
|
|
|
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']
|
|
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
|
|
|
|
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-')
|
|
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, 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 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()
|
|
|
|
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:
|
|
logger.exception('-unable to post to pnut channel-')
|
|
return
|
|
|
|
except Exception:
|
|
logger.exception('-something bad happened here-')
|
|
return
|
|
|
|
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':
|
|
value = {'type': "photo", 'version': "1.0"}
|
|
value['url'] = url
|
|
value['title'] = event['content']['body']
|
|
if 'info' in event['content']:
|
|
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']:
|
|
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
|
|
value = {'type': "html5video", 'version': "1.0"}
|
|
source = {'url': url}
|
|
value['title'] = event['content']['body']
|
|
if 'info' in event['content']:
|
|
value['width'] = event['content']['info']['w']
|
|
value['height'] = event['content']['info']['h']
|
|
source['type'] = event['content']['info']['mimetype']
|
|
else:
|
|
return None
|
|
value['sources'] = [source]
|
|
|
|
elif event['content']['msgtype'] == 'm.audio':
|
|
# TODO: Need to sort out the oembed for this media type
|
|
value = {'type': "audio", 'version': "1.0"}
|
|
return None
|
|
else:
|
|
return None
|
|
|
|
return {'type': "io.pnut.core.oembed", 'value': value}
|
|
|
|
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:
|
|
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
|
|
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:
|
|
logger.exception('-unable to get channel settings-')
|
|
|
|
return channel_settings
|
|
|
|
def create_room(channel, user):
|
|
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(user)
|
|
logger.debug(room)
|
|
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.public:
|
|
room['preset'] = 'public_chat'
|
|
room['visibility'] = 'public'
|
|
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'
|
|
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)
|
|
rdata = r.json()
|
|
rr = Rooms(
|
|
room_id=rdata['room_id'],
|
|
pnut_chan=channel.id,
|
|
portal=True
|
|
)
|
|
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)
|
|
|
|
def new_matrix_user(username):
|
|
matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
|
|
token=app.config['MATRIX_AS_TOKEN'])
|
|
data = {'type': 'm.login.application_service','username': 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'])
|
|
logger.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:
|
|
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] == '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'))
|
|
|
|
except Exception:
|
|
errmsg = "- on_admin_event -"
|
|
logger.exception(errmsg)
|
|
|
|
return jsonify({})
|
|
|
|
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 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 text
|
|
|
|
def cmd_admin_link(room_id, pnut_chan_id):
|
|
matrix_api = MatrixHttpApi(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:
|
|
return "- room may already be linked -"
|
|
|
|
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,
|
|
portal=False
|
|
)
|
|
db_session.add(rec)
|
|
db_session.commit()
|
|
|
|
except pnutpy.errors.PnutAuthAPIException:
|
|
errmsg = "- unable to subscribe to channel -"
|
|
logger.exception(errmsg)
|
|
return errmsg
|
|
|
|
except Exception:
|
|
errmsg = "- unable to link room for some reason -"
|
|
logger.exception(errmsg)
|
|
return errmsg
|
|
|
|
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 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']
|
|
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['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)
|
|
matrix_api.leave_room(room.room_id)
|
|
|
|
if room is not None:
|
|
db_session.delete(room)
|
|
db_session.commit()
|
|
return "- room has been unlinked -"
|
|
else:
|
|
return "- unable to locate room to unlink -"
|
|
|
|
except Exception:
|
|
errmsg = "- error while unlinking room -"
|
|
logger.exception(errmsg)
|
|
return errmsg
|
|
|
|
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'])
|
|
|
|
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)
|
|
new_matrix_user(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):
|
|
direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none()
|
|
if direct is not None:
|
|
|
|
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, 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 -")
|
|
|
|
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:
|
|
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']))
|
|
|
|
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({})
|
|
|
|
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 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 <token>"
|
|
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
|
|
|
|
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 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:
|
|
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"
|
|
|
|
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
|