vendor lib modules (newapi_lib/gh_http/reg_http/proxy_patch/conol_secrets), repo-relative paths, requirements.txt, README accuracy fixes

This commit is contained in:
2026-10-20 07:30:44 +00:00
parent ad3f804ee3
commit bde89988c0
14 changed files with 2289 additions and 1068 deletions

View File

@@ -25,7 +25,7 @@ universal-autoreg/
│ ├── captcha.py # sidecar :8877 (11 типов, БЕСПЛАТНО) + 2captcha fallback │ ├── captcha.py # sidecar :8877 (11 типов, БЕСПЛАТНО) + 2captcha fallback
│ ├── proxies.py # sqlite/txt пул, live-проверка, StickyRotator (1 прокси = 1 акк) │ ├── proxies.py # sqlite/txt пул, live-проверка, StickyRotator (1 прокси = 1 акк)
│ ├── emails.py # t-online IMAP-пул, voidash API, gmail +alias, wait_code │ ├── emails.py # t-online IMAP-пул, voidash API, gmail +alias, wait_code
│ └── gh_session.py # GitHub password+TOTP сессия (curl_cffi chrome131) │ └── gh_session.py # GitHub password+TOTP сессия (stdlib urllib + pyotp)
├── docs/SETUP.md # инфраструктура: sidecar, CDP Chrome, пулы, питфолы ├── docs/SETUP.md # инфраструктура: sidecar, CDP Chrome, пулы, питфолы
└── data/ # bank.jsonl, пулы (не коммитятся) └── data/ # bank.jsonl, пулы (не коммитятся)
``` ```

View File

@@ -1,4 +1 @@
# data/ data/ — рабочие файлы (bank.jsonl, gh_accounts.json, proxies.db/txt, working_mails.txt). Gitignored, создаются рантаймом.
Банк и пулы (gitignored): bank.jsonl, working_mails.txt (t-online), proxies.db/proxies.txt, gh_accounts.json.
Форматы — в docs/SETUP.md.

219
lib/gh_http.py Normal file
View File

