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.
- package/README.md +107 -8
- package/dist/cli/commands/sync.js +4 -1
- package/dist/cli/commands/sync.js.map +1 -1
- package/dist/cli/commands/syncHighLevel.d.ts.map +1 -1
- package/dist/cli/commands/syncHighLevel.js +4 -1
- package/dist/cli/commands/syncHighLevel.js.map +1 -1
- package/dist/cli/commands/syncLearningSuite.d.ts +35 -0
- package/dist/cli/commands/syncLearningSuite.d.ts.map +1 -0
- package/dist/cli/commands/syncLearningSuite.js +765 -0
- package/dist/cli/commands/syncLearningSuite.js.map +1 -0
- package/dist/cli/index.js +38 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/downloader/hlsDownloader.d.ts +10 -4
- package/dist/downloader/hlsDownloader.d.ts.map +1 -1
- package/dist/downloader/hlsDownloader.js +38 -16
- package/dist/downloader/hlsDownloader.js.map +1 -1
- package/dist/downloader/index.d.ts +4 -0
- package/dist/downloader/index.d.ts.map +1 -1
- package/dist/downloader/index.js +6 -6
- package/dist/downloader/index.js.map +1 -1
- package/dist/downloader/loomDownloader.d.ts +1 -1
- package/dist/downloader/loomDownloader.d.ts.map +1 -1
- package/dist/downloader/loomDownloader.js +9 -7
- package/dist/downloader/loomDownloader.js.map +1 -1
- package/dist/scraper/learningsuite/extractor.d.ts +50 -0
- package/dist/scraper/learningsuite/extractor.d.ts.map +1 -0
- package/dist/scraper/learningsuite/extractor.js +429 -0
- package/dist/scraper/learningsuite/extractor.js.map +1 -0
- package/dist/scraper/learningsuite/index.d.ts +4 -0
- package/dist/scraper/learningsuite/index.d.ts.map +1 -0
- package/dist/scraper/learningsuite/index.js +4 -0
- package/dist/scraper/learningsuite/index.js.map +1 -0
- package/dist/scraper/learningsuite/navigator.d.ts +122 -0
- package/dist/scraper/learningsuite/navigator.d.ts.map +1 -0
- package/dist/scraper/learningsuite/navigator.js +736 -0
- package/dist/scraper/learningsuite/navigator.js.map +1 -0
- package/dist/scraper/learningsuite/schemas.d.ts +270 -0
- package/dist/scraper/learningsuite/schemas.d.ts.map +1 -0
- package/dist/scraper/learningsuite/schemas.js +147 -0
- package/dist/scraper/learningsuite/schemas.js.map +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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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"}
|