diff --git a/docs/wco-feasibility.md b/docs/wco-feasibility.md new file mode 100644 index 0000000..59cd4f3 --- /dev/null +++ b/docs/wco-feasibility.md @@ -0,0 +1,121 @@ +# WCO feasibility investigation + +Observed **2026-10-20 UTC**. Scope: adding Western-cartoon browsing and native playback to Tubular, using ordinary HTTPS without browser automation, challenge/login/premium bypass, fabricated ad-verification results, or weakened TLS. No production implementation was added. + +## Decision + +**Browsing is feasible for the sampled catalog. Native video extraction and playback are not established.** The current declared free-player route reaches a JavaScript ad-verification step; investigation stopped before submitting that verification or requesting the post-gate player. Separately, the current Tubular/Terebi source and progress contracts reject an honest WCO identity. + +Do not build another full browser UI or weaken Terebi's network policy on this evidence alone. The missing prerequisite is a legitimately obtained final media URL, its required headers, and a successful native decode/seek probe. Nothing below demonstrates that WCO cannot ever support a native client. + +Evidence labels: + +- **Observed:** live requests or an executed check in this investigation. +- **Source:** current site markup, JavaScript, or implementation inspection. +- **[INFERENCE]:** architectural implication, not verified WCO playback. + +## Live native HTTP evidence + +A temporary Rust example used the project's existing `ureq 3.4.2` dependency with its `rustls` feature: default agent, TLS verification enabled, 25-second global timeout, `http_status_as_error(false)`, and zero automatic redirects. Responses were bounded to 512 KiB, or 4 KiB for the artwork signature check. No cookies, browser impersonation, proxy, script execution, or challenge solution was supplied. Player/initialization requests carried their referring page explicitly. + +| Resource | Transport | HTTP | Content-Type | Body bytes read | +| --- | --- | ---: | --- | ---: | +| [Cartoon directory][directory] | ureq/rustls | 200 | text/html | 311285 | +| [OTGW series][otgw] | ureq/rustls | 200 | text/html | 42840 | +| [Adventure Time series][adventure] | ureq/rustls | 200 | text/html | 148050 | +| [OTGW episode 1][episode] | ureq/rustls | 200 | text/html | 39154 | +| Episode's declared iframe, `embed.wcostream.com/inc/embed/index.php` | ureq/rustls | 200 | text/html | 9177 | +| [Declared player initialization][preinit] | ureq/rustls | 200 | application/javascript | 3871 | +| [OTGW cover][cover] | ureq/rustls | 200 | image/jpeg | 4096 | +| Same OTGW episode-1 URL | Python urllib | 403 | text/html | 5601 | +| Same OTGW episode-1 URL | curl | 403 | text/html | 5580 | + +**Observed:** the cover prefix has a JPEG signature. Full image decoding was not tested. Native successful page responses contained neither the Cloudflare challenge markers checked nor the explicit premium-only marker. Python's episode response contained Cloudflare's “Just a moment”; curl independently returned 403 HTML. Accessibility is transport-dependent; successful web-reader access alone would not have established local native access. + +## Smallest catalog integration + +**Observed/Source:** the directory and series metadata are server-rendered. No JavaScript execution is needed to read the sampled listings. + +1. `GET /cartoon-list`; parse `#ddmcc_container` title/series links. +2. Filter that Western-cartoon directory locally. The site's own directory search performs case-insensitive substring filtering, not another API request. +3. Fetch the selected series page for description, genres, cover reference, season labels, and `#episodeList a.dark-episode-item` entries. +4. Preserve the provider's actual playable-item labels and opaque page paths. + +The global search form declares **POST `/search`**, with `catara=` and `konuara=series` or `episodes`; the input declares a 40-character maximum. This is a markup contract, **not a successful live POST probe**. Local directory filtering avoids depending on that unverified endpoint for Western-cartoon title search. [Site form][otgw]; [reference search implementation][kodi-search]. + +Series identity should use the observed canonical provider path, not a slug generated from the display title or the numeric artwork filename. The directory contains historical paths such as `/anime/american-dad-season-1` for “American Dad!”. Long-term URL stability has not been established. [Directory][directory]. + +Artwork is available through `og:image` and `#sidebar_cat img.img5`; normalize scheme-relative references. Sidebar prose and genre links are available, but quality varies: Adventure Time's synopsis describes its pilot rather than the whole series. No episode runtime, publication date, or chapter timestamps were established. + +### Identity and ordering traps + +- OTGW has **eight page entries covering ten numbered segments**: 1–6 separately, 7–8 together, 9–10 together. These eight links were also extracted from the native response. +- Adventure Time season 1 labels one video “Episode 1 Slumber Party Panic / Trouble in Lumpy Space”; season 10 has a combined “Episode 13–16 Come Along with Me”. Pilot, specials, Distant Lands and Side Quests coexist on the series page. +- Map `data-season` through `#seasonFilter option[value]`; preserve the token and label. Adventure Time includes `s-1-0`, labeled “Special: Distant Lands”. +- Read the direct title span, not all anchor text: quality/finale badges are separate metadata. +- Preserve source order initially. It is mostly descending, but not consistently numerically sorted. +- Use an opaque episode-page identity, not series ID plus an assumed single positive episode number. Do not synthesize chapters, segment boundaries, or separate watched states from slash-separated titles. + +Sources: [OTGW][otgw], [Adventure Time][adventure]. The current Miruro identity cannot be reused unchanged: [`src/miruro.rs:28-77`](../src/miruro.rs). + +## Actual page-to-player boundary + +**Observed:** ordinary native requests reached this sequence: + +1. The episode HTML contains a statically encoded iframe string. Its visible script applies Base64 decoding, retains decimal digits, subtracts a page-supplied constant, then constructs characters. This transform was decoded as data; no downloaded JavaScript was executed. +2. The decoded iframe targets `embed.wcostream.com/inc/embed/index.php` with episode query parameters. +3. That iframe returns an **“Announcement”** document, not an MP4/HLS URL. Its close button is initially disabled, with a 10-second countdown and a 12-second fallback timer. +4. The document loads [`/inc/embed/pre-init.js?v2`][preinit]. That script generates a browser nonce, performs DOM ad-bait and script-loading checks, and sends an `/ad-verify` beacon containing the nonce, video ID and `clear`/`blocked` result. +5. Closing the announcement normally navigates to `video-js.php`, using `AdGate.buildUrl()` to append the nonce. The page also has a fallback navigation if `AdGate` is unavailable; that fallback was not requested to skip verification. + +**Boundary:** no `/ad-verify` request was submitted; no DOM results were fabricated, scripts executed, announcement flow skipped, alternative player endpoint substituted, premium workaround invoked, or post-gate player/media requested. Server-side enforcement of the nonce was **not tested**. A script containing a gate is not proof that every WCO stream is inaccessible to a native client. + +The [Kodi resolver][kodi-resolver] is reference code, not a proven current contract. It rewrites `index.php` to `video-js-new.php`, parses internal source JSON, resolves media redirects, and contains separate premium-workaround paths. Those behaviors were not transplanted or exercised. Its recent maintenance does not establish that this sample is playable through Tubular's permitted flow. + +**Unverified:** current source-JSON schema after the gate; final media type/codecs; required headers/cookies; redirect chain; URL expiration/IP binding; byte-range/seek support; nested HLS origins; subtitles; actual Terebi playback. No episode media was downloaded. + +## Actual Tubular/Terebi compatibility + +### Executed source-contract smoke + +A temporary Foundation-only Swift executable compiled **the actual** `../terebi/Sources/App/ResolvedSource.swift` unchanged. It validated a synthetic, otherwise valid source document with provider `miruro`, then changed only the provider to `wco` and required rejection. The synthetic example URL was not fetched. + +Observed output, exit 0: + +```text +Actual Terebi validator: Miruro control accepted; identical document with provider=wco rejected +``` + +This proves source-contract rejection, not anything about live media decoding. No player UI was launched. + +### Required integration changes if usable media is established + +- **Provider selection:** Tubular's chooser is Boolean/two-way, not a third-provider interface: `src/main.rs:3674-3698,3762-3774`. +- **Honest stable identity:** Tubular's `Identity` validation/key parsing and Terebi's source validator hardcode Miruro. WCO needs a provider-correct opaque playback-item identity that accommodates paired episodes and specials. Do not disguise WCO media as Miruro. `src/miruro.rs:28-77`; `../terebi/Sources/App/ResolvedSource.swift:84-102`. +- **Native admission and safety:** native progress identity validation accepts only canonical YouTube URLs or `miruro:`. Resolved-source security classification also uses `starts_with("miruro:")`. Updating Swift admission alone is insufficient and would not apply the protected resolved-source path. `../terebi/Sources/Core/progress_reporter.cpp:62-90`; `../terebi/Sources/Core/player.cpp:565-567,1460-1466`. +- **Progress/state:** native version-2 reporting emits a literal Miruro provider. Tubular sessions, snapshot validation, report admission and provider-specific collection filtering also need coordinated changes. Broadening the Miruro identity parser without updating its UI callers would mix WCO records into Miruro collections. `../terebi/Sources/Core/progress_reporter.cpp:163-186`; `src/video_state.rs:379-396,651-669`; `src/miruro_ui.rs:265-268,989-990,1938-1941`. +- **Reuse, not another player:** the existing source launcher already passes private source/progress files to Terebi. Resume storage is identity-keyed. No new playback framework is indicated. `src/youtube.rs:937-981`; `../terebi/Sources/App/TerebiApp.swift:516-524,1525-1530`; `../terebi/Sources/App/PlaybackProgress.swift:24-35,54-57`. + +**[INFERENCE] Direct MP4 should fit the general native media architecture.** Source-file validation is not HLS-only; native FFmpeg opens input without forcing a demuxer and discovers audio/video streams. The HLS-only rule belongs to Miruro's Sun projection, not the general source document. WCO must not be routed through that projection. `src/miruro.rs:101-178`; `../terebi/Sources/Core/player.cpp:543-547,565-580`. + +The existing protected network path rejects **all media redirects**, even same-origin redirects, and requires HLS variants/segments/keys/init resources to share the initial media origin. Preserve that policy. A resolver would need to obtain an already-final acceptable URL through a separately reviewed extraction flow; no need for redirect/cross-origin support has yet been established. `../terebi/Sources/Core/player.cpp:111-126,532-537`. + +The existing `resolved-playback-check` is not an unchanged subtitle-free WCO smoke: it requires a default subtitle and two cues. The full player suite also requires graphical/audio resources. Neither was run. See `../terebi/Checks/resolved-playback.swift:24-80` and `../terebi/README.md:178-190`. + +## Recommendation and limits + +- **Go:** a native WCO catalog is evidenced for the sampled directory, two shows, episode page and cover host. +- **Not yet go:** do not implement end-to-end WCO playback on the strength of catalog access or old extractor code. +- **Next prerequisite:** observe/document an authorized normal free-player route through the verification boundary, or obtain a supported native playback interface. Browser observation would need explicit permission and would not itself prove native extraction. Do not emulate successful ad-verification results or weaken security to create a passing demo. +- **Then:** resolve one real item, inspect only bounded media/manifest data with explicit headers, and verify native decode, duration and seek behavior before implementing browsing/state changes. Test a combined entry as well as a single episode before claiming identity correctness. + +Investigation changes are this report only; temporary HTTP/Swift probes and response files were removed. No application behavior, player source, dependency, remote state, or persistent stream token was changed. Findings are a point-in-time sample, not a service reliability or distribution-rights guarantee. + +[directory]: https://www.wco.tv/cartoon-list +[otgw]: https://www.wco.tv/anime/over-the-garden-wall +[adventure]: https://www.wco.tv/anime/adventure-time +[episode]: https://www.wco.tv/over-the-garden-wall-episode-1-the-old-grist-mill +[preinit]: https://embed.wcostream.com/inc/embed/pre-init.js?v2 +[cover]: https://images.wcostream.com/catimg/161158.jpg +[kodi-search]: https://github.com/christianhaitian/plugin.video.watchnixtoons2/blob/kodi19/lib/plugin.py#L1201-L1263 +[kodi-resolver]: https://github.com/christianhaitian/plugin.video.watchnixtoons2/blob/kodi19/lib/plugin.py#L1353-L1641