feat: Live chat, comments, chapters & 18 bug fixes - #27
Conversation
- Export/share TranscriptLine, add TranscriptFetchError for live fallback - Eliminate redundant HTTP requests (comment count, checkIfLive) - Fix array mutation bug in comment reply insertion - Fix extractYoutubeId swallowing McpError from URL parsing - Fix visitorData lost on live chat continuation calls - Add SIGTERM handler for container graceful shutdown - Fix broken seenIds dedup logic in poll logging - Improve chapter regex resilience (handle title.runs format) - Move mcp-evals to devDependencies - Sync server version with package.json (0.1.1) - Remove dead webPayload code - Fix Shorts URL trailing path segment parsing - Add type safety to handleToolCall args (Record<string,unknown>) - Extract shared formatCount helper (DRY) - Remove defensive boolean handling for include_comments - Add tool annotations (readOnlyHint, openWorldHint) from upstream - Upgrade MCP SDK to ^1.11.0, adapt for Node16 module resolution - Exclude src/evals from tsconfig (ESM/CJS incompatibility) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Newlines in structuredContent fields render as literal \n in Claude Code. Use ' | ' separator for structuredContent.messages while keeping \n for content[].text which renders correctly. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Walkthrough패키지 설정과 TypeScript 제외 경로를 조정하고, YouTube 트랜스크립트에 챕터·댓글·메타데이터를 추가했습니다. 라이브 채팅 조회와 백그라운드 스트리밍 도구를 도입했으며 README와 서버 종료 처리를 갱신했습니다. ChangesYouTube 콘텐츠 확장
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Client
participant MCP as MCP Server
participant Fetcher as YouTube Fetcher
participant YouTube as YouTube API
participant Streams as activeStreams
Client->>MCP: get_transcript(options)
MCP->>Fetcher: transcript/comments 요청
Fetcher->>YouTube: 페이지·캡션·댓글 조회
YouTube-->>Fetcher: 자막·메타데이터·댓글
Fetcher-->>MCP: 구조화된 트랜스크립트 결과
MCP-->>Client: transcript, chapters, comments, meta
Client->>MCP: get_live_chat(url, stream)
MCP->>Fetcher: 라이브 채팅 시작 또는 조회
Fetcher->>YouTube: continuation 요청
YouTube-->>Fetcher: 메시지와 다음 continuation
Fetcher->>Streams: 스트림 버퍼 갱신
Streams-->>MCP: 메시지와 스트리밍 상태
MCP-->>Client: live chat 결과
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 405-407: The current branch uses comments_only and calls
getComments({ videoID: videoId, limit: 0 }) which requests unlimited comments
and can exhaust resources; change this to enforce a sensible upper bound by
replacing limit: 0 with a capped value (e.g., determine a DEFAULT_COMMENTS_LIMIT
= 1000 or 500 and use Math.min(requestedLimit || DEFAULT_COMMENTS_LIMIT,
DEFAULT_COMMENTS_LIMIT)), or require a CLI/env param and validate it before
calling getComments; update the call site using comments_only and the
getComments invocation to pass the validated/capped limit and document the cap
in the surrounding code.
In `@src/youtube-fetcher.ts`:
- Around line 1448-1461: When the poll() catch block detects a stream has
"ended", it only sets state.isPolling = false but doesn't remove the stream from
activeStreams or clear timers/buffers, causing memory leaks; update the error
branch in poll so that when (err as Error).message?.includes('ended') you also
clear state.pollTimer (if set), remove the entry from activeStreams
(activeStreams.delete(videoId)) or call stopLiveChatStream(videoId) to perform
full cleanup, and ensure any per-stream buffers (seenIds/messages) are released;
keep the existing logging and return behavior but add these cleanup steps to
fully remove the ended stream.
- Around line 1158-1162: The current regex that finds the continuation token
(html.match(/"continuation":"([^"]+)"/)) is too broad and may capture
non-live-chat continuations; narrow the search to the live chat context by
matching the continuation token that appears near liveChatRenderer or
liveChatContinuation (e.g., restrict the regex to look for
liveChatRenderer.*"continuation":"([^"]+)" or
liveChatContinuation.*"continuation":"([^"]+)"). Update the code that sets
continuationMatch to use this context-aware pattern, validate the match exists,
and use the captured group as before (symbols: html.match, continuationMatch,
the continuation token extraction logic).
🧹 Nitpick comments (5)
src/youtube-fetcher.ts (3)
1214-1224: 하드코딩된 API 키 — 환경 변수 또는 페이지 추출로 전환 권장정적 분석 도구(Gitleaks)가 Line 1216에서 GCP API 키를 감지했습니다. 이 키는 YouTube 웹 페이지 소스에 포함된 공개 키로 보이지만, 하드코딩하면:
- 키 로테이션 시 코드 변경이 필요합니다.
- 비밀 스캐닝 도구에서 지속적으로 경고가 발생합니다.
다른 API 호출(
/youtubei/v1/next,/youtubei/v1/get_transcript)에서는 키 없이 요청하고 있으므로, 여기서도 키를 제거하거나 페이지에서 동적으로 추출하는 것을 권장합니다.제안된 수정
- path: '/youtubei/v1/live_chat/get_live_chat?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + path: '/youtubei/v1/live_chat/get_live_chat',
1321-1322: 프로세스 종료 시 활성 스트림 정리 누락
activeStreams의 모든 타이머가 프로세스 종료 시 정리되지 않습니다.process.exit(0)호출로 강제 종료되긴 하지만, 명시적 정리를 추가하면 그레이스풀 셧다운이 개선됩니다.youtube-fetcher.ts에 cleanup 함수 추가 제안
+export function stopAllStreams(): void { + for (const [videoId, state] of activeStreams) { + state.isPolling = false; + if (state.pollTimer) clearTimeout(state.pollTimer); + } + activeStreams.clear(); +}
index.ts의stop()메서드에서stopAllStreams()를 호출하면 됩니다.
444-499:getCommentCount는 내부에서 사용되지 않음 — 제거하거나 목적을 문서화하세요
getCommentCount는src/youtube-fetcher.ts에서 내보내지만src/index.ts에 import되지 않으며, 정의 위치 외에는 코드 전체에서 참조되지 않습니다.getPageData가 이미 HTML에서 댓글 수를 추출하므로(372-384줄), 이 함수의 별도 API 호출은 중복됩니다. 외부 소비자를 위해 유지하려면 JSDoc에 용도를 명시하거나, 내부 사용이 없다면 제거하세요.src/index.ts (2)
438-462: 라이브 스트림 감지 응답 포맷팅 코드 중복Lines 438-462와 544-566은 거의 동일한 라이브 채팅 포맷팅 로직입니다. 헬퍼 함수로 추출하면 유지보수성이 개선됩니다.
헬퍼 함수 제안
private formatLiveChatResponse( liveResult: { messages: LiveChatMessage[]; continuation?: string; isLive: boolean; pollIntervalMs: number }, metadata: { title: string; author: string } ): CallToolResult { const messagesFormatted = liveResult.messages.map((m: LiveChatMessage) => { const prefix = m.isPaid ? `[${m.paidAmount}] ` : ''; return `${prefix}${m.author}: ${m.text}`; }).join('\n'); return { content: [{ type: "text" as const, text: `[LIVE STREAM DETECTED - Auto-switched to live chat with background streaming]\n\nStream: ${metadata.title || 'Unknown'}\nChannel: ${metadata.author || 'Unknown'}\n\nRecent chat:\n${messagesFormatted || '[No messages yet]'}\n\n[Use get_live_chat with stream:true to check for new messages, or stop_live_chat to stop]` }], structuredContent: { meta: `🔴 LIVE | ${metadata.title || 'Live Stream'} | ${metadata.author || 'Unknown'} | ${liveResult.messages.length} messages | STREAMING STARTED`, messages: messagesFormatted.replace(/\n/g, ' | '), isLive: true, streaming: true, note: 'Auto-detected live stream. Background streaming started. Use get_live_chat(stream:true) for updates.' } }; }Also applies to: 544-566
515-519:structuredResult에any타입 사용
structuredResult가any로 타입이 지정되어 있어 후속 프로퍼티 추가(chapters,comments)에 대한 타입 안전성이 없습니다. 인터페이스를 정의하면 유지보수성이 개선됩니다.
- Add TranscriptStructuredResult interface with index signature (replaces `any`) - Extract formatLiveChatResponse helper to deduplicate live chat formatting - Remove dead getCommentCount function (getPageData already extracts count) - Default comments_only to 500 cap; user can override via include_comments - Clean up activeStreams on stream end to prevent memory leaks - Narrow live chat continuation regex to liveChatRenderer context - Add stopAllStreams() called on server shutdown for graceful cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/index.ts`:
- Around line 564-575: The catch block is re-calling
extractor.extractYoutubeId(input) even though videoId from earlier in the try is
in scope, which can mask the original TranscriptFetchError with a new McpError;
update the catch to reuse the existing videoId variable (from the outer scope)
when calling startLiveChatStream and formatLiveChatResponse, and only attempt a
fresh extractYoutubeId(input) as a last-resort guarded step that preserves and
rethrows or logs the original TranscriptFetchError; adjust the
TranscriptFetchError handling so startLiveChatStream(videoId) is invoked with
the original videoId and avoid swallowing the original error when handling
fallback failures.
In `@src/youtube-fetcher.ts`:
- Around line 1171-1175: In getLiveChat, wrap the JSON.parse(response) call in a
try-catch to safely handle malformed/non-JSON responses: catch SyntaxError (or
generic errors), log the raw response and error (including response content)
using the existing logger, and then either throw a new, descriptive Error or
return a safe fallback (e.g., null/empty chat object) so the polling loop
doesn't crash; specifically update the code around JSON.parse(response) and the
subsequent json.error check to use the parsed value from the try block (refer to
getLiveChat, the json variable and the JSON.parse call).
🧹 Nitpick comments (5)
src/youtube-fetcher.ts (4)
1158-1158: 하드코딩된 YouTube API 키 — Gitleaks에서 감지됨이 키(
AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8)는 YouTube의 공개 innertube API 키로 널리 알려져 있지만, static analysis(Gitleaks)에서 GCP API 키로 플래그됩니다. 환경 변수나 상수 파일로 추출하면 false positive를 억제하고 향후 키 교체도 용이해집니다.제안
+const INNERTUBE_API_KEY = process.env.YOUTUBE_INNERTUBE_KEY || 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; + // In getLiveChat: - path: '/youtubei/v1/live_chat/get_live_chat?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + path: `/youtubei/v1/live_chat/get_live_chat?key=${INNERTUBE_API_KEY}`,
1097-1101:/s플래그가 있는.*?패턴 — 대형 HTML에서 성능 문제 가능
/s플래그로.이 개행도 매칭하므로,.*?가 수십~수백 KB HTML 전체를 탐색하게 됩니다.liveChatContinuation이 HTML 뒷부분에 있으면 상당한 백트래킹이 발생할 수 있습니다.[^"]*이나indexOf기반 접근이 더 안전합니다.대안 예시
- const liveChatContMatch = html.match(/"liveChatContinuation".*?"continuation":"([^"]+)"/s); - const liveChatRendererMatch = html.match(/"liveChatRenderer".*?"continuation":"([^"]+)"/s); + // Scope search to relevant section to avoid backtracking across entire HTML + const lcIdx = html.indexOf('"liveChatContinuation"'); + const lrIdx = html.indexOf('"liveChatRenderer"'); + const liveChatContMatch = lcIdx !== -1 + ? html.slice(lcIdx, lcIdx + 5000).match(/"continuation":"([^"]+)"/) + : null; + const liveChatRendererMatch = lrIdx !== -1 + ? html.slice(lrIdx, lrIdx + 5000).match(/"continuation":"([^"]+)"/) + : null;
82-87:formatCount(0)이 빈 문자열 반환 — 의도적인지 확인 필요
n <= 0일 때 빈 문자열을 반환합니다. 댓글 수가 0인 영상에서commentCount가''가 되어, UI에서 "comments" 라벨만 남고 숫자가 없는 상황이 될 수 있습니다.0을 명시적으로 반환하는 것이 더 일관적일 수 있습니다.
1063-1116:getLiveChatContinuation이getPageData와 동일한 페이지를 중복 요청
getSubtitles→getPageData로 이미 페이지를 가져오고, 라이브 스트림이면 다시getLiveChatContinuation에서 같은 URL을 요청합니다.getPageData에서 이미 추출한isLive,visitorData등을 전달하면 불필요한 HTTP 요청을 줄일 수 있습니다. 지금 당장 필수는 아니지만 라이브 스트림 감지 시 응답 시간이 2배가 됩니다.src/index.ts (1)
376-401:formatLiveChatResponse— 반복되는 메시지 포맷팅 로직 추출 고려이 헬퍼는 좋은 시작이지만, 동일한 메시지 포맷팅 패턴(
map→join('\n')→replace(/\n/g, ' | '))이get_live_chat핸들러(Lines 613-616, 640-643, 671-674)와stop_live_chat(Lines 729-732)에서도 반복됩니다.formatMessages(messages: LiveChatMessage[]): { text: string; inline: string }같은 헬퍼로 추출하면 중복을 줄일 수 있습니다.
- Hoist videoId extraction before try block so catch can reuse it instead of redundantly re-calling extractYoutubeId(input) - Wrap JSON.parse in getLiveChat with try-catch for malformed responses Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Chapter markers appear as --- [mm:ss] Title --- section dividers directly in the transcript, replacing separate chapters field - When chapters exist, per-line timestamps are dropped (chapter timestamps provide structural context) - Strip subtitle line breaks (\n) from individual transcript lines - Fix false "No chapter markers found" note on chaptered videos - Shared formatTimestamp helper replaces duplicated timestamp logic - Update README with all 4 tools, key features, and usage examples Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 7
🤖 Fix all issues with AI agents
In `@README.md`:
- Around line 33-34: 문구가 모호하므로 README의 해당 섹션을 수정해 `comments_only`와
`include_comments`의 상호작용을 명확히 하세요: 명시적으로 `include_comments`는 "0 = disabled"이며,
`comments_only: true`일 때 `include_comments > 0`이면 그 값이 가져올 댓글 수의 제한으로 사용되고, 그렇지
않으면 기본 제한으로 500개가 적용된다고 기술하도록 `comments_only`와 `include_comments` 항목을 업데이트하세요
(참조 심볼: comments_only, include_comments, 기본값 500).
- Around line 36-41: The README entry for get_live_chat is missing the
continuation parameter described in the tool definition (see get_live_chat
signature where continuation is defined); update the docs to list `continuation`
(string, optional) alongside `url` and `stream`, explain it accepts a YouTube
continuation token used for manual polling to resume from a specific chat
position, and note that it should be provided only in non-streaming/manual poll
mode; reference the get_live_chat function and the continuation parameter name
when adding this documentation.
In `@src/index.ts`:
- Around line 512-521: The current logic unconditionally redirects to live chat
when result.isLive is true, discarding any successfully fetched transcript;
change the flow in the block that calls startLiveChatStream so that if a
transcript exists (check result.transcript or result.metadata.transcript) you do
not discard it—either return both transcript and live chat data together (e.g.,
call or add a new formatter like this.formatLiveAndTranscriptResponse or extend
formatLiveChatResponse to accept the transcript) or fall back to returning the
transcript immediately and let the caller choose; update the code paths around
result.isLive, startLiveChatStream, and formatLiveChatResponse to
include/preserve result.metadata (and transcript) instead of throwing it away.
In `@src/youtube-fetcher.ts`:
- Around line 1240-1248: The return value currently hardcodes isLive: true in
getLiveChat which prevents detecting stream end; change isLive to be derived
from the parsed continuation (e.g., set isLive = Boolean(nextContinuation) or
check whether the YouTube response contained a continuation token) instead of
true, and update the return object (messages, continuation: nextContinuation,
isLive, ...) so callers can accurately detect stream termination.
- Around line 760-766: fetchCommentsPage currently calls JSON.parse(response)
without handling parse failures; wrap the parse in a try-catch inside
fetchCommentsPage, catch SyntaxError (or generic Error), and throw a new Error
that includes context ("Failed to parse YouTube response in fetchCommentsPage")
plus the original error message and a truncated/escaped snapshot of response to
aid debugging; ensure the thrown error preserves the original error message so
callers like getComments receive a clear, actionable parse error instead of an
opaque crash.
- Around line 1097-1108: The title/author extraction in getLiveChatContinuation
is too broad (using /"title":"([^"]+)"/ and /"author":"([^"]+)"/) and can return
unrelated matches; instead scope extraction to the "videoDetails" context like
getPageData does: first extract the "videoDetails" JSON block (e.g., match
/"videoDetails"\s*:\s*\{([\s\S]*?)\}/ or parse that JSON if already isolated),
then extract "title" and "author" from within that block (or parse the block as
JSON) so getLiveChatContinuation uses the videoDetails-specific title/author
rather than the first global occurrences.
- Around line 963-965: The initial continuationToken is being
encodeURIComponent'd while subsequent tokens from
item.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token
are raw, causing inconsistent encoding; remove the encodeURIComponent wrapper
around buildCommentsParams(videoID, sortByNum) so continuationToken holds the
raw base64, and instead centralize encoding in fetchCommentsPage (ensure it
consistently encodeURIComponent the token when constructing the JSON body) so
both the initial token from buildCommentsParams and subsequent tokens extracted
from responses are treated identically.
🧹 Nitpick comments (5)
src/youtube-fetcher.ts (3)
79-87:formatCount(0)및 음수 값에서 빈 문자열 반환
n <= 0일 때 빈 문자열을 반환하는 것은 "카운트 없음" 의미로 사용되고 있어 의도된 동작으로 보이지만, 정확히 0개의 댓글이 있는 경우에도 빈 문자열을 반환합니다. 현재 사용처에서는parseInt가 NaN을 반환하거나 카운트가 없는 경우에 해당하므로 큰 문제는 아닙니다.
1275-1303:checkIfLive가 별도의 HTTP 요청을 수행 —getPageData와 중복 fetch
checkIfLive는 독립 유틸리티로 설계되었지만,getPageData에서 이미isLive를 추출합니다.get_transcript흐름에서getSubtitles→getPageData후isLive가 반환되므로,checkIfLive를 추가로 호출하면 불필요한 HTTP 요청이 발생합니다.현재 코드에서
checkIfLive는index.ts에서 import되지만 직접 호출하지 않는 것으로 보이므로 즉각적인 문제는 아닙니다. 향후 사용 시 중복 fetch에 주의가 필요합니다.
1063-1091:getLiveChatContinuation이getPageData와 중복 페이지 fetch 수행
get_live_chat도구가 호출될 때getLiveChatContinuation이 YouTube 페이지를 다시 fetch합니다. 만약get_transcript에서 라이브 감지 후startLiveChatStream→getLiveChat→getLiveChatContinuation경로로 흐르면, 동일 영상 페이지를 최소 2회 fetch하게 됩니다.페이지 데이터를 캐싱하거나
getPageData의 결과를 파이프라인으로 전달하면 불필요한 네트워크 요청을 줄일 수 있습니다.src/index.ts (2)
38-49:formatTimestamp함수가youtube-fetcher.ts의 duration 포맷 로직과 중복
youtube-fetcher.tslines 394-399의 duration 포맷팅과 동일한 로직입니다. 공통 유틸리티로 추출하면 유지보수성이 향상됩니다.
486-494:comments_only와include_comments모드의 댓글 포맷이 불일치
comments_only모드(line 494)에서는\n으로 구분하고,include_comments모드(line 540)에서는|로 구분합니다.structuredContent에서도comments_only는 원본 줄바꿈을 유지하지만,include_comments는 pipe로 구분합니다.의도적인 설계일 수 있지만(comments_only는 상세, include_comments는 요약), 일관성이 떨어집니다. 포맷 선택을 문서화하거나 통일하는 것을 권장합니다.
Also applies to: 537-540
| // Build initial comments params (0 = TOP_COMMENTS, 1 = NEWEST_FIRST) | ||
| const sortByNum = sortBy === 'NEWEST_FIRST' ? 1 : 0; | ||
| let continuationToken: string | undefined = encodeURIComponent(buildCommentsParams(videoID, sortByNum)); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check if encodeURIComponent is used consistently with continuation tokens
rg -n 'encodeURIComponent' --type=ts -C3
echo "---"
# Check how continuation tokens are used in fetchCommentsPage
rg -n 'continuationToken|continuation:' --type=ts -C2Repository: kimtaeyoon83/mcp-server-youtube-transcript
Length of output: 7654
🏁 Script executed:
#!/bin/bash
# Find where nextContinuation is assigned and extracted
rg -n 'nextContinuation' --type=ts -B2 -A2
echo "---"
# Check how pageResult.continuation is extracted/set
rg -n 'pageResult\|\.continuation' --type=ts -B1 -A1 | head -80Repository: kimtaeyoon83/mcp-server-youtube-transcript
Length of output: 1878
🏁 Script executed:
#!/bin/bash
# Find buildCommentsParams function implementation
rg -n 'buildCommentsParams' --type=ts -B2 -A10Repository: kimtaeyoon83/mcp-server-youtube-transcript
Length of output: 2060
🏁 Script executed:
#!/bin/bash
# Get full buildCommentsParams function
sed -n '151,200p' src/youtube-fetcher.tsRepository: kimtaeyoon83/mcp-server-youtube-transcript
Length of output: 1604
초기 댓글 continuation 토큰과 후속 페이지네이션 토큰의 인코딩 상태 불일치
buildCommentsParams가 반환하는 base64 문자열에 encodeURIComponent를 적용하면 = → %3D, + → %2B 등으로 변환됩니다. 이 인코딩된 토큰은 fetchCommentsPage의 JSON body에 전송됩니다.
그러나 후속 페이지네이션 토큰은 API 응답에서 직접 추출되므로(item.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token), 인코딩이 적용되지 않습니다. 결과적으로 동일한 fetchCommentsPage 함수가 서로 다른 인코딩 상태의 토큰을 받게 되는 문제가 발생합니다.
YouTube API가 JSON body의 continuation 필드에서 인코딩된 토큰과 인코딩되지 않은 토큰을 모두 수용하는지, 아니면 일관된 형식을 요구하는지 확인하여 초기 토큰과 후속 토큰의 인코딩 방식을 통일하는 것이 필요합니다.
🤖 Prompt for AI Agents
In `@src/youtube-fetcher.ts` around lines 963 - 965, The initial continuationToken
is being encodeURIComponent'd while subsequent tokens from
item.continuationItemRenderer.continuationEndpoint?.continuationCommand?.token
are raw, causing inconsistent encoding; remove the encodeURIComponent wrapper
around buildCommentsParams(videoID, sortByNum) so continuationToken holds the
raw base64, and instead centralize encoding in fetchCommentsPage (ensure it
consistently encodeURIComponent the token when constructing the JSON body) so
both the initial token from buildCommentsParams and subsequent tokens extracted
from responses are treated identically.
- README: Clarify comments_only + include_comments interaction - README: Add continuation param docs for get_live_chat - index.ts: Only redirect to live chat when no transcript was fetched - youtube-fetcher.ts: Derive isLive from continuation token presence - youtube-fetcher.ts: Wrap fetchCommentsPage JSON.parse in try-catch - youtube-fetcher.ts: Scope title/author to videoDetails context - youtube-fetcher.ts: Remove encodeURIComponent from initial comments token for consistent encoding with subsequent tokens Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/youtube-fetcher.ts`:
- Around line 640-643: The JSON.parse(contResponse) call in getSubtitles (after
building continuationPayload) lacks a try-catch and can throw on malformed
YouTube responses; wrap the parse in a try-catch, log the parsing error along
with contResponse and continuationPayload (consistent with the existing
try-catch at the earlier block), and return a safe fallback (e.g., empty
subtitles/continuation result) or gracefully propagate a descriptive error
instead of letting a raw SyntaxError crash the whole getSubtitles flow.
🧹 Nitpick comments (4)
src/youtube-fetcher.ts (3)
1165-1165: 하드코딩된 GCP API 키가 소스 코드에 노출됨이 키(
AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8)는 YouTube 웹 클라이언트에서 공개적으로 사용되는 키이지만, 소스 코드에 직접 하드코딩하면 키 로테이션이 필요할 때 코드 변경이 필요합니다. 환경 변수나 상수 설정 파일로 분리하는 것을 권장합니다.제안된 수정
+const YOUTUBE_API_KEY = process.env.YOUTUBE_API_KEY || 'AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8'; + // In getLiveChat: - path: '/youtubei/v1/live_chat/get_live_chat?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8', + path: `/youtubei/v1/live_chat/get_live_chat?key=${YOUTUBE_API_KEY}`,
1297-1306:checkIfLive의 title/author 추출이videoDetails로 스코핑되지 않음
getPageData(Line 364-365)와getLiveChatContinuation(Line 1113)에서는"videoDetails"컨텍스트 내에서 title/author를 추출하지만,checkIfLive에서는 여전히 광범위한 패턴(/"title":"([^"]+)"/))을 사용합니다. 추천 동영상이나 광고의 title이 반환될 수 있습니다.제안된 수정
- const titleMatch = html.match(/"title":"([^"]+)"/); - const authorMatch = html.match(/"author":"([^"]+)"/); + const titleMatch = html.match(/"videoDetails":\{.*?"title":"([^"]+)"/); + const authorMatch = html.match(/"videoDetails":\{.*?"author":"([^"]+)"/);
82-87:formatCount에서n === 0처리
n <= 0일 때 빈 문자열을 반환하는데, 댓글 수가 실제로 0인 경우에도 빈 문자열이 반환됩니다. 이는 "댓글 없음"과 "파싱 실패"를 구분할 수 없게 합니다. 의도된 동작이라면 괜찮지만,n === 0일 때'0'을 반환하는 것이 더 명확할 수 있습니다.src/index.ts (1)
415-437:formatLiveChatResponse에서structuredContent.messages의 파이프 구분자
messagesFormatted.replace(/\n/g, ' | ')로 모든 줄바꿈을 파이프로 치환하는데, 메시지 텍스트 자체에|문자가 포함된 경우 파싱이 모호해질 수 있습니다.structuredContent가 기계적으로 파싱되지 않는 표시용이라면 괜찮지만, 향후 파싱이 필요하면 구조화된 배열로 전달하는 것이 더 안전합니다.
Gracefully handles malformed continuation responses by breaking out of the fetch loop and falling back to segments already collected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Mirrors the same fix applied to getLiveChatContinuation - scopes title/author regex to videoDetails block to avoid matching recommended video titles/authors. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Addressing remaining nitpicks from latest review1. Hardcoded GCP API Key (
|
|
Tip For best results, initiate chat on the files or code changes.
All of these decisions are well-reasoned. Thanks for addressing the feedback! ✏️ Learnings added
|
…+ timedtext YouTube now returns HTTP 400 'Precondition check failed' on /youtubei/v1/get_transcript for all client types (WEB, ANDROID, IOS), which broke transcript fetching entirely. Switch getSubtitles to the surface youtube-transcript-api uses: POST /youtubei/v1/player as ANDROID 20.10.38, read captions.playerCaptionsTracklistRenderer.captionTracks, and download the timedtext XML from the selected track's baseUrl (no poToken required there). Language fallback, ASR detection, chapters, ad markers, metadata and error semantics are preserved; the protobuf params builder, multi-client retry loop and continuation handling are removed (-103 net lines). Verified live: manual captions (61 lines), ASR + 31 chapters (6006 lines), entity decoding, language fallback, strict-language errors, and comments regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/youtube-fetcher.ts`:
- Around line 79-80: Remove HTTP status 400 from the RETRYABLE_CODES set in
src/youtube-fetcher.ts, leaving the existing retry behavior unchanged for the
other retryable statuses and RETRY_DELAYS.
- Around line 580-586: Update the caption URL handling in the timedtext download
flow to parse track.baseUrl as a URL, check trackUrl.searchParams.get('exp') for
the 'xpe' value regardless of query order, and remove fmt via
searchParams.delete('fmt') before fetching. Preserve the existing
TranscriptFetchError behavior and use the resulting URL for download and
parsing.
- Around line 588-599: Wrap the timed-text `httpsRequest` and
`parseTimedTextXml` flow in a catch path that converts any network or parsing
failure into the public `TranscriptFetchError` contract. Preserve and attach the
existing `isLive` and `metadata` values when constructing or propagating the
error, while leaving successful transcript parsing unchanged.
- Around line 569-577: Update the track selection logic around playerTracks.find
so it tries an exact language match first, then accepts a regional subtag such
as en-US for a requested base language. When falling back, select only the exact
English track or an en-* regional track before considering no match; do not fall
through to an unrelated first track.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 86f4c0f7-7023-4121-af8f-d677f5fc2d9c
📒 Files selected for processing (1)
src/youtube-fetcher.ts
- Match regional language subtags: exact match first, then base-language prefix (de -> de-DE); English fallback narrowed to en/en-* before first-track. Requesting 'de' now returns de-DE instead of English. - Handle caption URL query params position-independently via URL/searchParams (fmt removal and exp=xpe detection no longer miss first-position params). - Wrap timedtext download/parse in TranscriptFetchError so isLive and metadata are preserved on every transcript failure path. - Remove HTTP 400 from RETRYABLE_CODES: client errors are not transient; retrying them just added ~3s of delay before failing. Verified live: de -> de-DE (60 lines), strict-language error, ASR + 31 chapters (6006 lines), entity decoding, re-serialized caption URL accepted by YouTube. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New Features
get_live_chat(with background streaming mode),stop_live_chat,list_live_streamsfor monitoring YouTube live streams in real-timeinclude_commentsparameter to fetch top comments with transcript, pluscomments_onlymode for fetching all available commentsinclude_chaptersparameter returns video chapters with timestampsget_transcriptautomatically detects live streams and redirects to live chat with background streamingBug Fixes (18 total)
Critical
High
extractYoutubeIdswallowingMcpErrorfrom URL parsing (specific error messages were lost)visitorDatalost on live chat continuation calls (caused potential auth failures)checkIfLivepage fetch (was fetching page HTML twice for every non-live video)SIGTERMhandler for container graceful shutdown (Docker/K8s compatibility)Medium
seenIdsdedup logic in poll logging (new message counts were always 0)simpleTextandrunstitle formats)mcp-evalsfrom dependencies to devDependencieswebPayloadcode (built but never used)\nin structuredContent (use pipe separator)Low
as Tooltype assertions (SDK 1.x includesoutputSchemanatively)handleToolCallargs (Record<string, unknown>instead ofany)formatCounthelper (was duplicated 4+ times)include_commentsnumber paramsrc/evalsfrom tsconfig (ESM/CJS incompatibility with Node16)Upstream Compatibility
0.6.0to^1.11.0.jsimport extensions)readOnlyHint,openWorldHint) on all toolsTest Plan
tsc --noEmitpasses cleannpm run buildsucceedsget_transcriptwith regular video (Rick Astley - confirmed metadata, transcript)get_live_chatwith live stream (CS2 IEM Krakow - confirmed 70 messages, streaming)get_transcripton live video🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Documentation
Chores