fix: address reader-UI feedback (shorts, search, tags, login, UX)

- Shorts: confirm via youtube.com/shorts/<id> probe (SOCS cookie bypasses the
  consent redirect) instead of a 60s heuristic; concurrent probing, shorts_probed
  flag, scheduled refinement (migration 0005)
- Search: match title + channel name only (descriptions caused noisy results)
- Faceted tag filtering: AND across categories (language AND topic narrows),
  OR within a category; any/all toggle applies to topics
- Language detection: majority vote over individual titles (fixes misdetections
  like multipoleguy -> English; drops bogus Polish/Romanian)
- Login: drop forced consent so returning sign-in is quick (select_account)
- Feed cards: clickable channel name (opens channel), persistent saved badge,
  undo toast on hide, Hidden view to restore; tag chips show counts in tooltip
This commit is contained in:
npeter83 2026-06-11 03:07:49 +02:00
parent e56502789f
commit 8c245e986f
17 changed files with 306 additions and 35 deletions

View file

@ -0,0 +1,32 @@
"""Confirm whether a video is a Short by probing youtube.com/shorts/<id>.
A real Short returns HTTP 200 at that URL; a regular video redirects to /watch.
This uses no API quota."""
import httpx
SHORTS_URL = "https://www.youtube.com/shorts/{video_id}"
# The SOCS cookie skips YouTube's cookie-consent interstitial, which otherwise
# redirects every server-side request to consent.youtube.com.
_HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"Cookie": "SOCS=CAI",
}
def probe_is_short(client: httpx.Client, video_id: str) -> bool | None:
"""True if a Short, False if a regular video, None if undetermined (retry later)."""
try:
resp = client.get(
SHORTS_URL.format(video_id=video_id), follow_redirects=False
)
except httpx.HTTPError:
return None
if resp.status_code == 200:
return True
if resp.status_code in (301, 302, 303, 307, 308):
return False
return None
def make_client() -> httpx.Client:
return httpx.Client(timeout=10.0, headers=_HEADERS)