# Misskey/Sharkey (Universe Federation) Registration Pattern ## Instance: dc.hhhl.cc **Software**: Sharkey fork "Universe Federation" v2025.5.2-dev **Captcha**: Cloudflare Turnstile (sitekey: `0x4AAAAAADUhQ0UI_7UTXChP`) **Email restriction**: Only `gmail.com` and `qq.com` accepted **Email verification**: Required for signup. Verification emails land in Gmail SPAM. ## API Endpoints ```bash # Instance meta (POST required) POST https://dc.hhhl.cc/api/meta Body: {} # Signup (requires Turnstile token) POST https://dc.hhhl.cc/api/signup Body: {"username":"...","password":"...","emailAddress":"...","turnstileResponse":"..."} # List users (public) POST https://dc.hhhl.cc/api/users Body: {} # Check user exists (ActivityPub) GET https://dc.hhhl.cc/@username Accept: application/activity+json # 404 = not found, 200 = exists ``` ## Signup Form Selectors (Misskey/Sharkey SPA) The form uses NO `name` attributes on inputs. Use type-based selectors: ``` Username: input[type="text"] Email: input[type="email"] Password: [REDACTED]"password"] (first) Confirm: input[type="password"] (second) ``` The form is inside a modal with hash `#mkmodal` or `#/signup`. The SPA uses Vue/React — native DOM events may not trigger framework state updates. Use `Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value').set.call(el, value)` + `dispatchEvent(new Event('input', {bubbles: true}))`. ## Turnstile Behavior 1. **Local Chrome (CDP)**: Turnstile loads in a separate Cloudflare challenge page/target. The widget may auto-solve after the checkbox is clicked. 2. **Browserbase (cloud browser)**: Turnstile iframe appears but has empty `src` — widget does NOT load. Cloudflare blocks cloud browser IPs. 3. **nodriver**: Works but may fail to connect to Chrome on Windows. Use explicit `browser_executable_path`. 4. **CDP direct (recommended)**: Most reliable. Launch Chrome with `--remote-debugging-port`, connect via WebSocket, use `Page.navigate`, `Runtime.evaluate`. ## Registration Flow 1. Navigate to `https://dc.hhhl.cc/` 2. Click "Присоединяйтесь к этому серверу" button 3. Check two checkboxes (terms agreement) 4. Click "Продолжить" (disabled until checkboxes checked) 5. Handle "Подтвердить" confirmation dialogs (up to 2) 6. Fill signup form: username, email, password ×2 7. Solve Turnstile (manual or auto) 8. Click "Начать" submit button 9. Check response: if URL changes from `#mkmodal` → success ## Silent Registration Failure **Critical**: The SPA may close the dialog and return to the main page WITHOUT an error message. This looks like success but the account is never created. Always verify: ```bash # Check if user exists (ActivityPub) curl -s -H "Accept: application/activity+json" "https://dc.hhhl.cc/@username" # 404 = registration failed silently # 200 = account exists # Better: Misskey API (JSON, more reliable) curl -s -X POST "https://dc.hhhl.cc/api/users/show" \ -H "Content-Type: application/json" \ -d '{"username":"username_here"}' # 200 + {"id":"..."} = account exists # 404 + {"error":{"code":"NO_SUCH_USER"}} = failed ``` **Pitfall: `#mkmodal` URL is NOT a success indicator** — The modal URL appears even when registration silently fails (Turnstile token rejected). The CDP script `autoreg_v4.py` used `#mkmodal` as a success check — all 10 accounts were fake (`NO_SUCH_USER`). Always verify via API, never trust the URL alone. **Pitfall: `#/signup` in URL = definitely failed** — if the page stays on the signup URL after submit, registration was rejected. ## Python 3.14 Incompatibility **`greenlet` C extension is broken on Python 3.14** — This affects `nodriver`, `patchright`, and `playwright`. All three fail with `ModuleNotFoundError: No module named 'greenlet._greenlet'`. **Fix**: Use Python 3.11 at `/c/Users/User/AppData/Local/Python/Python311/python.exe`. Working versions: - Python 3.11.9 ✅ (greenlet, patchright, playwright all work) - Python 3.13 ✅ (available but untested for Turnstile) - Python 3.14 ❌ (greenlet broken) ## Turnstile Bypass Attempts — All Failed on dc.hhhl.cc Cloudflare Turnstile on dc.hhhl.cc (sitekey `0x4AAAAAADUhQ0UI_7UTXChP`) blocks ALL automated approaches tested: | Approach | Result | Notes | |----------|--------|-------| | **CDP (Chrome DevTools Protocol)** | ❌ Blocked | Turnstile never auto-solves, submit button stays disabled | | **Browserbase (cloud browser)** | ❌ Blocked | Turnstile iframe has empty `src` — widget doesn't load | | **patchright (Playwright anti-detection)** | ❌ Blocked | Turnstile checkbox clickable but never auto-solves | | **Theyka Turnstile-Solver** | ❌ Domain mismatch | Solver generates token on localhost — token invalid for dc.hhhl.cc | **Only working path**: manual Turnstile solve in a real browser, then extract the `cf-turnstile-response` token from the DOM. Or use a paid service (capsolver, 2captcha) that can generate domain-valid tokens. **Turnstile-Solver domain-binding note**: The Theyka solver (`github.com/Theyka/Turnstile-Solver`) uses `page.route()` to intercept the target URL and serve a local page with the Turnstile widget. The token is generated for `localhost`, not the target domain. Cloudflare rejects it. This pattern is NOT suitable for actual site registration — only for testing the solver itself. ## Vue/Sharkey Form Interaction Pattern The Sharkey (Misskey fork) SPA uses Vue.js. Standard Playwright/CDP clicks on checkboxes and buttons are silently ignored by the Vue reactivity system. **JS evaluation + dispatchEvent is required**: ```javascript // Check checkboxes and click Continue (all in one JS call) (() => { const cbs = document.querySelectorAll('input[type="checkbox"]'); cbs.forEach(cb => { cb.checked = true; cb.dispatchEvent(new Event('change', {bubbles: true})); cb.dispatchEvent(new Event('input', {bubbles: true})); }); const btns = document.querySelectorAll('button'); for (const btn of btns) { if (btn.textContent.includes('Продолжить')) { btn.disabled = false; btn.click(); return 'clicked'; } } })() ``` **Form filling also needs JS bypass**: ```javascript const el = document.querySelector('input[name="username"]'); el.focus(); el.value = 'my_username'; el.dispatchEvent(new Event('input', {bubbles: true})); ``` **Turnstile checkbox click** (inside cross-origin iframe): ```javascript const frames = document.querySelectorAll('iframe'); for (const frame of frames) { try { const doc = frame.contentDocument || frame.contentWindow.document; const cb = doc.querySelector('input[type="checkbox"]'); if (cb) { cb.click(); break; } } catch(e) {} } ``` ## Verification Email Problem Short-term Gmail via AnyMessage cannot read spam folder. Verification emails from Misskey go to Gmail spam. Solutions: - Use long-term email with IMAP (but AnyMessage long-term has no Gmail) - Use external Gmail PVA service with IMAP/app-password - Check spam via `[REDACTED-NAME]@gmail.com` IMAP (if forwarded) ## CDP Launch Pattern ```python CHROME = r"C:\Program Files\Google\Chrome\Application\chrome.exe" CDP_PORT = 9225 # Kill existing Chrome on port first subprocess.run(f'for /f "tokens=5" %a in (\'netstat -ano ^| findstr :{CDP_PORT}\') do taskkill /F /PID %a', shell=True, capture_output=True, timeout=5) subprocess.run('taskkill /F /IM chrome.exe 2>nul', shell=True, capture_output=True, timeout=5) time.sleep(2) cmd = [ CHROME, f"--remote-debugging-port={CDP_PORT}", f"--user-data-dir={profile_dir}", "--no-sandbox", "--disable-gpu", "--no-first-run", "--no-default-browser-check", "--no-restore-last-session", # CRITICAL: prevents Chrome from restoring previous tabs "--disable-blink-features=AutomationControlled", "--window-size=1280,900", "--new-window", "about:blank", ] proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(4) ``` ### CRITICAL: Browser-level vs Page-level WebSocket When connecting to CDP, there are TWO WebSocket endpoints: - **Browser-level** (`/json/version` → `webSocketDebuggerUrl`): Only supports `Target.*` methods (createTarget, closeTarget, etc.) - **Page-level** (`/json` → page's `webSocketDebuggerUrl`): Supports `Page.*`, `Runtime.*`, `DOM.*` **Calling `Page.enable` on the browser-level WS returns `"'Page.enable' wasn't found"` (error -32601).** Correct pattern: ```python # 1. Connect to browser-level WS browser_ws = GET /json/version → data["webSocketDebuggerUrl"] browser_cdp = connect(browser_ws) # 2. Create a new tab (browser-level) result = await browser_cdp.cmd("Target.createTarget", {"url": "about:blank"}) target_id = result["targetId"] # 3. Close browser connection, open page connection await browser_cdp.close() # 4. Find the new page's WS URL pages = GET /json page_ws = next(p["webSocketDebuggerUrl"] for p in pages if p["id"] == target_id) # 5. Connect to page-level WS — NOW you can call Page.enable, Runtime.enable page_cdp = connect(page_ws) await page_cdp.cmd("Page.enable") await page_cdp.cmd("Runtime.enable") ``` ### Chrome `--no-restore-last-session` Flag Without this flag, Chrome restores the previous session's tabs and ignores the URL argument. The browser opens on a chrome-extension page or previous session's tabs instead of the intended URL. This caused `manual_turnstile.py` to hang on `chrome-extension://nkeimhogjdpnpccoofpliimaahmaaome/background` for 180 seconds. ### Turnstile Token Polling + form.submit() Pattern After the user solves Turnstile manually, the token appears in `input[name="cf-turnstile-response"]`. Poll at 1-second intervals (not 2s — faster detection reduces token expiry risk): ```python token = None t0 = time.time() while time.time() - t0 < 180: token = await cdp.js(""" (function(){ var el=document.querySelector('input[name="cf-turnstile-response"]'); if(el&&el.value&&el.value.length>20)return el.value; return ''; })() """) if isinstance(token, str) and len(token) > 20: break await asyncio.sleep(1) # 1s polling # IMMEDIATE submit via form.submit() — faster than button.click() await cdp.js(""" (function(){ var forms = document.querySelectorAll('form'); for (var i = 0; i < forms.length; i++) { if (forms[i].querySelector('input[name="username"]')) { forms[i].submit(); return 'submitted'; } } // Fallback var btns = document.querySelectorAll('button'); for (var i = 0; i < btns.length; i++) { if (btns[i].textContent.includes('Начать')) { btns[i].click(); return 'clicked'; } } return 'not found'; })() """) ``` **Why `form.submit()` over `button.click()`**: The button click triggers Vue/React event handlers which may add delays. `form.submit()` sends the form data directly, reducing the window between token detection and server submission. This matters because the Turnstile token may have a short validity window. ### API Verification with Retries Misskey is slow to index new users. `POST /api/users/show` may return `NO_SUCH_USER` for 10-30 seconds after successful registration. Retry pattern: ```python for attempt in range(8): ok, info = verify_api(username) if ok: break await asyncio.sleep(3) # 3s between retries ``` ## Gmail IMAP UTF-7 Folder Names (Russian) Russian Gmail accounts use UTF-7 encoded folder names: - Spam: `[Gmail]/&BCEEPwQwBDw-` (= "Спам") - All Mail: `[Gmail]/&BBIEQQRP- &BD8EPgRHBEIEMA-` (= "Вся почта") - Sent: `[Gmail]/&BB4EQgQ,BEAEMAQyBDsENQQ9BD0ESwQ1-` (= "Отправленные") - Trash: `[Gmail]/&BBoEPgRABDcEOAQ9BDA-` (= "Корзина") Use `mail.list()` to discover actual folder names, then use the encoded names in `mail.select()`.