114 lines
5.4 KiB
Python
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())
|