Files
codex-oauth-plus-onboarding…/skill/codex-oauth-plus-onboarding/scripts/phone_verify_eu_total10_latest.py
T

232 lines
15 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import json, pathlib, re, subprocess, sys, time, urllib.request, urllib.parse
ENV = pathlib.Path('/Users/chick/.hermes/env/codex_oauth_onboarding.env').read_text()
def get(k, d=''):
m = re.search(r'^' + re.escape(k) + r'=(.*)$', ENV, re.M)
return (m.group(1).strip().strip('"\'') if m else d)
TOKEN = get('FIVE_SIM_API_KEY') or get('FIVESIM_API_KEY')
HEAD = {'Authorization': 'Bearer ' + TOKEN, 'Accept': 'application/json'}
ORDER = [x.strip() for x in get('FIVE_SIM_COUNTRY_ORDER', '').split(',') if x.strip()] or ['indonesia']
LIMIT = int(get('PHONE_VERIFICATION_REPLACEMENT_LIMIT', '10') or '10')
PRODUCT = get('FIVE_SIM_PRODUCT', 'openai') or 'openai'
OPERATOR = get('FIVE_SIM_OPERATOR', 'any') or 'any'
MAX_TOTAL_ATTEMPTS = int(get('PHONE_VERIFICATION_MAX_TOTAL_ATTEMPTS', '10') or '10')
META = {
'argentina': {'iso': 'AR', 'visible': '阿根廷 (+54)', 'prefix': '54'},
'netherlands': {'iso': 'NL', 'visible': '荷兰 (+31)', 'prefix': '31'},
'indonesia': {'iso': 'ID', 'visible': '印度尼西亚 (+62)', 'prefix': '62'},
'england': {'iso': 'GB', 'visible': '英国 (+44)', 'prefix': '44'},
'italy': {'iso': 'IT', 'visible': '意大利 (+39)', 'prefix': '39'},
'poland': {'iso': 'PL', 'visible': '波兰 (+48)', 'prefix': '48'},
'spain': {'iso': 'ES', 'visible': '西班牙 (+34)', 'prefix': '34'},
'vietnam': {'iso': 'VN', 'visible': '越南 (+84)', 'prefix': '84'},
}
def emit(**kw):
print(json.dumps(kw, ensure_ascii=False), flush=True)
def req_json(url):
req = urllib.request.Request(url, headers=HEAD)
with urllib.request.urlopen(req, timeout=45) as r:
return json.loads(r.read().decode())
def cancel(aid):
try:
return req_json(f'https://5sim.net/v1/user/cancel/{aid}')
except Exception as e:
return {'error': str(e)}
def finish(aid):
try:
return req_json(f'https://5sim.net/v1/user/finish/{aid}')
except Exception as e:
return {'error': str(e)}
def check(aid):
return req_json(f'https://5sim.net/v1/user/check/{aid}')
def cdp(js, match='auth.openai.com', timeout=45):
p = subprocess.run(['bash', '-lc', f"CDP_MATCH='{match}' python3 /tmp/cdp_async_eval.py"], input=js, text=True, capture_output=True, timeout=timeout)
return (p.stdout or '') + (p.stderr or '')
def current_state():
return cdp("""
return {url: location.href, title: document.title,
country: [...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\(\+\d+\)/.test(t)),
selectValue: document.querySelector('select')?.value,
tel: document.querySelector('input[type=tel]')?.value,
text: document.body.innerText.slice(0, 900)};
""", timeout=25)
def restore_add_phone():
st = current_state()
if 'auth.openai.com/add-phone' in st:
return True
raw = get('CODEX2API_URL')
u = urllib.parse.urlsplit(raw)
origin = f'{u.scheme}://{u.netloc}' if u.scheme and u.netloc else raw.rstrip('/')
req = urllib.request.Request(origin + '/api/admin/oauth/generate-auth-url', data=b'{}', headers={'X-Admin-Key': get('CODEX2API_ADMIN_KEY'), 'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=20) as r:
data = json.loads(r.read().decode())
url = data.get('auth_url') or data.get('url')
emit(phase='oauth_recover', session_id=data.get('session_id') or data.get('id'))
urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:9223/json/new?' + urllib.parse.quote(url, safe=''), method='PUT'), timeout=10).read()
time.sleep(6)
email = json.loads(pathlib.Path('/tmp/current_codex_run.json').read_text())['email']
pw = get('CUSTOM_PASSWORD')
js = f"""
const email={json.dumps(email)}, pw={json.dumps(pw)};
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
let emailI=document.querySelector('input[type=email], input[name=email], input[autocomplete=username], input');
if(emailI && location.href.includes('/log-in')){{emailI.focus(); setter.call(emailI,email); emailI.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:email}})); emailI.dispatchEvent(new Event('change',{{bubbles:true}})); [...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();}}
await new Promise(r=>setTimeout(r,6000));
let pwI=document.querySelector('input[type=password]');
if(pwI){{pwI.focus(); setter.call(pwI,pw); pwI.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:pw}})); pwI.dispatchEvent(new Event('change',{{bubbles:true}})); [...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();}}
await new Promise(r=>setTimeout(r,12000));
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,600)}};
"""
out = cdp(js, timeout=40)
emit(phase='recover_state', state=out[-800:])
return 'auth.openai.com/add-phone' in current_state()
def set_country_gate(country):
meta = META[country]
js = f"""
const iso={json.dumps(meta['iso'])}, visible={json.dumps(meta['visible'])};
const sel=document.querySelector('select');
if(!sel) return {{ok:false, reason:'no_select', url:location.href, text:document.body.innerText.slice(0,300)}};
sel.focus();
sel.value=iso;
sel.dispatchEvent(new Event('input',{{bubbles:true}}));
sel.dispatchEvent(new Event('change',{{bubbles:true}}));
await new Promise(r=>setTimeout(r,900));
const tel=document.querySelector('input[type=tel]');
if(tel){{const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set; setter.call(tel,''); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'deleteContentBackward'}})); tel.dispatchEvent(new Event('change',{{bubbles:true}}));}}
const country=[...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t));
const selectValue=document.querySelector('select')?.value;
return {{ok:selectValue===iso && country===visible, country, selectValue, expected:visible, iso, url:location.href}};
"""
return cdp(js, timeout=25)
def submit_number(country, phone, aid):
meta = META[country]
digits = re.sub(r'\D+', '', str(phone))
local = digits[len(meta['prefix']):] if digits.startswith(meta['prefix']) else digits
js = f"""
const iso={json.dumps(meta['iso'])}, visible={json.dumps(meta['visible'])}, local={json.dumps(local)};
function visibleCountry(){{return [...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t));}}
let country=visibleCountry(), selectValue=document.querySelector('select')?.value;
if(selectValue!==iso || country!==visible) return {{clicked:false, reason:'pre_fill_country_gate_failed', country, selectValue, expected:visible, iso}};
const tel=document.querySelector('input[type=tel]');
if(!tel) return {{clicked:false, reason:'no_tel', country, selectValue}};
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
tel.focus(); setter.call(tel,''); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'deleteContentBackward'}}));
setter.call(tel,local); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:local}})); tel.dispatchEvent(new Event('change',{{bubbles:true}}));
await new Promise(r=>setTimeout(r,800));
country=visibleCountry(); selectValue=document.querySelector('select')?.value;
if(selectValue!==iso || country!==visible) return {{clicked:false, reason:'post_fill_country_gate_failed', country, selectValue, expected:visible, iso, tel:document.querySelector('input[type=tel]')?.value}};
[...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();
await new Promise(r=>setTimeout(r,10000));
return {{clicked:true,url:location.href,title:document.title,text:document.body.innerText.slice(0,1200),country:visibleCountry(),selectValue:document.querySelector('select')?.value,tel:document.querySelector('input[type=tel]')?.value}};
"""
return cdp(js, timeout=35)
emit(phase='phone_strict_start', countries=ORDER, limit=LIMIT, max_total_attempts=MAX_TOTAL_ATTEMPTS)
attempt_total = 0
for country in ORDER:
if attempt_total >= MAX_TOTAL_ATTEMPTS:
break
if country not in META:
emit(phase='skip_unknown_country', country=country)
continue
for local_attempt in range(1, LIMIT + 1):
if attempt_total >= MAX_TOTAL_ATTEMPTS:
break
attempt_total += 1
attempt = attempt_total
emit(phase='attempt_start', country=country, attempt=attempt, local_attempt=local_attempt, max_total=MAX_TOTAL_ATTEMPTS)
if not restore_add_phone():
emit(phase='cannot_restore_add_phone', country=country, attempt=attempt, local_attempt=local_attempt, state=current_state()[-800:])
sys.exit(20)
gate = set_country_gate(country)
emit(phase='country_gate_before_buy', country=country, attempt=attempt, local_attempt=local_attempt, state=gate[-700:])
if '"ok": true' not in gate:
continue
try:
act = req_json(f'https://5sim.net/v1/user/buy/activation/{country}/{OPERATOR}/{PRODUCT}')
except Exception as e:
emit(phase='buy_failed', country=country, attempt=attempt, local_attempt=local_attempt, error=str(e))
continue
aid = act.get('id'); phone = str(act.get('phone'))
pathlib.Path('/tmp/fivesim_activation.json').write_text(json.dumps(act, ensure_ascii=False, indent=2))
masked = re.sub(r'(\+?\d{4})\d+(\d{3})', r'\1***\2', phone)
emit(phase='buy', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, masked=masked, status=act.get('status'))
gate2 = current_state()
if META[country]['visible'] not in gate2 or ('"selectValue": "' + META[country]['iso'] + '"') not in gate2:
emit(phase='cancel_before_submit_country_mismatch', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=gate2[-600:], result=cancel(aid).get('status'))
continue
out = submit_number(country, phone, aid)
emit(phase='submit_result', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=out[-1000:])
if 'clicked": false' in out or 'country_gate_failed' in out:
emit(phase='cancel_submit_gate_failed', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
continue
if any(x in out for x in ['无法向此电话号码发送', '无法向该号码发送', '电话号码无效']):
emit(phase='direct_reject_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
continue
got = None
for r in range(1, 7):
time.sleep(10)
st = check(aid); sms = st.get('sms') or []
emit(phase='sms_poll', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, round=r, status=st.get('status'), sms_count=len(sms))
if sms:
got = sms[0].get('code') or sms[0].get('text')
break
if not got:
resend = cdp("""
const text=(document.title+' '+document.body.innerText).replace(/\s+/g,' ').trim();
if(/this page isn[']?t working|currently unable to handle this request|http error 500|500 internal server error/i.test(text)) return {clicked:false, reason:'resend_server_error', url:location.href, text:text.slice(0,600)};
const btn=[...document.querySelectorAll('button,input[type=submit],input[type=button]')].find(b=>{const t=[b.value,b.getAttribute?.('aria-label'),b.getAttribute?.('title'),b.innerText,b.textContent].filter(Boolean).join(' '); return /resend|重新发送|再次发送|whats\s*app/i.test(t);});
const channelText=btn?[btn.value,btn.getAttribute?.('aria-label'),btn.getAttribute?.('title'),btn.innerText,btn.textContent].filter(Boolean).join(' ').replace(/\s+/g,' ').trim():'';
const channel=/whats\s*app/i.test(channelText)?'whatsapp':(/sms|text message|短信/i.test(channelText)?'sms':'unknown');
if(channel==='whatsapp') return {clicked:false, channel, channelText, url:location.href};
btn?.click();
await new Promise(r=>setTimeout(r,2000));
const after=(document.title+' '+document.body.innerText).replace(/\s+/g,' ').trim();
return {clicked:!!btn,channel,channelText,url:location.href,text:after.slice(0,600),serverError:/this page isn[']?t working|currently unable to handle this request|http error 500|500 internal server error/i.test(after)};
""", match='auth.openai.com', timeout=20)
emit(phase='resend', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=resend[-700:])
if 'whatsapp' in resend.lower():
emit(phase='whatsapp_resend_detected_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
continue
if 'resend_server_error' in resend or 'serverError' in resend:
emit(phase='resend_server_error_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
continue
for r in range(1, 7):
time.sleep(10)
st = check(aid); sms = st.get('sms') or []
emit(phase='sms_poll_after_resend', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, round=r, status=st.get('status'), sms_count=len(sms))
if sms:
got = sms[0].get('code') or sms[0].get('text')
break
if not got:
emit(phase='no_sms_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
continue
emit(phase='sms_received', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid)
code = ''.join(re.findall(r'\d', str(got)))[:6]
fill = cdp(f"""
const code={json.dumps(code)};
const input=document.querySelector('input[autocomplete=one-time-code], input[name=code], input');
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
input.focus(); setter.call(input,code); input.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:code}})); input.dispatchEvent(new Event('change',{{bubbles:true}}));
[...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();
await new Promise(r=>setTimeout(r,12000));
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,1000)}};
""", match='auth.openai.com', timeout=35)
emit(phase='code_submit', state=fill[-1000:])
finish(aid)
sys.exit(0)
emit(phase='phone_failed_all', total_attempts=attempt_total, order=ORDER)
sys.exit(3)