#!/usr/bin/env python3 """ Generate classic GitHub PATs using account credentials + puppeteer-like scraping. Reads creds from data/gh_accounts.txt (format: user:pass:email) Mints ghp_ tokens via GitHub settings/tokens page. """ import os, re, sys, time, random, sqlite3 import requests KF = os.path.dirname(os.path.abspath(__file__)) ACC_PATH = os.path.join(KF, "data", "gh_accounts.txt") TOK_PATH = os.path.join(KF, "gh_tokens.txt") # Token patterns RE_GH_CLASSIC = re.compile(r'ghp_[A-Za-z0-9]{36}') def load_accounts(): if not os.path.exists(ACC_PATH): print("[!] No gh_accounts.txt — need user:pass:email lines") # Try finding from pat_remint.log log_path = os.path.join(KF, "data", "pat_remint.log") if os.path.exists(log_path): with open(log_path) as f: text = f.read() users = re.findall(r'\[(\d+)/\d+\]\s+(\w+)', text) # Only get the usernames from the log print(f"Found {len(users)} usernames in pat_remint.log") print("Need passwords file to proceed. Create data/gh_accounts.txt") return [] accounts = [] with open(ACC_PATH) as f: for line in f: line = line.strip() if line and not line.startswith('#') and ':' in line: parts = line.split(':') if len(parts) >= 2: accounts.append({ 'user': parts[0], 'pass': parts[1], 'email': parts[2] if len(parts) > 2 else f"{parts[0]}@proton.me" }) return accounts def mint_token(session, user, password): """Mint a classic PAT via GitHub UI — direct API where possible.""" # Step 1: Login r = session.get("https://github.com/login", timeout=10) if r.status_code != 200: return None # Extract authenticity_token m = re.search(r'name="authenticity_token" value="([^"]+)"', r.text) if not m: return None token_csrf = m.group(1) # Step 2: POST login r = session.post("https://github.com/session", data={ "authenticity_token": token_csrf, "login": user, "password": password, "commit": "Sign in", "return_to": "" }, timeout=15) if "Incorrect username or password" in r.text or r.status_code != 200: return None # Step 3: Go to tokens page r = session.get("https://github.com/settings/tokens", timeout=10) m = re.search(r'name="authenticity_token" value="([^"]+)"', r.text) if not m: # Might need OTP return None auth_token = m.group(1) # Step 4: Create classic token r = session.post("https://github.com/settings/tokens", data={ "authenticity_token": auth_token, "personal_access_token[description]": f"dev-{random.randint(1000,9999)}", "personal_access_token[scopes][]": ["repo", "read:user", "user:email"], }, headers={"Accept": "text/html"}, timeout=15) # Step 5: Extract token from redirect/response m = RE_GH_CLASSIC.search(r.text) if m: return m.group(0) # Try from headers/cookies for hist in r.history: m = RE_GH_CLASSIC.search(hist.text) if hasattr(hist, 'text') else None if m: return m.group(0) m = RE_GH_CLASSIC.search(hist.headers.get('Location', '')) if m: return m.group(0) return None def main(): accounts = load_accounts() if not accounts: print("[!] No accounts. Create data/gh_accounts.txt with user:pass[:email] lines") return 1 print(f"Accounts: {len(accounts)}") # Load existing tokens existing = set() if os.path.exists(TOK_PATH): with open(TOK_PATH) as f: for l in f: l = l.strip() if l and not l.startswith('#'): existing.add(l) minted = 0 failed = 0 for i, acc in enumerate(accounts): if minted >= 500: print(f"\nTarget 500 reached!") break session = requests.Session() session.headers.update({ "User-Agent": f"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/130.0.0.0 Safari/537.36", "Accept": "text/html,application/xhtml+xml", }) try: token = mint_token(session, acc['user'], acc['pass']) if token: if token not in existing: existing.add(token) with open(TOK_PATH, "a") as f: f.write(token + "\n") minted += 1 print(f"[{i+1}/{len(accounts)}] ✓ {token[4:14]}... | user={acc['user']} | total={minted}") else: print(f"[{i+1}/{len(accounts)}] ~ duplicate | user={acc['user']}") else: failed += 1 if failed % 5 == 0: print(f"[{i+1}/{len(accounts)}] ✗ fail #{failed} | user={acc['user']}") except Exception as e: failed += 1 print(f"[{i+1}/{len(accounts)}] ! {e}") time.sleep(random.uniform(1.5, 3.5)) print(f"\nDone. Minted: {minted}, Failed: {failed}, Pool: {len(existing)}") if __name__ == "__main__": sys.exit(main())