autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
305
skills/github-oauth-automation/scripts/harvest_existing.py
Normal file
305
skills/github-oauth-automation/scripts/harvest_existing.py
Normal file
@@ -0,0 +1,305 @@
|
||||
"""
|
||||
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 <start_idx> <count>
|
||||
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())
|
||||
73
skills/github-oauth-automation/scripts/token_pool.py
Normal file
73
skills/github-oauth-automation/scripts/token_pool.py
Normal file
@@ -0,0 +1,73 @@
|
||||
"""
|
||||
token_pool.py — GoRouter token pool with rotation
|
||||
Usage:
|
||||
python token_pool.py # show pool status
|
||||
python token_pool.py next # get next token (round-robin)
|
||||
python token_pool.py random # get random token
|
||||
python token_pool.py refresh # reload from tokens.json
|
||||
"""
|
||||
import json, random, sys
|
||||
from pathlib import Path
|
||||
|
||||
TOKENS_FILE = Path(__file__).parent / "tokens.json"
|
||||
POOL_FILE = Path(__file__).parent / "pool_state.json"
|
||||
|
||||
class TokenPool:
|
||||
def __init__(self):
|
||||
self.tokens = []
|
||||
self.index = 0
|
||||
self._load()
|
||||
|
||||
def _load(self):
|
||||
if TOKENS_FILE.exists():
|
||||
raw = json.loads(TOKENS_FILE.read_text(encoding="utf-8"))
|
||||
self.tokens = [t for t in raw if t.get("platform") == "gorouter"]
|
||||
if POOL_FILE.exists():
|
||||
state = json.loads(POOL_FILE.read_text(encoding="utf-8"))
|
||||
self.index = state.get("index", 0)
|
||||
|
||||
def save_state(self):
|
||||
POOL_FILE.write_text(json.dumps({"index": self.index, "total": len(self.tokens)}))
|
||||
|
||||
def refresh(self):
|
||||
self._load()
|
||||
return len(self.tokens)
|
||||
|
||||
def next(self) -> dict | None:
|
||||
if not self.tokens:
|
||||
return None
|
||||
token = [REDACTED] % len(self.tokens)]
|
||||
self.index = (self.index + 1) % len(self.tokens)
|
||||
self.save_state()
|
||||
return token
|
||||
|
||||
def random_token(self) -> dict | None:
|
||||
if not self.tokens:
|
||||
return None
|
||||
return random.choice(self.tokens)
|
||||
|
||||
def status(self):
|
||||
return {
|
||||
"total": len(self.tokens),
|
||||
"index": self.index,
|
||||
"value": f"${len(self.tokens) * 70}",
|
||||
"tokens": [{"email": t["github_email"], "token_preview": t["token"][:8] + "..."} for t in self.tokens]
|
||||
}
|
||||
|
||||
if __name__ == "__main__":
|
||||
pool = TokenPool()
|
||||
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
|
||||
|
||||
if cmd == "next":
|
||||
t = pool.next()
|
||||
if t: print(t["token"])
|
||||
else: print("NO_TOKENS")
|
||||
elif cmd == "random":
|
||||
t = pool.random_token()
|
||||
if t: print(t["token"])
|
||||
else: print("NO_TOKENS")
|
||||
elif cmd == "refresh":
|
||||
n = pool.refresh()
|
||||
print(f"Pool refreshed: {n} tokens")
|
||||
else:
|
||||
print(json.dumps(pool.status(), indent=2, ensure_ascii=False))
|
||||
Reference in New Issue
Block a user