offcourse 0.0.1 → 1.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.
Files changed (284) hide show
  1. package/.github/workflows/ci.yml +50 -0
  2. package/.husky/commit-msg +2 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.husky/pre-push +3 -0
  5. package/.prettierrc +8 -0
  6. package/.release-it.json +23 -0
  7. package/ARCHITECTURE.md +233 -0
  8. package/CHANGELOG.md +78 -0
  9. package/README.md +256 -16
  10. package/commitlint.config.js +4 -0
  11. package/dist/ai/openRouter.d.ts +47 -0
  12. package/dist/ai/openRouter.d.ts.map +1 -0
  13. package/dist/ai/openRouter.js +116 -0
  14. package/dist/ai/openRouter.js.map +1 -0
  15. package/dist/ai/transcriptPolisher.d.ts +24 -0
  16. package/dist/ai/transcriptPolisher.d.ts.map +1 -0
  17. package/dist/ai/transcriptPolisher.js +89 -0
  18. package/dist/ai/transcriptPolisher.js.map +1 -0
  19. package/dist/cli/commands/config.d.ts +13 -0
  20. package/dist/cli/commands/config.d.ts.map +1 -0
  21. package/dist/cli/commands/config.js +66 -0
  22. package/dist/cli/commands/config.js.map +1 -0
  23. package/dist/cli/commands/enrich.d.ts +14 -0
  24. package/dist/cli/commands/enrich.d.ts.map +1 -0
  25. package/dist/cli/commands/enrich.js +271 -0
  26. package/dist/cli/commands/enrich.js.map +1 -0
  27. package/dist/cli/commands/inspect.d.ts +11 -0
  28. package/dist/cli/commands/inspect.d.ts.map +1 -0
  29. package/dist/cli/commands/inspect.js +365 -0
  30. package/dist/cli/commands/inspect.js.map +1 -0
  31. package/dist/cli/commands/login.d.ts +12 -0
  32. package/dist/cli/commands/login.d.ts.map +1 -0
  33. package/dist/cli/commands/login.js +55 -0
  34. package/dist/cli/commands/login.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +15 -0
  36. package/dist/cli/commands/status.d.ts.map +1 -0
  37. package/dist/cli/commands/status.js +118 -0
  38. package/dist/cli/commands/status.js.map +1 -0
  39. package/dist/cli/commands/sync.d.ts +16 -0
  40. package/dist/cli/commands/sync.d.ts.map +1 -0
  41. package/dist/cli/commands/sync.js +922 -0
  42. package/dist/cli/commands/sync.js.map +1 -0
  43. package/dist/cli/commands/syncGhl.d.ts +20 -0
  44. package/dist/cli/commands/syncGhl.d.ts.map +1 -0
  45. package/dist/cli/commands/syncGhl.js +483 -0
  46. package/dist/cli/commands/syncGhl.js.map +1 -0
  47. package/dist/cli/commands/syncHighLevel.d.ts +24 -0
  48. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  49. package/dist/cli/commands/syncHighLevel.js +483 -0
  50. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  51. package/dist/cli/commands/syncHighLevel.test.d.ts +2 -0
  52. package/dist/cli/commands/syncHighLevel.test.d.ts.map +1 -0
  53. package/dist/cli/commands/syncHighLevel.test.js +102 -0
  54. package/dist/cli/commands/syncHighLevel.test.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +106 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/config/configManager.d.ts +31 -0
  60. package/dist/config/configManager.d.ts.map +1 -0
  61. package/dist/config/configManager.js +64 -0
  62. package/dist/config/configManager.js.map +1 -0
  63. package/dist/config/paths.d.ts +21 -0
  64. package/dist/config/paths.d.ts.map +1 -0
  65. package/dist/config/paths.js +33 -0
  66. package/dist/config/paths.js.map +1 -0
  67. package/dist/config/paths.test.d.ts +2 -0
  68. package/dist/config/paths.test.d.ts.map +1 -0
  69. package/dist/config/paths.test.js +70 -0
  70. package/dist/config/paths.test.js.map +1 -0
  71. package/dist/config/schema.d.ts +60 -0
  72. package/dist/config/schema.d.ts.map +1 -0
  73. package/dist/config/schema.js +50 -0
  74. package/dist/config/schema.js.map +1 -0
  75. package/dist/config/schema.test.d.ts +2 -0
  76. package/dist/config/schema.test.d.ts.map +1 -0
  77. package/dist/config/schema.test.js +151 -0
  78. package/dist/config/schema.test.js.map +1 -0
  79. package/dist/downloader/hlsDownloader.d.ts +58 -0
  80. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  81. package/dist/downloader/hlsDownloader.js +254 -0
  82. package/dist/downloader/hlsDownloader.js.map +1 -0
  83. package/dist/downloader/hlsDownloader.test.d.ts +2 -0
  84. package/dist/downloader/hlsDownloader.test.d.ts.map +1 -0
  85. package/dist/downloader/hlsDownloader.test.js +116 -0
  86. package/dist/downloader/hlsDownloader.test.js.map +1 -0
  87. package/dist/downloader/hlsValidator.d.ts +35 -0
  88. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  89. package/dist/downloader/hlsValidator.js +148 -0
  90. package/dist/downloader/hlsValidator.js.map +1 -0
  91. package/dist/downloader/index.d.ts +26 -0
  92. package/dist/downloader/index.d.ts.map +1 -0
  93. package/dist/downloader/index.js +52 -0
  94. package/dist/downloader/index.js.map +1 -0
  95. package/dist/downloader/loomDownloader.d.ts +56 -0
  96. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  97. package/dist/downloader/loomDownloader.js +559 -0
  98. package/dist/downloader/loomDownloader.js.map +1 -0
  99. package/dist/downloader/loomDownloader.test.d.ts +2 -0
  100. package/dist/downloader/loomDownloader.test.d.ts.map +1 -0
  101. package/dist/downloader/loomDownloader.test.js +36 -0
  102. package/dist/downloader/loomDownloader.test.js.map +1 -0
  103. package/dist/downloader/queue.d.ts +56 -0
  104. package/dist/downloader/queue.d.ts.map +1 -0
  105. package/dist/downloader/queue.js +88 -0
  106. package/dist/downloader/queue.js.map +1 -0
  107. package/dist/downloader/queue.test.d.ts +2 -0
  108. package/dist/downloader/queue.test.d.ts.map +1 -0
  109. package/dist/downloader/queue.test.js +158 -0
  110. package/dist/downloader/queue.test.js.map +1 -0
  111. package/dist/downloader/videoDownloader.d.ts +32 -0
  112. package/dist/downloader/videoDownloader.d.ts.map +1 -0
  113. package/dist/downloader/videoDownloader.js +173 -0
  114. package/dist/downloader/videoDownloader.js.map +1 -0
  115. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  116. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  117. package/dist/downloader/vimeoDownloader.js +565 -0
  118. package/dist/downloader/vimeoDownloader.js.map +1 -0
  119. package/dist/downloader/vimeoDownloader.test.d.ts +2 -0
  120. package/dist/downloader/vimeoDownloader.test.d.ts.map +1 -0
  121. package/dist/downloader/vimeoDownloader.test.js +51 -0
  122. package/dist/downloader/vimeoDownloader.test.js.map +1 -0
  123. package/dist/scraper/auth.d.ts +29 -0
  124. package/dist/scraper/auth.d.ts.map +1 -0
  125. package/dist/scraper/auth.js +115 -0
  126. package/dist/scraper/auth.js.map +1 -0
  127. package/dist/scraper/extractor.d.ts +49 -0
  128. package/dist/scraper/extractor.d.ts.map +1 -0
  129. package/dist/scraper/extractor.js +627 -0
  130. package/dist/scraper/extractor.js.map +1 -0
  131. package/dist/scraper/extractor.test.d.ts +2 -0
  132. package/dist/scraper/extractor.test.d.ts.map +1 -0
  133. package/dist/scraper/extractor.test.js +65 -0
  134. package/dist/scraper/extractor.test.js.map +1 -0
  135. package/dist/scraper/ghl/auth.d.ts +25 -0
  136. package/dist/scraper/ghl/auth.d.ts.map +1 -0
  137. package/dist/scraper/ghl/auth.js +187 -0
  138. package/dist/scraper/ghl/auth.js.map +1 -0
  139. package/dist/scraper/ghl/extractor.d.ts +96 -0
  140. package/dist/scraper/ghl/extractor.d.ts.map +1 -0
  141. package/dist/scraper/ghl/extractor.js +345 -0
  142. package/dist/scraper/ghl/extractor.js.map +1 -0
  143. package/dist/scraper/ghl/index.d.ts +4 -0
  144. package/dist/scraper/ghl/index.d.ts.map +1 -0
  145. package/dist/scraper/ghl/index.js +4 -0
  146. package/dist/scraper/ghl/index.js.map +1 -0
  147. package/dist/scraper/ghl/navigator.d.ts +93 -0
  148. package/dist/scraper/ghl/navigator.d.ts.map +1 -0
  149. package/dist/scraper/ghl/navigator.js +447 -0
  150. package/dist/scraper/ghl/navigator.js.map +1 -0
  151. package/dist/scraper/highlevel/auth.d.ts +25 -0
  152. package/dist/scraper/highlevel/auth.d.ts.map +1 -0
  153. package/dist/scraper/highlevel/auth.js +189 -0
  154. package/dist/scraper/highlevel/auth.js.map +1 -0
  155. package/dist/scraper/highlevel/extractor.d.ts +97 -0
  156. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  157. package/dist/scraper/highlevel/extractor.js +386 -0
  158. package/dist/scraper/highlevel/extractor.js.map +1 -0
  159. package/dist/scraper/highlevel/extractor.test.d.ts +2 -0
  160. package/dist/scraper/highlevel/extractor.test.d.ts.map +1 -0
  161. package/dist/scraper/highlevel/extractor.test.js +101 -0
  162. package/dist/scraper/highlevel/extractor.test.js.map +1 -0
  163. package/dist/scraper/highlevel/index.d.ts +3 -0
  164. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  165. package/dist/scraper/highlevel/index.js +3 -0
  166. package/dist/scraper/highlevel/index.js.map +1 -0
  167. package/dist/scraper/highlevel/navigator.d.ts +93 -0
  168. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  169. package/dist/scraper/highlevel/navigator.js +492 -0
  170. package/dist/scraper/highlevel/navigator.js.map +1 -0
  171. package/dist/scraper/highlevel/navigator.test.d.ts +2 -0
  172. package/dist/scraper/highlevel/navigator.test.d.ts.map +1 -0
  173. package/dist/scraper/highlevel/navigator.test.js +78 -0
  174. package/dist/scraper/highlevel/navigator.test.js.map +1 -0
  175. package/dist/scraper/navigator.d.ts +65 -0
  176. package/dist/scraper/navigator.d.ts.map +1 -0
  177. package/dist/scraper/navigator.js +300 -0
  178. package/dist/scraper/navigator.js.map +1 -0
  179. package/dist/scraper/navigator.test.d.ts +2 -0
  180. package/dist/scraper/navigator.test.d.ts.map +1 -0
  181. package/dist/scraper/navigator.test.js +63 -0
  182. package/dist/scraper/navigator.test.js.map +1 -0
  183. package/dist/scraper/skoolApi.d.ts +17 -0
  184. package/dist/scraper/skoolApi.d.ts.map +1 -0
  185. package/dist/scraper/skoolApi.js +72 -0
  186. package/dist/scraper/skoolApi.js.map +1 -0
  187. package/dist/scraper/videoInterceptor.d.ts +19 -0
  188. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  189. package/dist/scraper/videoInterceptor.js +315 -0
  190. package/dist/scraper/videoInterceptor.js.map +1 -0
  191. package/dist/shared/auth.d.ts +58 -0
  192. package/dist/shared/auth.d.ts.map +1 -0
  193. package/dist/shared/auth.js +211 -0
  194. package/dist/shared/auth.js.map +1 -0
  195. package/dist/shared/fs.d.ts +31 -0
  196. package/dist/shared/fs.d.ts.map +1 -0
  197. package/dist/shared/fs.js +73 -0
  198. package/dist/shared/fs.js.map +1 -0
  199. package/dist/shared/http.d.ts +15 -0
  200. package/dist/shared/http.d.ts.map +1 -0
  201. package/dist/shared/http.js +31 -0
  202. package/dist/shared/http.js.map +1 -0
  203. package/dist/shared/index.d.ts +4 -0
  204. package/dist/shared/index.d.ts.map +1 -0
  205. package/dist/shared/index.js +4 -0
  206. package/dist/shared/index.js.map +1 -0
  207. package/dist/state/database.d.ts +245 -0
  208. package/dist/state/database.d.ts.map +1 -0
  209. package/dist/state/database.js +676 -0
  210. package/dist/state/database.js.map +1 -0
  211. package/dist/state/database.test.d.ts +2 -0
  212. package/dist/state/database.test.d.ts.map +1 -0
  213. package/dist/state/database.test.js +34 -0
  214. package/dist/state/database.test.js.map +1 -0
  215. package/dist/state/index.d.ts +2 -0
  216. package/dist/state/index.d.ts.map +1 -0
  217. package/dist/state/index.js +2 -0
  218. package/dist/state/index.js.map +1 -0
  219. package/dist/storage/fileSystem.d.ts +56 -0
  220. package/dist/storage/fileSystem.d.ts.map +1 -0
  221. package/dist/storage/fileSystem.js +121 -0
  222. package/dist/storage/fileSystem.js.map +1 -0
  223. package/dist/transcription/whisperService.d.ts +27 -0
  224. package/dist/transcription/whisperService.d.ts.map +1 -0
  225. package/dist/transcription/whisperService.js +102 -0
  226. package/dist/transcription/whisperService.js.map +1 -0
  227. package/eslint.config.js +55 -0
  228. package/package.json +68 -11
  229. package/src/__fixtures__/highlevel-post-response.json +68 -0
  230. package/src/__fixtures__/hls-master-playlist.m3u8 +24 -0
  231. package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +38 -0
  232. package/src/cli/commands/config.ts +74 -0
  233. package/src/cli/commands/inspect.ts +441 -0
  234. package/src/cli/commands/login.ts +68 -0
  235. package/src/cli/commands/status.ts +147 -0
  236. package/src/cli/commands/sync.ts +1235 -0
  237. package/src/cli/commands/syncHighLevel.test.ts +144 -0
  238. package/src/cli/commands/syncHighLevel.ts +639 -0
  239. package/src/cli/index.ts +121 -0
  240. package/src/config/configManager.ts +75 -0
  241. package/src/config/paths.test.ts +83 -0
  242. package/src/config/paths.ts +36 -0
  243. package/src/config/schema.test.ts +173 -0
  244. package/src/config/schema.ts +65 -0
  245. package/src/downloader/hlsDownloader.test.ts +148 -0
  246. package/src/downloader/hlsDownloader.ts +327 -0
  247. package/src/downloader/hlsValidator.ts +196 -0
  248. package/src/downloader/index.ts +122 -0
  249. package/src/downloader/loomDownloader.test.ts +43 -0
  250. package/src/downloader/loomDownloader.ts +742 -0
  251. package/src/downloader/queue.test.ts +199 -0
  252. package/src/downloader/queue.ts +118 -0
  253. package/src/downloader/vimeoDownloader.test.ts +62 -0
  254. package/src/downloader/vimeoDownloader.ts +722 -0
  255. package/src/scraper/extractor.test.ts +124 -0
  256. package/src/scraper/extractor.ts +757 -0
  257. package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +41 -0
  258. package/src/scraper/highlevel/extractor.test.ts +134 -0
  259. package/src/scraper/highlevel/extractor.ts +537 -0
  260. package/src/scraper/highlevel/index.ts +2 -0
  261. package/src/scraper/highlevel/navigator.test.ts +110 -0
  262. package/src/scraper/highlevel/navigator.ts +668 -0
  263. package/src/scraper/highlevel/schemas.ts +183 -0
  264. package/src/scraper/navigator.test.ts +122 -0
  265. package/src/scraper/navigator.ts +355 -0
  266. package/src/scraper/schemas.ts +177 -0
  267. package/src/scraper/videoInterceptor.ts +435 -0
  268. package/src/shared/auth.test.ts +58 -0
  269. package/src/shared/auth.ts +251 -0
  270. package/src/shared/firebase.ts +151 -0
  271. package/src/shared/fs.ts +80 -0
  272. package/src/shared/http.ts +34 -0
  273. package/src/shared/index.ts +6 -0
  274. package/src/shared/slug.ts +26 -0
  275. package/src/shared/url.test.ts +122 -0
  276. package/src/shared/url.ts +57 -0
  277. package/src/state/database.test.ts +49 -0
  278. package/src/state/database.ts +919 -0
  279. package/src/state/index.ts +14 -0
  280. package/src/storage/fileSystem.test.ts +64 -0
  281. package/src/storage/fileSystem.ts +175 -0
  282. package/tsconfig.json +28 -0
  283. package/vitest.config.ts +29 -0
  284. package/cli.js +0 -45
