Compare commits

...

12 commits
1.3.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
8 changed files with 171 additions and 54 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,11 @@ 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

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

@ -43,7 +43,7 @@ curl --data '{"type": "m.login.application_service", "username": "your_sender_lo
## 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 to the [mailing list] or directly to [morgan@mcmillian.dev].
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.
@ -59,7 +59,6 @@ GPLv3, see [LICENSE].
[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/
[mailing list]: https://lists.sr.ht/~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

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:]
@ -64,9 +65,11 @@ def query_alias(alias):
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)
@ -79,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)
@ -88,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):
@ -382,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
@ -436,9 +446,11 @@ def create_room(channel, user):
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:
@ -451,13 +463,17 @@ def create_room(channel, user):
)
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','user': app.config['MATRIX_PNUT_PREFIX'] + username}
data = {'type': 'm.login.application_service','username': app.config['MATRIX_PNUT_PREFIX'] + username}
try:
matrix_api.register(content=data)
@ -466,13 +482,13 @@ def new_matrix_user(username):
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:
@ -555,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'])
@ -568,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,
@ -588,7 +604,7 @@ def cmd_admin_link(room_id, pnut_chan_id):
return errmsg
def cmd_admin_unlink(rid):
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'])
@ -730,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:
@ -839,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')

View file

@ -70,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:
@ -83,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)
@ -92,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)
@ -124,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)
@ -139,7 +141,7 @@ 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(time.time()) * 1000
@ -184,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)
@ -195,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)
@ -227,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)
@ -258,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)
@ -287,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'
@ -310,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']:
@ -405,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))

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 "$@"