feat(feed): max video age filter, Members-only filter, membership badge fixes #49

Merged
365DevNet merged 0 commits from refs/pull/49/head into main 2026-08-03 21:27:13 +02:00
365DevNet commented 2026-08-03 21:25:18 +02:00 (Migrated from codeberg.org)

Summary

Adds two feed-filtering settings and repairs membership badge detection, which had silently stopped working against YouTube's current markup.

Started as "is the filter catching this members-only shelf?" — the answer was no, for three independent reasons, one of which turned out to be a live bug affecting an existing feature.

Fixes

Badge detection was dead. Labels now render in .ytBadgeShapeText. The code still looked for .yt-badge-shape__text, .badge-shape-wiz__text and [class*="badge-style-type"] — none of which match anything on the page any more — so Hide Members first videos was matching nothing at all. CHANGELOG.md:88 shows this happened once before: a .badge-shape-wiz__text guess logged as "best-effort; pending live confirmation" that was never confirmed. Current selectors added alongside the legacy ones.

Badge labels were English-only, unlike SIDEBAR_RULES, RELATED_RESULT_LABELS and parseViewCount, so the membership filters did nothing on a NL/DE/FR YouTube while neighbouring filters worked. Now cover all four. The two token lists are mutually exclusive so the settings cannot trigger on each other; the French tokens avoid the apostrophe in "Membres d'abord" because YouTube may render it as a typographic .

Added

  • Hide Members only videos (off by default). Members-only videos never become public, unlike Members-first, so this is a separate toggle rather than a widening of the existing one — which keeps its meaning for existing users.
  • Maximum video age — a preset dropdown (1/2/3/5/10 years, no day counts) that hides older videos on home, subscriptions and related, but never on search results, where hiding old hits would bury exactly what was asked for. Unknown ages (live, upcoming, not-yet-rendered) stay visible, and the comparison is strictly >, so a 5-year limit keeps "5 years ago" and drops "6 years ago".
  • The "Get more from memberships" promo shelf is hidden under Hide sponsored content. Its CTA is /channel_memberships, so the existing /premium brand-shelf rule never covered it, and its items are ytd-rich-grid-media rather than feed items, so the per-item filters could not reach them either.

Also

  • Both membership hints said the channel could be allowed "below", but the Trusted channels list lives in the Dashboard — they now name its location. The Dashboard copy claimed the allowlist only exempts from keyword and view-count filters; it now covers video age and both membership badges too.
  • Untrack .DS_Store. It was committed before .gitignore listed it, so the ignore rule never applied and it showed dirty on every checkout.

All three filters honour the Trusted channels allowlist.

Notes for review

  • Age is approximate by design. YouTube only reports "5 years ago", so a 5-year limit really keeps up to ~6 years. Stated in the hint text.
  • The age parser scans every number+word pair, not the first. "1.2M views • 3 years ago" would otherwise read 2m, fail to recognise the unit, and give up before reaching the date.
  • Localized badge strings are my best knowledge, not verified against a live page. Worth a spot-check on a NL/DE/FR YouTube — a wrong token fails closed (filter does nothing), it will not hide the wrong videos.
  • Single managed-hide key. An earlier pass used two keys on one element; setManagedHidden records fyOriginalDisplay from the live style, so a second key claiming an already-hidden element would store display: none as its "original" and the item would never come back. Collapsed to one membership-feed-item key.

Test plan

  • npm test — 24/24
  • jsdom harness against the live shelf markup: badge extraction, FEED_ITEM_SELECTOR reachability, :has() rule matches the shelf and not ordinary rows, regression guard confirming the old selectors match zero elements
  • Age parsing: 19 date strings across EN/NL/DE/FR, 9 non-date strings (1.2M views, 1,234 views, 1,2 Mio. Aufrufe, 21:31, LIVE) correctly return null, combined metadata lines, cut-off boundaries
  • Token mutual-exclusivity: 13 badge strings each match their own filter and not the other, incl. typographic apostrophe
  • i18n: label + hint + all six dropdown options resolve in all four languages
  • Manual check on a live YouTube home feed (English)
  • Manual check of the NL/DE/FR badge strings

The harness is not committed — it lives in a scratchpad and needs jsdom, which would be the project's first devDependency. Promoting it to test/*.test.js is worth doing separately; test/ currently covers only SponsorBlock, so the entire DOM-facing surface — the largest and most fragile part — has no coverage.

