260 lines
10 KiB
Python
260 lines
10 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""xkiro_farm.py — FULL E2E: GH OAuth -> login -> TG ticket -> bot verify (30M tokens) -> API key -> bank.
|
|
One cffi session per account (middleware rotates __xt_rt single-use — cookies MUST stay in-session).
|
|
Usage: PYTHONPATH="" Python311 xkiro_farm.py <count> [start]
|
|
Bank: C:/Users/User/tmp/xkiro/xkiro_bank.jsonl
|
|
"""
|
|
import os
|
|
import sys, os, json, time, re, asyncio, argparse
|
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'))
|
|
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
|
|
from curl_cffi import requests as cq
|
|
import gh_http
|
|
|
|
XK = 'https://xkiro.com'
|
|
API = 'https://api.xkiro.com'
|
|
CID = 'Ov23li0EyjNfkXUSKZel'
|
|
BANK = r'C:/Users/User/tmp/xkiro/xkiro_bank.jsonl'
|
|
GOOD_TG = r'C:/Users/User/tmp/xkiro/xkiro_good_tg.json' # proven REUSABLE sessions (verified 2+ accounts)
|
|
POOL = r'C:/Users/User/tmp/gh_accounts.json'
|
|
USED = r'C:/Users/User/tmp/xkiro/xkiro_used_tg.json'
|
|
AVAIL = r'C:/Users/User/tmp/mia_avail_sessions.json'
|
|
DIRS = [r'C:/Users/User/Desktop/CLEAN_ALIVE_SESSIONS', r'C:/Users/User/.orca/sessions']
|
|
IMP = 'chrome131'
|
|
H = {'Accept': 'application/json', 'Content-Type': 'application/json',
|
|
'Referer': XK + '/dashboard', 'Origin': XK}
|
|
|
|
# TG creds
|
|
import glob as _g
|
|
TG_ID, TG_HASH = None, None
|
|
for p in [r'C:/Users/User/tmp/mia_verify_slow.py', r'C:/Users/User/tmp/freemodel/freemodel_full.py']:
|
|
try:
|
|
t = open(p, encoding='utf-8').read()
|
|
m1 = re.search(r'API_ID\s*=\s*(\d+)', t)
|
|
m2 = re.search(r'API_HASH\s*=\s*[\'"]([a-f0-9]+)[\'"]', t)
|
|
if m1 and m2:
|
|
TG_ID, TG_HASH = int(m1.group(1)), m2.group(1)
|
|
break
|
|
except Exception:
|
|
pass
|
|
|
|
def log(*a):
|
|
print(*a, flush=True)
|
|
|
|
def good_sessions():
|
|
"""Proven reusable TG sessions (rotate, no consumption)."""
|
|
out = []
|
|
try:
|
|
for p in json.load(open(GOOD_TG)):
|
|
if os.path.exists(p):
|
|
out.append(p)
|
|
except Exception:
|
|
pass
|
|
return out
|
|
|
|
def mark_good(path):
|
|
try:
|
|
g = json.load(open(GOOD_TG))
|
|
except Exception:
|
|
g = []
|
|
if path not in g:
|
|
g.append(path)
|
|
json.dump(g, open(GOOD_TG, 'w'))
|
|
|
|
def tg_sessions(n=4):
|
|
names = json.load(open(AVAIL))
|
|
try:
|
|
used = set(json.load(open(USED)))
|
|
except Exception:
|
|
used = set()
|
|
# exclude sessions already consumed by miarouter verifier (avoid AuthKeyDuplicated)
|
|
try:
|
|
mstate = json.load(open(r'C:/Users/User/Desktop/farm/miarouter/verifier_state.json'))
|
|
used |= set(mstate.get('used_tg', []))
|
|
except Exception:
|
|
pass
|
|
tail = [b for b in names if b not in used]
|
|
tail = tail[-n:] # TAIL: miarouter verifier walks head-first
|
|
out = []
|
|
for b in tail:
|
|
for d in DIRS:
|
|
p = os.path.join(d, b)
|
|
if os.path.exists(p):
|
|
out.append(p)
|
|
break
|
|
return out
|
|
|
|
def mark_tg_used(basename):
|
|
try:
|
|
used = set(json.load(open(USED)))
|
|
except Exception:
|
|
used = set()
|
|
used.add(basename)
|
|
json.dump(sorted(used), open(USED, 'w'))
|
|
|
|
async def bot_start(sess_path, ticket):
|
|
from telethon import TelegramClient
|
|
name = os.path.basename(sess_path)
|
|
client = TelegramClient(sess_path.replace('.session', ''), TG_ID, TG_HASH)
|
|
try:
|
|
await asyncio.wait_for(client.connect(), 45)
|
|
if not await client.is_user_authorized():
|
|
return name, 'NOT_AUTH'
|
|
await client.send_message('xkiro_official_bot', '/start ' + ticket)
|
|
await asyncio.sleep(9)
|
|
msgs = await client.get_messages('xkiro_official_bot', limit=4)
|
|
txt = ' '.join((m.message or '')[:200] for m in msgs if not m.out).lower()
|
|
if 'verified successfully' in txt or "you've received" in txt:
|
|
return name, 'OK'
|
|
if 'join' in txt or 'channel' in txt:
|
|
from telethon.tl.functions.channels import JoinChannelRequest
|
|
try:
|
|
await client(JoinChannelRequest('xtrouter'))
|
|
await asyncio.sleep(3)
|
|
await client.send_message('xkiro_official_bot', '/start ' + ticket)
|
|
await asyncio.sleep(8)
|
|
msgs = await client.get_messages('xkiro_official_bot', limit=4)
|
|
txt = ' '.join((m.message or '')[:200] for m in msgs if not m.out).lower()
|
|
if 'verified' in txt:
|
|
return name, 'OK'
|
|
return name, 'REPLY:' + txt[:120]
|
|
except Exception as e:
|
|
return name, 'JOIN_FAIL:' + type(e).__name__
|
|
return name, 'REPLY:' + txt[:120]
|
|
finally:
|
|
try:
|
|
await client.disconnect()
|
|
except Exception:
|
|
pass
|
|
|
|
def farm_one(acc, tgsess):
|
|
lg = acc['login']
|
|
rec = {'gh_login': lg, 'ts': time.strftime('%H:%M:%S')}
|
|
s = cq.Session(impersonate=IMP)
|
|
# 1 state
|
|
try:
|
|
r = s.get(API + '/api/v1/auth/github', headers={**H, 'Accept': 'text/html', 'Sec-Fetch-Site': 'none'},
|
|
allow_redirects=False, timeout=30)
|
|
loc = r.headers.get('Location', '') or ''
|
|
m = re.search(r'state=([a-f0-9]{16,})', loc)
|
|
if not m:
|
|
m = re.search(r'state=([a-f0-9]{16,})', r.text or '')
|
|
if not m:
|
|
rec['stage'] = 'state'; rec['err'] = f'http={r.status_code}'
|
|
return rec
|
|
state = m.group(1)
|
|
except Exception as e:
|
|
rec['stage'] = 'state'; rec['err'] = str(e)[:100]; return rec
|
|
# 2 gh code
|
|
gh = gh_http.GHSession(bind=False)
|
|
ok, err = gh.login(lg, acc['password'], acc.get('totp'))
|
|
if not ok:
|
|
rec['stage'] = 'gh_login'; rec['err'] = err; return rec
|
|
code, cerr = gh.oauth_code(CID, state, scope='read:user user:email')
|
|
if not code:
|
|
rec['stage'] = 'gh_code'; rec['err'] = cerr; return rec
|
|
# 3 callback (same session!)
|
|
try:
|
|
r3 = s.get(API + f'/api/v1/auth/github/callback?code={code}&state={state}',
|
|
headers={**H, 'Accept': 'text/html', 'Sec-Fetch-Site': 'cross-site',
|
|
'Referer': 'https://github.com/'}, timeout=40)
|
|
cks = dict(s.cookies)
|
|
if not cks.get('__xt_at'):
|
|
rec['stage'] = 'callback'; rec['err'] = f'http={r3.status_code} no __xt_at'
|
|
return rec
|
|
rec['cookies'] = cks
|
|
except Exception as e:
|
|
rec['stage'] = 'callback'; rec['err'] = str(e)[:100]; return rec
|
|
# 4 ticket via xkiro.com proxy (cookies same names, set on xkiro.com)
|
|
s2 = cq.Session(impersonate=IMP)
|
|
for k, v in cks.items():
|
|
s2.cookies.set(k, v, domain='xkiro.com')
|
|
s2.cookies.set(k, v, domain='.xkiro.com')
|
|
if not tgsess:
|
|
rec['tg'] = 'NO_TG_SESSION'
|
|
else:
|
|
try:
|
|
rt = s2.post(XK + '/api/v1/me/telegram/ticket', headers=H, data='{}', timeout=25)
|
|
j = rt.json()
|
|
data = j.get('data') or {}
|
|
if data.get('alreadyVerified'):
|
|
rec['tg'] = 'already'
|
|
else:
|
|
dl = data.get('deepLink', '')
|
|
ticket = dl.split('start=')[-1] if 'start=' in dl else ''
|
|
if not ticket:
|
|
rec['stage'] = 'ticket'; rec['err'] = rt.text[:120]; return rec
|
|
# 5 bot verify
|
|
try:
|
|
sp, res = asyncio.run(bot_start(tgsess, ticket))
|
|
except Exception as be:
|
|
res = f'EXC:{type(be).__name__}:{str(be)[:60]}'
|
|
rec['tg'] = res; rec['tg_sess'] = os.path.basename(tgsess)
|
|
if res == 'OK':
|
|
mark_tg_used(os.path.basename(tgsess)) # consumed: 1 TG acct = 1 xkiro verification
|
|
else:
|
|
mark_tg_used(os.path.basename(tgsess)) # any failure: never reuse (dup/NOT_AUTH/burned)
|
|
except Exception as e:
|
|
rec['stage'] = 'ticket'; rec['err'] = str(e)[:100]
|
|
# refresh cookies after middleware rotation
|
|
fresh = dict(s2.cookies)
|
|
if fresh.get('__xt_at'):
|
|
rec['cookies'] = {**cks, **fresh}
|
|
# 6 key + wallet + status
|
|
try:
|
|
rk = s2.post(XK + '/api/v1/keys', headers=H, data=json.dumps({'name': 'farm'}), timeout=25)
|
|
jk = rk.json()
|
|
rec['key'] = (jk.get('data') or {}).get('plaintext')
|
|
rec['key_id'] = ((jk.get('data') or {}).get('key') or {}).get('id')
|
|
except Exception as e:
|
|
rec['key_err'] = str(e)[:80]
|
|
try:
|
|
rec['wallet'] = s2.get(XK + '/api/v1/wallet', headers=H, timeout=20).json().get('data')
|
|
rec['tg_status'] = s2.get(XK + '/api/v1/me/telegram/status', headers=H, timeout=20).json().get('data')
|
|
except Exception:
|
|
pass
|
|
rec['stage'] = 'done'
|
|
return rec
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser()
|
|
ap.add_argument('count', type=int, nargs='?', default=1)
|
|
ap.add_argument('start', type=int, nargs='?', default=0)
|
|
args = ap.parse_args()
|
|
pool = json.load(open(POOL))
|
|
done = set()
|
|
try:
|
|
for line in open(BANK, encoding='utf-8'):
|
|
r = json.loads(line)
|
|
if r.get('stage') == 'done' or r.get('key'):
|
|
done.add(r['gh_login'])
|
|
except Exception:
|
|
pass
|
|
cand = [a for a in pool if a['login'] not in done]
|
|
log(f'=== xkiro farm: {args.count}, candidates {len(cand)}, TG_ID set: {TG_ID is not None}')
|
|
tgs = tg_sessions(args.count + 2)
|
|
log('tg sessions:', len(tgs))
|
|
n = 0
|
|
for i, a in enumerate(cand[args.start:]):
|
|
if n >= args.count:
|
|
break
|
|
goods = good_sessions()
|
|
if goods:
|
|
tg = goods[n % len(goods)] # REUSE: proven sessions verify unlimited accounts
|
|
else:
|
|
tgs = tg_sessions(1) # fresh per account: excludes xkiro-used + miarouter used_tg
|
|
tg = tgs[0] if tgs else None
|
|
log(f'--- {a["login"]} (tg: {os.path.basename(tg) if tg else "NONE"})')
|
|
try:
|
|
rec = farm_one(a, tg)
|
|
except Exception as e:
|
|
rec = {'gh_login': a['login'], 'stage': 'exc', 'err': f'{type(e).__name__}:{e}'[:120]}
|
|
with open(BANK, 'a', encoding='utf-8') as f:
|
|
f.write(json.dumps(rec, ensure_ascii=False) + '\n')
|
|
log(' ', {k: (str(v)[:60] if k not in ('cookies',) else '<saved>') for k, v in rec.items() if k != 'gh_login'})
|
|
n += 1
|
|
time.sleep(25)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|