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

173 lines
7.1 KiB
Python

#!/usr/bin/env python3
"""Strict 2925/OpenAI email resend cadence runner.
Cadence encoded from BOSS correction:
for each resend slot:
refresh 2925 Inbox + Junk for N rounds, sleeping between rounds
only then click OpenAI resend once
This script is intentionally env-driven and JSONL-only. It does not buy SMS.
Required environment:
RUN_JSON=/tmp/codex_2925_luban_uk_run.json # contains {"email":..., "alias":...}
Optional environment:
MAIL_CDP=http://127.0.0.1:9227
OPENAI_CDP=http://127.0.0.1:9228
START_RESEND_DONE=0
TARGET_RESENDS=10
REFRESH_ROUNDS=3
SLEEP_BETWEEN_REFRESH_ROUNDS=15
CODE_PATH=/tmp/openai_2925_email_code.txt
"""
import itertools
import json
import os
import pathlib
import time
import urllib.request
import websocket
RUN_JSON = pathlib.Path(os.environ.get("RUN_JSON", "/tmp/codex_2925_luban_uk_run.json"))
RUN = json.loads(RUN_JSON.read_text())
ACCOUNT = RUN["email"]
ALIAS = RUN["alias"]
LOCAL = ALIAS.split("@")[0].lower()
MAIL_CDP = os.environ.get("MAIL_CDP", "http://127.0.0.1:9227")
OPENAI_CDP = os.environ.get("OPENAI_CDP", "http://127.0.0.1:9228")
START_RESEND_DONE = int(os.environ.get("START_RESEND_DONE", "0"))
TARGET_RESENDS = int(os.environ.get("TARGET_RESENDS", "10"))
REFRESH_ROUNDS = int(os.environ.get("REFRESH_ROUNDS", "3"))
SLEEP_BETWEEN_REFRESH_ROUNDS = int(os.environ.get("SLEEP_BETWEEN_REFRESH_ROUNDS", "15"))
CODE_PATH = pathlib.Path(os.environ.get("CODE_PATH", "/tmp/openai_2925_email_code.txt"))
FOLDERS = [
("inbox", "https://2925.com/#/mailList?type=%E6%94%B6%E4%BB%B6%E7%AE%B1"),
("junk", "https://2925.com/#/mailList?type=%E5%9E%83%E5%9C%BE%E7%AE%B1"),
]
def log(obj):
print(json.dumps({**obj, "ts": int(time.time())}, ensure_ascii=False), flush=True)
def tabs(cdp):
return json.loads(urllib.request.urlopen(cdp + "/json", timeout=10).read().decode())
def connect(cdp, pred):
pages = [t for t in tabs(cdp) if t.get("type") == "page"]
selected = next((t for t in pages if pred(t)), None) or (pages[0] if pages else None)
if not selected:
raise RuntimeError(f"no_page_target:{cdp}")
ws = websocket.create_connection(selected["webSocketDebuggerUrl"], timeout=20)
counter = itertools.count(1)
def call(method, params=None, timeout=45):
msg_id = next(counter)
ws.send(json.dumps({"id": msg_id, "method": method, "params": params or {}}))
end = time.time() + timeout
while time.time() < end:
msg = json.loads(ws.recv())
if msg.get("id") == msg_id:
if "error" in msg:
raise RuntimeError(msg["error"])
return msg.get("result", {})
raise TimeoutError(method)
call("Page.enable")
call("Runtime.enable")
return call
def evaluate(call, js, await_promise=False, timeout=45):
result = call(
"Runtime.evaluate",
{"expression": js, "awaitPromise": await_promise, "returnByValue": True},
timeout,
)
return result.get("result", {}).get("value")
def refresh_once(folder, url, resend_index, refresh_round):
call = connect(MAIL_CDP, lambda t: "2925.com" in (t.get("url") or ""))
call("Page.navigate", {"url": url})
time.sleep(2)
js = r'''(async(local,account)=>{
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const click=e=>{if(!e)return false; e.dispatchEvent(new MouseEvent('mousedown',{bubbles:true})); e.dispatchEvent(new MouseEvent('mouseup',{bubbles:true})); e.click(); return true;};
const refreshButtons=[...document.querySelectorAll('button,.el-button,a,span,div')].filter(e=>(e.innerText||e.textContent||'').trim()==='刷新');
const clicked=click(refreshButtons[refreshButtons.length-1]||refreshButtons[0]);
await sleep(2500);
const txt=document.body?.innerText||'';
const low=txt.toLowerCase();
const emails=[...new Set([...txt.matchAll(/[A-Za-z0-9._%+-]+@2925\.com/g)].map(m=>m[0].toLowerCase()))];
const lines=txt.split(/\n+/).map(s=>s.trim()).filter(Boolean);
const snippets=[];
for(let i=0;i<lines.length;i++){
const around=lines.slice(Math.max(0,i-8),Math.min(lines.length,i+12)).join('\n');
if(around.toLowerCase().includes(local)&&/OpenAI|ChatGPT|验证码|verification|code/i.test(around)) snippets.push(around);
}
const codes=[...new Set(snippets.flatMap(s=>[...s.matchAll(/(?<!\d)(\d{6})(?!\d)/g)].map(m=>m[1])))];
return {clicked,emails,accountOk:emails.includes(account.toLowerCase()),hasLocal:low.includes(local),hasOpenAI:/OpenAI|ChatGPT|验证码|verification|code/i.test(txt),codes};
})(LOCAL,ACCOUNT)'''.replace("LOCAL", json.dumps(LOCAL)).replace("ACCOUNT", json.dumps(ACCOUNT))
val = evaluate(call, js, True) or {}
log({
"phase": "mail_refresh",
"resend_index": resend_index,
"refresh_round": refresh_round,
"folder": folder,
"clicked": val.get("clicked"),
"accountOk": val.get("accountOk"),
"hasLocal": val.get("hasLocal"),
"hasOpenAI": val.get("hasOpenAI"),
"codesCount": len(val.get("codes") or []),
"emails": val.get("emails"),
})
if not val.get("accountOk"):
log({"phase": "account_mismatch", "expected": ACCOUNT, "emails": val.get("emails")})
raise SystemExit(4)
if val.get("codes"):
CODE_PATH.write_text(val["codes"][0])
log({"phase": "current_alias_code_saved", "resend_index": resend_index, "folder": folder})
return True
return False
def click_resend(resend_index):
call = connect(OPENAI_CDP, lambda t: "auth.openai.com" in (t.get("url") or ""))
res = evaluate(call, r'''(async()=>{
const sleep=ms=>new Promise(r=>setTimeout(r,ms));
const btn=[...document.querySelectorAll('button,a')].find(e=>/重新发送|Resend/i.test(e.innerText||e.textContent||''));
if(btn){btn.scrollIntoView({block:'center'}); btn.click(); await sleep(2500);}
return {clicked:!!btn,href:location.href,body:(document.body?.innerText||'').slice(0,260)};
})()''', True) or {}
log({"phase": "openai_resend", "resend_index": resend_index, "clicked": res.get("clicked"), "href": res.get("href")})
if not res.get("clicked"):
log({"phase": "openai_resend_not_found", "resend_index": resend_index, "body": res.get("body")})
raise SystemExit(5)
def main():
log({
"phase": "strict_resend_cadence_start",
"alias": ALIAS,
"account": ACCOUNT,
"start_resend_done": START_RESEND_DONE,
"target_resends": TARGET_RESENDS,
"refresh_rounds": REFRESH_ROUNDS,
})
for resend_index in range(START_RESEND_DONE + 1, TARGET_RESENDS + 1):
log({"phase": "pre_resend_refresh_block_start", "resend_index": resend_index})
for refresh_round in range(1, REFRESH_ROUNDS + 1):
for name, url in FOLDERS:
if refresh_once(name, url, resend_index, refresh_round):
return 0
if refresh_round < REFRESH_ROUNDS:
time.sleep(SLEEP_BETWEEN_REFRESH_ROUNDS)
click_resend(resend_index)
log({"phase": "no_code_after_target_resends", "alias": ALIAS, "total_resends": TARGET_RESENDS})
return 2
if __name__ == "__main__":
raise SystemExit(main())