youtube-knowledge-mcp 2.1.0 → 3.0.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 +153 -38
- package/dist/brain-schemas.d.ts +10 -10
- package/dist/brain-schemas.d.ts.map +1 -1
- package/dist/brain-schemas.js +0 -4
- package/dist/brain-schemas.js.map +1 -1
- package/dist/harvest-schemas.d.ts +75 -0
- package/dist/harvest-schemas.d.ts.map +1 -0
- package/dist/harvest-schemas.js +106 -0
- package/dist/harvest-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 -517
- 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 -100
- 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/harvest-tools.d.ts +10 -0
- package/dist/registry/harvest-tools.d.ts.map +1 -0
- package/dist/registry/harvest-tools.js +109 -0
- package/dist/registry/harvest-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 +39 -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 +29 -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 +13 -10
- package/dist/resources.js.map +1 -1
- package/dist/schemas.d.ts +250 -2
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +61 -0
- 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.js +1 -1
- package/dist/tools/ask-brain.js.map +1 -1
- package/dist/tools/batch.js +1 -1
- package/dist/tools/batch.js.map +1 -1
- package/dist/tools/brain-info.js +1 -1
- package/dist/tools/brain-info.js.map +1 -1
- package/dist/tools/build-brain.js +1 -1
- package/dist/tools/build-brain.js.map +1 -1
- package/dist/tools/check-health.d.ts +61 -0
- package/dist/tools/check-health.d.ts.map +1 -1
- package/dist/tools/check-health.js +94 -6
- package/dist/tools/check-health.js.map +1 -1
- package/dist/tools/clips.d.ts +9 -41
- 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 +18 -4
- package/dist/tools/fetch-videos.d.ts.map +1 -1
- package/dist/tools/fetch-videos.js +64 -20
- 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-comments.d.ts +238 -6
- package/dist/tools/get-comments.d.ts.map +1 -1
- package/dist/tools/get-comments.js +85 -21
- package/dist/tools/get-comments.js.map +1 -1
- package/dist/tools/get-coverage.d.ts +122 -0
- package/dist/tools/get-coverage.d.ts.map +1 -0
- package/dist/tools/get-coverage.js +105 -0
- package/dist/tools/get-coverage.js.map +1 -0
- 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.map +1 -1
- package/dist/tools/get-transcript.js +2 -14
- package/dist/tools/get-transcript.js.map +1 -1
- package/dist/tools/harvest-channel.d.ts +92 -0
- package/dist/tools/harvest-channel.d.ts.map +1 -0
- package/dist/tools/harvest-channel.js +62 -0
- package/dist/tools/harvest-channel.js.map +1 -0
- package/dist/tools/harvest-comments.d.ts +81 -0
- package/dist/tools/harvest-comments.d.ts.map +1 -0
- package/dist/tools/harvest-comments.js +115 -0
- package/dist/tools/harvest-comments.js.map +1 -0
- package/dist/tools/library.d.ts.map +1 -1
- package/dist/tools/library.js +14 -4
- 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.js +1 -1
- package/dist/tools/list-library.js.map +1 -1
- package/dist/tools/prune-harvest.d.ts +59 -0
- package/dist/tools/prune-harvest.d.ts.map +1 -0
- package/dist/tools/prune-harvest.js +108 -0
- package/dist/tools/prune-harvest.js.map +1 -0
- package/dist/tools/query-comments.d.ts +125 -0
- package/dist/tools/query-comments.d.ts.map +1 -0
- package/dist/tools/query-comments.js +106 -0
- package/dist/tools/query-comments.js.map +1 -0
- package/dist/tools/query-videos.d.ts +118 -0
- package/dist/tools/query-videos.d.ts.map +1 -0
- package/dist/tools/query-videos.js +97 -0
- package/dist/tools/query-videos.js.map +1 -0
- package/dist/tools/repair-store.d.ts +34 -0
- package/dist/tools/repair-store.d.ts.map +1 -0
- package/dist/tools/repair-store.js +58 -0
- package/dist/tools/repair-store.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 +6 -0
- package/dist/tools/search-channels.d.ts.map +1 -1
- package/dist/tools/search-channels.js +5 -1
- package/dist/tools/search-channels.js.map +1 -1
- 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 +3 -0
- package/dist/tools/search-videos.d.ts.map +1 -1
- package/dist/tools/search-videos.js +1 -1
- package/dist/tools/search-videos.js.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.map +1 -1
- package/dist/utils/brain-build.js +1 -12
- package/dist/utils/brain-build.js.map +1 -1
- package/dist/utils/brain-index.d.ts.map +1 -1
- package/dist/utils/brain-index.js +3 -2
- package/dist/utils/brain-index.js.map +1 -1
- package/dist/utils/brain-lock.d.ts +2 -18
- package/dist/utils/brain-lock.d.ts.map +1 -1
- package/dist/utils/brain-lock.js +4 -80
- package/dist/utils/brain-lock.js.map +1 -1
- package/dist/utils/brain-lookup.d.ts +8 -0
- package/dist/utils/brain-lookup.d.ts.map +1 -1
- package/dist/utils/brain-lookup.js +5 -31
- package/dist/utils/brain-lookup.js.map +1 -1
- package/dist/utils/brain-storage.d.ts.map +1 -1
- package/dist/utils/brain-storage.js +5 -5
- package/dist/utils/brain-storage.js.map +1 -1
- 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 +18 -26
- package/dist/utils/clips.js.map +1 -1
- package/dist/utils/comment-store.d.ts +71 -0
- package/dist/utils/comment-store.d.ts.map +1 -0
- package/dist/utils/comment-store.js +129 -0
- package/dist/utils/comment-store.js.map +1 -0
- package/dist/utils/comment-threads.d.ts +51 -0
- package/dist/utils/comment-threads.d.ts.map +1 -0
- package/dist/utils/comment-threads.js +76 -0
- package/dist/utils/comment-threads.js.map +1 -0
- package/dist/utils/coverage-text.d.ts +3 -0
- package/dist/utils/coverage-text.d.ts.map +1 -0
- package/dist/utils/coverage-text.js +62 -0
- package/dist/utils/coverage-text.js.map +1 -0
- package/dist/utils/coverage.d.ts +57 -0
- package/dist/utils/coverage.d.ts.map +1 -0
- package/dist/utils/coverage.js +142 -0
- package/dist/utils/coverage.js.map +1 -0
- 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 +28 -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 +18 -4
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +2 -6
- 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/harvest-catalog.d.ts +44 -0
- package/dist/utils/harvest-catalog.d.ts.map +1 -0
- package/dist/utils/harvest-catalog.js +164 -0
- package/dist/utils/harvest-catalog.js.map +1 -0
- package/dist/utils/harvest-lock.d.ts +3 -0
- package/dist/utils/harvest-lock.d.ts.map +1 -0
- package/dist/utils/harvest-lock.js +26 -0
- package/dist/utils/harvest-lock.js.map +1 -0
- package/dist/utils/harvest-receipts.d.ts +14 -0
- package/dist/utils/harvest-receipts.d.ts.map +1 -0
- package/dist/utils/harvest-receipts.js +110 -0
- package/dist/utils/harvest-receipts.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 +14 -5
- package/dist/utils/json-file.d.ts.map +1 -1
- package/dist/utils/json-file.js +24 -9
- package/dist/utils/json-file.js.map +1 -1
- package/dist/utils/listing-cursor.d.ts +31 -0
- package/dist/utils/listing-cursor.d.ts.map +1 -0
- package/dist/utils/listing-cursor.js +39 -0
- package/dist/utils/listing-cursor.js.map +1 -0
- package/dist/utils/paths.d.ts +10 -0
- package/dist/utils/paths.d.ts.map +1 -1
- package/dist/utils/paths.js +14 -0
- package/dist/utils/paths.js.map +1 -1
- 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/pot-preflight.d.ts +53 -0
- package/dist/utils/pot-preflight.d.ts.map +1 -0
- package/dist/utils/pot-preflight.js +146 -0
- package/dist/utils/pot-preflight.js.map +1 -0
- package/dist/utils/search-index.d.ts.map +1 -1
- package/dist/utils/search-index.js +9 -4
- package/dist/utils/search-index.js.map +1 -1
- package/dist/utils/sleep.d.ts +9 -0
- package/dist/utils/sleep.d.ts.map +1 -0
- package/dist/utils/sleep.js +25 -0
- package/dist/utils/sleep.js.map +1 -0
- package/dist/utils/storage.d.ts.map +1 -1
- package/dist/utils/storage.js +14 -8
- package/dist/utils/storage.js.map +1 -1
- package/dist/utils/store-health.d.ts +37 -0
- package/dist/utils/store-health.d.ts.map +1 -0
- package/dist/utils/store-health.js +92 -0
- package/dist/utils/store-health.js.map +1 -0
- package/dist/utils/store-migrations.d.ts +33 -0
- package/dist/utils/store-migrations.d.ts.map +1 -0
- package/dist/utils/store-migrations.js +41 -0
- package/dist/utils/store-migrations.js.map +1 -0
- package/dist/utils/store-paths.d.ts +33 -0
- package/dist/utils/store-paths.d.ts.map +1 -0
- package/dist/utils/store-paths.js +48 -0
- package/dist/utils/store-paths.js.map +1 -0
- package/dist/utils/store-rows.d.ts +22 -0
- package/dist/utils/store-rows.d.ts.map +1 -0
- package/dist/utils/store-rows.js +62 -0
- package/dist/utils/store-rows.js.map +1 -0
- package/dist/utils/store-schema.d.ts +16 -0
- package/dist/utils/store-schema.d.ts.map +1 -0
- package/dist/utils/store-schema.js +168 -0
- package/dist/utils/store-schema.js.map +1 -0
- package/dist/utils/store.d.ts +34 -0
- package/dist/utils/store.d.ts.map +1 -0
- package/dist/utils/store.js +157 -0
- package/dist/utils/store.js.map +1 -0
- 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 +10 -31
- package/dist/utils/transcript.d.ts.map +1 -1
- package/dist/utils/transcript.js +2 -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 -4
- package/dist/utils/validate.d.ts.map +1 -1
- package/dist/utils/validate.js +43 -36
- 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/video-stats-cache.d.ts +16 -0
- package/dist/utils/video-stats-cache.d.ts.map +1 -0
- package/dist/utils/video-stats-cache.js +81 -0
- package/dist/utils/video-stats-cache.js.map +1 -0
- package/dist/utils/video-stats.d.ts +186 -0
- package/dist/utils/video-stats.d.ts.map +1 -0
- package/dist/utils/video-stats.js +168 -0
- package/dist/utils/video-stats.js.map +1 -0
- package/dist/utils/video-store.d.ts +69 -0
- package/dist/utils/video-store.d.ts.map +1 -0
- package/dist/utils/video-store.js +77 -0
- package/dist/utils/video-store.js.map +1 -0
- package/dist/utils/youtube-channel.d.ts +51 -0
- package/dist/utils/youtube-channel.d.ts.map +1 -0
- package/dist/utils/youtube-channel.js +91 -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 +492 -0
- package/dist/utils/youtube-schemas.d.ts.map +1 -0
- package/dist/utils/youtube-schemas.js +148 -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 +68 -0
- package/dist/utils/youtube-video.d.ts.map +1 -0
- package/dist/utils/youtube-video.js +110 -0
- package/dist/utils/youtube-video.js.map +1 -0
- package/dist/utils/youtube.d.ts +10 -122
- package/dist/utils/youtube.d.ts.map +1 -1
- package/dist/utils/youtube.js +11 -486
- 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 +27 -0
- package/dist/utils/ytdlp-limiter.d.ts.map +1 -0
- package/dist/utils/ytdlp-limiter.js +85 -0
- package/dist/utils/ytdlp-limiter.js.map +1 -0
- package/dist/utils/ytdlp-pacer.d.ts +64 -0
- package/dist/utils/ytdlp-pacer.d.ts.map +1 -0
- package/dist/utils/ytdlp-pacer.js +165 -0
- package/dist/utils/ytdlp-pacer.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-stream.d.ts +32 -0
- package/dist/utils/ytdlp-stream.d.ts.map +1 -0
- package/dist/utils/ytdlp-stream.js +61 -0
- package/dist/utils/ytdlp-stream.js.map +1 -0
- package/dist/utils/ytdlp.d.ts +23 -18
- package/dist/utils/ytdlp.d.ts.map +1 -1
- package/dist/utils/ytdlp.js +56 -134
- package/dist/utils/ytdlp.js.map +1 -1
- package/package.json +3 -2
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})*$/;
|
|
@@ -41,24 +65,6 @@ export function assertLanguageTag(language) {
|
|
|
41
65
|
}
|
|
42
66
|
return language;
|
|
43
67
|
}
|
|
44
|
-
/**
|
|
45
|
-
* YouTube channel ids are `UC` followed by 22 base64url characters.
|
|
46
|
-
*
|
|
47
|
-
* This is stricter than it looks like it needs to be. A channel id arrives from
|
|
48
|
-
* yt-dlp's output and is then used as a directory name under
|
|
49
|
-
* `~/.youtube-knowledge/brains`, so a value containing a separator or `..`
|
|
50
|
-
* would write outside that tree. Matching the real format exactly is cheaper
|
|
51
|
-
* than sanitising a value that should never have been unusual.
|
|
52
|
-
*/
|
|
53
|
-
const CHANNEL_ID_PATTERN = /^UC[A-Za-z0-9_-]{22}$/;
|
|
54
|
-
export function assertChannelId(channelId) {
|
|
55
|
-
if (!CHANNEL_ID_PATTERN.test(channelId)) {
|
|
56
|
-
throw new YouTubeError('INVALID_INPUT', `"${channelId}" is not a valid YouTube channel id.`, {
|
|
57
|
-
nextStep: 'Channel ids look like UCxxxxxxxxxxxxxxxxxxxxxx. Call get_channel_info or list_brains to get one.',
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
return channelId;
|
|
61
|
-
}
|
|
62
68
|
/** Accepts `90`, `1:30`, `01:02:03`, `1:30.5` and returns seconds. */
|
|
63
69
|
export function parseTimestamp(value, field) {
|
|
64
70
|
if (typeof value === 'number') {
|
|
@@ -79,4 +85,5 @@ export function parseTimestamp(value, field) {
|
|
|
79
85
|
// The hours group is optional, so it is genuinely absent for "MM:SS" input.
|
|
80
86
|
return Number(match[1] ?? 0) * 3600 + Number(match[2] ?? 0) * 60 + Number(match[3] ?? 0);
|
|
81
87
|
}
|
|
88
|
+
export * from './validate-youtube.js';
|
|
82
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,16 @@
|
|
|
1
|
+
import { type VideoStats } from './video-stats.js';
|
|
2
|
+
export interface StatsResult {
|
|
3
|
+
stats: VideoStats;
|
|
4
|
+
/** True when served from disk rather than refetched. */
|
|
5
|
+
cached: boolean;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* A video's stats, from disk when a fresh copy is there.
|
|
9
|
+
*
|
|
10
|
+
* A cache write that fails is not allowed to fail the read: the caller already
|
|
11
|
+
* has the answer, and a full disk should cost speed, not correctness.
|
|
12
|
+
*/
|
|
13
|
+
export declare function readVideoStats(urlOrId: string, options?: {
|
|
14
|
+
refresh?: boolean;
|
|
15
|
+
}, now?: number): Promise<StatsResult>;
|
|
16
|
+
//# sourceMappingURL=video-stats-cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stats-cache.d.ts","sourceRoot":"","sources":["../../src/utils/video-stats-cache.ts"],"names":[],"mappings":"AAQA,OAAO,EAIL,KAAK,UAAU,EAChB,MAAM,kBAAkB,CAAC;AAqD1B,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,UAAU,CAAC;IAClB,wDAAwD;IACxD,MAAM,EAAE,OAAO,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAO,EACnC,GAAG,SAAa,GACf,OAAO,CAAC,WAAW,CAAC,CAyBtB"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { dataDir, ensurePrivateDir } from './paths.js';
|
|
5
|
+
import { readJsonFile, writeJsonAtomic } from './json-file.js';
|
|
6
|
+
import { envInt } from './env.js';
|
|
7
|
+
import { assertVideoId } from './validate.js';
|
|
8
|
+
import { extractVideoId } from './youtube-url.js';
|
|
9
|
+
import { fetchVideoStatsRow, toVideoStats, videoStatsRowSchema, } from './video-stats.js';
|
|
10
|
+
/**
|
|
11
|
+
* Keeping one video's stats on disk.
|
|
12
|
+
*
|
|
13
|
+
* Every per-video read goes through here, so get_video_info, get_chapters, a
|
|
14
|
+
* caption inventory and a catalogue detail pass share a single request rather
|
|
15
|
+
* than making one each. Requests are what gets a client throttled, so this is
|
|
16
|
+
* the largest reduction available.
|
|
17
|
+
*
|
|
18
|
+
* The cached document is the yt-dlp row, not the derived object: the row is
|
|
19
|
+
* what has a schema, and re-deriving on read means a cached video and a fresh
|
|
20
|
+
* one always come back through the same code path. Shaped after
|
|
21
|
+
* transcript-cache.ts — sharded so no directory grows to 100,000 entries,
|
|
22
|
+
* validated on read, and anything unreadable treated as absent.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Resolved per call, not once at import.
|
|
26
|
+
*
|
|
27
|
+
* A module-level dataDir() reads the home directory the instant the module
|
|
28
|
+
* loads, which is before a test can point HOME anywhere — and it means the
|
|
29
|
+
* path is fixed for the process. store-paths.ts uses functions for the same
|
|
30
|
+
* reason.
|
|
31
|
+
*/
|
|
32
|
+
function cacheDir() {
|
|
33
|
+
return dataDir('stats');
|
|
34
|
+
}
|
|
35
|
+
/** Counts move constantly, so a day is already generous. */
|
|
36
|
+
const VIDEO_STATS_TTL_MS = envInt('YOUTUBE_MCP_STATS_TTL_MS', 86_400_000, { min: 0 });
|
|
37
|
+
/** Bumped when the stored shape changes, so old files are refetched, not misread. */
|
|
38
|
+
const CACHE_VERSION = 1;
|
|
39
|
+
const cachedStatsSchema = z.object({
|
|
40
|
+
version: z.literal(CACHE_VERSION),
|
|
41
|
+
fetchedAt: z.number(),
|
|
42
|
+
row: videoStatsRowSchema,
|
|
43
|
+
});
|
|
44
|
+
function shardDir(videoId) {
|
|
45
|
+
return join(cacheDir(), assertVideoId(videoId).slice(0, 2));
|
|
46
|
+
}
|
|
47
|
+
function cachePath(videoId) {
|
|
48
|
+
return join(shardDir(videoId), `${assertVideoId(videoId)}.json`);
|
|
49
|
+
}
|
|
50
|
+
function isFresh(fetchedAt, now) {
|
|
51
|
+
return VIDEO_STATS_TTL_MS === 0 || now - fetchedAt < VIDEO_STATS_TTL_MS;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A video's stats, from disk when a fresh copy is there.
|
|
55
|
+
*
|
|
56
|
+
* A cache write that fails is not allowed to fail the read: the caller already
|
|
57
|
+
* has the answer, and a full disk should cost speed, not correctness.
|
|
58
|
+
*/
|
|
59
|
+
export async function readVideoStats(urlOrId, options = {}, now = Date.now()) {
|
|
60
|
+
// The id comes from the URL, not from a fetch: checking the cache after
|
|
61
|
+
// fetching would be no cache at all.
|
|
62
|
+
const videoId = extractVideoId(urlOrId);
|
|
63
|
+
if (options.refresh !== true) {
|
|
64
|
+
const path = cachePath(videoId);
|
|
65
|
+
if (existsSync(path)) {
|
|
66
|
+
const parsed = await readJsonFile(path, cachedStatsSchema);
|
|
67
|
+
if (parsed !== undefined && isFresh(parsed.fetchedAt, now)) {
|
|
68
|
+
return { stats: toVideoStats(parsed.row), cached: true };
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const row = await fetchVideoStatsRow(urlOrId);
|
|
73
|
+
await ensurePrivateDir(shardDir(videoId));
|
|
74
|
+
await writeJsonAtomic(cachePath(videoId), {
|
|
75
|
+
version: CACHE_VERSION,
|
|
76
|
+
fetchedAt: now,
|
|
77
|
+
row,
|
|
78
|
+
}).catch(() => undefined);
|
|
79
|
+
return { stats: toVideoStats(row), cached: false };
|
|
80
|
+
}
|
|
81
|
+
//# sourceMappingURL=video-stats-cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stats-cache.js","sourceRoot":"","sources":["../../src/utils/video-stats-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* One full read of a video, serving every caller that needs part of it.
|
|
4
|
+
*
|
|
5
|
+
* `getVideoInfo` ran a narrow `--print` and `getVideoDetails` ran a full `-j`
|
|
6
|
+
* and discarded all but three fields, so a single video cost two spawns for
|
|
7
|
+
* data one already had. Requests to YouTube are the thing that gets a client
|
|
8
|
+
* throttled, so halving them is the cheapest reliability win available.
|
|
9
|
+
*
|
|
10
|
+
* What is NOT kept matters as much as what is. Measured on a 637KB payload:
|
|
11
|
+
* `automatic_captions` is 460,965 bytes (72%) of URLs that expire, `formats`
|
|
12
|
+
* another 113,601 (18%), and `heatmap` 6,589 — larger than the whole rest of
|
|
13
|
+
* the record. Stripped, a video is 4,785 bytes. Storing the raw object would
|
|
14
|
+
* cost 6.4GB per 10,000 videos instead of 48MB.
|
|
15
|
+
*/
|
|
16
|
+
/** A stored snapshot must stay small enough that a catalogue is not mostly URLs. */
|
|
17
|
+
export declare const MAX_DETAIL_JSON_BYTES = 32768;
|
|
18
|
+
/** Keys dropped before storage: expiring URLs and bulk we can re-fetch. */
|
|
19
|
+
export declare const STRIPPED_KEYS: readonly ["formats", "requested_formats", "automatic_captions", "subtitles", "thumbnails", "comments", "heatmap"];
|
|
20
|
+
export declare const videoStatsRowSchema: z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
title: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
23
|
+
channel: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
24
|
+
channel_id: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
25
|
+
channel_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
26
|
+
channel_follower_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
27
|
+
upload_date: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
28
|
+
duration: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
29
|
+
view_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
30
|
+
like_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
31
|
+
comment_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
32
|
+
concurrent_view_count: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
33
|
+
categories: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
|
|
34
|
+
tags: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodString, "many">>>;
|
|
35
|
+
age_limit: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
36
|
+
availability: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
37
|
+
live_status: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
38
|
+
was_live: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
39
|
+
is_live: z.ZodOptional<z.ZodNullable<z.ZodBoolean>>;
|
|
40
|
+
description: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
41
|
+
thumbnail: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
42
|
+
chapters: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
43
|
+
title: z.ZodString;
|
|
44
|
+
start_time: z.ZodNumber;
|
|
45
|
+
end_time: z.ZodNumber;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
title: string;
|
|
48
|
+
start_time: number;
|
|
49
|
+
end_time: number;
|
|
50
|
+
}, {
|
|
51
|
+
title: string;
|
|
52
|
+
start_time: number;
|
|
53
|
+
end_time: number;
|
|
54
|
+
}>, "many">>>;
|
|
55
|
+
heatmap: z.ZodOptional<z.ZodNullable<z.ZodArray<z.ZodObject<{
|
|
56
|
+
start_time: z.ZodNumber;
|
|
57
|
+
end_time: z.ZodNumber;
|
|
58
|
+
value: z.ZodNumber;
|
|
59
|
+
}, "strip", z.ZodTypeAny, {
|
|
60
|
+
value: number;
|
|
61
|
+
start_time: number;
|
|
62
|
+
end_time: number;
|
|
63
|
+
}, {
|
|
64
|
+
value: number;
|
|
65
|
+
start_time: number;
|
|
66
|
+
end_time: number;
|
|
67
|
+
}>, "many">>>;
|
|
68
|
+
automatic_captions: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
69
|
+
subtitles: z.ZodOptional<z.ZodNullable<z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
70
|
+
}, "strip", z.ZodTypeAny, {
|
|
71
|
+
id: string;
|
|
72
|
+
title?: string | null | undefined;
|
|
73
|
+
channel?: string | null | undefined;
|
|
74
|
+
description?: string | null | undefined;
|
|
75
|
+
tags?: string[] | null | undefined;
|
|
76
|
+
duration?: number | null | undefined;
|
|
77
|
+
upload_date?: string | null | undefined;
|
|
78
|
+
thumbnail?: string | null | undefined;
|
|
79
|
+
view_count?: number | null | undefined;
|
|
80
|
+
like_count?: number | null | undefined;
|
|
81
|
+
comment_count?: number | null | undefined;
|
|
82
|
+
availability?: string | null | undefined;
|
|
83
|
+
live_status?: string | null | undefined;
|
|
84
|
+
chapters?: {
|
|
85
|
+
title: string;
|
|
86
|
+
start_time: number;
|
|
87
|
+
end_time: number;
|
|
88
|
+
}[] | null | undefined;
|
|
89
|
+
subtitles?: Record<string, unknown> | null | undefined;
|
|
90
|
+
automatic_captions?: Record<string, unknown> | null | undefined;
|
|
91
|
+
channel_id?: string | null | undefined;
|
|
92
|
+
channel_url?: string | null | undefined;
|
|
93
|
+
channel_follower_count?: number | null | undefined;
|
|
94
|
+
heatmap?: {
|
|
95
|
+
value: number;
|
|
96
|
+
start_time: number;
|
|
97
|
+
end_time: number;
|
|
98
|
+
}[] | null | undefined;
|
|
99
|
+
concurrent_view_count?: number | null | undefined;
|
|
100
|
+
categories?: string[] | null | undefined;
|
|
101
|
+
age_limit?: number | null | undefined;
|
|
102
|
+
was_live?: boolean | null | undefined;
|
|
103
|
+
is_live?: boolean | null | undefined;
|
|
104
|
+
}, {
|
|
105
|
+
id: string;
|
|
106
|
+
title?: string | null | undefined;
|
|
107
|
+
channel?: string | null | undefined;
|
|
108
|
+
description?: string | null | undefined;
|
|
109
|
+
tags?: string[] | null | undefined;
|
|
110
|
+
duration?: number | null | undefined;
|
|
111
|
+
upload_date?: string | null | undefined;
|
|
112
|
+
thumbnail?: string | null | undefined;
|
|
113
|
+
view_count?: number | null | undefined;
|
|
114
|
+
like_count?: number | null | undefined;
|
|
115
|
+
comment_count?: number | null | undefined;
|
|
116
|
+
availability?: string | null | undefined;
|
|
117
|
+
live_status?: string | null | undefined;
|
|
118
|
+
chapters?: {
|
|
119
|
+
title: string;
|
|
120
|
+
start_time: number;
|
|
121
|
+
end_time: number;
|
|
122
|
+
}[] | null | undefined;
|
|
123
|
+
subtitles?: Record<string, unknown> | null | undefined;
|
|
124
|
+
automatic_captions?: Record<string, unknown> | null | undefined;
|
|
125
|
+
channel_id?: string | null | undefined;
|
|
126
|
+
channel_url?: string | null | undefined;
|
|
127
|
+
channel_follower_count?: number | null | undefined;
|
|
128
|
+
heatmap?: {
|
|
129
|
+
value: number;
|
|
130
|
+
start_time: number;
|
|
131
|
+
end_time: number;
|
|
132
|
+
}[] | null | undefined;
|
|
133
|
+
concurrent_view_count?: number | null | undefined;
|
|
134
|
+
categories?: string[] | null | undefined;
|
|
135
|
+
age_limit?: number | null | undefined;
|
|
136
|
+
was_live?: boolean | null | undefined;
|
|
137
|
+
is_live?: boolean | null | undefined;
|
|
138
|
+
}>;
|
|
139
|
+
export interface VideoStats {
|
|
140
|
+
id: string;
|
|
141
|
+
title: string;
|
|
142
|
+
url: string;
|
|
143
|
+
channel: string;
|
|
144
|
+
channelId?: string;
|
|
145
|
+
channelFollowerCount?: number;
|
|
146
|
+
uploadDate: string;
|
|
147
|
+
durationSeconds: number;
|
|
148
|
+
durationFormatted: string;
|
|
149
|
+
viewCount?: number;
|
|
150
|
+
likeCount?: number;
|
|
151
|
+
/** YouTube's own count. Trustworthy here because this read has no --write-comments. */
|
|
152
|
+
commentCount?: number;
|
|
153
|
+
categories: string[];
|
|
154
|
+
tags: string[];
|
|
155
|
+
ageLimit: number;
|
|
156
|
+
availability?: string;
|
|
157
|
+
liveStatus?: string;
|
|
158
|
+
wasLive: boolean;
|
|
159
|
+
chapters: {
|
|
160
|
+
title: string;
|
|
161
|
+
startTime: number;
|
|
162
|
+
endTime: number;
|
|
163
|
+
}[];
|
|
164
|
+
/** Language tags only — never the URL arrays, which are 72% of the payload. */
|
|
165
|
+
captions: {
|
|
166
|
+
manual: string[];
|
|
167
|
+
automatic: string[];
|
|
168
|
+
};
|
|
169
|
+
hasHeatmap: boolean;
|
|
170
|
+
heatmap?: {
|
|
171
|
+
startSeconds: number;
|
|
172
|
+
endSeconds: number;
|
|
173
|
+
value: number;
|
|
174
|
+
}[];
|
|
175
|
+
descriptionChars: number;
|
|
176
|
+
description: string;
|
|
177
|
+
thumbnailUrl?: string;
|
|
178
|
+
}
|
|
179
|
+
export declare const CATALOG_DESCRIPTION_PREVIEW_CHARS = 500;
|
|
180
|
+
/** Drops the bulk keys, so what is stored is the record rather than its attachments. */
|
|
181
|
+
export declare function stripForStorage(raw: Record<string, unknown>): Record<string, unknown>;
|
|
182
|
+
export declare function toVideoStats(row: z.infer<typeof videoStatsRowSchema>): VideoStats;
|
|
183
|
+
/** One `-j` read, serving info, details, chapters, captions and the heatmap. */
|
|
184
|
+
export declare function fetchVideoStatsRow(urlOrId: string): Promise<z.infer<typeof videoStatsRowSchema>>;
|
|
185
|
+
export declare function getVideoStats(urlOrId: string): Promise<VideoStats>;
|
|
186
|
+
//# sourceMappingURL=video-stats.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stats.d.ts","sourceRoot":"","sources":["../../src/utils/video-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAE5C,2EAA2E;AAC3E,eAAO,MAAM,aAAa,mHAQhB,CAAC;AAQX,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA4B9B,CAAC;AAEH,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IAClE,+EAA+E;IAC/E,QAAQ,EAAE;QAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACpD,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE;QAAE,YAAY,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACxE,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,iCAAiC,MAAM,CAAC;AAErD,wFAAwF;AACxF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcrF;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,GAAG,UAAU,CA0EjF;AAED,gFAAgF;AAChF,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC,CAS9C;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAExE"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { TIMEOUTS, parseYtDlpJson, runYtDlp } from './ytdlp.js';
|
|
3
|
+
import { extractVideoId, formatDuration, watchUrl } from './youtube-url.js';
|
|
4
|
+
import { classifyPlayability } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* One full read of a video, serving every caller that needs part of it.
|
|
7
|
+
*
|
|
8
|
+
* `getVideoInfo` ran a narrow `--print` and `getVideoDetails` ran a full `-j`
|
|
9
|
+
* and discarded all but three fields, so a single video cost two spawns for
|
|
10
|
+
* data one already had. Requests to YouTube are the thing that gets a client
|
|
11
|
+
* throttled, so halving them is the cheapest reliability win available.
|
|
12
|
+
*
|
|
13
|
+
* What is NOT kept matters as much as what is. Measured on a 637KB payload:
|
|
14
|
+
* `automatic_captions` is 460,965 bytes (72%) of URLs that expire, `formats`
|
|
15
|
+
* another 113,601 (18%), and `heatmap` 6,589 — larger than the whole rest of
|
|
16
|
+
* the record. Stripped, a video is 4,785 bytes. Storing the raw object would
|
|
17
|
+
* cost 6.4GB per 10,000 videos instead of 48MB.
|
|
18
|
+
*/
|
|
19
|
+
/** A stored snapshot must stay small enough that a catalogue is not mostly URLs. */
|
|
20
|
+
export const MAX_DETAIL_JSON_BYTES = 32_768;
|
|
21
|
+
/** Keys dropped before storage: expiring URLs and bulk we can re-fetch. */
|
|
22
|
+
export const STRIPPED_KEYS = [
|
|
23
|
+
'formats',
|
|
24
|
+
'requested_formats',
|
|
25
|
+
'automatic_captions',
|
|
26
|
+
'subtitles',
|
|
27
|
+
'thumbnails',
|
|
28
|
+
'comments',
|
|
29
|
+
'heatmap',
|
|
30
|
+
];
|
|
31
|
+
const heatmapEntrySchema = z.object({
|
|
32
|
+
start_time: z.number(),
|
|
33
|
+
end_time: z.number(),
|
|
34
|
+
value: z.number(),
|
|
35
|
+
});
|
|
36
|
+
export const videoStatsRowSchema = z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
title: z.string().nullish(),
|
|
39
|
+
channel: z.string().nullish(),
|
|
40
|
+
channel_id: z.string().nullish(),
|
|
41
|
+
channel_url: z.string().nullish(),
|
|
42
|
+
channel_follower_count: z.number().nullish(),
|
|
43
|
+
upload_date: z.string().nullish(),
|
|
44
|
+
duration: z.number().nullish(),
|
|
45
|
+
view_count: z.number().nullish(),
|
|
46
|
+
like_count: z.number().nullish(),
|
|
47
|
+
comment_count: z.number().nullish(),
|
|
48
|
+
concurrent_view_count: z.number().nullish(),
|
|
49
|
+
categories: z.array(z.string()).nullish(),
|
|
50
|
+
tags: z.array(z.string()).nullish(),
|
|
51
|
+
age_limit: z.number().nullish(),
|
|
52
|
+
availability: z.string().nullish(),
|
|
53
|
+
live_status: z.string().nullish(),
|
|
54
|
+
was_live: z.boolean().nullish(),
|
|
55
|
+
is_live: z.boolean().nullish(),
|
|
56
|
+
description: z.string().nullish(),
|
|
57
|
+
thumbnail: z.string().nullish(),
|
|
58
|
+
chapters: z
|
|
59
|
+
.array(z.object({ title: z.string(), start_time: z.number(), end_time: z.number() }))
|
|
60
|
+
.nullish(),
|
|
61
|
+
heatmap: z.array(heatmapEntrySchema).nullish(),
|
|
62
|
+
automatic_captions: z.record(z.string(), z.unknown()).nullish(),
|
|
63
|
+
subtitles: z.record(z.string(), z.unknown()).nullish(),
|
|
64
|
+
});
|
|
65
|
+
export const CATALOG_DESCRIPTION_PREVIEW_CHARS = 500;
|
|
66
|
+
/** Drops the bulk keys, so what is stored is the record rather than its attachments. */
|
|
67
|
+
export function stripForStorage(raw) {
|
|
68
|
+
const kept = {};
|
|
69
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
70
|
+
if (STRIPPED_KEYS.some((stripped) => stripped === key))
|
|
71
|
+
continue;
|
|
72
|
+
kept[key] = value;
|
|
73
|
+
}
|
|
74
|
+
const description = kept.description;
|
|
75
|
+
if (typeof description === 'string' && description.length > CATALOG_DESCRIPTION_PREVIEW_CHARS) {
|
|
76
|
+
kept.description = description.slice(0, CATALOG_DESCRIPTION_PREVIEW_CHARS);
|
|
77
|
+
kept.description_truncated = true;
|
|
78
|
+
}
|
|
79
|
+
return kept;
|
|
80
|
+
}
|
|
81
|
+
export function toVideoStats(row) {
|
|
82
|
+
// classifyPlayability RETURNS the error rather than throwing it, so ignoring
|
|
83
|
+
// the result would hand back ordinary-looking stats for a private or
|
|
84
|
+
// members-only video.
|
|
85
|
+
const unplayable = classifyPlayability({
|
|
86
|
+
availability: row.availability ?? undefined,
|
|
87
|
+
liveStatus: row.live_status ?? undefined,
|
|
88
|
+
});
|
|
89
|
+
if (unplayable !== undefined)
|
|
90
|
+
throw unplayable;
|
|
91
|
+
const duration = row.duration ?? 0;
|
|
92
|
+
const heatmap = row.heatmap ?? undefined;
|
|
93
|
+
const description = row.description ?? '';
|
|
94
|
+
return {
|
|
95
|
+
id: row.id,
|
|
96
|
+
title: row.title ?? 'Unknown',
|
|
97
|
+
url: watchUrl(row.id),
|
|
98
|
+
channel: row.channel ?? 'Unknown',
|
|
99
|
+
...(row.channel_id === null || row.channel_id === undefined
|
|
100
|
+
? {}
|
|
101
|
+
: { channelId: row.channel_id }),
|
|
102
|
+
...(row.channel_follower_count === null || row.channel_follower_count === undefined
|
|
103
|
+
? {}
|
|
104
|
+
: { channelFollowerCount: row.channel_follower_count }),
|
|
105
|
+
uploadDate: row.upload_date ?? '',
|
|
106
|
+
durationSeconds: duration,
|
|
107
|
+
durationFormatted: formatDuration(Math.floor(duration)),
|
|
108
|
+
...(row.view_count === null || row.view_count === undefined
|
|
109
|
+
? {}
|
|
110
|
+
: { viewCount: row.view_count }),
|
|
111
|
+
...(row.like_count === null || row.like_count === undefined
|
|
112
|
+
? {}
|
|
113
|
+
: { likeCount: row.like_count }),
|
|
114
|
+
...(row.comment_count === null || row.comment_count === undefined
|
|
115
|
+
? {}
|
|
116
|
+
: { commentCount: row.comment_count }),
|
|
117
|
+
categories: row.categories ?? [],
|
|
118
|
+
tags: row.tags ?? [],
|
|
119
|
+
ageLimit: row.age_limit ?? 0,
|
|
120
|
+
...(row.availability === null || row.availability === undefined
|
|
121
|
+
? {}
|
|
122
|
+
: { availability: row.availability }),
|
|
123
|
+
...(row.live_status === null || row.live_status === undefined
|
|
124
|
+
? {}
|
|
125
|
+
: { liveStatus: row.live_status }),
|
|
126
|
+
wasLive: row.was_live ?? false,
|
|
127
|
+
chapters: (row.chapters ?? []).map((chapter) => ({
|
|
128
|
+
title: chapter.title,
|
|
129
|
+
startTime: chapter.start_time,
|
|
130
|
+
endTime: chapter.end_time,
|
|
131
|
+
})),
|
|
132
|
+
// Keys only: the values are arrays of expiring URLs, and the inventory is
|
|
133
|
+
// what a caller actually asks about.
|
|
134
|
+
captions: {
|
|
135
|
+
manual: Object.keys(row.subtitles ?? {}),
|
|
136
|
+
automatic: Object.keys(row.automatic_captions ?? {}),
|
|
137
|
+
},
|
|
138
|
+
hasHeatmap: heatmap !== undefined && heatmap.length > 0,
|
|
139
|
+
...(heatmap === undefined
|
|
140
|
+
? {}
|
|
141
|
+
: {
|
|
142
|
+
heatmap: heatmap.map((entry) => ({
|
|
143
|
+
startSeconds: entry.start_time,
|
|
144
|
+
endSeconds: entry.end_time,
|
|
145
|
+
value: entry.value,
|
|
146
|
+
})),
|
|
147
|
+
}),
|
|
148
|
+
descriptionChars: description.length,
|
|
149
|
+
description,
|
|
150
|
+
...(row.thumbnail === null || row.thumbnail === undefined
|
|
151
|
+
? {}
|
|
152
|
+
: { thumbnailUrl: row.thumbnail }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/** One `-j` read, serving info, details, chapters, captions and the heatmap. */
|
|
156
|
+
export async function fetchVideoStatsRow(urlOrId) {
|
|
157
|
+
const videoId = extractVideoId(urlOrId);
|
|
158
|
+
const stdout = await runYtDlp(['-j', '--skip-download'], {
|
|
159
|
+
label: 'get_video_stats',
|
|
160
|
+
timeoutMs: TIMEOUTS.metadata,
|
|
161
|
+
target: watchUrl(videoId),
|
|
162
|
+
});
|
|
163
|
+
return parseYtDlpJson(stdout, videoStatsRowSchema, 'video stats');
|
|
164
|
+
}
|
|
165
|
+
export async function getVideoStats(urlOrId) {
|
|
166
|
+
return toVideoStats(await fetchVideoStatsRow(urlOrId));
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=video-stats.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-stats.js","sourceRoot":"","sources":["../../src/utils/video-stats.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAElD;;;;;;;;;;;;;GAaG;AAEH,oFAAoF;AACpF,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,SAAS;IACT,mBAAmB;IACnB,oBAAoB;IACpB,WAAW;IACX,YAAY;IACZ,UAAU;IACV,SAAS;CACD,CAAC;AAEX,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IAClC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE;IACpB,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;CAClB,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC1C,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE;IACd,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC3B,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC7B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAChC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACjC,sBAAsB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC9B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAChC,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAChC,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACnC,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC3C,UAAU,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,EAAE;IACnC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC/B,YAAY,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAClC,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACjC,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE;IAC/B,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,EAAE;IAC9B,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IACjC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,EAAE;IAC/B,QAAQ,EAAE,CAAC;SACR,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;SACpF,OAAO,EAAE;IACZ,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC,OAAO,EAAE;IAC9C,kBAAkB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IAC/D,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;CACvD,CAAC,CAAC;AAgCH,MAAM,CAAC,MAAM,iCAAiC,GAAG,GAAG,CAAC;AAErD,wFAAwF;AACxF,MAAM,UAAU,eAAe,CAAC,GAA4B;IAC1D,MAAM,IAAI,GAA4B,EAAE,CAAC;IACzC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/C,IAAI,aAAa,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,QAAQ,KAAK,GAAG,CAAC;YAAE,SAAS;QACjE,IAAI,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IACpB,CAAC;IAED,MAAM,WAAW,GAAG,IAAI,CAAC,WAAW,CAAC;IACrC,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,WAAW,CAAC,MAAM,GAAG,iCAAiC,EAAE,CAAC;QAC9F,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,iCAAiC,CAAC,CAAC;QAC3E,IAAI,CAAC,qBAAqB,GAAG,IAAI,CAAC;IACpC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,GAAwC;IACnE,6EAA6E;IAC7E,qEAAqE;IACrE,sBAAsB;IACtB,MAAM,UAAU,GAAG,mBAAmB,CAAC;QACrC,YAAY,EAAE,GAAG,CAAC,YAAY,IAAI,SAAS;QAC3C,UAAU,EAAE,GAAG,CAAC,WAAW,IAAI,SAAS;KACzC,CAAC,CAAC;IACH,IAAI,UAAU,KAAK,SAAS;QAAE,MAAM,UAAU,CAAC;IAE/C,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,IAAI,CAAC,CAAC;IACnC,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,IAAI,SAAS,CAAC;IACzC,MAAM,WAAW,GAAG,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC;IAE1C,OAAO;QACL,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,KAAK,EAAE,GAAG,CAAC,KAAK,IAAI,SAAS;QAC7B,GAAG,EAAE,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACrB,OAAO,EAAE,GAAG,CAAC,OAAO,IAAI,SAAS;QACjC,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS;YACzD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QAClC,GAAG,CAAC,GAAG,CAAC,sBAAsB,KAAK,IAAI,IAAI,GAAG,CAAC,sBAAsB,KAAK,SAAS;YACjF,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,oBAAoB,EAAE,GAAG,CAAC,sBAAsB,EAAE,CAAC;QACzD,UAAU,EAAE,GAAG,CAAC,WAAW,IAAI,EAAE;QACjC,eAAe,EAAE,QAAQ;QACzB,iBAAiB,EAAE,cAAc,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACvD,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS;YACzD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QAClC,GAAG,CAAC,GAAG,CAAC,UAAU,KAAK,IAAI,IAAI,GAAG,CAAC,UAAU,KAAK,SAAS;YACzD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,SAAS,EAAE,GAAG,CAAC,UAAU,EAAE,CAAC;QAClC,GAAG,CAAC,GAAG,CAAC,aAAa,KAAK,IAAI,IAAI,GAAG,CAAC,aAAa,KAAK,SAAS;YAC/D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,aAAa,EAAE,CAAC;QACxC,UAAU,EAAE,GAAG,CAAC,UAAU,IAAI,EAAE;QAChC,IAAI,EAAE,GAAG,CAAC,IAAI,IAAI,EAAE;QACpB,QAAQ,EAAE,GAAG,CAAC,SAAS,IAAI,CAAC;QAC5B,GAAG,CAAC,GAAG,CAAC,YAAY,KAAK,IAAI,IAAI,GAAG,CAAC,YAAY,KAAK,SAAS;YAC7D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,CAAC;QACvC,GAAG,CAAC,GAAG,CAAC,WAAW,KAAK,IAAI,IAAI,GAAG,CAAC,WAAW,KAAK,SAAS;YAC3D,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,WAAW,EAAE,CAAC;QACpC,OAAO,EAAE,GAAG,CAAC,QAAQ,IAAI,KAAK;QAC9B,QAAQ,EAAE,CAAC,GAAG,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAC/C,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,SAAS,EAAE,OAAO,CAAC,UAAU;YAC7B,OAAO,EAAE,OAAO,CAAC,QAAQ;SAC1B,CAAC,CAAC;QACH,0EAA0E;QAC1E,qCAAqC;QACrC,QAAQ,EAAE;YACR,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,IAAI,EAAE,CAAC;YACxC,SAAS,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,IAAI,EAAE,CAAC;SACrD;QACD,UAAU,EAAE,OAAO,KAAK,SAAS,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;QACvD,GAAG,CAAC,OAAO,KAAK,SAAS;YACvB,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC;gBACE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;oBAC/B,YAAY,EAAE,KAAK,CAAC,UAAU;oBAC9B,UAAU,EAAE,KAAK,CAAC,QAAQ;oBAC1B,KAAK,EAAE,KAAK,CAAC,KAAK;iBACnB,CAAC,CAAC;aACJ,CAAC;QACN,gBAAgB,EAAE,WAAW,CAAC,MAAM;QACpC,WAAW;QACX,GAAG,CAAC,GAAG,CAAC,SAAS,KAAK,IAAI,IAAI,GAAG,CAAC,SAAS,KAAK,SAAS;YACvD,CAAC,CAAC,EAAE;YACJ,CAAC,CAAC,EAAE,YAAY,EAAE,GAAG,CAAC,SAAS,EAAE,CAAC;KACrC,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAe;IAEf,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,CAAC,IAAI,EAAE,iBAAiB,CAAC,EAAE;QACvD,KAAK,EAAE,iBAAiB;QACxB,SAAS,EAAE,QAAQ,CAAC,QAAQ;QAC5B,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC;KAC1B,CAAC,CAAC;IAEH,OAAO,cAAc,CAAC,MAAM,EAAE,mBAAmB,EAAE,aAAa,CAAC,CAAC;AACpE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAAe;IACjD,OAAO,YAAY,CAAC,MAAM,kBAAkB,CAAC,OAAO,CAAC,CAAC,CAAC;AACzD,CAAC"}
|