83 lines
4.7 KiB
Markdown
83 lines
4.7 KiB
Markdown
# Apodex consumer bridge — pool-of-farmed-accounts → OpenAI API (2026-10-03)
|
|
|
|
New chat2api variant: NOT one subscription + one token, but a **pool of 100 free
|
|
accounts** (each with $20 welcome credits) behind one OpenAI-compatible front.
|
|
Live-verified: `apodex_bridge.py` on :8420, standard ~11s/6cr, pro deep-research
|
|
~293s/54cr. Full Apodex specifics: skill `apodex-api` → `references/web-chat-stream-bridge-2026-10-03.md`.
|
|
|
|
## Pattern (generalizable to any web-chat with account farms)
|
|
|
|
```
|
|
OpenAI client → bridge :8420 (stdlib http.server, ThreadingHTTPServer)
|
|
→ Pool(accounts.json): acquire/release per request, thread-safe
|
|
→ replay web app's internal SSE endpoint with Bearer consumer token
|
|
→ final_answer event → OpenAI chat.completion (stream emulated as 3 SSE chunks)
|
|
```
|
|
|
|
Key design points that proved necessary:
|
|
1. **Rotate accounts per request, not per failure only.** The web endpoint 429s on
|
|
rapid same-account/same-IP use. Bridge marks 401/403 → dead, 429 → rotate + sleep,
|
|
max N tries with an excluded-set so the same dead account isn't retried.
|
|
2. **Token TTL ≈ 1h and refresh_token also dies** (single-use / short-lived on many
|
|
platforms). Don't build on refresh — build a **revival worker** (`bridge_reauth.py`):
|
|
re-login the SAME email via passwordless OTP (proxy-rotated send-code), credits
|
|
persist. Bridge **hot-reloads the pool file by mtime** so revival works while serving.
|
|
3. Balance gate: check `/api/vip/info` every ~40min per account, retire below floor.
|
|
4. stdlib-only server = zero deps, survives any venv mess; `-u` for unbuffered logs.
|
|
|
|
## Endpoint discovery when bundles don't reveal it
|
|
|
|
Next.js SPA lazy-chunks hid the chat client; grepping downloaded bundles failed.
|
|
What worked (reusable):
|
|
1. Mine storage keys from the bundles you DO have: grep `localStorage.setItem`,
|
|
`SIGNED_IN_COOKIE`, persist-store names (zustand → `mirage-auth-store`).
|
|
2. Playwright headless: goto site → **seed the persist store** with your API-obtained
|
|
token: `{"state":{"token":<jwt>,"expiresAt":<ms>,"refreshToken":null},"version":0}`
|
|
+ set the signed-in cookie.
|
|
3. Reload the app route → attach `page.on('request')`/`page.on('response')` →
|
|
type a message in the UI → the real internal endpoint (method, headers, exact
|
|
JSON body incl. client-generated uuids) lands in the capture log.
|
|
4. Replay from urllib — no browser needed afterwards.
|
|
Wrong seed key = app silently treats you as logged out (redirect to marketing page).
|
|
|
|
## Pitfalls (new this session)
|
|
|
|
### Zombie server on the port answering with OLD code
|
|
Symptom: fresh code works when called **in-process** (`import bridge; run_completion(...)`)
|
|
but HTTP responses come back in ~1s with pre-patch behaviour. Cause: TWO listeners on
|
|
:8420 (old process survived a "restart"), OS hands the connection to either.
|
|
Fix: `netstat -ano | grep :PORT` → kill **every** PID listed → verify port free →
|
|
start ONE server. Always diff "in-process works but HTTP doesn't" as a stale-server
|
|
signal, not a code bug.
|
|
|
|
### taskkill /F mangled by git-bash (MSYS)
|
|
`taskkill //F //PID n` → "Invalid argument/option - '//F'". MSYS path conversion eats
|
|
the flags. Reliable kill from this environment:
|
|
```python
|
|
subprocess.run(['taskkill','/F','/PID',pid], capture_output=True, text=True)
|
|
```
|
|
(via python one-liner in terminal). Also `powershell Get-Process | Format-Table`
|
|
can hang >60s — prefer netstat+python kill.
|
|
|
|
### Publishing a repo that has secrets in GIT HISTORY
|
|
Flipping an existing private repo to public exposes secrets in old commits even if
|
|
HEAD is sanitized. This session: IMAP app-password was committed early.
|
|
Correct flow: create a **NEW public repo** with a single clean commit (Contents API
|
|
PUTs on an empty repo work once; git-data blobs/tree may 409 "Repository is empty" —
|
|
just use Contents API sequentially). Before publishing, sweep every blob **downloaded
|
|
from the repo** (not local files) with a regex for creds; rotate any secret that was
|
|
ever committed.
|
|
|
|
### Sanitization must include indirect leaks
|
|
Hardcoded mailbox + app-password appeared in 6 scripts, not just the main one.
|
|
Sweep ALL repo-bound files for `email|password|sk-` patterns, replace with
|
|
`os.environ.get('X', 'placeholder')`, then `py_compile` every file — and check
|
|
`import os` is actually present (py_compile does NOT catch NameError-at-runtime).
|
|
|
|
## Files (local + public repo ArthurMonteiro08586/apodex-credit-farm-public)
|
|
|
|
- `apodex_bridge.py` — OpenAI front + pool (models `apodex-web`, `apodex-web-pro`)
|
|
- `bridge_reauth.py` — OTP revival worker, proxy-rotated, atomic pool rewrite
|
|
- `capture_chat.py` — Playwright localStorage-seed + request capture
|
|
- `test_stream.py` — raw SSE dump of one query (event census)
|