replaced sqlalchemy with peewee for orm and revamped schema

Also covers issue #62
This commit is contained in:
Morgan McMillian 2024-12-22 10:31:30 -08:00
parent 734be1edda
commit 3d96f910ee
5 changed files with 263 additions and 404 deletions

View file

@ -17,11 +17,11 @@ dependencies = [
"requests",
"Flask[async]",
"pnutpy>=0.5.0",
"sqlalchemy",
"filemagic",
"mautrix>=0.20.6,<0.21",
"websockets",
"asyncclick",
"peewee",
]
[project.urls]

View file

@ -11,10 +11,7 @@ import os
from mautrix.client import ClientAPI
from mautrix.types import *
from pnut_matrix.models import Avatars, Rooms, Events, Users, DirectRooms, ControlRooms
from pnut_matrix.database import db_session
from sqlalchemy import and_
from pnut_matrix.models import *
from flask import Flask, jsonify, request, abort
logger = logging.getLogger(__name__)
@ -29,10 +26,6 @@ def not_found(error):
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/<alias>")
@app.route("/rooms/<alias>")
async def query_alias(alias):
@ -40,7 +33,8 @@ async 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()
room = PnutChannels.select().where(PnutChannels.pnut_chan ==
channel_id).first()
if room is not None:
abort(404)
@ -58,9 +52,9 @@ async def query_alias(alias):
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']
name = f"🥜 {setting['name']}"
else:
name = None
name = f"🥜 channel {channel.id}"
if 'description' in setting:
topic = setting['description']['text']
else:
@ -84,15 +78,11 @@ async def query_alias(alias):
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()
room = PnutChannels(room_id=room_id, pnut_chan=channel_id)
room.save()
logging.debug(f'-created new room for channel {room.pnut_chan}-')
logging.debug(room.room_id)
except pnutpy.errors.PnutPermissionDenied:
logging.debug("-permission denied-")
@ -114,23 +104,25 @@ async def on_receive_events(transaction):
events = request.get_json()["events"]
for event in events:
logging.debug('-----event-----')
logging.debug(event)
logging.debug('~~~~~~~~~~~~~~~')
if (app.config['MATRIX_ADMIN_ROOM'] and
app.config['MATRIX_ADMIN_ROOM'] == event['room_id']):
logging.debug('>----on_admin_event----<')
await on_admin_event(event)
return jsonify({})
user = Users.query.filter(Users.matrix_id ==
event['sender']).one_or_none()
user = PnutUsers.select().where(PnutUsers.matrix_id ==
event['sender']).first()
if event['type'] == 'm.room.message':
logging.debug('>----new_message----<')
await new_message(event, user)
# elif event['type'] == 'm.sticker':
# new_sticker(event, user)
elif event['type'] == 'm.room.redaction':
logging.debug('>----delete_message----<')
delete_message(event, user)
elif event['type'] == 'm.room.member':
@ -138,59 +130,55 @@ async def on_receive_events(transaction):
'membership' in event['content']):
if (event['content']['membership'] == "invite" and
event['content']['is_direct']):
logging.debug('----> direct invite <----')
logging.debug('>----on_direct_invite----<')
await on_direct_invite(event)
return jsonify({})
if 'membership' in event['content']:
if event['content']['membership'] == "leave":
logging.debug('----> leave event <----')
logging.debug('>----on_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-')
if event['sender'] == app.config['MATRIX_AS_ID']:
return
if 'msgtype' not in event['content']:
logging.debug('-unknown message type-')
if app.config['MATRIX_PNUT_PREFIX'] in event['sender']:
return
control = ControlRooms.query.filter(ControlRooms.room_id ==
event['room_id']).one_or_none()
if control is not None:
await on_control_message(event)
if user.room_id == event['room_id']:
await on_control_message(event, user)
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 = PnutChannels.select().where(PnutChannels.room_id ==
event['room_id']).first()
logging.debug(f'room: {room}')
room = Rooms.query.filter(Rooms.room_id == event['room_id']).one_or_none()
if room is None:
logging.debug('-room not mapped-')
return
if room.is_direct:
logging.debug('>----on_direct_message----<')
return 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:
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 = {}
@ -204,16 +192,13 @@ async def new_message(event, user):
try:
msg, meta = pnutpy.api.create_message(room.pnut_chan,
data={'text': text, 'raw': raw})
revent = Events(
bridge_event = 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
pnut_id=msg.id,
pnut_channel=room.pnut_chan
)
db_session.add(revent)
db_session.commit()
bridge_event.save()
# TODO: need to redo this for global message
# if user is not None:
@ -362,8 +347,8 @@ def delete_message(event, user):
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()
e = Events.select().where((Events.event_id == events['redacts']) &
(Events.deleted == False)).first()
if e is None:
logging.debug("- can't find the event to remove -")
return
@ -371,7 +356,7 @@ def delete_message(event, user):
try:
r, meta = pnutpy.api.delete_message(e.pnut_chan_id, e.pnut_msg_id)
e.deleted = True
db_session.commit()
e.save()
except pnutpy.errors.PnutPermissionDenied as e:
pass
@ -406,9 +391,11 @@ async def create_pnut_matrix_room(channel, user):
if channel.acl.read.public:
visibility = RoomDirectoryVisibility.PUBLIC
preset = RoomCreatePreset.PUBLIC
is_private = False
else:
visibility = RoomDirectoryVisibility.PRIVATE
preset = RoomCreatePreset.PRIVATE
is_private = True
if 'io.pnut.core.chat-settings' in channel.raw:
for setting in channel.raw['io.pnut.core.chat-settings']:
@ -428,9 +415,20 @@ async def create_pnut_matrix_room(channel, user):
name=name,
topic=topic)
rr = Rooms(room_id=room_id, pnut_chan=channel.id, portal=True)
db_session.add(rr)
db_session.commit()
room = PnutChannels(room_id=room_id, pnut_chan=channel.id,
is_private=is_private)
room.save()
if is_private:
chan_member = PnutPrivateChanMembers(pnut_chan=channel.id,
room_id=room_id,
pnut_user_id=user.pnut_user_id,
matrix_id=user.matrix_id)
chan_member.save()
logging.debug('-create_pnut_matrix_room-')
logging.debug(f'-created new room for channel {room.pnut_chan}-')
logging.debug(room.room_id)
def new_matrix_user(username):
endpoint = "/_matrix/client/v3/register"
@ -479,22 +477,6 @@ async def on_admin_event(event):
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)
@ -504,10 +486,6 @@ def cmd_admin_help(cmd=None):
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"
@ -532,7 +510,7 @@ def cmd_admin_help(cmd=None):
def cmd_admin_list():
text = ""
rooms = Rooms.query.all()
rooms = PnutChannels.select()
if len(rooms) > 0:
text = "ID\tMATRIX ID\tPNUT CHANNEL\n"
@ -543,108 +521,21 @@ def cmd_admin_list():
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):
# direct chat with the appservice user
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:
dm = PnutUsers(matrix_id=event['sender'], room_id=event['room_id'])
# direct chat with another pnut user
elif app.config['MATRIX_PNUT_PREFIX'] in event['state_key']:
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'],
@ -654,62 +545,76 @@ async def on_direct_invite(event):
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)
user = PnutUsers.select().where(PnutUsers.matrix_id ==
event['sender']).first()
dm = DirectRooms(room_id=event['room_id'],
bridge_user=bridge_user, pnut_chan=channel.id)
if user is not None:
pnutpy.api.add_authorization_token(user.pnut_user_token)
try:
channel, meta = pnutpy.api.existing_pm(ids=pnut_user)
new_matrix_user(pnut_user)
dm = PnutChannels(pnut_chan=channel.id,
room_id=event['room_id'],
is_direct=True,
direct_pnut_user=bridge_user,
direct_mtrx_user=user.matrix_id)
except pnutpy.errors.PnutAuthAPIException:
abort(403)
else:
abort(403)
else:
return
try:
logging.debug('--> trying to join room <--')
await matrix_api.join_room_by_id(event['room_id'])
db_session.add(dm)
db_session.commit()
dm.save()
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:
direct_room = PnutChannels.select().where(PnutChannels.room_id ==
event['room_id']).first()
user = PnutUsers.select().where(PnutUsers.room_id ==
event['room_id']).first()
if direct_room 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())
as_user_id=direct_room.direct_pnut_user.lower())
try:
await matrix_api.leave_room(event['room_id'])
db_session.delete(direct)
db_session.commit()
direct_room.delete_instance()
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:
if user 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()
user.room_id = None
user.save()
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 = ""
@ -727,19 +632,17 @@ def on_direct_message(event, user, room):
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(
bridge_event = 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
pnut_id=msg.id,
pnut_channel=room.pnut_chan
)
db_session.add(revent)
db_session.commit()
bridge_event.save()
except pnutpy.errors.PnutAuthAPIException:
logging.exception('-unable to post to pnut channel-')
@ -749,11 +652,10 @@ def on_direct_message(event, user, room):
return jsonify({})
async def on_control_message(event):
async def on_control_message(event, user):
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({})
@ -775,26 +677,25 @@ async def on_control_message(event):
elif msg[0] == '!save':
if len(msg) > 1:
await matrix_api.send_message(event['room_id'],
cmd_user_save(event['sender'],
msg[1]))
cmd_user_save(user, 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']))
r = await cmd_user_drop(user)
await matrix_api.send_message(event['room_id'], r)
elif msg[0] == '!status':
await matrix_api.send_message(event['room_id'],
cmd_user_status(event['sender']))
cmd_user_status(user))
elif msg[0] == '!join':
if len(msg) > 1:
r = await cmd_user_join(event['sender'], msg[1])
r = await cmd_user_join(user, msg[1])
await matrix_api.send_message(event['room_id'], r)
else:
r = await cmd_user_join(event['sender'])
r = await cmd_user_join(user)
await matrix_api.send_message(event['room_id'], r)
except Exception:
@ -822,7 +723,7 @@ def cmd_user_auth():
return TextMessageEventContent(msgtype='m.text', body=reply)
def cmd_user_save(sender=None, token=None):
def cmd_user_save(user, token=None):
if token is None:
reply = "You must provide a token with this command.\n"
reply += "!save <token>"
@ -832,13 +733,9 @@ def cmd_user_save(sender=None, token=None):
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()
user.pnut_user_id = response.id
user.pnut_user_token = token
user.save()
reply = "Success! You are now authorized as " + response['username']
@ -851,25 +748,37 @@ def cmd_user_save(sender=None, token=None):
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."
async def cmd_user_drop(user):
except Exception as e:
logging.exception('!drop')
reply = "Error! Problem removing your token."
direct_rooms = PnutChannels.select().where(PnutChannels.direct_mtrx_user ==
user.matrix_id)
for dir_room in direct_rooms:
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'],
as_user_id=dir_room.direct_pnut_user.lower())
await matrix_api.leave_room(dir_room.room_id)
dir_room.delete_instance()
private_rooms = PnutPrivateChanMembers.select().where(
PnutPrivateChanMembers.pnut_user_id == user.pnut_user_id)
for priv_room in private_rooms:
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'])
await matrix_api.kick_user(priv_room.room_id, user.matrix_id,
reason='user left from bridge')
priv_room.delete_instance()
user.pnut_user_id = None
user.pnut_user_token = None
user.save()
reply = "Success! Your auth token has been removed."
return TextMessageEventContent(msgtype='m.text', body=reply)
def cmd_user_status(sender=None):
def cmd_user_status(user):
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:
@ -886,28 +795,27 @@ def cmd_user_status(sender=None):
return TextMessageEventContent(msgtype='m.text', body=reply)
async def cmd_user_join(sender=None, channel_id=None):
async def cmd_user_join(user, 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()
room = PnutChannels.select().where(PnutChannels.pnut_chan ==
channel_id).first()
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)
await matrix_api.invite_user(room.room_id, user.matrix_id)
reply = "ok"
except pnutpy.errors.PnutAuthAPIException as e:
@ -952,6 +860,10 @@ def main():
app.config.update(config)
logging.basicConfig(level=logging.DEBUG)
db.init(config['SERVICE_DB'])
db_create_tables()
app.run(host=config['LISTEN_HOST'], port=config['LISTEN_PORT'])
if __name__ == '__main__':

