Proper Newznab Category / Download Category Support #355
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Hostname Redaction | |
| on: | |
| issues: | |
| types: [ opened, edited ] | |
| pull_request: | |
| types: [ opened, closed, reopened, synchronize, edited ] | |
| issue_comment: | |
| types: [ created, edited ] | |
| pull_request_review_comment: | |
| types: [ created, edited ] | |
| jobs: | |
| redact-hostnames: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 1 | |
| permissions: | |
| issues: write | |
| pull-requests: write | |
| steps: | |
| - name: Redact hostnames | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| HOSTNAMES_URL: ${{ secrets.HOSTNAMES_URL }} | |
| EVENT_NAME: ${{ github.event_name }} | |
| REPO: ${{ github.repository }} | |
| SENDER: ${{ github.event.sender.login }} | |
| # Issue fields | |
| ISSUE_BODY: ${{ github.event.issue.body }} | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| # PR fields | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| PR_TITLE: ${{ github.event.pull_request.title }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| # Comment fields | |
| COMMENT_BODY: ${{ github.event.comment.body }} | |
| COMMENT_ID: ${{ github.event.comment.id }} | |
| run: | | |
| python - <<'EOF' | |
| import json | |
| import os | |
| import re | |
| import subprocess | |
| import urllib.request | |
| # Load environment | |
| HOSTNAMES_URL = os.environ.get("HOSTNAMES_URL", "") | |
| EVENT_NAME = os.environ.get("EVENT_NAME", "") | |
| REPO = os.environ.get("REPO", "") | |
| SENDER = os.environ.get("SENDER", "") | |
| ISSUE_BODY = os.environ.get("ISSUE_BODY") or "" | |
| ISSUE_TITLE = os.environ.get("ISSUE_TITLE") or "" | |
| ISSUE_NUMBER = os.environ.get("ISSUE_NUMBER") or "" | |
| PR_BODY = os.environ.get("PR_BODY") or "" | |
| PR_TITLE = os.environ.get("PR_TITLE") or "" | |
| PR_NUMBER = os.environ.get("PR_NUMBER") or "" | |
| COMMENT_BODY = os.environ.get("COMMENT_BODY") or "" | |
| COMMENT_ID = os.environ.get("COMMENT_ID") or "" | |
| # Determine event type | |
| is_pr_event = EVENT_NAME == "pull_request" | |
| is_issue_event = EVENT_NAME == "issues" | |
| is_issue_comment = EVENT_NAME == "issue_comment" | |
| is_pr_review_comment = EVENT_NAME == "pull_request_review_comment" | |
| is_comment = is_issue_comment or is_pr_review_comment | |
| # Get the right number for commenting | |
| if is_pr_event or is_pr_review_comment: | |
| NUMBER = PR_NUMBER | |
| else: | |
| NUMBER = ISSUE_NUMBER | |
| # Prevent infinite loop when the action itself edits | |
| if SENDER.endswith("[bot]"): | |
| print(f"Edit by bot ({SENDER}), skipping") | |
| exit(0) | |
| if not HOSTNAMES_URL: | |
| print("HOSTNAMES_URL secret not set, skipping") | |
| exit(0) | |
| if not NUMBER: | |
| print("Could not determine issue/PR number, skipping") | |
| exit(0) | |
| # Fetch hostname list | |
| try: | |
| req = urllib.request.Request(HOSTNAMES_URL, headers={"User-Agent": "Mozilla/5.0"}) | |
| with urllib.request.urlopen(req, timeout=10) as resp: | |
| hostnames_data = resp.read().decode("utf-8") | |
| except Exception as e: | |
| print(f"Failed to fetch hostnames: {e}") | |
| exit(1) | |
| # Parse hostname list into domain_base -> alias mapping | |
| domain_to_alias = {} | |
| for line in hostnames_data.strip().splitlines(): | |
| if "=" not in line: | |
| continue | |
| alias, hostname = line.split("=", 1) | |
| alias = alias.strip() | |
| hostname = hostname.strip() | |
| if "." in hostname: | |
| domain_base = hostname.rsplit(".", 1)[0] | |
| domain_to_alias[domain_base.lower()] = alias | |
| if not domain_to_alias: | |
| print("No hostnames parsed, skipping") | |
| exit(0) | |
| # Build regex pattern to match domains with any TLD | |
| escaped_domains = [re.escape(d) for d in domain_to_alias.keys()] | |
| pattern = re.compile( | |
| r'\b(' + '|'.join(escaped_domains) + r')\.[a-z]{2,}(?![a-z.])', | |
| re.IGNORECASE | |
| ) | |
| redacted_count = 0 | |
| redacted_aliases = set() | |
| def replace_hostname(match): | |
| global redacted_count | |
| domain_base = match.group(1).lower() | |
| alias = domain_to_alias.get(domain_base, "??") | |
| redacted_count += 1 | |
| redacted_aliases.add(alias) | |
| return f"({alias} redacted)" | |
| def post_warning(count, target_type): | |
| """Post a warning comment about redacted hostnames.""" | |
| if count == 1: | |
| msg = f"⚠️ **1 hostname was automatically redacted from this {target_type}.**" | |
| else: | |
| msg = f"⚠️ **{count} hostnames were automatically redacted from this {target_type}.**" | |
| msg += "\n\nPlease use two-letter aliases (e.g. `al`, `dd`, `nx`) instead of actual hostnames." | |
| if is_pr_event or is_pr_review_comment: | |
| subprocess.run( | |
| ["gh", "pr", "comment", NUMBER, "--body", msg, "-R", REPO], | |
| check=True | |
| ) | |
| else: | |
| subprocess.run( | |
| ["gh", "issue", "comment", NUMBER, "--body", msg, "-R", REPO], | |
| check=True | |
| ) | |
| # Handle comments (issue comments, PR comments, PR review comments) | |
| if is_comment: | |
| if not COMMENT_BODY: | |
| print("Comment is empty, skipping") | |
| exit(0) | |
| matches = pattern.findall(COMMENT_BODY) | |
| if not matches: | |
| print("No hostnames found in comment") | |
| exit(0) | |
| # Count unique aliases found | |
| for domain_base in matches: | |
| alias = domain_to_alias.get(domain_base.lower(), "??") | |
| redacted_count += 1 | |
| redacted_aliases.add(alias) | |
| print(f"Found {redacted_count} hostname(s) in comment: {', '.join(sorted(redacted_aliases))}") | |
| # Delete the comment (editing leaves visible history) | |
| if is_pr_review_comment: | |
| api_endpoint = f"/repos/{REPO}/pulls/comments/{COMMENT_ID}" | |
| else: | |
| api_endpoint = f"/repos/{REPO}/issues/comments/{COMMENT_ID}" | |
| subprocess.run( | |
| ["gh", "api", "--method", "DELETE", api_endpoint], | |
| check=True | |
| ) | |
| # Post explanation | |
| if redacted_count == 1: | |
| msg = f"🗑️ **A comment was automatically deleted because it contained a hostname.**" | |
| else: | |
| msg = f"🗑️ **A comment was automatically deleted because it contained {redacted_count} hostnames.**" | |
| msg += "\n\nPlease repost using two-letter aliases (e.g. `al`, `dd`, `nx`) instead of actual hostnames." | |
| if is_pr_review_comment: | |
| subprocess.run( | |
| ["gh", "pr", "comment", NUMBER, "--body", msg, "-R", REPO], | |
| check=True | |
| ) | |
| else: | |
| subprocess.run( | |
| ["gh", "issue", "comment", NUMBER, "--body", msg, "-R", REPO], | |
| check=True | |
| ) | |
| # Handle issues | |
| elif is_issue_event: | |
| if not ISSUE_TITLE and not ISSUE_BODY: | |
| print("Issue is empty, skipping") | |
| exit(0) | |
| new_title = pattern.sub(replace_hostname, ISSUE_TITLE) | |
| new_body = pattern.sub(replace_hostname, ISSUE_BODY) | |
| if redacted_count == 0: | |
| print("No hostnames found in issue") | |
| exit(0) | |
| print(f"Redacted {redacted_count} hostname(s) from issue: {', '.join(sorted(redacted_aliases))}") | |
| with open("new_body.md", "w") as f: | |
| f.write(new_body) | |
| cmd = ["gh", "issue", "edit", NUMBER, "--body-file", "new_body.md", "-R", REPO] | |
| if new_title != ISSUE_TITLE: | |
| cmd.extend(["--title", new_title]) | |
| subprocess.run(cmd, check=True) | |
| post_warning(redacted_count, "issue") | |
| # Handle pull requests | |
| elif is_pr_event: | |
| if not PR_TITLE and not PR_BODY: | |
| print("PR is empty, skipping") | |
| exit(0) | |
| new_title = pattern.sub(replace_hostname, PR_TITLE) | |
| new_body = pattern.sub(replace_hostname, PR_BODY) | |
| if redacted_count == 0: | |
| print("No hostnames found in PR") | |
| exit(0) | |
| print(f"Redacted {redacted_count} hostname(s) from PR: {', '.join(sorted(redacted_aliases))}") | |
| with open("new_body.md", "w") as f: | |
| f.write(new_body) | |
| cmd = ["gh", "pr", "edit", NUMBER, "--body-file", "new_body.md", "-R", REPO] | |
| if new_title != PR_TITLE: | |
| cmd.extend(["--title", new_title]) | |
| subprocess.run(cmd, check=True) | |
| post_warning(redacted_count, "pull request") | |
| else: | |
| print(f"Unknown event type: {EVENT_NAME}") | |
| exit(0) | |
| print("Done") | |
| EOF |