docs: record eu4 phone run and round-robin fix
This commit is contained in:
@@ -48,7 +48,7 @@ These are confirmed operating rules for BOSS's environment:
|
||||
- ClawEmail creates a fresh mailbox for every run (`CLAWEMAIL_CREATE_PER_RUN=true`). Record every created mailbox locally with outcome classification: pending, success, failed. **Immediately after creation, actively set/verify the mailbox's communication settings before submitting it to OpenAI:** new sub-mailboxes can default to internal-only (`commLevel=1`, `extReceiveType=0`), which blocks OpenAI verification mail. The gate must pass with external receive enabled (`commLevel=2`, `extReceiveType=1`). Do not merely tell BOSS to do this manually: first try the logged-in ClawEmail Dashboard API `POST /api/v1/mailboxes/comm-settings?id=<mailboxId>` with `{commLevel:2, extReceiveType:1, extSendType:0}`. If the Dashboard is not logged in, open/login to it and ask BOSS only for the QQ master-mailbox 6-digit login code if the agent cannot read QQ mail. See `clawemail-operations` reference `references/clawemail-dashboard-comm-settings-openai-signup-2026-05-10.md` and this skill's `references/clawemail-external-receive-gate-openai-2026-05-10.md`.
|
||||
- Phone/SMS platform has no default. Keep `PHONE_VERIFICATION_ENABLED=false` and `PHONE_SMS_PROVIDER` empty unless BOSS configures one or approves enabling it after a phone verification appears.
|
||||
- If phone verification appears and BOSS has configured 5sim, use **SMS activation for `openai`**, not WhatsApp receive channels. The original extension's 5sim provider buys `/v1/user/buy/activation/{country}/{operator}/openai`, polls `/v1/user/check/{activationId}`, and finishes/cancels the activation. **Before buying any 5sim number, pass the identity gate:** the browser must already be confirmed on `auth.openai.com/add-phone` for a usable OpenAI account. If OpenAI returns `account_deactivated`, if ClawEmail cannot create a new sub-mailbox due to quota, or if reused sub-mailboxes only show password mismatch, stop and ask BOSS to free mailbox quota/provide the correct password/approve another mailbox provider. **Country selection priority is `FIVE_SIM_COUNTRY_ORDER` first** (comma-separated list such as `argentina,netherlands,indonesia`), then `FIVE_SIM_COUNTRY_ID`, then the original extension default `vietnam` only if both are empty. For each configured country, rotate numbers up to `PHONE_VERIFICATION_REPLACEMENT_LIMIT`; do **not** silently switch outside the configured order unless BOSS approves. Before buying/submitting a non-USA number, verify the OpenAI visible country selector is already the expected country (for example `阿根廷 (+54)`, `荷兰 (+31)`, `印度尼西亚 (+62)`, or `越南 (+84)`); if it reverted to `美国 (+1)`, fix the selector first or the number will be parsed as invalid. Use visible UI paste/click on `add-phone` when possible because CDP-only input mutation can desync React state and revert the country on submit. If a number is **not immediately rejected**, do not cancel it after one polling timeout: poll 5sim, click OpenAI `重新发送`, poll again, and retry resend at least once before canceling/rotating. If changing proxy (for example `1085` → `1083` or back), expect OpenAI auth session invalidation and restart from a fresh target OAuth URL. Codex2API env URLs may point at `/admin/accounts`; derive the API origin before calling `/api/admin/oauth/generate-auth-url`. If OpenAI returns `account_deactivated` after email verification, stop the current account and start with a new ClawEmail sub-mailbox instead of burning 5sim numbers. See `references/openai-add-phone-5sim-sms-activation-2026-05-10.md`, `references/openai-phone-verification-5sim-sms-2026-05-10.md`, `references/openai-phone-verification-proxy1083-resend-2026-05-10.md`, `references/codex-oauth-token-lite-automation-flow-2026-05-10.md`, and `references/clawemail-quota-identity-gate-before-phone-verify-2026-05-10.md` for observed behavior, resend strategy, proxy-switch recovery, identity-gate checks, and pitfalls.
|
||||
- If a phone-verification runner is stopped or interrupted, immediately inspect/cancel any active 5sim activation left in `RECEIVED`; killed scripts may not execute cleanup. For a UK `+44` run, use 5sim country slug `england`, OpenAI ISO `GB`, visible selector `英国 (+44)`, and dial prefix `44`. See `references/openai-add-phone-strict-country-gate-2026-05-10.md` and `references/openai-add-phone-uk44-and-runner-cleanup-2026-05-10.md`.
|
||||
- If a phone-verification runner is stopped or interrupted, immediately inspect/cancel any active 5sim activation left in `RECEIVED`; killed scripts may not execute cleanup. For a UK `+44` run, use 5sim country slug `england`, OpenAI ISO `GB`, visible selector `英国 (+44)`, and dial prefix `44`. For a Europe broadened run requested as Italy/Poland/Spain/UK, use `italy -> IT -> 意大利 (+39)`, `poland -> PL -> 波兰 (+48)`, `spain -> ES -> 西班牙 (+34)`, and `england -> GB -> 英国 (+44)`. When BOSS says to run “10 次” across a country set, treat it as **10 total attempts across the ordered set**, not 10 per country, unless he explicitly says per-country; schedule countries **round-robin** so the first country cannot consume the whole budget. Before clicking OpenAI resend, **probe the resend channel without clicking** by reading button `value`/`aria-label`/`title`/text; if it says WhatsApp and BOSS's policy is SMS-only, cancel the SMS activation and rotate/stop instead of clicking. Classify `This page isn’t working` / `HTTP ERROR 500` / `500 Internal Server Error` after resend as `resend_server_error`, cancel the order, and recover to `add-phone`. See `references/openai-add-phone-strict-country-gate-2026-05-10.md`, `references/openai-add-phone-uk44-and-runner-cleanup-2026-05-10.md`, `references/openai-add-phone-eu4-roundrobin-correction-2026-05-10.md`, and `references/official-extension-phone-update-2026-05-10.md`.
|
||||
- Plus mode uses `PLUS_PAYMENT_METHOD=gopay`. Any paid GoPay action still requires explicit confirmation immediately before payment/approval.
|
||||
- GoPay WhatsApp OTP uses **方案 A / manual checkpoint**: set `GOPAY_OTP_SOURCE=manual`. The extension detects OTP input and opens a side-panel prompt (`requestGoPayOtpInput` / “输入 GoPay 验证码”); BOSS pastes the WhatsApp code, then the extension fills OTP, fills `GOPAY_PIN`, and continues.
|
||||
- `GOPAY_OTP` should normally stay empty before the run. It is only a temporary prefill/cache for the current OTP dialog, not a durable secret.
|
||||
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
# EU4 phone verification run and round-robin correction (2026-05-10)
|
||||
|
||||
## Request
|
||||
|
||||
BOSS requested broadening 5sim SMS countries to:
|
||||
|
||||
```text
|
||||
Italy, Poland, Spain, United Kingdom
|
||||
```
|
||||
|
||||
and running the latest strict-gate workflow for 10 attempts.
|
||||
|
||||
## Intended config
|
||||
|
||||
```text
|
||||
FIVE_SIM_COUNTRY_ORDER=italy,poland,spain,england
|
||||
PHONE_VERIFICATION_REPLACEMENT_LIMIT=10
|
||||
```
|
||||
|
||||
Country mappings:
|
||||
|
||||
| 5sim country | OpenAI ISO | Visible selector | Prefix |
|
||||
| --- | --- | --- | --- |
|
||||
| `italy` | `IT` | `意大利 (+39)` | `39` |
|
||||
| `poland` | `PL` | `波兰 (+48)` | `48` |
|
||||
| `spain` | `ES` | `西班牙 (+34)` | `34` |
|
||||
| `england` | `GB` | `英国 (+44)` | `44` |
|
||||
|
||||
## Actual first EU4 run outcome
|
||||
|
||||
Runner:
|
||||
|
||||
```text
|
||||
/tmp/phone_verify_eu4_total10_latest.py
|
||||
```
|
||||
|
||||
Log:
|
||||
|
||||
```text
|
||||
/tmp/phone_verify_eu4_total10_latest.log
|
||||
```
|
||||
|
||||
Result:
|
||||
|
||||
```json
|
||||
{"phase":"phone_failed_all","total_attempts":10,"order":["italy","poland","spain","england"]}
|
||||
```
|
||||
|
||||
Important correction: although the order included four countries, the script loop ran all 10 attempts against the first country (`italy`) because it nested `for country in ORDER` outside `for attempt in LIMIT` and stopped after the global total reached 10. It did not test Poland, Spain, or UK in that run.
|
||||
|
||||
## Italy findings
|
||||
|
||||
All bought orders were canceled. Examples:
|
||||
|
||||
- `1006744560`
|
||||
- `1006745080`
|
||||
- `1006745501`
|
||||
- `1006746015`
|
||||
- `1006746588`
|
||||
- `1006747049`
|
||||
- `1006747555`
|
||||
- `1006748145`
|
||||
|
||||
Observed pattern:
|
||||
|
||||
1. Strict country gate passed before buying:
|
||||
- `意大利 (+39)`
|
||||
- `selectValue=IT`
|
||||
2. OpenAI accepted or transitioned into phone verification/waiting state.
|
||||
3. 5sim status stayed `RECEIVED` with `sms_count=0`.
|
||||
4. Resend probe detected WhatsApp delivery, e.g.:
|
||||
```text
|
||||
重新发送 WhatsApp 消息
|
||||
```
|
||||
5. Under BOSS's SMS-only policy, runner canceled the SMS activation instead of clicking WhatsApp resend.
|
||||
|
||||
Conclusion for Italy:
|
||||
|
||||
```text
|
||||
Italy +39 did not produce SMS via 5sim/openai in this run; OpenAI repeatedly exposed WhatsApp resend, so SMS-only automation canceled the activations.
|
||||
```
|
||||
|
||||
## Runner bug fixed
|
||||
|
||||
A corrected round-robin runner was written:
|
||||
|
||||
```text
|
||||
/tmp/phone_verify_eu4_roundrobin10_latest.py
|
||||
```
|
||||
|
||||
The corrected runner attempts countries in round-robin order:
|
||||
|
||||
```text
|
||||
attempt 1: italy
|
||||
attempt 2: poland
|
||||
attempt 3: spain
|
||||
attempt 4: england
|
||||
attempt 5: italy
|
||||
...
|
||||
```
|
||||
|
||||
This prevents a global limit from being exhausted by the first country only.
|
||||
|
||||
## Workflow rule added
|
||||
|
||||
When BOSS says "expand countries A/B/C/D and run N attempts", interpret `N` as global attempts unless BOSS explicitly says "per country". Use a round-robin schedule so each configured country is actually tested before repeating the first one.
|
||||
|
||||
## Still mandatory
|
||||
|
||||
- Strict country/area-code gate before buying and before submit.
|
||||
- No non-US number on `美国 (+1)`.
|
||||
- Probe resend channel before clicking.
|
||||
- If resend channel is WhatsApp and BOSS policy is SMS-only, cancel activation and rotate/stop.
|
||||
- Cancel any active activation on interruption or flow failure.
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
# OpenAI add-phone EU4 total-10 SMS run notes (2026-05-10)
|
||||
|
||||
## Trigger
|
||||
|
||||
Use these notes when BOSS asks to broaden 5sim SMS phone-verification countries around Europe while keeping the latest strict OpenAI `add-phone` runner behavior.
|
||||
|
||||
## User preference captured
|
||||
|
||||
When BOSS says to try a set of regions and says "设为/跑 10 次", interpret it as **10 total attempts across the configured country order** unless BOSS explicitly says "每个国家 10 次". This avoids burning unnecessary 5sim orders.
|
||||
|
||||
## Country order used
|
||||
|
||||
```text
|
||||
italy,poland,spain,england
|
||||
```
|
||||
|
||||
## Required OpenAI country mappings
|
||||
|
||||
| 5sim slug | OpenAI ISO | Visible selector text | Dial prefix |
|
||||
| --- | --- | --- | --- |
|
||||
| `italy` | `IT` | `意大利 (+39)` | `39` |
|
||||
| `poland` | `PL` | `波兰 (+48)` | `48` |
|
||||
| `spain` | `ES` | `西班牙 (+34)` | `34` |
|
||||
| `england` | `GB` | `英国 (+44)` | `44` |
|
||||
|
||||
## Runner changes made during the session
|
||||
|
||||
A temporary latest runner was created from the strict-gate runner:
|
||||
|
||||
```text
|
||||
/tmp/phone_verify_eu4_total10_latest.py
|
||||
```
|
||||
|
||||
It should be promoted/copied into the skill package if it continues to be useful. It differs from the previous strict runner by:
|
||||
|
||||
1. Adding Italy/Poland/Spain mappings.
|
||||
2. Keeping UK mapping as `england` -> `GB` -> `英国 (+44)`.
|
||||
3. Enforcing `MAX_TOTAL_ATTEMPTS = 10` across all countries, not per-country.
|
||||
4. Retaining the existing strict country gate before buy and before submit.
|
||||
5. Retaining official-update learnings:
|
||||
- probe resend channel before clicking;
|
||||
- cancel/rotate if WhatsApp is detected under SMS-only policy;
|
||||
- classify 500/contact-verification server errors;
|
||||
- cancel activations if a gate or server error occurs.
|
||||
|
||||
## First-run evidence
|
||||
|
||||
The EU4 run started successfully. The first attempt was Italy and passed the country gate before buying:
|
||||
|
||||
```json
|
||||
{"phase":"country_gate_before_buy","country":"italy","attempt":1,"ok":true,"country":"意大利 (+39)","selectValue":"IT","expected":"意大利 (+39)"}
|
||||
```
|
||||
|
||||
A 5sim activation was bought only after the gate passed. Full phone number is intentionally not recorded.
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- Do not rely on `PHONE_VERIFICATION_REPLACEMENT_LIMIT=10` alone when multiple countries are configured; the old runner interprets that as per-country. Add a global attempt counter when BOSS asks for total attempts.
|
||||
- Keep the strict country/area-code gate. Do not submit European numbers while the selector is still `美国 (+1)`.
|
||||
- Keep SMS-only policy. Do not click WhatsApp resend unless BOSS explicitly approves switching away from SMS-only.
|
||||
- If the run is interrupted, immediately check/cancel any active 5sim activation in `RECEIVED` state.
|
||||
@@ -0,0 +1,231 @@
|
||||
import json, pathlib, re, subprocess, sys, time, urllib.request, urllib.parse
|
||||
|
||||
ENV = pathlib.Path('/Users/chick/.hermes/env/codex_oauth_onboarding.env').read_text()
|
||||
|
||||
def get(k, d=''):
|
||||
m = re.search(r'^' + re.escape(k) + r'=(.*)$', ENV, re.M)
|
||||
return (m.group(1).strip().strip('"\'') if m else d)
|
||||
|
||||
TOKEN = get('FIVE_SIM_API_KEY') or get('FIVESIM_API_KEY')
|
||||
HEAD = {'Authorization': 'Bearer ' + TOKEN, 'Accept': 'application/json'}
|
||||
ORDER = [x.strip() for x in get('FIVE_SIM_COUNTRY_ORDER', '').split(',') if x.strip()] or ['indonesia']
|
||||
LIMIT = int(get('PHONE_VERIFICATION_REPLACEMENT_LIMIT', '10') or '10')
|
||||
PRODUCT = get('FIVE_SIM_PRODUCT', 'openai') or 'openai'
|
||||
OPERATOR = get('FIVE_SIM_OPERATOR', 'any') or 'any'
|
||||
MAX_TOTAL_ATTEMPTS = int(get('PHONE_VERIFICATION_MAX_TOTAL_ATTEMPTS', '10') or '10')
|
||||
META = {
|
||||
'argentina': {'iso': 'AR', 'visible': '阿根廷 (+54)', 'prefix': '54'},
|
||||
'netherlands': {'iso': 'NL', 'visible': '荷兰 (+31)', 'prefix': '31'},
|
||||
'indonesia': {'iso': 'ID', 'visible': '印度尼西亚 (+62)', 'prefix': '62'},
|
||||
'england': {'iso': 'GB', 'visible': '英国 (+44)', 'prefix': '44'},
|
||||
'italy': {'iso': 'IT', 'visible': '意大利 (+39)', 'prefix': '39'},
|
||||
'poland': {'iso': 'PL', 'visible': '波兰 (+48)', 'prefix': '48'},
|
||||
'spain': {'iso': 'ES', 'visible': '西班牙 (+34)', 'prefix': '34'},
|
||||
'vietnam': {'iso': 'VN', 'visible': '越南 (+84)', 'prefix': '84'},
|
||||
}
|
||||
|
||||
def emit(**kw):
|
||||
print(json.dumps(kw, ensure_ascii=False), flush=True)
|
||||
|
||||
def req_json(url):
|
||||
req = urllib.request.Request(url, headers=HEAD)
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.loads(r.read().decode())
|
||||
|
||||
def cancel(aid):
|
||||
try:
|
||||
return req_json(f'https://5sim.net/v1/user/cancel/{aid}')
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
|
||||
def finish(aid):
|
||||
try:
|
||||
return req_json(f'https://5sim.net/v1/user/finish/{aid}')
|
||||
except Exception as e:
|
||||
return {'error': str(e)}
|
||||
|
||||
def check(aid):
|
||||
return req_json(f'https://5sim.net/v1/user/check/{aid}')
|
||||
|
||||
def cdp(js, match='auth.openai.com', timeout=45):
|
||||
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 current_state():
|
||||
return cdp("""
|
||||
return {url: location.href, title: document.title,
|
||||
country: [...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\(\+\d+\)/.test(t)),
|
||||
selectValue: document.querySelector('select')?.value,
|
||||
tel: document.querySelector('input[type=tel]')?.value,
|
||||
text: document.body.innerText.slice(0, 900)};
|
||||
""", timeout=25)
|
||||
|
||||
def restore_add_phone():
|
||||
st = current_state()
|
||||
if 'auth.openai.com/add-phone' in st:
|
||||
return True
|
||||
raw = get('CODEX2API_URL')
|
||||
u = urllib.parse.urlsplit(raw)
|
||||
origin = f'{u.scheme}://{u.netloc}' if u.scheme and u.netloc else raw.rstrip('/')
|
||||
req = urllib.request.Request(origin + '/api/admin/oauth/generate-auth-url', data=b'{}', headers={'X-Admin-Key': get('CODEX2API_ADMIN_KEY'), 'Content-Type': 'application/json'})
|
||||
with urllib.request.urlopen(req, timeout=20) as r:
|
||||
data = json.loads(r.read().decode())
|
||||
url = data.get('auth_url') or data.get('url')
|
||||
emit(phase='oauth_recover', session_id=data.get('session_id') or data.get('id'))
|
||||
urllib.request.urlopen(urllib.request.Request('http://127.0.0.1:9223/json/new?' + urllib.parse.quote(url, safe=''), method='PUT'), timeout=10).read()
|
||||
time.sleep(6)
|
||||
email = json.loads(pathlib.Path('/tmp/current_codex_run.json').read_text())['email']
|
||||
pw = get('CUSTOM_PASSWORD')
|
||||
js = f"""
|
||||
const email={json.dumps(email)}, pw={json.dumps(pw)};
|
||||
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||
let emailI=document.querySelector('input[type=email], input[name=email], input[autocomplete=username], input');
|
||||
if(emailI && location.href.includes('/log-in')){{emailI.focus(); setter.call(emailI,email); emailI.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:email}})); emailI.dispatchEvent(new Event('change',{{bubbles:true}})); [...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();}}
|
||||
await new Promise(r=>setTimeout(r,6000));
|
||||
let pwI=document.querySelector('input[type=password]');
|
||||
if(pwI){{pwI.focus(); setter.call(pwI,pw); pwI.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:pw}})); pwI.dispatchEvent(new Event('change',{{bubbles:true}})); [...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();}}
|
||||
await new Promise(r=>setTimeout(r,12000));
|
||||
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,600)}};
|
||||
"""
|
||||
out = cdp(js, timeout=40)
|
||||
emit(phase='recover_state', state=out[-800:])
|
||||
return 'auth.openai.com/add-phone' in current_state()
|
||||
|
||||
def set_country_gate(country):
|
||||
meta = META[country]
|
||||
js = f"""
|
||||
const iso={json.dumps(meta['iso'])}, visible={json.dumps(meta['visible'])};
|
||||
const sel=document.querySelector('select');
|
||||
if(!sel) return {{ok:false, reason:'no_select', url:location.href, text:document.body.innerText.slice(0,300)}};
|
||||
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,900));
|
||||
const tel=document.querySelector('input[type=tel]');
|
||||
if(tel){{const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set; setter.call(tel,''); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'deleteContentBackward'}})); tel.dispatchEvent(new Event('change',{{bubbles:true}}));}}
|
||||
const country=[...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t));
|
||||
const selectValue=document.querySelector('select')?.value;
|
||||
return {{ok:selectValue===iso && country===visible, country, selectValue, expected:visible, iso, url:location.href}};
|
||||
"""
|
||||
return cdp(js, timeout=25)
|
||||
|
||||
def submit_number(country, phone, aid):
|
||||
meta = META[country]
|
||||
digits = re.sub(r'\D+', '', str(phone))
|
||||
local = digits[len(meta['prefix']):] if digits.startswith(meta['prefix']) else digits
|
||||
js = f"""
|
||||
const iso={json.dumps(meta['iso'])}, visible={json.dumps(meta['visible'])}, local={json.dumps(local)};
|
||||
function visibleCountry(){{return [...document.querySelectorAll('button')].map(b=>b.innerText).find(t=>/\\(\\+\\d+\\)/.test(t));}}
|
||||
let country=visibleCountry(), selectValue=document.querySelector('select')?.value;
|
||||
if(selectValue!==iso || country!==visible) return {{clicked:false, reason:'pre_fill_country_gate_failed', country, selectValue, expected:visible, iso}};
|
||||
const tel=document.querySelector('input[type=tel]');
|
||||
if(!tel) return {{clicked:false, reason:'no_tel', country, selectValue}};
|
||||
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set;
|
||||
tel.focus(); setter.call(tel,''); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'deleteContentBackward'}}));
|
||||
setter.call(tel,local); tel.dispatchEvent(new InputEvent('input',{{bubbles:true,inputType:'insertText',data:local}})); tel.dispatchEvent(new Event('change',{{bubbles:true}}));
|
||||
await new Promise(r=>setTimeout(r,800));
|
||||
country=visibleCountry(); selectValue=document.querySelector('select')?.value;
|
||||
if(selectValue!==iso || country!==visible) return {{clicked:false, reason:'post_fill_country_gate_failed', country, selectValue, expected:visible, iso, tel:document.querySelector('input[type=tel]')?.value}};
|
||||
[...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();
|
||||
await new Promise(r=>setTimeout(r,10000));
|
||||
return {{clicked:true,url:location.href,title:document.title,text:document.body.innerText.slice(0,1200),country:visibleCountry(),selectValue:document.querySelector('select')?.value,tel:document.querySelector('input[type=tel]')?.value}};
|
||||
"""
|
||||
return cdp(js, timeout=35)
|
||||
|
||||
emit(phase='phone_strict_start', countries=ORDER, limit=LIMIT, max_total_attempts=MAX_TOTAL_ATTEMPTS)
|
||||
attempt_total = 0
|
||||
for country in ORDER:
|
||||
if attempt_total >= MAX_TOTAL_ATTEMPTS:
|
||||
break
|
||||
if country not in META:
|
||||
emit(phase='skip_unknown_country', country=country)
|
||||
continue
|
||||
for local_attempt in range(1, LIMIT + 1):
|
||||
if attempt_total >= MAX_TOTAL_ATTEMPTS:
|
||||
break
|
||||
attempt_total += 1
|
||||
attempt = attempt_total
|
||||
emit(phase='attempt_start', country=country, attempt=attempt, local_attempt=local_attempt, max_total=MAX_TOTAL_ATTEMPTS)
|
||||
if not restore_add_phone():
|
||||
emit(phase='cannot_restore_add_phone', country=country, attempt=attempt, local_attempt=local_attempt, state=current_state()[-800:])
|
||||
sys.exit(20)
|
||||
gate = set_country_gate(country)
|
||||
emit(phase='country_gate_before_buy', country=country, attempt=attempt, local_attempt=local_attempt, state=gate[-700:])
|
||||
if '"ok": true' not in gate:
|
||||
continue
|
||||
try:
|
||||
act = req_json(f'https://5sim.net/v1/user/buy/activation/{country}/{OPERATOR}/{PRODUCT}')
|
||||
except Exception as e:
|
||||
emit(phase='buy_failed', country=country, attempt=attempt, local_attempt=local_attempt, error=str(e))
|
||||
continue
|
||||
aid = act.get('id'); phone = str(act.get('phone'))
|
||||
pathlib.Path('/tmp/fivesim_activation.json').write_text(json.dumps(act, ensure_ascii=False, indent=2))
|
||||
masked = re.sub(r'(\+?\d{4})\d+(\d{3})', r'\1***\2', phone)
|
||||
emit(phase='buy', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, masked=masked, status=act.get('status'))
|
||||
gate2 = current_state()
|
||||
if META[country]['visible'] not in gate2 or ('"selectValue": "' + META[country]['iso'] + '"') not in gate2:
|
||||
emit(phase='cancel_before_submit_country_mismatch', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=gate2[-600:], result=cancel(aid).get('status'))
|
||||
continue
|
||||
out = submit_number(country, phone, aid)
|
||||
emit(phase='submit_result', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=out[-1000:])
|
||||
if 'clicked": false' in out or 'country_gate_failed' in out:
|
||||
emit(phase='cancel_submit_gate_failed', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
|
||||
continue
|
||||
if any(x in out for x in ['无法向此电话号码发送', '无法向该号码发送', '电话号码无效']):
|
||||
emit(phase='direct_reject_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
|
||||
continue
|
||||
got = None
|
||||
for r in range(1, 7):
|
||||
time.sleep(10)
|
||||
st = check(aid); sms = st.get('sms') or []
|
||||
emit(phase='sms_poll', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, round=r, status=st.get('status'), sms_count=len(sms))
|
||||
if sms:
|
||||
got = sms[0].get('code') or sms[0].get('text')
|
||||
break
|
||||
if not got:
|
||||
resend = cdp("""
|
||||
const text=(document.title+' '+document.body.innerText).replace(/\s+/g,' ').trim();
|
||||
if(/this page isn['’]?t working|currently unable to handle this request|http error 500|500 internal server error/i.test(text)) return {clicked:false, reason:'resend_server_error', url:location.href, text:text.slice(0,600)};
|
||||
const btn=[...document.querySelectorAll('button,input[type=submit],input[type=button]')].find(b=>{const t=[b.value,b.getAttribute?.('aria-label'),b.getAttribute?.('title'),b.innerText,b.textContent].filter(Boolean).join(' '); return /resend|重新发送|再次发送|whats\s*app/i.test(t);});
|
||||
const channelText=btn?[btn.value,btn.getAttribute?.('aria-label'),btn.getAttribute?.('title'),btn.innerText,btn.textContent].filter(Boolean).join(' ').replace(/\s+/g,' ').trim():'';
|
||||
const channel=/whats\s*app/i.test(channelText)?'whatsapp':(/sms|text message|短信/i.test(channelText)?'sms':'unknown');
|
||||
if(channel==='whatsapp') return {clicked:false, channel, channelText, url:location.href};
|
||||
btn?.click();
|
||||
await new Promise(r=>setTimeout(r,2000));
|
||||
const after=(document.title+' '+document.body.innerText).replace(/\s+/g,' ').trim();
|
||||
return {clicked:!!btn,channel,channelText,url:location.href,text:after.slice(0,600),serverError:/this page isn['’]?t working|currently unable to handle this request|http error 500|500 internal server error/i.test(after)};
|
||||
""", match='auth.openai.com', timeout=20)
|
||||
emit(phase='resend', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, state=resend[-700:])
|
||||
if 'whatsapp' in resend.lower():
|
||||
emit(phase='whatsapp_resend_detected_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
|
||||
continue
|
||||
if 'resend_server_error' in resend or 'serverError' in resend:
|
||||
emit(phase='resend_server_error_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
|
||||
continue
|
||||
for r in range(1, 7):
|
||||
time.sleep(10)
|
||||
st = check(aid); sms = st.get('sms') or []
|
||||
emit(phase='sms_poll_after_resend', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, round=r, status=st.get('status'), sms_count=len(sms))
|
||||
if sms:
|
||||
got = sms[0].get('code') or sms[0].get('text')
|
||||
break
|
||||
if not got:
|
||||
emit(phase='no_sms_cancel', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid, result=cancel(aid).get('status'))
|
||||
continue
|
||||
emit(phase='sms_received', country=country, attempt=attempt, local_attempt=local_attempt, activation_id=aid)
|
||||
code = ''.join(re.findall(r'\d', str(got)))[:6]
|
||||
fill = cdp(f"""
|
||||
const code={json.dumps(code)};
|
||||
const input=document.querySelector('input[autocomplete=one-time-code], input[name=code], input');
|
||||
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}}));
|
||||
[...document.querySelectorAll('button')].find(b=>(b.innerText||'').trim()==='继续')?.click();
|
||||
await new Promise(r=>setTimeout(r,12000));
|
||||
return {{url:location.href,title:document.title,text:document.body.innerText.slice(0,1000)}};
|
||||
""", match='auth.openai.com', timeout=35)
|
||||
emit(phase='code_submit', state=fill[-1000:])
|
||||
finish(aid)
|
||||
sys.exit(0)
|
||||
emit(phase='phone_failed_all', total_attempts=attempt_total, order=ORDER)
|
||||
sys.exit(3)
|
||||
Reference in New Issue
Block a user