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

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()