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

128 lines
7.5 KiB
Python

import json, pathlib, urllib.request, urllib.parse, re, subprocess, time, sys
ENV=pathlib.Path('/Users/chick/.hermes/env/codex_oauth_onboarding.env')
text=ENV.read_text()
def get(k,default=''):
m=re.search(r'^'+re.escape(k)+r'=(.*)$', text, re.M)
return (m.group(1).strip().strip('"\'') if m else default)
TOKEN=get('FIVE_SIM_API_KEY') or get('FIVESIM_API_KEY')
ORDER=[x.strip() for x in get('FIVE_SIM_COUNTRY_ORDER','').split(',') if x.strip()] or ([get('FIVE_SIM_COUNTRY_ID')] if get('FIVE_SIM_COUNTRY_ID') else ['vietnam'])
LIMIT=int(get('PHONE_VERIFICATION_REPLACEMENT_LIMIT','3') or '3')
PRODUCT=get('FIVE_SIM_PRODUCT','openai') or 'openai'
OP=get('FIVE_SIM_OPERATOR','any') or 'any'
META={'argentina':('阿根廷','54'),'netherlands':('荷兰','31'),'indonesia':('印度尼西亚','62'),'vietnam':('越南','84')}
HEAD={'Authorization':'Bearer '+TOKEN,'Accept':'application/json'}
def emit(**kw): print(json.dumps(kw,ensure_ascii=False),flush=True)
def req(method,url,data=None,timeout=30):
body=None
if data is not None: body=json.dumps(data).encode()
r=urllib.request.Request(url,data=body,headers=HEAD,method=method)
if body: r.add_header('Content-Type','application/json')
with urllib.request.urlopen(r,timeout=timeout) as resp: return json.loads(resp.read().decode())
def cdp(js,match='auth.openai.com',timeout=40):
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 page_state():
return cdp("""return {url:location.href,title:document.title,text:document.body.innerText.slice(0,1600),country:[...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t)),tel:document.querySelector('input[type=tel]')?.value};""",timeout=25)
def select_country(country):
label,prefix=META[country]
iso={"argentina":"AR","netherlands":"NL","indonesia":"ID","vietnam":"VN"}[country]
js=f"""
const iso={json.dumps(iso)};
const sel=document.querySelector('select');
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,800));
return {{selectValue:sel.value,country:[...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t)),text:document.body.innerText.slice(0,700)}};
"""
return cdp(js,timeout=25)
def buy(country):
url=f'https://5sim.net/v1/user/buy/activation/{urllib.parse.quote(country)}/{urllib.parse.quote(OP)}/{urllib.parse.quote(PRODUCT)}'
return req('GET',url,timeout=40)
def cancel(aid):
try: return req('GET',f'https://5sim.net/v1/user/cancel/{aid}',timeout=20)
except Exception as e: return {'error':str(e)}
def check(aid):
return req('GET',f'https://5sim.net/v1/user/check/{aid}',timeout=20)
def submit_phone(country,phone):
label,prefix=META[country]
digits=re.sub(r'\D+','',phone)
local=digits[len(prefix):] if digits.startswith(prefix) else digits
js=f"""
const local={json.dumps(local)};
const input=document.querySelector('input[type=tel]');
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
input.focus(); setter.call(input,''); input.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'deleteContentBackward'}}));
setter.call(input,local); input.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:local}})); input.dispatchEvent(new Event('change',{{bubbles:true}}));
await new Promise(r=>setTimeout(r,500));
let btn=[...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续'); btn?.click();
await new Promise(r=>setTimeout(r,12000));
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,1200),country:[...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t)),tel:document.querySelector('input[type=tel]')?.value}};
"""
return cdp(js,timeout=45)
def resend_phone():
js="""const btn=[...document.querySelectorAll('button')].find(b=>/重新发送/.test(b.innerText||'')); btn?.click(); await new Promise(r=>setTimeout(r,3000)); return {clicked:!!btn,text:document.body.innerText.slice(0,800),url:location.href};"""
return cdp(js,timeout=20)
def poll_sms(aid,rounds=8):
for i in range(rounds):
time.sleep(12)
st=check(aid)
sms=st.get('sms') or []
emit(phase='sms_poll',activation_id=aid,round=i+1,status=st.get('status'),sms_count=len(sms))
if sms:
txt=json.dumps(sms,ensure_ascii=False)
m=re.search(r'(?<!\d)(\d{4,8})(?!\d)',txt)
if m: return m.group(1),st
return None,None
emit(phase='phone_start',countries=ORDER,limit=LIMIT)
for country in ORDER:
if country not in META:
emit(phase='country_skip',country=country,reason='missing_meta'); continue
for attempt in range(1,LIMIT+1):
sel=select_country(country)
if META[country][0] not in sel or META[country][1] not in sel:
emit(phase='country_select_failed',country=country,attempt=attempt,out=sel[-500:])
continue
act=buy(country)
aid=act.get('id'); phone=act.get('phone')
pathlib.Path('/tmp/fivesim_activation.json').write_text(json.dumps(act,ensure_ascii=False,indent=2))
emit(phase='buy',country=country,attempt=attempt,activation_id=aid,masked=re.sub(r'(\+?\d{4})\d+(\d{3})',r'\1***\2',str(phone)),status=act.get('status'))
out=submit_phone(country,phone)
if '电话号码无效' in out or '美国 (+1)' in out and country!='usa':
emit(phase='submit_invalid_or_country_revert',country=country,activation_id=aid)
emit(phase='cancel',activation_id=aid,result=cancel(aid).get('status'))
continue
if '无法向此电话号码发送验证码' in out:
emit(phase='direct_reject',country=country,activation_id=aid)
emit(phase='cancel',activation_id=aid,result=cancel(aid).get('status'))
continue
emit(phase='accepted_or_waiting',country=country,activation_id=aid,state=out[-300:])
code,st=poll_sms(aid,rounds=5)
if not code:
emit(phase='resend_phone',activation_id=aid,out=resend_phone()[-300:])
code,st=poll_sms(aid,rounds=5)
if code:
emit(phase='sms_code_received',activation_id=aid,code='[REDACTED]')
js=f"""
const code={json.dumps(code)};
let inputs=[...document.querySelectorAll('input')].filter(i=>i.type==='text'||i.type==='tel'||i.inputMode==='numeric');
let input=inputs.find(i=>i!==document.querySelector('input[type=tel]'))||inputs[0];
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}}));
await new Promise(r=>setTimeout(r,500));
[...document.querySelectorAll('button')].find(b=>/继续|Continue|验证/.test(b.innerText||''))?.click();
await new Promise(r=>setTimeout(r,15000));
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,1600)}};
"""
final=cdp(js,timeout=45)
emit(phase='phone_code_submitted',activation_id=aid,state=final[-500:])
sys.exit(0)
emit(phase='no_sms_after_resend',country=country,activation_id=aid)
emit(phase='cancel',activation_id=aid,result=cancel(aid).get('status'))
emit(phase='phone_failed_all')
sys.exit(3)