Add support for private message #1 #70

Merged
thrrgilag merged 6 commits from pnut_pm into main 2021-03-20 13:26:34 +00:00
3 changed files with 259 additions and 120 deletions
Showing only changes of commit 249d21eea4 - Show all commits

View file

@ -8,7 +8,7 @@ import time
from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixHttpApi
from matrix_client.api import MatrixError, MatrixRequestError 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 database import db_session
from sqlalchemy import and_ from sqlalchemy import and_
from flask import Flask, jsonify, request, abort from flask import Flask, jsonify, request, abort
@ -94,7 +94,6 @@ def on_receive_events(transaction):
for event in events: for event in events:
logger.debug(event) 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']: if app.config['MATRIX_ADMIN_ROOM'] and app.config['MATRIX_ADMIN_ROOM'] == event['room_id']:
return on_admin_event(event) return on_admin_event(event)
@ -109,7 +108,7 @@ def on_receive_events(transaction):
elif event['type'] == 'm.room.member': elif event['type'] == 'm.room.member':
if 'is_direct' in event['content'] and 'membership' in event['content']: if 'is_direct' in event['content'] and 'membership' in event['content']:
if event['content']['membership'] == "invite" and event['content']['is_direct']: 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 'membership' in event['content']:
if event['content']['membership'] == "leave": if event['content']['membership'] == "leave":
@ -122,28 +121,26 @@ def on_receive_events(transaction):
return jsonify({}) return jsonify({})
def new_message(event, user): def new_message(event, user):
if app.config['MATRIX_PNUT_PREFIX'] in event['user_id'] or 'pnut-bridge' in event['user_id']: if app.config['MATRIX_PNUT_PREFIX'] in event['user_id'] or 'pnut-bridge' in event['user_id']:
logger.debug('-skipping dup event-') logger.debug('-skipping dup event-')
return 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-')
return
if 'msgtype' not in event['content']: if 'msgtype' not in event['content']:
logger.debug('-unknown message type-') logger.debug('-unknown message type-')
return return
cross_profile = {'username': event['user_id']} control = ControlRooms.query.filter(ControlRooms.room_id == event['room_id']).one_or_none()
matrix_profile = get_profile(event['user_id']) if control is not None:
if "avatar_url" in matrix_profile: return on_control_message(event)
cross_profile['avatar_image'] = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + matrix_profile['avatar_url'][6:]
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: if user is not None:
token = user.pnut_user_token token = user.pnut_user_token
@ -154,49 +151,10 @@ def new_message(event, user):
prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n" prefix = "[" + matrix_profile['displayname'] + "] (" + event['user_id'] + ")\n"
else: else:
prefix = "(" + event['user_id'] + ")\n" prefix = "(" + event['user_id'] + ")\n"
pnutpy.api.add_authorization_token(token) 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': embed = [crosspost_raw(event)]
text = event['content']['body'] text = prefix + msg_from_event(event)
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
try: try:
msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': embed}) msg, meta = pnutpy.api.create_message(room.pnut_chan, data={'text': text, 'raw': embed})
revent = Events( revent = Events(
@ -240,8 +198,57 @@ def new_message(event, user):
logger.exception('-something bad happened here-') logger.exception('-something bad happened here-')
return return
def raw_from_event(event): def msg_from_event(event):
text = 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':
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
return text
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:] url = app.config['MATRIX_URL'] + '/_matrix/media/r0/download/' + event['content']['url'][6:]
if event['content']['msgtype'] == 'm.image': if event['content']['msgtype'] == 'm.image':
@ -345,6 +352,7 @@ def on_admin_event(event):
msg = event['content']['body'].split(' ') msg = event['content']['body'].split(' ')
try:
if msg[0] == 'help': if msg[0] == 'help':
if len(msg) > 1: if len(msg) > 1:
matrix_api.send_message(event['room_id'], cmd_admin_help(msg[1])) matrix_api.send_message(event['room_id'], cmd_admin_help(msg[1]))
@ -366,6 +374,10 @@ def on_admin_event(event):
else: else:
matrix_api.send_message(event['room_id'], cmd_admin_help('link')) matrix_api.send_message(event['room_id'], cmd_admin_help('link'))
except Exception:
errmsg = "- on_admin_event -"
logger.exception(errmsg)
return jsonify({}) return jsonify({})
def cmd_admin_help(cmd=None): def cmd_admin_help(cmd=None):
@ -496,31 +508,107 @@ def cmd_admin_unlink(id):
logger.exception(errmsg) logger.exception(errmsg)
return errmsg return errmsg
def on_admin_invite(event): def on_direct_invite(event):
if event['state_key'] == app.config['MATRIX_AS_ID']:
matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'], matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN']) 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)
dm = DirectRooms(room_id=event['room_id'],
bridge_user=bridge_user, pnut_chan=channel.id)
try:
matrix_api.join_room(event['room_id']) matrix_api.join_room(event['room_id'])
direct = DirectRooms(room_id=event['room_id']) db_session.add(dm)
db_session.add(direct)
db_session.commit() db_session.commit()
except Exception:
errmsg = "- on_direct_invite -"
logger.exception(errmsg)
return jsonify({}) return jsonify({})
def on_leave_event(event): 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() direct = DirectRooms.query.filter(DirectRooms.room_id == event['room_id']).one_or_none()
if direct is not 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.delete(direct)
db_session.commit() 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']) 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({}) 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']
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)]
text = prefix + msg_from_event(event)
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'], matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN']) token=app.config['MATRIX_AS_TOKEN'])
logger.debug("- direct room event received -") logger.debug("- direct room event received -")
@ -530,6 +618,7 @@ def on_direct_message(event):
msg = event['content']['body'].split(' ') msg = event['content']['body'].split(' ')
try:
if msg[0] == '!help' or msg[0] == 'help': if msg[0] == '!help' or msg[0] == 'help':
if len(msg) > 1: if len(msg) > 1:
matrix_api.send_message(event['room_id'], cmd_user_help(msg[1])) matrix_api.send_message(event['room_id'], cmd_user_help(msg[1]))
@ -551,6 +640,10 @@ def on_direct_message(event):
elif msg[0] == '!status': elif msg[0] == '!status':
matrix_api.send_message(event['room_id'], cmd_user_status(event['sender'])) matrix_api.send_message(event['room_id'], cmd_user_status(event['sender']))
except Exception:
errmsg = "- on_direct_message -"
logger.exception(errmsg)
return jsonify({}) return jsonify({})
def cmd_user_help(cmd=None): def cmd_user_help(cmd=None):

