youtube-knowledge-mcp 3.0.0 → 3.0.1

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.
Files changed (267) hide show
  1. package/README.md +87 -41
  2. package/dist/brain-schemas.d.ts +10 -0
  3. package/dist/brain-schemas.d.ts.map +1 -1
  4. package/dist/harvest-schemas.d.ts +6 -6
  5. package/dist/harvest-schemas.d.ts.map +1 -1
  6. package/dist/harvest-schemas.js +5 -0
  7. package/dist/harvest-schemas.js.map +1 -1
  8. package/dist/registry/brain-tools.d.ts.map +1 -1
  9. package/dist/registry/brain-tools.js +16 -0
  10. package/dist/registry/brain-tools.js.map +1 -1
  11. package/dist/registry/channel-tools.d.ts.map +1 -1
  12. package/dist/registry/channel-tools.js +16 -0
  13. package/dist/registry/channel-tools.js.map +1 -1
  14. package/dist/registry/index.d.ts.map +1 -1
  15. package/dist/registry/index.js +4 -0
  16. package/dist/registry/index.js.map +1 -1
  17. package/dist/registry/media-tools.d.ts.map +1 -1
  18. package/dist/registry/media-tools.js +31 -0
  19. package/dist/registry/media-tools.js.map +1 -1
  20. package/dist/registry/store-tools.d.ts +9 -0
  21. package/dist/registry/store-tools.d.ts.map +1 -0
  22. package/dist/registry/store-tools.js +59 -0
  23. package/dist/registry/store-tools.js.map +1 -0
  24. package/dist/registry/transcript-tools.d.ts +4 -1
  25. package/dist/registry/transcript-tools.d.ts.map +1 -1
  26. package/dist/registry/transcript-tools.js +20 -1
  27. package/dist/registry/transcript-tools.js.map +1 -1
  28. package/dist/registry/video-tools.d.ts.map +1 -1
  29. package/dist/registry/video-tools.js +48 -0
  30. package/dist/registry/video-tools.js.map +1 -1
  31. package/dist/registry/vision-tools.d.ts +7 -0
  32. package/dist/registry/vision-tools.d.ts.map +1 -0
  33. package/dist/registry/vision-tools.js +55 -0
  34. package/dist/registry/vision-tools.js.map +1 -0
  35. package/dist/schemas.d.ts +6 -0
  36. package/dist/schemas.d.ts.map +1 -1
  37. package/dist/schemas.js +2 -0
  38. package/dist/schemas.js.map +1 -1
  39. package/dist/thumbnail-schemas.d.ts +10 -0
  40. package/dist/thumbnail-schemas.d.ts.map +1 -1
  41. package/dist/tools/ask-brains.d.ts +66 -0
  42. package/dist/tools/ask-brains.d.ts.map +1 -0
  43. package/dist/tools/ask-brains.js +111 -0
  44. package/dist/tools/ask-brains.js.map +1 -0
  45. package/dist/tools/check-health.d.ts +16 -6
  46. package/dist/tools/check-health.d.ts.map +1 -1
  47. package/dist/tools/check-health.js +30 -1
  48. package/dist/tools/check-health.js.map +1 -1
  49. package/dist/tools/check-new-uploads.d.ts +55 -0
  50. package/dist/tools/check-new-uploads.d.ts.map +1 -0
  51. package/dist/tools/check-new-uploads.js +74 -0
  52. package/dist/tools/check-new-uploads.js.map +1 -0
  53. package/dist/tools/clips.d.ts +15 -0
  54. package/dist/tools/clips.d.ts.map +1 -1
  55. package/dist/tools/clips.js +5 -5
  56. package/dist/tools/clips.js.map +1 -1
  57. package/dist/tools/detect-scenes.d.ts +38 -0
  58. package/dist/tools/detect-scenes.d.ts.map +1 -0
  59. package/dist/tools/detect-scenes.js +66 -0
  60. package/dist/tools/detect-scenes.js.map +1 -0
  61. package/dist/tools/export-data.d.ts +85 -0
  62. package/dist/tools/export-data.d.ts.map +1 -0
  63. package/dist/tools/export-data.js +99 -0
  64. package/dist/tools/export-data.js.map +1 -0
  65. package/dist/tools/frames-subtitles.d.ts.map +1 -1
  66. package/dist/tools/frames-subtitles.js +16 -8
  67. package/dist/tools/frames-subtitles.js.map +1 -1
  68. package/dist/tools/frames.d.ts +73 -0
  69. package/dist/tools/frames.d.ts.map +1 -0
  70. package/dist/tools/frames.js +136 -0
  71. package/dist/tools/frames.js.map +1 -0
  72. package/dist/tools/get-channel-info.d.ts +2 -0
  73. package/dist/tools/get-channel-info.d.ts.map +1 -1
  74. package/dist/tools/get-channel-info.js +4 -0
  75. package/dist/tools/get-channel-info.js.map +1 -1
  76. package/dist/tools/get-comments.d.ts +5 -5
  77. package/dist/tools/get-coverage.d.ts +5 -5
  78. package/dist/tools/get-sponsor-segments.d.ts +49 -0
  79. package/dist/tools/get-sponsor-segments.d.ts.map +1 -0
  80. package/dist/tools/get-sponsor-segments.js +61 -0
  81. package/dist/tools/get-sponsor-segments.js.map +1 -0
  82. package/dist/tools/get-thumbnail.d.ts.map +1 -1
  83. package/dist/tools/get-thumbnail.js +2 -1
  84. package/dist/tools/get-thumbnail.js.map +1 -1
  85. package/dist/tools/get-transcript.d.ts +1 -0
  86. package/dist/tools/get-transcript.d.ts.map +1 -1
  87. package/dist/tools/get-transcript.js +11 -4
  88. package/dist/tools/get-transcript.js.map +1 -1
  89. package/dist/tools/get-video-details.d.ts +118 -0
  90. package/dist/tools/get-video-details.d.ts.map +1 -0
  91. package/dist/tools/get-video-details.js +147 -0
  92. package/dist/tools/get-video-details.js.map +1 -0
  93. package/dist/tools/gif-concat.d.ts +49 -0
  94. package/dist/tools/gif-concat.d.ts.map +1 -0
  95. package/dist/tools/gif-concat.js +96 -0
  96. package/dist/tools/gif-concat.js.map +1 -0
  97. package/dist/tools/harvest-channel.d.ts +5 -5
  98. package/dist/tools/harvest-comments.d.ts +5 -5
  99. package/dist/tools/harvest-details.d.ts +84 -0
  100. package/dist/tools/harvest-details.d.ts.map +1 -0
  101. package/dist/tools/harvest-details.js +71 -0
  102. package/dist/tools/harvest-details.js.map +1 -0
  103. package/dist/tools/list-captions.d.ts +17 -0
  104. package/dist/tools/list-captions.d.ts.map +1 -0
  105. package/dist/tools/list-captions.js +60 -0
  106. package/dist/tools/list-captions.js.map +1 -0
  107. package/dist/tools/list-channel-playlists.d.ts +30 -0
  108. package/dist/tools/list-channel-playlists.d.ts.map +1 -0
  109. package/dist/tools/list-channel-playlists.js +44 -0
  110. package/dist/tools/list-channel-playlists.js.map +1 -0
  111. package/dist/tools/query-comments.d.ts +5 -5
  112. package/dist/tools/query-videos.d.ts +5 -5
  113. package/dist/tools/search-channels.d.ts +6 -0
  114. package/dist/tools/search-channels.d.ts.map +1 -1
  115. package/dist/tools/transcribe-video.d.ts +28 -0
  116. package/dist/tools/transcribe-video.d.ts.map +1 -0
  117. package/dist/tools/transcribe-video.js +52 -0
  118. package/dist/tools/transcribe-video.js.map +1 -0
  119. package/dist/utils/brain-fusion.d.ts +24 -0
  120. package/dist/utils/brain-fusion.d.ts.map +1 -0
  121. package/dist/utils/brain-fusion.js +18 -0
  122. package/dist/utils/brain-fusion.js.map +1 -0
  123. package/dist/utils/channel-playlists.d.ts +21 -0
  124. package/dist/utils/channel-playlists.d.ts.map +1 -0
  125. package/dist/utils/channel-playlists.js +56 -0
  126. package/dist/utils/channel-playlists.js.map +1 -0
  127. package/dist/utils/clips.d.ts.map +1 -1
  128. package/dist/utils/clips.js +3 -17
  129. package/dist/utils/clips.js.map +1 -1
  130. package/dist/utils/concat.d.ts +19 -0
  131. package/dist/utils/concat.d.ts.map +1 -0
  132. package/dist/utils/concat.js +135 -0
  133. package/dist/utils/concat.js.map +1 -0
  134. package/dist/utils/contact-sheet.d.ts +28 -0
  135. package/dist/utils/contact-sheet.d.ts.map +1 -0
  136. package/dist/utils/contact-sheet.js +65 -0
  137. package/dist/utils/contact-sheet.js.map +1 -0
  138. package/dist/utils/detail-batch.d.ts +25 -0
  139. package/dist/utils/detail-batch.d.ts.map +1 -0
  140. package/dist/utils/detail-batch.js +58 -0
  141. package/dist/utils/detail-batch.js.map +1 -0
  142. package/dist/utils/errors.d.ts +1 -1
  143. package/dist/utils/errors.d.ts.map +1 -1
  144. package/dist/utils/errors.js +10 -0
  145. package/dist/utils/errors.js.map +1 -1
  146. package/dist/utils/export-data.d.ts +41 -0
  147. package/dist/utils/export-data.d.ts.map +1 -0
  148. package/dist/utils/export-data.js +114 -0
  149. package/dist/utils/export-data.js.map +1 -0
  150. package/dist/utils/export-format.d.ts +23 -0
  151. package/dist/utils/export-format.d.ts.map +1 -0
  152. package/dist/utils/export-format.js +59 -0
  153. package/dist/utils/export-format.js.map +1 -0
  154. package/dist/utils/export-load.d.ts +28 -0
  155. package/dist/utils/export-load.d.ts.map +1 -0
  156. package/dist/utils/export-load.js +70 -0
  157. package/dist/utils/export-load.js.map +1 -0
  158. package/dist/utils/export-rows.d.ts +35 -0
  159. package/dist/utils/export-rows.d.ts.map +1 -0
  160. package/dist/utils/export-rows.js +69 -0
  161. package/dist/utils/export-rows.js.map +1 -0
  162. package/dist/utils/ffmpeg.d.ts +26 -0
  163. package/dist/utils/ffmpeg.d.ts.map +1 -0
  164. package/dist/utils/ffmpeg.js +28 -0
  165. package/dist/utils/ffmpeg.js.map +1 -0
  166. package/dist/utils/format.d.ts +6 -0
  167. package/dist/utils/format.d.ts.map +1 -1
  168. package/dist/utils/format.js +4 -0
  169. package/dist/utils/format.js.map +1 -1
  170. package/dist/utils/frame-plan.d.ts +24 -0
  171. package/dist/utils/frame-plan.d.ts.map +1 -0
  172. package/dist/utils/frame-plan.js +58 -0
  173. package/dist/utils/frame-plan.js.map +1 -0
  174. package/dist/utils/frames.d.ts +32 -0
  175. package/dist/utils/frames.d.ts.map +1 -0
  176. package/dist/utils/frames.js +60 -0
  177. package/dist/utils/frames.js.map +1 -0
  178. package/dist/utils/gif.d.ts +31 -0
  179. package/dist/utils/gif.d.ts.map +1 -0
  180. package/dist/utils/gif.js +77 -0
  181. package/dist/utils/gif.js.map +1 -0
  182. package/dist/utils/harvest-catalog.d.ts +10 -0
  183. package/dist/utils/harvest-catalog.d.ts.map +1 -1
  184. package/dist/utils/harvest-catalog.js +14 -5
  185. package/dist/utils/harvest-catalog.js.map +1 -1
  186. package/dist/utils/harvest-details.d.ts +30 -0
  187. package/dist/utils/harvest-details.d.ts.map +1 -0
  188. package/dist/utils/harvest-details.js +98 -0
  189. package/dist/utils/harvest-details.js.map +1 -0
  190. package/dist/utils/local-transcription.d.ts +14 -0
  191. package/dist/utils/local-transcription.d.ts.map +1 -0
  192. package/dist/utils/local-transcription.js +85 -0
  193. package/dist/utils/local-transcription.js.map +1 -0
  194. package/dist/utils/moment.d.ts +24 -0
  195. package/dist/utils/moment.d.ts.map +1 -0
  196. package/dist/utils/moment.js +22 -0
  197. package/dist/utils/moment.js.map +1 -0
  198. package/dist/utils/most-replayed.d.ts +15 -0
  199. package/dist/utils/most-replayed.d.ts.map +1 -0
  200. package/dist/utils/most-replayed.js +25 -0
  201. package/dist/utils/most-replayed.js.map +1 -0
  202. package/dist/utils/scenes.d.ts +39 -0
  203. package/dist/utils/scenes.d.ts.map +1 -0
  204. package/dist/utils/scenes.js +83 -0
  205. package/dist/utils/scenes.js.map +1 -0
  206. package/dist/utils/seconds.d.ts +5 -0
  207. package/dist/utils/seconds.d.ts.map +1 -0
  208. package/dist/utils/seconds.js +7 -0
  209. package/dist/utils/seconds.js.map +1 -0
  210. package/dist/utils/sponsorblock.d.ts +24 -0
  211. package/dist/utils/sponsorblock.d.ts.map +1 -0
  212. package/dist/utils/sponsorblock.js +153 -0
  213. package/dist/utils/sponsorblock.js.map +1 -0
  214. package/dist/utils/transcript-cache.d.ts +5 -1
  215. package/dist/utils/transcript-cache.d.ts.map +1 -1
  216. package/dist/utils/transcript-cache.js +31 -50
  217. package/dist/utils/transcript-cache.js.map +1 -1
  218. package/dist/utils/transcript-files.d.ts +24 -0
  219. package/dist/utils/transcript-files.d.ts.map +1 -0
  220. package/dist/utils/transcript-files.js +61 -0
  221. package/dist/utils/transcript-files.js.map +1 -0
  222. package/dist/utils/transcript.d.ts +3 -0
  223. package/dist/utils/transcript.d.ts.map +1 -1
  224. package/dist/utils/transcript.js +2 -0
  225. package/dist/utils/transcript.js.map +1 -1
  226. package/dist/utils/upload-check.d.ts +38 -0
  227. package/dist/utils/upload-check.d.ts.map +1 -0
  228. package/dist/utils/upload-check.js +93 -0
  229. package/dist/utils/upload-check.js.map +1 -0
  230. package/dist/utils/validate.d.ts +2 -0
  231. package/dist/utils/validate.d.ts.map +1 -1
  232. package/dist/utils/validate.js +4 -0
  233. package/dist/utils/validate.js.map +1 -1
  234. package/dist/utils/video-detail-store.d.ts +47 -0
  235. package/dist/utils/video-detail-store.d.ts.map +1 -0
  236. package/dist/utils/video-detail-store.js +89 -0
  237. package/dist/utils/video-detail-store.js.map +1 -0
  238. package/dist/utils/video-stats-cache.js +1 -1
  239. package/dist/utils/video-stats-schema.d.ts +187 -0
  240. package/dist/utils/video-stats-schema.d.ts.map +1 -0
  241. package/dist/utils/video-stats-schema.js +50 -0
  242. package/dist/utils/video-stats-schema.js.map +1 -0
  243. package/dist/utils/video-stats-storage.d.ts +16 -0
  244. package/dist/utils/video-stats-storage.d.ts.map +1 -0
  245. package/dist/utils/video-stats-storage.js +37 -0
  246. package/dist/utils/video-stats-storage.js.map +1 -0
  247. package/dist/utils/video-stats.d.ts +23 -127
  248. package/dist/utils/video-stats.d.ts.map +1 -1
  249. package/dist/utils/video-stats.js +42 -102
  250. package/dist/utils/video-stats.js.map +1 -1
  251. package/dist/utils/video-stream.d.ts +21 -0
  252. package/dist/utils/video-stream.d.ts.map +1 -0
  253. package/dist/utils/video-stream.js +36 -0
  254. package/dist/utils/video-stream.js.map +1 -0
  255. package/dist/utils/whisper.d.ts +39 -0
  256. package/dist/utils/whisper.d.ts.map +1 -0
  257. package/dist/utils/whisper.js +137 -0
  258. package/dist/utils/whisper.js.map +1 -0
  259. package/dist/utils/youtube-channel.d.ts +4 -0
  260. package/dist/utils/youtube-channel.d.ts.map +1 -1
  261. package/dist/utils/youtube-channel.js +6 -0
  262. package/dist/utils/youtube-channel.js.map +1 -1
  263. package/dist/utils/youtube-schemas.d.ts +6 -0
  264. package/dist/utils/youtube-schemas.d.ts.map +1 -1
  265. package/dist/utils/youtube-schemas.js +2 -0
  266. package/dist/utils/youtube-schemas.js.map +1 -1
  267. package/package.json +9 -3
