initial commit

This commit is contained in:
Thies Mueller
2025-09-24 00:03:38 +02:00
commit 032941ae0a
4 changed files with 281 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
.venv
__pycache__
*.pyc
config.yaml
printed.csv

81
config.yaml.example Normal file
View File

@@ -0,0 +1,81 @@
mails:
sender: sender@email.address
server: server.example.org
user: sender-user
password: sender-pass
port: 587
starttls: true
meals:
tag-1mittag:
subevent: 1
date: 2025-12-25
name: "Tag -1 Mittagessen"
name_en: "Day -1 Lunch"
tag-1abend:
subevent: 2
date: 2025-12-25
name: "Tag -1 Abendessen"
name_en: "Day -1 Dinner"
tag0mittag:
subevent: 3
date: 2025-12-26
name: "Tag 0 Mittagessen"
name_en: "Day 0 Lunch"
tag0abend:
subevent: 4
date: 2025-12-26
name: "Tag 0 Abendessen"
name_en: "Day 0 Dinner"
tag1mittag:
subevent: 5
date: 2025-12-27
name: "Tag 1 Mittagessen"
name_en: "Day 1 Lunch"
tag1abend:
subevent: 6
date: 2025-12-27
name: "Tag 1 Abendessen"
name_en: "Day 1 Dinner"
tag2mittag:
subevent: 7
date: 2025-12-28
name: "Tag 2 Mittagessen"
name_en: "Day 2 Lunch"
tag2abend:
subevent: 8
date: 2025-12-28
name: "Tag 2 Abendessen"
name_en: "Day 2 Dinner"
tag3mittag:
subevent: 9
date: 2025-12-29
name: "Tag 3 Mittagessen"
name_en: "Day 3 Lunch"
tag3abend:
subevent: 10
date: 2025-12-29
name: "Tag 3 Abendessen"
name_en: "Day 3 Dinner"
tag4mittag:
subevent: 11
date: 2025-12-30
name: "Tag 4 Mittagessen"
name_en: "Day 4 Lunch"
tag4abend:
subevent: 12
date: 2025-12-30
name: "Tag 4 Abendessen"
name_en: "Day 4 Dinner"
types:
crew: id_for_crew
regular: id_for_regular
pretix:
host: https://tickets.events.ccc.de
organizer: organizer
event: eventslug
token: randomtokenforpretixapiaccess1234567890
auth:
token: randomtokenforauthaccess1234567890

174
main.py Normal file
View File

