#!/usr/bin/env python3 """Meta Accounts Center read API (via authenticated browser session). Usage: meta-acct.py list-linked # linked profiles in this Meta Account meta-acct.py security-status # login & recovery / security checkup summary meta-acct.py login-activity # "Where you're logged in" sessions The agent's browser must have an active Meta session (phone/email OTP login). Reads NODES.md for the CDP port. Outputs JSON. Scope: Accounts Center linkage/security surface only. No writes. """ import json, sys, time, urllib.request, os NETVM = os.environ.get("NETVM_DIR") if not NETVM: script_dir = os.path.dirname(os.path.abspath(__file__)) parent = os.path.dirname(script_dir) if os.path.exists(os.path.join(parent, "NODES.md")): NETVM = parent else: NETVM = "/home/super/Projects/NetVM" AC_BASE = "https://accountscenter.meta.com" def cdp_port(agent): for line in open(os.path.join(NETVM, "NODES.md")): line = line.strip() if not line.startswith("|"): continue cells = [c.strip() for c in line.strip("|").split("|")] if len(cells) >= 4 and cells[0] == agent: return int(cells[3]) raise SystemExit(f"meta-acct: unknown agent '{agent}' (not in NODES.md)") def cdp_get(port, path, method="GET", timeout=10): req = urllib.request.Request(f"http://127.0.0.1:{port}{path}", method=method) with urllib.request.urlopen(req, timeout=timeout) as r: return json.loads(r.read()) def get_ac_tab(port): tabs = cdp_get(port, "/json/list") for t in tabs: if "accountscenter.meta.com" in t.get("url", "") and t.get("type") == "page": return t # create one return cdp_get(port, f"/json/new?{AC_BASE}/", method="PUT") def evaluate(port, ws_url, js, timeout=15): import websocket ws = websocket.create_connection(ws_url, timeout=timeout) try: ws.send(json.dumps({"id": 1, "method": "Page.navigate", "params": {"url": js[0]}})) ws.recv() time.sleep(4) ws.send(json.dumps({"id": 2, "method": "Runtime.evaluate", "params": {"expression": js[1], "returnByValue": True}})) resp = json.loads(ws.recv()) return json.loads(resp["result"]["result"]["value"]) finally: ws.close() def cmd_list_linked(port): tab = get_ac_tab(port) data = evaluate(port, tab["webSocketDebuggerUrl"], ( f"{AC_BASE}/account_overview/", """JSON.stringify((() => { const h2 = [...document.querySelectorAll('h2')] .find(e=>e.innerText.trim()==='Profiles'); // Full section is 3 levels up (H2 > DIV > DIV > MAIN) const container = h2?.parentElement?.parentElement?.parentElement; return { email: (document.body.innerText.match( /[\\w.+-]+@[\\w-]+\\.[\\w.]+/)||[])[0]||null, section: container?.innerText?.slice(0,1000) || null }; })())""")) # Parse: lines after "Profiles" are name/type pairs until "Add more", # then "AI and devices" section follows profiles = [] section = data.get("section", "") if section: lines = [l.strip() for l in section.split("\n") if l.strip()] try: start = lines.index("Profiles") + 1 except ValueError: start = 0 i = start while i < len(lines): if lines[i] == "Add more": i += 1 continue if lines[i] == "AI and devices": # Next line is the device/service name if i + 1 < len(lines): profiles.append({"name": lines[i+1], "type": "ai_device"}) break # Expect name + type pair if i + 1 < len(lines) and lines[i+1] not in ( "Add more", "AI and devices", "Profiles"): profiles.append({"name": lines[i], "type": lines[i+1].lower()}) i += 2 else: i += 1 return {"email": data.get("email"), "profiles": profiles} def cmd_security_status(port): tab = get_ac_tab(port) data = evaluate(port, tab["webSocketDebuggerUrl"], ( f"{AC_BASE}/password_and_security/", """JSON.stringify({ checkup: document.body.innerText.match( /Meta Security Checkup\\s*(\\d+) recommended actions/)?.[1] || null, sections: [...document.querySelectorAll('h2')] .map(e=>e.innerText.trim()).slice(0,10), body: document.body.innerText.slice(0,2000) })""")) return {"security_checkup_actions": data.get("checkup"), "sections": data.get("sections")} def cmd_login_activity(port): tab = get_ac_tab(port) # "Where you're logged in" is on the password_and_security page data = evaluate(port, tab["webSocketDebuggerUrl"], ( f"{AC_BASE}/password_and_security/", """JSON.stringify({ body: document.body.innerText.slice(0,4000) })""")) body = data.get("body", "") # Extract the "Where you're logged in" section idx = body.find("Where you're logged in") section = body[idx:idx+1500] if idx >= 0 else "" return {"where_logged_in": section} def main(): if len(sys.argv) != 3: print(__doc__.strip().split("\n")[0]) print("Usage: meta-acct.py ") sys.exit(2) cmd, agent = sys.argv[1], sys.argv[2] port = cdp_port(agent) try: if cmd == "list-linked": out = cmd_list_linked(port) elif cmd == "security-status": out = cmd_security_status(port) elif cmd == "login-activity": out = cmd_login_activity(port) else: raise SystemExit(f"meta-acct: unknown command '{cmd}'") except Exception as e: print(json.dumps({"error": str(e), "agent": agent})) sys.exit(1) out["agent"] = agent out["checked_at"] = int(time.time()) print(json.dumps(out, indent=2)) if __name__ == "__main__": main()