offcourse 1.0.1 → 1.1.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 (41) hide show
  1. package/README.md +107 -8
  2. package/dist/cli/commands/sync.js +4 -1
  3. package/dist/cli/commands/sync.js.map +1 -1
  4. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -1
  5. package/dist/cli/commands/syncHighLevel.js +4 -1
  6. package/dist/cli/commands/syncHighLevel.js.map +1 -1
  7. package/dist/cli/commands/syncLearningSuite.d.ts +35 -0
  8. package/dist/cli/commands/syncLearningSuite.d.ts.map +1 -0
  9. package/dist/cli/commands/syncLearningSuite.js +765 -0
  10. package/dist/cli/commands/syncLearningSuite.js.map +1 -0
  11. package/dist/cli/index.js +38 -0
  12. package/dist/cli/index.js.map +1 -1
  13. package/dist/downloader/hlsDownloader.d.ts +10 -4
  14. package/dist/downloader/hlsDownloader.d.ts.map +1 -1
  15. package/dist/downloader/hlsDownloader.js +38 -16
  16. package/dist/downloader/hlsDownloader.js.map +1 -1
  17. package/dist/downloader/index.d.ts +4 -0
  18. package/dist/downloader/index.d.ts.map +1 -1
  19. package/dist/downloader/index.js +6 -6
  20. package/dist/downloader/index.js.map +1 -1
  21. package/dist/downloader/loomDownloader.d.ts +1 -1
  22. package/dist/downloader/loomDownloader.d.ts.map +1 -1
  23. package/dist/downloader/loomDownloader.js +9 -7
  24. package/dist/downloader/loomDownloader.js.map +1 -1
  25. package/dist/scraper/learningsuite/extractor.d.ts +50 -0
  26. package/dist/scraper/learningsuite/extractor.d.ts.map +1 -0
  27. package/dist/scraper/learningsuite/extractor.js +429 -0
  28. package/dist/scraper/learningsuite/extractor.js.map +1 -0
  29. package/dist/scraper/learningsuite/index.d.ts +4 -0
  30. package/dist/scraper/learningsuite/index.d.ts.map +1 -0
  31. package/dist/scraper/learningsuite/index.js +4 -0
  32. package/dist/scraper/learningsuite/index.js.map +1 -0
  33. package/dist/scraper/learningsuite/navigator.d.ts +122 -0
  34. package/dist/scraper/learningsuite/navigator.d.ts.map +1 -0
  35. package/dist/scraper/learningsuite/navigator.js +736 -0
  36. package/dist/scraper/learningsuite/navigator.js.map +1 -0
  37. package/dist/scraper/learningsuite/schemas.d.ts +270 -0
  38. package/dist/scraper/learningsuite/schemas.d.ts.map +1 -0
  39. package/dist/scraper/learningsuite/schemas.js +147 -0
  40. package/dist/scraper/learningsuite/schemas.js.map +1 -0
  41. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.d.ts","sourceRoot":"","sources":["../../../src/scraper/learningsuite/extractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvC,MAAM,WAAW,sBAAsB;IACrC,IAAI,EAAE,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7E,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,wBAAwB;IACvC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACrC,WAAW,EAAE;QACX,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,EAAE,CAAC;CACL;AAOD;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,sBAAsB,CAAC,MAAM,CAAC,CAuB3E;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAkI7F;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkE3E;AAED;;GAEG;AACH,wBAAsB,0BAA0B,CAC9C,IAAI,EAAE,IAAI,GACT,OAAO,CAAC,wBAAwB,CAAC,aAAa,CAAC,CAAC,CAuElD;AAED;;;GAGG;AACH,wBAAsB,+BAA+B,CACnD,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,GAAG,IAAI,CAAC,CAqH1C;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,IAAI,EAAE,IAAI,EACV,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,sBAAsB,GAAG,IAAI,CAAC,CAwDxC"}
