""" harvest_existing.py — OAuth harvest from EXISTING GitHub accounts Uses Playwright + ZTE proxy + TOTP 2FA + recovery codes + checkup dismissal Targets: GoRouter ($70), Tabi AI ($120, broken), CodeBuddy (250cr, no GH button) Usage: python harvest_existing.py Reads: C:/Users/User/tmp/github_accounts.json (or C:/Users/User/Desktop/farm/gh-farm-og/github_accounts.json) Writes: tokens.json (GoRouter API tokens) Key features: - TOTP 2FA via pyotp - Recovery code fallback for device verification - "More options" → "tomorrow" checkup dismissal - ZTE 4G proxy rotation (12 ports) - 46% GoRouter success rate """ import asyncio, json, random, time, re, sys, os from pathlib import Path from urllib.parse import urlparse, parse_qs import pyotp from playwright.async_api import async_playwright ACCOUNTS_IN = Path("C:/Users/User/tmp/github_accounts.json") TOKENS_OUT = Path("C:/Users/User/Desktop/farm/gh-farm-og/tokens.json") LOG_FILE = Path("C:/Users/User/Desktop/farm/gh-farm-og/harvest_log.txt") PROXY_BASE = "http://192.168.0.4" PROXY_PORTS = [8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090, 8091] PLATFORMS = [ {"id": "tabiai", "name": "Tabi AI", "url": "https://tabitoken.com/sign-up?aff=sn7K", "reward": "$120"}, {"id": "gorouter", "name": "GoRouter", "url": "https://gorouter.app/sign-up?aff=ivjz", "reward": "$70"}, {"id": "codebuddy", "name": "CodeBuddy", "url": "https://www.codebuddy.ai/profile/plans-usage", "reward": "250cr"}, ] def log(msg: str): line = f"[{time.strftime('%H:%M:%S')}] {msg}" print(line) with open(LOG_FILE, "a", encoding="utf-8") as f: f.write(line + "\n") def get_proxy(idx: int) -> str: return f"{PROXY_BASE}:{PROXY_PORTS[idx % len(PROXY_PORTS)]}" async def login_github(page, account: dict) -> bool: """Login to GitHub with 2FA + recovery code + checkup dismissal""" email = account["email"] password = [REDACTED]"password"] seed = account.get("seed_2fa", "") await page.goto("https://github.com/login", wait_until="domcontentloaded", timeout=30000) await asyncio.sleep(2) content = await page.content() if len(content) < 2000: log(f" [-] Cloudflare block") return False await page.fill('input[name="login"]', email) await page.fill('input[name="password"]', password) await page.locator('input[type="submit"]').click() await asyncio.sleep(4) url = page.url content = await page.content() if "two-factor" in url.lower() or "2fa" in content.lower(): if not seed: return False totp = pyotp.TOTP(seed) code = totp.now() await page.fill('input[name="app_otp"]', code) await asyncio.sleep(1) for btn_text in ["Verify", "Submit", "Confirm"]: try: btn = page.locator(f"button:has-text('{btn_text}')").first if await btn.is_visible(timeout=1000): await btn.click() break except: continue await asyncio.sleep(5) url = page.url content = await page.content() # Device verification → recovery code if "device" in content.lower() and "verify" in content.lower(): recovery_codes = account.get("recovery_codes", "") if not recovery_codes: return False for alt_sel in ["a:has-text('recovery')", "a:has-text('another way')"]: try: el = page.locator(alt_sel).first if await el.is_visible(timeout=2000): await el.click() await asyncio.sleep(2) break except: continue codes = [c.strip() for c in recovery_codes.split(",") if c.strip()] if codes: await page.fill('input[type="text"]', codes[0]) await asyncio.sleep(1) for btn_text in ["Verify", "Submit", "Confirm"]: try: btn = page.locator(f"button:has-text('{btn_text}')").first if await btn.is_visible(timeout=1000): await btn.click() await asyncio.sleep(5) break except: continue url = page.url content = await page.content() if "dashboard" in url.lower() or ("github.com" in url and "login" not in url): if "sign out" in content.lower() or "signout" in content.lower(): return True if "password" in content.lower() and "incorrect" in content.lower(): return False if "account" in content.lower() and ("suspended" in content.lower() or "disabled" in content.lower() or "locked" in content.lower()): log(f" [-] Account suspended/locked") return False return False async def dismiss_checkup(page): """Dismiss GitHub 2FA checkup: More options → tomorrow""" if "two_factor_checkup" not in page.url: return for skip_sel in ["button:has-text('tomorrow')", "button:has-text('Verify your 2FA')", "button:has-text('More options')"]: try: el = page.locator(skip_sel).first if await el.is_visible(timeout=2000): await el.click() if "More options" in skip_sel: await asyncio.sleep(2) for sub in ["button:has-text('tomorrow')", "button:has-text('Skip')"]: try: sub_el = page.locator(sub).first if await sub_el.is_visible(timeout=2000): await sub_el.click() break except: continue log(f" [+] Checkup dismissed via {skip_sel[:40]}") await asyncio.sleep(3) return except: continue async def harvest_platform(page, platform: dict, account: dict) -> dict | None: """Go to platform, click Sign in with GitHub, authorize, extract token""" log(f" [{platform['name']}] OAuth...") try: await page.goto(platform["url"], wait_until="domcontentloaded", timeout=60000) await asyncio.sleep(3) clicked = False for sel in ["a[href*='github']", "button:has-text('GitHub')", "button:has-text('github')", "a:has-text('GitHub')", "a:has-text('Sign in')", "button:has-text('Sign in')"]: try: btn = page.locator(sel).first if await btn.is_visible(timeout=2000): await btn.click() clicked = True break except: continue if not clicked: links = await page.evaluate("() => [...document.querySelectorAll('a')].filter(a => a.href.includes('github.com/login/oauth')).map(a => a.href)") if links: await page.goto(links[0], wait_until="domcontentloaded") clicked = True if not clicked: return None await asyncio.sleep(4) # Authorize if "github.com" in page.url and ("authorize" in page.url.lower() or "oauth" in page.url.lower()): auth_clicked = False for sel in ["button[name='authorize']", "button:has-text('Authorize')", "button:has-text('authorize')", "#js-oauth-authorize-btn", "button[type='submit']", "button.btn-primary"]: try: el = page.locator(sel).first if await el.is_visible(timeout=2000): await el.click() auth_clicked = True await asyncio.sleep(5) break except: continue if not auth_clicked: result = await page.evaluate(""" () => { const btns = [...document.querySelectorAll('button')]; const auth = btns.find(b => b.textContent.includes('Authorize')); if (auth) { auth.click(); return true; } const submit = document.querySelector('button[type="submit"]'); if (submit) { submit.click(); return true; } return false; }""") if result: await asyncio.sleep(5) await asyncio.sleep(5) # Dismiss checkup if it appears after authorize await dismiss_checkup(page) # Extract token url = page.url token = None parsed = urlparse(url) for param in ["token", "access_token", "accessToken", "session", "key", "code"]: for qs in [parse_qs(parsed.query), parse_qs(parsed.fragment)]: if param in qs: token = [REDACTED]][0] break if token: break if not token: try: ls = await page.evaluate("() => JSON.parse(JSON.stringify(localStorage))") for k in ["token", "accessToken", "access_token", "authToken", "session"]: if k in ls: token = ls[k] break except: pass if token: log(f" [+] TOKEN: {token[:50]}...") return { "platform": platform["id"], "name": platform["name"], "reward": platform["reward"], "token": token, "github_email": account["email"], "github_username": account.get("username", ""), "timestamp": time.strftime("%Y-%m-%d %H:%M:%S"), } return None except Exception as e: log(f" [-] Error: {str(e)[:80]}") return None async def process_account(account: dict, idx: int) -> list[dict]: proxy = get_proxy(idx) results = [] log(f"\n{'='*50}") log(f"[{idx+1}] {account.get('username','?')} | {account['email']}") log(f" Proxy: {proxy}") try: async with async_playwright() as p: browser = await p.chromium.launch( headless=False, proxy={"server": proxy}, args=["--no-first-run", "--no-default-browser-check"], ) page = await browser.new_page() try: if not await login_github(page, account): await browser.close() return results await dismiss_checkup(page) for platform in PLATFORMS: result = await harvest_platform(page, platform, account) if result: results.append(result) finally: await browser.close() except Exception as e: log(f" [-] Browser error: {str(e)[:80]}") return results async def main(): if not ACCOUNTS_IN.exists(): log(f"FATAL: {ACCOUNTS_IN} not found") return accounts = json.loads(ACCOUNTS_IN.read_text(encoding="utf-8")) log(f"Loaded {len(accounts)} GitHub accounts") tokens = json.loads(TOKENS_OUT.read_text(encoding="utf-8")) if TOKENS_OUT.exists() else [] start_idx = int(sys.argv[1]) if len(sys.argv) > 1 else 0 count = int(sys.argv[2]) if len(sys.argv) > 2 else len(accounts) end_idx = min(start_idx + count, len(accounts)) log(f"Processing: {start_idx} → {end_idx-1} ({end_idx - start_idx} accounts)") for i in range(start_idx, end_idx): try: results = await process_account(accounts[i], i) if results: tokens.extend(results) TOKENS_OUT.write_text(json.dumps(tokens, ensure_ascii=False, indent=2), encoding="utf-8") log(f" [+] Saved {len(results)} tokens (total: {len(tokens)})") except Exception as e: log(f" [!] Fatal: {str(e)[:80]}") if i < end_idx - 1: delay = random.uniform(15, 30) log(f" [zZz] {delay:.0f}s delay...") await asyncio.sleep(delay) log(f"\n=== DONE: {len(tokens)} tokens total ===") if __name__ == "__main__": asyncio.run(main())