Compare commits

...

25 commits
1.2.0 ... main

Author SHA1 Message Date
Morgan McMillian 409b2a5a3c update base docker image 2023-07-04 09:55:43 -07:00
Morgan McMillian 84f3882466 replace access_token url parameter with authorization header
issue #66
2023-07-04 09:33:24 -07:00
Morgan McMillian 1a5c9d84b0 fix username parameter for /register endpoint
resolves #65
2023-07-04 09:31:18 -07:00
Morgan McMillian a26759bb7b Add versioned appservice paths to existing routes
Synapse v1.81 changed to attempt using version paths when calling
an appservice before falling back to the legacy paths. However the
appservice was generating a 404 and the server wouldn't fallback
so this adds the versioned paths to the existing routes.

Resolves #64
2023-05-01 14:18:53 -07:00
Morgan McMillian 782c3d070b whitespace trim 2023-02-15 18:08:51 -08:00
Morgan McMillian 037fee7796 removed mailing list link from README 2023-02-10 08:00:54 -08:00
Morgan McMillian 03ba94ecb9 Avoid inviting self when sending a PM from a different pnut client
Fixes #63
2022-12-16 12:07:16 -08:00
Morgan McMillian de7f4f5c35 allow protocol to be specified in argument
work towards issue #48
2022-12-11 07:13:28 -08:00
Morgan McMillian 32d38bc005 fix conditional in until loop
work towards issue #48
2022-12-11 07:02:56 -08:00
Morgan McMillian 45f621f9af added curl to image for wrapper script
work towards issue #48
2022-12-11 07:01:55 -08:00
Morgan McMillian edd1ef6212 add wrapper script for a clean startup in container 2022-12-11 06:29:12 -08:00
Morgan McMillian 82c2ab105a fixup docker build
work towards issue #48
2022-12-10 07:21:37 -08:00
Morgan McMillian 38cd7b347e updated changelog for 1.3.0 release 2022-08-20 07:07:05 -07:00
Morgan McMillian ee6baaa579 ignore malformed sticker messages
resolves issue #60
2022-08-13 08:16:59 -07:00
Morgan McMillian 24d1258265 clean up display name for pnut user 2022-07-20 16:29:53 -07:00
Morgan McMillian 05893172f8 sticker support
closes issue #43
2022-07-01 14:58:32 -07:00
Morgan McMillian ad49c3a354 add config and filters to logging
Basic logging configuration can now be done in the config.yaml file and
filter has been added to redact the access tokens from the log output.
Closes issue #38
2022-07-01 10:57:16 -07:00
Morgan McMillian 2d4476b9a3 added workaround for invite in README
Resolves issue #57
2022-06-30 13:27:51 -07:00
Morgan McMillian c75b7517ca use correct pnut acl attribute for public
When creating of the matrix room is triggered, the pnut attribute
channel.acl.read.public flag should be used to determine if the matrix
room preset should be set to public or private. Issue #58
2022-06-30 12:47:31 -07:00
Morgan McMillian f04ec6fef9 README: added installation and configuration
Started with the bare minimum instructions needed for getting the bridge
installed and running from source. Related to #34
2022-06-25 12:00:10 -07:00
Morgan McMillian 904140320d README: added contribution details 2022-06-25 10:26:49 -07:00
Morgan McMillian 937ad8cea1 attempt to generate the pnut matrix user on event 2022-06-11 21:55:06 -07:00
Morgan McMillian 03949ff67b use local timestamp when creating events 2022-06-11 21:47:27 -07:00
Morgan McMillian 9571238043 Updated for the new public bridge 2022-05-26 18:47:51 -07:00
Morgan McMillian eb78c95ec6 Removed public bridge details 2021-07-03 07:57:55 -07:00
11 changed files with 395 additions and 83 deletions

15
.dockerignore Normal file
View file

@ -0,0 +1,15 @@
__pycache__/
*.py[cod]
*.yaml
*.db
*.sublime-project
*.sublime-workspace
.vscode/
.git/
.gitignore
Dockerfile
.dockerignore
snap/
Jenkinsfile
.gitlab-ci.yml
contrib/

