replaced config file format with toml
All checks were successful
git.dreamfall.space/pnut-matrix/pipeline/head This commit looks good

This commit is contained in:
Morgan McMillian 2025-01-12 08:15:57 -08:00
parent af411677a7
commit 03e895e87b
4 changed files with 468 additions and 223 deletions

View file

@ -22,6 +22,7 @@ dependencies = [
"websockets",
"asyncclick",
"peewee",
"tomlkit",
]
[project.urls]

View file

@ -1,5 +1,6 @@
import json
import yaml
import tomlkit
import requests
import logging
import logging.config
@ -30,14 +31,14 @@ def forbidden(error):
async def query_alias(alias):
logging.debug("--- query alias ---")
alias_localpart = alias.split(":")[0][1:]
channel_id = int(alias_localpart.split('_')[1])
channel_id = int(alias_localpart.lstrip(app.config['matrix']['namespace']))
room = PnutChannels.select().where(PnutChannels.pnut_chan ==
channel_id).first()
if room is not None:
abort(404)
token = app.config['MATRIX_PNUT_TOKEN']
token = app.config['pnut']['bot_token']
pnutpy.api.add_authorization_token(token)
try:
logging.debug("---- getting the channel ----")
@ -59,9 +60,11 @@ async def query_alias(alias):
else:
topic = None
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'])
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
if channel.acl.read.public:
visibility = RoomDirectoryVisibility.PUBLIC
@ -98,7 +101,7 @@ async def query_alias(alias):
async def on_receive_events(transaction):
access_token = request.args.get('access_token', '')
if access_token != app.config['MATRIX_HS_TOKEN']:
if access_token != app.config['matrix']['hs_token']:
abort(403)
events = request.get_json()["events"]
@ -107,11 +110,11 @@ async def on_receive_events(transaction):
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({})
# 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 = PnutUsers.select().where(PnutUsers.matrix_id ==
event['sender']).first()
@ -142,11 +145,13 @@ async def on_receive_events(transaction):
return jsonify({})
async def new_message(event, user):
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
if event['sender'] == app.config['MATRIX_AS_ID']:
if event['sender'] == as_id:
return
if app.config['MATRIX_PNUT_PREFIX'] in event['sender']:
if app.config['matrix']['namespace'] in event['sender']:
return
if user.room_id == event['room_id']:
@ -158,9 +163,9 @@ async def new_message(event, user):
logging.debug(f'room: {room}')
if room is None:
if event['room_id'] == app.config['MATRIX_GLOBAL_ROOM']:
if event['room_id'] == app.config['pnut']['global_room']:
room = PnutChannels(pnut_chan=0,
room_id=app.config['MATRIX_GLOBAL_ROOM'])
room_id=app.config['pnut']['global_room'])
else:
logging.debug('-room not mapped-')
@ -168,14 +173,14 @@ async def new_message(event, user):
if room.is_direct:
logging.debug('>----on_direct_message----<')
return on_direct_message(event, user, room)
return await on_direct_message(event, user, room)
if user is not None:
token = user.pnut_user_token
prefix = ""
else:
token = app.config['MATRIX_PNUT_TOKEN']
token = app.config['pnut']['bot_token']
matrix_profile = get_profile(event['sender'])
if ('displayname' in matrix_profile):
prefix = (f"[{matrix_profile['displayname']}]"
@ -261,9 +266,9 @@ async def new_message(event, user):
if room.pnut_chan != 0:
logging.exception('-unable to post to pnut channel-')
else:
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'])
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
await matrix_api.redact(event['room_id'], event['event_id'],
reason='user not authenticated')
@ -314,7 +319,7 @@ async def msg_from_event(event, user):
elif event['content']['msgtype'] == 'm.file':
file_url = event['content']['url'][6:]
file_name = event['content']['body']
dl_url = (f"{app.config['MATRIX_URL']}"
dl_url = (f"{app.config['matrix']['homeserver']}"
f"/_matrix/client/v1/media/download/{file_url}"
f"/{file_name}")
text = (f"[{file_name}]"
@ -330,20 +335,22 @@ 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']}"
cross_profile['avatar_image'] = (f"{app.config['matrix']['homeserver']}"
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']}")
f":{app.config['matrix']['domain']}")
crosspost['source'] = {'name': "matrix.", 'url': "https://matrix.org"}
crosspost['user'] = cross_profile
return crosspost
async def media_from_event(event, user):
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
mxc_url = event['content']['url']
if event['content']['msgtype'] == 'm.image':
kind = 'image'
@ -359,9 +366,9 @@ async def media_from_event(event, user):
'name': file_name, 'kind': kind,
'mimetype': mime_type, 'is_public': True}
matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
base_url=app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'])
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
media_file = await matrix_api.download_media(mxc_url)
pnutpy.api.add_authorization_token(user.pnut_user_token)
@ -391,7 +398,7 @@ async def media_from_event(event, user):
def oembed_from_event(event):
media_url = event['content']['url'][6:]
file_name = event['content']['body']
dl_url = (f"{app.config['MATRIX_URL']}"
dl_url = (f"{app.config['matrix']['homeserver']}"
f"/_matrix/client/v1/media/download/{media_url}"
f"/{file_name}")
@ -439,7 +446,7 @@ def delete_message(event, user):
if user is not None:
token = user.pnut_user_token
else:
token = app.config['MATRIX_PNUT_TOKEN']
token = app.config['pnut']['bot_token']
pnutpy.api.add_authorization_token(token)
e = Events.select().where((Events.event_id == event['redacts']) &
@ -457,7 +464,7 @@ def delete_message(event, user):
pass
def get_profile(userid):
url = app.config['MATRIX_HOST'] + "/_matrix/client/r0/profile/" + userid
url = app.config['matrix']['homeserver'] + "/_matrix/client/r0/profile/" + userid
r = requests.get(url)
if r.status_code == 200:
return json.loads(r.text)
@ -480,7 +487,7 @@ def get_channel_settings(channel_id):
async def create_pnut_matrix_room(channel, user):
name = None
topic = None
alias_localpart = f"{app.config['MATRIX_PNUT_PREFIX']}{channel.id}"
alias_localpart = f"{app.config['matrix']['namespace']}{channel.id}"
invitees = [user.matrix_id]
if channel.acl.read.public:
@ -501,9 +508,11 @@ async def create_pnut_matrix_room(channel, user):
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'])
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
room_id = await matrix_api.create_room(alias_localpart,
invitees=invitees,
@ -529,15 +538,15 @@ async def create_pnut_matrix_room(channel, user):
def new_matrix_user(username):
endpoint = "/_matrix/client/v3/register"
url = app.config['MATRIX_HOST'] + endpoint
url = app.config['matrix']['homeserver'] + endpoint
params = {'kind': 'user'}
data = {
'type': 'm.login.application_service',
'username': app.config['MATRIX_PNUT_PREFIX'] + username
'username': app.config['matrix']['namespace'] + username
}
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + app.config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + app.config['matrix']['as_token']
}
r = requests.post(url, headers=headers, json=data, params=params)
if r.status_code == 200:
@ -550,96 +559,99 @@ def new_matrix_user(username):
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'])
# async def on_admin_event(event):
# matrix_api = ClientAPI(app.config['MATRIX_AS_ID'],
# base_url=app.config['MATRIX_HOST'],
# token=app.config['MATRIX_AS_TOKEN'])
#
# logging.debug("- admin room event recieved -")
#
# if event['type'] != 'm.room.message':
# return jsonify({})
#
# msg = event['content']['body'].split(' ')
#
# try:
# if msg[0] == 'help':
# if len(msg) > 1:
# await matrix_api.send_message(event['room_id'],
# cmd_admin_help(msg[1]))
# else:
# await matrix_api.send_message(event['room_id'],
# cmd_admin_help())
#
# elif msg[0] == 'list':
# await matrix_api.send_message(event['room_id'], cmd_admin_list())
#
# except Exception:
# errmsg = "- on_admin_event -"
# logging.exception(errmsg)
logging.debug("- admin room event recieved -")
# 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."
#
# 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)
if event['type'] != 'm.room.message':
return jsonify({})
msg = event['content']['body'].split(' ')
try:
if msg[0] == 'help':
if len(msg) > 1:
await matrix_api.send_message(event['room_id'],
cmd_admin_help(msg[1]))
else:
await matrix_api.send_message(event['room_id'],
cmd_admin_help())
elif msg[0] == 'list':
await matrix_api.send_message(event['room_id'], cmd_admin_list())
except Exception:
errmsg = "- on_admin_event -"
logging.exception(errmsg)
def cmd_admin_help(cmd=None):
help_usage = "help [command]"
help_desc = "Show information about available commands."
list_usage = "list"
list_desc = "List the rooms currently linked with pnut.io."
if cmd == 'help':
text = "usage: " + help_usage + "\n\n"
text += help_desc
if cmd == 'list':
text = "usage: " + list_usage + "\n\n"
text += list_desc
elif cmd == 'unlink':
text = "usage: " + unlink_usage + "\n\n"
text += unlink_desc
elif cmd == 'link':
text = "usage: " + link_usage + "\n\n"
text += link_desc
else:
text = "The following commands are available:\n\n"
text += help_usage + "\n"
text += list_usage + "\n"
text += unlink_usage + "\n"
text += link_usage + "\n"
return TextMessageEventContent(msgtype='m.text', body=text)
def cmd_admin_list():
text = ""
rooms = PnutChannels.select()
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'
text += '\n'
return TextMessageEventContent(msgtype='m.text', body=text)
# def cmd_admin_list():
# text = ""
# rooms = PnutChannels.select()
#
# 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'
# text += '\n'
#
# return TextMessageEventContent(msgtype='m.text', body=text)
async def on_direct_invite(event):
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
# 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'])
if event['state_key'] == as_id:
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
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'],
elif app.config['matrix']['namespace'] in event['state_key']:
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
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'],
pnut_user = bridge_user.replace(app.config['matrix']['namespace'],
'').split(':')[0]
user = PnutUsers.select().where(PnutUsers.matrix_id ==
@ -685,10 +697,12 @@ async def on_leave_event(event):
user = PnutUsers.select().where(PnutUsers.room_id ==
event['room_id']).first()
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
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'],
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'],
as_user_id=direct_room.direct_pnut_user.lower())
try:
@ -699,13 +713,13 @@ async def on_leave_event(event):
errmsg = "- on_leave_event -"
logging.exception(errmsg)
def on_direct_message(event, user, room):
async 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']
token = app.config['pnut']['bot_token']
matrix_profile = get_profile(event['sender'])
if "displayname" in matrix_profile:
prefix = (f"[{matrix_profile['displayname']}]"
@ -716,7 +730,7 @@ def on_direct_message(event, user, room):
raw = {}
raw['io.pnut.core.crosspost'] = [crosspost_raw(event)]
evtext, evraw = msg_from_event(event)
evtext, evraw = await msg_from_event(event, user)
text = prefix + evtext
try:
@ -739,9 +753,11 @@ def on_direct_message(event, user, room):
return jsonify({})
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'])
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
if event['type'] != 'm.room.message':
return jsonify({})
@ -835,13 +851,14 @@ def cmd_user_save(user, token=None):
return TextMessageEventContent(msgtype='m.text', body=reply)
async def cmd_user_drop(user):
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
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'],
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
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()
@ -849,9 +866,9 @@ async def cmd_user_drop(user):
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'])
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
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()
@ -882,6 +899,8 @@ def cmd_user_status(user):
return TextMessageEventContent(msgtype='m.text', body=reply)
async def cmd_user_join(user, channel_id=None):
as_id = (f"@{app.config['matrix']['sender_local']}:"
f"{app.config['matrix']['domain']}")
if channel_id is None:
reply = "You must provide a channel id number with this command.\n"
reply += "!join <channel #>"
@ -898,9 +917,9 @@ async def cmd_user_join(user, channel_id=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'])
matrix_api = ClientAPI(as_id,
base_url=app.config['matrix']['homeserver'],
token=app.config['matrix']['as_token'])
await matrix_api.invite_user(room.room_id, user.matrix_id)
reply = "ok"
@ -935,14 +954,20 @@ class MLogFilter(logging.Filter):
def main():
a_parser = argparse.ArgumentParser()
a_parser.add_argument('-c', '--config', dest='configyaml',
default="config.yaml", help="configuration file")
a_parser.add_argument('-c', '--config', dest='config',
default="config.toml", help="configuration file")
a_parser.add_argument('-l', '--log_config', dest='log_config',
default='logging-config.yaml',
help='logging configuration')
args = a_parser.parse_args()
with open(args.configyaml, "rb") as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
with open(args.config, "rb") as config_file:
config = tomlkit.load(config_file)
logging.config.dictConfig(config['logging'])
with open(args.log_config, 'rb') as log_config_file:
log_config = yaml.load(log_config_file, Loader=yaml.SafeLoader)
logging.config.dictConfig(log_config)
redact_filter = MLogFilter()
logging.getLogger("werkzeug").addFilter(redact_filter)
logging.getLogger("urllib3.connectionpool").addFilter(redact_filter)
@ -950,10 +975,11 @@ def main():
app.config.update(config)
logging.basicConfig(level=logging.DEBUG)
db.init(config['SERVICE_DB'])
db.init(config['database'])
db_create_tables()
app.run(host=config['LISTEN_HOST'], port=config['LISTEN_PORT'])
host_addr, host_port = config['listen_addr'].split(':')
app.run(host=host_addr, port=host_port)
if __name__ == '__main__':
main()

View file

@ -7,6 +7,9 @@ import requests
import pnutpy
import json
import yaml
import tomlkit
import secrets
import string
from mautrix.client import ClientAPI
from mautrix.types import TextMessageEventContent, Format, MessageType, EventType
@ -17,25 +20,132 @@ PNUT_API="https://api.pnut.io/v1"
@click.group()
@click.option('--debug', '-d', is_flag=True)
@click.option('--config', '-c', required=True)
@click.option('--config', '-c')
@click.pass_context
async def cmd(ctx, debug, config):
if debug:
logging.basicConfig(level=logging.DEBUG)
ctx.ensure_object(dict)
with open(config, "rb") as config_file:
ctx.obj['config'] = yaml.load(config_file, Loader=yaml.SafeLoader)
if config is None:
ctx.obj['config'] = tomlkit.document()
else:
with open(config, "rb") as config_file:
ctx.obj['config'] = tomlkit.load(config_file)
click.echo(ctx.obj['config'])
@cmd.command()
@click.option('--listen_addr', prompt='Host address and port to listen on',
default='127.0.0.1:5000')
@click.option('--homeserver', prompt='Matrix homeserver URL',
default='http://127.0.0.1:8008')
@click.option('--domain', prompt='Matrix domain')
@click.option('--namespace', prompt='Namespace for bridge)',
default='_pnut_')
@click.option('--sender', prompt='Sender localpart for bridge',
default='_pnut_bot')
@click.option('--client_id', prompt='Pnut Client ID')
@click.option('--client_secret', prompt='Pnut Client Secret')
@click.option('--bot_user', prompt='Pnut bot username')
@click.option('--bot_token', prompt='Pnut bot user token')
@click.option('--app_token', prompt='Pnut app token')
@click.option('--app_key', prompt='Pnut app stream key')
@click.pass_context
def generate_config(ctx, listen_addr, homeserver, domain, namespace, sender,
client_id, client_secret, bot_user, bot_token, app_token,
app_key):
alpha = string.ascii_letters + string.digits
as_token = ''.join(secrets.choice(alpha) for i in range(33))
hs_token = ''.join(secrets.choice(alpha) for i in range(33))
config = tomlkit.document()
config.add("listen_addr", listen_addr)
config.add("database", "store.db")
matrix = tomlkit.table()
matrix.add("homeserver", homeserver)
matrix.add("domain", domain)
matrix.add("as_token", as_token)
matrix.add("hs_token", hs_token)
matrix.add("namespace", namespace)
matrix.add("sender_local", sender)
config.add("matrix", matrix)
pnut = tomlkit.table()
pnut.add("client_id", client_id)
pnut.add("client_secret", client_secret)
pnut.add("bot_user", bot_user)
pnut.add("bot_token", bot_token)
pnut.add("app_token", app_token)
pnut.add("app_key", app_key)
pnut.add("global_stream", False)
pnut.add("global_room", "<ROOM_ID>")
pnut.add("global_humans_only", True)
config.add("pnut", pnut)
click.echo()
write_conf = click.confirm("Write pnut-matrix configuration to config.toml?")
if write_conf:
with open('config.toml', 'w') as write_file:
tomlkit.dump(config, write_file)
else:
click.echo()
click.echo('##################################################' +
'####################')
click.echo('# The following is the generated config.toml file.' +
' with pnut-matrix. #')
click.echo('##################################################' +
'####################')
click.echo(tomlkit.dumps(config))
click.echo('##################################################' +
'####################')
appsrv = {}
appsrv['id'] = 'pnut'
appsrv['url'] = f'http://127.0.0.1:5000'
appsrv['as_token'] = as_token
appsrv['hs_token'] = hs_token
appsrv['sender_localpart'] = sender
appsrv['namespaces'] = {
'users': [{'exclusive': True, 'regex': f'@{namespace}.*'}],
'rooms': [],
'aliases': [{'exclusive': True, 'regex': f'#{namespace}.*'}]}
write_asconf = click.confirm("Write matrix configuration to appservice.yaml?")
if write_asconf:
with open('appservice.yaml', 'w') as write_file:
yaml.dump(appsrv, write_file)
else:
click.echo()
click.echo('############################################################' +
'###########################')
click.echo('# The following is the generated appservice.yaml file to use' +
' with your matrix server. #')
click.echo('############################################################' +
'###########################')
click.echo(yaml.dump(appsrv))
click.echo('############################################################' +
'###########################')
@cmd.command()
@click.pass_context
async def get_streams(ctx):
config = ctx.obj['config']
logging.debug(config)
if 'pnut' not in config:
click.echo("Pnut configuration missing!")
exit(1)
if 'app_token' not in config['pnut']:
click.echo("Pnut app token missing from configuration!")
exit(1)
endpoint = "/streams"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['PNUT_APPTOKEN']
"Authorization": "Bearer " + config['pnut']['app_token']
}
url = f"{PNUT_API}{endpoint}"
@ -53,11 +163,18 @@ async def get_streams(ctx):
@click.pass_context
def rm_stream(ctx, key):
config = ctx.obj['config']
logging.debug(config)
if 'pnut' not in config:
click.echo("Pnut configuration missing!")
exit(1)
if 'app_token' not in config['pnut']:
click.echo("Pnut app token missing from configuration!")
exit(1)
endpoint = f"/streams/{key}"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['PNUT_APPTOKEN']
"Authorization": "Bearer " + config['pnut']['app_token']
}
url = f"{PNUT_API}{endpoint}"
@ -74,11 +191,18 @@ def rm_stream(ctx, key):
@click.pass_context
def rm_streams(ctx):
config = ctx.obj['config']
logging.debug(config)
if 'pnut' not in config:
click.echo("Pnut configuration missing!")
exit(1)
if 'app_token' not in config['pnut']:
click.echo("Pnut app token missing from configuration!")
exit(1)
endpoint = f"/streams"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['PNUT_APPTOKEN']
"Authorization": "Bearer " + config['pnut']['app_token']
}
url = f"{PNUT_API}{endpoint}"
@ -97,10 +221,18 @@ def rm_streams(ctx):
@click.pass_context
def new_stream(ctx, key, object_types):
config = ctx.obj['config']
if 'pnut' not in config:
click.echo("Pnut configuration missing!")
exit(1)
if 'app_token' not in config['pnut']:
click.echo("Pnut app token missing from configuration!")
exit(1)
endpoint = f"/streams"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['PNUT_APPTOKEN']
"Authorization": "Bearer " + config['pnut']['app_token']
}
otypes = [x.strip() for x in object_types.split(',')]
data = {
@ -124,10 +256,18 @@ def new_stream(ctx, key, object_types):
@click.pass_context
def update_stream(ctx, key, object_types):
config = ctx.obj['config']
if 'pnut' not in config:
click.echo("Pnut configuration missing!")
exit(1)
if 'app_token' not in config['pnut']:
click.echo("Pnut app token missing from configuration!")
exit(1)
endpoint = f"/streams/{key}"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['PNUT_APPTOKEN']
"Authorization": "Bearer " + config['pnut']['app_token']
}
otypes = [x.strip() for x in object_types.split(',')]
data = {
@ -171,18 +311,29 @@ def pnut_app_token(ctx):
# click.echo(r.text)
@cmd.command()
@click.argument('username')
@click.pass_context
def new_matrix_asuser(ctx, username):
def create_matrix_asuser(ctx):
config = ctx.obj['config']
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
if 'sender_local' not in config['matrix']:
click.echo("matrix local sender missing from configuration!")
exit(1)
endpoint = "/_matrix/client/v3/register"
url = config['MATRIX_HOST'] + endpoint
url = config['matrix']['homeserver'] + endpoint
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + config['matrix']['as_token']
}
params = {'kind': 'user'}
data = {'type': 'm.login.application_service','username': username}
data = {'type': 'm.login.application_service','username': config['matrix']['sender_local']}
r = requests.post(url, headers=headers, json=data, params=params)
if r.status_code == 200:
@ -195,23 +346,36 @@ def new_matrix_asuser(ctx, username):
@cmd.command()
@click.argument('invitee')
@click.pass_context
def new_pnut_admin_room(ctx, invitee):
def create_pnut_admin_room(ctx, invitee):
config = ctx.obj['config']
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
if 'namespace' not in config['matrix']:
click.echo("matrix namespace missing from configuration!")
exit(1)
as_id = f"@{config['matrix']['sender_local']}:{config['matrix']['domain']}"
endpoint = "/_matrix/client/v3/createRoom"
url = config['MATRIX_HOST'] + endpoint
url = config['matrix']['homeserver'] + endpoint
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + config['matrix']['as_token']
}
data = {
'visibility': "private",
'is_direct': False,
'name': "Pnut Bridge Admin Room",
'room_alias_name': f"{config['MATRIX_PNUT_PREFIX']}admin",
'room_alias_name': f"{config['matrix']['namespace']}admin",
'invite': [invitee],
'power_level_content_override': {
'users': {
f"{config['MATRIX_AS_ID']}": 100,
f"{as_id}": 100,
f"{invitee}": 100
}
}
@ -228,13 +392,25 @@ def new_pnut_admin_room(ctx, invitee):
@cmd.command()
@click.pass_context
def new_pnut_global_room(ctx):
def create_pnut_global_room(ctx):
config = ctx.obj['config']
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
if 'namespace' not in config['matrix']:
click.echo("matrix namespace missing from configuration!")
exit(1)
endpoint = "/_matrix/client/v3/createRoom"
url = config['MATRIX_HOST'] + endpoint
url = config['matrix']['homeserver'] + endpoint
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + config['matrix']['as_token']
}
# data = {
# 'visibility': "public",
@ -248,7 +424,7 @@ def new_pnut_global_room(ctx):
data = {
'visibility': "public",
'name': "Pnut Global Stream",
'room_alias_name': f"{config['MATRIX_PNUT_PREFIX']}global"
'room_alias_name': f"{config['matrix']['namespace']}global"
}
logging.debug(data)
r = requests.post(url, headers=headers, json=data)
@ -267,15 +443,24 @@ def new_pnut_global_room(ctx):
@click.pass_context
def elevate_matrix_user(ctx, room_id, matrix_id, power_level):
config = ctx.obj['config']
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
as_id = f"@{config['matrix']['sender_local']}:{config['matrix']['domain']}"
endpoint = f"/_matrix/client/v3/rooms/{room_id}/state/"
url = config['MATRIX_HOST'] + endpoint + "m.room.power_levels"
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + config['matrix']['as_token']
}
data = {
'users': {
f"{config['MATRIX_AS_ID']}": 100,
f"{as_id}": 100,
f"{matrix_id}": int(power_level)
}
}
@ -293,9 +478,18 @@ def elevate_matrix_user(ctx, room_id, matrix_id, power_level):
@click.pass_context
async def list_joined_rooms(ctx):
config = ctx.obj['config']
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
as_id = f"@{config['matrix']['sender_local']}:{config['matrix']['domain']}"
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'])
room_list = await matrix_api.get_joined_rooms()
for room_id in room_list:
click.echo(room_id)
@ -323,9 +517,18 @@ async def list_joined_rooms(ctx):
@click.pass_context
async def part_room(ctx, room_id):
config = ctx.obj['config']
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
if 'matrix' not in config:
click.echo("Matrix configuration missing!")
exit(1)
if 'as_token' not in config['matrix']:
click.echo("matrix appservice token missing from configuration!")
exit(1)
as_id = f"@{config['matrix']['sender_local']}:{config['matrix']['domain']}"
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'])
# TODO: need to clear alias
await matrix_api.leave_room(room_id)

