173 lines
7.1 KiB
Python
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())
|