docs: add getting started community tutorial #1417
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: Spam Guard | |
| # Handles T1/T2 bounty comment moderation: | |
| # - Deletes tiny/spam comments | |
| # - Redirects "claiming" attempts on T1/T2 with FCFS rules | |
| # - Auto-deletes repeat offenders | |
| # T3 claim approval is handled separately by claim-guard.yml | |
| on: | |
| issue_comment: | |
| types: [created] | |
| permissions: | |
| issues: write | |
| jobs: | |
| spam-guard: | |
| runs-on: ubuntu-latest | |
| # Skip T3 issues — those go to claim-guard.yml | |
| if: "!contains(join(github.event.issue.labels.*.name, ','), 'tier-3')" | |
| concurrency: | |
| group: spam-guard-${{ github.event.issue.number }}-${{ github.event.comment.user.login }} | |
| steps: | |
| - name: Moderate comments on T1/T2 bounties | |
| env: | |
| GH_TOKEN: ${{ secrets.SOLFOUNDRY_GITHUB_PAT }} | |
| ISSUE_LABELS: ${{ join(github.event.issue.labels.*.name, ',') }} | |
| ISSUE_TITLE: ${{ github.event.issue.title }} | |
| COMMENT_BODY: ${{ github.event.comment.body }} | |
| COMMENT_USER: ${{ github.event.comment.user.login }} | |
| COMMENT_ID: ${{ github.event.comment.id }} | |
| ISSUE_NUMBER: ${{ github.event.issue.number }} | |
| run: | | |
| python3 << 'PYEOF' | |
| import os, re, json, urllib.request, unicodedata | |
| labels = os.environ.get("ISSUE_LABELS", "") | |
| issue_title = os.environ.get("ISSUE_TITLE", "").lower() | |
| comment = os.environ.get("COMMENT_BODY", "") | |
| user = os.environ.get("COMMENT_USER", "") | |
| comment_id = os.environ.get("COMMENT_ID", "") | |
| issue = os.environ.get("ISSUE_NUMBER", "") | |
| # Skip content/social bounties | |
| content_keywords = ["x/twitter", "x post", "content", "tweet", "social media", "video", "article", "blog"] | |
| if any(kw in issue_title for kw in content_keywords): | |
| print(f"Content bounty #{issue} — skipping") | |
| exit(0) | |
| # Skip star-reward and content-bounty labeled issues | |
| label_names = [l.strip() for l in labels.split(",")] | |
| if any(lbl in ("star-reward", "content-bounty") for lbl in label_names): | |
| exit(0) | |
| repo = os.environ.get("GITHUB_REPOSITORY", "SolFoundry/solfoundry") | |
| token = os.environ.get("GH_TOKEN", "") | |
| if user in ["github-actions[bot]", "chronoeth-creator", "coderabbitai[bot]"]: | |
| exit(0) | |
| if "bounty" not in labels: | |
| print("Not a bounty issue - skipping") | |
| exit(0) | |
| def gh_post(path, data): | |
| url = f"https://api.github.com/{path}" | |
| req = urllib.request.Request(url, json.dumps(data).encode(), method="POST") | |
| req.add_header("Authorization", f"token {token}") | |
| req.add_header("Accept", "application/vnd.github.v3+json") | |
| req.add_header("Content-Type", "application/json") | |
| try: | |
| return urllib.request.urlopen(req) | |
| except Exception as e: | |
| print(f"POST failed: {e}") | |
| return None | |
| def gh_delete(path): | |
| url = f"https://api.github.com/{path}" | |
| req = urllib.request.Request(url, method="DELETE") | |
| req.add_header("Authorization", f"token {token}") | |
| req.add_header("Accept", "application/vnd.github.v3+json") | |
| try: | |
| return urllib.request.urlopen(req) | |
| except Exception as e: | |
| print(f"DELETE failed: {e}") | |
| return None | |
| def gh_get(path): | |
| url = f"https://api.github.com/{path}" | |
| req = urllib.request.Request(url) | |
| req.add_header("Authorization", f"token {token}") | |
| req.add_header("Accept", "application/vnd.github.v3+json") | |
| try: | |
| resp = urllib.request.urlopen(req) | |
| return json.loads(resp.read().decode()), resp.status | |
| except urllib.error.HTTPError as e: | |
| return None, e.code | |
| # Tiny comment filter | |
| if len(comment.strip()) < 5: | |
| print(f"Tiny comment from {user} on #{issue} — deleting") | |
| gh_delete(f"repos/{repo}/issues/comments/{comment_id}") | |
| exit(0) | |
| # Normalize unicode | |
| normalized = unicodedata.normalize('NFKC', comment).lower() | |
| confusables = str.maketrans('аеорсухіјёАЕОРСУХІ', 'aeopcyxijeAEOPCYXI') | |
| lower = normalized.translate(confusables) | |
| stripped = re.sub(r'[*_`~>\[\]()]', '', lower) | |
| # Repeat offender check | |
| comments_data = [] | |
| cpage = 1 | |
| while True: | |
| cpage_data, _ = gh_get(f"repos/{repo}/issues/{issue}/comments?per_page=100&page={cpage}") | |
| if not cpage_data: | |
| break | |
| comments_data.extend(cpage_data) | |
| if len(cpage_data) < 100: | |
| break | |
| cpage += 1 | |
| if comments_data: | |
| already_warned = False | |
| for c in comments_data: | |
| body_c = c.get("body", "") | |
| author = c["user"]["login"] | |
| if author in ["chronoeth-creator", "github-actions[bot]"] and f"@{user}" in body_c: | |
| if any(kw in body_c.lower() for kw in [ | |
| "open race", "fcfs", "no claiming needed", | |
| "reputation-gated", "posts have been removed", | |
| "auto-deleted", "just ship it" | |
| ]): | |
| already_warned = True | |
| break | |
| if already_warned: | |
| print(f"REPEAT OFFENDER: {user} already warned on #{issue} — auto-deleting") | |
| gh_delete(f"repos/{repo}/issues/comments/{comment_id}") | |
| exit(0) | |
| # Spam detection | |
| spam_patterns = [ | |
| r"(i am a .* developer|hire me|my portfolio|check my profile)", | |
| r"(dm me|contact me at|whatsapp|telegram.*@)", | |
| r"(upvote|follow me|subscribe|check out my)", | |
| r"(great (project|repo|work).*star|nice repo.*followed)", | |
| r"https?://\S+\.(click|xyz|top|buzz|gq|ml|tk|ga|cf)\b", | |
| ] | |
| if any(re.search(p, stripped) for p in spam_patterns): | |
| print(f"SPAM detected from {user} on #{issue} — deleting") | |
| gh_delete(f"repos/{repo}/issues/comments/{comment_id}") | |
| exit(0) | |
| # Claim detection on T1/T2 | |
| claim_re = r"(/attempt|claim(ing)?(\s+this)?|dibs|assigning myself|assign\s*(me|this)|i.?ll take this|i want to (take|work on) this|want to work on this|working on this|taking this|want to implement|i.?d like to (work on|take|build|implement) this|interested in (working on |taking )?this|please assign|let me work on this|i.?ll work on this|i.?ll build this|i.?ll implement this|can i (work on|take|do|have|claim) this|is this (still )?(available|open|unclaimed)|i.?m interested\b|i am interested\b|can (this|you) (be )?assign(ed)? (to )?me|i would like to (contribute|work|help)|requesting (this )?assignment|pick(ing)? (this )?up|i.?ll pick this up|i want to work on|let me take this)" | |
| is_claim = bool(re.search(claim_re, stripped)) | |
| if not is_claim: | |
| print("Not a claim attempt — ignoring") | |
| exit(0) | |
| is_t1 = "tier-1" in labels | |
| is_t2 = "tier-2" in labels | |
| if is_t1: | |
| print(f"T1 claim from {user} on #{issue} — deleting + FCFS rules") | |
| gh_delete(f"repos/{repo}/issues/comments/{comment_id}") | |
| body = ( | |
| f"Hey @{user} — This is a Tier 1/2 open-race bounty. " | |
| f"No claiming needed — just submit a PR with your solution. " | |
| f"First quality submission that passes AI review wins. Good luck!\n\n" | |
| f"---\n*-- SolFoundry Bot*" | |
| ) | |
| gh_post(f"repos/{repo}/issues/{issue}/comments", {"body": body}) | |
| elif is_t2: | |
| print(f"T2 claim from {user} on #{issue} — deleting + FCFS rules") | |
| gh_delete(f"repos/{repo}/issues/comments/{comment_id}") | |
| body = ( | |
| f"Hey @{user} — This is a Tier 1/2 open-race bounty. " | |
| f"No claiming needed — just submit a PR with your solution. " | |
| f"First quality submission that passes AI review wins. Good luck!\n\n" | |
| f"---\n*-- SolFoundry Bot*" | |
| ) | |
| gh_post(f"repos/{repo}/issues/{issue}/comments", {"body": body}) | |
| PYEOF |