commit 99c2c62831646004891e05d1da993f1285672546 Author: Finn Christiansen Date: Wed Jun 26 19:52:11 2024 +0200 🎉 First commit! diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1a3a766 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.env +bin +lib +include diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..4b47705 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +BOT_HOMESERVER="https://example.org" +BOT_USERNAME="PrayingTimesBotUsername" +BOT_PASSWORD="supersecret" +REGISTRATION_API_URL="http://localhost:5001/api/token" +REGISTRATION_API_SHARED_SECRET="supersecret" +REGISTRATION_URL="https://matrix.example.org/register?token=" +SMTP_HOSTNAME="smtp.example.org" +SMTP_USERNAME="username" +SMTP_PASSWORD="password" +MAIL_FROM_ADDRESS="me@example.org" diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml new file mode 100644 index 0000000..ffeef98 --- /dev/null +++ b/.forgejo/workflows/ci.yaml @@ -0,0 +1,38 @@ +name: ci + +on: + push: + branches: + - "main" + +jobs: + docker: + runs-on: ubuntu-latest + services: + registry: + image: registry:2 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Docker for QEMU + uses: https://github.com/papodaca/install-docker-action@main + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Log in to the Container registry + uses: docker/login-action@v2 + with: + registry: code.f2n.me + username: ${{ secrets.MY_FORGEJO_USERNAME }} + password: ${{ secrets.MY_FORGEJO_TOKEN }} + - name: Build and push to local registry + uses: docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: code.f2n.me/finn/matrix-bot-invitation-mailer:latest + - name: Inspect + run: | + docker buildx imagetools inspect code.f2n.me/finn/matrix-bot-invitation-mailer:latest diff --git a/.forgejo/workflows/python-pep8.yaml b/.forgejo/workflows/python-pep8.yaml new file mode 100644 index 0000000..67a431a --- /dev/null +++ b/.forgejo/workflows/python-pep8.yaml @@ -0,0 +1,21 @@ +name: Python formatting PEP8 +run-name: ${{ forgejo.actor }} is running PEP8 check +on: [push] + +jobs: + Python-PEP8: + runs-on: ubuntu-22.04 + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + apt-get update + apt-get install -y python3-pip + pip install flake8 + + - name: Run checking + run: | + flake8 --ignore=E501 --extend-exclude bin,lib . diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23b7304 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +bin/ +lib/ +include/ +__pycache__ +pyvenv.cfg +.env +session.txt +tags diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f812f0d --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +# syntax=docker/dockerfile:1 +FROM python:3.12-alpine + +WORKDIR /bot + +RUN apk update && apk add python3-dev gcc libc-dev libffi-dev + +COPY requirements.txt requirements.txt +RUN pip3 install -r requirements.txt + +COPY . . + +ENTRYPOINT ["/bot/entrypoint.sh"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..16ce889 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# Matrix Bot for sending homeserver invitation link via e-mail + +A early stage and simlpe Matrix bot which I use in addition to [matrix-registration](https://github.com/ZerataX/matrix-registration). + +The bot calls the Api and sends the invitation link via e-mail to a given receipient. + +## Usage + +Use container image or run this after cloning: + +``` +. bin/active +python -m matrix_bot_invitation_mailer +``` + diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..5935b1f --- /dev/null +++ b/compose.yml @@ -0,0 +1,17 @@ +--- +services: + matrix-bot-invitation-mailer: + build: + context: . + image: code.f2n.me/finn/matrix-bot-invitation-mailer:latest + environment: + - BOT_HOMESERVER + - BOT_USERNAME + - BOT_PASSWORD + - REGISTRATION_API_URL + - REGISTRATION_API_SHARED_SECRET + - REGISTRATION_URL + - SMTP_HOSTNAME + - SMTP_USERNAME + - SMTP_PASSWORD + - MAIL_FROM_ADDRESS diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..5e7d273 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 0000000..f330afc --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,2 @@ +#!/bin/sh +python -m matrix_bot_invitation_mailer diff --git a/matrix_bot_invitation_mailer/__init__.py b/matrix_bot_invitation_mailer/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/matrix_bot_invitation_mailer/__main__.py b/matrix_bot_invitation_mailer/__main__.py new file mode 100644 index 0000000..d8911c5 --- /dev/null +++ b/matrix_bot_invitation_mailer/__main__.py @@ -0,0 +1,113 @@ +import simplematrixbotlib as botlib +import requests +import os +import logging +import smtplib +import datetime +from email.mime.text import MIMEText +from dotenv import load_dotenv + + +load_dotenv() + +creds = botlib.Creds( + os.getenv("BOT_HOMESERVER"), + os.getenv("BOT_USERNAME"), + os.getenv("BOT_PASSWORD"), + session_stored_file="data/session.txt" +) + +bot = botlib.Bot(creds) +PREFIX = '!' + + +logging.basicConfig(level=logging.INFO) + + +@bot.listener.on_message_event +async def echo(room, message) -> None: + match = botlib.MessageMatch(room, message, bot, PREFIX) + + if match.is_not_from_this_bot() and match.prefix() and match.command("echo"): + + await bot.api.send_text_message( + room.room_id, " ".join(arg for arg in match.args()) + ) + + +@bot.listener.on_message_event +async def usage(room, message) -> None: + match = botlib.MessageMatch(room, message, bot, PREFIX) + + response = """usage: +- **!invite** - sends e-mail with invitation link to given mail address +- **everything else** - prints this help + """ + + if match.is_not_from_this_bot() and not match.prefix() and room.room_id: + await bot.api.send_markdown_message(room.room_id, response) + + +@bot.listener.on_message_event +async def invite(room, message) -> None: + match = botlib.MessageMatch(room, message, bot, PREFIX) + + if match.is_not_from_this_bot() and match.prefix() and match.command("invite"): + logging.info("preparing invite") + # TODO: add validation + mail_address = " ".join(arg for arg in match.args()) + + send_invitation_mail(mail_address) + response = "mail sent" + await bot.api.send_text_message(room.room_id, response) + + +def send_invitation_mail(receiver_email): + logging.info("sending email...") + + # make reqest to obtain registration token + headers = { + 'Content-Type': "application/json", + 'Authorization': "SharedSecret {}".format(os.getenv("REGISTRATION_API_SHARED_SECRET")) + } + payload = { + "max-usage": "1", + "expiration_date": (datetime.date.today() + datetime.timedelta(days=7)).strftime("%Y-%m-%d") + } + response = requests.post(os.getenv("REGISTRATION_API_URL"), headers=headers, json=payload) + logging.info(response.text) + logging.info(response.json()) + sender_email = os.getenv("MAIL_FROM_ADDRESS") + + body = """Hello, + +you have been invited to the f2n.me matrix homeserver! + +Click the following link and create your account: + +{} + +The link will be valid for 7 days.""".format(os.getenv("REGISTRATION_URL") + response.json()["name"]) + msg = MIMEText(body, 'plain') + msg['From'] = sender_email + msg['To'] = receiver_email + msg['Subject'] = "Your matrix homeserver invitation link" + + try: + # Connect to the SMTP server + server = smtplib.SMTP(os.getenv("SMTP_HOSTNAME"), os.getenv("SMTP_PORT")) + server.starttls() # Secure the connection + server.login(os.getenv("SMTP_USERNAME"), os.getenv("SMTP_PASSWORD")) # Login to the server + + # Send the email + server.sendmail(sender_email, receiver_email, msg.as_string()) + + print("Email sent successfully") + except Exception as e: + print(f"Error: {e}") + finally: + # Close the connection + server.quit() + + +bot.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..7729eb7 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,35 @@ +aiofiles==23.2.1 +aiohttp==3.9.5 +aiohttp-socks==0.8.4 +aiosignal==1.3.1 +async-timeout==4.0.3 +attrs==23.2.0 +certifi==2024.6.2 +cffi==1.16.0 +charset-normalizer==3.3.2 +cryptography==42.0.8 +frozenlist==1.4.1 +h11==0.14.0 +h2==4.1.0 +hpack==4.0.0 +hyperframe==6.0.1 +idna==3.7 +jsonschema==4.22.0 +jsonschema-specifications==2023.12.1 +Markdown==3.6 +matrix-nio==0.24.0 +multidict==6.0.5 +pillow==10.3.0 +pycparser==2.22 +pycryptodome==3.20.0 +python-cryptography-fernet-wrapper==1.0.4 +python-dotenv==1.0.1 +python-socks==2.5.0 +referencing==0.35.1 +requests==2.32.3 +rpds-py==0.18.1 +simplematrixbotlib==2.11.0 +toml==0.10.2 +unpaddedbase64==2.1.0 +urllib3==2.2.2 +yarl==1.9.4