@@ -0,0 +1,25 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { CompletenessReason } from '../harvest-schemas.js';
3
+ import { type YouTubeErrorCode } from './errors.js';
4
+ import { fetchVideoStatsRow } from './video-stats.js';
5
+ /** Reading one batch of videos and keeping what came back, a batch at a time. */
6
+ export interface DetailFailure {
7
+ videoId: string;
8
+ code: YouTubeErrorCode;
9
+ }
10
+ /** Refusals that say the next video will be refused too: stop rather than hammer. */
11
+ export declare const STOPPING: Partial<Record<YouTubeErrorCode, CompletenessReason>>;
12
+ export type Read = {
13
+ videoId: string;
14
+ row: Awaited<ReturnType<typeof fetchVideoStatsRow>>;
15
+ } | {
16
+ videoId: string;
17
+ code: YouTubeErrorCode;
18
+ };
19
+ export declare function read(videoId: string): Promise<Read>;
20
+ export declare function stoppingCode(batch: Read[]): YouTubeErrorCode | undefined;
21
+ export declare function persist(database: DatabaseSync, batch: Read[]): {
22
+ stored: number;
23
+ failures: DetailFailure[];
24
+ };
25
+ //# sourceMappingURL=detail-batch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detail-batch.d.ts","sourceRoot":"","sources":["../../src/utils/detail-batch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAChE,OAAO,EAAqC,KAAK,gBAAgB,EAAE,MAAM,aAAa,CAAC;AASvF,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAEtD,iFAAiF;AAEjF,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,qFAAqF;AACrF,eAAO,MAAM,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAK1E,CAAC;AAEF,MAAM,MAAM,IAAI,GACZ;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,CAAA;CAAE,GACxE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAEhD,wBAAsB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAczD;AAUD,wBAAgB,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE,GAAG,gBAAgB,GAAG,SAAS,CAIxE;AAED,wBAAgB,OAAO,CACrB,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,IAAI,EAAE,GACZ;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,aAAa,EAAE,CAAA;CAAE,CAqB/C"}
@@ -0,0 +1,58 @@
1
+ import { YouTubeError, classifyPlayability } from './errors.js';
2
+ import { inTransaction } from './store.js';
3
+ import { READ_FAILED_AVAILABILITY, UNREADABLE_AVAILABILITY, markUnreadable, saveVideoDetail, } from './video-detail-store.js';
4
+ import { fetchVideoStatsRow } from './video-stats.js';
5
+ /** Refusals that say the next video will be refused too: stop rather than hammer. */
6
+ export const STOPPING = {
7
+ BOT_CHECK: 'SOURCE_REFUSED',
8
+ LOGIN_REQUIRED: 'SOURCE_REFUSED',
9
+ RATE_LIMITED: 'RATE_LIMITED',
10
+ CANCELLED: 'CANCELLED',
11
+ };
12
+ export async function read(videoId) {
13
+ try {
14
+ // Tolerant, so a private or members-only video comes back as a row whose
15
+ // availability says so, not as a failure that asks to sign in and would be taken
16
+ // for a problem with the whole session.
17
+ const row = await fetchVideoStatsRow(videoId, { tolerateRefusal: true });
18
+ const refusal = classifyPlayability({
19
+ availability: row.availability ?? undefined,
20
+ liveStatus: row.live_status ?? undefined,
21
+ });
22
+ return refusal === undefined ? { videoId, row } : { videoId, code: refusal.code };
23
+ }
24
+ catch (error) {
25
+ return { videoId, code: error instanceof YouTubeError ? error.code : 'YTDLP_FAILED' };
26
+ }
27
+ }
28
+ /** What to record for a video that failed, so it is not tried first on every run. */
29
+ function unreadableMarker(code) {
30
+ return (Object.entries(UNREADABLE_AVAILABILITY).find(([key]) => key === code)?.[1] ??
31
+ READ_FAILED_AVAILABILITY);
32
+ }
33
+ export function stoppingCode(batch) {
34
+ return batch
35
+ .flatMap((outcome) => ('code' in outcome ? [outcome.code] : []))
36
+ .find((code) => STOPPING[code] !== undefined);
37
+ }
38
+ export function persist(database, batch) {
39
+ const failures = [];
40
+ let stored = 0;
41
+ inTransaction(database, () => {
42
+ for (const outcome of batch) {
43
+ if ('row' in outcome) {
44
+ saveVideoDetail(database, { ...outcome.row, id: outcome.videoId }, Date.now());
45
+ stored += 1;
46
+ continue;
47
+ }
48
+ failures.push({ videoId: outcome.videoId, code: outcome.code });
49
+ // A refusal that will hit every video is a session problem: the run stops for it
50
+ // below, and marking this video would blame it for something it did not do.
51
+ if (STOPPING[outcome.code] === undefined) {
52
+ markUnreadable(database, outcome.videoId, unreadableMarker(outcome.code));
53
+ }
54
+ }
55
+ });
56
+ return { stored, failures };
57
+ }
58
+ //# sourceMappingURL=detail-batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detail-batch.js","sourceRoot":"","sources":["../../src/utils/detail-batch.ts"],"names":[],"mappings":"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"}
@@ -7,7 +7,7 @@ import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
7
7
  * than a wall of yt-dlp stderr it has to guess at — and never a leaked command
