feat(cred): harden client onboarding with Instagram linking portal, age verification bypass, and fleet runbook
This commit is contained in:
+77
-37
@@ -95,50 +95,71 @@ def main():
|
||||
page = get_page(port)
|
||||
ws = websocket.create_connection(page['webSocketDebuggerUrl'], timeout=15)
|
||||
|
||||
# Step 1: Check if already logged in
|
||||
# Step 1: Check if already logged in or blocked at verification gate
|
||||
title = ev(ws, "document.title")
|
||||
body = ev(ws, "document.body.innerText.slice(0,200)")
|
||||
if "Connected" in body or "Chats" in body:
|
||||
body = ev(ws, "document.body.innerText.slice(0,500)")
|
||||
url = ev(ws, "location.href") or ""
|
||||
if "access/verification" in url or "confirm your age" in body.lower():
|
||||
ig_link = ev(ws, """(async()=>{
|
||||
try {
|
||||
const r = await fetch('/api/hatch/age-confirmation/linking-web-auth?account_type=instagram', {
|
||||
headers: {'Accept': 'application/json'}
|
||||
});
|
||||
const j = await r.json();
|
||||
return j && j.url ? j.url : null;
|
||||
} catch(e) { return null; }
|
||||
})()""", True)
|
||||
if ig_link:
|
||||
print(f"LINK_INSTAGRAM_URL: {ig_link}")
|
||||
print(f"NEEDS_HUMAN: Age verification required for {args.email} at {url}. Client intervention required to confirm age or link Instagram/Facebook.", file=sys.stderr)
|
||||
ws.close()
|
||||
sys.exit(3)
|
||||
|
||||
if ("Connected" in body or "Chats" in body) and "Enter your code" not in body and "access/verification" not in url:
|
||||
print(f"Already logged in (title: {title})")
|
||||
ws.close()
|
||||
return 0
|
||||
|
||||
# Step 2: Click Log in (if on landing page)
|
||||
if "Log in" in body:
|
||||
print("Clicking Log in...")
|
||||
already_on_otp = ("Enter your code" in body or "code we sent" in body) and args.email in body
|
||||
if not already_on_otp:
|
||||
# Step 2: Click Log in (if on landing page)
|
||||
if "Log in" in body:
|
||||
print("Clicking Log in...")
|
||||
ev(ws, """(async()=>{
|
||||
const b=[...document.querySelectorAll('button')].find(x=>x.innerText.includes('Log in'));
|
||||
if(b) b.click(); return !!b;
|
||||
})()""", True)
|
||||
time.sleep(3)
|
||||
|
||||
# Step 3: Enter email
|
||||
print("Entering email: [redacted]")
|
||||
result = ev(ws, f"""(async()=>{{
|
||||
const inp=[...document.querySelectorAll('input')].find(i=>
|
||||
(i.placeholder&&i.placeholder.toLowerCase().includes('email'))||
|
||||
(i.getAttribute('aria-label')&&i.getAttribute('aria-label').toLowerCase().includes('email'))
|
||||
);
|
||||
if(!inp) return 'NOINPUT';
|
||||
inp.focus();
|
||||
document.execCommand('insertText',false,'{args.email}');
|
||||
await new Promise(r=>setTimeout(r,500));
|
||||
return 'entered:'+inp.value;
|
||||
}})()""", True)
|
||||
print("Email: [redacted]")
|
||||
if result == 'NOINPUT':
|
||||
print("ERROR: Email input not found", file=sys.stderr)
|
||||
ws.close()
|
||||
sys.exit(1)
|
||||
time.sleep(1)
|
||||
|
||||
# Step 4: Click Continue
|
||||
print("Clicking Continue...")
|
||||
ev(ws, """(async()=>{
|
||||
const b=[...document.querySelectorAll('button')].find(x=>x.innerText.includes('Log in'));
|
||||
const b=[...document.querySelectorAll('button')].find(x=>x.innerText.includes('Continue'));
|
||||
if(b) b.click(); return !!b;
|
||||
})()""", True)
|
||||
time.sleep(3)
|
||||
|
||||
# Step 3: Enter email
|
||||
print("Entering email: [redacted]")
|
||||
result = ev(ws, f"""(async()=>{{
|
||||
const inp=[...document.querySelectorAll('input')].find(i=>
|
||||
(i.placeholder&&i.placeholder.toLowerCase().includes('email'))||
|
||||
(i.getAttribute('aria-label')&&i.getAttribute('aria-label').toLowerCase().includes('email'))
|
||||
);
|
||||
if(!inp) return 'NOINPUT';
|
||||
inp.focus();
|
||||
document.execCommand('insertText',false,'{args.email}');
|
||||
await new Promise(r=>setTimeout(r,500));
|
||||
return 'entered:'+inp.value;
|
||||
}})()""", True)
|
||||
print("Email: [redacted]")
|
||||
if result == 'NOINPUT':
|
||||
print("ERROR: Email input not found", file=sys.stderr)
|
||||
ws.close()
|
||||
sys.exit(1)
|
||||
time.sleep(1)
|
||||
|
||||
# Step 4: Click Continue
|
||||
print("Clicking Continue...")
|
||||
ev(ws, """(async()=>{
|
||||
const b=[...document.querySelectorAll('button')].find(x=>x.innerText.includes('Continue'));
|
||||
if(b) b.click(); return !!b;
|
||||
})()""", True)
|
||||
time.sleep(4)
|
||||
time.sleep(4)
|
||||
else:
|
||||
print("Page is already on OTP prompt for this email, skipping email entry.")
|
||||
|
||||
# Step 5: Check for OTP prompt
|
||||
body = ev(ws, "document.body.innerText.slice(0,500)")
|
||||
@@ -176,7 +197,26 @@ def main():
|
||||
# Verify login
|
||||
body = ev(ws, "document.body.innerText.slice(0,500)")
|
||||
title = ev(ws, "document.title") or ""
|
||||
if "Connected" in body or "Chats" in body or "Muse" in title or "Chat" in title:
|
||||
url = ev(ws, "location.href") or ""
|
||||
|
||||
# Check for post-login gates requiring human/client intervention
|
||||
if "access/verification" in url or "confirm your age" in body.lower():
|
||||
ig_link = ev(ws, """(async()=>{
|
||||
try {
|
||||
const r = await fetch('/api/hatch/age-confirmation/linking-web-auth?account_type=instagram', {
|
||||
headers: {'Accept': 'application/json'}
|
||||
});
|
||||
const j = await r.json();
|
||||
return j && j.url ? j.url : null;
|
||||
} catch(e) { return null; }
|
||||
})()""", True)
|
||||
if ig_link:
|
||||
print(f"LINK_INSTAGRAM_URL: {ig_link}")
|
||||
print(f"NEEDS_HUMAN: Age verification required for {args.email} at {url}. Client intervention required to confirm age or link Instagram/Facebook.", file=sys.stderr)
|
||||
ws.close()
|
||||
sys.exit(3)
|
||||
|
||||
if "Connected" in body or "Chats" in body or (("Muse" in title or "Chat" in title) and "access/verification" not in url):
|
||||
print(f"SUCCESS: Logged in as [redacted] (title: {title})")
|
||||
ws.close()
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user