#!/usr/bin/env python3 """ Gitea → OpenProject Webhook Handler Parses commit messages for WP-XXX patterns and adds comments to Work Packages """ import os import re import json import base64 import urllib.request from http.server import HTTPServer, BaseHTTPRequestHandler OPENPROJECT_URL = os.getenv("OPENPROJECT_URL", "https://project.toppyr.de") OPENPROJECT_API_KEY = os.getenv("OPENPROJECT_API_KEY", "") GITEA_URL = os.getenv("GITEA_URL", "https://git.toppyr.de") WP_PATTERN = re.compile(r"WP-(\d+)", re.IGNORECASE) class WebhookHandler(BaseHTTPRequestHandler): def do_POST(self): content_length = int(self.headers.get("Content-Length", 0)) body = self.rfile.read(content_length) try: payload = json.loads(body) self.process_push(payload) self.send_response(200) self.end_headers() self.wfile.write(b"OK") except Exception as e: print(f"Error: {e}") self.send_response(500) self.end_headers() self.wfile.write(str(e).encode()) def process_push(self, payload): repo_name = payload.get("repository", {}).get("full_name", "unknown") commits = payload.get("commits", []) for commit in commits: message = commit.get("message", "") sha = commit.get("id", "")[:8] author = commit.get("author", {}).get("name", "Unknown") url = commit.get("url", "") # Find all WP-XXX references matches = WP_PATTERN.findall(message) for wp_id in matches: self.add_openproject_comment( wp_id=wp_id, repo=repo_name, sha=sha, message=message, author=author, url=url ) def add_openproject_comment(self, wp_id, repo, sha, message, author, url): if not OPENPROJECT_API_KEY: print(f"No API key - would comment on WP-{wp_id}") return comment = f"""**Git Commit** [{sha}]({url}) **Repository:** {repo} **Author:** {author} ``` {message} ``` """ api_url = f"{OPENPROJECT_URL}/api/v3/work_packages/{wp_id}/activities" data = json.dumps({"comment": {"raw": comment}}).encode() # Basic auth with apikey as username auth = base64.b64encode(f"apikey:{OPENPROJECT_API_KEY}".encode()).decode() req = urllib.request.Request( api_url, data=data, headers={ "Content-Type": "application/json", "Authorization": f"Basic {auth}" }, method="POST" ) try: with urllib.request.urlopen(req) as resp: print(f"Added comment to WP-{wp_id}: {resp.status}") except Exception as e: print(f"Failed to comment on WP-{wp_id}: {e}") def log_message(self, format, *args): print(f"[Webhook] {args[0]}") if __name__ == "__main__": port = int(os.getenv("PORT", 9000)) print(f"Starting webhook handler on port {port}") HTTPServer(("0.0.0.0", port), WebhookHandler).serve_forever()