8
8
  * line or stack trace.
9
9
  */
10
- export type YouTubeErrorCode = 'PRIVATE' | 'AGE_GATED' | 'MEMBERS_ONLY' | 'PREMIUM_ONLY' | 'LOGIN_REQUIRED' | 'BOT_CHECK' | 'NOT_FOUND' | 'NO_CAPTIONS' | 'LIVE_NOT_ENDED' | 'RATE_LIMITED' | 'TIMEOUT' | 'CANCELLED' | 'YTDLP_MISSING' | 'FFMPEG_MISSING' | 'YTDLP_FAILED' | 'FETCH_FAILED' | 'MALFORMED_RESPONSE' | 'INVALID_INPUT' | 'STORE_UNAVAILABLE' | 'STORE_CORRUPT';
10
+ export type YouTubeErrorCode = 'PRIVATE' | 'AGE_GATED' | 'MEMBERS_ONLY' | 'PREMIUM_ONLY' | 'LOGIN_REQUIRED' | 'BOT_CHECK' | 'NOT_FOUND' | 'NO_CAPTIONS' | 'LIVE_NOT_ENDED' | 'RATE_LIMITED' | 'TIMEOUT' | 'CANCELLED' | 'YTDLP_MISSING' | 'FFMPEG_MISSING' | 'WHISPER_MISSING' | 'YTDLP_FAILED' | 'FETCH_FAILED' | 'MALFORMED_RESPONSE' | 'INVALID_INPUT' | 'STORE_UNAVAILABLE' | 'STORE_CORRUPT';
11
11
  export interface YouTubeErrorOptions {
12
12
  /** What the caller should try next. Shown to the model verbatim. */
13
13
  nextStep?: string;
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,SAAS,GACT,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,cAAc,GACd,cAAc,GACd,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,eAAe,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAQtF,iFAAiF;IACjF,aAAa,IAAI,MAAM;CAGxB;AAwGD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,YAAY,GAAG,SAAS,CA2C3B;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAM1D"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AAEzE;;;;;;;GAOG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,WAAW,GACX,cAAc,GACd,cAAc,GACd,gBAAgB,GAChB,WAAW,GACX,WAAW,GACX,aAAa,GACb,gBAAgB,GAChB,cAAc,GACd,SAAS,GACT,WAAW,GACX,eAAe,GACf,gBAAgB,GAChB,iBAAiB,GACjB,cAAc,GACd,cAAc,GACd,oBAAoB,GACpB,eAAe,GACf,mBAAmB,GACnB,eAAe,CAAC;AAEpB,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAChC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,IAAI,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,mBAAwB;IAQtF,iFAAiF;IACjF,aAAa,IAAI,MAAM;CAGxB;AAkHD;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,GAAG,YAAY,CAkBlF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,YAAY,GAAG,SAAS,CA2C3B;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,CAQ3D;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,CAM1D"}
@@ -51,6 +51,16 @@ const RULES = [
51
51
  nextStep: 'Wait a minute before retrying, and request fewer items per call.',
52
52
  retryable: true,
53
53
  },
