nx_cam_chaturbate.nx
buildroot/runtime/nx_cam_chaturbate.nx
about
nx_cam_chaturbate.nx -- /compare/mediaingest R9 (lr_watch) SITE ADAPTER: Chaturbate.
WHY THIS EXISTS, MEASURED 2026-09-01 -- not recalled, and it corrects the record.
The retired Python detector whose output sits in ~/.ctbrec/logs tried 8 regex patterns, 4
JS-object patterns and one API endpoint against the ROOM PAGE, then reported:
"Room Status: PRIVATE (found 'private show') / Streams Found: 0 / DETECTION FAILED"
"3. JavaScript rendering required ... Consider using Selenium/WebDriver"
Re-reading the HTML that same run SAVED (page_dj_woman_20251029_110425.html, 184,898 B)
refutes every part of that conclusion:
- the body carries next="/?next=/dj_woman/" plus roomlist / endless_page markers, i.e. the
request was BOUNCED TO THE LANDING PAGE and the room page was never retrieved at all;
- the page <title> is the generic Chaturbate homepage title, not a room title;
- the literal "private show" it reports matching is homepage boilerplate (3 hits, none a room
status field), so the PRIVATE verdict was read off the wrong document.
ALL THIRTEEN "pattern misses" therefore have ONE cause, and it is not JavaScript. A check that
ran on the wrong subject produced a confident, complete-looking false negative, and its remedy
("render JavaScript") would have bought a browser engine to fix a fetch that never landed.
INDEPENDENT SECOND WITNESS, found in our own tree: nx_http_client.nx's build_request_cookie
comment already says "chaturbate's /?next= age-gate hands back csrftoken/sbr/AG_Key that must be
replayed to reach the room -- without a jar we land on the gate page forever."
THE REAL MECHANISM needs no room page and no browser. Two endpoints:
1. STATUS IN BULK -- GET /affiliates/api/onlinerooms/?format=json returns EVERY online room in
ONE request. This is the rate-limit answer: N watched performers cost 1 call, not N. Paying
one ajax per performer across a large fleet is precisely what flags an exit IP into a
site-wide 403, so bulk is not an optimisation here, it is the correctness path.
2. URL RESOLVE -- POST /get_edge_hls_url_ajax/ (application/x-www-form-urlencoded,
room_slug=<name>&bandwidth=high, X-Requested-With: XMLHttpRequest) -> JSON carrying
room_status, url and cmaf_edge.
*** THE EDGE TOKEN IN THAT url IS SINGLE-USE / CONNECTION-BOUND. ***
ANY fetch of the tokenised playlist -- even a HEAD, even a "quick validity check" -- CONSUMES it,
and the next consumer gets HTTP 403. So this organ RESOLVES AND RETURNS; it never probes, never
validates and never pre-fetches the URL it hands back. That is not style: our own nx_hls_get
fetches the MASTER playlist and THEN fetches the variant media playlist, which spends the token
on the first fetch and 403s on the second. cb_capture_contract() states the handoff rule in the
return value rather than in a comment, because a rule that lives only in a comment is invisible
to every caller -- the same lesson nx_https_client.nx's NOT_IMPLEMENTED verdict records.
EGRESS ETHICS (standing estate bound, /compare/mediaingest R12): sovereign-owned egress only.
dependencies 2 imports · 2 importers
imports: nx_syscalls.nxnx_paced_fetch.nx
imported by: nx_cam_chaturbate_gate.nxnx_cam_chaturbate_probe.nx
structs
| none |
consts
| 56 | const CB_UNKNOWN: i64 = 0 |
| 57 | const CB_PUBLIC: i64 = 1 |
| 58 | const CB_PRIVATE: i64 = 2 |
| 59 | const CB_HIDDEN: i64 = 3 |
| 60 | const CB_OFFLINE: i64 = 4 |
| 61 | const CB_RESTRICTED: i64 = 5 // room says PUBLIC but the ajax returned an EMPTY url -> geo/age-gated for this exit |
| 62 | const CB_RATELIMIT: i64 = 6 // 429 / 5xx / 403-challenge -> BACK OFF, never fast-retry |
| 63 | const CB_NOTEXIST: i64 = 7 // 404 |
| 64 | const CB_WALL: i64 = 8 // challenge body, classified by nx_antibot in the capture path (R13) |
| 65 | const CB_STATE_N: i64 = 9 |
| 67 | const CB_HTTP_OK: i64 = 200 |
| 68 | const CB_HTTP_FORBIDDEN: i64 = 403 |
| 69 | const CB_HTTP_NOTFOUND: i64 = 404 |
| 70 | const CB_HTTP_TOOMANY: i64 = 429 |
| 71 | const CB_HTTP_SERVER_MIN: i64 = 500 |
| 74 | const CB_HANDOFF_SINGLE_CONSUMER: i64 = 1 |
| 76 | const CB_CH_QUOTE: i64 = 34 |
| 77 | const CB_CH_COLON: i64 = 58 |
| 78 | const CB_CH_SPACE: i64 = 32 |
| 79 | const CB_CH_TAB: i64 = 9 |
| 80 | const CB_CH_DIGIT0: i64 = 48 |
| 81 | const CB_CH_UPPER_A: i64 = 65 |
| 82 | const CB_CH_UPPER_Z: i64 = 90 |
| 83 | const CB_CH_CASE_DELTA: i64 = 32 |
| 85 | const CB_NUMBUF_BYTES: i64 = 32 |
functions
| 87 | func cb_slen(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } |
| 88 | func cb_w(s: *u8) -> i64 { sys_write(1, s, cb_slen(s)); return 0 } |
| 89 | func cb_wb(b: *u8, n: i64) -> i64 { sys_write(1, b, n); return 0 } calls 1: sys_write |
| 90 | func cb_wn(v: i64) -> i64 |
| 102 | func cb_state_name(st: i64) -> *u8 called by 1: main |
| 114 | func cb_state_is_valid(st: i64) -> i64 |
| 123 | func cb_is_recordable(st: i64) -> i64 { if st == CB_PUBLIC { return 1 } return 0 } called by 1: main |
| 128 | func cb_should_backoff(st: i64) -> i64 called by 1: main |
| 137 | func cb_capture_contract() -> i64 { return CB_HANDOFF_SINGLE_CONSUMER } |
| 139 | func cb_lc(c: i64) -> i64 called by 1: cb_ieq |
| 145 | func cb_ieq(buf: *u8, off: i64, n: i64, lit: *u8) -> i64 |
| 161 | func cb_decide(http_status: i64, rs: *u8, rs_off: i64, rs_len: i64, url_len: i64) -> i64 |
| 183 | func cb_field_from(buf: *u8, n: i64, key: *u8, from: i64, off_out: *i64) -> i64 |
| 246 | func cb_flag(buf: *u8, n: i64, key: *u8) -> i64 |
| 288 | func cb_find(buf: *u8, n: i64, lit: *u8, from: i64) -> i64 |
| 305 | func cb_rfind(buf: *u8, n: i64, lit: *u8) -> i64 |
| 323 | func cb_apps(dst: *u8, k0: i64, src: *u8) -> i64 |
| 331 | func cb_appn(dst: *u8, k0: i64, src: *u8, n: i64) -> i64 called by 1: cb_cmaf_rewrite |
| 345 | func cb_cmaf_rewrite(src: *u8, srclen: i64, out: *u8, outcap: i64) -> i64 |
| 374 | func cb_field(buf: *u8, n: i64, key: *u8, off_out: *i64) -> i64 |
| 383 | func cb_state_of_show(rs: *u8, off: i64, len: i64) -> i64 |
| 404 | func cb_record_start(buf: *u8, n: i64, at: i64) -> i64 called by 1: cb_bulk_lookup |
| 414 | func cb_record_end(buf: *u8, n: i64, at: i64) -> i64 called by 1: cb_bulk_lookup |
| 424 | func cb_bulk_count(buf: *u8, n: i64) -> i64 |
| 445 | func cb_bulk_lookup(buf: *u8, n: i64, user: *u8, off_out: *i64) -> i64 |
| 478 | func cb_build_body(user: *u8, out: *u8, outcap: i64) -> i64 |