Add lazy comment fetching to watch page
- VideoComment model (video_id, author, text, likes, is_pinned, published_at)
- fetch_video_comments() in ytdlp.py: top 20 comments, no reply threads,
sorted pinned-first then by likes
- GET /videos/by-yt/{id}/comments — returns cached comments instantly
- POST /videos/by-yt/{id}/comments/refresh — fetches from YouTube, stores, returns
- Watch page: CommentsSection shows "Load comments" button when uncached,
renders comments with author/likes once loaded; Refresh link to re-fetch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -382,6 +382,46 @@ def fetch_channel_links(channel_id: str) -> list[str]:
|
||||
return list(channel_ids)
|
||||
|
||||
|
||||
def fetch_video_comments(youtube_video_id: str, max_comments: int = 20) -> list[dict]:
|
||||
"""Fetch top comments for a single video. Returns empty list on failure."""
|
||||
url = f"https://www.youtube.com/watch?v={youtube_video_id}"
|
||||
args = [
|
||||
"yt-dlp", url,
|
||||
"--dump-json",
|
||||
"--write-comments",
|
||||
"--extractor-args", f"youtube:max_comments={max_comments},max_comment_depth=1",
|
||||
"--no-download",
|
||||
"--no-playlist",
|
||||
"--quiet",
|
||||
*_cookie_args(),
|
||||
]
|
||||
stdout, _, code = _run(args, timeout=60)
|
||||
if not stdout.strip():
|
||||
return []
|
||||
try:
|
||||
info = json.loads(stdout.strip())
|
||||
except json.JSONDecodeError:
|
||||
return []
|
||||
|
||||
result = []
|
||||
for c in (info.get("comments") or []):
|
||||
if c.get("parent") not in (None, "root"):
|
||||
continue # skip replies
|
||||
ts = c.get("timestamp")
|
||||
published_at = datetime.utcfromtimestamp(ts) if ts else None
|
||||
result.append({
|
||||
"youtube_comment_id": c.get("id"),
|
||||
"author": c.get("author"),
|
||||
"text": c.get("text"),
|
||||
"likes": c.get("like_count") or 0,
|
||||
"is_pinned": bool(c.get("is_pinned")),
|
||||
"published_at": published_at,
|
||||
})
|
||||
# Sort pinned first, then by likes
|
||||
result.sort(key=lambda c: (not c["is_pinned"], -(c["likes"] or 0)))
|
||||
return result[:max_comments]
|
||||
|
||||
|
||||
QUALITY_FORMATS = {
|
||||
"best": "bestvideo[ext=mp4][vcodec^=avc1]+bestaudio[ext=m4a]/bestvideo[ext=mp4]+bestaudio[ext=m4a]/22/18/bestvideo+bestaudio/best",
|
||||
"2160p": "bestvideo[ext=mp4][height<=2160]+bestaudio[ext=m4a]/bestvideo[height<=2160]+bestaudio/best[height<=2160]",
|
||||
|
||||
Reference in New Issue
Block a user