56
.gitlab-ci.yml Normal file
View file

@ -0,0 +1,56 @@
# This file is a template, and might need editing before it works on your project.
# To contribute improvements to CI/CD templates, please follow the Development guide at:
# https://docs.gitlab.com/ee/development/cicd/templates.html
# This specific template is located at:
# https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Docker.gitlab-ci.yml
# Build a Docker image with CI/CD and push to the GitLab registry.
# Docker-in-Docker documentation: https://docs.gitlab.com/ee/ci/docker/using_docker_build.html
#
# This template uses one generic job with conditional builds
# for the default branch and all other (MR) branches.
stages:
- build
docker-build:
# Use the official docker image.
image: docker:latest
stage: build
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
# Default branch leaves tag empty (= latest tag)
# All other branches are tagged with the escaped branch name (commit ref slug)
script:
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
tag=""
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
else
tag=":$CI_COMMIT_REF_SLUG"
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
fi
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
- docker push "$CI_REGISTRY_IMAGE${tag}"
# Run this job in a branch where a Dockerfile exists
rules:
- if: $CI_COMMIT_BRANCH
exists:
- Dockerfile
# snap-build-arm64:
# stage: build
# tags:
# - snap-arm64
# script:
# - snapcraft
# - snapcraft upload --release=edge *.snap
#
# snap-build-amd64:
# stage: build
# tags:
# - snap-amd64
# script:
# - snapcraft
# - snapcraft upload --release=edge *.snap

View file

@ -4,6 +4,24 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- Docker image
### Fixed
- Self-invite when sending a PM from a pnut app
## [1.3.0] - 2022-08-20
### Added
- Support for matrix stickers
### Fixed
- External matrix users unable to DM appservice
- Joining a non-public channels on pnut makes matrix room public
- Timestamps on generated events
### Changed
- Improved display name for pnut users in matrix rooms
- Access tokens redacted from appservice logs
## [1.2.0] - 2021-03-20
### Added

View file

@ -1,16 +1,14 @@
FROM python:3
VOLUME /data
FROM python:3.11-slim-bookworm
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
RUN apt-get update && apt-get install libmagic-dev curl -y
COPY . .
RUN pip install --no-cache-dir -r requirements.txt
ENV CONFIG_FILE=/data/config.yaml
VOLUME /data
WORKDIR /data
EXPOSE 5000/tcp
CMD [ "python", "/usr/src/app/pnut-matrix.py", "-d" ]
EXPOSE 5000
CMD [ "python", "/usr/src/app/pnut-matrix.py" ]

View file

