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

114 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""Scrape OpenAI/ChatGPT verification codes from 2925 Webmail via Chrome CDP.
Key 2925 quirks captured from live runs:
- Do NOT navigate to #/mailList?type=收件箱 and trust the result; click the left nav item `收件箱`.
- Real mail rows are `table.maillist-table tr`.
- The generated alias local-part may be hidden in sender tooltip/bounce text such as
`...-chickliu2031som6d8=2925.com@tm1.openai.com`, not visible row text.
- Open the newest matching row, then extract the strict 6-digit code near code words.
Inputs:
--cdp http://127.0.0.1:9227
--account chickliu2031@2925.com
--alias chickliu2031som6d8@2925.com
--out /tmp/openai_2925_email_code.txt
The script prints JSON with code redacted and writes the raw code only to --out.
"""
import argparse, json, pathlib, re, sys, time, urllib.request, websocket, itertools
def tabs(cdp):
return json.loads(urllib.request.urlopen(cdp.rstrip('/') + '/json', timeout=10).read().decode())
def connect(cdp):
pages = [t for t in tabs(cdp) if t.get('type') == 'page']
sel = next((t for t in pages if '2925.com' in (t.get('url') or '')), None) or pages[0]
ws = websocket.create_connection(sel['webSocketDebuggerUrl'], timeout=20)
counter = itertools.count(1)
def call(method, params=None, timeout=45):
i = next(counter)
ws.send(json.dumps({'id': i, 'method': method, 'params': params or {}}))
end = time.time() + timeout
while time.time() < end:
msg = json.loads(ws.recv())
if msg.get('id') == i:
if 'error' in msg:
raise RuntimeError(msg['error'])
return msg.get('result', {})
raise TimeoutError(method)
call('Page.enable')
call('Runtime.enable')
return call
def ev(call, js, awaitp=False, timeout=45):
return call('Runtime.evaluate', {
'expression': js,
'awaitPromise': awaitp,
'returnByValue': True,
}, timeout).get('result', {}).get('value')
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--cdp', default='http://127.0.0.1:9227')
ap.add_argument('--account', required=True)
ap.add_argument('--alias', required=True)
ap.add_argument('--out', required=True)
args = ap.parse_args()
local = args.alias.split('@', 1)[0].lower()
call = connect(args.cdp)
js = r'''(async(local,account)=>{
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const click=e=>{if(!e)return false; e.scrollIntoView({block:'center'}); e.dispatchEvent(new MouseEvent('mousedown',{bubbles:true})); e.dispatchEvent(new MouseEvent('mouseup',{bubbles:true})); e.click(); return true;};
const all=[...document.querySelectorAll('*')];
const inbox=all.find(e=>(e.innerText||e.textContent||'').trim()==='收件箱');
const clickedInbox=click(inbox); await sleep(1800);
const refresh=[...document.querySelectorAll('*')].filter(e=>(e.innerText||e.textContent||'').trim()==='刷新').pop();
const clickedRefresh=click(refresh); await sleep(3000);
const pageText=document.body?.innerText||'';
const accountOk=pageText.includes(account);
const rows=[...document.querySelectorAll('table.maillist-table tr')].filter(r=>/OpenAI|ChatGPT|验证码|临时验证码|verification|code/i.test(r.innerText||''));
const rowSummaries=[];
let chosen=null, chosenIndex=-1;
for(let i=0;i<rows.length;i++){
const r=rows[i];
const hidden=[...r.querySelectorAll('*')].map(e=>[e.innerText,e.textContent,e.title,e.getAttribute('aria-label')].filter(Boolean).join(' ')).join(' ');
const combined=(r.innerText+' '+hidden).toLowerCase();
rowSummaries.push({i,text:(r.innerText||'').replace(/\s+/g,' ').trim().slice(0,180),hiddenHasLocal:combined.includes(local)});
if(!chosen && combined.includes(local)){ chosen=r; chosenIndex=i; }
}
if(!chosen && rows[0]){ chosen=rows[0]; chosenIndex=0; }
if(chosen){ click(chosen); await sleep(3500); }
let body=document.body?.innerText||'';
for(const f of [...document.querySelectorAll('iframe')]){ try{ body+='\n'+(f.contentDocument?.body?.innerText||''); }catch(e){} }
const candidates=[];
for(const m of body.matchAll(/(?<!\d)(\d{6})(?!\d)/g)){
const ctx=body.slice(Math.max(0,m.index-120), Math.min(body.length,m.index+126));
const score=(/验证码|verification|code|临时验证码/i.test(ctx)?10:0)+(/OpenAI|ChatGPT/i.test(ctx)?3:0)+(body.toLowerCase().includes(local)?2:0);
candidates.push({code:m[1],score,ctx:ctx.replace(m[1],'[CODE]')});
}
candidates.sort((a,b)=>b.score-a.score);
return {href:location.href,clickedInbox,clickedRefresh,accountOk,rowCount:rows.length,chosenIndex,rowSummaries:rowSummaries.slice(0,8),hasLocal:body.toLowerCase().includes(local),candidates};
})(LOCAL,ACCOUNT)'''.replace('LOCAL', json.dumps(local)).replace('ACCOUNT', json.dumps(args.account))
val = ev(call, js, True, 90) or {}
candidates = val.get('candidates') or []
code = candidates[0]['code'] if candidates else ''
if code:
pathlib.Path(args.out).write_text(code)
redacted = dict(val)
redacted['candidates'] = [{k: v for k, v in c.items() if k != 'code'} for c in candidates[:5]]
redacted['codeSaved'] = bool(code)
print(json.dumps(redacted, ensure_ascii=False))
return 0 if code else 2
if __name__ == '__main__':
sys.exit(main())