162 lines
7.4 KiB
Markdown
162 lines
7.4 KiB
Markdown
# Camoufox Registration Patterns (2026-08-10)
|
|
|
|
## OctoCaptcha Form Validation (`_wait_form_valid`)
|
|
|
|
GitHub signup has two independent gates keeping the submit button disabled:
|
|
1. Field auto-checks (email validity, username, password)
|
|
2. OctoCaptcha (DataDome iframe) — hidden `octocaptcha-token` stays empty until verification finishes
|
|
|
|
Must wait for BOTH before clicking submit. JS evaluation to probe state:
|
|
|
|
```python
|
|
def wait_form_valid(page, timeout_s=60):
|
|
"""Wait for GitHub's async field validation AND octocaptcha to resolve."""
|
|
deadline = time.time() + timeout_s
|
|
while time.time() < deadline:
|
|
state = page.evaluate("""() => {
|
|
const out = {email_taken: false, errors: [], pending: false,
|
|
fields_valid: false, captcha_pending: false,
|
|
submit_enabled: false};
|
|
const fields = [
|
|
['email', 'input[name="user[email]"], #email'],
|
|
['username', 'input[name="user[login]"], #login'],
|
|
['password', 'input[name="user[password]"], #password'],
|
|
];
|
|
let successCount = 0, total = 0;
|
|
for (const [label, sel] of fields) {
|
|
const el = document.querySelector(sel);
|
|
if (!el) continue;
|
|
total += 1;
|
|
if (el.getAttribute('aria-invalid') === 'true') {
|
|
const container = el.closest('auto-check') || el.parentElement;
|
|
const errEl = container ? container.querySelector('.error') : null;
|
|
const txt = errEl ? errEl.textContent.trim().replace(/\\s+/g,' ') : (label + ' invalid');
|
|
out.errors.push(label + ': ' + txt.slice(0, 160));
|
|
if (/already associated|already been taken|already taken/i.test(txt))
|
|
out.email_taken = true;
|
|
} else if (el.classList.contains('is-autocheck-successful')) {
|
|
successCount += 1;
|
|
}
|
|
if (el.classList.contains('is-autocheck-in-progress')) out.pending = true;
|
|
}
|
|
out.fields_valid = (total > 0 && successCount === total && out.errors.length === 0);
|
|
const tok = document.querySelector('input[name="octocaptcha-token"], .js-octocaptcha-token');
|
|
const tokenVal = tok ? (tok.value || '').trim() : '';
|
|
const spinner = document.querySelector('.js-octocaptcha-spinner');
|
|
const success = document.querySelector('.js-octocaptcha-success');
|
|
out.captcha_pending = (!!tok && !tokenVal && !success.classList.contains('d-none') && !!spinner && !spinner.classList.contains('d-none'));
|
|
const email = document.querySelector('input[name="user[email]"], #email');
|
|
const form = email ? email.closest('form') : null;
|
|
if (form) {
|
|
const b = Array.from(form.querySelectorAll('button, input[type=submit]'))
|
|
.find(x => (x.getAttribute('name')==='commit') ||
|
|
/create account/i.test(x.textContent||x.value||''));
|
|
if (b) out.submit_enabled = !b.disabled;
|
|
}
|
|
return out;
|
|
}""")
|
|
if state.get("submit_enabled") and not state.get("pending"):
|
|
return {"ready": True}
|
|
time.sleep(1)
|
|
return {"ready": False, "timeout": True}
|
|
```
|
|
|
|
## Proper Click (NOT social buttons)
|
|
|
|
GitHub's signup page has BOTH email-signup submit buttons AND social-login buttons ("Continue with Google/Apple"). A naive `page.click("button:has-text('Create account')")` may click the wrong button.
|
|
|
|
Use JS evaluation to find the right button:
|
|
|
|
```python
|
|
page.evaluate("""() => {
|
|
const email = document.querySelector('input[name="user[email]"], #email');
|
|
const form = email.closest('form');
|
|
const act = (form.getAttribute('action') || '').toLowerCase();
|
|
// Skip social/OAuth forms
|
|
if (act.includes('/sessions/social/') || act.includes('oauth')) return 'social_form';
|
|
const btns = Array.from(form.querySelectorAll('button, input[type=submit]'));
|
|
const good = btns.find(b => {
|
|
const t = (b.textContent || b.value || '').toLowerCase();
|
|
const prov = b.getAttribute('data-provider') || '';
|
|
if (prov) return false; // skip social buttons
|
|
return t.includes('create') || t.includes('account') || b.getAttribute('name') === 'commit';
|
|
});
|
|
if (good) { good.click(); return 'clicked'; }
|
|
return 'no_button';
|
|
}""")
|
|
```
|
|
|
|
## Navigation Wait (post-submit)
|
|
|
|
After clicking submit, the page navigates from `/signup` to `/account_verifications`. The navigation can be slow via proxies (30+ seconds). Instead of polling `page.url` (which is a live property — see pitfall), use `page.wait_for_selector`:
|
|
|
|
```python
|
|
try:
|
|
page.wait_for_selector('input[name="launch_code[]"]', state="visible", timeout=90000)
|
|
# On verification page
|
|
except:
|
|
# Check for rate limit or unexpected redirect
|
|
body = page.inner_text("body")[:500]
|
|
if "rate limit" in body.lower():
|
|
return "rate_limited"
|
|
```
|
|
|
|
## Camoufox Launch Options
|
|
|
|
```python
|
|
launch_opts = {"headless": False, "geoip": True}
|
|
if proxy_config:
|
|
launch_opts["proxy"] = {
|
|
"server": proxy["server"],
|
|
"username": proxy["username"],
|
|
"password": proxy["password"],
|
|
}
|
|
with Camoufox(**launch_opts) as browser:
|
|
...
|
|
```
|
|
|
|
`geoip=True` is recommended when using proxies to avoid `LeakWarning`.
|
|
|
|
## Mail.tm API
|
|
|
|
```python
|
|
base = "https://api.mail.tm"
|
|
# Create account
|
|
r = requests.post(f"{base}/accounts", json={"address": f"{username}@{domain}", "password": pw})
|
|
token = [REDACTED]"{base}/token", json={"address": email, "password": pw}).json()["token"]
|
|
# Poll inbox
|
|
headers = {"Authorization": f"Bearer {token}"}
|
|
r = requests.get(f"{base}/messages", headers=headers)
|
|
```
|
|
|
|
## Pitfalls from this session
|
|
|
|
### `page.url` is a LIVE property
|
|
`page.url` re-evaluates every time it's accessed. Between `log(f"URL: {page.url}")` and `if "account_verif" in page.url:`, the page can navigate.
|
|
**Fix:** read once into a local variable.
|
|
|
|
### Post-OTP "accepted" without session
|
|
After OTP, GitHub may redirect to root path (`/` or `/dashboard`) making it look like success, but the account actually needs a post-signup login.
|
|
**Fix:** after "accepted" result, navigate to `/settings/profile`. If redirected to `/login`, do the post-signup login.
|
|
|
|
### Rate limit persistence
|
|
GitHub secondary rate limits can persist for **hours**, not minutes. A 15-minute wait is often insufficient. Rotate IPs aggressively.
|
|
|
|
### Proxy pool dead (2026-08-10)
|
|
bpproxy.at residential pool — ALL 10 proxies dead. curl returns empty response, Camoufox returns `NS_ERROR_PROXY_CONNECTION_REFUSED`. Proxies are not rate-limited — they are physically unreachable. Always verify proxy alive before use:
|
|
```bash
|
|
curl -sI --max-time 10 -x <proxy_url> https://github.com | head -1
|
|
```
|
|
If no `HTTP/1.1 200` response, proxy is dead — don't use it.
|
|
|
|
### Camoufox proxy config format
|
|
Playwright REQUIRES credentials in separate `username`/`password` keys:
|
|
```python
|
|
from core.proxy_utils import playwright_proxy_config
|
|
# Input: "http://user:pass@host:1000"
|
|
# Output: {"server": "http://host:1000", "username": "user", "password": "pass"}
|
|
```
|
|
Embedding credentials in the `server` field yields `NS_ERROR_PROXY_CONNECTION_REFUSED`.
|
|
|
|
### mail.tm domains blocked (2026-08-10)
|
|
GitHub silently rejects accounts registered with mail.tm domains (`emalupe.com`). OTP is accepted, but account returns 404 on API check. Use t-online.de IMAP pool instead. |