#!/usr/bin/env python3 """ GitHub Activity Downloader for Julien Serbanescu RAG System Downloads issues and pull requests authored by the user across all repos, saving them as markdown documents for indexing. Usage: python download_github_activity.py --username Julien-ser python download_github_activity.py --username Julien-ser --token YOUR_GITHUB_TOKEN """ import os import sys import re import time import argparse import requests from pathlib import Path # Emoji in progress output cannot encode to the default Windows console # codepage (cp1252). Never let a decorative character fail the pipeline. # No-op on Linux. if hasattr(sys.stdout, "reconfigure"): sys.stdout.reconfigure(encoding="utf-8", errors="replace") sys.stderr.reconfigure(encoding="utf-8", errors="replace") def clean_filename(text): cleaned = re.sub(r'[^\w\-_.]', '_', text) cleaned = re.sub(r'_+', '_', cleaned) return cleaned.strip('_')[:120] def fetch_paginated(url, headers, params=None, max_pages=10): """Fetch all pages from a GitHub API endpoint.""" results = [] params = params or {} params.setdefault("per_page", 100) for page in range(1, max_pages + 1): params["page"] = page try: resp = requests.get(url, headers=headers, params=params, timeout=15) if resp.status_code == 401: print(f" Token rejected (401). Retrying without authentication...") headers.pop("Authorization", None) resp = requests.get(url, headers=headers, params=params, timeout=15) if resp.status_code == 403: print(f" Rate limit hit, waiting 60s...") time.sleep(60) resp = requests.get(url, headers=headers, params=params, timeout=15) resp.raise_for_status() data = resp.json() items = data.get("items", data) if isinstance(data, dict) else data if not items: break results.extend(items) if len(items) < params["per_page"]: break time.sleep(0.5) except Exception as e: print(f" Error fetching {url} page {page}: {e}") break return results def download_issues_and_prs(username, token=None, output_dir="docs/github_activity"): """Download issues and PRs authored by the user.""" output_path = Path(output_dir) output_path.mkdir(parents=True, exist_ok=True) headers = {"Accept": "application/vnd.github.v3+json"} if token: headers["Authorization"] = f"token {token}" saved = 0 for item_type, qualifier in [("PR", "is:pr"), ("Issue", "is:issue")]: print(f"\nFetching {item_type}s authored by {username}...") query = f"author:{username} {qualifier} is:public" items = fetch_paginated( "https://api.github.com/search/issues", headers, params={"q": query, "sort": "updated", "order": "desc"}, ) print(f" Found {len(items)} {item_type}(s)") for item in items: repo_name = item.get("repository_url", "").split("/")[-2:] repo_full = "/".join(repo_name) if len(repo_name) == 2 else "unknown" number = item.get("number", 0) title = item.get("title", "Untitled") body = item.get("body") or "" state = item.get("state", "unknown") labels = [l["name"] for l in item.get("labels", [])] created = item.get("created_at", "") updated = item.get("updated_at", "") html_url = item.get("html_url", "") content = f"""# {item_type}: {title} **Repository:** {repo_full} **Number:** #{number} **State:** {state} **Labels:** {', '.join(labels) if labels else 'None'} **Created:** {created} **Updated:** {updated} **URL:** {html_url} --- {body} """ filename = clean_filename(f"{repo_full.replace('/', '_')}_{item_type}_{number}_{title}") filepath = output_path / f"{filename}.md" filepath.write_text(content, encoding="utf-8") saved += 1 time.sleep(1) print(f"\nSaved {saved} issues/PRs to {output_dir}/") return saved def main(): parser = argparse.ArgumentParser(description="Download GitHub issues & PRs for RAG indexing") parser.add_argument("--username", required=True, help="GitHub username") parser.add_argument("--token", help="GitHub personal access token (recommended)") parser.add_argument("--output-dir", default="docs/github_activity", help="Output directory") args = parser.parse_args() print(f"=== Downloading GitHub Activity for {args.username} ===") download_issues_and_prs(args.username, args.token, args.output_dir) if __name__ == "__main__": main()