@@ -0,0 +1,429 @@
1
+ // ============================================================================
2
+ // Browser/API Automation
3
+ // ============================================================================
4
+ /* v8 ignore start */
5
+ /**
6
+ * Detects the video type from a URL.
7
+ */
8
+ export function detectVideoType(url) {
9
+ const lowerUrl = url.toLowerCase();
10
+ if (lowerUrl.includes("vimeo.com") || lowerUrl.includes("player.vimeo")) {
11
+ return "vimeo";
12
+ }
13
+ if (lowerUrl.includes("loom.com")) {
14
+ return "loom";
15
+ }
16
+ if (lowerUrl.includes("youtube.com") || lowerUrl.includes("youtu.be")) {
17
+ return "youtube";
18
+ }
19
+ if (lowerUrl.includes("wistia.com") || lowerUrl.includes("wistia.net")) {
20
+ return "wistia";
21
+ }
22
+ if (lowerUrl.includes(".m3u8")) {
23
+ return "hls";
24
+ }
25
+ if (lowerUrl.includes(".mp4") || lowerUrl.includes(".webm")) {
26
+ return "native";
27
+ }
28
+ return "unknown";
29
+ }
30
+ /**
31
+ * Extracts video information from a lesson page.
32
+ */
33
+ export async function extractVideoFromPage(page) {
34
+ // Check for HLS video
35
+ const hlsUrl = await page.evaluate(() => {
36
+ // Look for video elements with HLS source
37
+ const videos = Array.from(document.querySelectorAll("video"));
38
+ for (const video of videos) {
39
+ const src = video.currentSrc ?? video.src;
40
+ if (src?.includes(".m3u8")) {
41
+ return src;
42
+ }
43
+ }
44
+ // Check for HLS source elements
45
+ const sources = Array.from(document.querySelectorAll('source[type*="m3u8"], source[src*=".m3u8"]'));
46
+ for (const source of sources) {
47
+ const src = source.src;
48
+ if (src)
49
+ return src;
50
+ }
51
+ // Look for HLS URLs in script tags
52
+ const scripts = Array.from(document.querySelectorAll("script"));
53
+ for (const script of scripts) {
54
+ const content = script.textContent ?? "";
55
+ const hlsMatch = /"(https?:\/\/[^"]+\.m3u8[^"]*)"/i.exec(content);
56
+ if (hlsMatch?.[1])
57
+ return hlsMatch[1];
58
+ }
59
+ return null;
60
+ });
61
+ if (hlsUrl) {
62
+ return {
63
+ type: "hls",
64
+ url: hlsUrl,
65
+ hlsUrl,
66
+ };
67
+ }
68
+ // Check for Vimeo embed
69
+ const vimeoUrl = await page.evaluate(() => {
70
+ const iframe = document.querySelector('iframe[src*="vimeo.com"], iframe[src*="player.vimeo"]');
71
+ if (iframe) {
72
+ return iframe.src;
73
+ }
74
+ return null;
75
+ });
76
+ if (vimeoUrl) {
77
+ return {
78
+ type: "vimeo",
79
+ url: vimeoUrl,
80
+ };
81
+ }
82
+ // Check for Loom embed
83
+ const loomUrl = await page.evaluate(() => {
84
+ const iframe = document.querySelector('iframe[src*="loom.com"]');
85
+ if (iframe) {
86
+ return iframe.src;
87
+ }
88
+ return null;
89
+ });
90
+ if (loomUrl) {
91
+ return {
92
+ type: "loom",
93
+ url: loomUrl,
94
+ };
95
+ }
96
+ // Check for YouTube embed
97
+ const youtubeUrl = await page.evaluate(() => {
98
+ const iframe = document.querySelector('iframe[src*="youtube.com"], iframe[src*="youtube-nocookie.com"], iframe[src*="youtu.be"]');
99
+ if (iframe) {
100
+ return iframe.src;
101
+ }
102
+ return null;
103
+ });
104
+ if (youtubeUrl) {
105
+ return {
106
+ type: "youtube",
107
+ url: youtubeUrl,
108
+ };
109
+ }
110
+ // Check for Wistia
111
+ const wistiaInfo = await page.evaluate(() => {
112
+ const wistiaEmbed = document.querySelector('[class*="wistia"]');
113
+ if (wistiaEmbed) {
114
+ const match = /wistia_embed wistia_async_(\w+)/.exec(wistiaEmbed.className);
115
+ if (match?.[1]) {
116
+ return { id: match[1] };
117
+ }
118
+ }
119
+ return null;
120
+ });
121
+ if (wistiaInfo?.id) {
122
+ return {
123
+ type: "wistia",
124
+ url: `https://fast.wistia.net/embed/medias/${wistiaInfo.id}`,
125
+ };
126
+ }
127
+ // Check for native video
128
+ const nativeVideoUrl = await page.evaluate(() => {
129
+ const video = document.querySelector("video");
130
+ if (video) {
131
+ const source = video.querySelector("source");
132
+ const src = source?.src ?? video.src ?? video.currentSrc;
133
+ if (src && !src.includes(".m3u8")) {
134
+ return src;
135
+ }
136
+ }
137
+ return null;
138
+ });
139
+ if (nativeVideoUrl) {
140
+ return {
141
+ type: "native",
142
+ url: nativeVideoUrl,
143
+ };
144
+ }
145
+ return null;
146
+ }
147
+ /**
148
+ * Extracts HTML content from the lesson page using semantic HTML structure.
149
+ * Uses accessibility-friendly selectors: main element, semantic headings, paragraphs, lists.
150
+ * Falls back to data-* attributes which are also stable.
151
+ */
152
+ export async function extractHtmlContent(page) {
153
+ return page.evaluate(() => {
154
+ // Find the main content area (semantic HTML)
155
+ const main = document.querySelector("main");
156
+ if (!main)
157
+ return null;
158
+ // Find content elements using semantic selectors first, then data attributes as fallback
159
+ // Priority: semantic HTML (p, ul, ol in main) > data-slate-node > data-cy attributes
160
+ const contentElements = main.querySelectorAll(
161
+ // Semantic HTML within main
162
+ "p[data-slate-node], ul[data-slate-node], ol[data-slate-node], " +
163
+ // Stable data attributes as fallback
164
+ '[data-cy="paragraph-element"], [data-cy="list-item"]');
165
+ if (contentElements.length > 0) {
166
+ const htmlParts = [];
167
+ const processedTexts = new Set();
168
+ for (const el of Array.from(contentElements)) {
169
+ const tag = el.tagName.toLowerCase();
170
+ const text = el.textContent?.trim() ?? "";
171
+ // Skip empty, duplicate, or very short text
172
+ if (!text || processedTexts.has(text) || text.length < 3)
173
+ continue;
174
+ processedTexts.add(text);
175
+ if (tag === "p") {
176
+ htmlParts.push(`<p>${text}</p>`);
177
+ }
178
+ else if (tag === "ul" || tag === "ol") {
179
+ const items = el.querySelectorAll("li");
180
+ const listItems = Array.from(items)
181
+ .map((li) => li.textContent?.trim() ?? "")
182
+ .filter((t) => t.length > 0)
183
+ .map((t) => `<li>${t}</li>`)
184
+ .join("");
185
+ if (listItems) {
186
+ htmlParts.push(`<${tag}>${listItems}</${tag}>`);
187
+ }
188
+ }
189
+ }
190
+ if (htmlParts.length > 0) {
191
+ return htmlParts.join("\n");
192
+ }
193
+ }
194
+ // Fallback: extract from main, excluding navigation and interactive elements
195
+ const clone = main.cloneNode(true);
196
+ // Remove non-content elements using semantic/role selectors
197
+ const unwanted = clone.querySelectorAll("script, style, nav, video, iframe, svg, button, input, " +
198
+ '[role="navigation"], [role="button"], [role="menuitem"], [role="menu"]');
199
+ unwanted.forEach((el) => {
200
+ el.remove();
201
+ });
202
+ const text = clone.textContent?.trim();
203
+ if (text && text.length > 50) {
204
+ return `<p>${text}</p>`;
205
+ }
206
+ return null;
207
+ });
208
+ }
209
+ /**
210
+ * Extracts attachments/materials from the lesson page.
211
+ */
212
+ export async function extractAttachmentsFromPage(page) {
213
+ return page.evaluate(() => {
214
+ const attachments = [];
215
+ // Look for download links - include storage URLs (Google Cloud Storage)
216
+ const downloadLinks = document.querySelectorAll('a[download], a[href*=".pdf"], a[href*=".doc"], a[href*=".xls"], a[href*=".ppt"], a[href*=".zip"], a[href*="storage.googleapis.com"], a[href*="storage.cloud.google"]');
217
+ const seen = new Set();
218
+ for (const link of Array.from(downloadLinks)) {
219
+ const anchor = link;
220
+ const url = anchor.href;
221
+ if (!url || seen.has(url))
222
+ continue;
223
+ // Skip non-file URLs
224
+ if (url.startsWith("javascript:") || url.startsWith("#"))
225
+ continue;
226
+ seen.add(url);
227
+ // Get filename from download attribute, text content, or URL
228
+ let name = anchor.download || "";
229
+ if (!name) {
230
+ // Try to get name from visible text (often the file name is shown)
231
+ const textContent = anchor.textContent?.trim() ?? "";
232
+ if (textContent?.includes(".")) {
233
+ name = textContent;
234
+ }
235
+ }
236
+ if (!name) {
237
+ // Extract from URL, handling encoded characters
238
+ const urlParts = url.split("/");
239
+ const lastPart = urlParts[urlParts.length - 1]?.split("?")[0] ?? "";
240
+ try {
241
+ name = decodeURIComponent(lastPart);
242
+ }
243
+ catch {
244
+ name = lastPart;
245
+ }
246
+ }
247
+ if (!name) {
248
+ name = "attachment";
249
+ }
250
+ // Determine type from extension
251
+ const ext = name.split(".").pop()?.toLowerCase() ?? "";
252
+ let type = "file";
253
+ if (["pdf"].includes(ext))
254
+ type = "pdf";
255
+ else if (["doc", "docx"].includes(ext))
256
+ type = "document";
257
+ else if (["xls", "xlsx"].includes(ext))
258
+ type = "spreadsheet";
259
+ else if (["ppt", "pptx"].includes(ext))
260
+ type = "presentation";
261
+ else if (["zip", "rar", "7z"].includes(ext))
262
+ type = "archive";
263
+ else if (["jpg", "jpeg", "png", "gif", "webp"].includes(ext))
264
+ type = "image";
265
+ attachments.push({
266
+ id: `attachment-${attachments.length}`,
267
+ name,
268
+ url,
269
+ type,
270
+ });
271
+ }
272
+ return attachments;
273
+ });
274
+ }
275
+ /**
276
+ * Extracts complete lesson content using DOM-based extraction with network interception.
277
+ * Note: LearningSuite uses persisted GraphQL queries, so we can't make arbitrary API calls.
278
+ */
279
+ export async function extractLearningSuitePostContent(page, lessonUrl, _tenantId, _courseId, lessonId) {
280
+ // Set up request interception to capture HLS video URLs
281
+ const hlsUrls = [];
282
+ const requestHandler = (request) => {
283
+ const url = request.url();
284
+ // Capture HLS playlists from Bunny API or direct m3u8
285
+ if (url.includes("/playlist/master") || url.includes(".m3u8")) {
286
+ hlsUrls.push(url);
287
+ }
288
+ };
289
+ page.on("request", requestHandler);
290
+ // Navigate to lesson page
291
+ await page.goto(lessonUrl, { timeout: 30000 });
292
+ await page.waitForLoadState("domcontentloaded");
293
+ // Wait for video player to appear (if any)
294
+ const hasVideoPlayer = await page
295
+ .locator("video, [class*='video'], [class*='Video'], [class*='player'], [class*='Player']")
296
+ .first()
297
+ .waitFor({ state: "attached", timeout: 5000 })
298
+ .then(() => true)
299
+ .catch(() => false);
300
+ // If video player exists but no HLS URL captured yet, try to trigger video load
301
+ if (hasVideoPlayer && hlsUrls.length === 0) {
302
+ // Try clicking play button or video element to trigger load
303
+ const playButton = page.locator('[aria-label*="play" i], [aria-label*="Play" i], [class*="play" i], button[class*="Play"], video');
304
+ try {
305
+ await playButton.first().click({ timeout: 2000 });
306
+ // Wait for HLS URL to be captured after clicking play
307
+ await page.waitForTimeout(2000);
308
+ }
309
+ catch {
310
+ // Play button not found or not clickable, continue anyway
311
+ }
312
+ }
313
+ // Give a bit more time for lazy-loaded videos
314
+ if (hlsUrls.length === 0 && hasVideoPlayer) {
315
+ await page.waitForTimeout(2000);
316
+ }
317
+ // Remove handler
318
+ page.off("request", requestHandler);
319
+ // Try to get video from intercepted requests first
320
+ let video = null;
321
+ const masterPlaylist = hlsUrls.find((url) => url.includes("/playlist/master"));
322
+ if (masterPlaylist) {
323
+ video = {
324
+ type: "hls",
325
+ url: masterPlaylist,
326
+ hlsUrl: masterPlaylist,
327
+ };
328
+ }
329
+ else if (hlsUrls.length > 0 && hlsUrls[0]) {
330
+ video = {
331
+ type: "hls",
332
+ url: hlsUrls[0],
333
+ hlsUrl: hlsUrls[0],
334
+ };
335
+ }
336
+ // Fallback to DOM extraction if no HLS found
337
+ video ??= await extractVideoFromPage(page);
338
+ const htmlContent = await extractHtmlContent(page);
339
+ const attachments = await extractAttachmentsFromPage(page);
340
+ // Get title from page using semantic HTML structure
341
+ // The lesson title is typically an h3 within the main element
342
+ const title = await page.evaluate(() => {
343
+ const main = document.querySelector("main");
344
+ // Find h3 heading within main (lesson title is usually h3)
345
+ if (main) {
346
+ const h3 = main.querySelector("h3");
347
+ if (h3?.textContent?.trim()) {
348
+ return h3.textContent.trim();
349
+ }
350
+ }
351
+ // Try breadcrumb navigation (last item is the current page)
352
+ const breadcrumb = document.querySelector('nav[aria-label*="breadcrumb"], [role="navigation"] li:last-child');
353
+ if (breadcrumb?.textContent?.trim()) {
354
+ return breadcrumb.textContent.trim();
355
+ }
356
+ // Try any h3 on the page
357
+ const h3 = document.querySelector("h3");
358
+ if (h3?.textContent?.trim()) {
359
+ return h3.textContent.trim();
360
+ }
361
+ // Try h1 as fallback
362
+ const h1 = document.querySelector("h1");
363
+ if (h1?.textContent?.trim()) {
364
+ return h1.textContent.trim();
365
+ }
366
+ // Use page title as last resort
367
+ return document.title.split(" - ")[0] ?? "Untitled";
368
+ });
369
+ return {
370
+ id: lessonId,
371
+ title,
372
+ description: null,
373
+ htmlContent,
374
+ video,
375
+ attachments,
376
+ };
377
+ }
378
+ /**
379
+ * Intercepts network requests to capture video URLs during page load.
380
+ */
381
+ export async function interceptVideoRequests(page, lessonUrl) {
382
+ const hlsUrls = [];
383
+ const videoUrls = [];
384
+ // Set up request interception
385
+ const requestHandler = (request) => {
386
+ const url = request.url();
387
+ // Capture HLS playlists
388
+ if (url.includes(".m3u8") || url.includes("master.m3u8")) {
389
+ hlsUrls.push(url);
390
+ }
391
+ // Capture video files
392
+ if (url.includes(".mp4") || url.includes(".webm")) {
393
+ videoUrls.push(url);
394
+ }
395
+ };
396
+ page.on("request", requestHandler);
397
+ // Navigate to the lesson
398
+ await page.goto(lessonUrl, { timeout: 30000 });
399
+ await page.waitForLoadState("domcontentloaded");
400
+ await page.waitForTimeout(3000);
401
+ // Remove handler
402
+ page.off("request", requestHandler);
403
+ // Return the best URL found
404
+ const masterPlaylist = hlsUrls.find((url) => url.includes("master.m3u8"));
405
+ if (masterPlaylist) {
406
+ return {
407
+ type: "hls",
408
+ url: masterPlaylist,
409
+ hlsUrl: masterPlaylist,
410
+ };
411
+ }
412
+ if (hlsUrls.length > 0 && hlsUrls[0]) {
413
+ return {
414
+ type: "hls",
415
+ url: hlsUrls[0],
416
+ hlsUrl: hlsUrls[0],
417
+ };
418
+ }
419
+ if (videoUrls.length > 0 && videoUrls[0]) {
420
+ return {
421
+ type: "native",
422
+ url: videoUrls[0],
423
+ };
424
+ }
425
+ // Fallback to DOM extraction
426
+ return extractVideoFromPage(page);
427
+ }
428
+ /* v8 ignore stop */
429
+ //# sourceMappingURL=extractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extractor.js","sourceRoot":"","sources":["../../../src/scraper/learningsuite/extractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export * from "./navigator.js";
2
+ export * from "./extractor.js";
3
+ export * from "./schemas.js";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/scraper/learningsuite/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from "./navigator.js";
2
+ export * from "./extractor.js";
3
+ export * from "./schemas.js";
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/scraper/learningsuite/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,122 @@
1
+ import type { Page } from "playwright";
2
+ import { type Course, type Module, type Lesson } from "./schemas.js";
3
+ export interface LearningSuiteCourse {
4
+ id: string;
5
+ title: string;
6
+ description: string | null;
7
+ thumbnailUrl: string | null;
8
+ moduleCount: number;
9
+ lessonCount: number;
10
+ }
11
+ export interface LearningSuiteModule {
12
+ id: string;
13
+ title: string;
14
+ description: string | null;
15
+ position: number;
16
+ isLocked: boolean;
17
+ }
18
+ export interface LearningSuiteLesson {
19
+ id: string;
20
+ title: string;
21
+ position: number;
22
+ moduleId: string;
23
+ isLocked: boolean;
24
+ isCompleted: boolean;
25
+ }
26
+ export interface LearningSuiteCourseStructure {
27
+ course: LearningSuiteCourse;
28
+ modules: (LearningSuiteModule & {
29
+ lessons: LearningSuiteLesson[];
30
+ })[];
31
+ tenantId: string;
32
+ domain: string;
33
+ courseSlug?: string;
34
+ }
35
+ export interface LearningSuiteScanProgress {
36
+ phase: "init" | "course" | "modules" | "lessons" | "done";
37
+ courseName?: string;
38
+ totalModules?: number;
39
+ currentModule?: string;
40
+ currentModuleIndex?: number;
41
+ lessonsFound?: number;
42
+ skippedLocked?: boolean;
43
+ }
44
+ /**
45
+ * Extracts the tenant ID from a LearningSuite URL.
46
+ * URL format: https://{subdomain}.learningsuite.io/...
47
+ */
48
+ export declare function extractTenantFromUrl(url: string): {
49
+ subdomain: string;
50
+ tenantId: string | null;
51
+ };
52
+ /**
53
+ * Extracts the tenant ID from the page by inspecting network requests or localStorage.
54
+ */
55
+ export declare function extractTenantId(page: Page): Promise<string | null>;
56
+ /**
57
+ * Gets the auth token from localStorage.
58
+ */
59
+ export declare function getAuthToken(page: Page): Promise<string | null>;
60
+ /**
61
+ * Makes a GraphQL request to the LearningSuite API.
62
+ */
63
+ export declare function graphqlRequest<T>(page: Page, tenantId: string, query: string, variables?: Record<string, unknown>): Promise<T | null>;
64
+ /**
65
+ * Fetches all courses available to the user.
66
+ */
67
+ export declare function fetchCourses(page: Page, tenantId: string): Promise<Course[]>;
68
+ /**
69
+ * Fetches modules for a course.
70
+ */
71
+ export declare function fetchModules(page: Page, tenantId: string, courseId: string): Promise<Module[]>;
72
+ /**
73
+ * Fetches lessons for a module.
74
+ */
75
+ export declare function fetchLessons(page: Page, tenantId: string, courseId: string, moduleId: string): Promise<Lesson[]>;
76
+ /**
77
+ * Extracts courses from the page DOM (fallback method).
78
+ */
79
+ export declare function extractCoursesFromPage(page: Page): Promise<LearningSuiteCourse[]>;
80
+ /**
81
+ * Builds the complete course structure for a LearningSuite course using DOM extraction.
82
+ * This is more reliable than GraphQL as the API structure may vary between instances.
83
+ */
84
+ export declare function buildLearningSuiteCourseStructure(page: Page, courseUrl: string, onProgress?: (progress: LearningSuiteScanProgress) => void): Promise<LearningSuiteCourseStructure | null>;
85
+ /**
86
+ * Constructs the URL for a LearningSuite course.
87
+ */
88
+ export declare function getLearningSuiteCourseUrl(domain: string, courseSlug: string, courseId: string): string;
89
+ /**
90
+ * Constructs the URL for a LearningSuite lesson.
91
+ * URL format: /student/course/{slug}/{courseId}/{topicId}
92
+ * Note: The topicId (lessonId from module page) is enough - the server redirects to the full URL.
93
+ */
94
+ export declare function getLearningSuiteLessonUrl(domain: string, courseSlug: string, courseId: string, _moduleId: string, // Unused - kept for API compatibility
95
+ lessonId: string): string;
96
+ /**
97
+ * Marks a lesson as completed by clicking the "Abschließen" button.
98
+ * This unlocks the next lesson in sequence.
99
+ *
100
+ * @returns true if successfully completed, false otherwise
101
+ */
102
+ export declare function markLessonComplete(page: Page, lessonUrl: string): Promise<boolean>;
103
+ /**
104
+ * Auto-completes all accessible lessons in sequence to unlock subsequent content.
105
+ * This is useful when lessons are sequentially locked.
106
+ *
107
+ * @param page - Playwright page
108
+ * @param lessons - List of lessons to complete
109
+ * @param onProgress - Callback for progress updates
110
+ * @returns Number of lessons successfully completed
111
+ */
112
+ export declare function autoCompleteLessons(page: Page, lessons: {
113
+ url: string;
114
+ title: string;
115
+ isLocked: boolean;
116
+ }[], onProgress?: (completed: number, total: number, currentLesson: string) => void): Promise<number>;
117
+ /**
118
+ * Checks if a lesson page shows the lesson as completed.
119
+ */
120
+ export declare function isLessonCompleted(page: Page): Promise<boolean>;
121
+ export { slugify, createFolderName } from "../../shared/slug.js";
122
+ //# sourceMappingURL=navigator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.d.ts","sourceRoot":"","sources":["../../../src/scraper/learningsuite/navigator.ts"],"names":[],"mappings":"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"}