@@ -0,0 +1,174 @@
import time
import smtplib
import yaml
import requests
import csv
import qrcode
import io
from email.message import EmailMessage
from flask import Flask, request, jsonify, abort
from jinja2 import Template
from functools import wraps
from datetime import datetime
app = Flask(__name__)
with open("config.yaml", "r") as f:
config = yaml.safe_load(f)
MAIL_CONFIG = config["mails"]
MEALS = config["meals"]
TYPES = config["types"]
PRETIX = config["pretix"]
AUTH_TOKEN = config.get("auth", {}).get("token")
with open("template.txt", "r") as f:
MAIL_TEMPLATE = Template(f.read())
def require_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
token = request.headers.get("Authorization")
if not token or token != f"Bearer {AUTH_TOKEN}":
abort(401, description="Unauthorized: Invalid or missing token")
return f(*args, **kwargs)
return decorated
def send_email(to_email, subject, body, attachment_bytes, filename):
print(f"[DEBUG] Sending email to {to_email} with attachment {filename}")
msg = EmailMessage()
msg["From"] = MAIL_CONFIG["sender"]
msg["To"] = to_email
msg["Subject"] = subject
msg.set_content(body)
msg.add_attachment(attachment_bytes, maintype="application", subtype="pdf", filename=filename)
server = smtplib.SMTP(MAIL_CONFIG["server"], MAIL_CONFIG["port"])
if MAIL_CONFIG.get("starttls"):
server.starttls()
server.login(MAIL_CONFIG["user"], MAIL_CONFIG["password"])
server.send_message(msg)
server.quit()
print("[DEBUG] Email sent successfully")
def get_meal_times(meal_key):
if meal_key.endswith("mittag"):
return "11:30 - 13:30"
elif meal_key.endswith("abend"):
return "17:30 - 19:30"
return ""
def log_printed(email, meal_key):
location = email.split("@")[0]
now = datetime.now()
date_str = now.strftime("%Y-%m-%d")
time_str = now.strftime("%H:%M:%S")
with open("printed.csv", "a", newline="") as csvfile:
writer = csv.writer(csvfile, delimiter=";")
writer.writerow([date_str, time_str, meal_key, location])
print(f"[DEBUG] Logged printed PDF: {location} - {meal_key}")
def generate_qr(secret):
qr = qrcode.QRCode(box_size=10, border=4)
qr.add_data(secret)
qr.make(fit=True)
img = qr.make_image(fill_color="black", back_color="white")
buf = io.BytesIO()
img.save(buf, format="PNG")
buf.seek(0)
return buf.getvalue()
@app.route("/order", methods=["POST"])
@require_auth
def order():
data = request.get_json()
print(f"[DEBUG] Incoming request JSON: {data}")
email = data.get("email")
typ = data.get("type")
meal_key = data.get("meal")
if not email or not typ or not meal_key:
return jsonify({"error": "Missing field"}), 400
meal_info = MEALS.get(meal_key)
if not meal_info:
return jsonify({"error": "Meal not found in config"}), 400
position = {
"positionid": 1,
"item": TYPES.get(typ),
"variation": None,
"price": "0",
"attendee_email": None,
"addon_to": None,
"subevent": meal_info["subevent"]
}
pretix_body = {
"email": "pretixfood@td00.de",
"locale": "en",
"sales_channel": "web",
"payment_provider": "manual",
"positions": [position]
}
url = f"{PRETIX['host']}/api/v1/organizers/{PRETIX['organizer']}/events/{PRETIX['event']}/orders/"
headers = {"Authorization": f"Token {PRETIX['token']}"}
print(f"[DEBUG] Sending POST to Pretix: {url} with body {pretix_body}")
resp = requests.post(url, json=pretix_body, headers=headers)
print(f"[DEBUG] Pretix response status: {resp.status_code}")
print(f"[DEBUG] Pretix response text: {resp.text}")
try:
resp_json = resp.json()
print(f"[DEBUG] Pretix response JSON: {resp_json}")
except ValueError:
print("[DEBUG] Pretix returned no JSON")
return jsonify({"error": "Pretix returned no JSON", "status_code": resp.status_code, "text": resp.text}), 502
if "positions" in resp_json and "item" in resp_json["positions"][0] and isinstance(resp_json["positions"][0]["item"], list):
if isinstance(resp_json["positions"][0]["item"][0], str):
print("[DEBUG] Not enough quota available")
return "Essen ist bereits alle", 418
try:
secret = resp_json["positions"][0]["secret"]
print(f"[DEBUG] Secret: {secret}")
except (KeyError, IndexError):
return jsonify({"error": "Secret not found in Pretix response", "resp_json": resp_json}), 500
qr_bytes = generate_qr(secret)
meal_times = get_meal_times(meal_key)
mail_body = MAIL_TEMPLATE.render(
meal_name=meal_info["name"],
meal_name_en=meal_info["name_en"],
meal_date=meal_info["date"],
meal_times=meal_times
)
if email.endswith("@printme.local"):
log_printed(email, meal_key)
response = app.response_class(
response=qr_bytes,
status=200,
mimetype='image/png'
)
response.headers["Content-Disposition"] = f"inline; filename=ticket.png"
return response
send_email(email, "Dein Engelessen / Your Angel Meal", mail_body, qr_bytes, "ticket.png")
return "Token gesendet", 201
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8000, debug=True)

21
template.txt Normal file
View File

@@ -0,0 +1,21 @@
Hallo,
Hier sind deine Informationen zum Essen:
Mahlzeit: {{meal_name}}
Datum: {{meal_date}}
Essenszeiten: {{meal_times}}
Bitte zeige den QR-Code vor, um dein Essen abzuholen.
---
Hello,
Here is your meal information:
Meal: {{meal_name_en}}
Date: {{meal_date}}
Meal times: {{meal_times}}
Please show the QR code to pick up your meal.