#!/usr/bin/env python3 import os import sys import re import json import requests # --- ANSI color helpers --- def color(text, code): return f"\033[{code}m{text}\033[0m" def green(text): return color(text, "32") def red(text): return color(text, "31") def yellow(text): return color(text, "33") def cyan(text): return color(text, "36") def bold(text): return color(text, "1") # --- Inputs --- platform = os.environ.get("PLATFORM", "github").lower() token = os.environ.get("TOKEN") owner = os.environ.get("REPO_OWNER", os.environ.get("GITHUB_REPOSITORY_OWNER")) repo = os.environ.get("REPO_NAME", os.environ.get("GITHUB_REPOSITORY")) pr_index = os.environ.get("PR_INDEX") api_url = os.environ.get("API_URL", os.environ.get("GITHUB_API_URL")) diff_text = os.environ.get("DIFF") comment_template = os.environ.get("COMMENT_TEMPLATE", "Auto-comment: changed line -> {line}") debug_mode = os.environ.get("DEBUG", "false").lower() == "true" if not token or not pr_index: print(red("❌ TOKEN and PR_INDEX are required.")) sys.exit(1) # --- Build URLs and headers --- def build_comment_url(): if platform == "github": return f"https://api.github.com/repos/{owner}/{repo}/issues/{pr_index}/comments" elif platform == "gitea": if not api_url: print(red("❌ API_URL required for Gitea platform.")) sys.exit(1) return f"{api_url}/repos/{owner}/{repo}/pulls/{pr_index}/reviews" else: print(red(f"❌ Unsupported platform: {platform}")) sys.exit(1) def build_headers(): return { "Authorization": f"token {token}", "Content-Type": "application/json", "Accept": "application/json", } # --- POST comment --- def post_comment(body_text): url = build_comment_url() headers = build_headers() payload = {"body": body_text} if debug_mode: preview = (body_text[:400] + "... [truncated]") if len(body_text) > 400 else body_text print(bold(cyan("🧩 DEBUG MODE ENABLED"))) print(f" {yellow('Platform:')} {platform}") print(f" {yellow('Repo:')} {owner}/{repo}") print(f" {yellow('PR Index:')} {pr_index}") print(f" {yellow('Endpoint:')} {url}") print(f" {yellow('Comment size:')} {len(body_text)} characters") print(f" {yellow('Payload preview:')} {json.dumps(payload)[:200]}{'... [truncated]' if len(json.dumps(payload))>200 else ''}") print(f" {yellow('Comment preview:')}\n{preview}\n{'-'*60}") try: resp = requests.post(url, headers=headers, json=payload, timeout=60) if resp.status_code in (200, 201): print(green(f"✅ Comment posted successfully to {platform.capitalize()}!")) else: print(red(f"❌ Failed to post comment ({resp.status_code}):")) print(resp.text) sys.exit(1) except requests.exceptions.RequestException as e: print(red(f"❌ Request error: {e}")) sys.exit(1) # --- No diff provided --- if not diff_text: body = comment_template.replace("{line}", "").replace("{lines}", "") post_comment(body) sys.exit(0) # --- Parse unified diff --- diff_files = {} current_file = None new_line_num = 0 for line in diff_text.splitlines(): if line.startswith("+++ b/"): current_file = line[6:].strip() diff_files[current_file] = [] new_line_num = 0 elif line.startswith("@@"): m = re.match(r"@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@", line) if m: new_line_num = int(m.group(1)) - 1 elif line.startswith("+") and not line.startswith("+++"): new_line_num += 1 content = line[1:] diff_files[current_file].append((new_line_num, content)) elif not line.startswith("-"): new_line_num += 1 # --- Post combined comment per file --- for file_path, lines in diff_files.items(): if not lines: continue all_lines_content = "\n".join([line_content for _, line_content in lines]) body = comment_template.replace("{lines}", all_lines_content) post_comment(body) print(green("🎉 All comments posted successfully."))