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:
330
lib/reg_http.py
Normal file
330
lib/reg_http.py
Normal 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()
|
||||
Reference in New Issue
Block a user