Paginate tag (and ASC) lists via GQL with integrity retry; mint follow integrity from a WebView because Dio /integrity tokens are bad_bot. Co-authored-by: Cursor <cursoragent@cursor.com>
61 lines
3.6 KiB
Markdown
61 lines
3.6 KiB
Markdown
# Frosty fork — Twitch ad blocking (DESIGN)
|
||
|
||
Personal Forgejo fork of [tommyxchow/frosty](https://github.com/tommyxchow/frosty) that adds **TwitchAdSolutions (vaft)**-style live ad blocking.
|
||
|
||
Canonical living plan: session `active-plan.md` for conversation `37b527bf-e540-4753-be1f-39906fe2ba28`. ADR: **ADR-026**.
|
||
|
||
## Problem
|
||
|
||
Upstream Frosty plays Twitch HLS (native) or `player.twitch.tv` (WebView). Twitch may stitch ad pods into the media playlist (`stitched` / `EXT-X-DATERANGE`). Upstream only **detects** native ad breaks (to avoid bogus latency recovery); it does **not** block ads. Turbo/sub `hide_ads` via linked web session is separate and not a client blocker.
|
||
|
||
## Approach (vaft-equivalent)
|
||
|
||
Browser vaft hooks `Worker`/`fetch` and rewrites m3u8. Frosty’s native player cannot do that from Dart (`loadUrl` + headers only).
|
||
|
||
| Layer | Behavior |
|
||
| --- | --- |
|
||
| Primary token | GQL `PlaybackAccessToken` with `playerType=popout` (vaft `ForceAccessTokenPlayerType`) when Settings `blockTwitchAds` is on |
|
||
| Ad break | Native `adBreakStream` → probe backup types `embed` → `popout` → `autoplay`; accept first Usher multivariant whose sampled media playlist lacks `stitched`; `loadUrl` that URL |
|
||
| Ad end | Restore primary popout Usher URL |
|
||
| WebView | Best-effort inject of vendored `assets/js/vaft.user.js` (not true document-start on Android) |
|
||
| UI | Settings toggle (default on; enabling forces native player); overlay “Blocking ads” |
|
||
|
||
## Key code
|
||
|
||
- `lib/apis/twitch_ad_solutions.dart` — constants + `TwitchAdFreePlayback`
|
||
- `lib/apis/twitch_gql_api.dart` — `playerType` / `platform`, `fetchPlaylistText`
|
||
- `lib/screens/channel/video/native_video_store.dart` — load + swap
|
||
- `lib/screens/channel/video/video_store.dart` — WebView inject
|
||
- `lib/screens/settings/stores/settings_store.dart` — `blockTwitchAds`
|
||
- `assets/js/vaft.user.js` — upstream TwitchAdSolutions script
|
||
|
||
## Non-goals (v1)
|
||
|
||
- Upstream PR
|
||
- Full Worker prototype cleaning
|
||
- Third-party proxies
|
||
- HLS segment stripping inside `better_native_video_player` (unless smoke tests demand it)
|
||
|
||
## Tags + viewer sort (v5.2.2+)
|
||
|
||
Helix cannot filter by freeform tags or sort ascending. GQL covers both:
|
||
|
||
- Search: `SearchFreeformTags` → tap → `TagStreams` (`streams` + `freeformTags`)
|
||
- Sort: Top (asc), category (`game.streams`), tag — `VIEWER_COUNT` / `VIEWER_COUNT_ASC` via `StreamSortButton`
|
||
|
||
Key code: `lib/apis/twitch_gql_api.dart`, `lib/screens/home/search/`, `lib/widgets/stream_sort_button.dart`, `ListStore` / `ListType.tag`.
|
||
|
||
## Stream languages (v5.2.4+)
|
||
|
||
Settings → General → Stream languages (multi-select chips). Default `de`. Applied to Top, category, and tag lists.
|
||
|
||
- Helix `language=` when one language; multi-language / tags via Helix over-fetch + client filter
|
||
- Viewer sort is persisted (`streamViewerSort`)
|
||
- **Tag lists (asc + desc):** GQL `streams` with `freeformTags` + `sort` + cursor. Page-2+ uses Client-Integrity (same mint/retry as follow via `UserStore.runWithGqlIntegrity`). Helix + client tag filter only if integrity fails / no web session.
|
||
- **Follow / integrity:** Dio `POST /integrity` is almost always `is_bad_bot`. Follow retries via `IntegrityWebViewDialog` (capture `Client-Integrity` + `X-Device-Id` from Twitch's own WebView GQL traffic), then `UserStore.applyWebIntegrity`.
|
||
- **Top / category most viewers (desc):** Helix directory order (paginates)
|
||
- **Top / category fewest (asc):** GQL `VIEWER_COUNT_ASC` with the same integrity pagination; Helix fill + client re-sort as fallback
|
||
|
||
## Build
|
||
|
||
Flutter **3.47.0** (pinned in `pubspec.yaml`). Local SDK: `/home/yuna/.local/flutter-3.47.0`.
|