@ -2,17 +2,65 @@
This is a pnut.io channel bridge for Matrix using the Application Services (AS) API.
This bridge will pass pnut.io channel messages through to Matrix, and Matrix messages through to pnut.io channels. Currently only public chat channels on pnut.io are supported but work is in progress to support private messages.
This bridge will pass pnut.io channel messages through to Matrix, and Matrix messages through to pnut.io channels.
## Usage
Visit the project [wiki](https://gitlab.dreamfall.space/thrrgilag/pnut-matrix/-/wikis/home) for up to date information on using the bridge hosted on dreamfall.space.
The public bridge is once again online!
See [Using-the-public-bridge](https://gitlab.com/thrrgilag/pnut-matrix/-/wikis/Using-the-public-bridge) for details.
## Questions, comments, or issues
## Installation
Join the chat!
Currently pnut-matrix has been only tested with and confirmed to work with [synapse]. Please refer to the [synapse installation instructions] for details on how to setup your homeserver.
* [#pnut-matrix:dreamfall.space](https://matrix.to/#/#pnut-matrix:dreamfall.space)
* [pnut.io](https://patter.chat/room/999)
To install the latest version of pnut-matrix from source:
```sh
git clone https://gitlab.com/thrrgilag/pnut-matrix.git
cd pnut-matrix
python3 -m venv env
source env/bin/activate
pip3 install -r requirements.txt
```
## Configuration
Copy `config.yaml-sample` to `config.yaml` and edit for your setup. Likewise copy `appservice.yaml-sample` to `appservice.yaml` and edit to match the tokens, prefix, and port listed in `config.yaml`. This is the configuration that synapse will need to reference so that it can connect the bridge. Make sure you modify your [syanpse configuration] accordingly.
## Tweaks
There exists a bug in synapse which prevents the bridge user from accepting DM invites from users on other homeservers. To work around the issue you can create a profile for the bot user.[^1]
```sh
curl --data '{"type": "m.login.application_service", "username": "your_sender_localpart"}' 'http://yourhomeserver/_matrix/client/r0/register?access_token=your_as_token'
```
## Contributing and support
You can open issues for bugs or feature requests and you can submit merge requests to this project on [GitLab]. You can also submit issues and patches directly to [morgan@mcmillian.dev].
Join my public chat room for development discussion.
- [pnut-matrix on pnut.io]
- [#pnut_999:pnut-matrix.dreamfall.space]
## License
GPLv3, see [LICENSE].
[synapse]: https://github.com/matrix-org/synapse
[synapse installation instructions]: https://matrix-org.github.io/synapse/latest/setup/installation.html
[syanpse configuration]: https://matrix-org.github.io/synapse/latest/application_services.html
[GitLab]: https://gitlab.com/thrrgilag/pnut-matrix/
[morgan@mcmillian.dev]: mailto:morgan@mcmillian.dev
[pnut-matrix on pnut.io]: https://patter.chat/999
[#pnut_999:pnut-matrix.dreamfall.space]: https://matrix.to/#/#pnut_999:pnut-matrix.dreamfall.space
[LICENSE]: LICENSE
[^1]: https://github.com/matrix-org/matrix-appservice-irc/issues/1270#issuecomment-849765090

View file

@ -29,6 +29,7 @@ def forbidden(error):
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:]
@ -58,13 +59,17 @@ def query_alias(alias):
room['name'] = channel_settings['name']
if 'description' in channel_settings:
room['topic'] = channel_settings['description']
if channel.acl.read.any_user:
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?access_token='
url += app.config['MATRIX_AS_TOKEN']
headers = {"Content-Type":"application/json"}
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)
@ -77,6 +82,12 @@ def query_alias(alias):
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)
@ -86,6 +97,7 @@ def query_alias(alias):
return jsonify({})
@app.route("/_matrix/app/v1/transactions/<transaction>", methods=["PUT"])
@app.route("/transactions/<transaction>", methods=["PUT"])
def on_receive_events(transaction):
@ -105,6 +117,9 @@ def on_receive_events(transaction):
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)
@ -123,6 +138,63 @@ def on_receive_events(transaction):
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-')
@ -320,7 +392,7 @@ def delete_message(event, user):
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
@ -350,7 +422,7 @@ def get_channel_settings(channel_id):
return channel_settings
def create_room(channel, invite):
def create_room(channel, user):
channel_settings = {}
for item in channel.raw:
if item.type == 'io.pnut.core.chat-settings':
@ -358,23 +430,27 @@ def create_room(channel, invite):
# Matrix sdk doesn't include all details in a single call
room = {'room_alias_name': app.config['MATRIX_PNUT_PREFIX'] + channel.id}
logger.debug(invite)
logger.debug(user)
logger.debug(room)
room['invite'] = [invite]
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.any_user:
if channel.acl.read.public:
room['preset'] = 'public_chat'
room['visibility'] = 'public'
else:
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?access_token='
url += app.config['MATRIX_AS_TOKEN']
headers = {"Content-Type":"application/json"}
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:
@ -387,17 +463,32 @@ def create_room(channel, invite):
)
db_session.add(rr)
db_session.commit()
logger.debug(r.status_code)
logger.debug(r)
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'],
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:
@ -480,7 +571,7 @@ def cmd_admin_list():
return text
def cmd_admin_link(room_id, pnut_chan_id):
matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
token=app.config['MATRIX_AS_TOKEN'])
pnutpy.api.add_authorization_token(app.config['MATRIX_PNUT_TOKEN'])
@ -493,7 +584,7 @@ def cmd_admin_link(room_id, pnut_chan_id):
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,
@ -512,15 +603,15 @@ def cmd_admin_link(room_id, pnut_chan_id):
logger.exception(errmsg)
return errmsg
def cmd_admin_unlink(id):
matrix_api = MatrixHttpApi(app.config['MATRIX_HOST'],
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 id.startswith('!'):
room = Rooms.query.filter(Rooms.room_id == id).one_or_none()
if rid.startswith('!'):
room = Rooms.query.filter(Rooms.room_id == rid).one_or_none()
else:
room = Rooms.query.filter(Rooms.pnut_chan == id).one_or_none()
room = Rooms.query.filter(Rooms.pnut_chan == rid).one_or_none()
if hasattr(room, 'portal'):
if room.portal:
@ -574,6 +665,7 @@ def on_direct_invite(event):
# 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)
@ -654,20 +746,20 @@ def on_direct_message(event, user, room):
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'])
logger.debug("- direct room event received -")
if event['type'] != 'm.room.message':
return jsonify({})
msg = event['content']['body'].split(' ')
try:
@ -763,7 +855,7 @@ def cmd_user_drop(sender=None):
db_session.commit()
reply = "Success! Your auth token has been removed."
else:
reply = "You do not appear to be registered."
reply = "You do not appear to be registered."
except Exception as e:
logging.exception('!drop')
@ -803,8 +895,14 @@ def cmd_user_join(sender=None, channel_id=None):
else:
pnutpy.api.add_authorization_token(user.pnut_user_token)
channel, meta = pnutpy.api.get_channel(channel_id, include_raw=1)
create_room(channel, user.matrix_id)
reply = "is working?"
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"

13
appservice.yaml-sample Normal file
View file

@ -0,0 +1,13 @@
id: "pnut"
url: "http://127.0.0.1:5000"
as_token: "<AS_AUTH_TOKEN>"
hs_token: "<HS_AUTH_TOKEN>"
sender_localpart: pnut
namespaces:
users:
- exclusive: true
regex: "@pnut_.*"
rooms: []
aliases:
- exclusive: true
regex: "#pnut_.*"

View file

@ -4,12 +4,34 @@ MATRIX_URL: 'https://<DOMAIN_NAME>' # public base URL of the matrix server
MATRIX_HOST: 'https://localhost:8448' # client URL of the matrix server
MATRIX_DOMAIN: '<DOMAIN_NAME>' # domain of the matrix server (right hand side of a matrix ID)
MATRIX_AS_ID: '<MATRIX_ID>' # matrix ID for the app service user
MATRIX_AS_TOKEN: '<AUTH_TOKEN>' # auth token for the app service user
MATRIX_HS_TOKEN: '<AUTH_TOKEN>' # auth token for the matrix server
MATRIX_PNUT_PREFIX: '<APP_SERVICE_PREFIX>' # prefix used for reserving matrix IDs and room aliases
MATRIX_AS_TOKEN: '<AS_AUTH_TOKEN>' # auth token for the app service user
MATRIX_HS_TOKEN: '<HS_AUTH_TOKEN>' # auth token for the matrix server
MATRIX_PNUT_PREFIX: 'pnut_' # prefix used for reserving matrix IDs and room aliases
MATRIX_ADMIN_ROOM: '<ROOM ID>' # Administrator control room ID
MATRIX_PNUT_USER: '<USERNAME>' # pnut.io username for the matrix bot
MATRIX_PNUT_TOKEN: '<AUTH_TOKEN>' # pnut.io auth token for the matrix bot
PNUTCLIENT_ID: '<CLIENT_ID>' # pnut.io app client ID
PNUT_APPTOKEN: '<APP TOKEN>' # pnut.io app token
PNUT_APPKEY: '<APPSTREAM KEY>' # pnut.io app stream key
logging:
version: 1
formatters:
precise:
format: '%(asctime)s - %(name)s - %(lineno)d - %(levelname)s - %(message)s'
normal:
format: '%(name)s - %(levelname)s - %(message)s'
handlers:
console:
class: logging.StreamHandler
formatter: normal
loggers:
werkzeug:
level: DEBUG
appservice:
level: DEBUG
urllib3.connectionpool:
level: DEBUG
root:
level: DEBUG
handlers: [console]

View file

@ -2,6 +2,7 @@ import websocket
import threading
import time
import logging
import logging.config
import yaml
import json
import pnutpy
@ -9,6 +10,7 @@ import requests
import magic
import argparse
import os
import re
from matrix_client.api import MatrixHttpApi
from matrix_client.api import MatrixError, MatrixRequestError
@ -22,6 +24,21 @@ logger = logging.getLogger()
_shutdown = threading.Event()
_reconnect = threading.Event()
class MLogFilter(logging.Filter):
ACCESS_TOKEN_RE = re.compile(r"(\?.*access(_|%5[Ff])token=)[^&]*(\s.*)$")
ACCESS_TOKEN_RE2 = re.compile(r"(\?.*access(_|%5[Ff])token=)[^&]*(.*)$")
def filter(self, record):
if record.name == "werkzeug" and len(record.args) > 0:
redacted_uri = MLogFilter.ACCESS_TOKEN_RE.sub(r"\1<redacted>\3", record.args[0])
record.args = (redacted_uri, ) + record.args[1:]
elif record.name == "urllib3.connectionpool" and len(record.args) > 3:
redacted_uri = MLogFilter.ACCESS_TOKEN_RE2.sub(r"\1<redacted>\3", record.args[4])
record.args = record.args[:4] + (redacted_uri,) + record.args[5:]
return True
def new_message(msg, meta):
logger.debug("channel: " + msg.channel_id)
logger.debug("username: " + msg.user.username)
@ -53,6 +70,8 @@ def new_message(msg, meta):
invitees=[]
for pm_user in meta['subscribed_user_ids']:
user = Users.query.filter(Users.pnut_user_id == pm_user).one_or_none()
if int(pm_user) == msg.user.id:
continue
if user is not None:
invitees.append(user.matrix_id)
if len(invitees) > 0:
@ -66,7 +85,7 @@ def new_message(msg, meta):
return
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
@ -75,11 +94,11 @@ def new_message(msg, meta):
new_matrix_user(msg.user.username)
logger.debug('-new_user-')
profile = {'displayname': None}
if profile['displayname'] != matrix_display_from_pnut(msg.user):
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:
set_matrix_avatar(msg.user)
@ -93,7 +112,7 @@ def new_message(msg, meta):
if 'content' in msg:
text = msg.content.text + "\n"
ts = int(msg.created_at.strftime('%s')) * 1000
ts = int(time.time()) * 1000
lnktext = ""
for link in msg.content.entities.links:
@ -107,10 +126,10 @@ def new_message(msg, meta):
r = matrix_api.send_message(room.room_id, text, timestamp=ts)
event = Events(
event_id=r['event_id'],
room_id=room.room_id,
pnut_msg_id=msg.id,
pnut_user_id=msg.user.id,
event_id=r['event_id'],
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)
@ -122,10 +141,10 @@ def new_message(msg, meta):
def new_media(room_id, msg):
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
ts = int(msg.created_at.strftime('%s')) * 1000
ts = int(time.time()) * 1000
if 'io.pnut.core.oembed' in msg.raw:
@ -167,10 +186,10 @@ def new_media(room_id, msg):
r = matrix_api.send_content(room_id, ul['content_uri'], title, msgtype, extra_information=info, timestamp=ts)
event = Events(
event_id=r['event_id'],
room_id=room_id,
pnut_msg_id=msg.id,
pnut_user_id=msg.user.id,
event_id=r['event_id'],
room_id=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)
@ -178,7 +197,7 @@ def new_media(room_id, msg):
def delete_message(msg):
matrix_id = matrix_id_from_pnut(msg.user.username)
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
@ -193,7 +212,7 @@ def matrix_id_from_pnut(username):
def matrix_display_from_pnut(user):
if 'name' in user:
display = user.name + " <@" + user.username + "> (pnut)"
display = user.name + " (@" + user.username + ")"
else:
display = "@" + user.username
return display
@ -210,14 +229,14 @@ def get_matrix_profile(matrix_id):
def set_matrix_display(user):
matrix_id = matrix_id_from_pnut(user.username)
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
matrix_api.set_display_name(matrix_id, matrix_display_from_pnut(user))
def set_matrix_avatar(user):
matrix_id = matrix_id_from_pnut(user.username)
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
@ -241,24 +260,24 @@ def set_matrix_avatar(user):
logger.exception('failed to set user avatar')
def new_matrix_user(username):
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
data = {
'type': 'm.login.application_service',
'user': config['MATRIX_PNUT_PREFIX'] + username
'username': config['MATRIX_PNUT_PREFIX'] + username
}
matrix_api.register(content=data)
def join_room(room_id, matrix_id):
matrix_api_as = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api_as = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'])
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=matrix_id)
try:
matrix_api.join_room(room_id)
except MatrixRequestError as e:
if e.code == 403:
matrix_api_as.invite_user(room_id, matrix_id)
@ -270,7 +289,7 @@ def join_room(room_id, matrix_id):
def new_room(pnut_user, invitees, chan):
dr = None
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
matrix_api = MatrixHttpApi(config['MATRIX_HOST'],
token=config['MATRIX_AS_TOKEN'],
identity=pnut_user)
url = matrix_url + '/createRoom'
@ -293,7 +312,7 @@ def on_message(ws, message):
# logger.debug("on_message: " + message)
msg = json.loads(message)
logger.debug(msg['meta'])
if 'data' in msg:
if 'channel_type' in msg['meta']:
@ -368,17 +387,17 @@ if __name__ == '__main__':
# )
args = a_parser.parse_args()
if args.debug:
# websocket.enableTrace(True)
logging.basicConfig(level=logging.DEBUG)
else:
logging.basicConfig(level=logging.INFO)
configyaml = os.environ.get("CONFIG_FILE")
with open(configyaml, "rb") as config_file:
config = yaml.load(config_file, Loader=yaml.SafeLoader)
# websocket.enableTrace(True)
logging.config.dictConfig(config['logging'])
redact_filter = MLogFilter()
logging.getLogger("werkzeug").addFilter(redact_filter)
logging.getLogger("urllib3.connectionpool").addFilter(redact_filter)
ws_url = 'wss://stream.pnut.io/v1/app?access_token='
ws_url += config['PNUT_APPTOKEN'] + '&key=' + config['PNUT_APPKEY']
ws_url += '&include_raw=1'
@ -388,7 +407,7 @@ if __name__ == '__main__':
init_db()
# setup the websocket connection
ws = websocket.WebSocketApp(ws_url, on_message=on_message,
ws = websocket.WebSocketApp(ws_url, on_message=on_message,
on_error=on_error, on_close=on_close)
ws.on_open = on_open
wst = threading.Thread(target=wsthreader(ws.run_forever))

View file

@ -1,9 +1,8 @@
pyyaml
requests
matrix-client
matrix-client==0.3.2
Flask
pnutpy
sqlalchemy
websocket-client
filemagic
git+https://github.com/shawnanastasio/python-matrix-bot-api

26
wait-for-synapse.sh Executable file
View file

@ -0,0 +1,26 @@
#!/bin/sh
# wait-for-synapse.sh
#
# based on an exmaple from https://docs.docker.com/compose/startup-order/
#
# command: ["/usr/src/app/wait-for-synapse.sh", "http://synapse:8008", "python", "/usr/src/app/pnut-matrix.py", "-d"]
set -e
host="$1"
# Shift arguments with mapping:
# - $0 => $0
# - $1 => <discarded>
# - $2 => $1
# - $3 => $2
# - ...
# This is done for `exec "$@"` below to work correctly
shift
until [ "200" -eq $(curl -s -o /dev/null --head -w "%{http_code}" ${host}/_matrix/client/versions) ]; do
>&2 echo "synapse is unavailable - sleeping"
sleep 3
done
>&2 echo "synapse is up - executing command"
exec "$@"