Add local subscriptions and the Feed tab

New ytsage/utils/ytsage_library_manager.py: sagetube_library.db
(separate from the upstream download-history DB) with WAL from day one,
holding subscriptions, cached feed_items, watch_history with resume
positions, and the persisted play_queue. Watch history and queue tables
are wired up by the next commit.

FeedPage:
- Local mode: FeedRefreshWorker refreshes each subscribed channel
  sequentially (feed.per_channel_items, default 15) and the grid fills
  incrementally per channel; results are cached so the feed is
  populated instantly on startup.
- Account mode: fetches youtube.com/feed/subscriptions with the user's
  browser cookies - the real logged-in feed; the option is enabled only
  while cookies are active and errors surface as a status banner.
- Sidebar lists subscriptions (double-click opens the channel in
  Browse; context menu unsubscribes). Browse's Subscribe button now
  toggles subscription state through the Feed page.

Verified live: subscribe -> refresh -> 15 videos cached in SQLite and
rendered as cards.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jaroslav Beneš
2026-07-25 01:56:12 +02:00
parent 8e935c6116
commit 3afe96a8cd
4 changed files with 542 additions and 8 deletions
+13 -1
View File
@@ -689,6 +689,18 @@
"hint": "Open a channel (youtube.com/@name) or playlist URL, or navigate here from Search."
},
"feed": {
"coming_soon": "Subscriptions feed coming soon"
"mode_local": "Local subscriptions",
"mode_account": "YouTube account (cookies)",
"refresh": "Refresh",
"subscriptions": "Subscriptions",
"no_subscriptions": "No subscriptions yet - subscribe from a channel page in Browse",
"refreshing": "Refreshing... {done}/{total}",
"refreshed": "{count} videos in feed",
"channel_failed": "A channel failed to refresh: {error}",
"fetching_account": "Fetching your subscription feed...",
"account_failed": "Account feed failed (are cookies valid?): {error}",
"subscribed": "Subscribed to {title}",
"unsubscribed": "Unsubscribed from {title}",
"open_channel": "Open channel"
}
}