offcourse 1.2.1 β 1.3.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 +37 -336
- package/dist/cli/commands/syncLearningSuite.d.ts.map +1 -1
- package/dist/cli/commands/syncLearningSuite.js +51 -11
- package/dist/cli/commands/syncLearningSuite.js.map +1 -1
- package/dist/cli/index.js +38 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/downloader/hlsDownloader.d.ts +26 -4
- package/dist/downloader/hlsDownloader.d.ts.map +1 -1
- package/dist/downloader/hlsDownloader.js +231 -13
- package/dist/downloader/hlsDownloader.js.map +1 -1
- package/dist/downloader/index.d.ts +2 -0
- package/dist/downloader/index.d.ts.map +1 -1
- package/dist/downloader/index.js +4 -4
- package/dist/downloader/index.js.map +1 -1
- package/dist/downloader/loomDownloader.d.ts.map +1 -1
- package/dist/downloader/loomDownloader.js +13 -1
- package/dist/downloader/loomDownloader.js.map +1 -1
- package/dist/scraper/extractor.d.ts +5 -0
- package/dist/scraper/extractor.d.ts.map +1 -1
- package/dist/scraper/extractor.js +10 -0
- package/dist/scraper/extractor.js.map +1 -1
- package/dist/scraper/learningsuite/extractor.d.ts +28 -0
- package/dist/scraper/learningsuite/extractor.d.ts.map +1 -1
- package/dist/scraper/learningsuite/extractor.js +207 -44
- package/dist/scraper/learningsuite/extractor.js.map +1 -1
- package/dist/scraper/learningsuite/navigator.d.ts.map +1 -1
- package/dist/scraper/learningsuite/navigator.js +54 -0
- package/dist/scraper/learningsuite/navigator.js.map +1 -1
- package/dist/scraper/videoInterceptor.d.ts +82 -0
- package/dist/scraper/videoInterceptor.d.ts.map +1 -1
- package/dist/scraper/videoInterceptor.js +142 -0
- package/dist/scraper/videoInterceptor.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -9,382 +9,83 @@
|
|
|
9
9
|
|
|
10
10
|
Download online courses for offline access β of course! π
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
**[β View Documentation & Homepage](https://sebastian-software.github.io/offcourse/)**
|
|
13
13
|
|
|
14
|
-
##
|
|
15
|
-
|
|
16
|
-
- π **Browser-based authentication** β Log in once, sessions are cached
|
|
17
|
-
- π **Course structure preservation** β Maintains module/lesson hierarchy
|
|
18
|
-
- π¬ **Video downloads** β HLS, Vimeo, Loom, native MP4/WebM
|
|
19
|
-
- π **Attachments** β Downloads PDFs and other course materials
|
|
20
|
-
- π **Content extraction** β Converts lesson text to clean Markdown
|
|
21
|
-
- βΈοΈ **Resumable syncs** β Skips already downloaded content
|
|
22
|
-
- β‘ **Concurrent downloads** β Configurable parallelism
|
|
23
|
-
- π **Auto-detection** β Automatically detects platform from URL
|
|
24
|
-
|
|
25
|
-
## Supported Platforms
|
|
26
|
-
|
|
27
|
-
| Platform | Status | Notes |
|
|
28
|
-
|----------|--------|-------|
|
|
29
|
-
| [Skool.com](https://skool.com) | β
Supported | Community courses |
|
|
30
|
-
| [HighLevel (GoHighLevel)](https://gohighlevel.com) | β
Supported | Membership portals, ClientClub |
|
|
31
|
-
| [LearningSuite.io](https://learningsuite.io) | β
Supported | German LMS platform |
|
|
32
|
-
|
|
33
|
-
## Installation
|
|
14
|
+
## Quick Start
|
|
34
15
|
|
|
35
16
|
```bash
|
|
17
|
+
# Install
|
|
36
18
|
npm install -g offcourse
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Or run directly with npx:
|
|
40
|
-
|
|
41
|
-
```bash
|
|
42
|
-
npx offcourse <command>
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Requires Node.js 22+.
|
|
46
|
-
|
|
47
|
-
For HLS video downloads (HighLevel native videos), [ffmpeg](https://ffmpeg.org/) must be installed:
|
|
48
|
-
|
|
49
|
-
```bash
|
|
50
|
-
# macOS
|
|
51
|
-
brew install ffmpeg
|
|
52
19
|
|
|
53
|
-
#
|
|
54
|
-
|
|
20
|
+
# Download a course
|
|
21
|
+
offcourse sync <course-url>
|
|
55
22
|
|
|
56
|
-
#
|
|
57
|
-
|
|
23
|
+
# Or run without installing
|
|
24
|
+
npx offcourse sync <course-url>
|
|
58
25
|
```
|
|
59
26
|
|
|
60
|
-
|
|
27
|
+
Requires Node.js 22+ and [ffmpeg](https://ffmpeg.org/) for HLS videos.
|
|
61
28
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
# Opens browser for interactive login
|
|
66
|
-
offcourse login
|
|
67
|
-
|
|
68
|
-
# Force re-login
|
|
69
|
-
offcourse login --force
|
|
70
|
-
```
|
|
29
|
+
## Supported Platforms
|
|
71
30
|
|
|
72
|
-
|
|
31
|
+
| Platform | URL Pattern |
|
|
32
|
+
|----------|-------------|
|
|
33
|
+
| [Skool](https://skool.com) | `skool.com/community/classroom` |
|
|
34
|
+
| [HighLevel](https://gohighlevel.com) | `member.*.com/courses/...` |
|
|
35
|
+
| [LearningSuite](https://learningsuite.io) | `*.learningsuite.io/student/...` |
|
|
73
36
|
|
|
74
|
-
|
|
37
|
+
## Key Commands
|
|
75
38
|
|
|
76
39
|
```bash
|
|
77
|
-
#
|
|
40
|
+
# Sync a course (auto-detects platform)
|
|
78
41
|
offcourse sync <url>
|
|
79
42
|
|
|
80
|
-
#
|
|
81
|
-
offcourse sync <url> --skip-videos
|
|
82
|
-
|
|
83
|
-
#
|
|
84
|
-
offcourse sync <url> --skip-content
|
|
85
|
-
|
|
86
|
-
# Preview without downloading
|
|
87
|
-
offcourse sync <url> --dry-run
|
|
88
|
-
|
|
89
|
-
# Limit to first N lessons (for testing)
|
|
90
|
-
offcourse sync <url> --limit 5
|
|
91
|
-
|
|
92
|
-
# Override course name (useful when auto-detection fails)
|
|
93
|
-
offcourse sync <url> --course-name "My Course Name"
|
|
94
|
-
|
|
95
|
-
# Prefer specific video quality
|
|
96
|
-
offcourse sync <url> --quality 720p
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
### Platform-Specific Commands
|
|
100
|
-
|
|
101
|
-
```bash
|
|
102
|
-
# Skool courses
|
|
103
|
-
offcourse sync-skool https://www.skool.com/your-community/classroom
|
|
43
|
+
# Sync with options
|
|
44
|
+
offcourse sync <url> --skip-videos # Text only
|
|
45
|
+
offcourse sync <url> --dry-run # Preview
|
|
46
|
+
offcourse sync <url> --limit 5 # Test with 5 lessons
|
|
104
47
|
|
|
105
|
-
#
|
|
106
|
-
offcourse sync-highlevel https://member.example.com/courses/products/<id>
|
|
107
|
-
offcourse sync-highlevel <url> --course-name "Course Name"
|
|
108
|
-
|
|
109
|
-
# LearningSuite courses
|
|
110
|
-
offcourse sync-learningsuite https://subdomain.learningsuite.io/student/course/<id>
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Complete Command (LearningSuite)
|
|
114
|
-
|
|
115
|
-
Some platforms lock lessons sequentially β you must complete lesson 1 before accessing lesson 2. The `complete` command automatically marks all accessible lessons as complete to unlock more content:
|
|
116
|
-
|
|
117
|
-
```bash
|
|
118
|
-
# Mark all lessons as complete (iterates until no new content unlocks)
|
|
48
|
+
# Unlock sequential content (LearningSuite)
|
|
119
49
|
offcourse complete <url>
|
|
120
50
|
|
|
121
|
-
#
|
|
122
|
-
offcourse complete <url> --visible
|
|
123
|
-
```
|
|
124
|
-
|
|
125
|
-
The command runs in rounds:
|
|
126
|
-
1. Scans course structure
|
|
127
|
-
2. Starts any unstarted modules
|
|
128
|
-
3. Marks accessible lessons as complete
|
|
129
|
-
4. Re-scans for newly unlocked content
|
|
130
|
-
5. Repeats until nothing changes
|
|
131
|
-
|
|
132
|
-
### Configuration
|
|
133
|
-
|
|
134
|
-
```bash
|
|
135
|
-
# Show current config
|
|
136
|
-
offcourse config show
|
|
137
|
-
|
|
138
|
-
# Set output directory
|
|
51
|
+
# Configuration
|
|
139
52
|
offcourse config set outputDir ~/Courses
|
|
140
|
-
|
|
141
|
-
# Set video quality (highest, lowest, 1080p, 720p, 480p)
|
|
142
53
|
offcourse config set videoQuality 720p
|
|
143
|
-
|
|
144
|
-
# Set download concurrency (1-5)
|
|
145
|
-
offcourse config set concurrency 3
|
|
146
|
-
|
|
147
|
-
# Run headless (no browser window)
|
|
148
|
-
offcourse config set headless true
|
|
149
|
-
```
|
|
150
|
-
|
|
151
|
-
### Inspect (Debugging)
|
|
152
|
-
|
|
153
|
-
```bash
|
|
154
|
-
# Analyze page structure
|
|
155
|
-
offcourse inspect <url>
|
|
156
|
-
|
|
157
|
-
# Save analysis to files
|
|
158
|
-
offcourse inspect <url> --output ./analysis
|
|
159
|
-
|
|
160
|
-
# Include full HTML dump
|
|
161
|
-
offcourse inspect <url> --full
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
## Output Structure
|
|
165
|
-
|
|
166
|
-
```
|
|
167
|
-
~/Downloads/offcourse/
|
|
168
|
-
βββ course-name/
|
|
169
|
-
βββ 01-module-name/
|
|
170
|
-
β βββ 01-lesson-name/
|
|
171
|
-
β β βββ content.md
|
|
172
|
-
β β βββ video.mp4
|
|
173
|
-
β βββ 02-another-lesson/
|
|
174
|
-
β βββ content.md
|
|
175
|
-
β βββ video.mp4
|
|
176
|
-
βββ 02-next-module/
|
|
177
|
-
βββ ...
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Video Providers
|
|
181
|
-
|
|
182
|
-
Offcourse supports downloading videos from various providers, regardless of which platform hosts the course:
|
|
183
|
-
|
|
184
|
-
| Provider | Status | Notes |
|
|
185
|
-
|----------|--------|-------|
|
|
186
|
-
| **HLS Streams** | β
Supported | Requires ffmpeg. Used by HighLevel, LearningSuite (via Bunny CDN) |
|
|
187
|
-
| **Vimeo** | β
Supported | Embedded players, extracts best quality |
|
|
188
|
-
| **Loom** | β
Supported | Share links and embeds |
|
|
189
|
-
| **Native MP4/WebM** | β
Supported | Direct file downloads |
|
|
190
|
-
| **YouTube** | π§ Planned | Requires yt-dlp |
|
|
191
|
-
| **Wistia** | π§ Planned | Requires special handling |
|
|
192
|
-
|
|
193
|
-
### CDN Support
|
|
194
|
-
|
|
195
|
-
Videos are often served through CDNs for better performance:
|
|
196
|
-
|
|
197
|
-
- **Bunny CDN** β Used by LearningSuite for HLS streams (requires session cookies)
|
|
198
|
-
- **Cloudflare Stream** β Common for HighLevel native videos
|
|
199
|
-
- **Vimeo CDN** β For embedded Vimeo players
|
|
200
|
-
|
|
201
|
-
## Platform Notes
|
|
202
|
-
|
|
203
|
-
### HighLevel (GoHighLevel)
|
|
204
|
-
|
|
205
|
-
HighLevel is an all-in-one marketing platform with a "Memberships" feature for hosting courses.
|
|
206
|
-
|
|
207
|
-
| Feature | Support |
|
|
208
|
-
|---------|---------|
|
|
209
|
-
| Authentication | Firebase-based login via browser |
|
|
210
|
-
| Course structure | API-based extraction (products, categories, posts) |
|
|
211
|
-
| Videos | Native HLS with quality selection, Vimeo, Loom embeds |
|
|
212
|
-
| Attachments | β
Supported |
|
|
213
|
-
|
|
214
|
-
**URL patterns:**
|
|
215
|
-
- `https://member.yourdomain.com/courses/...`
|
|
216
|
-
- `https://portal.yourdomain.com/courses/...`
|
|
217
|
-
- `https://courses.yourdomain.com/...`
|
|
218
|
-
|
|
219
|
-
### LearningSuite
|
|
220
|
-
|
|
221
|
-
LearningSuite is a German LMS platform popular with coaches and course creators.
|
|
222
|
-
|
|
223
|
-
| Feature | Support |
|
|
224
|
-
|---------|---------|
|
|
225
|
-
| Authentication | Browser-based with session caching |
|
|
226
|
-
| Course structure | DOM-based extraction |
|
|
227
|
-
| Videos | HLS streams via Bunny CDN (requires ffmpeg + cookies) |
|
|
228
|
-
| Attachments | β
PDFs and course materials |
|
|
229
|
-
| Sequential unlocking | Use `offcourse complete <url>` |
|
|
230
|
-
|
|
231
|
-
**URL format:** `https://{subdomain}.learningsuite.io/student/course/{slug}/{courseId}`
|
|
232
|
-
|
|
233
|
-
**Note:** Videos require session cookies which are automatically extracted from the browser session.
|
|
234
|
-
|
|
235
|
-
## Architecture
|
|
236
|
-
|
|
237
|
-
```
|
|
238
|
-
src/
|
|
239
|
-
βββ cli/ # Command-line interface
|
|
240
|
-
β βββ commands/ # Individual commands (sync, login, config, etc.)
|
|
241
|
-
β βββ index.ts # CLI entry point
|
|
242
|
-
βββ config/ # Configuration management
|
|
243
|
-
βββ downloader/ # Video download handlers
|
|
244
|
-
β βββ hlsDownloader.ts # HLS/m3u8 streams (ffmpeg)
|
|
245
|
-
β βββ vimeoDownloader.ts # Vimeo video extraction
|
|
246
|
-
β βββ loomDownloader.ts # Loom video extraction
|
|
247
|
-
β βββ queue.ts # Download queue with concurrency
|
|
248
|
-
βββ scraper/ # Platform-specific scrapers
|
|
249
|
-
β βββ highlevel/ # HighLevel/GoHighLevel support
|
|
250
|
-
β βββ learningsuite/# LearningSuite support
|
|
251
|
-
β βββ extractor.ts # Common content extraction
|
|
252
|
-
β βββ navigator.ts # Common navigation utilities
|
|
253
|
-
βββ shared/ # Shared utilities
|
|
254
|
-
β βββ auth.ts # Authentication helpers
|
|
255
|
-
β βββ url.ts # URL parsing utilities
|
|
256
|
-
β βββ slug.ts # Filename sanitization
|
|
257
|
-
βββ state/ # SQLite database for tracking
|
|
258
|
-
βββ storage/ # File system operations
|
|
259
54
|
```
|
|
260
55
|
|
|
261
|
-
### Adding a New Platform
|
|
262
|
-
|
|
263
|
-
1. Create a new directory under `src/scraper/` (e.g., `src/scraper/newplatform/`)
|
|
264
|
-
2. Implement required modules:
|
|
265
|
-
- `auth.ts` β Session detection and validation
|
|
266
|
-
- `navigator.ts` β Course structure extraction
|
|
267
|
-
- `extractor.ts` β Lesson content extraction
|
|
268
|
-
- `schemas.ts` β Zod schemas for API responses
|
|
269
|
-
3. Add CLI command in `src/cli/commands/`
|
|
270
|
-
4. Register in `src/cli/index.ts`
|
|
271
|
-
|
|
272
56
|
## Development
|
|
273
57
|
|
|
274
|
-
### Setup
|
|
275
|
-
|
|
276
58
|
```bash
|
|
277
|
-
# Clone the repository
|
|
278
59
|
git clone https://github.com/sebastian-software/offcourse.git
|
|
279
60
|
cd offcourse
|
|
280
|
-
|
|
281
|
-
# Install dependencies
|
|
282
61
|
npm install
|
|
283
|
-
|
|
284
|
-
# Build
|
|
285
62
|
npm run build
|
|
286
|
-
|
|
287
|
-
# Link globally (optional)
|
|
288
|
-
npm link
|
|
63
|
+
npm link # optional: link globally
|
|
289
64
|
```
|
|
290
65
|
|
|
291
66
|
### Commands
|
|
292
67
|
|
|
293
68
|
```bash
|
|
294
|
-
# Watch mode
|
|
295
|
-
npm run
|
|
296
|
-
|
|
297
|
-
#
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
# Lint
|
|
301
|
-
npm run lint
|
|
302
|
-
|
|
303
|
-
# Format
|
|
304
|
-
npm run format
|
|
305
|
-
|
|
306
|
-
# Type check
|
|
307
|
-
npm run typecheck
|
|
308
|
-
|
|
309
|
-
# Test
|
|
310
|
-
npm test
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
### Git Hooks
|
|
314
|
-
|
|
315
|
-
This project uses [Husky](https://typicode.github.io/husky/) for Git hooks:
|
|
316
|
-
|
|
317
|
-
- **pre-commit**: Runs Prettier on staged files via lint-staged
|
|
318
|
-
- **pre-push**: Runs ESLint and TypeScript type checking
|
|
319
|
-
- **commit-msg**: Validates commit messages follow [Conventional Commits](https://www.conventionalcommits.org/)
|
|
320
|
-
|
|
321
|
-
### Commit Convention
|
|
322
|
-
|
|
323
|
-
We follow [Conventional Commits](https://www.conventionalcommits.org/). Commit messages must follow this format:
|
|
324
|
-
|
|
69
|
+
npm run dev # Watch mode
|
|
70
|
+
npm run lint # ESLint
|
|
71
|
+
npm run typecheck # TypeScript
|
|
72
|
+
npm test # Tests
|
|
73
|
+
npm run release # Release to npm
|
|
325
74
|
```
|
|
326
|
-
<type>[optional scope]: <description>
|
|
327
|
-
|
|
328
|
-
[optional body]
|
|
329
|
-
|
|
330
|
-
[optional footer(s)]
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
**Types:**
|
|
334
|
-
|
|
335
|
-
- `feat`: New feature
|
|
336
|
-
- `fix`: Bug fix
|
|
337
|
-
- `docs`: Documentation changes
|
|
338
|
-
- `style`: Code style changes (formatting, semicolons, etc.)
|
|
339
|
-
- `refactor`: Code refactoring
|
|
340
|
-
- `perf`: Performance improvements
|
|
341
|
-
- `test`: Adding or updating tests
|
|
342
|
-
- `chore`: Maintenance tasks
|
|
343
|
-
- `ci`: CI/CD changes
|
|
344
75
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
```bash
|
|
348
|
-
git commit -m "feat: add support for Vimeo downloads"
|
|
349
|
-
git commit -m "fix: handle missing video URLs gracefully"
|
|
350
|
-
git commit -m "docs: update installation instructions"
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
### Releasing
|
|
354
|
-
|
|
355
|
-
Releases are managed with [release-it](https://github.com/release-it/release-it). The release process:
|
|
356
|
-
|
|
357
|
-
1. Runs linting, type checking, and tests
|
|
358
|
-
2. Bumps version based on conventional commits
|
|
359
|
-
3. Generates/updates `CHANGELOG.md`
|
|
360
|
-
4. Creates a Git tag and GitHub release
|
|
361
|
-
5. Publishes to npm
|
|
362
|
-
|
|
363
|
-
```bash
|
|
364
|
-
# Interactive release (will prompt for version bump)
|
|
365
|
-
npm run release
|
|
366
|
-
|
|
367
|
-
# Dry run (preview what would happen)
|
|
368
|
-
npm run release -- --dry-run
|
|
76
|
+
### Adding a New Platform
|
|
369
77
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
78
|
+
1. Create `src/scraper/newplatform/` with:
|
|
79
|
+
- `auth.ts` β Session detection
|
|
80
|
+
- `navigator.ts` β Course structure
|
|
81
|
+
- `extractor.ts` β Content extraction
|
|
82
|
+
- `schemas.ts` β Zod schemas
|
|
83
|
+
2. Add CLI command in `src/cli/commands/`
|
|
84
|
+
3. Register in `src/cli/index.ts`
|
|
374
85
|
|
|
375
86
|
## Acknowledgments
|
|
376
87
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
- [`@sindresorhus/slugify`](https://github.com/sindresorhus/slugify) β Slugify a string
|
|
380
|
-
- [`conf`](https://github.com/sindresorhus/conf) β Simple config handling
|
|
381
|
-
- [`delay`](https://github.com/sindresorhus/delay) β Delay a promise
|
|
382
|
-
- [`execa`](https://github.com/sindresorhus/execa) β Process execution for humans
|
|
383
|
-
- [`ky`](https://github.com/sindresorhus/ky) β Tiny & elegant HTTP client
|
|
384
|
-
- [`p-queue`](https://github.com/sindresorhus/p-queue) β Promise queue with concurrency control
|
|
385
|
-
- [`p-retry`](https://github.com/sindresorhus/p-retry) β Retry a promise-returning function
|
|
386
|
-
|
|
387
|
-
His commitment to high-quality, well-documented, and beautifully designed open source software is truly inspiring. If you find his work useful, consider [sponsoring him](https://github.com/sponsors/sindresorhus).
|
|
88
|
+
Thanks to [Sindre Sorhus](https://github.com/sindresorhus) for the excellent packages powering this project: `slugify`, `conf`, `delay`, `execa`, `ky`, `p-queue`, `p-retry`.
|
|
388
89
|
|
|
389
90
|
## License
|
|
390
91
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"syncLearningSuite.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/syncLearningSuite.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"syncLearningSuite.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/syncLearningSuite.ts"],"names":[],"mappings":"AA4EA,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,4BAA4B;IAC3C,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAaD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,0BAKnC,CAAC;AAoEH;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED;;;GAGG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,IAAI,CAAC,CA+Wf;AAoID;;GAEG;AACH,wBAAgB,2BAA2B,CACzC,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,GAAG,IAAI,EAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,GACzB,MAAM,CAkCR;AA8BD;;GAEG;AACH,wBAAsB,4BAA4B,CAChD,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,IAAI,CAAC,CAqQf"}
|
|
@@ -5,7 +5,7 @@ import { join } from "node:path";
|
|
|
5
5
|
import { loadConfig } from "../../config/configManager.js";
|
|
6
6
|
import { downloadVideo } from "../../downloader/index.js";
|
|
7
7
|
import { getAuthenticatedSession, createLoginChecker } from "../../shared/auth.js";
|
|
8
|
-
import { buildLearningSuiteCourseStructure, createFolderName, extractLearningSuitePostContent, getLearningSuiteLessonUrl, slugify, } from "../../scraper/learningsuite/index.js";
|
|
8
|
+
import { buildLearningSuiteCourseStructure, createFolderName, extractLearningSuitePostContent, getAuthToken, getLearningSuiteLessonUrl, slugify, } from "../../scraper/learningsuite/index.js";
|
|
9
9
|
import { createCourseDirectory, createModuleDirectory, getVideoPath, saveMarkdown, isLessonSynced, downloadFile, } from "../../storage/fileSystem.js";
|
|
10
10
|
/**
|
|
11
11
|
* Tracks if shutdown has been requested (Ctrl+C).
|
|
@@ -325,11 +325,12 @@ export async function syncLearningSuiteCommand(url, options) {
|
|
|
325
325
|
}
|
|
326
326
|
// Queue video download
|
|
327
327
|
if (!options.skipVideos && !syncStatus.video && content.video?.url) {
|
|
328
|
+
const videoUrl = content.video.hlsUrl ?? content.video.url;
|
|
328
329
|
videoTasks.push({
|
|
329
330
|
lessonId: lesson.id,
|
|
330
331
|
lessonName: lesson.title,
|
|
331
|
-
videoUrl
|
|
332
|
-
videoType: mapVideoType(content.video.type),
|
|
332
|
+
videoUrl,
|
|
333
|
+
videoType: mapVideoType(content.video.type, videoUrl),
|
|
333
334
|
outputPath: getVideoPath(moduleDir, lessonIndex, lesson.title),
|
|
334
335
|
preferredQuality: options.quality,
|
|
335
336
|
});
|
|
@@ -350,11 +351,12 @@ export async function syncLearningSuiteCommand(url, options) {
|
|
|
350
351
|
lesson.id);
|
|
351
352
|
const content = await extractLearningSuitePostContent(session.page, lessonUrl, courseStructure.tenantId, courseStructure.course.id, lesson.id);
|
|
352
353
|
if (content?.video?.url) {
|
|
354
|
+
const videoUrl = content.video.hlsUrl ?? content.video.url;
|
|
353
355
|
videoTasks.push({
|
|
354
356
|
lessonId: lesson.id,
|
|
355
357
|
lessonName: lesson.title,
|
|
356
|
-
videoUrl
|
|
357
|
-
videoType: mapVideoType(content.video.type),
|
|
358
|
+
videoUrl,
|
|
359
|
+
videoType: mapVideoType(content.video.type, videoUrl),
|
|
358
360
|
outputPath: getVideoPath(moduleDir, lessonIndex, lesson.title),
|
|
359
361
|
preferredQuality: options.quality,
|
|
360
362
|
});
|
|
@@ -382,31 +384,54 @@ export async function syncLearningSuiteCommand(url, options) {
|
|
|
382
384
|
console.log(` Content: ${contentParts.join(", ")}`);
|
|
383
385
|
// Phase 3: Download videos
|
|
384
386
|
if (!options.skipVideos && videoTasks.length > 0) {
|
|
385
|
-
// Extract cookies from session for authenticated video downloads
|
|
387
|
+
// Extract cookies and auth token from session for authenticated video downloads
|
|
386
388
|
const browserCookies = await session.page.context().cookies();
|
|
387
389
|
const cookieString = browserCookies.map((c) => `${c.name}=${c.value}`).join("; ");
|
|
388
390
|
const refererUrl = `https://${courseStructure.domain}/`;
|
|
389
|
-
|
|
391
|
+
const authToken = await getAuthToken(session.page);
|
|
392
|
+
// Add cookies, referer, and auth token to all video tasks
|
|
390
393
|
for (const task of videoTasks) {
|
|
391
394
|
task.cookies = cookieString;
|
|
392
395
|
task.referer = refererUrl;
|
|
396
|
+
if (authToken) {
|
|
397
|
+
task.authToken = authToken;
|
|
398
|
+
}
|
|
393
399
|
}
|
|
394
400
|
await downloadVideos(videoTasks, config);
|
|
395
401
|
}
|
|
396
402
|
console.log(chalk.green("\nβ
Sync complete!\n"));
|
|
397
403
|
console.log(chalk.gray(` Output: ${courseDir}\n`));
|
|
398
404
|
}
|
|
405
|
+
catch (error) {
|
|
406
|
+
console.error(chalk.red("\nβ Sync failed"));
|
|
407
|
+
if (error instanceof Error) {
|
|
408
|
+
console.error(chalk.gray(` Error: ${error.message}`));
|
|
409
|
+
}
|
|
410
|
+
throw error;
|
|
411
|
+
}
|
|
399
412
|
finally {
|
|
400
|
-
|
|
413
|
+
// Always close the browser to prevent hanging
|
|
414
|
+
if (browser) {
|
|
415
|
+
try {
|
|
416
|
+
await browser.close();
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// Ignore errors during browser close
|
|
420
|
+
}
|
|
421
|
+
}
|
|
401
422
|
}
|
|
402
423
|
}
|
|
403
424
|
/**
|
|
404
425
|
* Maps LearningSuite video type to downloader video type.
|
|
405
426
|
*/
|
|
406
|
-
function mapVideoType(type) {
|
|
427
|
+
function mapVideoType(type, url) {
|
|
428
|
+
// Special case: segments:... URLs use direct HLS segment download
|
|
429
|
+
if (url?.startsWith("segments:")) {
|
|
430
|
+
return "hls";
|
|
431
|
+
}
|
|
407
432
|
switch (type) {
|
|
408
433
|
case "hls":
|
|
409
|
-
return "highlevel"; // Use HLS downloader
|
|
434
|
+
return "highlevel"; // Use HLS downloader for standard HLS
|
|
410
435
|
case "vimeo":
|
|
411
436
|
return "vimeo";
|
|
412
437
|
case "loom":
|
|
@@ -758,8 +783,23 @@ export async function completeLearningSuiteCommand(url, options) {
|
|
|
758
783
|
}
|
|
759
784
|
console.log(chalk.green("β
Complete finished!\n"));
|
|
760
785
|
}
|
|
786
|
+
catch (error) {
|
|
787
|
+
console.error(chalk.red("\nβ Complete failed"));
|
|
788
|
+
if (error instanceof Error) {
|
|
789
|
+
console.error(chalk.gray(` Error: ${error.message}`));
|
|
790
|
+
}
|
|
791
|
+
throw error;
|
|
792
|
+
}
|
|
761
793
|
finally {
|
|
762
|
-
|
|
794
|
+
// Always close the browser to prevent hanging
|
|
795
|
+
if (browser) {
|
|
796
|
+
try {
|
|
797
|
+
await browser.close();
|
|
798
|
+
}
|
|
799
|
+
catch {
|
|
800
|
+
// Ignore errors during browser close
|
|
801
|
+
}
|
|
802
|
+
}
|
|
763
803
|
}
|
|
764
804
|
}
|
|
765
805
|
//# sourceMappingURL=syncLearningSuite.js.map
|