54
+ {
55
+ // Raised as an expected error, so a harvest that walks every tab of a channel meets
56
+ // it for each tab the channel lacks. It is an answer, not a failure, and has to be
57
+ // recognisable as one: the generic fallback below carries none of yt-dlp's words.
58
+ code: 'NOT_FOUND',
59
+ emits: 'This channel does not have a',
60
+ source: 'yt_dlp/extractor/youtube/_tab.py:2393',
61
+ message: 'This channel does not have that tab.',
62
+ nextStep: 'Not every channel has every tab; this is not an error with the channel.',
63
+ },
54
64
  {
55
65
  code: 'NO_CAPTIONS',
56
66
  emits: 'There are no subtitles for the requested languages',
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"AAwCA,MAAM,OAAO,YAAa,SAAQ,KAAK;IAC5B,IAAI,CAAmB;IACvB,QAAQ,CAAU;IAClB,SAAS,CAAU;IAE5B,YAAY,IAAsB,EAAE,OAAe,EAAE,UAA+B,EAAE;QACpF,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;QAC3B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;QACjC,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAC;IAC9C,CAAC;IAED,iFAAiF;IACjF,aAAa;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,OAAO,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC;IAC9E,CAAC;CACF;AA8BD,6EAA6E;AAC7E,MAAM,WAAW,GACf,4JAA4J,CAAC;AAE/J,MAAM,KAAK,GAAW;IACpB;QACE,yEAAyE;QACzE,wEAAwE;QACxE,wEAAwE;QACxE,qEAAqE;QACrE,sEAAsE;QACtE,wEAAwE;QACxE,2EAA2E;QAC3E,sEAAsE;QACtE,qCAAqC;QACrC,IAAI,EAAE,WAAW;QACjB,KAAK,EAAE,wBAAwB;QAC/B,MAAM,EAAE,2EAA2E;QACnF,OAAO,EAAE,yDAAyD;QAClE,sEAAsE;QACtE,oEAAoE;QACpE,wEAAwE;QACxE,uEAAuE;QACvE,gEAAgE;QAChE,sEAAsE;QACtE,0EAA0E;QAC1E,kEAAkE;QAClE,QAAQ,EAAE,8LAA8L,WAAW,0GAA0G;KAC9T;IACD;QACE,IAAI,EAAE,cAAc;QACpB,4EAA4E;QAC5E,wDAAwD;QACxD,KAAK,EAAE,gBAAgB;QACvB,MAAM,EAAE,oCAAoC;QAC5C,OAAO,EAAE,uCAAuC;QAChD,QAAQ,EAAE,kEAAkE;QAC5E,SAAS,EAAE,IAAI;KAChB;IACD;QACE,IAAI,EAAE,aAAa;QACnB,KAAK,EAAE,oDAAoD;QAC3D,MAAM,EAAE,0BAA0B;QAClC,OAAO,EAAE,qEAAqE;QAC9E,QAAQ,EACN,uFAAuF;KAC1F;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,6BAA6B;QACpC,MAAM,EAAE,0BAA0B;QAClC,OAAO,EAAE,6DAA6D;QACtE,QAAQ,EAAE,gEAAgE;KAC3E;IACD;QACE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,8EAA8E;QACrF,MAAM,EAAE,oCAAoC;QAC5C,OAAO,EAAE,6DAA6D;QACtE,QAAQ,EAAE,gEAAgE;KAC3E;IACD;QACE,oEAAoE;QACpE,yEAAyE;QACzE,yEAAyE;QACzE,iEAAiE;QACjE,IAAI,EAAE,gBAAgB;QACtB,KAAK,EAAE,mDAAmD;QAC1D,MAAM,EAAE,uCAAuC;QAC/C,OAAO,EAAE,kDAAkD;QAC3D,QAAQ,EAAE,sDAAsD,WAAW,EAAE;KAC9E;CACF,CAAC;AAEF;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,KAAe;IAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IAEtC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,EAAE,CAAC;YAChD,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE;gBAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,KAAK;aACN,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,IAAI,YAAY,CAAC,cAAc,EAAE,yCAAyC,EAAE;QACjF,QAAQ,EACN,kLAAkL;QACpL,KAAK;KACN,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAGnC;IACC,QAAQ,MAAM,CAAC,YAAY,EAAE,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,IAAI,YAAY,CAAC,SAAS,EAAE,wBAAwB,EAAE;gBAC3D,QAAQ,EAAE,0EAA0E;aACrF,CAAC,CAAC;QACL,KAAK,iBAAiB;YACpB,OAAO,IAAI,YAAY,CAAC,cAAc,EAAE,8CAA8C,EAAE;gBACtF,QAAQ,EAAE,iEAAiE;aAC5E,CAAC,CAAC;QACL,KAAK,cAAc;YACjB,OAAO,IAAI,YAAY,CAAC,cAAc,EAAE,gDAAgD,EAAE;gBACxF,QAAQ,EAAE,4DAA4D;aACvE,CAAC,CAAC;QACL,KAAK,YAAY;YACf,OAAO,IAAI,YAAY,CACrB,WAAW,EACX,gEAAgE,EAChE;gBACE,QAAQ,EAAE,2DAA2D,WAAW,EAAE;aACnF,CACF,CAAC;IACN,CAAC;IAED,4EAA4E;IAC5E,gDAAgD;IAChD,QAAQ,MAAM,CAAC,UAAU,EAAE,CAAC;QAC1B,KAAK,aAAa;YAChB,OAAO,IAAI,YAAY,CAAC,gBAAgB,EAAE,uCAAuC,EAAE;gBACjF,QAAQ,EAAE,qEAAqE;aAChF,CAAC,CAAC;QACL,KAAK,WAAW;YACd,OAAO,IAAI,YAAY,CACrB,gBAAgB,EAChB,qDAAqD,EACrD;gBACE,QAAQ,EAAE,+DAA+D;gBACzE,SAAS,EAAE,IAAI;aAChB,CACF,CAAC;IACN,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,YAAY,YAAY;QAAE,OAAO,KAAK,CAAC;IAEhD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,IAAI,YAAY,CAAC,cAAc,EAAE,sCAAsC,EAAE;QAC9E,QAAQ,EAAE,0EAA0E;QACpF,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC;KAC3D,CAAC,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;IAC3C,OAAO;QACL,OAAO,EAAE,IAAI;QACb,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,YAAY,CAAC,IAAI,KAAK,YAAY,CAAC,aAAa,EAAE,EAAE,EAAE,CAAC;KAC5F,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/utils/errors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Coverage } from '../harvest-schemas.js';
3
+ /**
4
+ * Taking harvested data out of the store, as a file.
5
+ *
6
+ * What is harvested is only as whole as its receipt says, and a file is read without
7
+ * the tool that made it. So an export carries its receipts with it: inside the file
8
+ * for JSON and Markdown, and beside it for CSV, which has nowhere to put them.
9
+ */
10
+ export declare const DEFAULT_EXPORT_ROWS = 10000;
11
+ export declare const MAX_EXPORT_ROWS = 100000;
12
+ export type ExportDataset = 'videos' | 'comments';
13
+ export type ExportFormat = 'json' | 'csv' | 'markdown';
14
+ export interface ExportRequest {
15
+ dataset: ExportDataset;
16
+ format: ExportFormat;
17
+ /** Restrict to one channel id. */
18
+ channel?: string;
19
+ /** Restrict to one video id; comments only. */
20
+ video?: string;
21
+ limit: number;
22
+ outputDir?: string;
23
+ }
24
+ export interface ExportResult {
25
+ filePath: string;
26
+ /** Present for CSV: where the receipts went, since a CSV cannot hold them. */
27
+ coveragePath?: string;
28
+ dataset: ExportDataset;
29
+ format: ExportFormat;
30
+ rows: number;
31
+ total: number;
32
+ truncated: boolean;
33
+ coverage: Coverage[];
34
+ /** Receipts that should exist for these rows and do not, as `scope:id`. */
35
+ withoutReceipt: string[];
36
+ /** Receipts left out of the list, past the limit a result carries. */
37
+ omittedReceipts: number;
38
+ bytes: number;
39
+ }
40
+ export declare function exportData(database: DatabaseSync, request: ExportRequest): Promise<ExportResult>;
41
+ //# sourceMappingURL=export-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-data.d.ts","sourceRoot":"","sources":["../../src/utils/export-data.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAUtD;;;;;;GAMG;AAEH,eAAO,MAAM,mBAAmB,QAAS,CAAC;AAC1C,eAAO,MAAM,eAAe,SAAU,CAAC;AAOvC,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,CAAC;AAClD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;AAEvD,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,kCAAkC;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,OAAO,EAAE,aAAa,CAAC;IACvB,MAAM,EAAE,YAAY,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,2EAA2E;IAC3E,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,sEAAsE;IACtE,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;CACf;AAsCD,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,YAAY,EACtB,OAAO,EAAE,aAAa,GACrB,OAAO,CAAC,YAAY,CAAC,CA4DvB"}
@@ -0,0 +1,114 @@
1
+ import { mkdir, stat } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { renderCoverage } from './coverage-text.js';
4
+ import { YouTubeError } from './errors.js';
5
+ import { toCsv, toMarkdownTable } from './export-format.js';
6
+ import { loadComments, loadVideos } from './export-load.js';
7
+ import { COMMENT_COLUMNS, VIDEO_COLUMNS } from './export-rows.js';
8
+ import { writeFileAtomic, writeJsonAtomic } from './json-file.js';
9
+ import { dataDir } from './paths.js';
10
+ import { assertChannelId, assertVideoId, resolveOutputDir } from './validate.js';
11
+ /**
12
+ * Taking harvested data out of the store, as a file.
13
+ *
14
+ * What is harvested is only as whole as its receipt says, and a file is read without
15
+ * the tool that made it. So an export carries its receipts with it: inside the file
16
+ * for JSON and Markdown, and beside it for CSV, which has nowhere to put them.
17
+ */
18
+ export const DEFAULT_EXPORT_ROWS = 10_000;
19
+ export const MAX_EXPORT_ROWS = 100_000;
20
+ /** Resolved per call, not at import, so the home directory is the one in force when it runs. */
21
+ function exportsDir() {
22
+ return dataDir('exports');
23
+ }
24
+ function csv(loaded) {
25
+ return loaded.dataset === 'videos'
26
+ ? toCsv(VIDEO_COLUMNS, loaded.rows)
27
+ : toCsv(COMMENT_COLUMNS, loaded.rows);
28
+ }
29
+ function markdownTable(loaded) {
30
+ return loaded.dataset === 'videos'
31
+ ? toMarkdownTable(VIDEO_COLUMNS, loaded.rows)
32
+ : toMarkdownTable(COMMENT_COLUMNS, loaded.rows);
33
+ }
34
+ function markdown(loaded, truncated) {
35
+ const { receipts } = loaded;
36
+ return [
37
+ `# ${loaded.dataset === 'videos' ? 'Videos' : 'Comments'}`,
38
+ '',
39
+ `${String(loaded.rows.length)} of ${String(loaded.total)} rows${truncated ? ' (truncated by the limit)' : ''}.`,
40
+ '',
41
+ '## Completeness',
42
+ '',
43
+ ...(receipts.coverage.length === 0
44
+ ? ['No receipt covers these rows, so how much of the source they are is unknown.']
45
+ : receipts.coverage.flatMap((receipt) => [renderCoverage(receipt), ''])),
46
+ ...(receipts.withoutReceipt.length === 0
47
+ ? []
48
+ : [`No receipt for: ${receipts.withoutReceipt.join(', ')}`, '']),
49
+ ...(receipts.omittedReceipts === 0
50
+ ? []
51
+ : [`${String(receipts.omittedReceipts)} further receipts are not listed.`, '']),
52
+ markdownTable(loaded),
53
+ '',
54
+ ].join('\n');
55
+ }
56
+ export async function exportData(database, request) {
57
+ if (request.channel !== undefined)
58
+ assertChannelId(request.channel);
59
+ if (request.video !== undefined) {
60
+ if (request.dataset !== 'comments') {
61
+ throw new YouTubeError('INVALID_INPUT', 'video only applies when exporting comments.', {
62
+ nextStep: 'Pass channel to restrict a video export, or export dataset comments.',
63
+ });
64
+ }
65
+ assertVideoId(request.video);
66
+ }
67
+ const targetDir = resolveOutputDir(request.outputDir, exportsDir());
68
+ const loaded = request.dataset === 'videos' ? loadVideos(database, request) : loadComments(database, request);
69
+ const truncated = loaded.rows.length < loaded.total;
70
+ const base = `${request.dataset}-${request.video ?? request.channel ?? 'all'}`;
71
+ const extension = request.format === 'markdown' ? 'md' : request.format;
72
+ const filePath = join(targetDir, `${base}.${extension}`);
73
+ const meta = {
74
+ exportedAt: new Date().toISOString(),
75
+ dataset: request.dataset,
76
+ filters: {
77
+ ...(request.channel === undefined ? {} : { channel: request.channel }),
78
+ ...(request.video === undefined ? {} : { video: request.video }),
79
+ },
80
+ total: loaded.total,
81
+ exported: loaded.rows.length,
82
+ truncated,
83
+ coverage: loaded.receipts.coverage,
84
+ withoutReceipt: loaded.receipts.withoutReceipt,
85
+ omittedReceipts: loaded.receipts.omittedReceipts,
86
+ };
87
+ await mkdir(targetDir, { recursive: true });
88
+ let coveragePath;
89
+ if (request.format === 'json') {
90
+ await writeJsonAtomic(filePath, { ...meta, rows: loaded.rows });
91
+ }
92
+ else if (request.format === 'markdown') {
93
+ await writeFileAtomic(filePath, markdown(loaded, truncated));
94
+ }
95
+ else {
96
+ await writeFileAtomic(filePath, csv(loaded));
97
+ coveragePath = join(targetDir, `${base}.coverage.json`);
98
+ await writeJsonAtomic(coveragePath, meta);
99
+ }
100
+ return {
101
+ filePath,
102
+ ...(coveragePath === undefined ? {} : { coveragePath }),
103
+ dataset: request.dataset,
104
+ format: request.format,
105
+ rows: loaded.rows.length,
106
+ total: loaded.total,
107
+ truncated,
108
+ coverage: loaded.receipts.coverage,
109
+ withoutReceipt: loaded.receipts.withoutReceipt,
110
+ omittedReceipts: loaded.receipts.omittedReceipts,
111
+ bytes: (await stat(filePath)).size,
112
+ };
113
+ }
114
+ //# sourceMappingURL=export-data.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-data.js","sourceRoot":"","sources":["../../src/utils/export-data.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Writing rows out as CSV and Markdown tables.
3
+ *
4
+ * Exported comments are text written by strangers, and a spreadsheet will run a cell
5
+ * that begins like a formula. Every text field is therefore defused on the way out,
6
+ * as OWASP's CSV-injection guidance prescribes, rather than trusted because it came
7
+ * from a platform.
8
+ */
9
+ export type Cell = string | number | boolean | null | undefined;
10
+ export interface Column<T> {
11
+ header: string;
12
+ value: (row: T) => Cell;
13
+ /**
14
+ * Written as it is, never defused. For identifiers, which are validated elsewhere and
15
+ * must come back exact: a YouTube id may begin with `-`, and a leading apostrophe
16
+ * would make it a different id.
17
+ */
18
+ verbatim?: boolean;
19
+ }
20
+ /** RFC 4180: a header row, CRLF line endings, every record ended with one. */
21
+ export declare function toCsv<T>(columns: Column<T>[], rows: T[]): string;
22
+ export declare function toMarkdownTable<T>(columns: Column<T>[], rows: T[]): string;
23
+ //# sourceMappingURL=export-format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-format.d.ts","sourceRoot":"","sources":["../../src/utils/export-format.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,GAAG,SAAS,CAAC;AAEhE,MAAM,WAAW,MAAM,CAAC,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAmBD,8EAA8E;AAC9E,wBAAgB,KAAK,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAMhE;AAwBD,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,MAAM,CAQ1E"}
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Writing rows out as CSV and Markdown tables.
3
+ *
4
+ * Exported comments are text written by strangers, and a spreadsheet will run a cell
5
+ * that begins like a formula. Every text field is therefore defused on the way out,
6
+ * as OWASP's CSV-injection guidance prescribes, rather than trusted because it came
7
+ * from a platform.
8
+ */
9
+ /** Characters that make a spreadsheet read a cell as a formula (or as a command). */
10
+ const FORMULA_TRIGGERS = new Set(['=', '+', '-', '@', '\t', '\r']);
11
+ function defused(text) {
12
+ const first = text[0];
13
+ return first !== undefined && FORMULA_TRIGGERS.has(first) ? `'${text}` : text;
14
+ }
15
+ function csvField(cell, verbatim = false) {
16
+ if (cell === null || cell === undefined)
17
+ return '';
18
+ // Numbers and booleans are not text: a negative number is data, not a formula.
19
+ if (typeof cell !== 'string')
20
+ return String(cell);
21
+ const text = verbatim ? cell : defused(cell);
22
+ return /[",\r\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
23
+ }
24
+ /** RFC 4180: a header row, CRLF line endings, every record ended with one. */
25
+ export function toCsv(columns, rows) {
26
+ const header = columns.map((column) => csvField(column.header)).join(',');
27
+ const body = rows.map((row) => columns.map((column) => csvField(column.value(row), column.verbatim)).join(','));
28
+ return [header, ...body].map((line) => `${line}\r\n`).join('');
29
+ }
30
+ /** Characters that carry meaning in Markdown or HTML, and so must not be left to the reader's renderer. */
31
+ const MARKDOWN_ESCAPES = {
32
+ '\\': '\\\\',
33
+ '|': '\\|',
34
+ '`': '\\`',
35
+ '[': '\\[',
36
+ ']': '\\]',
37
+ '<': '&lt;',
38
+ '>': '&gt;',
39
+ };
40
+ /**
41
+ * One table cell. Comment text is written by strangers and may hold HTML or links; it is
42
+ * shown as text, never as markup, and cannot end its cell or its row.
43
+ */
44
+ function markdownCell(cell) {
45
+ if (cell === null || cell === undefined)
46
+ return '';
47
+ return String(cell)
48
+ .replace(/[\\|`[\]<>]/g, (character) => MARKDOWN_ESCAPES[character] ?? character)
49
+ .replace(/\r\n|\r|\n/g, ' ');
50
+ }
51
+ export function toMarkdownTable(columns, rows) {
52
+ const line = (cells) => `| ${cells.join(' | ')} |`;
53
+ return [
54
+ line(columns.map((column) => markdownCell(column.header))),
55
+ line(columns.map(() => '---')),
56
+ ...rows.map((row) => line(columns.map((column) => markdownCell(column.value(row))))),
57
+ ].join('\n');
58
+ }
59
+ //# sourceMappingURL=export-format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-format.js","sourceRoot":"","sources":["../../src/utils/export-format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import type { DatabaseSync } from 'node:sqlite';
2
+ import type { Coverage } from '../harvest-schemas.js';
3
+ import { type CommentRow, type VideoRow } from './export-rows.js';
4
+ /** What is loaded: the part of an export request that decides which rows. */
5
+ export interface LoadRequest {
6
+ channel?: string;
7
+ video?: string;
8
+ limit: number;
9
+ }
10
+ export interface Receipts {
11
+ coverage: Coverage[];
12
+ withoutReceipt: string[];
13
+ omittedReceipts: number;
14
+ }
15
+ export type Loaded = {
16
+ dataset: 'videos';
17
+ rows: VideoRow[];
18
+ total: number;
19
+ receipts: Receipts;
20
+ } | {
21
+ dataset: 'comments';
22
+ rows: CommentRow[];
23
+ total: number;
24
+ receipts: Receipts;
25
+ };
26
+ export declare function loadVideos(database: DatabaseSync, request: LoadRequest): Loaded;
27
+ export declare function loadComments(database: DatabaseSync, request: LoadRequest): Loaded;
28
+ //# sourceMappingURL=export-load.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-load.d.ts","sourceRoot":"","sources":["../../src/utils/export-load.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAA4B,KAAK,UAAU,EAAE,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAU5F,6EAA6E;AAC7E,MAAM,WAAW,WAAW;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,QAAQ,EAAE,CAAC;IACrB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,MAAM,MAAM,GACd;IAAE,OAAO,EAAE,QAAQ,CAAC;IAAC,IAAI,EAAE,QAAQ,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,GAC1E;IAAE,OAAO,EAAE,UAAU,CAAC;IAAC,IAAI,EAAE,UAAU,EAAE,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC;AAsCnF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAgB/E;AAED,wBAAgB,YAAY,CAAC,QAAQ,EAAE,YAAY,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,CAiBjF"}
@@ -0,0 +1,70 @@
1
+ import { countComments, queryComments } from './comment-store.js';
2
+ import { COVERAGE_IN_RESULT_LIMIT } from './coverage.js';
3
+ import { toCommentRow, toVideoRow } from './export-rows.js';
4
+ import { readReceipt } from './harvest-receipts.js';
5
+ import { countVideos, queryVideos } from './video-store.js';
6
+ /**
7
+ * Reading the rows an export holds out of the store, and the receipts that say how whole they are.
8
+ */
9
+ const PAGE = 1_000;
10
+ /** Pages through a query until `limit` rows, so memory holds rows and not a second copy of them. */
11
+ function collect(limit, page) {
12
+ const rows = [];
13
+ while (rows.length < limit) {
14
+ const got = page(rows.length, Math.min(PAGE, limit - rows.length));
15
+ rows.push(...got);
16
+ if (got.length === 0)
17
+ break;
18
+ }
19
+ return rows;
20
+ }
21
+ function unique(values) {
22
+ return [...new Set(values.filter((value) => value !== undefined))];
23
+ }
24
+ /** The receipts for the targets in these rows, up to what a result carries. */
25
+ function receiptsFor(database, targets) {
26
+ const coverage = [];
27
+ const withoutReceipt = [];
28
+ let omittedReceipts = 0;
29
+ for (const { scope, ids, required } of targets) {
30
+ for (const id of ids.slice(0, COVERAGE_IN_RESULT_LIMIT)) {
31
+ const receipt = readReceipt(database, scope, id);
32
+ if (receipt !== undefined)
33
+ coverage.push(receipt);
34
+ else if (required)
35
+ withoutReceipt.push(`${scope}:${id}`);
36
+ }
37
+ omittedReceipts += Math.max(0, ids.length - COVERAGE_IN_RESULT_LIMIT);
38
+ }
39
+ return { coverage, withoutReceipt, omittedReceipts };
40
+ }
41
+ export function loadVideos(database, request) {
42
+ const filter = request.channel === undefined ? {} : { channelId: request.channel };
43
+ const rows = collect(request.limit, (offset, size) => queryVideos(database, { ...filter, order: 'catalog', limit: size, offset }).map(toVideoRow));
44
+ const channels = unique([request.channel, ...rows.map((row) => row.channelId)]);
45
+ return {
46
+ dataset: 'videos',
47
+ rows,
48
+ total: countVideos(database, filter),
49
+ receipts: receiptsFor(database, [
50
+ { scope: 'channel-catalog', ids: channels, required: true },
51
+ { scope: 'video-details', ids: channels, required: false },
52
+ ]),
53
+ };
54
+ }
55
+ export function loadComments(database, request) {
56
+ const filter = {
57
+ ...(request.video === undefined ? {} : { videoId: request.video }),
58
+ ...(request.channel === undefined ? {} : { channelId: request.channel }),
59
+ };
60
+ const rows = collect(request.limit, (offset, size) => queryComments(database, { ...filter, order: 'oldest', limit: size, offset }).map(toCommentRow));
61
+ return {
62
+ dataset: 'comments',
63
+ rows,
64
+ total: countComments(database, filter),
65
+ receipts: receiptsFor(database, [
66
+ { scope: 'video-comments', ids: unique(rows.map((row) => row.videoId)), required: true },
67
+ ]),
68
+ };
69
+ }
70
+ //# sourceMappingURL=export-load.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-load.js","sourceRoot":"","sources":["../../src/utils/export-load.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,wBAAwB,EAAE,MAAM,eAAe,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAkC,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE5D;;GAEG;AAEH,MAAM,IAAI,GAAG,KAAK,CAAC;AAmBnB,oGAAoG;AACpG,SAAS,OAAO,CAAI,KAAa,EAAE,IAA2C;IAC5E,MAAM,IAAI,GAAQ,EAAE,CAAC;IACrB,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,EAAE,CAAC;QAC3B,MAAM,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,CAAC;QAClB,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,MAAM,CAAC,MAA8B;IAC5C,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,KAAK,EAAmB,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,CAAC;AACtF,CAAC;AAED,+EAA+E;AAC/E,SAAS,WAAW,CAClB,QAAsB,EACtB,OAAyE;IAEzE,MAAM,QAAQ,GAAe,EAAE,CAAC;IAChC,MAAM,cAAc,GAAa,EAAE,CAAC;IACpC,IAAI,eAAe,GAAG,CAAC,CAAC;IAExB,KAAK,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,OAAO,EAAE,CAAC;QAC/C,KAAK,MAAM,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,wBAAwB,CAAC,EAAE,CAAC;YACxD,MAAM,OAAO,GAAG,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;YACjD,IAAI,OAAO,KAAK,SAAS;gBAAE,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;iBAC7C,IAAI,QAAQ;gBAAE,cAAc,CAAC,IAAI,CAAC,GAAG,KAAK,IAAI,EAAE,EAAE,CAAC,CAAC;QAC3D,CAAC;QACD,eAAe,IAAI,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,MAAM,GAAG,wBAAwB,CAAC,CAAC;IACxE,CAAC;IAED,OAAO,EAAE,QAAQ,EAAE,cAAc,EAAE,eAAe,EAAE,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,QAAsB,EAAE,OAAoB;IACrE,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;IACnF,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CACnD,WAAW,CAAC,QAAQ,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,CAC5F,CAAC;IACF,MAAM,QAAQ,GAAG,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAEhF,OAAO;QACL,OAAO,EAAE,QAAQ;QACjB,IAAI;QACJ,KAAK,EAAE,WAAW,CAAC,QAAQ,EAAE,MAAM,CAAC;QACpC,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE;YAC9B,EAAE,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE;YAC3D,EAAE,KAAK,EAAE,eAAe,EAAE,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE;SAC3D,CAAC;KACH,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAAsB,EAAE,OAAoB;IACvE,MAAM,MAAM,GAAG;QACb,GAAG,CAAC,OAAO,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;QAClE,GAAG,CAAC,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,OAAO,EAAE,CAAC;KACzE,CAAC;IACF,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,CACnD,aAAa,CAAC,QAAQ,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,CAC/F,CAAC;IAEF,OAAO;QACL,OAAO,EAAE,UAAU;QACnB,IAAI;QACJ,KAAK,EAAE,aAAa,CAAC,QAAQ,EAAE,MAAM,CAAC;QACtC,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE;YAC9B,EAAE,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;SACzF,CAAC;KACH,CAAC;AACJ,CAAC"}
@@ -0,0 +1,35 @@
1
+ import type { StoredComment } from './comment-store.js';
2
+ import type { Column } from './export-format.js';
3
+ import type { StoredVideo } from './video-store.js';
4
+ /** The rows an export holds, as they are written, and the columns a CSV or table gives them. */
5
+ export interface VideoRow {
6
+ videoId: string;
7
+ channelId?: string;
8
+ title: string;
9
+ durationSeconds?: number;
10
+ uploadDate?: string;
11
+ viewCount?: number;
12
+ commentCount?: number;
13
+ tab?: string;
14
+ alsoInTabs: string[];
15
+ detailed: boolean;
16
+ storedComments: number;
17
+ }
18
+ export interface CommentRow {
19
+ commentId: string;
20
+ videoId: string;
21
+ parentId?: string;
22
+ author: string;
23
+ authorId?: string;
24
+ likeCount: number;
25
+ isPinned: boolean;
26
+ isUploader: boolean;
27
+ isVerified: boolean;
28
+ publishedAt?: string;
29
+ text: string;
30
+ }
31
+ export declare const VIDEO_COLUMNS: Column<VideoRow>[];
32
+ export declare const COMMENT_COLUMNS: Column<CommentRow>[];
33
+ export declare function toVideoRow(row: StoredVideo): VideoRow;
34
+ export declare function toCommentRow(row: StoredComment): CommentRow;
35
+ //# sourceMappingURL=export-rows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"export-rows.d.ts","sourceRoot":"","sources":["../../src/utils/export-rows.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,gGAAgG;AAEhG,MAAM,WAAW,QAAQ;IACvB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,QAAQ,EAAE,OAAO,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;IACpB,UAAU,EAAE,OAAO,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC,EAY3C,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,MAAM,CAAC,UAAU,CAAC,EAY/C,CAAC;AAWF,wBAAgB,UAAU,CAAC,GAAG,EAAE,WAAW,GAAG,QAAQ,CAcrD;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG,UAAU,CAgB3D"}