Files
autoreg-skills-max/skills/bulk-account-registration/references/misskey-sharkey-registration.md

12 KiB
Raw Permalink Blame History

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

# 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:

# 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:

// 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:

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):

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

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:

# 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):

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:

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().