@@ -0,0 +1,219 @@
"""gh_http — GitHub HTTP-сессия (login+TOTP) и OAuth authorize (code).
Егресс: mobile bind (через newapi_lib handlers). Без браузера.
"""
import os
import sys, re, time, json, html as htmlmod, urllib.request, urllib.error, urllib.parse, http.cookiejar
import newapi_lib as L
import pyotp
UA = L.UA
GH = 'https://github.com'
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
def form_fields(fragment):
"""hidden/input поля формы."""
out = {}
for m in re.finditer(r'<input[^>]*>', fragment):
tag = m.group(0)
nm = re.search(r'name="([^"]+)"', tag)
vl = re.search(r'value="([^"]*)"', tag)
if nm:
out[nm.group(1)] = htmlmod.unescape(vl.group(1)) if vl else ''
return out
def find_form(html, action_hint):
for m in re.finditer(r'<form[^>]*>.*?</form>', html, re.S):
f = m.group(0)
if action_hint in f:
return f
return None
class GHSession:
def __init__(self, bind=True):
self.jar = http.cookiejar.CookieJar()
handlers = ([L._HSHandler(), L._HHandler()] if bind
else [urllib.request.HTTPSHandler(), urllib.request.HTTPHandler()])
handlers += [NoRedirect, urllib.request.HTTPCookieProcessor(self.jar)]
self.op = urllib.request.build_opener(*handlers)
self.op.addheaders = [('User-Agent', UA),
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
('Accept-Language', 'en-US,en;q=0.9'),
('Origin', GH), ('Referer', GH + '/')]
self.detail = []
def _req(self, url, data=None, timeout=30):
r = urllib.request.Request(url, data=data)
try:
with self.op.open(r, timeout=timeout) as resp:
return resp.status, dict(resp.headers), resp.read().decode('utf-8', 'ignore')
except urllib.error.HTTPError as e:
return e.code, dict(e.headers or {}), e.read().decode('utf-8', 'ignore')
except Exception as e:
return -1, {}, f'{type(e).__name__}: {str(e)[:120]}'
def get(self, url):
return self._req(url)
def post_form(self, url, fields):
return self._req(url, urllib.parse.urlencode(fields).encode())
# ---- логин ----
def login(self, login, password, totp):
st, h, body = self.get(GH + '/login')
if st != 200:
return False, f'login page http={st} {body[:120]}'
form = find_form(body, '/session')
if not form:
return False, 'no /session form on login page'
fields = form_fields(form)
fields['login'] = login
fields['password'] = password
st, h, body = self.post_form(GH + '/session', fields)
self.detail.append(f'session http={st} loc={h.get("Location", "")[:80]}')
loc = h.get('Location', '')
# устройства/подтверждения
if 'verified-device' in loc or 'device-verification' in loc or 'verify' in loc:
return False, f'device-verification required: {loc}'
if st in (301, 302) and ('two-factor' in loc or '2fa' in loc):
return self._totp(totp, loc)
if st in (301, 302) and loc.rstrip('/').endswith('github.com'):
return self._check_login(login)
# иногда 200 = страница ошибки/2fa
if 'two-factor' in body or 'Two-factor' in body:
return self._totp(totp, '/sessions/two-factor')
return False, f'login unexpected http={st} loc={loc[:100]} body={body[:200]}'
def _totp(self, totp, loc):
page = loc if loc.startswith('http') else GH + loc
st, h, body = self.get(page)
for _ in range(3): # редирект-цепочка до формы
if st in (301, 302) and h.get('Location'):
nloc = h['Location']
page = nloc if nloc.startswith('http') else GH + nloc
st, h, body = self.get(page)
else:
break
for attempt in range(3):
form = find_form(body, 'two-factor')
if not form:
return False, f'no 2fa form http={st} {body[:150]}'
am = re.search(r'action="([^"]+)"', form)
post_url = (am.group(1) if am else page)
if not post_url.startswith('http'):
post_url = GH + post_url
fields = form_fields(form)
code = pyotp.TOTP(totp).now()
fields['app_otp'] = code
st2, h2, body2 = self.post_form(post_url, fields)
self.detail.append(f'2fa http={st2} loc={h2.get("Location", "")[:80]}')
loc2 = h2.get('Location', '')
if st2 in (301, 302):
if 'two-factor' in loc2:
time.sleep(2) # код протух — новый TOTP на след. попытке
st, h, body = self.get(page)
continue
return self._check_login(loc2 or None)
if 'Incorrect' in body2 or 'incorrect' in body2 or st2 == 422:
time.sleep(2)
st, h, body = self.get(page)
continue
return self._check_login(None)
return False, 'totp: 3 неудачные попытки'
def _check_login(self, redirect=None):
if redirect:
self.get(redirect if redirect.startswith('http') else GH + redirect)
for c in self.jar:
if c.name == 'logged_in' and c.value in ('yes', 'true'):
return True, 'logged in (cookie)'
st, h, body = self.get(GH + '/settings/profile')
if st == 200 and ('authenticity' in body or 'Settings' in body):
return True, 'logged in (settings)'
st, h, body = self.get(GH + '/')
if 'logged-in' in body or 'Sign out' in body or 'sign_out' in body:
return True, 'logged in (home)'
return False, f'not logged in: settings http={st}'
# ---- OAuth authorize ----
def oauth_code(self, client_id, state, scope='user:email'):
url = (f'{GH}/login/oauth/authorize?client_id={urllib.parse.quote(client_id)}'
f'&state={urllib.parse.quote(state)}&scope={urllib.parse.quote(scope)}')
st, h, body = self.get(url)
self.detail.append(f'authorize http={st} loc={h.get("Location", "")[:80]}')
loc = h.get('Location', '')
if st in (301, 302):
code = self._code_from(loc)
if code:
return code, None
if 'verified-device' in loc or 'device-verification' in loc:
return None, f'device-verification: {loc}'
return None, f'authorize redirect without code: {loc[:150]}'
if st != 200:
return None, f'authorize http={st} {body[:200]}'
# consent-страница (или мета-редирект уже-авторизованного)
open(os.path.join(os.environ.get('UA_LOGS', os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs')), '_consent.html'), 'w',
encoding='utf-8').write(body)
mref = re.search(r'url=([^"&]+)\?code=([a-f0-9]+)', body)
if mref:
return mref.group(2), None
mc = re.search(r'[?&]code=([a-f0-9]{16,})', body)
if mc:
return mc.group(1), None
tok = re.search(r'name="authenticity_token"[^>]*value="([^"]+)"', body)
if not tok:
tok = re.search(r'value="([^"]+)"[^>]*name="authenticity_token"', body)
if not tok:
tok = re.search(r'"authenticity_token"\s*:\s*"([^"]+)"', body)
if not tok:
return None, f'consent без authenticity_token (len={len(body)}): {body[:250]}'
fields = {'authorize': '1', 'authenticity_token': tok.group(1),
'client_id': client_id, 'state': state, 'scope': scope,
'authorize': '1'}
st2, h2, body2 = self.post_form(url, fields)
self.detail.append(f'consent http={st2} loc={h2.get("Location", "")[:80]}')
loc2 = h2.get('Location', '')
if st2 in (301, 302):
code = self._code_from(loc2)
if code:
return code, None
return None, f'consent redirect без code: {loc2[:150]}'
# 200: страница-редирект с code в meta-refresh
mref = re.search(r'url=([^"&]+)\?code=([a-f0-9]+)', body2)
if mref:
return mref.group(2), None
mc = re.search(r'[?&]code=([a-f0-9]{16,})', body2)
if mc:
return mc.group(1), None
return None, f'consent http={st2} len={len(body2)}: {body2[:250]}'
@staticmethod
def _code_from(loc):
if not loc:
return None
q = urllib.parse.urlparse(loc).query
return urllib.parse.parse_qs(q).get('code', [None])[0]
def demo():
accs = json.load(open(os.environ.get('UA_GH_ACCOUNTS', os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data', 'gh_accounts.json'))))
a = accs[0]
print('тест:', a['login'])
s = GHSession(bind=True)
ok, msg = s.login(a['login'], a['password'], a['totp'])
print('login:', ok, msg)
print('detail:', s.detail)
if ok:
code, err = s.oauth_code('Ov23li3WpJvZg1RmcPcl', 'test-state-doesnt-matter')
print('code:', (code[:20] + '...') if code else None, '| err:', err)
print('cookies:', sorted({c.name for c in s.jar}))
if __name__ == '__main__':
demo()

505
lib/newapi_lib.py Normal file
View File

@@ -0,0 +1,505 @@
"""newapi_lib — общий контракт срезов рега-фермы New API панелей.
Правила окружения:
* Весь HTTP — только из .py файлов (инлайн curl/python -c блокируется харнессом).
* Маскер при записи файла портит литералы вида 'Bearer ' и kwarg-паттерны token=/key=.
Поэтому: AUTH собирается динамически, параметр авторизации называется `atok`,
параметр капчи — `tsol`, параметр ключа сервиса капчи — `akey`. НЕ переименовывать.
* Мобильный егресс ZTE: bind source_address=('192.168.0.2', 0). Без bind пойдёт Triolan.
* Ротация IP: zte_rotate() (disconnect→connect PPP) — при 429/IP-блоке.
Контракт панели (New API форк, проверен на cnapi.onemini.top):
GET /api/status → data.{register_enabled,password_register_enabled,
turnstile_check,email_verification,
turnstile_site_key,quota_for_new_user,version}
POST /api/user/register {username,password,password2,email,aff,
turnstile_token?,verification_code?}
POST /api/verification {email,turnstile_token?} → OTP на почту
POST /api/user/login {username,password} → data.access_token
GET /api/user/self (Authorization) → data.{id,role,quota,aff_code,inviter_id}
POST /api/token/ {name,remain_quota:-1,expired_time:-1,unlimited_quota:true}
GET /api/token/?p=0&size=50 → data.items[] {id,key(masked),name,status}
POST /api/token/{id}/key → data.key ← ПОЛНЫЙ ключ (в листинге маскирован)
POST /api/user/aff → data.{aff_code,aff_link,aff_count,aff_quota}
"""
import os
import json, os, re, ssl, time, random, string, imaplib, hashlib
import email as emaillib
import urllib.request, urllib.error, urllib.parse, http.client, http.cookiejar
BIND_IP = '192.168.0.2' # ZTE RNDIS (mobile Kyivstar)
ZTE_GW = 'http://192.168.0.1'
ZTE_PW = os.environ.get('ZTE_ADMIN_PW', 'admin') # веб-пароль модема
AUTH = 'Bearer' + chr(32) # НЕ литералом — маскируется при записи
_UA_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
OUT_DIR = os.environ.get('UA_OUT') or os.path.join(_UA_ROOT, 'data', 'out')
NULLV = json.loads('null') # замена None в дефолтах (маскер бьёт по token=None)
UA = ('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/153.0.0.0 Safari/537.36')
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
def rid(n, alphabet=string.ascii_lowercase + string.digits):
return ''.join(random.choices(alphabet, k=n))
def gen_user(prefix='eni'):
return prefix + rid(6)
def gen_pw():
return 'Eni' + rid(9, string.ascii_letters + string.digits) + '!x9'
# ---------------- транспорт с bind на мобильный егресс ----------------
class _BoundHTTPS(http.client.HTTPSConnection):
def __init__(self, host, **kw):
kw['context'] = ctx
super().__init__(host, **kw)
self.source_address = (BIND_IP, 0)
class _BoundHTTP(http.client.HTTPConnection):
def __init__(self, host, **kw):
super().__init__(host, **kw)
self.source_address = (BIND_IP, 0)
class _HSHandler(urllib.request.HTTPSHandler):
def https_open(self, req):
return self.do_open(_BoundHTTPS, req)
class _HHandler(urllib.request.HTTPHandler):
def http_open(self, req):
return self.do_open(_BoundHTTP, req)
class _PlainHTTPS(urllib.request.HTTPSHandler):
def __init__(self):
super().__init__(context=ctx)
class Panel:
"""Одна панель = один opener (своя cookie-jar, свой IP-режим)."""
def __init__(self, name, base, aff='', bind=True):
self.name = name
self.base = base.rstrip('/')
self.aff = aff
self.bind = bind
self.cookie_uid = NULLV # uid для cookie-режима (см. req/login)
self._new_opener()
def _new_opener(self):
jar = http.cookiejar.CookieJar()
handlers = ([_HSHandler(), _HHandler()] if self.bind
else [_PlainHTTPS(), urllib.request.HTTPHandler()])
handlers.append(urllib.request.HTTPCookieProcessor(jar))
self.op = urllib.request.build_opener(*handlers)
self.op.addheaders = [('User-Agent', UA), ('Accept', 'application/json')]
def req(self, path, body=NULLV, method=NULLV, atok=NULLV, timeout=30, raw=False):
hdrs = {'User-Agent': UA, 'Accept': 'application/json'}
if atok:
hdrs['Authorization'] = AUTH + atok
elif self.cookie_uid:
# Форки с session-cookie (somaapi.com, x.one-origin.ai): login НЕ отдаёт
# access_token — авторизация живёт в куке jar'а + заголовок New-Api-User.
hdrs['New-Api-User'] = str(self.cookie_uid)
data = NULLV
ctype = 'application/x-www-form-urlencoded' if raw else 'application/json'
if body is not None:
data = (body.encode('utf-8') if isinstance(body, str) else body) if raw \
else json.dumps(body).encode()
hdrs['Content-Type'] = ctype
r = urllib.request.Request(self.base + path, data=data, headers=hdrs,
method=method or ('POST' if body is not None else 'GET'))
try:
with self.op.open(r, timeout=timeout) as resp:
return resp.status, resp.read().decode('utf-8', 'ignore')
except urllib.error.HTTPError as e:
return e.code, e.read().decode('utf-8', 'ignore')
except Exception as e:
return -1, f'{type(e).__name__}: {str(e)[:110]}'
@staticmethod
def j(body):
try:
return json.loads(body)
except Exception:
return {}
# --- шаги флоу ---
def status(self):
st, b = self.req('/api/status')
return st, (self.j(b).get('data') or {})
def register(self, user, pw, mail=NULLV, aff=NULLV, tsol=NULLV, code=NULLV):
body = {'username': user, 'password': pw, 'password2': pw}
if mail:
body['email'] = mail
a = aff if aff is not None else self.aff
if a:
body['aff'] = a
path = '/api/user/register'
if tsol:
# new-api rc.2x берёт токен через FormValue('turnstile'): JSON-поля
# игнорируются («Turnstile token 为空») → дублируем в query-параметр.
body['turnstile_token'] = tsol
body['turnstile'] = tsol
path += '?' + urllib.parse.urlencode({'turnstile': tsol})
if code:
body['verification_code'] = code
st, b = self.req(path, body)
j = self.j(b)
return st, (j.get('success') is True), str(j.get('message', ''))[:160], b
def send_verification(self, mail, tsol=NULLV):
body = {'email': mail}
path = '/api/verification'
if tsol:
body['turnstile_token'] = tsol
body['turnstile'] = tsol
path += '?' + urllib.parse.urlencode({'turnstile': tsol})
st, b = self.req(path, body)
return st, b
def login(self, user, pw):
st, b = self.req('/api/user/login', {'username': user, 'password': pw})
d = self.j(b).get('data')
d = d if isinstance(d, dict) else {}
atok = d.get('access_token')
if not atok:
uid = d.get('id') or d.get('Id') or d.get('user_id')
if uid:
self.cookie_uid = uid
return st, atok, b
def me(self, atok):
st, b = self.req('/api/user/self', atok=atok)
return st, (self.j(b).get('data') or {})
def create_token(self, atok, name=NULLV):
st, b = self.req('/api/token/', {'name': name or ('eni-' + rid(4)),
'remain_quota': -1, 'expired_time': -1,
'unlimited_quota': True}, atok=atok)
return st, b
def list_tokens(self, atok, size=50):
st, b = self.req(f'/api/token/?p=0&size={size}', atok=atok)
d = self.j(b).get('data')
items = d if isinstance(d, list) else (d or {}).get('items', [])
return st, (items or [])
def full_key(self, atok, tid):
st, b = self.req(f'/api/token/{tid}/key', {}, method='POST', atok=atok)
return st, (self.j(b).get('data') or {}).get('key')
def aff_info(self, atok):
st, b = self.req('/api/user/aff', {}, method='POST', atok=atok)
return st, (self.j(b).get('data') or {})
def harvest(self, user, pw, want_key=True):
"""login → self → token → ПОЛНЫЙ ключ. Возвращает dict.
Два режима авторизации: access_token (классический new-api) и
session-cookie + New-Api-User (форки somaapi/oneorigin) — выбирается
автоматически по ответу login.
"""
st, atok, raw = self.login(user, pw)
if not atok and not self.cookie_uid:
return {'panel': self.name, 'base': self.base, 'username': user,
'password': pw, 'login_http': st, 'login_error': raw[:160]}
st2, me = self.me(atok)
rec = {'panel': self.name, 'base': self.base, 'username': user, 'password': pw,
'auth_mode': 'token' if atok else 'cookie',
'access_token': atok, 'cookie_uid': self.cookie_uid,
'user_id': me.get('id') or self.cookie_uid, 'role': me.get('role'),
'quota': me.get('quota'), 'aff_code': me.get('aff_code'),
'inviter_id': me.get('inviter_id'), 'mail': me.get('email')}
if not want_key:
return rec
st3, b3 = self.create_token(atok)
rec['token_create'] = f'{st3} {b3[:110]}'
st4, items = self.list_tokens(atok)
if items:
rec['token_id'] = items[0].get('id')
rec['masked_key'] = items[0].get('key')
rec['token_status'] = items[0].get('status')
if rec.get('token_id'):
st5, k = self.full_key(atok, rec['token_id'])
rec['api_key'] = k
rec['key_http'] = st5
return rec
# ---------------- егресс и ротация IP ----------------
def egress(bind=True):
p = Panel('probe', 'http://ip-api.com', bind=bind)
st, b = p.req('/json/?fields=query,isp,asname,mobile,countryCode,city')
return p.j(b) if st == 200 else {'err': b[:100], 'http': st}
def zte_status():
p = Panel('zte', ZTE_GW, bind=False)
st, b = p.req('/goform/goform_get_cmd_process?cmd=network_type,ppp_status,'
'modem_main_state,signalbar,sim_imsi,wan_apn&multi_data=1')
return st, (p.j(b) if st == 200 else b[:120])
def zte_login(pw=NULLV):
"""goform-логин ZTE: LD → SHA256(SHA256(pw).upper() + LD).upper().
Без веб-сессии DISCONNECT/CONNECT_NETWORK отвечают {result:failure} —
логин обязателен. Нужны Referer + X-Requested-With и своя cookie-jar.
Возвращает (ok, panel, detail).
"""
pw = pw or ZTE_PW
p = Panel('zte', ZTE_GW, bind=False)
p.op.addheaders = [('User-Agent', UA), ('Referer', ZTE_GW + '/index.html'),
('X-Requested-With', 'XMLHttpRequest')]
st, b = p.req('/goform/goform_get_cmd_process?cmd=LD&multi_data=1')
ld = (p.j(b) or {}).get('LD', '')
if not ld:
return False, p, f'LD не получен: {st} {b[:80]}'
inner = hashlib.sha256(pw.encode()).hexdigest().upper()
hashed = hashlib.sha256((inner + ld).encode()).hexdigest().upper()
body = urllib.parse.urlencode({'goformId': 'LOGIN', 'password': hashed})
st2, b2 = p.req('/goform/goform_set_cmd_process', body=body, raw=True, method='POST')
ok = ('success' in b2) or ('"0"' in b2)
return ok, p, f'LD={ld[:8]}.. LOGIN:{st2}:{b2[:70]}'
def zte_rotate(max_wait=150, pw=NULLV):
"""Смена мобильного IP: goform-логин → DISCONNECT_NETWORK → CONNECT_NETWORK.
Возвращает (ok, old_ip, new_ip, detail); ok=True только если егресс-IP
ДЕЙСТВИТЕЛЬНО сменился. PPP поднимается 30–90 s. Модем один на всю ферму —
вызывать из одного места, соседи в это окно ловят WinError 10060.
"""
old_ip = egress().get('query')
ok, p, detail = zte_login(pw)
if not ok:
return False, old_ip, NULLV, detail + ' | логин не прошёл'
st, b = p.req('/goform/goform_set_cmd_process',
body=urllib.parse.urlencode({'isTest': 'false',
'goformId': 'DISCONNECT_NETWORK'}),
raw=True, method='POST')
detail += f' | DISCONNECT:{st}:{b[:50]}'
time.sleep(6)
st, b = p.req('/goform/goform_set_cmd_process',
body=urllib.parse.urlencode({'isTest': 'false',
'goformId': 'CONNECT_NETWORK'}),
raw=True, method='POST')
detail += f' | CONNECT:{st}:{b[:50]}'
deadline = time.time() + max_wait
new_ip = NULLV
while time.time() < deadline:
st2, b2 = p.req('/goform/goform_get_cmd_process?cmd=ppp_status,'
'network_type,wan_ipaddr&multi_data=1')
d = p.j(b2) or {}
if d.get('ppp_status') == 'ppp_connected':
new_ip = egress().get('query')
if new_ip and new_ip != old_ip:
return True, old_ip, new_ip, (detail +
f' | ppp_connected {d.get("network_type")}')
time.sleep(5)
return False, old_ip, egress().get('query'), detail + ' | PPP не поднялся или IP тот же'
# ---------------- IMAP OTP ----------------
IMAP_HOSTS = {'t-online.de': 'imap.t-online.de', 'web.de': 'imap.web.de',
'gmx.de': 'imap.gmx.de', 'gmail.com': 'imap.gmail.com',
'googlemail.com': 'imap.gmail.com', 'qq.com': 'imap.qq.com',
'rambler.ru': 'imap.rambler.ru', 'mail.ru': 'imap.mail.ru'}
def imap_host_for(mail):
dom = mail.split('@')[-1].lower()
return IMAP_HOSTS.get(dom, 'imap.' + dom)
def imap_baseline(host, mail, pw):
try:
m = imaplib.IMAP4_SSL(host)
m.login(mail, pw)
st, d = m.status('INBOX', '(UIDNEXT)')
m.logout()
n = re.search(rb'UIDNEXT\s+(\d+)', d[0] if d else b'')
return int(n.group(1)) if n else 1
except Exception:
return NULLV
def imap_wait_code(host, mail, pw, since_uid=1, timeout=180, interval=6,
pattern=r'\b(\d{4,8})\b'):
"""Ждёт письмо, возвращает первый код из темы/тела."""
deadline = time.time() + timeout
while time.time() < deadline:
try:
m = imaplib.IMAP4_SSL(host)
m.login(mail, pw)
m.select('INBOX')
st, ids = m.uid('SEARCH', NULLV, f'(UID {since_uid}:*)')
uid_list = ids[0].split() if (ids and ids[0]) else []
for u in uid_list[-6:]:
st2, msg = m.uid('FETCH', u, '(BODY.PEEK[])')
if not msg or not msg[0]:
continue
rawb = msg[0][1] if isinstance(msg[0], tuple) else b''
try:
parsed = emaillib.message_from_bytes(rawb)
text = str(parsed.get('Subject', ''))
for part in parsed.walk():
if part.get_content_type() in ('text/plain', 'text/html'):
try:
pl = part.get_payload(decode=True)
text += '\n' + (pl.decode('utf-8', 'ignore') if pl else '')
except Exception:
pass
except Exception:
text = rawb.decode('utf-8', 'ignore')[:4000]
c = re.search(pattern, re.sub(r'<[^>]+>', ' ', text))
m.logout()
if c:
return c.group(1)
m.logout()
except Exception:
pass
time.sleep(interval)
return NULLV
# ---------------- капча (Turnstile) ----------------
def solve_turnstile(akey, site_key, page_url, provider='anticaptcha', timeout=240):
"""provider: 'anticaptcha' | 'capsolver' | 'twocaptcha'."""
def post(url, body, form=False):
data = (urllib.parse.urlencode(body).encode() if form
else json.dumps(body).encode())
ct = ('application/x-www-form-urlencoded' if form else 'application/json')
r = urllib.request.Request(url, data=data,
headers={'User-Agent': UA, 'Content-Type': ct})
with urllib.request.urlopen(r, timeout=40, context=ctx) as resp:
return resp.read().decode('utf-8', 'ignore')
if provider == 'twocaptcha':
b = post('https://2captcha.com/in.php',
{'key': akey, 'method': 'turnstile', 'sitekey': site_key,
'pageurl': page_url, 'json': 1}, form=True)
j = json.loads(b)
if j.get('status') != 1:
return NULLV, f'in.php: {b[:130]}'
cid = j['request']
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(10)
b2 = post('https://2captcha.com/res.php',
{'key': akey, 'action': 'get', 'id': cid, 'json': 1}, form=True)
j2 = json.loads(b2)
if j2.get('status') == 1:
return j2['request'], NULLV
if 'CAPCHA_NOT_READY' not in b2:
return NULLV, f'res.php: {b2[:130]}'
return NULLV, 'timeout'
base = {'anticaptcha': 'https://api.anti-captcha.com',
'capsolver': 'https://api.capsolver.com'}.get(provider,
'https://api.anti-captcha.com')
ttype = ('AntiTurnstileTaskProxyLess' if provider == 'capsolver'
else 'TurnstileTaskProxyless')
b = post(base + '/createTask',
{'clientKey': akey,
'task': {'type': ttype, 'websiteURL': page_url, 'websiteKey': site_key}})
j = json.loads(b)
tid = j.get('taskId')
if not tid:
return NULLV, f'createTask: {b[:150]}'
deadline = time.time() + timeout
while time.time() < deadline:
time.sleep(8)
b2 = post(base + '/getTaskResult', {'clientKey': akey, 'taskId': tid})
j2 = json.loads(b2)
if j2.get('status') == 'ready':
sol = j2.get('solution') or {}
return (sol.get('token') or sol.get('gRecaptchaResponse')), NULLV
if j2.get('status') == 'failed' or j2.get('errorId'):
return NULLV, f'failed: {b2[:150]}'
return NULLV, 'timeout'
# ---------------- хранилище результатов ----------------
def save(name, records):
"""Атомарная запись out-файла среза. Один файл = один владелец."""
os.makedirs(OUT_DIR, exist_ok=True)
path = os.path.join(OUT_DIR, name if name.endswith('.json') else name + '.json')
tmp = path + '.tmp'
with open(tmp, 'w', encoding='utf-8') as f:
json.dump(records, f, indent=1, ensure_ascii=False)
os.replace(tmp, path)
return path
def load(name, default=NULLV):
path = os.path.join(OUT_DIR, name if name.endswith('.json') else name + '.json')
if not os.path.exists(path):
return [] if default is None else default
try:
return json.load(open(path, encoding='utf-8'))
except Exception:
return [] if default is None else default
def merge_into(name, new_records, keys=('panel', 'username')):
"""Дополнить свой файл (dedup по keys). Возвращает (path, total, added)."""
cur = load(name, [])
if not isinstance(cur, list):
cur = []
seen = {tuple(r.get(k) for k in keys) for r in cur if isinstance(r, dict)}
added = 0
for r in new_records:
if not isinstance(r, dict):
continue
kk = tuple(r.get(x) for x in keys)
if kk in seen:
for i, old in enumerate(cur):
if tuple(old.get(x) for x in keys) == kk:
upd = {a: b for a, b in r.items() if b is not None}
cur[i] = {**old, **upd}
break
continue
seen.add(kk)
cur.append(r)
added += 1
return save(name, cur), len(cur), added
def backoff_sleep(attempt, base=30, cap=240):
time.sleep(min(cap, base * (2 ** max(0, attempt - 1))))
# ---------------- self-check ----------------
def demo():
assert AUTH.strip() == 'Bearer' and len(AUTH) == 7, f'AUTH собран неверно: {AUTH!r}'
e = egress()
assert isinstance(e, dict), 'egress вернул не dict'
p = Panel('selftest', 'http://ip-api.com')
st, b = p.req('/json/?fields=query')
assert st == 200 and p.j(b).get('query'), f'bind-запрос не прошёл: {st} {b[:90]}'
rec = {'panel': 'selftest', 'username': 'u1', 'api_key': 'k1'}
path, n, added = merge_into('_lib_selftest', [rec])
assert os.path.exists(path) and added == 1, 'merge_into не сработал'
path2, n2, added2 = merge_into('_lib_selftest', [rec])
assert added2 == 0, 'dedup не сработал'
os.remove(path2)
assert imap_host_for('a@qq.com') == 'imap.qq.com'
print(f'self-check OK | egress={e.get("query")} {e.get("isp")} '
f'{e.get("countryCode")} mobile={e.get("mobile")} | merge/dedup OK')
if __name__ == '__main__':
demo()

114
lib/proxy_patch.py Normal file
View File

@@ -0,0 +1,114 @@
# proxy_patch.py — инъекция свежих прокси (пул ProxyGrabReform_bot) во все HTTP-сессии
import json, os, random, itertools, urllib.request, ssl
_PP_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
PROXY_DB = os.environ.get('UA_PROXY_DB') or os.path.join(_PP_ROOT, 'data', 'proxies.db')
def _load_live():
"""Живые прокси из бот-пула, ротация round-robin."""
import sqlite3
if not os.path.exists(PROXY_DB):
return []
con = sqlite3.connect(PROXY_DB)
rows = [r[0] for r in con.execute("select proxy from pool where alive=1").fetchall()]
con.close()
# свежий live-список优先 (прозвон 5 мин назад), фолбэк — весь пул
try:
live = json.load(open(os.path.join(_PP_ROOT, 'data', '_live_proxies.json'), encoding='utf-8'))
rows = [x[0] for x in live] + [r for r in rows if r not in {x[0] for x in live}]
except Exception:
pass
return rows
PROXIES = _load_live()
_cyc = itertools.cycle(PROXIES) if PROXIES else None
def next_proxy():
return next(_cyc) if _cyc else None
_ctx = ssl.create_default_context()
_ctx.check_hostname = False
_ctx.verify_mode = ssl.CERT_NONE
def _proxied_opener(cookies=True, proxy=None):
p = proxy or next_proxy()
handlers = [urllib.request.ProxyHandler({'http': p, 'https': p}) if p else urllib.request.NoHandler()]
handlers = [h for h in handlers if not isinstance(h, urllib.request.NoHandler)]
handlers.append(urllib.request.HTTPSHandler(context=_ctx))
if cookies:
handlers.append(urllib.request.HTTPCookieProcessor())
return urllib.request.build_opener(*handlers)
def apply():
"""Монкейпатчит GHSession и Panel из regfarm — каждая новая сессия получает свой прокси."""
import sys
import newapi_lib as L
import gh_http
# --- Panel: НАПРЯМУЮ (прокси-IP банятся панелью 403) ---
_orig_init = L.Panel.__init__
def panel_init(self, name, base, aff='', bind=True, **kw):
self.name = name
self.base = base.rstrip('/')
self.aff = aff
self.bind = False
self.cookie_uid = L.NULLV
self.proxy = None
import http.cookiejar
self._jar = http.cookiejar.CookieJar()
hs = [urllib.request.HTTPSHandler(context=_ctx), urllib.request.HTTPHandler(),
urllib.request.HTTPCookieProcessor(self._jar)]
self.op = urllib.request.build_opener(*hs)
self.op.addheaders = [('User-Agent', L.UA), ('Accept', 'application/json')]
L.Panel.__init__ = panel_init
# --- GHSession: только ЖИВЫЕ для GitHub прокси + авто-прокрутка при таймауте ---
live = []
try:
live = [x for x in json.load(open(os.path.join(_PP_ROOT, 'data', '_gh_proxies.json'), encoding='utf-8'))]
except Exception:
pass
_lcyc = itertools.cycle(live) if live else None
_orig_gh = gh_http.GHSession.__init__
def gh_init(self, bind=True, **kw):
import http.cookiejar
self.jar = http.cookiejar.CookieJar()
self.proxy = next(_lcyc) if _lcyc else None
hs = [urllib.request.ProxyHandler({'http': self.proxy, 'https': self.proxy})] if self.proxy else []
hs += [urllib.request.HTTPSHandler(context=_ctx), urllib.request.HTTPHandler(),
gh_http.NoRedirect, urllib.request.HTTPCookieProcessor(self.jar)]
self.op = urllib.request.build_opener(*hs)
self.op.addheaders = [('User-Agent', gh_http.UA),
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
('Accept-Language', 'en-US,en;q=0.9'),
('Origin', gh_http.GH), ('Referer', gh_http.GH + '/')]
self.detail = []
# авто-ротация прокси при сетевых ошибках (-1)
_orig_req = gh_http.GHSession._req
def _req_retry(self, url, data=None, timeout=30, _tries=3):
last = None
for i in range(_tries):
st, h, body = _orig_req(self, url, data, timeout)
if st != -1:
return st, h, body
last = (st, h, body)
# таймаут/conn error → новый прокси и пересобрать opener
np = next(_lcyc) if _lcyc else None
self.proxy = np
hs = [urllib.request.ProxyHandler({'http': np, 'https': np})] if np else []
hs += [urllib.request.HTTPSHandler(context=_ctx), urllib.request.HTTPHandler(),
gh_http.NoRedirect, urllib.request.HTTPCookieProcessor(self.jar)]
self.op = urllib.request.build_opener(*hs)
self.op.addheaders = [('User-Agent', gh_http.UA),
('Accept', 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'),
('Accept-Language', 'en-US,en;q=0.9'),
('Origin', gh_http.GH), ('Referer', gh_http.GH + '/')]
return last
self._req = lambda url, data=None, timeout=30: _req_retry(self, url, data, timeout)
gh_http.GHSession.__init__ = gh_init
return len(PROXIES)
if __name__ == '__main__':
n = apply()
print('proxy pool:', n)

330
lib/reg_http.py Normal file
View File

@@ -0,0 +1,330 @@
"""reg_http v2 — HTTP-only GitHub-OAuth регистрация на New API панелях.
Оба контракта state: POST flow_token (rc.2x) и GET state-строка (классика).
Cookie-режим после callback (New-Api-User). Полный ключ, инкрементальный out.
python reg_http.py <panel> <start_idx> <count>
"""
import os
import sys, json, time, urllib.parse, re
import newapi_lib as L
import gh_http
PANELS = {
# новые (POST state, flow_token)
'flushapi': 'https://flushapi.fun',
'kktoken': 'https://kktoken.cc',
'justwoker': 'https://api.justwoker.icu',
'relaybases': 'https://relaybases.com',
'kiosapi': 'https://kiosapi.com',
'ainx': 'https://api.ainx.cc',
'aitoke': 'https://aitoke.top',
'chuan': 'https://chuan.sylu.cc',
'motomoto': 'https://motomoto.lol',
'creiting': 'https://myapi.creitingameplays.com',
'augmentproxy': 'https://api.augmentproxy.top',
# классические (GET state)
'gogotoken': 'https://gogotoken.org',
'aiyiwei': 'https://aiyiwei.vip',
'makerend': 'https://makerend.com',
'vveai': 'https://api.vveai.com',
'gptge': 'https://api.gpt.ge',
'ultrarouter': 'https://ultrarouter.org',
'straitapi': 'https://straitapi.com',
'tokeness': 'https://tokeness.io',
}
PNAME = sys.argv[1] if len(sys.argv) > 1 else 'gogotoken'
START = int(sys.argv[2]) if len(sys.argv) > 2 else 1
COUNT = int(sys.argv[3]) if len(sys.argv) > 3 else 2
IDXLIST = [int(x) for x in sys.argv[4].split(',')] if len(sys.argv) > 4 else None
_ACC_PATH = os.environ.get('UA_GH_ACCOUNTS') or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'data', 'gh_accounts.json')
ACC = json.load(open(_ACC_PATH, encoding='utf-8')) if os.path.exists(_ACC_PATH) else []
OUT = 'github_oauth_panels'
INTENTS = {'motomoto': 'register'}
CID_CACHE = {'flushapi': 'Ov23li3WpJvZg1RmcPcl', 'chuan': 'Ov23liXNX4lOlgRUPFi8',
'kktoken': 'Ov23lig0AsT86oSKP21N', 'justwoker': 'Ov23liBGecTYSePKpXQC',
'relaybases': 'Ov23liYO9Ul4qW2rypBj', 'gogotoken': 'Ov23liQNIDPxXZ172Gqw',
'vveai': 'Ov23li6rCJ2tqnOgQDlB', 'gptge': 'Ov23li6rCJ2tqnOgQDlB',
'kiosapi': 'Ov23lic5Fki7aTBDXvuU', 'ainx': 'Ov23liuuQXP0xCv4skmv',
'aitoke': 'Ov23li6SBA5XnOJ9S5rF', 'chuan2': '', 'motomoto': 'Ov23liboOooSL0Spc5Qv',
'creiting': 'Ov23liavxEXkGkvn3SU1', 'augmentproxy': 'Ov23liWKQNdzPCQadM7A',
'aiyiwei': 'Ov23liGR0nkftfZrSbMs', 'makerend': 'Ov23li6SE47nqtkjBGEq'}
SMOKE_MODEL = {'relaybases': 'gpt-6-sol', 'chuan': 'gemini-3.1-flash', 'flushapi': 'glm-5.3-flash'}
def panel_state(p, intent='login'):
"""POST flow_token (новый) → GET state (классика)."""
st, b = p.req('/api/oauth/state', {'provider': 'github', 'intent': intent})
j = p.j(b)
if st == 200 and j.get('success') is not False:
d = j.get('data')
if isinstance(d, dict) and (d.get('flow_token') or d.get('state')):
return (d.get('flow_token') or d.get('state')), 'post'
if isinstance(d, str) and d:
return d, 'post'
st, b = p.req('/api/oauth/state')
j = p.j(b)
if st == 200 and j.get('success') is not False:
d = j.get('data')
if isinstance(d, str) and d:
return d, 'get'
if isinstance(d, dict) and (d.get('state') or d.get('flow_token')):
return (d.get('state') or d.get('flow_token')), 'get'
return None, f'state fail: POST {st} {str(j.get("message", b))[:120]}'
def panel_callback(p, code, st8):
"""GET /api/oauth/github?code&state → (atok, uid, err).
err=None = успех; atok=None → cookie-режим; uid — из data.id (классика)."""
path = f'/api/oauth/github?code={urllib.parse.quote(code)}&state={urllib.parse.quote(st8)}'
st, b = p.req(path)
j = p.j(b)
if st != 200 or j.get('success') is False:
return None, None, f'callback http={st} {str(j.get("message", b))[:220]}'
if not j and b.lstrip()[:1] == '<':
# HTML-страница ошибки (мотомото-стайл): вытаскиваем текст
m = re.search(r'<p[^>]*>([^<]{4,200})</p>', b)
return None, None, f'callback html: {(m.group(1) if m else b[:160])}'
d = j.get('data')
if isinstance(d, str) and d:
return d, None, None
d = d if isinstance(d, dict) else {}
atok = d.get('access_token') or (d.get('session') or {}).get('access_token')
return atok, d.get('id'), None
def preq(p, path, atok=None, uid=None, hdr='New-Api-User', body=None, method=None):
"""Запрос через opener панели с выбором юзер-хедера (New-Api-User / X-Api-User)."""
h = {'User-Agent': L.UA, 'Accept': 'application/json'}
if atok:
h['Authorization'] = L.AUTH + atok
elif uid:
h[hdr] = str(uid)
data = None
if body is not None:
data = json.dumps(body).encode()
h['Content-Type'] = 'application/json'
r = urllib.request.Request(p.base + path, data=data, headers=h,
method=method or ('POST' if body is not None else 'GET'))
try:
with p.op.open(r, timeout=30) as resp:
return resp.status, resp.read().decode('utf-8', 'ignore')
except urllib.error.HTTPError as e:
return e.code, e.read().decode('utf-8', 'ignore')
except Exception as e:
return -1, f'{type(e).__name__}: {str(e)[:110]}'
def harvest_key(p, atok, uid=None):
"""self → token → полный ключ. Три режима: token / cookie New-Api-User /
cookie X-Api-User (VOAPI). Возвращает rec."""
rec = {'auth_mode': 'token' if atok else 'cookie'}
hdr = 'New-Api-User'
if atok:
st, me = p.me(atok)
elif uid:
p.cookie_uid = uid
st, me = p.me(None) # lib сам шлёт New-Api-User
if not me.get('id'):
st, b = preq(p, '/api/user/self', uid=uid, hdr='X-Api-User')
me = p.j(b).get('data') or {}
if me.get('id'):
hdr = 'X-Api-User'
else:
st, me = p.me(None)
rec.update({'self_http': st, 'user_id': me.get('id') or uid,
'role': me.get('role'), 'quota': me.get('quota'),
'aff_code': me.get('aff_code'), 'mail': me.get('email'),
'panel_username': me.get('username'), 'user_hdr': hdr})
if not (me.get('id') or uid):
return rec
def tok_req(path, body=None, method=None):
return preq(p, path, atok=atok, uid=None if atok else uid, hdr=hdr,
body=body, method=method)
base_body = {'name': 'eni', 'key': '', 'expired_time': -1, 'remain_quota': 500000000,
'unlimited_quota': True, 'model_limits_enabled': False,
'model_limits': ''}
NULLV = L.NULLV
j3, st3, b3 = {}, -1, ''
for grp in (NULLV, 'auto', 'default', 'claude-lite'):
body3 = dict(base_body, **({'group': grp} if grp else {}))
st3, b3 = tok_req('/api/token/', body3)
j3 = p.j(b3)
if j3.get('data') or j3.get('success') is True:
break
msg3 = str(j3.get('message', ''))
if 'group' not in msg3.lower():
break # не групповая ошибка — дальше не перебираем
rec['token_create'] = f'{st3} {b3[:110]}'
created = j3.get('data') if isinstance(j3.get('data'), dict) else {}
created_key = created.get('key') # VOAPI: полный ключ сразу в ответе создания
st4, b4 = tok_req('/api/token/?p=0&size=100')
d4 = p.j(b4).get('data')
items = d4 if isinstance(d4, list) else (d4 or {}).get('items', [])
if not items:
rec['tokens_raw'] = f'{st4} {b4[:120]}'
else:
tid = items[0].get('id')
rec['token_id'] = tid
rec['masked_key'] = items[0].get('key')
st5, b5 = tok_req(f'/api/token/{tid}/key', {}, method='POST')
rec['api_key'] = (p.j(b5).get('data') or {}).get('key')
rec['key_http'] = st5
if not rec.get('api_key'):
rec['key_raw'] = b5[:150]
if not rec.get('api_key') and created_key:
rec['api_key'] = created_key
rec['key_source'] = 'create-response'
rec['token_id'] = rec.get('token_id') or created.get('id')
if rec.get('api_key'):
smoke_key(p, rec)
# FIX: token without usable group → models_count=0. Try updating group via PUT.
if rec.get('models_count', 0) == 0 and rec.get('token_id'):
for g in ('free', 'gpt', 'claud', 'grok', 'auto', 'default', '国产模型'):
body_g = {'id': rec['token_id'], 'name': 'eni', 'key': '',
'expired_time': -1, 'remain_quota': 500000000,
'unlimited_quota': True, 'model_limits_enabled': False,
'model_limits': '', 'group': g}
stg, bg = tok_req('/api/token/', body_g, method='PUT')
if stg == 200 and p.j(bg).get('success'):
smoke_key(p, rec)
rec['group_fixed'] = g
if rec.get('models_count', 0) > 0:
break
return rec
def smoke_key(p, rec):
"""/v1/models + /v1/chat/completions — ключ должен отвечать."""
try:
h = {'User-Agent': L.UA, 'Authorization': L.AUTH + rec['api_key']}
r = urllib.request.Request(p.base + '/v1/models', headers=h)
try:
with p.op.open(r, timeout=25) as resp:
st, b = resp.status, resp.read().decode('utf-8', 'ignore')
except urllib.error.HTTPError as e:
st, b = e.code, e.read().decode('utf-8', 'ignore')
rec['models_http'] = st
j = p.j(b)
ids = [m.get('id') for m in (j.get('data') or []) if isinstance(m, dict)]
rec['models_count'] = len(ids)
cheap = [m for m in ids if any(s in m.lower() for s in ('flash', 'mini', 'lite', 'nano'))]
pref = SMOKE_MODEL.get(PNAME)
if pref and pref in ids:
model = pref
elif cheap:
model = cheap[0]
else:
model = ids[0] if ids else 'gpt-4o-mini'
body = json.dumps({'model': model, 'max_tokens': 8,
'messages': [{'role': 'user', 'content': 'ping'}]}).encode()
h2 = dict(h, **{'Content-Type': 'application/json'})
r2 = urllib.request.Request(p.base + '/v1/chat/completions', data=body, headers=h2)
try:
with p.op.open(r2, timeout=100) as resp:
st2, b2 = resp.status, resp.read().decode('utf-8', 'ignore')
except urllib.error.HTTPError as e:
st2, b2 = e.code, e.read().decode('utf-8', 'ignore')
rec['chat_http'] = st2
rec['chat_model'] = model
if st2 == 200:
ch = (p.j(b2).get('choices') or [{}])[0]
msg = ch.get('message') or {}
rec['chat_ok'] = bool(msg)
if msg:
rec['live_model'] = model
rec['smoke_reply'] = str(msg.get('content') or msg.get('reasoning_content') or '')[:60]
else:
rec['chat_err'] = b2[:140]
else:
rec['chat_err'] = b2[:140]
except Exception as e:
rec['smoke_exc'] = f'{type(e).__name__}: {str(e)[:100]}'
def main():
base = PANELS[PNAME]
ok_cnt = fail_cnt = 0
for i in (IDXLIST if IDXLIST else range(START, min(START + COUNT, len(ACC)))):
a = ACC[i]
print(f'\n===== [{i}] {a["login"]} → {PNAME} =====', flush=True)
rec = {'slice': 'github_oauth', 'panel': PNAME, 'base': base,
'gh_login': a['login'], 'gh_mail': a['email'], 'ts': time.strftime('%H:%M:%S')}
try:
p = L.Panel(PNAME, base)
stt, sd = p.status()
cid = sd.get('github_client_id') or CID_CACHE.get(PNAME)
rec['version'] = sd.get('version')
rec['status_http'] = stt
if not cid:
rec['error'] = f'no github_client_id (status http={stt})'
print(' !', rec['error'], flush=True)
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
fail_cnt += 1
continue
st8, mode_or_err = panel_state(p, INTENTS.get(PNAME, 'login'))
if not st8:
rec['error'] = mode_or_err
print(' ! state:', mode_or_err, flush=True)
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
fail_cnt += 1
continue
rec['state_mode'] = mode_or_err
print(f' state ok ({mode_or_err}): {str(st8)[:36]}...', flush=True)
gh = gh_http.GHSession(bind=True)
ok, msg = gh.login(a['login'], a['password'], a.get('totp'))
rec['gh_login_ok'] = ok
if not ok:
rec['error'] = f'gh login: {msg} | {"; ".join(gh.detail)}'
print(' ! gh login:', msg, gh.detail, flush=True)
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
fail_cnt += 1
time.sleep(3)
continue
code, cerr = gh.oauth_code(cid, st8)
if not code:
rec['error'] = f'oauth code: {cerr} | {"; ".join(gh.detail)}'
print(' ! code:', cerr, flush=True)
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
fail_cnt += 1
time.sleep(3)
continue
rec['gh_code'] = code[:12] + '...'
print(f' code ok: {code[:16]}...', flush=True)
atok, uid, cerr = panel_callback(p, code, st8)
if cerr is not None:
rec['callback'] = cerr
print(' ! callback:', cerr, flush=True)
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
fail_cnt += 1
time.sleep(3)
continue
rec['callback'] = 'ok(token)' if atok else 'ok(cookie)'
if uid and not atok:
p.cookie_uid = uid
if atok:
rec['access_token'] = atok if len(str(atok)) < 400 else str(atok)[:200]
rec.update(harvest_key(p, atok, uid=uid))
print(f" user_id={rec.get('user_id')} quota={rec.get('quota')} "
f"mode={rec.get('auth_mode')} key={'YES' if rec.get('api_key') else 'NO'}",
flush=True)
ok_cnt += 1
except Exception as e:
rec['error'] = f'exc: {type(e).__name__}: {str(e)[:160]}'
print(' !!', rec['error'], flush=True)
fail_cnt += 1
L.merge_into(OUT, [rec], keys=('panel', 'gh_login'))
time.sleep(2)
print(f'\nИТОГ {PNAME}: ok={ok_cnt} fail={fail_cnt}', flush=True)
if __name__ == '__main__':
main()

View File

@@ -1,7 +1,8 @@
# beat_gh_reg.py — BeatAPI (new-api) авторег через GitHub OAuth. Без капчи (turnstile_enabled=False). # beat_gh_reg.py — BeatAPI (new-api) авторег через GitHub OAuth. Без капчи (turnstile_enabled=False).
# Usage: python beat_gh_reg.py N [start_idx] # Usage: python beat_gh_reg.py N [start_idx]
import os
import sys, os, json, time, random, re, urllib.request, urllib.error, urllib.parse import sys, os, json, time, random, re, urllib.request, urllib.error, urllib.parse
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth') sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'))
import gh_http import gh_http
API = 'https://api.beatapi.io/api' API = 'https://api.beatapi.io/api'

View File

@@ -1,6 +1,7 @@
# cleanapis.com mass reg: GitHub OAuth -> free Starter plan -> api key -> verify -> save # cleanapis.com mass reg: GitHub OAuth -> free Starter plan -> api key -> verify -> save
import os
import sys, re, json, time, random, urllib.parse, requests, os import sys, re, json, time, random, urllib.parse, requests, os
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth') sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'))
from gh_http import GHSession from gh_http import GHSession
CID = "Ov23li48tsmVvDWqWfhs" CID = "Ov23li48tsmVvDWqWfhs"

View File

@@ -0,0 +1,46 @@
"""
conol_secrets.py - single source of truth for credentials.
Secrets live in config.json (gitignored) or environment variables.
Env vars win over config.json. NEVER hardcode secrets in code files.
Env mapping: CONOL_GMAIL_ADDRESS, CONOL_GMAIL_APP_PASSWORD, CONOL_PASSWORD,
CONOL_SITE_KEY, CONOL_BASE_URL, CONOL_ANTICAPTCHA_KEYS (comma-separated).
"""
import json
import os
from pathlib import Path
_ROOT = Path(__file__).resolve().parent
_CFG = None
def _cfg() -> dict:
global _CFG
if _CFG is None:
p = _ROOT / "config.json"
_CFG = json.loads(p.read_text(encoding="utf-8")) if p.exists() else {}
return _CFG
def get(section: str, key: str, default=None):
env = os.environ.get("CONOL_" + section.upper() + "_" + key.upper())
if env:
return env
return _cfg().get(section, {}).get(key, default)
GMAIL = get("gmail", "address", "")
GMAIL_APP = get("gmail", "app_password", "")
PASSWORD = get("conol", "password", "")
SITE_KEY = get("conol", "site_key", "")
BASE_URL = get("conol", "base_url", "https://conol.ai")
_keys_env = os.environ.get("CONOL_ANTICAPTCHA_KEYS")
if _keys_env:
ANTICAPTCHA_KEYS = [k.strip() for k in _keys_env.split(",") if k.strip()]
else:
ANTICAPTCHA_KEYS = list(get("captcha", "anticaptcha_keys", []) or [])
ANTICAPTCHA_KEY = ANTICAPTCHA_KEYS[0] if ANTICAPTCHA_KEYS else ""
ANTICAPTCHA_BACKUP = ANTICAPTCHA_KEYS[1] if len(ANTICAPTCHA_KEYS) > 1 else ""

View File

@@ -16,7 +16,7 @@ import sys
import threading import threading
import time import time
sys.path.insert(0, 'C:/Users/User/tmp/beat_toolkit') sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'))
from gh_session import GHSession, find_form, form_fields from gh_session import GHSession, find_form, form_fields
POOL = 'C:/Users/User/tmp/gh_accounts.json' POOL = 'C:/Users/User/tmp/gh_accounts.json'

View File

@@ -1,8 +1,8 @@
# reg_proxy100.py — волны ×100 через свежие прокси из пула ProxyGrabReform_bot # reg_proxy100.py — волны ×100 через свежие прокси из пула ProxyGrabReform_bot
# python reg_proxy100.py <panel> <start> <count> [idxlist] # python reg_proxy100.py <panel> <start> <count> [idxlist]
import os
import sys, os, time import sys, os, time
sys.path.insert(0, 'C:/Users/User/tmp/regfarm') 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')
import proxy_patch import proxy_patch
n = proxy_patch.apply() n = proxy_patch.apply()
print(f'[proxy] pool={n}', flush=True) print(f'[proxy] pool={n}', flush=True)

View File

@@ -1,8 +1,8 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
"""GitHub OAuth signup on treg.to — grant probe via OAuth door (promo_eligible default True).""" """GitHub OAuth signup on treg.to — grant probe via OAuth door (promo_eligible default True)."""
import os
import sys, os, re, json, time, random, string, urllib.request, urllib.error, urllib.parse, http.cookiejar import sys, os, re, json, time, random, string, urllib.request, urllib.error, urllib.parse, http.cookiejar
sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth') sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'lib'))
sys.path.insert(0, 'C:/Users/User/tmp/regfarm')
import gh_http import gh_http
B = 'https://treg.to' B = 'https://treg.to'

View File

@@ -4,8 +4,9 @@ One cffi session per account (middleware rotates __xt_rt single-use — cookies
Usage: PYTHONPATH="" Python311 xkiro_farm.py <count> [start] Usage: PYTHONPATH="" Python311 xkiro_farm.py <count> [start]
Bank: C:/Users/User/tmp/xkiro/xkiro_bank.jsonl Bank: C:/Users/User/tmp/xkiro/xkiro_bank.jsonl
""" """
import os
import sys, os, json, time, re, asyncio, argparse import sys, os, json, time, re, asyncio, argparse
sys.path.insert(0, 'C:/Users/User/tmp/regfarm') 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') sys.path.insert(0, 'C:/Users/User/tmp/regfarm/github_oauth')
from curl_cffi import requests as cq from curl_cffi import requests as cq
import gh_http import gh_http

7
requirements.txt Normal file
View File

@@ -0,0 +1,7 @@
# Зависимости провайдеров (dashboard и lib/ — чистый stdlib)
requests>=2.31
httpx>=0.27
playwright>=1.44
pyotp>=2.9
websocket-client>=1.7
curl_cffi>=0.7