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:
@@ -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("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
||||
|
||||
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:
|
||||
|
||||
@@ -119,6 +119,18 @@ def get_stats(
|
||||
{"uid": uid},
|
||||
).mappings().all()
|
||||
|
||||
peak_hours = db.execute(
|
||||
text("""
|
||||
SELECT CAST(strftime('%H', uv.last_watched_at) AS INTEGER) AS hour,
|
||||
COUNT(*) AS count
|
||||
FROM user_videos uv
|
||||
WHERE uv.user_id = :uid AND uv.watched = 1 AND uv.last_watched_at IS NOT NULL
|
||||
GROUP BY hour
|
||||
ORDER BY hour ASC
|
||||
"""),
|
||||
{"uid": uid},
|
||||
).mappings().all()
|
||||
|
||||
liked_count = db.execute(
|
||||
text("SELECT COUNT(*) AS n FROM user_videos WHERE user_id = :uid AND liked = 1"),
|
||||
{"uid": uid},
|
||||
@@ -153,6 +165,7 @@ def get_stats(
|
||||
"rewatched_videos": avg_completion["rewatched_videos"] or 0,
|
||||
"total_liked": liked_count["n"] or 0,
|
||||
"top_categories": [dict(r) for r in top_categories],
|
||||
"peak_hours": [dict(r) for r in peak_hours],
|
||||
"taste_profile": [dict(r) for r in taste_profile],
|
||||
"disk": {
|
||||
"total_bytes": disk.total if disk else None,
|
||||
|
||||
Reference in New Issue
Block a user