offcourse 0.0.2 → 1.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 (139) hide show
  1. package/README.md +255 -20
  2. package/dist/cli/commands/config.d.ts +13 -0
  3. package/dist/cli/commands/config.d.ts.map +1 -0
  4. package/dist/cli/commands/config.js +66 -0
  5. package/dist/cli/commands/config.js.map +1 -0
  6. package/dist/cli/commands/inspect.d.ts +11 -0
  7. package/dist/cli/commands/inspect.d.ts.map +1 -0
  8. package/dist/cli/commands/inspect.js +365 -0
  9. package/dist/cli/commands/inspect.js.map +1 -0
  10. package/dist/cli/commands/login.d.ts +12 -0
  11. package/dist/cli/commands/login.d.ts.map +1 -0
  12. package/dist/cli/commands/login.js +55 -0
  13. package/dist/cli/commands/login.js.map +1 -0
  14. package/dist/cli/commands/status.d.ts +15 -0
  15. package/dist/cli/commands/status.d.ts.map +1 -0
  16. package/dist/cli/commands/status.js +118 -0
  17. package/dist/cli/commands/status.js.map +1 -0
  18. package/dist/cli/commands/sync.d.ts +15 -0
  19. package/dist/cli/commands/sync.d.ts.map +1 -0
  20. package/dist/cli/commands/sync.js +921 -0
  21. package/dist/cli/commands/sync.js.map +1 -0
  22. package/dist/cli/commands/syncHighLevel.d.ts +23 -0
  23. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  24. package/dist/cli/commands/syncHighLevel.js +479 -0
  25. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  26. package/dist/cli/index.d.ts +3 -0
  27. package/dist/cli/index.d.ts.map +1 -0
  28. package/dist/cli/index.js +106 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/config/configManager.d.ts +31 -0
  31. package/dist/config/configManager.d.ts.map +1 -0
  32. package/dist/config/configManager.js +68 -0
  33. package/dist/config/configManager.js.map +1 -0
  34. package/dist/config/paths.d.ts +21 -0
  35. package/dist/config/paths.d.ts.map +1 -0
  36. package/dist/config/paths.js +33 -0
  37. package/dist/config/paths.js.map +1 -0
  38. package/dist/config/schema.d.ts +60 -0
  39. package/dist/config/schema.d.ts.map +1 -0
  40. package/dist/config/schema.js +50 -0
  41. package/dist/config/schema.js.map +1 -0
  42. package/dist/downloader/hlsDownloader.d.ts +58 -0
  43. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  44. package/dist/downloader/hlsDownloader.js +263 -0
  45. package/dist/downloader/hlsDownloader.js.map +1 -0
  46. package/dist/downloader/hlsValidator.d.ts +35 -0
  47. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  48. package/dist/downloader/hlsValidator.js +152 -0
  49. package/dist/downloader/hlsValidator.js.map +1 -0
  50. package/dist/downloader/index.d.ts +29 -0
  51. package/dist/downloader/index.d.ts.map +1 -0
  52. package/dist/downloader/index.js +55 -0
  53. package/dist/downloader/index.js.map +1 -0
  54. package/dist/downloader/loomDownloader.d.ts +56 -0
  55. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  56. package/dist/downloader/loomDownloader.js +562 -0
  57. package/dist/downloader/loomDownloader.js.map +1 -0
  58. package/dist/downloader/queue.d.ts +56 -0
  59. package/dist/downloader/queue.d.ts.map +1 -0
  60. package/dist/downloader/queue.js +88 -0
  61. package/dist/downloader/queue.js.map +1 -0
  62. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  63. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  64. package/dist/downloader/vimeoDownloader.js +569 -0
  65. package/dist/downloader/vimeoDownloader.js.map +1 -0
  66. package/dist/scraper/extractor.d.ts +53 -0
  67. package/dist/scraper/extractor.d.ts.map +1 -0
  68. package/dist/scraper/extractor.js +627 -0
  69. package/dist/scraper/extractor.js.map +1 -0
  70. package/dist/scraper/highlevel/extractor.d.ts +89 -0
  71. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  72. package/dist/scraper/highlevel/extractor.js +373 -0
  73. package/dist/scraper/highlevel/extractor.js.map +1 -0
  74. package/dist/scraper/highlevel/index.d.ts +3 -0
  75. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  76. package/dist/scraper/highlevel/index.js +3 -0
  77. package/dist/scraper/highlevel/index.js.map +1 -0
  78. package/dist/scraper/highlevel/navigator.d.ts +86 -0
  79. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  80. package/dist/scraper/highlevel/navigator.js +505 -0
  81. package/dist/scraper/highlevel/navigator.js.map +1 -0
  82. package/dist/scraper/highlevel/schemas.d.ts +188 -0
  83. package/dist/scraper/highlevel/schemas.d.ts.map +1 -0
  84. package/dist/scraper/highlevel/schemas.js +139 -0
  85. package/dist/scraper/highlevel/schemas.js.map +1 -0
  86. package/dist/scraper/navigator.d.ts +68 -0
  87. package/dist/scraper/navigator.d.ts.map +1 -0
  88. package/dist/scraper/navigator.js +257 -0
  89. package/dist/scraper/navigator.js.map +1 -0
  90. package/dist/scraper/schemas.d.ts +57 -0
  91. package/dist/scraper/schemas.d.ts.map +1 -0
  92. package/dist/scraper/schemas.js +135 -0
  93. package/dist/scraper/schemas.js.map +1 -0
  94. package/dist/scraper/videoInterceptor.d.ts +23 -0
  95. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  96. package/dist/scraper/videoInterceptor.js +330 -0
  97. package/dist/scraper/videoInterceptor.js.map +1 -0
  98. package/dist/shared/auth.d.ts +58 -0
  99. package/dist/shared/auth.d.ts.map +1 -0
  100. package/dist/shared/auth.js +197 -0
  101. package/dist/shared/auth.js.map +1 -0
  102. package/dist/shared/firebase.d.ts +60 -0
  103. package/dist/shared/firebase.d.ts.map +1 -0
  104. package/dist/shared/firebase.js +102 -0
  105. package/dist/shared/firebase.js.map +1 -0
  106. package/dist/shared/fs.d.ts +31 -0
  107. package/dist/shared/fs.d.ts.map +1 -0
  108. package/dist/shared/fs.js +77 -0
  109. package/dist/shared/fs.js.map +1 -0
  110. package/dist/shared/http.d.ts +15 -0
  111. package/dist/shared/http.d.ts.map +1 -0
  112. package/dist/shared/http.js +31 -0
  113. package/dist/shared/http.js.map +1 -0
  114. package/dist/shared/index.d.ts +7 -0
  115. package/dist/shared/index.d.ts.map +1 -0
  116. package/dist/shared/index.js +7 -0
  117. package/dist/shared/index.js.map +1 -0
  118. package/dist/shared/slug.d.ts +11 -0
  119. package/dist/shared/slug.d.ts.map +1 -0
  120. package/dist/shared/slug.js +25 -0
  121. package/dist/shared/slug.js.map +1 -0
  122. package/dist/shared/url.d.ts +43 -0
  123. package/dist/shared/url.d.ts.map +1 -0
  124. package/dist/shared/url.js +54 -0
  125. package/dist/shared/url.js.map +1 -0
  126. package/dist/state/database.d.ts +246 -0
  127. package/dist/state/database.d.ts.map +1 -0
  128. package/dist/state/database.js +679 -0
  129. package/dist/state/database.js.map +1 -0
  130. package/dist/state/index.d.ts +2 -0
  131. package/dist/state/index.d.ts.map +1 -0
  132. package/dist/state/index.js +2 -0
  133. package/dist/state/index.js.map +1 -0
  134. package/dist/storage/fileSystem.d.ts +56 -0
  135. package/dist/storage/fileSystem.d.ts.map +1 -0
  136. package/dist/storage/fileSystem.js +129 -0
  137. package/dist/storage/fileSystem.js.map +1 -0
  138. package/package.json +71 -11
  139. package/cli.js +0 -45
