Files
tubular/docs/wco-feasibility.md

122 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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=<query>` 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