View file

@ -1,24 +0,0 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import scoped_session, sessionmaker
from sqlalchemy.ext.declarative import declarative_base
import yaml
import os
configyaml = os.environ.get("CONFIG_FILE")
with open(configyaml, "rb") as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
engine = create_engine(config['SERVICE_DB'])
db_session = scoped_session(sessionmaker(bind=engine))
Base = declarative_base()
Base.query = db_session.query_property()
def init_db():
# import all modules here that might define models so that
# they will be registered properly on the metadata. Otherwise
# you will have to import them first before calling init_db()
import pnut_matrix.models
Base.metadata.create_all(bind=engine)

View file

@ -1,44 +1,48 @@
from sqlalchemy import Column, ForeignKey, Integer, String, Boolean
from pnut_matrix.database import Base
import logging
class Avatars(Base):
__tablename__ = 'avatars'
id = Column(Integer, primary_key=True)
pnut_user = Column(String(250), unique=True)
avatar = Column(String(250))
from peewee import *
from playhouse.migrate import *
class Rooms(Base):
__tablename__ = 'rooms'
id = Column(Integer, primary_key=True)
room_id = Column(String(250), unique=True)
pnut_chan = Column(Integer, unique=True)
portal = Column(Boolean)
db = SqliteDatabase(None)
migrator = SqliteMigrator(db)
class DirectRooms(Base):
__tablename__ = 'direct'
id = Column(Integer, primary_key=True)
room_id = Column(String(250), unique=True)
pnut_chan = Column(Integer, unique=True)
bridge_user = Column(String(250))
class BaseModel(Model):
class Meta:
database = db
class ControlRooms(Base):
__tablename__ = 'control'
id = Column(Integer, primary_key=True)
room_id = Column(String(250), unique=True)
class PnutAvatars(BaseModel):
pnut_user = CharField(unique=True)
avatar_url = CharField()
class Events(Base):
__tablename__ = 'events'
id = Column(Integer, primary_key=True)
event_id = Column(String(250))
room_id = Column(String(250))
pnut_msg_id = Column(Integer)
pnut_user_id = Column(Integer)
pnut_chan_id = Column(Integer)
deleted = Column(Boolean)
class PnutChannels(BaseModel):
pnut_chan = IntegerField(unique=True)
room_id = CharField()
is_private = BooleanField(default=False)
is_direct = BooleanField(default=False)
direct_pnut_user = CharField(null=True)
direct_mtrx_user = CharField(null=True)
class Users(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
matrix_id = Column(String(250))
pnut_user_id = Column(Integer)
pnut_user_token = Column(String(250))
class PnutPrivateChanMembers(BaseModel):
pnut_chan = IntegerField()
room_id = CharField()
pnut_user_id = IntegerField()
matrix_id = CharField()
class Events(BaseModel):
event_id = CharField(unique=True)
room_id = CharField()
pnut_id = IntegerField()
pnut_channel = IntegerField()
revised = BooleanField(default=False)
deleted = BooleanField(default=False)
class PnutUsers(BaseModel):
matrix_id = CharField(unique=True)
room_id = CharField()
pnut_user_id = IntegerField(unique=True, null=True)
pnut_user_token = CharField(null=True)
def db_create_tables():
with db:
db.create_tables([PnutUsers, Events, PnutChannels, PnutAvatars,
PnutPrivateChanMembers])

View file

@ -19,13 +19,13 @@ from mautrix.errors.request import MNotFound, MForbidden
from websockets.asyncio.client import connect
from websockets.exceptions import ConnectionClosed
from pnut_matrix.models import Avatars, Rooms, Events, DirectRooms, Users
from pnut_matrix.database import db_session, init_db
from sqlalchemy import and_
from pnut_matrix.models import *
logger = logging.getLogger()
config = None
matrix_url = None
class MLogFilter(logging.Filter):
@ -64,12 +64,11 @@ async def new_pnut_message(msg, meta):
token=config['MATRIX_AS_TOKEN'],
as_user_id=matrix_id.lower())
if meta['channel_type'] == 'io.pnut.core.chat':
room = Rooms.query.filter(Rooms.pnut_chan ==
msg.channel_id).one_or_none()
elif meta['channel_type'] == 'io.pnut.core.pm':
room = DirectRooms.query.filter(DirectRooms.pnut_chan ==
msg.channel_id).one_or_none()
channel_id = int(msg.channel_id)
room = PnutChannels.select().where(PnutChannels.pnut_chan ==
channel_id).first()
if meta['channel_type'] == 'io.pnut.core.pm':
if room is None:
# Do do an invite from the bridge user?
logger.debug('new invite?')
@ -77,21 +76,22 @@ async def new_pnut_message(msg, meta):
# subscribed_user_ids from meta
logger.debug(meta['subscribed_user_ids'])
pnut_user = matrix_id_from_pnut(msg.user.username)
profile = await matrix_api.get_profile(matrix_id.lower())
if not profile:
try:
profile = await matrix_api.get_profile(matrix_id.lower())
except MNotFound:
new_matrix_user(msg.user.username)
invitees=[]
for pm_user in meta['subscribed_user_ids']:
user = Users.query.filter(Users.pnut_user_id ==
pm_user).one_or_none()
user = PnutUsers.select().where(PnutUsers.pnut_user_id ==
pm_user).first()
if int(pm_user) == msg.user.id:
continue
if user is not None:
invitees.append(user.matrix_id)
if len(invitees) > 0:
room = new_room(pnut_user, invitees, msg.channel_id)
else:
room = None
logger.debug(room)
if room is None:
@ -111,17 +111,19 @@ async def new_pnut_message(msg, meta):
await set_matrix_display(msg.user)
logger.debug('-set_display-')
avatar = Avatars.query.filter(Avatars.pnut_user ==
msg.user.username).one_or_none()
if avatar is None or avatar.avatar != msg.user.content.avatar_image.url:
avatar = PnutAvatars.select().where(PnutAvatars.pnut_user ==
msg.user.username).first()
if avatar is None or avatar.avatar_url != msg.user.content.avatar_image.url:
await set_matrix_avatar(msg.user)
logger.debug('-set_avatar-')
# members = matrix_api.get_room_members(room.room_id)
# logger.debug(members)
# join_room(room.room_id, config['MATRIX_AS_ID'])
# TODO: sort out room invite and join logic
await join_room(room.room_id, matrix_id)
if room.is_private:
matrix_api_as = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
await matrix_api_as.invite_user(room.room_id, matrix_id.lower())
await matrix_api.join_room(room.room_id)
if 'content' in msg:
eventtext = TextMessageEventContent(msgtype=MessageType.TEXT,
@ -129,15 +131,13 @@ async def new_pnut_message(msg, meta):
body=msg.content.text,
formatted_body=msg.content.html)
rid = await matrix_api.send_message(room.room_id, eventtext)
event = Events(
bridge_event = Events(
event_id=rid,
room_id=room.room_id,
pnut_msg_id=msg.id,
pnut_user_id=msg.user.id,
pnut_chan_id=msg.channel_id,
deleted=False)
db_session.add(event)
db_session.commit()
pnut_id=msg.id,
pnut_channel=msg.channel_id
)
bridge_event.save()
if 'raw' in msg:
logger.debug('-handle media uploads-')
@ -177,15 +177,15 @@ async def new_pnut_post(post, meta):
await set_matrix_display(post.user)
logger.debug('-set_display-')
avatar = Avatars.query.filter(Avatars.pnut_user ==
post.user.username).one_or_none()
avatar = PnutAvatars.select().where(PnutAvatars.pnut_user ==
post.user.username).first()
if (avatar is None or
avatar.avatar != post.user.content.avatar_image.url):
avatar.avatar_url != post.user.content.avatar_image.url):
await set_matrix_avatar(post.user)
logger.debug('-set_avatar-')
room_id = config['MATRIX_GLOBAL_ROOM']
await join_room(room_id, matrix_id)
await matrix_api.join_room(room_id)
postlink = f"https://posts.pnut.io/{post.id}"
plaintext = f"{post.content.text}\n{postlink}"
htmltext = (f"{post.content.html}"
@ -195,15 +195,13 @@ async def new_pnut_post(post, meta):
body=plaintext,
formatted_body=htmltext)
rid = await matrix_api.send_message(room_id, eventtext)
event = Events(
bridge_event = Events(
event_id=rid,
room_id=room_id,
pnut_msg_id=post.id,
pnut_user_id=post.user.id,
pnut_chan_id=0,
deleted=False)
db_session.add(event)
db_session.commit()
pnut_id=post.id,
pnut_channel=0
)
bridge_event.save()
if 'raw' in post:
logger.debug('-handle media uploads-')
@ -231,7 +229,7 @@ async def new_media(room_id, msg):
dl_url = oembed.url
elif oembed.type == 'video':
msgtype = 'm.video'
dl_url = oembed.url
dl_url = oembed.embeddable_url
info['h'] = oembed.height
info['w'] = oembed.width
elif oembed.type == 'html5video':
@ -267,15 +265,13 @@ async def new_media(room_id, msg):
channel_id = msg.channel_id
else:
channel_id = 0
event = Events(
bridge_event = Events(
event_id=rid,
room_id=room_id,
pnut_msg_id=msg.id,
pnut_user_id=msg.user.id,
pnut_chan_id=channel_id,
deleted=False)
db_session.add(event)
db_session.commit()
pnut_id=msg.id,
pnut_channel=channel_id
)
bridge_event.save()
async def delete_message(msg):
matrix_id = matrix_id_from_pnut(msg.user.username)
@ -284,12 +280,12 @@ async def delete_message(msg):
token=config['MATRIX_AS_TOKEN'],
as_user_id=matrix_id.lower())
events = Events.query.filter(and_(Events.pnut_msg_id ==
msg.id, Events.deleted == False)).all()
events = Events.select().where((Events.pnut_id == msg.id) &
(Events.deleted == False))
for event in events:
await matrix_api.redact(event.room_id, event.event_id)
event.deleted = True
db_session.commit()
event.save()
def matrix_id_from_pnut(username):
matrix_id = (f"@{config['MATRIX_PNUT_PREFIX']}{username}"
@ -334,15 +330,17 @@ async def set_matrix_avatar(user):
try:
await matrix_api.set_avatar_url(ul)
avatar = Avatars.query.filter(Avatars.pnut_user ==
user.username).one_or_none()
avatar = PnutAvatars.select().where(PnutAvatars.pnut_user ==
user.username).first()
if avatar is None:
avatar = Avatars(pnut_user=user.username,
avatar=user.content.avatar_image.url)
db_session.add(avatar)
avatar = PnutAvatars(pnut_user=user.username,
avatar_url=user.content.avatar_image.url)
else:
avatar.avatar = user.content.avatar_image.url
db_session.commit()
avatar.avatar_url = user.content.avatar_image.url
avatar.save()
except Exception:
logger.exception('failed to set user avatar')
@ -371,39 +369,6 @@ def new_matrix_user(username):
logger.debug(r.text)
return
async def join_room(room_id, matrix_id):
logging.debug('----- trying to join room -----')
matrix_api_as = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
as_user_id=matrix_id.lower())
try:
await matrix_api.join_room(room_id)
# logging.debug('----- should be joined -----')
except MForbidden:
# logging.debug('------ got a forbidden ------')
await matrix_api_as.invite_user(room_id, matrix_id.lower())
await matrix_api.join_room(room_id)
except MatrixConnectionError:
# logger.debug(e)
# if 'code' in e and e.code == 403:
# await matrix_api_as.invite_user(room_id, matrix_id)
# await matrix_api.join_room(room_id)
# else:
logging.debug('------- moar join errors -------')
logger.exception('failed to join room')
logger.debug(f"{room_id}")
logger.debug('-room_join-')
def new_room(pnut_user, invitees, chan):
dr = None
url = matrix_url + '/createRoom'
@ -424,13 +389,11 @@ def new_room(pnut_user, invitees, chan):
logger.debug(r.text)
logger.debug(response)
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
direct_room = PnutChannels(pnut_chan=chan, room_id=response['room_id'],
is_direct=True, direct_pnut_user=pnut_user,
direct_mtrx_user=bridge_user)
direct_room.save()
return direct_room
async def on_message(message):
logger.debug("on_message: " + message)
@ -473,7 +436,10 @@ async def on_message(message):
async def asmain():
if config['MATRIX_ADMIN_ROOM']:
logger.debug("- sould join admin room -")
await join_room(config['MATRIX_ADMIN_ROOM'], config['MATRIX_AS_ID'])
matrix_api_as = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
await matrix_api_as.join_room(config['MATRIX_ADMIN_ROOM'])
ws_url = 'wss://stream.pnut.io/v1/app?access_token='
ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY']
@ -490,6 +456,7 @@ async def asmain():
def main():
global config
global matrix_url
a_parser = argparse.ArgumentParser()
a_parser.add_argument(
'-d', action='store_true', dest='debug',
@ -504,6 +471,9 @@ def main():
with open(configyaml, "rb") as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
db.init(config['SERVICE_DB'])
db_create_tables()
logging.config.dictConfig(config['logging'])
redact_filter = MLogFilter()
logging.getLogger("werkzeug").addFilter(redact_filter)
@ -511,9 +481,6 @@ def main():
matrix_url = config['MATRIX_HOST'] + '/_matrix/client/v3'
# setup the database connection
init_db()
asyncio.run(asmain())
logger.info('!! shutdown initiated !!')