@@ -0,0 +1,65 @@
1
+ import type { Page } from "playwright";
2
+ export interface CourseModule {
3
+ name: string;
4
+ slug: string;
5
+ url: string;
6
+ isLocked: boolean;
7
+ }
8
+ export interface Lesson {
9
+ name: string;
10
+ slug: string;
11
+ url: string;
12
+ index: number;
13
+ isLocked: boolean;
14
+ }
15
+ export interface CourseStructure {
16
+ name: string;
17
+ url: string;
18
+ modules: Array<CourseModule & {
19
+ lessons: Lesson[];
20
+ }>;
21
+ }
22
+ /**
23
+ * Extracts the course/community name from page data.
24
+ */
25
+ export declare function extractCourseName(page: Page): Promise<string>;
26
+ /**
27
+ * Extracts module data from the embedded JSON in the page.
28
+ * Skool embeds course structure as JSON in a script tag.
29
+ */
30
+ export declare function extractModulesFromJson(page: Page): Promise<CourseModule[]>;
31
+ /**
32
+ * Extracts lessons from a module page.
33
+ * Lessons are listed in the sidebar with links.
34
+ */
35
+ export declare function extractLessons(page: Page, moduleUrl: string): Promise<Lesson[]>;
36
+ /**
37
+ * Alternative: Extract modules from the classroom overview page links.
38
+ */
39
+ export declare function extractModulesFromPage(page: Page): Promise<CourseModule[]>;
40
+ /**
41
+ * Progress callback for buildCourseStructure.
42
+ */
43
+ export interface ScanProgress {
44
+ phase: "init" | "modules" | "lessons" | "done";
45
+ courseName?: string;
46
+ totalModules?: number;
47
+ currentModule?: string;
48
+ currentModuleIndex?: number;
49
+ lessonsFound?: number;
50
+ skippedLocked?: boolean;
51
+ }
52
+ /**
53
+ * Builds the complete course structure by crawling all modules and lessons.
54
+ */
55
+ export declare function buildCourseStructure(page: Page, classroomUrl: string, onProgress?: (progress: ScanProgress) => void): Promise<CourseStructure>;
56
+ /**
57
+ * Creates a filesystem-safe name from a string.
58
+ * Uses @sindresorhus/slugify for proper transliteration.
59
+ */
60
+ export declare function slugify(name: string): string;
61
+ /**
62
+ * Creates a folder name with index prefix.
63
+ */
64
+ export declare function createFolderName(index: number, name: string): string;
65
+ //# sourceMappingURL=navigator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.d.ts","sourceRoot":"","sources":["../../src/scraper/navigator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGvC,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,KAAK,CAAC,YAAY,GAAG;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;CACtD;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC,CAKnE;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAkFhF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAsFrF;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CA0ChF;AAqBD;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IAC/C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,IAAI,EACV,YAAY,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,GAC5C,OAAO,CAAC,eAAe,CAAC,CA4E1B;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAK5C;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAIpE"}
@@ -0,0 +1,300 @@
1
+ import slugifyLib from "@sindresorhus/slugify";
2
+ /**
3
+ * Extracts the course/community name from page data.
4
+ */
5
+ export async function extractCourseName(page) {
6
+ const title = await page.title();
7
+ // Title format: "Classroom · Community Name"
8
+ const match = /·\s*(.+)$/.exec(title);
9
+ return (match?.[1]?.trim() ?? title.replace("Classroom", "").trim()) || "Unknown Course";
10
+ }
11
+ /**
12
+ * Extracts module data from the embedded JSON in the page.
13
+ * Skool embeds course structure as JSON in a script tag.
14
+ */
15
+ export async function extractModulesFromJson(page) {
16
+ const modules = await page.evaluate(() => {
17
+ // Try to get module data from __NEXT_DATA__
18
+ const nextDataScript = document.getElementById("__NEXT_DATA__");
19
+ if (nextDataScript?.textContent) {
20
+ try {
21
+ const json = JSON.parse(nextDataScript.textContent);
22
+ const courseChildren = json?.props?.pageProps?.course?.children;
23
+ if (Array.isArray(courseChildren)) {
24
+ const results = [];
25
+ for (const child of courseChildren) {
26
+ const course = child?.course;
27
+ if (!course?.name || !/^[a-f0-9]{8}$/.test(course.name))
28
+ continue;
29
+ const slug = course.name;
30
+ const title = course.metadata?.title ?? `Module ${results.length + 1}`;
31
+ // Check hasAccess field - if false, the module/lesson is locked
32
+ const hasAccess = child?.hasAccess !== false;
33
+ if (!results.some((m) => m.slug === slug)) {
34
+ results.push({
35
+ name: title,
36
+ slug,
37
+ url: "",
38
+ isLocked: !hasAccess,
39
+ });
40
+ }
41
+ }
42
+ if (results.length > 0)
43
+ return results;
44
+ }
45
+ }
46
+ catch {
47
+ // Fall through to regex approach
48
+ }
49
+ }
50
+ // Fallback: Find script tags that contain course data
51
+ const scripts = Array.from(document.querySelectorAll("script"));
52
+ const results = [];
53
+ for (const script of scripts) {
54
+ const content = script.textContent ?? "";
55
+ // Look for module data pattern in the JSON
56
+ // Structure: "id":"...","name":"SLUG","metadata":{..."title":"TITLE"...}
57
+ // Pattern: "name":"8-char-hex" followed by "title":"..." within metadata
58
+ const modulePattern = /"name":"([a-f0-9]{8})","metadata":\{[^}]*"title":"([^"]+)"/g;
59
+ let match;
60
+ while ((match = modulePattern.exec(content)) !== null) {
61
+ const slug = match[1];
62
+ const title = match[2];
63
+ // Skip if already added
64
+ if (slug && title && !results.some((m) => m.slug === slug)) {
65
+ // Decode unicode escapes (e.g., \u0026 -> &)
66
+ const decodedTitle = title.replace(/\\u([0-9a-fA-F]{4})/g, (_, code) => String.fromCharCode(parseInt(code, 16)));
67
+ results.push({
68
+ name: decodedTitle,
69
+ slug,
70
+ url: "", // Will be set later
71
+ isLocked: false,
72
+ });
73
+ }
74
+ }
75
+ }
76
+ return results;
77
+ });
78
+ // Build URLs for each module
79
+ const baseUrl = page.url().split("/classroom")[0];
80
+ return modules.map((module) => ({
81
+ ...module,
82
+ url: `${baseUrl}/classroom/${module.slug}`,
83
+ }));
84
+ }
85
+ /**
86
+ * Extracts lessons from a module page.
87
+ * Lessons are listed in the sidebar with links.
88
+ */
89
+ export async function extractLessons(page, moduleUrl) {
90
+ const currentUrl = page.url();
91
+ const moduleBasePath = moduleUrl.split("?")[0] ?? moduleUrl;
92
+ if (!currentUrl.includes(moduleBasePath)) {
93
+ await page.goto(moduleUrl, { timeout: 30000 });
94
+ await page.waitForLoadState("domcontentloaded");
95
+ await page.waitForTimeout(2000);
96
+ }
97
+ const lessons = await page.evaluate(() => {
98
+ const results = [];
99
+ // First try to get hasAccess from __NEXT_DATA__
100
+ const accessMap = new Map();
101
+ const nextDataScript = document.getElementById("__NEXT_DATA__");
102
+ if (nextDataScript?.textContent) {
103
+ try {
104
+ const json = JSON.parse(nextDataScript.textContent);
105
+ const courseChildren = json?.props?.pageProps?.course?.children;
106
+ if (Array.isArray(courseChildren)) {
107
+ for (const child of courseChildren) {
108
+ const lessonId = child?.course?.id;
109
+ const hasAccess = child?.hasAccess;
110
+ if (lessonId && typeof hasAccess === "boolean") {
111
+ accessMap.set(lessonId, hasAccess);
112
+ }
113
+ }
114
+ }
115
+ }
116
+ catch {
117
+ // Ignore parse errors
118
+ }
119
+ }
120
+ // Skool uses styled-components with "ChildrenLink" in the class name
121
+ const lessonLinks = document.querySelectorAll('a[class*="ChildrenLink"]');
122
+ lessonLinks.forEach((link, index) => {
123
+ const anchor = link;
124
+ const href = anchor.href;
125
+ const name = anchor.textContent?.trim() ?? `Lesson ${index + 1}`;
126
+ // Extract lesson ID from URL (?md=...)
127
+ const urlParams = new URL(href).searchParams;
128
+ const lessonId = urlParams.get("md") ?? "";
129
+ // Check hasAccess from JSON data first
130
+ let isLocked = false;
131
+ if (accessMap.has(lessonId)) {
132
+ isLocked = !accessMap.get(lessonId);
133
+ }
134
+ else {
135
+ // Fallback: Check for lock icon in DOM
136
+ let parent = anchor;
137
+ for (let i = 0; i < 3 && parent; i++) {
138
+ if (parent.querySelector('[class*="lock"], [class*="Lock"], svg[class*="lock"], svg[class*="Lock"]')) {
139
+ isLocked = true;
140
+ break;
141
+ }
142
+ parent = parent.parentElement;
143
+ }
144
+ // Also check if the link itself has a lock indicator
145
+ if (anchor.querySelector('[class*="lock"], [class*="Lock"]')) {
146
+ isLocked = true;
147
+ }
148
+ }
149
+ if (lessonId && !results.some((l) => l.slug === lessonId)) {
150
+ results.push({
151
+ name,
152
+ slug: lessonId,
153
+ url: href,
154
+ index: results.length,
155
+ isLocked,
156
+ });
157
+ }
158
+ });
159
+ return results;
160
+ });
161
+ return lessons;
162
+ }
163
+ /**
164
+ * Alternative: Extract modules from the classroom overview page links.
165
+ */
166
+ export async function extractModulesFromPage(page) {
167
+ await page.waitForTimeout(1000);
168
+ const modules = await page.evaluate(() => {
169
+ // Look for module cards - they're usually divs/links with course images
170
+ const moduleCards = document.querySelectorAll('a[href*="/classroom/"]');
171
+ const results = [];
172
+ const seen = new Set();
173
+ moduleCards.forEach((card) => {
174
+ const anchor = card;
175
+ const href = anchor.href;
176
+ // Extract slug from URL (8 character hex string)
177
+ const slugMatch = /\/classroom\/([a-f0-9]{8})(?:\?|$)/.exec(href);
178
+ if (!slugMatch?.[1])
179
+ return;
180
+ const slug = slugMatch[1];
181
+ if (seen.has(slug))
182
+ return;
183
+ seen.add(slug);
184
+ // Find title - could be in various child elements
185
+ const titleEl = card.querySelector("h3, h4, [class*='title'], [class*='Title']") ??
186
+ card.querySelector("div > div > div");
187
+ const name = titleEl?.textContent?.trim() ?? `Module ${results.length + 1}`;
188
+ // Check for lock icon
189
+ const isLocked = card.querySelector('[class*="lock"], [class*="Lock"]') !== null;
190
+ results.push({
191
+ name,
192
+ slug,
193
+ url: href,
194
+ isLocked,
195
+ });
196
+ });
197
+ return results;
198
+ });
199
+ return modules;
200
+ }
201
+ /**
202
+ * Checks if a URL points to a specific module (has 8-char hex slug).
203
+ */
204
+ function isModuleUrl(url) {
205
+ const match = /\/classroom\/([a-f0-9]{8})(?:\?|$)/.exec(url);
206
+ return {
207
+ isModule: !!match,
208
+ moduleSlug: match?.[1] ?? null,
209
+ };
210
+ }
211
+ /**
212
+ * Gets the classroom base URL (without module slug).
213
+ */
214
+ function getClassroomBaseUrl(url) {
215
+ // Remove module slug and query params
216
+ return url.replace(/\/classroom\/[a-f0-9]{8}.*$/, "/classroom");
217
+ }
218
+ /**
219
+ * Builds the complete course structure by crawling all modules and lessons.
220
+ */
221
+ export async function buildCourseStructure(page, classroomUrl, onProgress) {
222
+ const { isModule, moduleSlug } = isModuleUrl(classroomUrl);
223
+ // If URL points to a specific module, get the base classroom URL first
224
+ const baseClassroomUrl = isModule ? getClassroomBaseUrl(classroomUrl) : classroomUrl;
225
+ // Navigate to the classroom overview to get all modules
226
+ await page.goto(baseClassroomUrl, { timeout: 30000 });
227
+ await page.waitForLoadState("domcontentloaded");
228
+ await page.waitForTimeout(2000);
229
+ const courseName = await extractCourseName(page);
230
+ onProgress?.({ phase: "init", courseName });
231
+ // Try JSON extraction first (more reliable), fall back to page scraping
232
+ let modules = await extractModulesFromJson(page);
233
+ if (modules.length === 0) {
234
+ modules = await extractModulesFromPage(page);
235
+ }
236
+ // If user specified a specific module, filter to just that one
237
+ if (isModule && moduleSlug) {
238
+ const targetModule = modules.find((m) => m.slug === moduleSlug);
239
+ if (targetModule) {
240
+ modules = [targetModule];
241
+ }
242
+ }
243
+ onProgress?.({ phase: "modules", totalModules: modules.length });
244
+ const modulesWithLessons = [];
245
+ for (let i = 0; i < modules.length; i++) {
246
+ const module = modules[i];
247
+ if (module.isLocked) {
248
+ onProgress?.({
249
+ phase: "lessons",
250
+ currentModule: module.name,
251
+ currentModuleIndex: i,
252
+ skippedLocked: true,
253
+ });
254
+ continue;
255
+ }
256
+ onProgress?.({
257
+ phase: "lessons",
258
+ currentModule: module.name,
259
+ currentModuleIndex: i,
260
+ });
261
+ if (module.url) {
262
+ const lessons = await extractLessons(page, module.url);
263
+ onProgress?.({
264
+ phase: "lessons",
265
+ currentModule: module.name,
266
+ currentModuleIndex: i,
267
+ lessonsFound: lessons.length,
268
+ });
269
+ modulesWithLessons.push({
270
+ ...module,
271
+ lessons,
272
+ });
273
+ }
274
+ }
275
+ onProgress?.({ phase: "done" });
276
+ return {
277
+ name: courseName,
278
+ url: baseClassroomUrl,
279
+ modules: modulesWithLessons,
280
+ };
281
+ }
282
+ /**
283
+ * Creates a filesystem-safe name from a string.
284
+ * Uses @sindresorhus/slugify for proper transliteration.
285
+ */
286
+ export function slugify(name) {
287
+ return slugifyLib(name, {
288
+ lowercase: true,
289
+ separator: "-",
290
+ }).substring(0, 100);
291
+ }
292
+ /**
293
+ * Creates a folder name with index prefix.
294
+ */
295
+ export function createFolderName(index, name) {
296
+ const prefix = String(index + 1).padStart(2, "0");
297
+ const slug = slugify(name);
298
+ return `${prefix}-${slug}`;
299
+ }
300
+ //# sourceMappingURL=navigator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.js","sourceRoot":"","sources":["../../src/scraper/navigator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=navigator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.test.d.ts","sourceRoot":"","sources":["../../src/scraper/navigator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,63 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { createFolderName, slugify } from "./navigator.js";
3
+ describe("slugify", () => {
4
+ it("converts to lowercase", () => {
5
+ expect(slugify("Hello World")).toBe("hello-world");
6
+ });
7
+ it("replaces spaces with hyphens", () => {
8
+ expect(slugify("hello world test")).toBe("hello-world-test");
9
+ });
10
+ it("removes special characters", () => {
11
+ expect(slugify("Hello! World? Test.")).toBe("hello-world-test");
12
+ });
13
+ it("handles German umlauts", () => {
14
+ expect(slugify("Größe")).toBe("groesse");
15
+ expect(slugify("Über")).toBe("ueber");
16
+ expect(slugify("Änderung")).toBe("aenderung");
17
+ expect(slugify("Straße")).toBe("strasse");
18
+ });
19
+ it("collapses multiple hyphens", () => {
20
+ expect(slugify("hello world")).toBe("hello-world");
21
+ expect(slugify("hello---world")).toBe("hello-world");
22
+ });
23
+ it("removes leading and trailing hyphens", () => {
24
+ expect(slugify(" hello world ")).toBe("hello-world");
25
+ expect(slugify("---hello---")).toBe("hello");
26
+ });
27
+ it("truncates to 100 characters", () => {
28
+ const longString = "a".repeat(150);
29
+ const result = slugify(longString);
30
+ expect(result.length).toBeLessThanOrEqual(100);
31
+ });
32
+ it("handles numbers", () => {
33
+ expect(slugify("Lesson 1: Introduction")).toBe("lesson-1-introduction");
34
+ });
35
+ it("handles empty string", () => {
36
+ expect(slugify("")).toBe("");
37
+ });
38
+ it("handles string with only special characters", () => {
39
+ // @sindresorhus/slugify converts & to "and"
40
+ expect(slugify("!@#$%^&*()")).toBe("and");
41
+ // Pure symbols without & become empty
42
+ expect(slugify("!@#$%^*()")).toBe("");
43
+ });
44
+ });
45
+ describe("createFolderName", () => {
46
+ it("creates folder name with zero-padded index", () => {
47
+ expect(createFolderName(0, "Introduction")).toBe("01-introduction");
48
+ expect(createFolderName(9, "Advanced Topics")).toBe("10-advanced-topics");
49
+ });
50
+ it("handles double-digit indices", () => {
51
+ expect(createFolderName(99, "Last Module")).toBe("100-last-module");
52
+ });
53
+ it("applies slugify to name", () => {
54
+ expect(createFolderName(0, "Hello World!")).toBe("01-hello-world");
55
+ });
56
+ it("handles German characters in name", () => {
57
+ expect(createFolderName(0, "Einführung")).toBe("01-einfuehrung");
58
+ });
59
+ it("handles empty name", () => {
60
+ expect(createFolderName(0, "")).toBe("01-");
61
+ });
62
+ });
63
+ //# sourceMappingURL=navigator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.test.js","sourceRoot":"","sources":["../../src/scraper/navigator.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { BrowserContext } from "playwright";
2
+ /**
3
+ * Marks a lesson as complete on Skool.
4
+ */
5
+ export declare function markLessonComplete(context: BrowserContext, lessonId: string): Promise<{
6
+ success: boolean;
7
+ error?: string;
8
+ }>;
9
+ /**
10
+ * Marks multiple lessons as complete.
11
+ */
12
+ export declare function markLessonsComplete(context: BrowserContext, lessonIds: string[]): Promise<{
13
+ completed: number;
14
+ failed: number;
15
+ errors: string[];
16
+ }>;
17
+ //# sourceMappingURL=skoolApi.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skoolApi.d.ts","sourceRoot":"","sources":["../../src/scraper/skoolApi.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAwBjD;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,OAAO,EAAE,cAAc,EACvB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC/C;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,cAAc,EACvB,SAAS,EAAE,MAAM,EAAE,GAClB,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAkBlE"}
@@ -0,0 +1,72 @@
1
+ const SKOOL_API_BASE = "https://api2.skool.com";
2
+ /**
3
+ * Extracts auth cookies from a Playwright browser context.
4
+ */
5
+ async function getAuthCookies(context) {
6
+ const cookies = await context.cookies("https://www.skool.com");
7
+ const authToken = cookies.find((c) => c.name === "auth_token")?.value;
8
+ const clientId = cookies.find((c) => c.name === "client_id")?.value;
9
+ const parts = [];
10
+ if (clientId) {
11
+ parts.push(`client_id=${clientId}`);
12
+ }
13
+ if (authToken) {
14
+ parts.push(`auth_token=${authToken}`);
15
+ }
16
+ return parts.join("; ");
17
+ }
18
+ /**
19
+ * Marks a lesson as complete on Skool.
20
+ */
21
+ export async function markLessonComplete(context, lessonId) {
22
+ try {
23
+ const cookieHeader = await getAuthCookies(context);
24
+ if (!cookieHeader.includes("auth_token")) {
25
+ return { success: false, error: "No auth token found" };
26
+ }
27
+ const response = await fetch(`${SKOOL_API_BASE}/courses/${lessonId}/mark-done`, {
28
+ method: "POST",
29
+ headers: {
30
+ "Accept": "*/*",
31
+ "Content-Type": "application/json",
32
+ "Cookie": cookieHeader,
33
+ "Origin": "https://www.skool.com",
34
+ "Referer": "https://www.skool.com/",
35
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
36
+ },
37
+ });
38
+ if (!response.ok) {
39
+ const text = await response.text();
40
+ return { success: false, error: `HTTP ${response.status}: ${text}` };
41
+ }
42
+ return { success: true };
43
+ }
44
+ catch (error) {
45
+ return {
46
+ success: false,
47
+ error: error instanceof Error ? error.message : String(error)
48
+ };
49
+ }
50
+ }
51
+ /**
52
+ * Marks multiple lessons as complete.
53
+ */
54
+ export async function markLessonsComplete(context, lessonIds) {
55
+ let completed = 0;
56
+ let failed = 0;
57
+ const errors = [];
58
+ for (const lessonId of lessonIds) {
59
+ const result = await markLessonComplete(context, lessonId);
60
+ if (result.success) {
61
+ completed++;
62
+ }
63
+ else {
64
+ failed++;
65
+ errors.push(`${lessonId}: ${result.error}`);
66
+ }
67
+ // Small delay to avoid rate limiting
68
+ await new Promise((r) => setTimeout(r, 100));
69
+ }
70
+ return { completed, failed, errors };
71
+ }
72
+ //# sourceMappingURL=skoolApi.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skoolApi.js","sourceRoot":"","sources":["../../src/scraper/skoolApi.ts"],"names":[],"mappings":"AAEA,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEhD;;GAEG;AACH,KAAK,UAAU,cAAc,CAAC,OAAuB;IACnD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,uBAAuB,CAAC,CAAC;IAE/D,MAAM,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,YAAY,CAAC,EAAE,KAAK,CAAC;IACtE,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,EAAE,KAAK,CAAC;IAEpE,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,IAAI,QAAQ,EAAE,CAAC;QACb,KAAK,CAAC,IAAI,CAAC,aAAa,QAAQ,EAAE,CAAC,CAAC;IACtC,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,cAAc,SAAS,EAAE,CAAC,CAAC;IACxC,CAAC;IAED,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,OAAuB,EACvB,QAAgB;IAEhB,IAAI,CAAC;QACH,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,OAAO,CAAC,CAAC;QAEnD,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACzC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC;QAC1D,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,cAAc,YAAY,QAAQ,YAAY,EAAE;YAC9E,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,QAAQ,EAAE,KAAK;gBACf,cAAc,EAAE,kBAAkB;gBAClC,QAAQ,EAAE,YAAY;gBACtB,QAAQ,EAAE,uBAAuB;gBACjC,SAAS,EAAE,wBAAwB;gBACnC,YAAY,EAAE,oEAAoE;aACnF;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,QAAQ,CAAC,MAAM,KAAK,IAAI,EAAE,EAAE,CAAC;QACvE,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAC3B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO;YACL,OAAO,EAAE,KAAK;YACd,KAAK,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;SAC9D,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAuB,EACvB,SAAmB;IAEnB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,MAAM,MAAM,GAAa,EAAE,CAAC;IAE5B,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC3D,IAAI,MAAM,CAAC,OAAO,EAAE,CAAC;YACnB,SAAS,EAAE,CAAC;QACd,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,CAAC;YACT,MAAM,CAAC,IAAI,CAAC,GAAG,QAAQ,KAAK,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;QAC9C,CAAC;QACD,qCAAqC;QACrC,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AACvC,CAAC"}
@@ -0,0 +1,19 @@
1
+ import type { Page } from "playwright";
2
+ /**
3
+ * Captures Vimeo video URL by extracting it from the running player.
4
+ * The key insight: the video is ALREADY playing in the iframe - we just need to get the URL.
5
+ */
6
+ export declare function captureVimeoConfig(page: Page, _videoId: string, timeoutMs?: number): Promise<{
7
+ hlsUrl: string | null;
8
+ progressiveUrl: string | null;
9
+ error?: string;
10
+ }>;
11
+ /**
12
+ * Captures Loom HLS URL by navigating directly to the embed page.
13
+ * This works better than CDP because we can intercept all requests on that page.
14
+ */
15
+ export declare function captureLoomHls(page: Page, videoId: string, timeoutMs?: number): Promise<{
16
+ hlsUrl: string | null;
17
+ error?: string;
18
+ }>;
19
+ //# sourceMappingURL=videoInterceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"videoInterceptor.d.ts","sourceRoot":"","sources":["../../src/scraper/videoInterceptor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA0MnF;AAED;;;GAGG;AACH,wBAAsB,cAAc,CAClC,IAAI,EAAE,IAAI,EACV,OAAO,EAAE,MAAM,EACf,SAAS,SAAQ,GAChB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAsIpD"}