## Summary Adds two feed-filtering settings and repairs membership badge detection, which had silently stopped working against YouTube's current markup. Started as "is the filter catching this members-only shelf?" — the answer was no, for three independent reasons, one of which turned out to be a live bug affecting an existing feature. ## Fixes **Badge detection was dead.** Labels now render in `.ytBadgeShapeText`. The code still looked for `.yt-badge-shape__text`, `.badge-shape-wiz__text` and `[class*="badge-style-type"]` — none of which match anything on the page any more — so **Hide Members first videos** was matching nothing at all. `CHANGELOG.md:88` shows this happened once before: a `.badge-shape-wiz__text` guess logged as *"best-effort; pending live confirmation"* that was never confirmed. Current selectors added alongside the legacy ones. **Badge labels were English-only**, unlike `SIDEBAR_RULES`, `RELATED_RESULT_LABELS` and `parseViewCount`, so the membership filters did nothing on a NL/DE/FR YouTube while neighbouring filters worked. Now cover all four. The two token lists are mutually exclusive so the settings cannot trigger on each other; the French tokens avoid the apostrophe in "Membres d'abord" because YouTube may render it as a typographic `’`. ## Added - **Hide Members only videos** (off by default). Members-only videos never become public, unlike Members-first, so this is a separate toggle rather than a widening of the existing one — which keeps its meaning for existing users. - **Maximum video age** — a preset dropdown (1/2/3/5/10 years, no day counts) that hides older videos on home, subscriptions and related, but **never on search results**, where hiding old hits would bury exactly what was asked for. Unknown ages (live, upcoming, not-yet-rendered) stay visible, and the comparison is strictly `>`, so a 5-year limit keeps "5 years ago" and drops "6 years ago". - The **"Get more from memberships" promo shelf** is hidden under *Hide sponsored content*. Its CTA is `/channel_memberships`, so the existing `/premium` brand-shelf rule never covered it, and its items are `ytd-rich-grid-media` rather than feed items, so the per-item filters could not reach them either. ## Also - Both membership hints said the channel could be allowed *"below"*, but the Trusted channels list lives in the Dashboard — they now name its location. The Dashboard copy claimed the allowlist only exempts from keyword and view-count filters; it now covers video age and both membership badges too. - Untrack `.DS_Store`. It was committed before `.gitignore` listed it, so the ignore rule never applied and it showed dirty on every checkout. All three filters honour the Trusted channels allowlist. ## Notes for review - **Age is approximate by design.** YouTube only reports "5 years ago", so a 5-year limit really keeps up to ~6 years. Stated in the hint text. - **The age parser scans every number+word pair**, not the first. `"1.2M views • 3 years ago"` would otherwise read `2m`, fail to recognise the unit, and give up before reaching the date. - **Localized badge strings are my best knowledge, not verified against a live page.** Worth a spot-check on a NL/DE/FR YouTube — a wrong token fails closed (filter does nothing), it will not hide the wrong videos. - **Single managed-hide key.** An earlier pass used two keys on one element; `setManagedHidden` records `fyOriginalDisplay` from the live style, so a second key claiming an already-hidden element would store `display: none` as its "original" and the item would never come back. Collapsed to one `membership-feed-item` key. ## Test plan - [x] `npm test` — 24/24 - [x] jsdom harness against the live shelf markup: badge extraction, `FEED_ITEM_SELECTOR` reachability, `:has()` rule matches the shelf and not ordinary rows, regression guard confirming the old selectors match zero elements - [x] Age parsing: 19 date strings across EN/NL/DE/FR, 9 non-date strings (`1.2M views`, `1,234 views`, `1,2 Mio. Aufrufe`, `21:31`, `LIVE`) correctly return null, combined metadata lines, cut-off boundaries - [x] Token mutual-exclusivity: 13 badge strings each match their own filter and not the other, incl. typographic apostrophe - [x] i18n: label + hint + all six dropdown options resolve in all four languages - [ ] Manual check on a live YouTube home feed (English) - [ ] Manual check of the NL/DE/FR badge strings The harness is not committed — it lives in a scratchpad and needs jsdom, which would be the project's first devDependency. Promoting it to `test/*.test.js` is worth doing separately; `test/` currently covers only SponsorBlock, so the entire DOM-facing surface — the largest and most fragile part — has no coverage.
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
365DevNet/CleanWatch!49
No description provided.