youtube-knowledge-mcp 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -33
- package/dist/brain-schemas.d.ts +502 -0
- package/dist/brain-schemas.d.ts.map +1 -0
- package/dist/brain-schemas.js +109 -0
- package/dist/brain-schemas.js.map +1 -0
- package/dist/http/app.d.ts +16 -0
- package/dist/http/app.d.ts.map +1 -0
- package/dist/http/app.js +176 -0
- package/dist/http/app.js.map +1 -0
- package/dist/http/auth.d.ts +38 -0
- package/dist/http/auth.d.ts.map +1 -0
- package/dist/http/auth.js +79 -0
- package/dist/http/auth.js.map +1 -0
- package/dist/http/config.d.ts +41 -0
- package/dist/http/config.d.ts.map +1 -0
- package/dist/http/config.js +65 -0
- package/dist/http/config.js.map +1 -0
- package/dist/http/rate-limiter.d.ts +24 -0
- package/dist/http/rate-limiter.d.ts.map +1 -0
- package/dist/http/rate-limiter.js +55 -0
- package/dist/http/rate-limiter.js.map +1 -0
- package/dist/http/sessions.d.ts +34 -0
- package/dist/http/sessions.d.ts.map +1 -0
- package/dist/http/sessions.js +62 -0
- package/dist/http/sessions.js.map +1 -0
- package/dist/http.d.ts +6 -48
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +35 -252
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +11 -431
- package/dist/index.js.map +1 -1
- package/dist/prompts-brain.d.ts +4 -0
- package/dist/prompts-brain.d.ts.map +1 -0
- package/dist/prompts-brain.js +48 -0
- package/dist/prompts-brain.js.map +1 -0
- package/dist/prompts-research.d.ts +4 -0
- package/dist/prompts-research.d.ts.map +1 -0
- package/dist/prompts-research.js +74 -0
- package/dist/prompts-research.js.map +1 -0
- package/dist/prompts-shared.d.ts +13 -0
- package/dist/prompts-shared.d.ts.map +1 -0
- package/dist/prompts-shared.js +7 -0
- package/dist/prompts-shared.js.map +1 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +5 -58
- package/dist/prompts.js.map +1 -1
- package/dist/registry/brain-tools.d.ts +4 -0
- package/dist/registry/brain-tools.d.ts.map +1 -0
- package/dist/registry/brain-tools.js +104 -0
- package/dist/registry/brain-tools.js.map +1 -0
- package/dist/registry/channel-tools.d.ts +4 -0
- package/dist/registry/channel-tools.d.ts.map +1 -0
- package/dist/registry/channel-tools.js +53 -0
- package/dist/registry/channel-tools.js.map +1 -0
- package/dist/registry/health-tools.d.ts +4 -0
- package/dist/registry/health-tools.d.ts.map +1 -0
- package/dist/registry/health-tools.js +21 -0
- package/dist/registry/health-tools.js.map +1 -0
- package/dist/registry/index.d.ts +8 -0
- package/dist/registry/index.d.ts.map +1 -0
- package/dist/registry/index.js +27 -0
- package/dist/registry/index.js.map +1 -0
- package/dist/registry/library-tools.d.ts +4 -0
- package/dist/registry/library-tools.d.ts.map +1 -0
- package/dist/registry/library-tools.js +116 -0
- package/dist/registry/library-tools.js.map +1 -0
- package/dist/registry/media-tools.d.ts +4 -0
- package/dist/registry/media-tools.d.ts.map +1 -0
- package/dist/registry/media-tools.js +100 -0
- package/dist/registry/media-tools.js.map +1 -0
- package/dist/registry/thumbnail-tools.d.ts +7 -0
- package/dist/registry/thumbnail-tools.d.ts.map +1 -0
- package/dist/registry/thumbnail-tools.js +72 -0
- package/dist/registry/thumbnail-tools.js.map +1 -0
- package/dist/registry/transcript-tools.d.ts +4 -0
- package/dist/registry/transcript-tools.d.ts.map +1 -0
- package/dist/registry/transcript-tools.js +68 -0
- package/dist/registry/transcript-tools.js.map +1 -0
- package/dist/registry/types.d.ts +41 -0
- package/dist/registry/types.d.ts.map +1 -0
- package/dist/registry/types.js +17 -0
- package/dist/registry/types.js.map +1 -0
- package/dist/registry/video-tools.d.ts +4 -0
- package/dist/registry/video-tools.d.ts.map +1 -0
- package/dist/registry/video-tools.js +101 -0
- package/dist/registry/video-tools.js.map +1 -0
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +75 -6
- package/dist/resources.js.map +1 -1
- package/dist/schemas.d.ts +74 -54
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +30 -4
- package/dist/schemas.js.map +1 -1
- package/dist/thumbnail-resource.d.ts +8 -0
- package/dist/thumbnail-resource.d.ts.map +1 -0
- package/dist/thumbnail-resource.js +38 -0
- package/dist/thumbnail-resource.js.map +1 -0
- package/dist/thumbnail-schemas.d.ts +512 -0
- package/dist/thumbnail-schemas.d.ts.map +1 -0
- package/dist/thumbnail-schemas.js +95 -0
- package/dist/thumbnail-schemas.js.map +1 -0
- package/dist/tools/ask-brain.d.ts +62 -0
- package/dist/tools/ask-brain.d.ts.map +1 -0
- package/dist/tools/ask-brain.js +70 -0
- package/dist/tools/ask-brain.js.map +1 -0
- package/dist/tools/batch.d.ts +10 -10
- package/dist/tools/batch.js +3 -3
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/brain-info.d.ts +180 -0
- package/dist/tools/brain-info.d.ts.map +1 -0
- package/dist/tools/brain-info.js +108 -0
- package/dist/tools/brain-info.js.map +1 -0
- package/dist/tools/build-brain.d.ts +120 -0
- package/dist/tools/build-brain.d.ts.map +1 -0
- package/dist/tools/build-brain.js +123 -0
- package/dist/tools/build-brain.js.map +1 -0
- package/dist/tools/check-health.d.ts +2 -0
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +9 -0
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/clips.d.ts +13 -45
- package/dist/tools/clips.d.ts.map +1 -1
- package/dist/tools/clips.js +52 -128
- package/dist/tools/clips.js.map +1 -1
- package/dist/tools/fetch-channel-thumbnails.d.ts +179 -0
- package/dist/tools/fetch-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/fetch-channel-thumbnails.js +123 -0
- package/dist/tools/fetch-channel-thumbnails.js.map +1 -0
- package/dist/tools/fetch-videos.d.ts +11 -8
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +3 -0
- package/dist/tools/fetch-videos.js.map +1 -1
- package/dist/tools/frames-subtitles.d.ts +44 -0
- package/dist/tools/frames-subtitles.d.ts.map +1 -0
- package/dist/tools/frames-subtitles.js +104 -0
- package/dist/tools/frames-subtitles.js.map +1 -0
- package/dist/tools/get-channel-info.d.ts +2 -0
- package/dist/tools/get-channel-info.d.ts.map +1 -1
- package/dist/tools/get-channel-info.js +8 -1
- package/dist/tools/get-channel-info.js.map +1 -1
- package/dist/tools/get-chapters.d.ts +2 -2
- package/dist/tools/get-comments.d.ts +2 -2
- package/dist/tools/get-playlist-info.d.ts.map +1 -1
- package/dist/tools/get-playlist-info.js +1 -0
- package/dist/tools/get-playlist-info.js.map +1 -1
- package/dist/tools/get-thumbnail.d.ts +38 -0
- package/dist/tools/get-thumbnail.d.ts.map +1 -0
- package/dist/tools/get-thumbnail.js +152 -0
- package/dist/tools/get-thumbnail.js.map +1 -0
- package/dist/tools/get-transcript.d.ts +2 -2
- package/dist/tools/get-transcript.d.ts.map +1 -1
- package/dist/tools/get-transcript.js +2 -14
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/library.d.ts +9 -7
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +23 -7
- package/dist/tools/library.js.map +1 -1
- package/dist/tools/list-channel-thumbnails.d.ts +235 -0
- package/dist/tools/list-channel-thumbnails.d.ts.map +1 -0
- package/dist/tools/list-channel-thumbnails.js +79 -0
- package/dist/tools/list-channel-thumbnails.js.map +1 -0
- package/dist/tools/list-library.d.ts +4 -4
- package/dist/tools/manage-brain.d.ts +39 -0
- package/dist/tools/manage-brain.d.ts.map +1 -0
- package/dist/tools/manage-brain.js +53 -0
- package/dist/tools/manage-brain.js.map +1 -0
- package/dist/tools/save-to-library.d.ts.map +1 -1
- package/dist/tools/save-to-library.js +5 -1
- package/dist/tools/save-to-library.js.map +1 -1
- package/dist/tools/search-channels.d.ts +10 -4
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-transcript.d.ts +2 -2
- package/dist/tools/search-transcript.d.ts.map +1 -1
- package/dist/tools/search-transcript.js +6 -13
- package/dist/tools/search-transcript.js.map +1 -1
- package/dist/tools/search-videos.d.ts +11 -8
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/utils/batches.d.ts +15 -0
- package/dist/utils/batches.d.ts.map +1 -0
- package/dist/utils/batches.js +31 -0
- package/dist/utils/batches.js.map +1 -0
- package/dist/utils/brain-build.d.ts +40 -0
- package/dist/utils/brain-build.d.ts.map +1 -0
- package/dist/utils/brain-build.js +200 -0
- package/dist/utils/brain-build.js.map +1 -0
- package/dist/utils/brain-chunks.d.ts +33 -0
- package/dist/utils/brain-chunks.d.ts.map +1 -0
- package/dist/utils/brain-chunks.js +87 -0
- package/dist/utils/brain-chunks.js.map +1 -0
- package/dist/utils/brain-index.d.ts +27 -0
- package/dist/utils/brain-index.d.ts.map +1 -0
- package/dist/utils/brain-index.js +101 -0
- package/dist/utils/brain-index.js.map +1 -0
- package/dist/utils/brain-ingest.d.ts +57 -0
- package/dist/utils/brain-ingest.d.ts.map +1 -0
- package/dist/utils/brain-ingest.js +127 -0
- package/dist/utils/brain-ingest.js.map +1 -0
- package/dist/utils/brain-lock.d.ts +4 -0
- package/dist/utils/brain-lock.d.ts.map +1 -0
- package/dist/utils/brain-lock.js +17 -0
- package/dist/utils/brain-lock.js.map +1 -0
- package/dist/utils/brain-lookup.d.ts +11 -0
- package/dist/utils/brain-lookup.d.ts.map +1 -0
- package/dist/utils/brain-lookup.js +36 -0
- package/dist/utils/brain-lookup.js.map +1 -0
- package/dist/utils/brain-paths.d.ts +19 -0
- package/dist/utils/brain-paths.d.ts.map +1 -0
- package/dist/utils/brain-paths.js +34 -0
- package/dist/utils/brain-paths.js.map +1 -0
- package/dist/utils/brain-phrases.d.ts +31 -0
- package/dist/utils/brain-phrases.d.ts.map +1 -0
- package/dist/utils/brain-phrases.js +116 -0
- package/dist/utils/brain-phrases.js.map +1 -0
- package/dist/utils/brain-stats.d.ts +25 -0
- package/dist/utils/brain-stats.d.ts.map +1 -0
- package/dist/utils/brain-stats.js +64 -0
- package/dist/utils/brain-stats.js.map +1 -0
- package/dist/utils/brain-storage.d.ts +28 -0
- package/dist/utils/brain-storage.d.ts.map +1 -0
- package/dist/utils/brain-storage.js +87 -0
- package/dist/utils/brain-storage.js.map +1 -0
- package/dist/utils/caption-probe.d.ts +7 -0
- package/dist/utils/caption-probe.d.ts.map +1 -0
- package/dist/utils/caption-probe.js +36 -0
- package/dist/utils/caption-probe.js.map +1 -0
- package/dist/utils/channel-images.d.ts +16 -0
- package/dist/utils/channel-images.d.ts.map +1 -0
- package/dist/utils/channel-images.js +35 -0
- package/dist/utils/channel-images.js.map +1 -0
- package/dist/utils/channel-lookup.d.ts +5 -0
- package/dist/utils/channel-lookup.d.ts.map +1 -0
- package/dist/utils/channel-lookup.js +37 -0
- package/dist/utils/channel-lookup.js.map +1 -0
- package/dist/utils/chapters.d.ts +11 -0
- package/dist/utils/chapters.d.ts.map +1 -0
- package/dist/utils/chapters.js +24 -0
- package/dist/utils/chapters.js.map +1 -0
- package/dist/utils/clips.d.ts.map +1 -1
- package/dist/utils/clips.js +22 -30
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/env.d.ts +26 -0
- package/dist/utils/env.d.ts.map +1 -0
- package/dist/utils/env.js +72 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/errors.d.ts +1 -1
- package/dist/utils/errors.d.ts.map +1 -1
- package/dist/utils/errors.js +20 -2
- package/dist/utils/errors.js.map +1 -1
- package/dist/utils/file-lock.d.ts +34 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +91 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/flat-listing.d.ts +89 -0
- package/dist/utils/flat-listing.d.ts.map +1 -0
- package/dist/utils/flat-listing.js +68 -0
- package/dist/utils/flat-listing.js.map +1 -0
- package/dist/utils/format.d.ts +1 -3
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +1 -5
- package/dist/utils/format.js.map +1 -1
- package/dist/utils/guard.d.ts +21 -0
- package/dist/utils/guard.d.ts.map +1 -0
- package/dist/utils/guard.js +105 -0
- package/dist/utils/guard.js.map +1 -0
- package/dist/utils/image-dimensions.d.ts +19 -0
- package/dist/utils/image-dimensions.d.ts.map +1 -0
- package/dist/utils/image-dimensions.js +93 -0
- package/dist/utils/image-dimensions.js.map +1 -0
- package/dist/utils/image-fetch.d.ts +31 -0
- package/dist/utils/image-fetch.d.ts.map +1 -0
- package/dist/utils/image-fetch.js +141 -0
- package/dist/utils/image-fetch.js.map +1 -0
- package/dist/utils/json-file.d.ts +45 -0
- package/dist/utils/json-file.d.ts.map +1 -0
- package/dist/utils/json-file.js +60 -0
- package/dist/utils/json-file.js.map +1 -0
- package/dist/utils/paths.d.ts +19 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +27 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/pattern.d.ts +30 -0
- package/dist/utils/pattern.d.ts.map +1 -0
- package/dist/utils/pattern.js +130 -0
- package/dist/utils/pattern.js.map +1 -0
- package/dist/utils/search-index.d.ts +22 -7
- package/dist/utils/search-index.d.ts.map +1 -1
- package/dist/utils/search-index.js +26 -8
- package/dist/utils/search-index.js.map +1 -1
- package/dist/utils/storage.d.ts +14 -44
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +47 -84
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/subtitles.d.ts +7 -0
- package/dist/utils/subtitles.d.ts.map +1 -0
- package/dist/utils/subtitles.js +30 -0
- package/dist/utils/subtitles.js.map +1 -0
- package/dist/utils/thumbnail-entry.d.ts +19 -0
- package/dist/utils/thumbnail-entry.d.ts.map +1 -0
- package/dist/utils/thumbnail-entry.js +119 -0
- package/dist/utils/thumbnail-entry.js.map +1 -0
- package/dist/utils/thumbnail-fetch.d.ts +37 -0
- package/dist/utils/thumbnail-fetch.d.ts.map +1 -0
- package/dist/utils/thumbnail-fetch.js +89 -0
- package/dist/utils/thumbnail-fetch.js.map +1 -0
- package/dist/utils/thumbnail-ladder.d.ts +44 -0
- package/dist/utils/thumbnail-ladder.d.ts.map +1 -0
- package/dist/utils/thumbnail-ladder.js +68 -0
- package/dist/utils/thumbnail-ladder.js.map +1 -0
- package/dist/utils/thumbnail-listing.d.ts +16 -0
- package/dist/utils/thumbnail-listing.d.ts.map +1 -0
- package/dist/utils/thumbnail-listing.js +26 -0
- package/dist/utils/thumbnail-listing.js.map +1 -0
- package/dist/utils/thumbnail-paths.d.ts +20 -0
- package/dist/utils/thumbnail-paths.d.ts.map +1 -0
- package/dist/utils/thumbnail-paths.js +35 -0
- package/dist/utils/thumbnail-paths.js.map +1 -0
- package/dist/utils/thumbnail-store.d.ts +19 -0
- package/dist/utils/thumbnail-store.d.ts.map +1 -0
- package/dist/utils/thumbnail-store.js +125 -0
- package/dist/utils/thumbnail-store.js.map +1 -0
- package/dist/utils/transcript-cache.d.ts +20 -0
- package/dist/utils/transcript-cache.d.ts.map +1 -0
- package/dist/utils/transcript-cache.js +127 -0
- package/dist/utils/transcript-cache.js.map +1 -0
- package/dist/utils/transcript-search.d.ts +20 -0
- package/dist/utils/transcript-search.d.ts.map +1 -0
- package/dist/utils/transcript-search.js +35 -0
- package/dist/utils/transcript-search.js.map +1 -0
- package/dist/utils/transcript.d.ts +14 -34
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +5 -64
- package/dist/utils/transcript.js.map +1 -1
- package/dist/utils/validate-youtube.d.ts +35 -0
- package/dist/utils/validate-youtube.d.ts.map +1 -0
- package/dist/utils/validate-youtube.js +87 -0
- package/dist/utils/validate-youtube.js.map +1 -0
- package/dist/utils/validate.d.ts +11 -3
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +43 -18
- package/dist/utils/validate.js.map +1 -1
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +19 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +44 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -0
- package/dist/utils/youtube-channel.js +61 -0
- package/dist/utils/youtube-channel.js.map +1 -0
- package/dist/utils/youtube-download.d.ts +27 -0
- package/dist/utils/youtube-download.d.ts.map +1 -0
- package/dist/utils/youtube-download.js +118 -0
- package/dist/utils/youtube-download.js.map +1 -0
- package/dist/utils/youtube-schemas.d.ts +394 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -0
- package/dist/utils/youtube-schemas.js +118 -0
- package/dist/utils/youtube-schemas.js.map +1 -0
- package/dist/utils/youtube-search.d.ts +14 -0
- package/dist/utils/youtube-search.d.ts.map +1 -0
- package/dist/utils/youtube-search.js +39 -0
- package/dist/utils/youtube-search.js.map +1 -0
- package/dist/utils/youtube-url.d.ts +25 -0
- package/dist/utils/youtube-url.d.ts.map +1 -0
- package/dist/utils/youtube-url.js +75 -0
- package/dist/utils/youtube-url.js.map +1 -0
- package/dist/utils/youtube-video.d.ts +47 -0
- package/dist/utils/youtube-video.d.ts.map +1 -0
- package/dist/utils/youtube-video.js +98 -0
- package/dist/utils/youtube-video.js.map +1 -0
- package/dist/utils/youtube.d.ts +13 -111
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +11 -480
- package/dist/utils/youtube.js.map +1 -1
- package/dist/utils/ytdlp-env.d.ts +33 -0
- package/dist/utils/ytdlp-env.d.ts.map +1 -0
- package/dist/utils/ytdlp-env.js +121 -0
- package/dist/utils/ytdlp-env.js.map +1 -0
- package/dist/utils/ytdlp-limiter.d.ts +24 -0
- package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
- package/dist/utils/ytdlp-limiter.js +69 -0
- package/dist/utils/ytdlp-limiter.js.map +1 -0
- package/dist/utils/ytdlp-parse.d.ts +13 -0
- package/dist/utils/ytdlp-parse.d.ts.map +1 -0
- package/dist/utils/ytdlp-parse.js +48 -0
- package/dist/utils/ytdlp-parse.js.map +1 -0
- package/dist/utils/ytdlp.d.ts +13 -18
- package/dist/utils/ytdlp.d.ts.map +1 -1
- package/dist/utils/ytdlp.js +21 -110
- package/dist/utils/ytdlp.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { YouTubeError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The identifiers and URLs this server is willing to hand to yt-dlp.
|
|
4
|
+
*
|
|
5
|
+
* A tool argument is model-controlled, and yt-dlp's generic extractor will
|
|
6
|
+
* fetch any URL it is given — including internal addresses — so anything that
|
|
7
|
+
* becomes a yt-dlp target is checked here first. Ids are also filenames, so the
|
|
8
|
+
* real formats are matched exactly rather than sanitised.
|
|
9
|
+
*/
|
|
10
|
+
/** YouTube video ids are 11 base64url characters. */
|
|
11
|
+
export const VIDEO_ID_PATTERN = /^[A-Za-z0-9_-]{11}$/;
|
|
12
|
+
export function assertVideoId(videoId) {
|
|
13
|
+
if (!VIDEO_ID_PATTERN.test(videoId)) {
|
|
14
|
+
throw new YouTubeError('INVALID_INPUT', `"${clip(videoId)}" is not a valid YouTube video id.`, {
|
|
15
|
+
nextStep: 'Video ids are 11 characters, for example dQw4w9WgXcQ.',
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return videoId;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* YouTube channel ids are `UC` followed by 22 base64url characters.
|
|
22
|
+
*
|
|
23
|
+
* This is stricter than it looks like it needs to be. A channel id arrives from
|
|
24
|
+
* yt-dlp's output and is then used as a directory name under
|
|
25
|
+
* `~/.youtube-knowledge/brains`, so a value containing a separator or `..`
|
|
26
|
+
* would write outside that tree. Matching the real format exactly is cheaper
|
|
27
|
+
* than sanitising a value that should never have been unusual.
|
|
28
|
+
*/
|
|
29
|
+
export const CHANNEL_ID_PATTERN = /^UC[A-Za-z0-9_-]{22}$/;
|
|
30
|
+
export function assertChannelId(channelId) {
|
|
31
|
+
if (!CHANNEL_ID_PATTERN.test(channelId)) {
|
|
32
|
+
throw new YouTubeError('INVALID_INPUT', `"${clip(channelId)}" is not a valid YouTube channel id.`, {
|
|
33
|
+
nextStep: 'Channel ids look like UCxxxxxxxxxxxxxxxxxxxxxx. Call get_channel_info or list_brains to get one.',
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
return channelId;
|
|
37
|
+
}
|
|
38
|
+
/** The hosts yt-dlp may be pointed at. Anything else is refused before it spawns. */
|
|
39
|
+
export const YOUTUBE_HOSTS = [
|
|
40
|
+
'youtube.com',
|
|
41
|
+
'www.youtube.com',
|
|
42
|
+
'm.youtube.com',
|
|
43
|
+
'music.youtube.com',
|
|
44
|
+
'youtu.be',
|
|
45
|
+
'www.youtube-nocookie.com',
|
|
46
|
+
];
|
|
47
|
+
const HOST_SET = new Set(YOUTUBE_HOSTS);
|
|
48
|
+
const SCHEMELESS_YOUTUBE = /^(?:www\.|m\.|music\.)?(?:youtube\.com|youtu\.be)\//i;
|
|
49
|
+
/**
|
|
50
|
+
* Validate a caller-supplied URL and return it normalised to https.
|
|
51
|
+
*
|
|
52
|
+
* `http:` is upgraded rather than refused, and a URL written without a scheme
|
|
53
|
+
* but starting with a YouTube host is accepted, because both are things people
|
|
54
|
+
* paste. Everything else — other hosts, other schemes, credentials, ports — is
|
|
55
|
+
* an `INVALID_INPUT`, since the only reason to send one is to make yt-dlp fetch
|
|
56
|
+
* something that is not YouTube.
|
|
57
|
+
*/
|
|
58
|
+
export function assertYouTubeUrl(input) {
|
|
59
|
+
const trimmed = input.trim();
|
|
60
|
+
const candidate = SCHEMELESS_YOUTUBE.test(trimmed) ? `https://${trimmed}` : trimmed;
|
|
61
|
+
let url;
|
|
62
|
+
try {
|
|
63
|
+
url = new URL(candidate);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
throw notYouTube(input);
|
|
67
|
+
}
|
|
68
|
+
if (url.protocol === 'http:')
|
|
69
|
+
url.protocol = 'https:';
|
|
70
|
+
const wrongScheme = url.protocol !== 'https:';
|
|
71
|
+
const wrongHost = !HOST_SET.has(url.hostname.toLowerCase());
|
|
72
|
+
const hasCredentials = url.username !== '' || url.password !== '';
|
|
73
|
+
const hasPort = url.port !== '';
|
|
74
|
+
if (wrongScheme || wrongHost || hasCredentials || hasPort)
|
|
75
|
+
throw notYouTube(input);
|
|
76
|
+
return url.toString();
|
|
77
|
+
}
|
|
78
|
+
function notYouTube(input) {
|
|
79
|
+
return new YouTubeError('INVALID_INPUT', `"${clip(input)}" is not a YouTube URL.`, {
|
|
80
|
+
nextStep: `Pass an https URL on one of: ${YOUTUBE_HOSTS.join(', ')}.`,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** Keeps a rejected value from flooding the error message it is quoted in. */
|
|
84
|
+
function clip(value) {
|
|
85
|
+
return value.length > 64 ? `${value.slice(0, 61)}...` : value;
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=validate-youtube.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-youtube.js","sourceRoot":"","sources":["../../src/utils/validate-youtube.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C;;;;;;;GAOG;AAEH,qDAAqD;AACrD,MAAM,CAAC,MAAM,gBAAgB,GAAG,qBAAqB,CAAC;AAEtD,MAAM,UAAU,aAAa,CAAC,OAAe;IAC3C,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACpC,MAAM,IAAI,YAAY,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,OAAO,CAAC,oCAAoC,EAAE;YAC7F,QAAQ,EAAE,uDAAuD;SAClE,CAAC,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,uBAAuB,CAAC;AAE1D,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,YAAY,CACpB,eAAe,EACf,IAAI,IAAI,CAAC,SAAS,CAAC,sCAAsC,EACzD;YACE,QAAQ,EACN,kGAAkG;SACrG,CACF,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,qFAAqF;AACrF,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,aAAa;IACb,iBAAiB;IACjB,eAAe;IACf,mBAAmB;IACnB,UAAU;IACV,0BAA0B;CAClB,CAAC;AAEX,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAS,aAAa,CAAC,CAAC;AAChD,MAAM,kBAAkB,GAAG,sDAAsD,CAAC;AAElF;;;;;;;;GAQG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAa;IAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,MAAM,SAAS,GAAG,kBAAkB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,WAAW,OAAO,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;IAEpF,IAAI,GAAQ,CAAC;IACb,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,GAAG,CAAC,SAAS,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAC1B,CAAC;IAED,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO;QAAE,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IAEtD,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC;IAC9C,MAAM,SAAS,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAC5D,MAAM,cAAc,GAAG,GAAG,CAAC,QAAQ,KAAK,EAAE,IAAI,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAC;IAClE,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,KAAK,EAAE,CAAC;IAEhC,IAAI,WAAW,IAAI,SAAS,IAAI,cAAc,IAAI,OAAO;QAAE,MAAM,UAAU,CAAC,KAAK,CAAC,CAAC;IAEnF,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,IAAI,YAAY,CAAC,eAAe,EAAE,IAAI,IAAI,CAAC,KAAK,CAAC,yBAAyB,EAAE;QACjF,QAAQ,EAAE,gCAAgC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;KACtE,CAAC,CAAC;AACL,CAAC;AAED,8EAA8E;AAC9E,SAAS,IAAI,CAAC,KAAa;IACzB,OAAO,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;AAChE,CAAC"}
|
package/dist/utils/validate.d.ts
CHANGED
|
@@ -1,16 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Input validation for the values that reach the filesystem or yt-dlp's
|
|
3
|
-
* language negotiation.
|
|
3
|
+
* language negotiation. The YouTube-specific checks — ids and URLs — live in
|
|
4
|
+
* validate-youtube.ts and are re-exported here. Zod already constrains shapes at the tool boundary;
|
|
4
5
|
* these are the checks Zod cannot express.
|
|
5
6
|
*/
|
|
6
7
|
/**
|
|
7
|
-
*
|
|
8
|
+
* Prove that a caller-supplied path lies inside the home directory.
|
|
8
9
|
*
|
|
9
10
|
* A tool argument is model-controlled, so it must not be able to write outside
|
|
10
|
-
* the user's home directory or reach the filesystem root through `..`.
|
|
11
|
+
* the user's home directory or reach the filesystem root through `..`. The
|
|
12
|
+
* check is made on the real path: a symlink inside home that points outside it
|
|
13
|
+
* passed a purely lexical comparison, and yt-dlp and ffmpeg then wrote through
|
|
14
|
+
* it. The nearest existing ancestor is resolved and the missing tail re-joined,
|
|
15
|
+
* so a directory that does not exist yet is judged by where it would be created.
|
|
11
16
|
*/
|
|
17
|
+
export declare function assertInsideHome(input: string, what: string): string;
|
|
18
|
+
/** Resolve a caller-supplied output directory, or the fallback when none is given. */
|
|
12
19
|
export declare function resolveOutputDir(outputDir: string | undefined, fallback: string): string;
|
|
13
20
|
export declare function assertLanguageTag(language: string): string;
|
|
14
21
|
/** Accepts `90`, `1:30`, `01:02:03`, `1:30.5` and returns seconds. */
|
|
15
22
|
export declare function parseTimestamp(value: string | number, field: string): number;
|
|
23
|
+
export * from './validate-youtube.js';
|
|
16
24
|
//# sourceMappingURL=validate.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"validate.d.ts","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"AAKA;;;;;GAKG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAqBpE;AAkBD,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGxF;AAKD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAO1D;AAED,sEAAsE;AACtE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAoB5E;AAED,cAAc,uBAAuB,CAAC"}
|
package/dist/utils/validate.js
CHANGED
|
@@ -1,35 +1,59 @@
|
|
|
1
|
+
import { existsSync, realpathSync } from 'node:fs';
|
|
1
2
|
import { homedir } from 'node:os';
|
|
2
|
-
import { isAbsolute, normalize, resolve, sep } from 'node:path';
|
|
3
|
+
import { basename, dirname, isAbsolute, join, normalize, resolve, sep } from 'node:path';
|
|
3
4
|
import { YouTubeError } from './errors.js';
|
|
4
5
|
/**
|
|
5
6
|
* Input validation for the values that reach the filesystem or yt-dlp's
|
|
6
|
-
* language negotiation.
|
|
7
|
+
* language negotiation. The YouTube-specific checks — ids and URLs — live in
|
|
8
|
+
* validate-youtube.ts and are re-exported here. Zod already constrains shapes at the tool boundary;
|
|
7
9
|
* these are the checks Zod cannot express.
|
|
8
10
|
*/
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* Prove that a caller-supplied path lies inside the home directory.
|
|
11
13
|
*
|
|
12
14
|
* A tool argument is model-controlled, so it must not be able to write outside
|
|
13
|
-
* the user's home directory or reach the filesystem root through `..`.
|
|
15
|
+
* the user's home directory or reach the filesystem root through `..`. The
|
|
16
|
+
* check is made on the real path: a symlink inside home that points outside it
|
|
17
|
+
* passed a purely lexical comparison, and yt-dlp and ffmpeg then wrote through
|
|
18
|
+
* it. The nearest existing ancestor is resolved and the missing tail re-joined,
|
|
19
|
+
* so a directory that does not exist yet is judged by where it would be created.
|
|
14
20
|
*/
|
|
15
|
-
export function
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
? resolve(homedir(), outputDir.slice(1).replace(/^[/\\]/, ''))
|
|
20
|
-
: resolve(normalize(outputDir));
|
|
21
|
-
if (expanded.includes('\0')) {
|
|
22
|
-
throw new YouTubeError('INVALID_INPUT', 'The output directory path is not valid.', {
|
|
23
|
-
nextStep: 'Provide a plain directory path, for example ~/Videos.',
|
|
21
|
+
export function assertInsideHome(input, what) {
|
|
22
|
+
if (input.includes('\0')) {
|
|
23
|
+
throw new YouTubeError('INVALID_INPUT', `The ${what} path is not valid.`, {
|
|
24
|
+
nextStep: 'Provide a plain path, for example ~/Videos.',
|
|
24
25
|
});
|
|
25
26
|
}
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
const expanded = input.startsWith('~')
|
|
28
|
+
? resolve(homedir(), input.slice(1).replace(/^[/\\]/, ''))
|
|
29
|
+
: resolve(normalize(input));
|
|
30
|
+
const home = realpathSync(homedir());
|
|
31
|
+
const real = realpathOfNearestAncestor(expanded);
|
|
32
|
+
if (!isAbsolute(real) || !(real === home || real.startsWith(home + sep))) {
|
|
33
|
+
throw new YouTubeError('INVALID_INPUT', `The ${what} must be inside your home directory.`, {
|
|
34
|
+
nextStep: `Choose a path under ${home}, or omit it to use the default location.`,
|
|
30
35
|
});
|
|
31
36
|
}
|
|
32
|
-
return
|
|
37
|
+
return real;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The real path of `target`, resolving symlinks in whichever prefix of it
|
|
41
|
+
* exists. The walk stops at the filesystem root, which always exists.
|
|
42
|
+
*/
|
|
43
|
+
function realpathOfNearestAncestor(target) {
|
|
44
|
+
const missing = [];
|
|
45
|
+
let probe = target;
|
|
46
|
+
while (dirname(probe) !== probe && !existsSync(probe)) {
|
|
47
|
+
missing.unshift(basename(probe));
|
|
48
|
+
probe = dirname(probe);
|
|
49
|
+
}
|
|
50
|
+
return join(realpathSync(probe), ...missing);
|
|
51
|
+
}
|
|
52
|
+
/** Resolve a caller-supplied output directory, or the fallback when none is given. */
|
|
53
|
+
export function resolveOutputDir(outputDir, fallback) {
|
|
54
|
+
if (outputDir === undefined || outputDir.trim() === '')
|
|
55
|
+
return fallback;
|
|
56
|
+
return assertInsideHome(outputDir, 'output directory');
|
|
33
57
|
}
|
|
34
58
|
/** BCP-47-ish: `en`, `pt-BR`, `zh-Hans`, plus yt-dlp's `-orig` suffix. */
|
|
35
59
|
const LANGUAGE_PATTERN = /^[a-zA-Z]{2,8}(?:-[a-zA-Z0-9]{2,8})*$/;
|
|
@@ -61,4 +85,5 @@ export function parseTimestamp(value, field) {
|
|
|
61
85
|
// The hours group is optional, so it is genuinely absent for "MM:SS" input.
|
|
62
86
|
return Number(match[1] ?? 0) * 3600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0);
|
|
63
87
|
}
|
|
88
|
+
export * from './validate-youtube.js';
|
|
64
89
|
//# sourceMappingURL=validate.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../src/utils/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAkBA,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { z } from 'zod';
|
|
5
|
+
/**
|
|
6
|
+
* The version this build reports.
|
|
7
|
+
*
|
|
8
|
+
* Advertised in the MCP initialize response and on the authenticated health
|
|
9
|
+
* endpoint, so a package.json that has lost it should fail loudly at startup
|
|
10
|
+
* rather than telling every client the server is version `undefined`.
|
|
11
|
+
*/
|
|
12
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const pkg = z
|
|
14
|
+
.object({ version: z.string() })
|
|
15
|
+
.parse(JSON.parse(readFileSync(join(here, '..', '..', 'package.json'), 'utf-8')));
|
|
16
|
+
export function serverVersion() {
|
|
17
|
+
return pkg.version;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;;GAMG;AACH,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,GAAG,GAAG,CAAC;KACV,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;KAC/B,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;AAEpF,MAAM,UAAU,aAAa;IAC3B,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ListedImage } from './flat-listing.js';
|
|
2
|
+
/** Listings and metadata for channels and playlists. */
|
|
3
|
+
export interface VideoListItem {
|
|
4
|
+
id: string;
|
|
5
|
+
title: string;
|
|
6
|
+
duration: number;
|
|
7
|
+
durationFormatted: string;
|
|
8
|
+
/** Empty for a flat listing, which carries no publication date. */
|
|
9
|
+
uploadDate: string;
|
|
10
|
+
url: string;
|
|
11
|
+
/** The largest thumbnail the listing offered. */
|
|
12
|
+
thumbnailUrl?: string;
|
|
13
|
+
/** Every thumbnail the listing offered, with sizes where known. */
|
|
14
|
+
thumbnails?: ListedImage[];
|
|
15
|
+
viewCount?: number;
|
|
16
|
+
liveStatus?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface ChannelInfo {
|
|
19
|
+
name: string;
|
|
20
|
+
channelId: string;
|
|
21
|
+
handle: string;
|
|
22
|
+
subscriberCount: number;
|
|
23
|
+
channelUrl: string;
|
|
24
|
+
description: string;
|
|
25
|
+
/** The channel's uncropped avatar, when yt-dlp listed one. */
|
|
26
|
+
avatarUrl?: string;
|
|
27
|
+
/** The channel's uncropped banner, when yt-dlp listed one. */
|
|
28
|
+
bannerUrl?: string;
|
|
29
|
+
}
|
|
30
|
+
export interface PlaylistInfo {
|
|
31
|
+
id: string;
|
|
32
|
+
title: string;
|
|
33
|
+
channel: string;
|
|
34
|
+
handle: string;
|
|
35
|
+
channelUrl: string;
|
|
36
|
+
videoCount: number;
|
|
37
|
+
lastModified: string;
|
|
38
|
+
url: string;
|
|
39
|
+
description: string;
|
|
40
|
+
}
|
|
41
|
+
export declare function listVideos(urlOrChannel: string, limit?: number): Promise<VideoListItem[]>;
|
|
42
|
+
export declare function getPlaylistInfo(playlistUrl: string): Promise<PlaylistInfo>;
|
|
43
|
+
export declare function getChannelInfo(channel: string): Promise<ChannelInfo>;
|
|
44
|
+
//# sourceMappingURL=youtube-channel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube-channel.d.ts","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,WAAW,EACjB,MAAM,mBAAmB,CAAC;AAK3B,wDAAwD;AAExD,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mEAAmE;IACnE,UAAU,CAAC,EAAE,WAAW,EAAE,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,wBAAsB,UAAU,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,SAAK,GAAG,OAAO,CAAC,aAAa,EAAE,CAAC,CAkB3F;AAED,wBAAsB,eAAe,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAwBhF;AAED,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAwB1E"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { formatYouTubeDate } from './format.js';
|
|
2
|
+
import { TIMEOUTS, parseYtDlpJson, parseYtDlpJsonLines, runYtDlp } from './ytdlp.js';
|
|
3
|
+
import { FLAT_PRINT_TEMPLATE, flatEntrySchema, toVideoListItem, } from './flat-listing.js';
|
|
4
|
+
import { selectChannelImages } from './channel-images.js';
|
|
5
|
+
import { channelMetaSchema, playlistMetaSchema } from './youtube-schemas.js';
|
|
6
|
+
import { channelUrlFor, resolveListTarget } from './youtube-url.js';
|
|
7
|
+
export async function listVideos(urlOrChannel, limit = 20) {
|
|
8
|
+
const stdout = await runYtDlp([
|
|
9
|
+
'--skip-download',
|
|
10
|
+
'--flat-playlist',
|
|
11
|
+
'--print',
|
|
12
|
+
FLAT_PRINT_TEMPLATE,
|
|
13
|
+
'--playlist-end',
|
|
14
|
+
limit.toString(),
|
|
15
|
+
], {
|
|
16
|
+
label: 'fetch_videos',
|
|
17
|
+
timeoutMs: TIMEOUTS.transcript,
|
|
18
|
+
target: resolveListTarget(urlOrChannel),
|
|
19
|
+
});
|
|
20
|
+
return parseYtDlpJsonLines(stdout, flatEntrySchema).map(toVideoListItem);
|
|
21
|
+
}
|
|
22
|
+
export async function getPlaylistInfo(playlistUrl) {
|
|
23
|
+
const target = resolveListTarget(playlistUrl);
|
|
24
|
+
const stdout = await runYtDlp(['--dump-single-json', '--flat-playlist', '--playlist-items', '0'], {
|
|
25
|
+
label: 'get_playlist_info',
|
|
26
|
+
target,
|
|
27
|
+
});
|
|
28
|
+
const data = parseYtDlpJson(stdout, playlistMetaSchema, 'playlist metadata');
|
|
29
|
+
const modDate = data.modified_date ?? '';
|
|
30
|
+
return {
|
|
31
|
+
id: data.id ?? '',
|
|
32
|
+
title: data.title ?? 'Unknown',
|
|
33
|
+
channel: data.channel ?? '',
|
|
34
|
+
handle: data.uploader_id ?? '',
|
|
35
|
+
channelUrl: data.channel_url ?? '',
|
|
36
|
+
videoCount: data.playlist_count ?? 0,
|
|
37
|
+
lastModified: formatYouTubeDate(modDate),
|
|
38
|
+
url: data.webpage_url ?? playlistUrl,
|
|
39
|
+
description: data.description ?? '',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export async function getChannelInfo(channel) {
|
|
43
|
+
const channelUrl = channelUrlFor(channel);
|
|
44
|
+
const stdout = await runYtDlp(['--dump-single-json', '--flat-playlist', '--playlist-items', '0'], {
|
|
45
|
+
label: 'get_channel_info',
|
|
46
|
+
target: channelUrl,
|
|
47
|
+
});
|
|
48
|
+
const data = parseYtDlpJson(stdout, channelMetaSchema, 'channel metadata');
|
|
49
|
+
return {
|
|
50
|
+
name: data.channel ?? 'Unknown',
|
|
51
|
+
channelId: data.channel_id ?? '',
|
|
52
|
+
handle: data.uploader_id ?? '',
|
|
53
|
+
subscriberCount: data.channel_follower_count ?? 0,
|
|
54
|
+
channelUrl: data.channel_url ?? channelUrl,
|
|
55
|
+
description: data.description ?? '',
|
|
56
|
+
// The same listing carries the avatar and banner; they were discarded here
|
|
57
|
+
// for two releases.
|
|
58
|
+
...selectChannelImages(data.thumbnails),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=youtube-channel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube-channel.js","sourceRoot":"","sources":["../../src/utils/youtube-channel.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface VideoFormat {
|
|
2
|
+
formatId: string;
|
|
3
|
+
ext: string;
|
|
4
|
+
resolution: string;
|
|
5
|
+
fps?: number;
|
|
6
|
+
vcodec: string;
|
|
7
|
+
acodec: string;
|
|
8
|
+
filesize?: number;
|
|
9
|
+
note: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DownloadResult {
|
|
12
|
+
videoId: string;
|
|
13
|
+
title: string;
|
|
14
|
+
filePath: string;
|
|
15
|
+
format: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function listFormats(urlOrId: string): Promise<VideoFormat[]>;
|
|
18
|
+
export type VideoQuality = 'best' | '2160p' | '1440p' | '1080p' | '720p' | '480p' | '360p' | 'audio';
|
|
19
|
+
export declare const QUALITY_FORMAT_SELECTORS: Record<VideoQuality, string>;
|
|
20
|
+
export declare function downloadVideo(urlOrId: string, formatId: string, outputDir?: string, quality?: VideoQuality): Promise<DownloadResult>;
|
|
21
|
+
/** The after_move print: the last JSON line yt-dlp wrote, with the path it wrote to. */
|
|
22
|
+
export declare const AFTER_MOVE_TEMPLATE = "after_move:%(.{title,filepath})j";
|
|
23
|
+
export declare function readAfterMove(stdout: string): {
|
|
24
|
+
title: string | undefined;
|
|
25
|
+
filepath: string;
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=youtube-download.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube-download.d.ts","sourceRoot":"","sources":["../../src/utils/youtube-download.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAyBzE;AAED,MAAM,MAAM,YAAY,GACtB,MAAM,GAAG,OAAO,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;AAG5E,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAejE,CAAC;AAEF,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,EAClB,OAAO,CAAC,EAAE,YAAY,GACrB,OAAO,CAAC,cAAc,CAAC,CA+DzB;AAED,wFAAwF;AACxF,eAAO,MAAM,mBAAmB,qCAAqC,CAAC;AAEtE,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAa7F"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { join } from 'path';
|
|
2
|
+
import { mkdir } from 'fs/promises';
|
|
3
|
+
import { TIMEOUTS, parseYtDlpJson, runYtDlp } from './ytdlp.js';
|
|
4
|
+
import { YouTubeError } from './errors.js';
|
|
5
|
+
import { afterMoveRowSchema, formatsRowSchema } from './youtube-schemas.js';
|
|
6
|
+
import { resolveOutputDir } from './validate.js';
|
|
7
|
+
import { log } from './context.js';
|
|
8
|
+
import { dataDir } from './paths.js';
|
|
9
|
+
import { extractVideoId, watchUrl } from './youtube-url.js';
|
|
10
|
+
/** Format listing and whole-video downloads. */
|
|
11
|
+
const DOWNLOADS_DIR = dataDir('downloads');
|
|
12
|
+
export async function listFormats(urlOrId) {
|
|
13
|
+
const videoId = extractVideoId(urlOrId);
|
|
14
|
+
const url = watchUrl(videoId);
|
|
15
|
+
const stdout = await runYtDlp(['-j', '--skip-download'], { label: 'list_formats', target: url });
|
|
16
|
+
const data = parseYtDlpJson(stdout, formatsRowSchema, 'video formats');
|
|
17
|
+
const formats = data.formats ?? [];
|
|
18
|
+
return formats
|
|
19
|
+
.filter((f) => !f.format_id.startsWith('sb')) // skip storyboards
|
|
20
|
+
.map((f) => {
|
|
21
|
+
const resolution = f.resolution ?? (f.width && f.height ? `${f.width}x${f.height}` : 'audio only');
|
|
22
|
+
return {
|
|
23
|
+
formatId: f.format_id,
|
|
24
|
+
ext: f.ext,
|
|
25
|
+
resolution,
|
|
26
|
+
fps: f.fps ?? undefined,
|
|
27
|
+
vcodec: f.vcodec ?? 'none',
|
|
28
|
+
acodec: f.acodec ?? 'none',
|
|
29
|
+
filesize: f.filesize ?? f.filesize_approx ?? undefined,
|
|
30
|
+
note: f.format_note ?? '',
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
// Smart format selectors that use yt-dlp's fallback syntax
|
|
35
|
+
export const QUALITY_FORMAT_SELECTORS = {
|
|
36
|
+
best: 'bestvideo*+bestaudio/best',
|
|
37
|
+
'2160p': 'bestvideo[height<=2160]+bestaudio/bestvideo*[height<=2160]+bestaudio/best[height<=2160]/bestvideo+bestaudio/best',
|
|
38
|
+
'1440p': 'bestvideo[height<=1440]+bestaudio/bestvideo*[height<=1440]+bestaudio/best[height<=1440]/bestvideo+bestaudio/best',
|
|
39
|
+
'1080p': 'bestvideo[height<=1080]+bestaudio/bestvideo*[height<=1080]+bestaudio/best[height<=1080]/bestvideo+bestaudio/best',
|
|
40
|
+
'720p': 'bestvideo[height<=720]+bestaudio/bestvideo*[height<=720]+bestaudio/best[height<=720]/bestvideo+bestaudio/best',
|
|
41
|
+
'480p': 'bestvideo[height<=480]+bestaudio/bestvideo*[height<=480]+bestaudio/best[height<=480]/bestvideo+bestaudio/best',
|
|
42
|
+
'360p': 'bestvideo[height<=360]+bestaudio/bestvideo*[height<=360]+bestaudio/best[height<=360]/bestvideo+bestaudio/best',
|
|
43
|
+
audio: 'bestaudio/best',
|
|
44
|
+
};
|
|
45
|
+
export async function downloadVideo(urlOrId, formatId, outputDir, quality) {
|
|
46
|
+
const videoId = extractVideoId(urlOrId);
|
|
47
|
+
const url = watchUrl(videoId);
|
|
48
|
+
const targetDir = resolveOutputDir(outputDir, DOWNLOADS_DIR);
|
|
49
|
+
// Ensure download directory exists
|
|
50
|
+
await mkdir(targetDir, { recursive: true });
|
|
51
|
+
// Download with specified format
|
|
52
|
+
const outputTemplate = join(targetDir, '%(title)s.%(ext)s');
|
|
53
|
+
// A quality preset always wins over formatId; that is what the tool schema
|
|
54
|
+
// promises, and the two are resolved in exactly one place.
|
|
55
|
+
const formatSelector = quality ? QUALITY_FORMAT_SELECTORS[quality] : formatId;
|
|
56
|
+
// -S vcodec:h264,acodec:m4a prefers H.264+AAC, which merge cleanly into MP4.
|
|
57
|
+
// The after_move print reports the title and the final path from the same
|
|
58
|
+
// run, once the merge has produced the file, so nothing is spawned twice.
|
|
59
|
+
const commonArgs = (selector) => [
|
|
60
|
+
'-f',
|
|
61
|
+
selector,
|
|
62
|
+
'-S',
|
|
63
|
+
'vcodec:h264,acodec:m4a',
|
|
64
|
+
'-o',
|
|
65
|
+
outputTemplate,
|
|
66
|
+
'--no-playlist',
|
|
67
|
+
'--merge-output-format',
|
|
68
|
+
'mp4',
|
|
69
|
+
'--print',
|
|
70
|
+
AFTER_MOVE_TEMPLATE,
|
|
71
|
+
];
|
|
72
|
+
// Transfers are not retried automatically: a partial file on disk plus a
|
|
73
|
+
// silent second attempt is worse than one clear failure.
|
|
74
|
+
const downloadOptions = {
|
|
75
|
+
label: 'download_video',
|
|
76
|
+
timeoutMs: TIMEOUTS.download,
|
|
77
|
+
retry: false,
|
|
78
|
+
target: url,
|
|
79
|
+
};
|
|
80
|
+
let stdout;
|
|
81
|
+
try {
|
|
82
|
+
stdout = await runYtDlp(commonArgs(formatSelector), downloadOptions);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// An explicitly requested format may simply not exist for this video, so
|
|
86
|
+
// falling back to "best" is worth one attempt. A preset failing is not:
|
|
87
|
+
// presets already encode their own fallback chain, and "best" failing
|
|
88
|
+
// leaves nothing to fall back to.
|
|
89
|
+
const alreadyBroadest = quality !== undefined || formatId === 'best';
|
|
90
|
+
if (alreadyBroadest)
|
|
91
|
+
throw error;
|
|
92
|
+
log('warning', `format ${formatId} unavailable, falling back to best`);
|
|
93
|
+
stdout = await runYtDlp(commonArgs(QUALITY_FORMAT_SELECTORS.best), downloadOptions);
|
|
94
|
+
}
|
|
95
|
+
const moved = readAfterMove(stdout);
|
|
96
|
+
return {
|
|
97
|
+
videoId,
|
|
98
|
+
title: moved.title ?? '',
|
|
99
|
+
filePath: moved.filepath,
|
|
100
|
+
format: quality ?? formatId,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
/** The after_move print: the last JSON line yt-dlp wrote, with the path it wrote to. */
|
|
104
|
+
export const AFTER_MOVE_TEMPLATE = 'after_move:%(.{title,filepath})j';
|
|
105
|
+
export function readAfterMove(stdout) {
|
|
106
|
+
const line = stdout.trim().split('\n').filter(Boolean).at(-1) ?? '';
|
|
107
|
+
const row = line === '' ? { title: undefined, filepath: undefined } : parseAfterMove(line);
|
|
108
|
+
if (typeof row.filepath !== 'string' || row.filepath === '') {
|
|
109
|
+
throw new YouTubeError('YTDLP_FAILED', 'yt-dlp finished without reporting where it wrote the file.', {
|
|
110
|
+
nextStep: 'Run `yt-dlp -U`; older versions do not print the after_move stage.',
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return { title: row.title ?? undefined, filepath: row.filepath };
|
|
114
|
+
}
|
|
115
|
+
function parseAfterMove(line) {
|
|
116
|
+
return parseYtDlpJson(line, afterMoveRowSchema, 'download result');
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=youtube-download.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"youtube-download.js","sourceRoot":"","sources":["../../src/utils/youtube-download.ts"],"names":[],"mappings":"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"}
|