@@ -0,0 +1,569 @@
1
+ import { createWriteStream, existsSync, mkdirSync, renameSync, unlinkSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import { USER_AGENT } from "../shared/http.js";
4
+ import { getBaseUrl } from "../shared/url.js";
5
+ /**
6
+ * Extracts the Vimeo video ID from various URL formats.
7
+ */
8
+ export function extractVimeoId(url) {
9
+ // Handle various Vimeo URL formats:
10
+ // https://vimeo.com/123456789
11
+ // https://vimeo.com/123456789?share=copy
12
+ // https://player.vimeo.com/video/123456789
13
+ // https://vimeo.com/channels/xxx/123456789
14
+ const patterns = [
15
+ /vimeo\.com\/(?:video\/)?(\d+)/,
16
+ /player\.vimeo\.com\/video\/(\d+)/,
17
+ /vimeo\.com\/channels\/[^/]+\/(\d+)/,
18
+ /vimeo\.com\/groups\/[^/]+\/videos\/(\d+)/,
19
+ ];
20
+ for (const pattern of patterns) {
21
+ const match = url.match(pattern);
22
+ if (match?.[1]) {
23
+ return match[1];
24
+ }
25
+ }
26
+ return null;
27
+ }
28
+ // Network I/O and file operations - excluded from coverage
29
+ /* v8 ignore start */
30
+ /**
31
+ * Extracts the unlisted hash from a Vimeo URL if present.
32
+ * Unlisted videos require this hash to access.
33
+ */
34
+ function extractUnlistedHash(url) {
35
+ // Format: https://vimeo.com/123456789/abcdef1234
36
+ // Or in player: https://player.vimeo.com/video/123456789?h=abcdef1234
37
+ const pathMatch = /vimeo\.com\/\d+\/([a-f0-9]+)/.exec(url);
38
+ if (pathMatch?.[1]) {
39
+ return pathMatch[1];
40
+ }
41
+ const paramMatch = /[?&]h=([a-f0-9]+)/.exec(url);
42
+ if (paramMatch?.[1]) {
43
+ return paramMatch[1];
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Fetches video information from Vimeo's player config.
49
+ * @param referer - Optional referer URL (e.g., the Skool page URL) for domain-restricted videos
50
+ */
51
+ export async function getVimeoVideoInfo(videoId, unlistedHash, referer) {
52
+ // Try the config endpoint first
53
+ let configUrl = `https://player.vimeo.com/video/${videoId}/config`;
54
+ if (unlistedHash) {
55
+ configUrl += `?h=${unlistedHash}`;
56
+ }
57
+ // Build headers - use provided referer for domain-restricted videos
58
+ const headers = {
59
+ "User-Agent": USER_AGENT,
60
+ Accept: "application/json",
61
+ };
62
+ // Try with Skool referer first if provided, otherwise use Vimeo's player
63
+ if (referer) {
64
+ headers.Referer = referer;
65
+ headers.Origin = new URL(referer).origin;
66
+ }
67
+ else {
68
+ headers.Referer = "https://player.vimeo.com/";
69
+ }
70
+ try {
71
+ let response = await fetch(configUrl, { headers });
72
+ // If we got 403 with a custom referer, the video might be strictly domain-locked
73
+ // Try with the embed page URL as referer
74
+ if (response.status === 403 && referer) {
75
+ headers.Referer = `https://player.vimeo.com/video/${videoId}`;
76
+ headers.Origin = "https://player.vimeo.com";
77
+ response = await fetch(configUrl, { headers });
78
+ }
79
+ if (response.status === 404) {
80
+ return {
81
+ success: false,
82
+ error: "Video not found",
83
+ errorCode: "VIDEO_NOT_FOUND",
84
+ details: `Video ID: ${videoId}`,
85
+ };
86
+ }
87
+ if (response.status === 403) {
88
+ return {
89
+ success: false,
90
+ error: "Video is private or requires authentication",
91
+ errorCode: "PRIVATE_VIDEO",
92
+ details: `Video ID: ${videoId}. This video may require login or is restricted.`,
93
+ };
94
+ }
95
+ if (response.status === 429) {
96
+ return {
97
+ success: false,
98
+ error: "Rate limited by Vimeo",
99
+ errorCode: "RATE_LIMITED",
100
+ };
101
+ }
102
+ if (!response.ok) {
103
+ return {
104
+ success: false,
105
+ error: `Vimeo returned HTTP ${response.status}`,
106
+ errorCode: "NETWORK_ERROR",
107
+ details: `Config URL: ${configUrl}`,
108
+ };
109
+ }
110
+ const config = (await response.json());
111
+ // Check for DRM
112
+ if (config.request?.files?.dash?.cdns &&
113
+ !config.request?.files?.hls &&
114
+ !config.request?.files?.progressive) {
115
+ // Only DASH with no HLS/progressive usually means DRM
116
+ const hasDrm = config.video?.drm ?? config.request?.drm;
117
+ if (hasDrm) {
118
+ return {
119
+ success: false,
120
+ error: "Video is DRM protected and cannot be downloaded",
121
+ errorCode: "DRM_PROTECTED",
122
+ details: `Video "${config.video?.title ?? videoId}" uses DRM protection. This video cannot be downloaded without the content provider's authorization.`,
123
+ };
124
+ }
125
+ }
126
+ // Extract HLS URL
127
+ let hlsUrl = null;
128
+ const hlsCdns = config.request?.files?.hls?.cdns;
129
+ if (hlsCdns) {
130
+ // Prefer akamai_live, then fastly, then any available CDN
131
+ const preferredCdns = ["akfire_interconnect_quic", "akamai_live", "fastly_skyfire", "fastly"];
132
+ for (const cdn of preferredCdns) {
133
+ if (hlsCdns[cdn]?.url) {
134
+ hlsUrl = hlsCdns[cdn].url;
135
+ break;
136
+ }
137
+ }
138
+ // Fallback to any CDN
139
+ if (!hlsUrl) {
140
+ const cdnKeys = Object.keys(hlsCdns);
141
+ const firstCdn = cdnKeys[0];
142
+ if (firstCdn) {
143
+ const cdnUrl = hlsCdns[firstCdn]?.url;
144
+ if (cdnUrl) {
145
+ hlsUrl = cdnUrl;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ // Extract progressive (direct MP4) URL - prefer highest quality
151
+ let progressiveUrl = null;
152
+ const progressive = config.request?.files?.progressive;
153
+ if (progressive && Array.isArray(progressive) && progressive.length > 0) {
154
+ // Sort by height descending to get best quality
155
+ const sorted = [...progressive].sort((a, b) => (b.height ?? 0) - (a.height ?? 0));
156
+ progressiveUrl = sorted[0]?.url ?? null;
157
+ }
158
+ if (!hlsUrl && !progressiveUrl) {
159
+ // Check if this is a DRM-only video
160
+ if (config.request?.files?.dash) {
161
+ return {
162
+ success: false,
163
+ error: "Video only has DRM-protected DASH streams",
164
+ errorCode: "DRM_PROTECTED",
165
+ details: `Video "${config.video?.title ?? videoId}" appears to use DRM protection. No downloadable streams available.`,
166
+ };
167
+ }
168
+ return {
169
+ success: false,
170
+ error: "No downloadable video streams found",
171
+ errorCode: "PARSE_ERROR",
172
+ details: "Could not find HLS or progressive download URLs in config",
173
+ };
174
+ }
175
+ return {
176
+ success: true,
177
+ info: {
178
+ id: videoId,
179
+ title: config.video?.title ?? "Vimeo Video",
180
+ duration: config.video?.duration ?? 0,
181
+ width: config.video?.width ?? 1920,
182
+ height: config.video?.height ?? 1080,
183
+ hlsUrl,
184
+ progressiveUrl,
185
+ },
186
+ };
187
+ }
188
+ catch (error) {
189
+ const errorMessage = error instanceof Error ? error.message : String(error);
190
+ return {
191
+ success: false,
192
+ error: `Network error: ${errorMessage}`,
193
+ errorCode: "NETWORK_ERROR",
194
+ };
195
+ }
196
+ }
197
+ /**
198
+ * Fetches Vimeo config from within a Playwright browser context.
199
+ * Uses page.request API which runs at browser level (no CORS restrictions)
200
+ * and includes the browser's cookies/session.
201
+ *
202
+ * @param page - Playwright page (should be on the Skool lesson page)
203
+ * @param videoId - Vimeo video ID
204
+ * @param unlistedHash - Optional hash for unlisted videos
205
+ */
206
+ export async function getVimeoVideoInfoFromBrowser(page, videoId, unlistedHash) {
207
+ let configUrl = `https://player.vimeo.com/video/${videoId}/config`;
208
+ if (unlistedHash) {
209
+ configUrl += `?h=${unlistedHash}`;
210
+ }
211
+ try {
212
+ // Use page.request (Playwright API) - runs at browser level, no CORS issues
213
+ // and includes the browser's cookies/session
214
+ const currentUrl = page.url();
215
+ const response = await page.request.get(configUrl, {
216
+ headers: {
217
+ Accept: "application/json",
218
+ Referer: currentUrl,
219
+ Origin: new URL(currentUrl).origin,
220
+ },
221
+ });
222
+ if (response.status() === 403) {
223
+ return {
224
+ success: false,
225
+ error: "Video is private or requires authentication",
226
+ errorCode: "PRIVATE_VIDEO",
227
+ details: `Video ID: ${videoId}. Domain-restricted even with browser session.`,
228
+ };
229
+ }
230
+ if (response.status() === 404) {
231
+ return {
232
+ success: false,
233
+ error: "Video not found",
234
+ errorCode: "VIDEO_NOT_FOUND",
235
+ details: `Video ID: ${videoId}`,
236
+ };
237
+ }
238
+ if (!response.ok()) {
239
+ return {
240
+ success: false,
241
+ error: `Vimeo returned HTTP ${response.status()}`,
242
+ errorCode: "NETWORK_ERROR",
243
+ details: `Config URL: ${configUrl}`,
244
+ };
245
+ }
246
+ const config = (await response.json());
247
+ // Extract HLS URL (same logic as getVimeoVideoInfo)
248
+ let hlsUrl = null;
249
+ const hlsCdns = config.request?.files?.hls?.cdns;
250
+ if (hlsCdns) {
251
+ const preferredCdns = ["akfire_interconnect_quic", "akamai_live", "fastly_skyfire", "fastly"];
252
+ for (const cdn of preferredCdns) {
253
+ if (hlsCdns[cdn]?.url) {
254
+ hlsUrl = hlsCdns[cdn].url;
255
+ break;
256
+ }
257
+ }
258
+ if (!hlsUrl) {
259
+ const cdnKeys = Object.keys(hlsCdns);
260
+ const firstCdn = cdnKeys[0];
261
+ if (firstCdn) {
262
+ const cdnUrl = hlsCdns[firstCdn]?.url;
263
+ if (cdnUrl) {
264
+ hlsUrl = cdnUrl;
265
+ }
266
+ }
267
+ }
268
+ }
269
+ // Extract progressive URL
270
+ let progressiveUrl = null;
271
+ const progressive = config.request?.files?.progressive;
272
+ if (progressive && Array.isArray(progressive) && progressive.length > 0) {
273
+ const sorted = [...progressive].sort((a, b) => (b.height ?? 0) - (a.height ?? 0));
274
+ progressiveUrl = sorted[0]?.url ?? null;
275
+ }
276
+ if (!hlsUrl && !progressiveUrl) {
277
+ if (config.request?.files?.dash) {
278
+ return {
279
+ success: false,
280
+ error: "Video only has DRM-protected DASH streams",
281
+ errorCode: "DRM_PROTECTED",
282
+ details: `Video "${config.video?.title ?? videoId}" uses DRM. Cannot download.`,
283
+ };
284
+ }
285
+ return {
286
+ success: false,
287
+ error: "No downloadable streams found",
288
+ errorCode: "PARSE_ERROR",
289
+ };
290
+ }
291
+ return {
292
+ success: true,
293
+ info: {
294
+ id: videoId,
295
+ title: config.video?.title ?? "Vimeo Video",
296
+ duration: config.video?.duration ?? 0,
297
+ width: config.video?.width ?? 1920,
298
+ height: config.video?.height ?? 1080,
299
+ hlsUrl,
300
+ progressiveUrl,
301
+ },
302
+ };
303
+ }
304
+ catch (error) {
305
+ return {
306
+ success: false,
307
+ error: error instanceof Error ? error.message : String(error),
308
+ errorCode: "NETWORK_ERROR",
309
+ };
310
+ }
311
+ }
312
+ /**
313
+ * Downloads a Vimeo video.
314
+ * Prefers progressive (direct MP4) download, falls back to HLS.
315
+ */
316
+ export async function downloadVimeoVideo(url, outputPath, onProgress) {
317
+ if (existsSync(outputPath)) {
318
+ return { success: true };
319
+ }
320
+ // Check if this is already a direct HLS/CDN URL (from previous validation)
321
+ if (url.includes("vimeocdn.com") && url.includes(".m3u8")) {
322
+ // Direct HLS download
323
+ const dir = dirname(outputPath);
324
+ if (!existsSync(dir)) {
325
+ mkdirSync(dir, { recursive: true });
326
+ }
327
+ return downloadHlsVideo(url, outputPath, onProgress);
328
+ }
329
+ const videoId = extractVimeoId(url);
330
+ if (!videoId) {
331
+ return {
332
+ success: false,
333
+ error: "Invalid Vimeo URL",
334
+ errorCode: "INVALID_URL",
335
+ details: `Could not extract video ID from: ${url}`,
336
+ };
337
+ }
338
+ const unlistedHash = extractUnlistedHash(url);
339
+ const fetchResult = await getVimeoVideoInfo(videoId, unlistedHash);
340
+ if (!fetchResult.success || !fetchResult.info) {
341
+ const result = {
342
+ success: false,
343
+ error: fetchResult.error ?? "Could not fetch video info",
344
+ };
345
+ if (fetchResult.errorCode) {
346
+ result.errorCode = fetchResult.errorCode;
347
+ }
348
+ if (fetchResult.details) {
349
+ result.details = fetchResult.details;
350
+ }
351
+ return result;
352
+ }
353
+ const info = fetchResult.info;
354
+ // Ensure output directory exists
355
+ const dir = dirname(outputPath);
356
+ if (!existsSync(dir)) {
357
+ mkdirSync(dir, { recursive: true });
358
+ }
359
+ // Prefer progressive (direct MP4) download - simpler and often better quality
360
+ if (info.progressiveUrl) {
361
+ const result = await downloadProgressiveVideo(info.progressiveUrl, outputPath, onProgress);
362
+ if (result.success) {
363
+ return result;
364
+ }
365
+ // Fall through to HLS if progressive fails
366
+ }
367
+ // Try HLS download
368
+ if (info.hlsUrl) {
369
+ return downloadHlsVideo(info.hlsUrl, outputPath, onProgress);
370
+ }
371
+ return {
372
+ success: false,
373
+ error: "No downloadable streams available",
374
+ errorCode: "NO_STREAM",
375
+ };
376
+ }
377
+ /**
378
+ * Downloads a progressive (direct) video file.
379
+ */
380
+ async function downloadProgressiveVideo(url, outputPath, onProgress) {
381
+ const tempPath = `${outputPath}.tmp`;
382
+ try {
383
+ const response = await fetch(url, {
384
+ headers: {
385
+ "User-Agent": USER_AGENT,
386
+ Referer: "https://player.vimeo.com/",
387
+ },
388
+ });
389
+ if (!response.ok) {
390
+ return {
391
+ success: false,
392
+ error: `Download failed: HTTP ${response.status}`,
393
+ errorCode: "DOWNLOAD_FAILED",
394
+ };
395
+ }
396
+ const contentLength = response.headers.get("content-length");
397
+ const total = contentLength ? parseInt(contentLength, 10) : 0;
398
+ if (!response.body) {
399
+ return {
400
+ success: false,
401
+ error: "No response body",
402
+ errorCode: "DOWNLOAD_FAILED",
403
+ };
404
+ }
405
+ const fileStream = createWriteStream(tempPath);
406
+ const reader = response.body.getReader();
407
+ let downloaded = 0;
408
+ while (true) {
409
+ const { done, value } = await reader.read();
410
+ if (done)
411
+ break;
412
+ fileStream.write(Buffer.from(value));
413
+ downloaded += value.length;
414
+ if (onProgress && total > 0) {
415
+ onProgress({
416
+ percent: (downloaded / total) * 100,
417
+ downloaded,
418
+ total,
419
+ });
420
+ }
421
+ }
422
+ await new Promise((resolve, reject) => {
423
+ fileStream.end((err) => {
424
+ if (err) {
425
+ reject(err);
426
+ }
427
+ else {
428
+ resolve();
429
+ }
430
+ });
431
+ });
432
+ renameSync(tempPath, outputPath);
433
+ return { success: true };
434
+ }
435
+ catch (error) {
436
+ if (existsSync(tempPath)) {
437
+ unlinkSync(tempPath);
438
+ }
439
+ return {
440
+ success: false,
441
+ error: error instanceof Error ? error.message : String(error),
442
+ errorCode: "DOWNLOAD_FAILED",
443
+ };
444
+ }
445
+ }
446
+ /**
447
+ * Downloads an HLS video stream.
448
+ */
449
+ async function downloadHlsVideo(masterUrl, outputPath, onProgress) {
450
+ try {
451
+ // Fetch master playlist
452
+ const masterResponse = await fetch(masterUrl, {
453
+ headers: {
454
+ "User-Agent": USER_AGENT,
455
+ Referer: "https://player.vimeo.com/",
456
+ },
457
+ });
458
+ if (!masterResponse.ok) {
459
+ return {
460
+ success: false,
461
+ error: `Failed to fetch HLS playlist: HTTP ${masterResponse.status}`,
462
+ errorCode: "DOWNLOAD_FAILED",
463
+ };
464
+ }
465
+ const masterPlaylist = await masterResponse.text();
466
+ const lines = masterPlaylist.split("\n");
467
+ const baseUrl = getBaseUrl(masterUrl);
468
+ // Find best quality video stream
469
+ let bestBandwidth = 0;
470
+ let videoPlaylistUrl = null;
471
+ for (let i = 0; i < lines.length; i++) {
472
+ const line = lines[i]?.trim();
473
+ if (!line)
474
+ continue;
475
+ if (line.startsWith("#EXT-X-STREAM-INF:")) {
476
+ const bandwidthMatch = /BANDWIDTH=(\d+)/.exec(line);
477
+ const bandwidth = bandwidthMatch?.[1] ? parseInt(bandwidthMatch[1], 10) : 0;
478
+ const nextLine = lines[i + 1]?.trim();
479
+ if (nextLine && !nextLine.startsWith("#") && bandwidth > bestBandwidth) {
480
+ bestBandwidth = bandwidth;
481
+ videoPlaylistUrl = nextLine.startsWith("http") ? nextLine : baseUrl + nextLine;
482
+ }
483
+ }
484
+ }
485
+ if (!videoPlaylistUrl) {
486
+ return {
487
+ success: false,
488
+ error: "Could not find video stream in HLS playlist",
489
+ errorCode: "DOWNLOAD_FAILED",
490
+ };
491
+ }
492
+ // Fetch video playlist and get segments
493
+ const videoResponse = await fetch(videoPlaylistUrl, {
494
+ headers: { "User-Agent": USER_AGENT },
495
+ });
496
+ if (!videoResponse.ok) {
497
+ return {
498
+ success: false,
499
+ error: `Failed to fetch video playlist: HTTP ${videoResponse.status}`,
500
+ errorCode: "DOWNLOAD_FAILED",
501
+ };
502
+ }
503
+ const videoPlaylist = await videoResponse.text();
504
+ const videoBaseUrl = getBaseUrl(videoPlaylistUrl);
505
+ const segments = [];
506
+ for (const line of videoPlaylist.split("\n")) {
507
+ const trimmed = line.trim();
508
+ if (trimmed && !trimmed.startsWith("#")) {
509
+ const segmentUrl = trimmed.startsWith("http") ? trimmed : videoBaseUrl + trimmed;
510
+ segments.push(segmentUrl);
511
+ }
512
+ }
513
+ if (segments.length === 0) {
514
+ return {
515
+ success: false,
516
+ error: "No segments found in video playlist",
517
+ errorCode: "DOWNLOAD_FAILED",
518
+ };
519
+ }
520
+ // Download all segments
521
+ const tempPath = `${outputPath}.tmp`;
522
+ const fileStream = createWriteStream(tempPath);
523
+ for (let i = 0; i < segments.length; i++) {
524
+ const segmentUrl = segments[i];
525
+ if (!segmentUrl)
526
+ continue;
527
+ const segResponse = await fetch(segmentUrl, {
528
+ headers: { "User-Agent": USER_AGENT },
529
+ });
530
+ if (!segResponse.ok || !segResponse.body)
531
+ continue;
532
+ const reader = segResponse.body.getReader();
533
+ while (true) {
534
+ const { done, value } = await reader.read();
535
+ if (done)
536
+ break;
537
+ fileStream.write(Buffer.from(value));
538
+ }
539
+ if (onProgress) {
540
+ onProgress({
541
+ percent: ((i + 1) / segments.length) * 100,
542
+ downloaded: i + 1,
543
+ total: segments.length,
544
+ });
545
+ }
546
+ }
547
+ await new Promise((resolve, reject) => {
548
+ fileStream.end((err) => {
549
+ if (err) {
550
+ reject(err);
551
+ }
552
+ else {
553
+ resolve();
554
+ }
555
+ });
556
+ });
557
+ renameSync(tempPath, outputPath);
558
+ return { success: true };
559
+ }
560
+ catch (error) {
561
+ return {
562
+ success: false,
563
+ error: error instanceof Error ? error.message : String(error),
564
+ errorCode: "DOWNLOAD_FAILED",
565
+ };
566
+ }
567
+ }
568
+ /* v8 ignore stop */
569
+ //# sourceMappingURL=vimeoDownloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vimeoDownloader.js","sourceRoot":"","sources":["../../src/downloader/vimeoDownloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ import type { Page } from "playwright";
2
+ export interface DownloadableFile {
3
+ url: string;
4
+ filename: string;
5
+ type: "pdf" | "doc" | "docx" | "xls" | "xlsx" | "ppt" | "pptx" | "zip" | "other";
6
+ }
7
+ export interface LessonContent {
8
+ title: string;
9
+ videoUrl: string | null;
10
+ videoType: "loom" | "vimeo" | "youtube" | "wistia" | "native" | "unknown" | null;
11
+ htmlContent: string;
12
+ markdownContent: string;
13
+ downloadableFiles: DownloadableFile[];
14
+ }
15
+ /**
16
+ * Extracts the video URL from the current lesson page.
17
+ * Supports Loom, Vimeo, YouTube, Wistia, and native video.
18
+ *
19
+ * For Vimeo: Prefers iframe src (has auth params) over __NEXT_DATA__ URL.
20
+ * For others: Uses __NEXT_DATA__ first, then falls back to DOM inspection.
21
+ */
22
+ export declare function extractVideoUrl(page: Page): Promise<{
23
+ url: string | null;
24
+ type: LessonContent["videoType"];
25
+ }>;
26
+ /**
27
+ * Extracts the text content from the lesson page.
28
+ */
29
+ export declare function extractTextContent(page: Page): Promise<{
30
+ html: string;
31
+ markdown: string;
32
+ }>;
33
+ /**
34
+ * Gets the file type from extension.
35
+ */
36
+ export declare function getFileType(ext: string): DownloadableFile["type"];
37
+ /**
38
+ * Extracts downloadable file links from the page content.
39
+ */
40
+ export declare function extractDownloadableFiles(page: Page): Promise<DownloadableFile[]>;
41
+ /**
42
+ * Extracts all content from a lesson page.
43
+ */
44
+ export declare function extractLessonContent(page: Page, lessonUrl: string): Promise<LessonContent>;
45
+ /**
46
+ * Extracts the Loom video ID from an embed URL.
47
+ */
48
+ export declare function extractLoomVideoId(embedUrl: string): string | null;
49
+ /**
50
+ * Cleans and formats the markdown content.
51
+ */
52
+ export declare function formatMarkdown(title: string, content: string, videoUrl: string | null, videoType: string | null): string;
53
+ //# sourceMappingURL=extractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../src/scraper/extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAOvC,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,CAAC;CAClF;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,GAAG,OAAO,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,IAAI,CAAC;IACjF,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,gBAAgB,EAAE,CAAC;CACvC;AAqPD;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,GACT,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,aAAa,CAAC,WAAW,CAAC,CAAA;CAAE,CAAC,CAgCnE;AAiOD;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA0DhG;AAuBD;;GAEG;AACH,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAWjE;AAGD;;GAEG;AACH,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAqDtF;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAyBhG;AAGD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GAAG,IAAI,EACvB,SAAS,EAAE,MAAM,GAAG,IAAI,GACvB,MAAM,CAmBR"}