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,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading an image's format and size from its first bytes.
|
|
3
|
+
*
|
|
4
|
+
* The manifest a thumbnail job writes records what was actually saved — a
|
|
5
|
+
* JPEG of 1280 by 720, say — and it must not take that from the URL's name or
|
|
6
|
+
* the server's Content-Type, both of which describe what was asked for rather
|
|
7
|
+
* than what arrived. Three container formats cover what YouTube's image hosts
|
|
8
|
+
* serve; anything else is reported as unknown rather than guessed.
|
|
9
|
+
*/
|
|
10
|
+
const JPEG_SIGNATURE = [0xff, 0xd8, 0xff];
|
|
11
|
+
const PNG_SIGNATURE = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a];
|
|
12
|
+
function startsWith(bytes, signature, at = 0) {
|
|
13
|
+
return signature.every((byte, index) => bytes[at + index] === byte);
|
|
14
|
+
}
|
|
15
|
+
function ascii(bytes, from, length) {
|
|
16
|
+
return String.fromCharCode(...bytes.subarray(from, from + length));
|
|
17
|
+
}
|
|
18
|
+
/** Every caller checks the length first, so a read here is always in range. */
|
|
19
|
+
function view(bytes) {
|
|
20
|
+
return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
21
|
+
}
|
|
22
|
+
function be16(bytes, at) {
|
|
23
|
+
return view(bytes).getUint16(at);
|
|
24
|
+
}
|
|
25
|
+
function be32(bytes, at) {
|
|
26
|
+
return view(bytes).getUint32(at);
|
|
27
|
+
}
|
|
28
|
+
function le16(bytes, at) {
|
|
29
|
+
return view(bytes).getUint16(at, true);
|
|
30
|
+
}
|
|
31
|
+
function le24(bytes, at) {
|
|
32
|
+
return le16(bytes, at) | (view(bytes).getUint8(at + 2) << 16);
|
|
33
|
+
}
|
|
34
|
+
/** Start-of-frame markers carry the dimensions; C4, C8 and CC are other things. */
|
|
35
|
+
function isStartOfFrame(marker) {
|
|
36
|
+
return marker >= 0xc0 && marker <= 0xcf && marker !== 0xc4 && marker !== 0xc8 && marker !== 0xcc;
|
|
37
|
+
}
|
|
38
|
+
function probeJpeg(bytes) {
|
|
39
|
+
let at = 2;
|
|
40
|
+
while (at + 9 < bytes.length) {
|
|
41
|
+
if (bytes[at] !== 0xff)
|
|
42
|
+
return undefined;
|
|
43
|
+
const marker = view(bytes).getUint8(at + 1);
|
|
44
|
+
// Standalone markers have no length field.
|
|
45
|
+
if (marker === 0xd8 || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd7)) {
|
|
46
|
+
at += 2;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Scan data or end of image: no frame header was found before it.
|
|
50
|
+
if (marker === 0xda || marker === 0xd9)
|
|
51
|
+
return undefined;
|
|
52
|
+
if (isStartOfFrame(marker)) {
|
|
53
|
+
return sized('jpg', 'image/jpeg', be16(bytes, at + 7), be16(bytes, at + 5));
|
|
54
|
+
}
|
|
55
|
+
at += 2 + be16(bytes, at + 2);
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
function probePng(bytes) {
|
|
60
|
+
if (bytes.length < 24 || ascii(bytes, 12, 4) !== 'IHDR')
|
|
61
|
+
return undefined;
|
|
62
|
+
return sized('png', 'image/png', be32(bytes, 16), be32(bytes, 20));
|
|
63
|
+
}
|
|
64
|
+
function probeWebp(bytes) {
|
|
65
|
+
if (bytes.length < 30 || ascii(bytes, 8, 4) !== 'WEBP')
|
|
66
|
+
return undefined;
|
|
67
|
+
switch (ascii(bytes, 12, 4)) {
|
|
68
|
+
case 'VP8 ':
|
|
69
|
+
return sized('webp', 'image/webp', le16(bytes, 26) & 0x3fff, le16(bytes, 28) & 0x3fff);
|
|
70
|
+
case 'VP8L': {
|
|
71
|
+
const bits = view(bytes).getUint32(21, true);
|
|
72
|
+
return sized('webp', 'image/webp', (bits & 0x3fff) + 1, ((bits >>> 14) & 0x3fff) + 1);
|
|
73
|
+
}
|
|
74
|
+
case 'VP8X':
|
|
75
|
+
return sized('webp', 'image/webp', le24(bytes, 24) + 1, le24(bytes, 27) + 1);
|
|
76
|
+
default:
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function sized(format, mimeType, width, height) {
|
|
81
|
+
return width > 0 && height > 0 ? { format, mimeType, width, height } : undefined;
|
|
82
|
+
}
|
|
83
|
+
/** The format and pixel size of an image, or `undefined` when the bytes are not one this reads. */
|
|
84
|
+
export function probeImage(bytes) {
|
|
85
|
+
if (startsWith(bytes, JPEG_SIGNATURE))
|
|
86
|
+
return probeJpeg(bytes);
|
|
87
|
+
if (startsWith(bytes, PNG_SIGNATURE))
|
|
88
|
+
return probePng(bytes);
|
|
89
|
+
if (bytes.length >= 12 && ascii(bytes, 0, 4) === 'RIFF')
|
|
90
|
+
return probeWebp(bytes);
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
//# sourceMappingURL=image-dimensions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-dimensions.js","sourceRoot":"","sources":["../../src/utils/image-dimensions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place this server fetches anything over HTTP itself.
|
|
3
|
+
*
|
|
4
|
+
* Everything else goes through yt-dlp. Thumbnails are the exception because
|
|
5
|
+
* their URLs are known from a single channel listing, and asking yt-dlp to
|
|
6
|
+
* extract each video just to download its image would multiply the requests
|
|
7
|
+
* YouTube sees by the size of the channel. The fetch is deliberately narrow:
|
|
8
|
+
* an allowlist of image hosts, https only, redirects re-checked hop by hop, a
|
|
9
|
+
* timeout, a byte cap enforced while reading, and an image content type.
|
|
10
|
+
*/
|
|
11
|
+
export declare const IMAGE_HOSTS: readonly ["i.ytimg.com", "yt3.googleusercontent.com", "yt3.ggpht.com"];
|
|
12
|
+
export declare const IMAGE_TIMEOUT_MS = 20000;
|
|
13
|
+
/** Cap for images written to disk. */
|
|
14
|
+
export declare const MAX_IMAGE_BYTES: number;
|
|
15
|
+
/** Cap for images returned inline as base64; a 1280x720 JPEG is about 140 KB. */
|
|
16
|
+
export declare const MAX_INLINE_IMAGE_BYTES: number;
|
|
17
|
+
export interface FetchImageOptions {
|
|
18
|
+
maxBytes?: number;
|
|
19
|
+
timeoutMs?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface FetchedImage {
|
|
22
|
+
bytes: Buffer;
|
|
23
|
+
contentType: string;
|
|
24
|
+
/** The URL the bytes came from, after any redirects. */
|
|
25
|
+
url: string;
|
|
26
|
+
}
|
|
27
|
+
/** Validate an image URL against the allowlist, returning it parsed. */
|
|
28
|
+
export declare function assertImageUrl(input: string): URL;
|
|
29
|
+
/** Fetch one image into memory, or throw a typed error saying why not. */
|
|
30
|
+
export declare function fetchImage(input: string, options?: FetchImageOptions): Promise<FetchedImage>;
|
|
31
|
+
//# sourceMappingURL=image-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-fetch.d.ts","sourceRoot":"","sources":["../../src/utils/image-fetch.ts"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,wEAAyE,CAAC;AAClG,eAAO,MAAM,gBAAgB,QAAS,CAAC;AACvC,sCAAsC;AACtC,eAAO,MAAM,eAAe,QAAkB,CAAC;AAC/C,iFAAiF;AACjF,eAAO,MAAM,sBAAsB,QAAkB,CAAC;AAKtD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,wDAAwD;IACxD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,GAAG,CAcjD;AAgBD,0EAA0E;AAC1E,wBAAsB,UAAU,CAC9B,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,YAAY,CAAC,CA0CvB"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { currentContext } from './context.js';
|
|
2
|
+
import { YouTubeError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* The one place this server fetches anything over HTTP itself.
|
|
5
|
+
*
|
|
6
|
+
* Everything else goes through yt-dlp. Thumbnails are the exception because
|
|
7
|
+
* their URLs are known from a single channel listing, and asking yt-dlp to
|
|
8
|
+
* extract each video just to download its image would multiply the requests
|
|
9
|
+
* YouTube sees by the size of the channel. The fetch is deliberately narrow:
|
|
10
|
+
* an allowlist of image hosts, https only, redirects re-checked hop by hop, a
|
|
11
|
+
* timeout, a byte cap enforced while reading, and an image content type.
|
|
12
|
+
*/
|
|
13
|
+
export const IMAGE_HOSTS = ['i.ytimg.com', 'yt3.googleusercontent.com', 'yt3.ggpht.com'];
|
|
14
|
+
export const IMAGE_TIMEOUT_MS = 20_000;
|
|
15
|
+
/** Cap for images written to disk. */
|
|
16
|
+
export const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
|
|
17
|
+
/** Cap for images returned inline as base64; a 1280x720 JPEG is about 140 KB. */
|
|
18
|
+
export const MAX_INLINE_IMAGE_BYTES = 2 * 1024 * 1024;
|
|
19
|
+
const MAX_REDIRECTS = 3;
|
|
20
|
+
const HOST_SET = new Set(IMAGE_HOSTS);
|
|
21
|
+
/** Validate an image URL against the allowlist, returning it parsed. */
|
|
22
|
+
export function assertImageUrl(input) {
|
|
23
|
+
let url;
|
|
24
|
+
try {
|
|
25
|
+
url = new URL(input);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw notAllowed(input);
|
|
29
|
+
}
|
|
30
|
+
const allowed = url.protocol === 'https:' &&
|
|
31
|
+
HOST_SET.has(url.hostname.toLowerCase()) &&
|
|
32
|
+
url.username === '' &&
|
|
33
|
+
url.password === '';
|
|
34
|
+
if (!allowed)
|
|
35
|
+
throw notAllowed(input);
|
|
36
|
+
return url;
|
|
37
|
+
}
|
|
38
|
+
function notAllowed(input) {
|
|
39
|
+
const shown = input.length > 96 ? `${input.slice(0, 93)}...` : input;
|
|
40
|
+
return new YouTubeError('INVALID_INPUT', `"${shown}" is not a YouTube image URL.`, {
|
|
41
|
+
nextStep: `Images are only fetched over https from ${IMAGE_HOSTS.join(', ')}.`,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function failed(message, cause) {
|
|
45
|
+
return new YouTubeError('FETCH_FAILED', message, {
|
|
46
|
+
nextStep: 'Retry later; if it keeps failing, the image may have been removed.',
|
|
47
|
+
cause,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
/** Fetch one image into memory, or throw a typed error saying why not. */
|
|
51
|
+
export async function fetchImage(input, options = {}) {
|
|
52
|
+
const { maxBytes = MAX_IMAGE_BYTES, timeoutMs = IMAGE_TIMEOUT_MS } = options;
|
|
53
|
+
const request = currentContext().signal;
|
|
54
|
+
const timeout = AbortSignal.timeout(timeoutMs);
|
|
55
|
+
const signal = request === undefined ? timeout : AbortSignal.any([timeout, request]);
|
|
56
|
+
let url = assertImageUrl(input);
|
|
57
|
+
for (let hop = 0;; hop++) {
|
|
58
|
+
const response = await send(url, signal, request);
|
|
59
|
+
if (response.status >= 300 && response.status < 400) {
|
|
60
|
+
const location = response.headers.get('location');
|
|
61
|
+
if (location === null || hop >= MAX_REDIRECTS)
|
|
62
|
+
throw failed('The image URL redirected too far.');
|
|
63
|
+
// Re-validated: a redirect is a URL the server chose, not the caller.
|
|
64
|
+
url = assertImageUrl(new URL(location, url).toString());
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (response.status === 404)
|
|
68
|
+
throw new YouTubeError('NOT_FOUND', 'There is no image at that size.');
|
|
69
|
+
if (response.status === 429) {
|
|
70
|
+
throw new YouTubeError('RATE_LIMITED', 'YouTube is rate limiting image downloads.', {
|
|
71
|
+
nextStep: 'Wait a minute before retrying.',
|
|
72
|
+
retryable: true,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!response.ok)
|
|
76
|
+
throw failed(`The image host answered ${response.status}.`);
|
|
77
|
+
const contentType = mediaType(response.headers.get('content-type') ?? '');
|
|
78
|
+
if (!contentType.startsWith('image/'))
|
|
79
|
+
throw failed('The response was not an image.');
|
|
80
|
+
const declared = Number(response.headers.get('content-length') ?? '0');
|
|
81
|
+
if (declared > maxBytes)
|
|
82
|
+
throw failed(`The image is larger than ${maxBytes} bytes.`);
|
|
83
|
+
return {
|
|
84
|
+
bytes: await readCapped(response, maxBytes, signal, request),
|
|
85
|
+
contentType,
|
|
86
|
+
url: url.toString(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async function send(url, signal, request) {
|
|
91
|
+
try {
|
|
92
|
+
return await fetch(url, { redirect: 'manual', signal });
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw translate(error, request);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/** The media type of a Content-Type header, without its parameters. */
|
|
99
|
+
function mediaType(header) {
|
|
100
|
+
const semicolon = header.indexOf(';');
|
|
101
|
+
return header.slice(0, semicolon === -1 ? header.length : semicolon).trim();
|
|
102
|
+
}
|
|
103
|
+
/** Read the body, refusing to go past the cap whatever Content-Length claimed. */
|
|
104
|
+
async function readCapped(response, maxBytes, signal, request) {
|
|
105
|
+
if (response.body === null)
|
|
106
|
+
return Buffer.alloc(0);
|
|
107
|
+
// A reader rather than `for await`: the cap has to cancel the stream from
|
|
108
|
+
// inside the loop, and the async iterator holds the lock that cancel needs.
|
|
109
|
+
const reader = response.body.getReader();
|
|
110
|
+
const chunks = [];
|
|
111
|
+
let total = 0;
|
|
112
|
+
try {
|
|
113
|
+
for (let next = await reader.read(); !next.done; next = await reader.read()) {
|
|
114
|
+
total += next.value.byteLength;
|
|
115
|
+
if (total > maxBytes) {
|
|
116
|
+
await reader.cancel();
|
|
117
|
+
throw failed(`The image is larger than ${maxBytes} bytes.`);
|
|
118
|
+
}
|
|
119
|
+
chunks.push(next.value);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
if (error instanceof YouTubeError)
|
|
124
|
+
throw error;
|
|
125
|
+
throw translate(error, request, signal);
|
|
126
|
+
}
|
|
127
|
+
return Buffer.concat(chunks);
|
|
128
|
+
}
|
|
129
|
+
function translate(error, request, signal) {
|
|
130
|
+
if (request?.aborted === true)
|
|
131
|
+
return new YouTubeError('CANCELLED', 'The request was cancelled.');
|
|
132
|
+
const timedOut = (error instanceof Error && error.name === 'TimeoutError') || signal?.aborted === true;
|
|
133
|
+
if (timedOut) {
|
|
134
|
+
return new YouTubeError('TIMEOUT', 'The image host did not respond in time.', {
|
|
135
|
+
nextStep: 'Retry; the host may be slow.',
|
|
136
|
+
retryable: true,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return failed('The image could not be downloaded.', error);
|
|
140
|
+
}
|
|
141
|
+
//# sourceMappingURL=image-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image-fetch.js","sourceRoot":"","sources":["../../src/utils/image-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Reading and writing the documents this server keeps on disk.
|
|
4
|
+
*
|
|
5
|
+
* Both halves exist because the library and the brains make the same two
|
|
6
|
+
* mistakes otherwise. Writing straight to the destination means an interrupted
|
|
7
|
+
* process leaves a half-written file where a valid one used to be — survivable
|
|
8
|
+
* for a cache, fatal for a brain manifest that is checkpointed every few videos
|
|
9
|
+
* across a build lasting tens of minutes. Reading without validation means a
|
|
10
|
+
* file edited by hand, or truncated by that same crash, propagates `undefined`
|
|
11
|
+
* into code that assumed a shape.
|
|
12
|
+
*/
|
|
13
|
+
export interface WriteFileOptions {
|
|
14
|
+
/** Permission bits for the new file. Owner-only by default: these are the user's notes and caches. */
|
|
15
|
+
mode?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface WriteJsonOptions extends WriteFileOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Indent the output. Worth it for files a human may open; not for a search
|
|
20
|
+
* index, where indentation is most of the bytes.
|
|
21
|
+
*/
|
|
22
|
+
pretty?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export declare const PRIVATE_FILE_MODE = 384;
|
|
25
|
+
/**
|
|
26
|
+
* Write a file through a temporary name and rename it into place.
|
|
27
|
+
*
|
|
28
|
+
* The data is flushed to disk before the rename: without `fsync` a power loss
|
|
29
|
+
* after the rename could leave a zero-length file under the final name, which
|
|
30
|
+
* is exactly the half-written document the rename was meant to rule out.
|
|
31
|
+
* `rename` within a directory is atomic on POSIX and on Windows via
|
|
32
|
+
* `MoveFileEx`, so a reader sees either the previous document or the new one.
|
|
33
|
+
*/
|
|
34
|
+
export declare function writeFileAtomic(path: string, data: string | Uint8Array, options?: WriteFileOptions): Promise<void>;
|
|
35
|
+
/** Serialise and write a JSON document atomically. */
|
|
36
|
+
export declare function writeJsonAtomic(path: string, value: unknown, options?: WriteJsonOptions): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Read and validate a JSON document, or `undefined` if it is missing,
|
|
39
|
+
* unparseable or does not match.
|
|
40
|
+
*
|
|
41
|
+
* Callers decide what a missing document means — an empty library, a brain that
|
|
42
|
+
* has not been built — which is information this function does not have.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readJsonFile<T>(path: string, schema: z.ZodType<T, z.ZodTypeDef, unknown>): Promise<T | undefined>;
|
|
45
|
+
//# sourceMappingURL=json-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-file.d.ts","sourceRoot":"","sources":["../../src/utils/json-file.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAE7B;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,sGAAsG;IACtG,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAiB,SAAQ,gBAAgB;IACxD;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,iBAAiB,MAAQ,CAAC;AAIvC;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GAAG,UAAU,EACzB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAqBf;AAED,sDAAsD;AACtD,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,IAAI,CAAC,CAIf;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,CAAC,EAClC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,CAAC,CAAC,UAAU,EAAE,OAAO,CAAC,GAC1C,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CASxB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { open, readFile, rename, rm } from 'node:fs/promises';
|
|
3
|
+
export const PRIVATE_FILE_MODE = 0o600;
|
|
4
|
+
let writeSequence = 0;
|
|
5
|
+
/**
|
|
6
|
+
* Write a file through a temporary name and rename it into place.
|
|
7
|
+
*
|
|
8
|
+
* The data is flushed to disk before the rename: without `fsync` a power loss
|
|
9
|
+
* after the rename could leave a zero-length file under the final name, which
|
|
10
|
+
* is exactly the half-written document the rename was meant to rule out.
|
|
11
|
+
* `rename` within a directory is atomic on POSIX and on Windows via
|
|
12
|
+
* `MoveFileEx`, so a reader sees either the previous document or the new one.
|
|
13
|
+
*/
|
|
14
|
+
export async function writeFileAtomic(path, data, options = {}) {
|
|
15
|
+
const { mode = PRIVATE_FILE_MODE } = options;
|
|
16
|
+
// A shared temporary name would let two writers of the same document destroy
|
|
17
|
+
// each other's staging file, and the loser's rename would fail on a path that
|
|
18
|
+
// the winner had already moved away.
|
|
19
|
+
const temporaryPath = `${path}.${process.pid}.${writeSequence++}.tmp`;
|
|
20
|
+
try {
|
|
21
|
+
const handle = await open(temporaryPath, 'w', mode);
|
|
22
|
+
try {
|
|
23
|
+
await handle.writeFile(data);
|
|
24
|
+
await handle.sync();
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await handle.close();
|
|
28
|
+
}
|
|
29
|
+
await rename(temporaryPath, path);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
await rm(temporaryPath, { force: true });
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Serialise and write a JSON document atomically. */
|
|
37
|
+
export async function writeJsonAtomic(path, value, options = {}) {
|
|
38
|
+
const { pretty = true, ...fileOptions } = options;
|
|
39
|
+
const serialised = JSON.stringify(value, null, pretty ? 2 : undefined);
|
|
40
|
+
await writeFileAtomic(path, serialised, fileOptions);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read and validate a JSON document, or `undefined` if it is missing,
|
|
44
|
+
* unparseable or does not match.
|
|
45
|
+
*
|
|
46
|
+
* Callers decide what a missing document means — an empty library, a brain that
|
|
47
|
+
* has not been built — which is information this function does not have.
|
|
48
|
+
*/
|
|
49
|
+
export async function readJsonFile(path, schema) {
|
|
50
|
+
if (!existsSync(path))
|
|
51
|
+
return undefined;
|
|
52
|
+
try {
|
|
53
|
+
const parsed = schema.safeParse(JSON.parse(await readFile(path, 'utf-8')));
|
|
54
|
+
return parsed.success ? parsed.data : undefined;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=json-file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-file.js","sourceRoot":"","sources":["../../src/utils/json-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AA4B9D,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,CAAC;AAEvC,IAAI,aAAa,GAAG,CAAC,CAAC;AAEtB;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,IAAyB,EACzB,UAA4B,EAAE;IAE9B,MAAM,EAAE,IAAI,GAAG,iBAAiB,EAAE,GAAG,OAAO,CAAC;IAE7C,6EAA6E;IAC7E,8EAA8E;IAC9E,qCAAqC;IACrC,MAAM,aAAa,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,aAAa,EAAE,MAAM,CAAC;IAEtE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC7B,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC;QACtB,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,MAAM,MAAM,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,EAAE,CAAC,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACzC,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC;AAED,sDAAsD;AACtD,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,IAAY,EACZ,KAAc,EACd,UAA4B,EAAE;IAE9B,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,WAAW,EAAE,GAAG,OAAO,CAAC;IAClD,MAAM,UAAU,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IACvE,MAAM,eAAe,CAAC,IAAI,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;AACvD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,IAAY,EACZ,MAA2C;IAE3C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,SAAS,CAAC;IAExC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;QAC3E,OAAO,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Everything this server writes lives under one directory in the user's home:
|
|
3
|
+
* transcripts, the library, clips, and the channel brains.
|
|
4
|
+
*
|
|
5
|
+
* Spelled out in six places before this existed, which is five chances for a
|
|
6
|
+
* rename to leave a stray directory behind.
|
|
7
|
+
*/
|
|
8
|
+
export declare function dataDir(...segments: string[]): string;
|
|
9
|
+
/** Permission bits for the directories this server owns: the user's data, nobody else's. */
|
|
10
|
+
export declare const PRIVATE_DIR_MODE = 448;
|
|
11
|
+
/**
|
|
12
|
+
* Create a directory the server owns, readable by its user only.
|
|
13
|
+
*
|
|
14
|
+
* Media that a tool writes for the user to open — downloads, clips, frames —
|
|
15
|
+
* keeps the umask default; this is for the transcript cache, the library, the
|
|
16
|
+
* brains and their indexes.
|
|
17
|
+
*/
|
|
18
|
+
export declare function ensurePrivateDir(path: string): Promise<string>;
|
|
19
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAIA;;;;;;GAMG;AACH,wBAAgB,OAAO,CAAC,GAAG,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,CAErD;AAED,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,MAAQ,CAAC;AAEtC;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAGpE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { mkdir } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* Everything this server writes lives under one directory in the user's home:
|
|
6
|
+
* transcripts, the library, clips, and the channel brains.
|
|
7
|
+
*
|
|
8
|
+
* Spelled out in six places before this existed, which is five chances for a
|
|
9
|
+
* rename to leave a stray directory behind.
|
|
10
|
+
*/
|
|
11
|
+
export function dataDir(...segments) {
|
|
12
|
+
return join(homedir(), '.youtube-knowledge', ...segments);
|
|
13
|
+
}
|
|
14
|
+
/** Permission bits for the directories this server owns: the user's data, nobody else's. */
|
|
15
|
+
export const PRIVATE_DIR_MODE = 0o700;
|
|
16
|
+
/**
|
|
17
|
+
* Create a directory the server owns, readable by its user only.
|
|
18
|
+
*
|
|
19
|
+
* Media that a tool writes for the user to open — downloads, clips, frames —
|
|
20
|
+
* keeps the umask default; this is for the transcript cache, the library, the
|
|
21
|
+
* brains and their indexes.
|
|
22
|
+
*/
|
|
23
|
+
export async function ensurePrivateDir(path) {
|
|
24
|
+
await mkdir(path, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
25
|
+
return path;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;GAMG;AACH,MAAM,UAAU,OAAO,CAAC,GAAG,QAAkB;IAC3C,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,oBAAoB,EAAE,GAAG,QAAQ,CAAC,CAAC;AAC5D,CAAC;AAED,4FAA4F;AAC5F,MAAM,CAAC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAEtC;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAY;IACjD,MAAM,KAAK,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAC/D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compiling a caller-supplied search pattern without handing the event loop
|
|
3
|
+
* to it.
|
|
4
|
+
*
|
|
5
|
+
* JavaScript's regex engine backtracks, and a pattern such as `(a+)+$` takes
|
|
6
|
+
* time exponential in the input on a non-matching string. `search_transcript`
|
|
7
|
+
* runs the caller's pattern over every caption window of a video, on a server
|
|
8
|
+
* whose HTTP transport is shared, so an unbounded pattern is a way to stall
|
|
9
|
+
* every session at once. The rule below is conservative — it refuses some
|
|
10
|
+
* harmless patterns — but it is simple enough to explain in an error message
|
|
11
|
+
* and needs no second regex engine.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MAX_PATTERN_LENGTH = 200;
|
|
14
|
+
/**
|
|
15
|
+
* Throws when a repeated group contains repetition or alternation, or when the
|
|
16
|
+
* pattern uses a backreference.
|
|
17
|
+
*/
|
|
18
|
+
export declare function assertSafePattern(source: string): void;
|
|
19
|
+
export interface PatternOptions {
|
|
20
|
+
regex: boolean;
|
|
21
|
+
caseSensitive: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compile the caller's query once, with the flags the search will use.
|
|
25
|
+
*
|
|
26
|
+
* A literal query is escaped so that `a.` matches "a." and nothing else; a
|
|
27
|
+
* regex query is checked for the backtracking rule and for syntax.
|
|
28
|
+
*/
|
|
29
|
+
export declare function compilePattern(query: string, options: PatternOptions): RegExp;
|
|
30
|
+
//# sourceMappingURL=pattern.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern.d.ts","sourceRoot":"","sources":["../../src/utils/pattern.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,eAAO,MAAM,kBAAkB,MAAM,CAAC;AAsDtC;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAuDtD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,OAAO,CAAC;IACf,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,CAgB7E"}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { YouTubeError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compiling a caller-supplied search pattern without handing the event loop
|
|
4
|
+
* to it.
|
|
5
|
+
*
|
|
6
|
+
* JavaScript's regex engine backtracks, and a pattern such as `(a+)+$` takes
|
|
7
|
+
* time exponential in the input on a non-matching string. `search_transcript`
|
|
8
|
+
* runs the caller's pattern over every caption window of a video, on a server
|
|
9
|
+
* whose HTTP transport is shared, so an unbounded pattern is a way to stall
|
|
10
|
+
* every session at once. The rule below is conservative — it refuses some
|
|
11
|
+
* harmless patterns — but it is simple enough to explain in an error message
|
|
12
|
+
* and needs no second regex engine.
|
|
13
|
+
*/
|
|
14
|
+
export const MAX_PATTERN_LENGTH = 200;
|
|
15
|
+
const QUANTIFIER_START = new Set(['*', '+', '?', '{']);
|
|
16
|
+
function unsafe(reason) {
|
|
17
|
+
return new YouTubeError('INVALID_INPUT', `This pattern could run for a very long time: ${reason}.`, {
|
|
18
|
+
nextStep: 'A repeated group may not itself contain repetition or alternation, and backreferences are not supported. Simplify the pattern, or set regex=false to search literally.',
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/** True when `source[at]` begins a quantifier: `*`, `+`, `?` or a `{n}`, `{n,}`, `{n,m}` bound. */
|
|
22
|
+
function quantifierAt(source, at) {
|
|
23
|
+
const char = source[at];
|
|
24
|
+
if (char === undefined || !QUANTIFIER_START.has(char))
|
|
25
|
+
return false;
|
|
26
|
+
if (char !== '{')
|
|
27
|
+
return true;
|
|
28
|
+
return /^\{\d+(?:,\d*)?\}/.test(source.slice(at));
|
|
29
|
+
}
|
|
30
|
+
/** Index just past the character class that opens at `at`. */
|
|
31
|
+
function skipClass(source, at) {
|
|
32
|
+
let index = at + 1;
|
|
33
|
+
while (index < source.length && source[index] !== ']') {
|
|
34
|
+
if (source[index] === '\\')
|
|
35
|
+
index++;
|
|
36
|
+
index++;
|
|
37
|
+
}
|
|
38
|
+
return index + 1;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Index just past a group opener: `(`, or `(?:`, `(?=`, `(?!`, `(?<=`, `(?<!`
|
|
42
|
+
* and `(?<name>`, whose `?` is syntax rather than a quantifier.
|
|
43
|
+
*/
|
|
44
|
+
function skipGroupOpener(source, at) {
|
|
45
|
+
if (source[at + 1] !== '?')
|
|
46
|
+
return at + 1;
|
|
47
|
+
const modifier = source[at + 2];
|
|
48
|
+
if (modifier === '<') {
|
|
49
|
+
const lookbehind = source[at + 3];
|
|
50
|
+
if (lookbehind === '=' || lookbehind === '!')
|
|
51
|
+
return at + 4;
|
|
52
|
+
const close = source.indexOf('>', at + 3);
|
|
53
|
+
return close === -1 ? at + 3 : close + 1;
|
|
54
|
+
}
|
|
55
|
+
return at + 3;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Throws when a repeated group contains repetition or alternation, or when the
|
|
59
|
+
* pattern uses a backreference.
|
|
60
|
+
*/
|
|
61
|
+
export function assertSafePattern(source) {
|
|
62
|
+
if (source.length > MAX_PATTERN_LENGTH) {
|
|
63
|
+
throw new YouTubeError('INVALID_INPUT', `Patterns are limited to ${MAX_PATTERN_LENGTH} characters.`, {
|
|
64
|
+
nextStep: 'Search for a shorter phrase, or set regex=false to search literally.',
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const root = { quantified: false, alternation: false };
|
|
68
|
+
const stack = [root];
|
|
69
|
+
const current = () => stack[stack.length - 1] ?? root;
|
|
70
|
+
let index = 0;
|
|
71
|
+
while (index < source.length) {
|
|
72
|
+
const char = source[index];
|
|
73
|
+
if (char === '\\') {
|
|
74
|
+
const next = source[index + 1];
|
|
75
|
+
if ((next !== undefined && /[1-9]/.test(next)) || next === 'k') {
|
|
76
|
+
throw unsafe('backreferences are not supported');
|
|
77
|
+
}
|
|
78
|
+
index += 2;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (char === '[') {
|
|
82
|
+
index = skipClass(source, index);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (char === '(') {
|
|
86
|
+
stack.push({ quantified: false, alternation: false });
|
|
87
|
+
index = skipGroupOpener(source, index);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (char === ')') {
|
|
91
|
+
const closed = stack.pop() ?? root;
|
|
92
|
+
index++;
|
|
93
|
+
if (quantifierAt(source, index)) {
|
|
94
|
+
if (closed.quantified || closed.alternation) {
|
|
95
|
+
throw unsafe('a repeated group contains repetition or alternation');
|
|
96
|
+
}
|
|
97
|
+
current().quantified = true;
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === '|')
|
|
102
|
+
current().alternation = true;
|
|
103
|
+
if (quantifierAt(source, index))
|
|
104
|
+
current().quantified = true;
|
|
105
|
+
index++;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compile the caller's query once, with the flags the search will use.
|
|
110
|
+
*
|
|
111
|
+
* A literal query is escaped so that `a.` matches "a." and nothing else; a
|
|
112
|
+
* regex query is checked for the backtracking rule and for syntax.
|
|
113
|
+
*/
|
|
114
|
+
export function compilePattern(query, options) {
|
|
115
|
+
const flags = options.caseSensitive ? 'g' : 'gi';
|
|
116
|
+
if (!options.regex) {
|
|
117
|
+
return new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), flags);
|
|
118
|
+
}
|
|
119
|
+
assertSafePattern(query);
|
|
120
|
+
try {
|
|
121
|
+
return new RegExp(query, flags);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new YouTubeError('INVALID_INPUT', `"${query}" is not a valid regular expression.`, {
|
|
125
|
+
nextStep: 'Fix the pattern, or set regex=false to search for it literally.',
|
|
126
|
+
cause: error,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
//# sourceMappingURL=pattern.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pattern.js","sourceRoot":"","sources":["../../src/utils/pattern.ts"],"names":[],"mappings":"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"}
|