From fbe66b147337807cd67531bde430858d0fff6f1f Mon Sep 17 00:00:00 2001 From: disqualifier Date: Mon, 22 Jun 2026 19:48:41 -0400 Subject: [PATCH] add proxy app to repo Signed-off-by: disqualifier --- app.py | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 app.py diff --git a/app.py b/app.py new file mode 100644 index 0000000..d0d0eae --- /dev/null +++ b/app.py @@ -0,0 +1,111 @@ +""" +discord webhook reformat proxy + +drop-in for discord.com: swap the hostname on a gitea (discord-type) webhook +so it points here, e.g. + https://discord.com/api/webhooks// +becomes + https://proxy.example.com/api/webhooks// + +the proxy receives gitea's native discord embed, rewrites only the embed +`description` into github's `[`hash`](url)` backtick style (hash backticked, +commit message collapsed to its first line, author kept), and forwards the +otherwise-untouched payload to the real discord webhook. everything gitea +already handled (title, branches, multiple commits, tags, colors) passes +through as-is. + +run: + pip install fastapi httpx uvicorn + uvicorn discord_reformat_proxy:app --host 127.0.0.1 --port 8000 + +then set a gitea webhook (type: discord) to: + http://127.0.0.1:8000/api/webhooks// +""" + +import re +import logging + +import httpx +from fastapi import FastAPI, Request, Response + +log = logging.getLogger(__name__) + +app = FastAPI() + +DISCORD_BASE = "https://discord.com" +TIMEOUT = 10 + +# gitea commit line: [hash](url) - author +# github commit line: [`hash`](url) - author +# match a hash-link, then the message up to an optional trailing " - author", +# stopping at the next commit line or end of string. +_COMMIT = re.compile( + r"\[`?([0-9a-f]{6,40})`?\]\((https?://[^)]+)\)" + r"\s+(.*?)" + r"(\s-\s[^\n]+?)?" + r"(?=\n\[|\Z)", + re.DOTALL, +) + + +def _commit_sub(m: "re.Match") -> str: + """rebuild one commit line github-style: backtick hash, first message line only""" + short, url, message, author = m.group(1), m.group(2), m.group(3), m.group(4) or "" + first_line = message.strip().split("\n", 1)[0].strip() + return f"[`{short}`]({url}) {first_line}{author}" + + +def _reformat(description: str) -> str: + """rewrite gitea commit lines into github-style (backtick hash, one line each)""" + return _COMMIT.sub(_commit_sub, description) + + +@app.get("/healthz") +async def healthz(): + """liveness probe""" + return {"ok": True} + + +@app.post("/api/webhooks/{webhook_id}/{token}") +async def proxy(webhook_id: str, token: str, request: Request): + """reformat embed descriptions and forward to the real discord webhook""" + try: + payload = await request.json() + except Exception: + body = await request.body() + async with httpx.AsyncClient(timeout=TIMEOUT) as client: + resp = await client.post( + f"{DISCORD_BASE}/api/webhooks/{webhook_id}/{token}", + content=body, + params=dict(request.query_params), + headers={"content-type": request.headers.get("content-type", "application/json")}, + ) + return _passthrough(resp) + + if isinstance(payload, dict): + for embed in payload.get("embeds") or []: + if isinstance(embed, dict) and embed.get("description"): + embed["description"] = _reformat(embed["description"]) + + async with httpx.AsyncClient(timeout=TIMEOUT) as client: + resp = await client.post( + f"{DISCORD_BASE}/api/webhooks/{webhook_id}/{token}", + json=payload, + params=dict(request.query_params), + ) + + return _passthrough(resp) + + +def _passthrough(resp: httpx.Response) -> Response: + """return discord's response verbatim, preserving rate-limit headers""" + forward = { + k: v for k, v in resp.headers.items() + if k.lower().startswith("x-ratelimit") or k.lower() == "retry-after" + } + return Response( + content=resp.content, + status_code=resp.status_code, + media_type=resp.headers.get("content-type"), + headers=forward, + )