autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user