offcourse 0.0.2 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +255 -20
  2. package/dist/cli/commands/config.d.ts +13 -0
  3. package/dist/cli/commands/config.d.ts.map +1 -0
  4. package/dist/cli/commands/config.js +66 -0
  5. package/dist/cli/commands/config.js.map +1 -0
  6. package/dist/cli/commands/inspect.d.ts +11 -0
  7. package/dist/cli/commands/inspect.d.ts.map +1 -0
  8. package/dist/cli/commands/inspect.js +365 -0
  9. package/dist/cli/commands/inspect.js.map +1 -0
  10. package/dist/cli/commands/login.d.ts +12 -0
  11. package/dist/cli/commands/login.d.ts.map +1 -0
  12. package/dist/cli/commands/login.js +55 -0
  13. package/dist/cli/commands/login.js.map +1 -0
  14. package/dist/cli/commands/status.d.ts +15 -0
  15. package/dist/cli/commands/status.d.ts.map +1 -0
  16. package/dist/cli/commands/status.js +118 -0
  17. package/dist/cli/commands/status.js.map +1 -0
  18. package/dist/cli/commands/sync.d.ts +15 -0
  19. package/dist/cli/commands/sync.d.ts.map +1 -0
  20. package/dist/cli/commands/sync.js +921 -0
  21. package/dist/cli/commands/sync.js.map +1 -0
  22. package/dist/cli/commands/syncHighLevel.d.ts +23 -0
  23. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  24. package/dist/cli/commands/syncHighLevel.js +479 -0
  25. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  26. package/dist/cli/index.d.ts +3 -0
  27. package/dist/cli/index.d.ts.map +1 -0
  28. package/dist/cli/index.js +106 -0
  29. package/dist/cli/index.js.map +1 -0
  30. package/dist/config/configManager.d.ts +31 -0
  31. package/dist/config/configManager.d.ts.map +1 -0
  32. package/dist/config/configManager.js +68 -0
  33. package/dist/config/configManager.js.map +1 -0
  34. package/dist/config/paths.d.ts +21 -0
  35. package/dist/config/paths.d.ts.map +1 -0
  36. package/dist/config/paths.js +33 -0
  37. package/dist/config/paths.js.map +1 -0
  38. package/dist/config/schema.d.ts +60 -0
  39. package/dist/config/schema.d.ts.map +1 -0
  40. package/dist/config/schema.js +50 -0
  41. package/dist/config/schema.js.map +1 -0
  42. package/dist/downloader/hlsDownloader.d.ts +58 -0
  43. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  44. package/dist/downloader/hlsDownloader.js +263 -0
  45. package/dist/downloader/hlsDownloader.js.map +1 -0
  46. package/dist/downloader/hlsValidator.d.ts +35 -0
  47. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  48. package/dist/downloader/hlsValidator.js +152 -0
  49. package/dist/downloader/hlsValidator.js.map +1 -0
  50. package/dist/downloader/index.d.ts +29 -0
  51. package/dist/downloader/index.d.ts.map +1 -0
  52. package/dist/downloader/index.js +55 -0
  53. package/dist/downloader/index.js.map +1 -0
  54. package/dist/downloader/loomDownloader.d.ts +56 -0
  55. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  56. package/dist/downloader/loomDownloader.js +562 -0
  57. package/dist/downloader/loomDownloader.js.map +1 -0
  58. package/dist/downloader/queue.d.ts +56 -0
  59. package/dist/downloader/queue.d.ts.map +1 -0
  60. package/dist/downloader/queue.js +88 -0
  61. package/dist/downloader/queue.js.map +1 -0
  62. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  63. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  64. package/dist/downloader/vimeoDownloader.js +569 -0
  65. package/dist/downloader/vimeoDownloader.js.map +1 -0
  66. package/dist/scraper/extractor.d.ts +53 -0
  67. package/dist/scraper/extractor.d.ts.map +1 -0
  68. package/dist/scraper/extractor.js +627 -0
  69. package/dist/scraper/extractor.js.map +1 -0
  70. package/dist/scraper/highlevel/extractor.d.ts +89 -0
  71. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  72. package/dist/scraper/highlevel/extractor.js +373 -0
  73. package/dist/scraper/highlevel/extractor.js.map +1 -0
  74. package/dist/scraper/highlevel/index.d.ts +3 -0
  75. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  76. package/dist/scraper/highlevel/index.js +3 -0
  77. package/dist/scraper/highlevel/index.js.map +1 -0
  78. package/dist/scraper/highlevel/navigator.d.ts +86 -0
  79. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  80. package/dist/scraper/highlevel/navigator.js +505 -0
  81. package/dist/scraper/highlevel/navigator.js.map +1 -0
  82. package/dist/scraper/highlevel/schemas.d.ts +188 -0
  83. package/dist/scraper/highlevel/schemas.d.ts.map +1 -0
  84. package/dist/scraper/highlevel/schemas.js +139 -0
  85. package/dist/scraper/highlevel/schemas.js.map +1 -0
  86. package/dist/scraper/navigator.d.ts +68 -0
  87. package/dist/scraper/navigator.d.ts.map +1 -0
  88. package/dist/scraper/navigator.js +257 -0
  89. package/dist/scraper/navigator.js.map +1 -0
  90. package/dist/scraper/schemas.d.ts +57 -0
  91. package/dist/scraper/schemas.d.ts.map +1 -0
  92. package/dist/scraper/schemas.js +135 -0
  93. package/dist/scraper/schemas.js.map +1 -0
  94. package/dist/scraper/videoInterceptor.d.ts +23 -0
  95. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  96. package/dist/scraper/videoInterceptor.js +330 -0
  97. package/dist/scraper/videoInterceptor.js.map +1 -0
  98. package/dist/shared/auth.d.ts +58 -0
  99. package/dist/shared/auth.d.ts.map +1 -0
  100. package/dist/shared/auth.js +197 -0
  101. package/dist/shared/auth.js.map +1 -0
  102. package/dist/shared/firebase.d.ts +60 -0
  103. package/dist/shared/firebase.d.ts.map +1 -0
  104. package/dist/shared/firebase.js +102 -0
  105. package/dist/shared/firebase.js.map +1 -0
  106. package/dist/shared/fs.d.ts +31 -0
  107. package/dist/shared/fs.d.ts.map +1 -0
  108. package/dist/shared/fs.js +77 -0
  109. package/dist/shared/fs.js.map +1 -0
  110. package/dist/shared/http.d.ts +15 -0
  111. package/dist/shared/http.d.ts.map +1 -0
  112. package/dist/shared/http.js +31 -0
  113. package/dist/shared/http.js.map +1 -0
  114. package/dist/shared/index.d.ts +7 -0
  115. package/dist/shared/index.d.ts.map +1 -0
  116. package/dist/shared/index.js +7 -0
  117. package/dist/shared/index.js.map +1 -0
  118. package/dist/shared/slug.d.ts +11 -0
  119. package/dist/shared/slug.d.ts.map +1 -0
  120. package/dist/shared/slug.js +25 -0
  121. package/dist/shared/slug.js.map +1 -0
  122. package/dist/shared/url.d.ts +43 -0
  123. package/dist/shared/url.d.ts.map +1 -0
  124. package/dist/shared/url.js +54 -0
  125. package/dist/shared/url.js.map +1 -0
  126. package/dist/state/database.d.ts +246 -0
  127. package/dist/state/database.d.ts.map +1 -0
  128. package/dist/state/database.js +679 -0
  129. package/dist/state/database.js.map +1 -0
  130. package/dist/state/index.d.ts +2 -0
  131. package/dist/state/index.d.ts.map +1 -0
  132. package/dist/state/index.js +2 -0
  133. package/dist/state/index.js.map +1 -0
  134. package/dist/storage/fileSystem.d.ts +56 -0
  135. package/dist/storage/fileSystem.d.ts.map +1 -0
  136. package/dist/storage/fileSystem.js +129 -0
  137. package/dist/storage/fileSystem.js.map +1 -0
  138. package/package.json +71 -11
  139. package/cli.js +0 -45
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import { configGetCommand, configSetCommand, configShowCommand } from "./commands/config.js";
4
+ import { inspectCommand } from "./commands/inspect.js";
5
+ import { loginCommand, logoutCommand } from "./commands/login.js";
6
+ import { statusCommand, statusListCommand } from "./commands/status.js";
7
+ import { syncCommand } from "./commands/sync.js";
8
+ import { syncHighLevelCommand, isHighLevelPortal, } from "./commands/syncHighLevel.js";
9
+ const program = new Command();
10
+ program
11
+ .name("offcourse")
12
+ .description("Download online courses for offline access – of course!")
13
+ .version("0.1.0");
14
+ // Login command
15
+ program
16
+ .command("login")
17
+ .description("Log in to a learning platform (opens browser)")
18
+ .option("-f, --force", "Force re-login even if session exists")
19
+ .action(loginCommand);
20
+ // Logout command
21
+ program.command("logout").description("Clear saved session").action(logoutCommand);
22
+ // Sync command - auto-detects platform
23
+ program
24
+ .command("sync <url>")
25
+ .description("Download a course for offline access (auto-detects platform)")
26
+ .option("--skip-videos", "Skip video downloads (only save text content)")
27
+ .option("--skip-content", "Skip text content (only download videos)")
28
+ .option("--dry-run", "Scan course structure without downloading")
29
+ .option("--limit <n>", "Limit to first N lessons (for testing)", parseInt)
30
+ .option("-f, --force", "Force full rescan of all lessons")
31
+ .option("--retry-failed", "Retry failed lessons with detailed diagnostics")
32
+ .option("--visible", "Show browser window (default: headless)")
33
+ .option("-q, --quality <quality>", "Preferred video quality (e.g., 720p, 1080p)")
34
+ .option("--course-name <name>", "Override detected course name")
35
+ .action((url, options) => {
36
+ // Auto-detect platform
37
+ if (url.includes("skool.com")) {
38
+ return syncCommand(url, options);
39
+ }
40
+ else if (isHighLevelPortal(url)) {
41
+ return syncHighLevelCommand(url, options);
42
+ }
43
+ else {
44
+ // Default: try HighLevel (most generic)
45
+ console.log("Platform not recognized, trying as HighLevel portal...");
46
+ return syncHighLevelCommand(url, options);
47
+ }
48
+ });
49
+ // Explicit Skool sync command
50
+ program
51
+ .command("sync-skool <url>")
52
+ .description("Download a Skool course for offline access")
53
+ .option("--skip-videos", "Skip video downloads (only save text content)")
54
+ .option("--skip-content", "Skip text content (only download videos)")
55
+ .option("--dry-run", "Scan course structure without downloading")
56
+ .option("--limit <n>", "Limit to first N lessons (for testing)", parseInt)
57
+ .option("-f, --force", "Force full rescan of all lessons")
58
+ .option("--retry-failed", "Retry failed lessons with detailed diagnostics")
59
+ .option("--visible", "Show browser window (default: headless)")
60
+ .action(syncCommand);
61
+ // Explicit HighLevel sync command
62
+ program
63
+ .command("sync-highlevel <url>")
64
+ .description("Download a HighLevel (GoHighLevel) course for offline access")
65
+ .option("--skip-videos", "Skip video downloads (only save text content)")
66
+ .option("--skip-content", "Skip text content (only download videos)")
67
+ .option("--dry-run", "Scan course structure without downloading")
68
+ .option("--limit <n>", "Limit to first N lessons (for testing)", parseInt)
69
+ .option("--visible", "Show browser window (default: headless)")
70
+ .option("-q, --quality <quality>", "Preferred video quality (e.g., 720p, 1080p)")
71
+ .option("--course-name <name>", "Override detected course name")
72
+ .action(syncHighLevelCommand);
73
+ // Status command
74
+ program
75
+ .command("status [url]")
76
+ .description("Show sync status for a course (or list all if no URL)")
77
+ .option("--errors", "Show details for failed downloads")
78
+ .option("--pending", "Show not-yet-scanned lessons")
79
+ .option("-a, --all", "Show all details")
80
+ .action((url, options) => {
81
+ if (url) {
82
+ statusCommand(url, options);
83
+ }
84
+ else {
85
+ void statusListCommand();
86
+ }
87
+ });
88
+ // Inspect command (debugging)
89
+ program
90
+ .command("inspect <url>")
91
+ .description("Analyze page structure for debugging")
92
+ .option("-o, --output <dir>", "Save analysis to directory")
93
+ .option("--full", "Save complete HTML as well")
94
+ .option("--click", "Try to click video preview to trigger lazy loading")
95
+ .action(inspectCommand);
96
+ // Config commands
97
+ const configCmd = program.command("config").description("Manage configuration");
98
+ configCmd.command("show").description("Show all configuration values").action(configShowCommand);
99
+ configCmd.command("get <key>").description("Get a configuration value").action(configGetCommand);
100
+ configCmd
101
+ .command("set <key> <value>")
102
+ .description("Set a configuration value")
103
+ .action(configSetCommand);
104
+ // Parse and run
105
+ program.parse();
106
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ import { Config } from "./schema.js";
2
+ /**
3
+ * Ensures all required application directories exist.
4
+ */
5
+ export declare function ensureAppDirectories(): Promise<void>;
6
+ /**
7
+ * Loads the application configuration.
8
+ * Returns validated config with defaults applied.
9
+ */
10
+ export declare function loadConfig(): Config;
11
+ /**
12
+ * Saves the configuration.
13
+ */
14
+ export declare function saveConfig(config: Config): void;
15
+ /**
16
+ * Updates specific config values.
17
+ */
18
+ export declare function updateConfig(updates: Partial<Config>): Config;
19
+ /**
20
+ * Gets a specific config value.
21
+ */
22
+ export declare function getConfigValue<K extends keyof Config>(key: K): Config[K];
23
+ /**
24
+ * Clears all configuration (for testing or reset).
25
+ */
26
+ export declare function clearConfig(): void;
27
+ /**
28
+ * Gets the path to the config file.
29
+ */
30
+ export declare function getConfigPath(): string;
31
+ //# sourceMappingURL=configManager.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configManager.d.ts","sourceRoot":"","sources":["../../src/config/configManager.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,MAAM,EAAgB,MAAM,aAAa,CAAC;AAcnD;;GAEG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1D;AAED;;;GAGG;AACH,wBAAgB,UAAU,IAAI,MAAM,CAGnC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAG/C;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAK7D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAExE;AAED;;GAEG;AACH,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Application configuration using the `conf` package.
3
+ * Excluded from coverage - testing would just test the conf package.
4
+ */
5
+ import Conf from "conf";
6
+ import { APP_DIR, SESSIONS_DIR } from "./paths.js";
7
+ import { configSchema } from "./schema.js";
8
+ import { ensureDir } from "../shared/fs.js";
9
+ /**
10
+ * Application configuration store using conf package.
11
+ * Provides atomic writes, dot-notation access, and safe defaults.
12
+ */
13
+ const store = new Conf({
14
+ projectName: "offcourse",
15
+ cwd: APP_DIR,
16
+ configName: "config",
17
+ defaults: configSchema.parse({}),
18
+ });
19
+ /**
20
+ * Ensures all required application directories exist.
21
+ */
22
+ export async function ensureAppDirectories() {
23
+ const dirs = [APP_DIR, SESSIONS_DIR, `${APP_DIR}/sync-state`];
24
+ await Promise.all(dirs.map((dir) => ensureDir(dir)));
25
+ }
26
+ /**
27
+ * Loads the application configuration.
28
+ * Returns validated config with defaults applied.
29
+ */
30
+ export function loadConfig() {
31
+ // Validate with zod to ensure type safety
32
+ return configSchema.parse(store.store);
33
+ }
34
+ /**
35
+ * Saves the configuration.
36
+ */
37
+ export function saveConfig(config) {
38
+ const validated = configSchema.parse(config);
39
+ store.store = validated;
40
+ }
41
+ /**
42
+ * Updates specific config values.
43
+ */
44
+ export function updateConfig(updates) {
45
+ const current = loadConfig();
46
+ const updated = configSchema.parse({ ...current, ...updates });
47
+ store.store = updated;
48
+ return updated;
49
+ }
50
+ /**
51
+ * Gets a specific config value.
52
+ */
53
+ export function getConfigValue(key) {
54
+ return store.get(key);
55
+ }
56
+ /**
57
+ * Clears all configuration (for testing or reset).
58
+ */
59
+ export function clearConfig() {
60
+ store.clear();
61
+ }
62
+ /**
63
+ * Gets the path to the config file.
64
+ */
65
+ export function getConfigPath() {
66
+ return store.path;
67
+ }
68
+ //# sourceMappingURL=configManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"configManager.js","sourceRoot":"","sources":["../../src/config/configManager.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAU,YAAY,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C;;;GAGG;AACH,MAAM,KAAK,GAAG,IAAI,IAAI,CAAS;IAC7B,WAAW,EAAE,WAAW;IACxB,GAAG,EAAE,OAAO;IACZ,UAAU,EAAE,QAAQ;IACpB,QAAQ,EAAE,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;CACjC,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB;IACxC,MAAM,IAAI,GAAG,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,OAAO,aAAa,CAAC,CAAC;IAC9D,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACvD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU;IACxB,0CAA0C;IAC1C,OAAO,YAAY,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,MAAM,SAAS,GAAG,YAAY,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAC7C,KAAK,CAAC,KAAK,GAAG,SAAS,CAAC;AAC1B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAAwB;IACnD,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IAC/D,KAAK,CAAC,KAAK,GAAG,OAAO,CAAC;IACtB,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAyB,GAAM;IAC3D,OAAO,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;AACxB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,WAAW;IACzB,KAAK,CAAC,KAAK,EAAE,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,KAAK,CAAC,IAAI,CAAC;AACpB,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Application directory paths.
3
+ * Uses ~/.offcourse/ for easy access and visibility.
4
+ */
5
+ export declare const APP_DIR: string;
6
+ export declare const SESSIONS_DIR: string;
7
+ export declare const CONFIG_FILE: string;
8
+ export declare const CACHE_DIR: string;
9
+ /**
10
+ * Get the session file path for a specific domain.
11
+ */
12
+ export declare function getSessionPath(domain: string): string;
13
+ /**
14
+ * Get the sync state file path for a course.
15
+ */
16
+ export declare function getSyncStatePath(courseSlug: string): string;
17
+ /**
18
+ * Expand ~ to home directory in paths.
19
+ */
20
+ export declare function expandPath(path: string): string;
21
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAIA;;;GAGG;AACH,eAAO,MAAM,OAAO,QAAgC,CAAC;AACrD,eAAO,MAAM,YAAY,QAA4B,CAAC;AACtD,eAAO,MAAM,WAAW,QAA+B,CAAC;AACxD,eAAO,MAAM,SAAS,QAAyB,CAAC;AAEhD;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAG3D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C"}
@@ -0,0 +1,33 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import untildify from "untildify";
4
+ /**
5
+ * Application directory paths.
6
+ * Uses ~/.offcourse/ for easy access and visibility.
7
+ */
8
+ export const APP_DIR = join(homedir(), ".offcourse");
9
+ export const SESSIONS_DIR = join(APP_DIR, "sessions");
10
+ export const CONFIG_FILE = join(APP_DIR, "config.json");
11
+ export const CACHE_DIR = join(APP_DIR, "cache");
12
+ /**
13
+ * Get the session file path for a specific domain.
14
+ */
15
+ export function getSessionPath(domain) {
16
+ // Sanitize domain for filesystem
17
+ const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, "_");
18
+ return join(SESSIONS_DIR, `${safeDomain}.json`);
19
+ }
20
+ /**
21
+ * Get the sync state file path for a course.
22
+ */
23
+ export function getSyncStatePath(courseSlug) {
24
+ const safeSlug = courseSlug.replace(/[^a-zA-Z0-9-]/g, "_");
25
+ return join(APP_DIR, "sync-state", `${safeSlug}.json`);
26
+ }
27
+ /**
28
+ * Expand ~ to home directory in paths.
29
+ */
30
+ export function expandPath(path) {
31
+ return untildify(path);
32
+ }
33
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/config/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,SAAS,MAAM,WAAW,CAAC;AAElC;;;GAGG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,YAAY,CAAC,CAAC;AACrD,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,UAAU,CAAC,CAAC;AACtD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;AACxD,MAAM,CAAC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;AAEhD;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,MAAc;IAC3C,iCAAiC;IACjC,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,CAAC;IAC1D,OAAO,IAAI,CAAC,YAAY,EAAE,GAAG,UAAU,OAAO,CAAC,CAAC;AAClD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAAkB;IACjD,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;IAC3D,OAAO,IAAI,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,QAAQ,OAAO,CAAC,CAAC;AACzD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,IAAY;IACrC,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,60 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Video quality preferences for downloads.
4
+ */
5
+ export declare const VIDEO_QUALITY: {
6
+ readonly highest: "highest";
7
+ readonly lowest: "lowest";
8
+ readonly "1080p": "1080p";
9
+ readonly "720p": "720p";
10
+ readonly "480p": "480p";
11
+ };
12
+ export type VideoQuality = keyof typeof VIDEO_QUALITY;
13
+ /**
14
+ * Global application configuration schema.
15
+ */
16
+ export declare const configSchema: z.ZodObject<{
17
+ outputDir: z.ZodDefault<z.ZodString>;
18
+ videoQuality: z.ZodDefault<z.ZodEnum<{
19
+ highest: "highest";
20
+ lowest: "lowest";
21
+ "1080p": "1080p";
22
+ "720p": "720p";
23
+ "480p": "480p";
24
+ }>>;
25
+ concurrency: z.ZodDefault<z.ZodNumber>;
26
+ retryAttempts: z.ZodDefault<z.ZodNumber>;
27
+ headless: z.ZodDefault<z.ZodBoolean>;
28
+ }, z.core.$strip>;
29
+ export type Config = z.infer<typeof configSchema>;
30
+ /**
31
+ * Course sync state to track progress and enable resume.
32
+ */
33
+ export declare const courseSyncStateSchema: z.ZodObject<{
34
+ url: z.ZodURL;
35
+ name: z.ZodString;
36
+ lastSyncedAt: z.ZodOptional<z.ZodISODateTime>;
37
+ modules: z.ZodArray<z.ZodObject<{
38
+ name: z.ZodString;
39
+ slug: z.ZodString;
40
+ lessons: z.ZodArray<z.ZodObject<{
41
+ name: z.ZodString;
42
+ slug: z.ZodString;
43
+ url: z.ZodURL;
44
+ isCompleted: z.ZodDefault<z.ZodBoolean>;
45
+ videoDownloaded: z.ZodDefault<z.ZodBoolean>;
46
+ contentSaved: z.ZodDefault<z.ZodBoolean>;
47
+ }, z.core.$strip>>;
48
+ }, z.core.$strip>>;
49
+ }, z.core.$strip>;
50
+ export type CourseSyncState = z.infer<typeof courseSyncStateSchema>;
51
+ /**
52
+ * Session info for a specific domain.
53
+ */
54
+ export declare const sessionInfoSchema: z.ZodObject<{
55
+ domain: z.ZodString;
56
+ createdAt: z.ZodISODateTime;
57
+ expiresAt: z.ZodOptional<z.ZodISODateTime>;
58
+ }, z.core.$strip>;
59
+ export type SessionInfo = z.infer<typeof sessionInfoSchema>;
60
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,aAAa;;;;;;CAMhB,CAAC;AAEX,MAAM,MAAM,YAAY,GAAG,MAAM,OAAO,aAAa,CAAC;AAEtD;;GAEG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;iBAMvB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC;AAElD;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;iBAoBhC,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AAEpE;;GAEG;AACH,eAAO,MAAM,iBAAiB;;;;iBAI5B,CAAC;AAEH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Video quality preferences for downloads.
4
+ */
5
+ export const VIDEO_QUALITY = {
6
+ highest: "highest",
7
+ lowest: "lowest",
8
+ "1080p": "1080p",
9
+ "720p": "720p",
10
+ "480p": "480p",
11
+ };
12
+ /**
13
+ * Global application configuration schema.
14
+ */
15
+ export const configSchema = z.object({
16
+ outputDir: z.string().default("~/Downloads/offcourse"),
17
+ videoQuality: z.enum(["highest", "lowest", "1080p", "720p", "480p"]).default("highest"),
18
+ concurrency: z.number().int().min(1).max(5).default(2),
19
+ retryAttempts: z.number().int().min(0).max(10).default(3),
20
+ headless: z.boolean().default(true),
21
+ });
22
+ /**
23
+ * Course sync state to track progress and enable resume.
24
+ */
25
+ export const courseSyncStateSchema = z.object({
26
+ url: z.url(),
27
+ name: z.string(),
28
+ lastSyncedAt: z.iso.datetime().optional(),
29
+ modules: z.array(z.object({
30
+ name: z.string(),
31
+ slug: z.string(),
32
+ lessons: z.array(z.object({
33
+ name: z.string(),
34
+ slug: z.string(),
35
+ url: z.url(),
36
+ isCompleted: z.boolean().default(false),
37
+ videoDownloaded: z.boolean().default(false),
38
+ contentSaved: z.boolean().default(false),
39
+ })),
40
+ })),
41
+ });
42
+ /**
43
+ * Session info for a specific domain.
44
+ */
45
+ export const sessionInfoSchema = z.object({
46
+ domain: z.string(),
47
+ createdAt: z.iso.datetime(),
48
+ expiresAt: z.iso.datetime().optional(),
49
+ });
50
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG;IAC3B,OAAO,EAAE,SAAS;IAClB,MAAM,EAAE,QAAQ;IAChB,OAAO,EAAE,OAAO;IAChB,MAAM,EAAE,MAAM;IACd,MAAM,EAAE,MAAM;CACN,CAAC;AAIX;;GAEG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,uBAAuB,CAAC;IACtD,YAAY,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;IACvF,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACtD,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IACzD,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC;CACpC,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;IACZ,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,YAAY,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;IACzC,OAAO,EAAE,CAAC,CAAC,KAAK,CACd,CAAC,CAAC,MAAM,CAAC;QACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;QAChB,OAAO,EAAE,CAAC,CAAC,KAAK,CACd,CAAC,CAAC,MAAM,CAAC;YACP,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;YAChB,GAAG,EAAE,CAAC,CAAC,GAAG,EAAE;YACZ,WAAW,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;YACvC,eAAe,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;YAC3C,YAAY,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;SACzC,CAAC,CACH;KACF,CAAC,CACH;CACF,CAAC,CAAC;AAIH;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE;IAClB,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE;IAC3B,SAAS,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE;CACvC,CAAC,CAAC"}
@@ -0,0 +1,58 @@
1
+ import type { DownloadProgress } from "./loomDownloader.js";
2
+ export interface HLSDownloadResult {
3
+ success: boolean;
4
+ error?: string;
5
+ errorCode?: string;
6
+ outputPath?: string;
7
+ duration?: number;
8
+ }
9
+ export interface HLSQuality {
10
+ label: string;
11
+ url: string;
12
+ bandwidth: number;
13
+ width?: number | undefined;
14
+ height?: number | undefined;
15
+ }
16
+ /**
17
+ * Checks if ffmpeg is available on the system.
18
+ */
19
+ export declare function checkFfmpeg(): Promise<boolean>;
20
+ /**
21
+ * Fetches an HLS master playlist and parses quality variants.
22
+ */
23
+ export declare function fetchHLSQualities(masterUrl: string): Promise<HLSQuality[]>;
24
+ /**
25
+ * Parses an HLS master playlist to extract quality variants.
26
+ * Uses hls-parser for robust parsing.
27
+ */
28
+ export declare function parseHLSPlaylist(content: string, baseUrl: string): HLSQuality[];
29
+ /**
30
+ * Gets the best quality URL from a master playlist.
31
+ * @param masterUrl The master playlist URL
32
+ * @param preferredHeight Preferred video height (e.g., 720, 1080)
33
+ */
34
+ export declare function getBestQualityUrl(masterUrl: string, preferredHeight?: number): Promise<string>;
35
+ /**
36
+ * Downloads an HLS stream using ffmpeg.
37
+ * @param hlsUrl The HLS playlist URL (master or media)
38
+ * @param outputPath The output file path (should end in .mp4)
39
+ * @param onProgress Progress callback
40
+ */
41
+ export declare function downloadHLSVideo(hlsUrl: string, outputPath: string, onProgress?: (progress: DownloadProgress) => void): Promise<HLSDownloadResult>;
42
+ /**
43
+ * Downloads a HighLevel HLS video with quality selection.
44
+ * @param masterUrl The master playlist URL (may include token)
45
+ * @param outputPath The output file path
46
+ * @param preferredQuality Preferred quality label (e.g., "720p", "1080p")
47
+ * @param onProgress Progress callback
48
+ */
49
+ export declare function downloadHighLevelVideo(masterUrl: string, outputPath: string, preferredQuality?: string, onProgress?: (progress: DownloadProgress) => void): Promise<HLSDownloadResult>;
50
+ /**
51
+ * Extracts video info from a HighLevel HLS URL.
52
+ */
53
+ export declare function parseHighLevelVideoUrl(url: string): {
54
+ locationId: string;
55
+ videoId: string;
56
+ token?: string | undefined;
57
+ } | null;
58
+ //# sourceMappingURL=hlsDownloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hlsDownloader.d.ts","sourceRoot":"","sources":["../../src/downloader/hlsDownloader.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED;;GAEG;AAEH,wBAAsB,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC,CAOpD;AAED;;GAEG;AAEH,wBAAsB,iBAAiB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAahF;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,EAAE,CAuC/E;AAED;;;;GAIG;AAEH,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,MAAM,CAAC,CAuBjB;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAChD,OAAO,CAAC,iBAAiB,CAAC,CA8G5B;AAED;;;;;;GAMG;AACH,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,EAClB,gBAAgB,CAAC,EAAE,MAAM,EACzB,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,gBAAgB,KAAK,IAAI,GAChD,OAAO,CAAC,iBAAiB,CAAC,CA0B5B;AAGD;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG;IACnD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B,GAAG,IAAI,CAuBP"}