View file

@ -2,6 +2,7 @@ import time
import logging
import logging.config
import yaml
import tomlkit
import json
import pnutpy
import requests
@ -25,7 +26,6 @@ from pnut_matrix.models import *
logger = logging.getLogger()
config = None
matrix_url = None
class MLogFilter(logging.Filter):
@ -52,16 +52,18 @@ async def new_pnut_message(msg, meta):
logger.debug("text: " + msg.content.text)
# ignore messages posted by the bridge
if msg.user.username == config['MATRIX_PNUT_USER']:
if msg.user.username == config['pnut']['bot_user']:
return
if msg.source.id == config['PNUTCLIENT_ID']:
if msg.source.id == config['pnut']['client_id']:
return
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
channel_id = int(msg.channel_id)
@ -118,9 +120,9 @@ async def new_pnut_message(msg, meta):
logger.debug('-set_avatar-')
if room.is_private:
matrix_api_as = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
matrix_api_as = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
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)
@ -145,10 +147,10 @@ async def new_pnut_message(msg, meta):
async def new_pnut_post(post, meta):
if not config['PNUT_GLOBAL']:
if not config['pnut']['global_stream']:
return
if (config['PNUT_GLOBAL_HUMAN_ONLY'] and
if (config['pnut']['global_humans_only'] and
post.user.type in ['feed', 'bot']):
logging.debug('-skipping non human post-')
return
@ -160,10 +162,12 @@ async def new_pnut_post(post, meta):
text += f"<{post.user.username}> reposted >> "
post = post.repost_of
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_id = matrix_id_from_pnut(post.user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
try:
profile = await matrix_api.get_profile(matrix_id.lower())
@ -184,7 +188,7 @@ async def new_pnut_post(post, meta):
await set_matrix_avatar(post.user)
logger.debug('-set_avatar-')
room_id = config['MATRIX_GLOBAL_ROOM']
room_id = config['pnut']['global_room']
await matrix_api.join_room(room_id)
postlink = f"https://posts.pnut.io/{post.id}"
plaintext = f"{post.content.text}\n{postlink}"
@ -209,9 +213,11 @@ async def new_pnut_post(post, meta):
async def new_media(room_id, msg):
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
if 'io.pnut.core.oembed' in msg.raw:
@ -275,9 +281,11 @@ async def new_media(room_id, msg):
async def delete_message(msg):
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
events = Events.select().where((Events.pnut_id == msg.id) &
@ -288,8 +296,8 @@ async def delete_message(msg):
event.save()
def matrix_id_from_pnut(username):
matrix_id = (f"@{config['MATRIX_PNUT_PREFIX']}{username}"
f":{config['MATRIX_DOMAIN']}")
matrix_id = (f"@{config['matrix']['namespace']}{username}"
f":{config['matrix']['domain']}")
return matrix_id
def matrix_display_from_pnut(user):
@ -308,17 +316,21 @@ def matrix_display_from_pnut(user):
async def set_matrix_display(user):
matrix_id = matrix_id_from_pnut(user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
await matrix_api.set_displayname(matrix_display_from_pnut(user))
async def set_matrix_avatar(user):
matrix_id = matrix_id_from_pnut(user.username)
matrix_api = ClientAPI(config['MATRIX_AS_ID'],
base_url=config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
as_id = (f"@{config['matrix']['sender_local']}:"
f"{config['matrix']['domain']}")
matrix_api = ClientAPI(as_id,
base_url=config['matrix']['homeserver'],
token=config['matrix']['as_token'],
as_user_id=matrix_id.lower())
dl = requests.get(user.content.avatar_image.url, stream=True)
@ -347,15 +359,15 @@ async def set_matrix_avatar(user):
def new_matrix_user(username):
endpoint = "/_matrix/client/v3/register"
url = config['MATRIX_HOST'] + endpoint
url = config['matrix']['homeserver'] + endpoint
params = {'kind': 'user'}
data = {
'type': 'm.login.application_service',
'username': config['MATRIX_PNUT_PREFIX'] + username
'username': config['matrix']['namespace'] + username
}
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer " + config['MATRIX_AS_TOKEN']
"Authorization": "Bearer " + config['matrix']['as_token']
}
logger.debug(data)
r = requests.post(url, headers=headers, json=data, params=params)
@ -371,9 +383,9 @@ def new_matrix_user(username):
def new_room(pnut_user, invitees, chan):
dr = None
url = matrix_url + '/createRoom'
url = config['matrix']['homeserver'] + '/_matrix/client/v3/createRoom'
params = {
"access_token": config['MATRIX_AS_TOKEN'],
"access_token": config['matrix']['as_token'],
"user_id": pnut_user.lower()
}
content = {
@ -434,15 +446,15 @@ async def on_message(message):
await new_pnut_post(pnut_post, meta)
async def asmain():
if config['MATRIX_ADMIN_ROOM']:
logger.debug("- sould join admin room -")
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'])
# if config['MATRIX_ADMIN_ROOM']:
# logger.debug("- sould join admin room -")
# 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']
ws_url += config['pnut']['app_token'] + '&key=' + config['pnut']['app_key']
ws_url += '&include_raw=1'
async for websocket in connect(uri=ws_url):
try:
@ -456,25 +468,28 @@ async def asmain():
def main():
global config
global matrix_url
a_parser = argparse.ArgumentParser()
a_parser.add_argument('-c', '--config', dest='configyaml',
default="config.yaml", help="configuration file")
a_parser.add_argument('-c', '--config', dest='config',
default="config.toml", help="configuration file")
a_parser.add_argument('-l', '--log_config', dest='log_config',
default='logging-config.yaml',
help='logging configuration')
args = a_parser.parse_args()
with open(args.configyaml, "rb") as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
with open(args.config, 'rb') as config_file:
config = tomlkit.load(config_file)
db.init(config['SERVICE_DB'])
with open(args.log_config, 'rb') as log_config_file:
log_config = yaml.load(log_config_file, Loader=yaml.SafeLoader)
db.init(config['database'])
db_create_tables()
logging.config.dictConfig(config['logging'])
logging.config.dictConfig(log_config)
redact_filter = MLogFilter()
logging.getLogger("werkzeug").addFilter(redact_filter)
logging.getLogger("urllib3.connectionpool").addFilter(redact_filter)
matrix_url = config['MATRIX_HOST'] + '/_matrix/client/v3'
asyncio.run(asmain())
logger.info('!! shutdown initiated !!')