Add stats peak hours, RSS feed, channel health view, bulk video download

Stats:
- Peak watching hours chart (24-bar) from last_watched_at timestamps

RSS:
- GET /api/channels/rss — last 100 videos from followed channels as RSS 2.0
- RSS link in Following > Health tab

Channel health:
- New Health tab in Following groups channels into Active / Slow / Dormant / Dead
  based on days since last upload

Bulk video download:
- Select mode on Channel page (Videos tab) with checkboxes
- Sticky bottom bar shows count + Download button
- Queues a download for each selected video

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 23:50:55 +02:00
parent 3652038cf5
commit ff601d3585
6 changed files with 231 additions and 3 deletions

View File

@@ -290,6 +290,57 @@ def sync_all_channels(
return {"indexing": len(channels)}
@router.get("/rss")
def rss_feed(
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
from fastapi.responses import Response
rows = db.execute(
text("""
SELECT v.youtube_video_id, v.title, v.description, v.published_at,
c.name AS channel_name, c.youtube_channel_id
FROM videos v
JOIN channels c ON v.channel_id = c.id
JOIN user_channels uc ON c.id = uc.channel_id AND uc.user_id = :uid AND uc.status = 'followed'
WHERE v.published_at IS NOT NULL
ORDER BY v.published_at DESC
LIMIT 100
"""),
{"uid": current_user.id},
).mappings().all()
def esc(s):
if not s:
return ""
return str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
items = []
for r in rows:
pub = r["published_at"]
pub_str = pub.strftime("%a, %d %b %Y %H:%M:%S +0000") if pub else ""
yt_id = r["youtube_video_id"]
items.append(f""" <item>
<title>{esc(r['title'])}</title>
<link>https://www.youtube.com/watch?v={yt_id}</link>
<description>{esc(r['description'] or '')}</description>
<author>{esc(r['channel_name'])}</author>
<pubDate>{pub_str}</pubDate>
<guid>https://www.youtube.com/watch?v={yt_id}</guid>
</item>""")
xml = f"""<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
<channel>
<title>YTContinue — Following</title>
<link>https://www.youtube.com/</link>
<description>Latest videos from your followed channels</description>
{chr(10).join(items)}
</channel>
</rss>"""
return Response(content=xml, media_type="application/rss+xml; charset=utf-8")
@router.get("/tasks")
def get_active_tasks(current_user: User = Depends(get_current_user)):
with _tasks_lock: