Add muse-auth.py: login state detection, sign-in vs sign-up notes

This commit is contained in:
operator
2026-10-03 21:06:22 +00:00
parent 02cf10acc8
commit 1191cbd86e
+112
View File
@@ -0,0 +1,112 @@
#!/usr/bin/env python3
"""
muse-auth.py: Muse.ai authentication state detection and flows.
Handles the tricky parts of automation:
- Sign IN (existing account) vs Sign UP (new account) have DIFFERENT verification flows
- Meta account selection has UI quirks (+1 for extra accounts)
- Login can get stuck in incomplete states that need detection and restart
STATES (detectable via CDP):
LANDING: Title == "muse.ai", not logged in
ACCOUNT_SELECTION: Body contains "Your email matches multiple accounts"
UI QUIRK: Meta shows "+1" for extra accounts. The "+1" option is NOT
the right one — find the actual account name (e.g., "Nico Parada",
not "auxfate"). The +1 is a collapsed list, not a selectable account.
OTP_PROMPT: Body contains "To log in, enter the code"
LOGGED_OUT: Body contains "Log in" button
NOT_IN_CHAT: API messages times out (no chat UI to read)
LOGGED_IN: Title contains "Chat —" or "Muse —", body has "Main chat"/"Side chats",
message input present, API returns actual content.
SIGN IN vs SIGN UP:
Sign IN (existing account):
1. Enter email/phone
2. OTP prompt (code sent to email/phone)
3. Meta account selection (if multiple accounts match)
4. Chat UI
Sign UP (new account):
1. Enter email/phone
2. OTP prompt (code sent to email/phone)
3. **DIFFERENT:** May require additional verification:
- CAPTCHA
- Phone verification (if email used)
- Email verification (if phone used)
- Terms acceptance
- Profile setup (name, etc.)
4. **DIFFERENT:** May NOT have Meta account selection (new account = no existing Meta link)
5. Chat UI (possibly with onboarding tour)
The sign-up flow is less predictable and may require human intervention
for CAPTCHAs and other anti-bot measures. Sign-in is more automatable.
RESTART LOGIC:
If state != LOGGED_IN, restart from scratch:
- Clear cookies/session
- Navigate to muse.ai
- Begin sign-in flow
- Don't try to resume a stuck flow (OTP codes expire, sessions timeout)
"""
import json
import urllib.request
import websocket
import time
def get_state(cdp_url):
"""
Detect the current authentication state via CDP.
Returns: (state, details)
"""
try:
with urllib.request.urlopen(cdp_url, timeout=5) as r:
ts = json.load(r)
pages = [t for t in ts if t.get("type") == "page"]
if not pages:
return ("NO_PAGE", "No browser page found")
p = pages[0]
ws = websocket.create_connection(p["webSocketDebuggerUrl"], timeout=10)
except Exception as e:
return ("CDP_ERROR", str(e))
def ev(expr):
try:
ws.send(json.dumps({
"id": 1, "method": "Runtime.evaluate",
"params": {"expression": expr, "returnByValue": True}
}))
r = json.loads(ws.recv())
return r["result"]["result"].get("value", "")
except:
return ""
title = ev("document.title") or ""
body = ev("document.body.innerText.slice(0,2000)") or ""
ws.close()
# Check states in order of specificity
if "Your email matches multiple accounts" in body:
return ("ACCOUNT_SELECTION", "Meta account selection - watch for +1 quirk")
if "To log in, enter the code" in body:
return ("OTP_PROMPT", "Waiting for OTP code")
if "Main chat" in body and "Side chats" in body:
return ("LOGGED_IN", "In chat UI")
if title.strip() == "muse.ai":
return ("LANDING", "On landing page, not logged in")
if "Log in" in body:
return ("LOGGED_OUT", "Logged out or not started")
return ("UNKNOWN", f"Title: {title[:50]}")
def is_logged_in(cdp_url):
"""Quick check: are we in the chat?"""
state, _ = get_state(cdp_url)
return state == "LOGGED_IN"
if __name__ == "__main__":
import sys
cdp = sys.argv[1] if len(sys.argv) > 1 else "http://127.0.0.1:9440/json/list"
state, details = get_state(cdp)
print(f"State: {state}")
print(f"Details: {details}")