View file

@ -18,6 +18,13 @@ class DirectRooms(Base):
__tablename__ = 'direct' __tablename__ = 'direct'
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
room_id = Column(String(250), unique=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): class Events(Base):
__tablename__ = 'events' __tablename__ = 'events'

View file

@ -12,7 +12,7 @@ import os
from matrix_client.api import MatrixHttpApi from matrix_client.api import MatrixHttpApi
from matrix_client.api import MatrixError, MatrixRequestError 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 database import db_session, init_db
from sqlalchemy import and_ from sqlalchemy import and_
from appservice import app from appservice import app
@ -22,7 +22,7 @@ logger = logging.getLogger()
_shutdown = threading.Event() _shutdown = threading.Event()
_reconnect = threading.Event() _reconnect = threading.Event()
def new_message(msg): def new_message(msg, meta):
logger.debug("channel: " + msg.channel_id) logger.debug("channel: " + msg.channel_id)
logger.debug("username: " + msg.user.username) logger.debug("username: " + msg.user.username)
if 'name' in msg.user: if 'name' in msg.user:
@ -36,7 +36,27 @@ def new_message(msg):
if msg.source.id == config['PNUTCLIENT_ID']: if msg.source.id == config['PNUTCLIENT_ID']:
return return
if meta['channel_type'] == 'io.pnut.core.chat':
room = Rooms.query.filter(Rooms.pnut_chan == msg.channel_id).one_or_none() 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)
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) logger.debug(room)
if room is None: if room is None:
logger.debug('-not_mapped-') logger.debug('-not_mapped-')
@ -245,6 +265,27 @@ def join_room(room_id, matrix_id):
logger.debug('-room_join-') 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): def on_message(ws, message):
# logger.debug("on_message: " + message) # logger.debug("on_message: " + message)
msg = json.loads(message) msg = json.loads(message)
@ -254,8 +295,8 @@ def on_message(ws, message):
if 'channel_type' in msg['meta']: if 'channel_type' in msg['meta']:
# TODO: bypassed other channel types for now if msg['meta']['channel_type'] not in ['io.pnut.core.chat',
if msg['meta']['channel_type'] != 'io.pnut.core.chat': 'io.pnut.core.pm']:
return return
for d_item in msg['data']: for d_item in msg['data']:
@ -266,9 +307,7 @@ def on_message(ws, message):
logger.debug("message: delete") logger.debug("message: delete")
delete_message(pmsg) delete_message(pmsg)
else: else:
logger.debug("uh whut?") new_message(pmsg, msg['meta'])
else:
new_message(pmsg)
def on_error(ws, error): def on_error(ws, error):
logger.error("on_error: !!! ERROR !!!") logger.error("on_error: !!! ERROR !!!")