offcourse 0.0.2 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (284) hide show
  1. package/.github/workflows/ci.yml +50 -0
  2. package/.husky/commit-msg +2 -0
  3. package/.husky/pre-commit +1 -0
  4. package/.husky/pre-push +3 -0
  5. package/.prettierrc +8 -0
  6. package/.release-it.json +23 -0
  7. package/ARCHITECTURE.md +233 -0
  8. package/CHANGELOG.md +78 -0
  9. package/README.md +255 -20
  10. package/commitlint.config.js +4 -0
  11. package/dist/ai/openRouter.d.ts +47 -0
  12. package/dist/ai/openRouter.d.ts.map +1 -0
  13. package/dist/ai/openRouter.js +116 -0
  14. package/dist/ai/openRouter.js.map +1 -0
  15. package/dist/ai/transcriptPolisher.d.ts +24 -0
  16. package/dist/ai/transcriptPolisher.d.ts.map +1 -0
  17. package/dist/ai/transcriptPolisher.js +89 -0
  18. package/dist/ai/transcriptPolisher.js.map +1 -0
  19. package/dist/cli/commands/config.d.ts +13 -0
  20. package/dist/cli/commands/config.d.ts.map +1 -0
  21. package/dist/cli/commands/config.js +66 -0
  22. package/dist/cli/commands/config.js.map +1 -0
  23. package/dist/cli/commands/enrich.d.ts +14 -0
  24. package/dist/cli/commands/enrich.d.ts.map +1 -0
  25. package/dist/cli/commands/enrich.js +271 -0
  26. package/dist/cli/commands/enrich.js.map +1 -0
  27. package/dist/cli/commands/inspect.d.ts +11 -0
  28. package/dist/cli/commands/inspect.d.ts.map +1 -0
  29. package/dist/cli/commands/inspect.js +365 -0
  30. package/dist/cli/commands/inspect.js.map +1 -0
  31. package/dist/cli/commands/login.d.ts +12 -0
  32. package/dist/cli/commands/login.d.ts.map +1 -0
  33. package/dist/cli/commands/login.js +55 -0
  34. package/dist/cli/commands/login.js.map +1 -0
  35. package/dist/cli/commands/status.d.ts +15 -0
  36. package/dist/cli/commands/status.d.ts.map +1 -0
  37. package/dist/cli/commands/status.js +118 -0
  38. package/dist/cli/commands/status.js.map +1 -0
  39. package/dist/cli/commands/sync.d.ts +16 -0
  40. package/dist/cli/commands/sync.d.ts.map +1 -0
  41. package/dist/cli/commands/sync.js +922 -0
  42. package/dist/cli/commands/sync.js.map +1 -0
  43. package/dist/cli/commands/syncGhl.d.ts +20 -0
  44. package/dist/cli/commands/syncGhl.d.ts.map +1 -0
  45. package/dist/cli/commands/syncGhl.js +483 -0
  46. package/dist/cli/commands/syncGhl.js.map +1 -0
  47. package/dist/cli/commands/syncHighLevel.d.ts +24 -0
  48. package/dist/cli/commands/syncHighLevel.d.ts.map +1 -0
  49. package/dist/cli/commands/syncHighLevel.js +483 -0
  50. package/dist/cli/commands/syncHighLevel.js.map +1 -0
  51. package/dist/cli/commands/syncHighLevel.test.d.ts +2 -0
  52. package/dist/cli/commands/syncHighLevel.test.d.ts.map +1 -0
  53. package/dist/cli/commands/syncHighLevel.test.js +102 -0
  54. package/dist/cli/commands/syncHighLevel.test.js.map +1 -0
  55. package/dist/cli/index.d.ts +3 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +106 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/config/configManager.d.ts +31 -0
  60. package/dist/config/configManager.d.ts.map +1 -0
  61. package/dist/config/configManager.js +64 -0
  62. package/dist/config/configManager.js.map +1 -0
  63. package/dist/config/paths.d.ts +21 -0
  64. package/dist/config/paths.d.ts.map +1 -0
  65. package/dist/config/paths.js +33 -0
  66. package/dist/config/paths.js.map +1 -0
  67. package/dist/config/paths.test.d.ts +2 -0
  68. package/dist/config/paths.test.d.ts.map +1 -0
  69. package/dist/config/paths.test.js +70 -0
  70. package/dist/config/paths.test.js.map +1 -0
  71. package/dist/config/schema.d.ts +60 -0
  72. package/dist/config/schema.d.ts.map +1 -0
  73. package/dist/config/schema.js +50 -0
  74. package/dist/config/schema.js.map +1 -0
  75. package/dist/config/schema.test.d.ts +2 -0
  76. package/dist/config/schema.test.d.ts.map +1 -0
  77. package/dist/config/schema.test.js +151 -0
  78. package/dist/config/schema.test.js.map +1 -0
  79. package/dist/downloader/hlsDownloader.d.ts +58 -0
  80. package/dist/downloader/hlsDownloader.d.ts.map +1 -0
  81. package/dist/downloader/hlsDownloader.js +254 -0
  82. package/dist/downloader/hlsDownloader.js.map +1 -0
  83. package/dist/downloader/hlsDownloader.test.d.ts +2 -0
  84. package/dist/downloader/hlsDownloader.test.d.ts.map +1 -0
  85. package/dist/downloader/hlsDownloader.test.js +116 -0
  86. package/dist/downloader/hlsDownloader.test.js.map +1 -0
  87. package/dist/downloader/hlsValidator.d.ts +35 -0
  88. package/dist/downloader/hlsValidator.d.ts.map +1 -0
  89. package/dist/downloader/hlsValidator.js +148 -0
  90. package/dist/downloader/hlsValidator.js.map +1 -0
  91. package/dist/downloader/index.d.ts +26 -0
  92. package/dist/downloader/index.d.ts.map +1 -0
  93. package/dist/downloader/index.js +52 -0
  94. package/dist/downloader/index.js.map +1 -0
  95. package/dist/downloader/loomDownloader.d.ts +56 -0
  96. package/dist/downloader/loomDownloader.d.ts.map +1 -0
  97. package/dist/downloader/loomDownloader.js +559 -0
  98. package/dist/downloader/loomDownloader.js.map +1 -0
  99. package/dist/downloader/loomDownloader.test.d.ts +2 -0
  100. package/dist/downloader/loomDownloader.test.d.ts.map +1 -0
  101. package/dist/downloader/loomDownloader.test.js +36 -0
  102. package/dist/downloader/loomDownloader.test.js.map +1 -0
  103. package/dist/downloader/queue.d.ts +56 -0
  104. package/dist/downloader/queue.d.ts.map +1 -0
  105. package/dist/downloader/queue.js +88 -0
  106. package/dist/downloader/queue.js.map +1 -0
  107. package/dist/downloader/queue.test.d.ts +2 -0
  108. package/dist/downloader/queue.test.d.ts.map +1 -0
  109. package/dist/downloader/queue.test.js +158 -0
  110. package/dist/downloader/queue.test.js.map +1 -0
  111. package/dist/downloader/videoDownloader.d.ts +32 -0
  112. package/dist/downloader/videoDownloader.d.ts.map +1 -0
  113. package/dist/downloader/videoDownloader.js +173 -0
  114. package/dist/downloader/videoDownloader.js.map +1 -0
  115. package/dist/downloader/vimeoDownloader.d.ts +52 -0
  116. package/dist/downloader/vimeoDownloader.d.ts.map +1 -0
  117. package/dist/downloader/vimeoDownloader.js +565 -0
  118. package/dist/downloader/vimeoDownloader.js.map +1 -0
  119. package/dist/downloader/vimeoDownloader.test.d.ts +2 -0
  120. package/dist/downloader/vimeoDownloader.test.d.ts.map +1 -0
  121. package/dist/downloader/vimeoDownloader.test.js +51 -0
  122. package/dist/downloader/vimeoDownloader.test.js.map +1 -0
  123. package/dist/scraper/auth.d.ts +29 -0
  124. package/dist/scraper/auth.d.ts.map +1 -0
  125. package/dist/scraper/auth.js +115 -0
  126. package/dist/scraper/auth.js.map +1 -0
  127. package/dist/scraper/extractor.d.ts +49 -0
  128. package/dist/scraper/extractor.d.ts.map +1 -0
  129. package/dist/scraper/extractor.js +627 -0
  130. package/dist/scraper/extractor.js.map +1 -0
  131. package/dist/scraper/extractor.test.d.ts +2 -0
  132. package/dist/scraper/extractor.test.d.ts.map +1 -0
  133. package/dist/scraper/extractor.test.js +65 -0
  134. package/dist/scraper/extractor.test.js.map +1 -0
  135. package/dist/scraper/ghl/auth.d.ts +25 -0
  136. package/dist/scraper/ghl/auth.d.ts.map +1 -0
  137. package/dist/scraper/ghl/auth.js +187 -0
  138. package/dist/scraper/ghl/auth.js.map +1 -0
  139. package/dist/scraper/ghl/extractor.d.ts +96 -0
  140. package/dist/scraper/ghl/extractor.d.ts.map +1 -0
  141. package/dist/scraper/ghl/extractor.js +345 -0
  142. package/dist/scraper/ghl/extractor.js.map +1 -0
  143. package/dist/scraper/ghl/index.d.ts +4 -0
  144. package/dist/scraper/ghl/index.d.ts.map +1 -0
  145. package/dist/scraper/ghl/index.js +4 -0
  146. package/dist/scraper/ghl/index.js.map +1 -0
  147. package/dist/scraper/ghl/navigator.d.ts +93 -0
  148. package/dist/scraper/ghl/navigator.d.ts.map +1 -0
  149. package/dist/scraper/ghl/navigator.js +447 -0
  150. package/dist/scraper/ghl/navigator.js.map +1 -0
  151. package/dist/scraper/highlevel/auth.d.ts +25 -0
  152. package/dist/scraper/highlevel/auth.d.ts.map +1 -0
  153. package/dist/scraper/highlevel/auth.js +189 -0
  154. package/dist/scraper/highlevel/auth.js.map +1 -0
  155. package/dist/scraper/highlevel/extractor.d.ts +97 -0
  156. package/dist/scraper/highlevel/extractor.d.ts.map +1 -0
  157. package/dist/scraper/highlevel/extractor.js +386 -0
  158. package/dist/scraper/highlevel/extractor.js.map +1 -0
  159. package/dist/scraper/highlevel/extractor.test.d.ts +2 -0
  160. package/dist/scraper/highlevel/extractor.test.d.ts.map +1 -0
  161. package/dist/scraper/highlevel/extractor.test.js +101 -0
  162. package/dist/scraper/highlevel/extractor.test.js.map +1 -0
  163. package/dist/scraper/highlevel/index.d.ts +3 -0
  164. package/dist/scraper/highlevel/index.d.ts.map +1 -0
  165. package/dist/scraper/highlevel/index.js +3 -0
  166. package/dist/scraper/highlevel/index.js.map +1 -0
  167. package/dist/scraper/highlevel/navigator.d.ts +93 -0
  168. package/dist/scraper/highlevel/navigator.d.ts.map +1 -0
  169. package/dist/scraper/highlevel/navigator.js +492 -0
  170. package/dist/scraper/highlevel/navigator.js.map +1 -0
  171. package/dist/scraper/highlevel/navigator.test.d.ts +2 -0
  172. package/dist/scraper/highlevel/navigator.test.d.ts.map +1 -0
  173. package/dist/scraper/highlevel/navigator.test.js +78 -0
  174. package/dist/scraper/highlevel/navigator.test.js.map +1 -0
  175. package/dist/scraper/navigator.d.ts +65 -0
  176. package/dist/scraper/navigator.d.ts.map +1 -0
  177. package/dist/scraper/navigator.js +300 -0
  178. package/dist/scraper/navigator.js.map +1 -0
  179. package/dist/scraper/navigator.test.d.ts +2 -0
  180. package/dist/scraper/navigator.test.d.ts.map +1 -0
  181. package/dist/scraper/navigator.test.js +63 -0
  182. package/dist/scraper/navigator.test.js.map +1 -0
  183. package/dist/scraper/skoolApi.d.ts +17 -0
  184. package/dist/scraper/skoolApi.d.ts.map +1 -0
  185. package/dist/scraper/skoolApi.js +72 -0
  186. package/dist/scraper/skoolApi.js.map +1 -0
  187. package/dist/scraper/videoInterceptor.d.ts +19 -0
  188. package/dist/scraper/videoInterceptor.d.ts.map +1 -0
  189. package/dist/scraper/videoInterceptor.js +315 -0
  190. package/dist/scraper/videoInterceptor.js.map +1 -0
  191. package/dist/shared/auth.d.ts +58 -0
  192. package/dist/shared/auth.d.ts.map +1 -0
  193. package/dist/shared/auth.js +211 -0
  194. package/dist/shared/auth.js.map +1 -0
  195. package/dist/shared/fs.d.ts +31 -0
  196. package/dist/shared/fs.d.ts.map +1 -0
  197. package/dist/shared/fs.js +73 -0
  198. package/dist/shared/fs.js.map +1 -0
  199. package/dist/shared/http.d.ts +15 -0
  200. package/dist/shared/http.d.ts.map +1 -0
  201. package/dist/shared/http.js +31 -0
  202. package/dist/shared/http.js.map +1 -0
  203. package/dist/shared/index.d.ts +4 -0
  204. package/dist/shared/index.d.ts.map +1 -0
  205. package/dist/shared/index.js +4 -0
  206. package/dist/shared/index.js.map +1 -0
  207. package/dist/state/database.d.ts +245 -0
  208. package/dist/state/database.d.ts.map +1 -0
  209. package/dist/state/database.js +676 -0
  210. package/dist/state/database.js.map +1 -0
  211. package/dist/state/database.test.d.ts +2 -0
  212. package/dist/state/database.test.d.ts.map +1 -0
  213. package/dist/state/database.test.js +34 -0
  214. package/dist/state/database.test.js.map +1 -0
  215. package/dist/state/index.d.ts +2 -0
  216. package/dist/state/index.d.ts.map +1 -0
  217. package/dist/state/index.js +2 -0
  218. package/dist/state/index.js.map +1 -0
  219. package/dist/storage/fileSystem.d.ts +56 -0
  220. package/dist/storage/fileSystem.d.ts.map +1 -0
  221. package/dist/storage/fileSystem.js +121 -0
  222. package/dist/storage/fileSystem.js.map +1 -0
  223. package/dist/transcription/whisperService.d.ts +27 -0
  224. package/dist/transcription/whisperService.d.ts.map +1 -0
  225. package/dist/transcription/whisperService.js +102 -0
  226. package/dist/transcription/whisperService.js.map +1 -0
  227. package/eslint.config.js +55 -0
  228. package/package.json +68 -11
  229. package/src/__fixtures__/highlevel-post-response.json +68 -0
  230. package/src/__fixtures__/hls-master-playlist.m3u8 +24 -0
  231. package/src/cli/commands/__snapshots__/syncHighLevel.test.ts.snap +38 -0
  232. package/src/cli/commands/config.ts +74 -0
  233. package/src/cli/commands/inspect.ts +441 -0
  234. package/src/cli/commands/login.ts +68 -0
  235. package/src/cli/commands/status.ts +147 -0
  236. package/src/cli/commands/sync.ts +1235 -0
  237. package/src/cli/commands/syncHighLevel.test.ts +144 -0
  238. package/src/cli/commands/syncHighLevel.ts +639 -0
  239. package/src/cli/index.ts +121 -0
  240. package/src/config/configManager.ts +75 -0
  241. package/src/config/paths.test.ts +83 -0
  242. package/src/config/paths.ts +36 -0
  243. package/src/config/schema.test.ts +173 -0
  244. package/src/config/schema.ts +65 -0
  245. package/src/downloader/hlsDownloader.test.ts +148 -0
  246. package/src/downloader/hlsDownloader.ts +327 -0
  247. package/src/downloader/hlsValidator.ts +196 -0
  248. package/src/downloader/index.ts +122 -0
  249. package/src/downloader/loomDownloader.test.ts +43 -0
  250. package/src/downloader/loomDownloader.ts +742 -0
  251. package/src/downloader/queue.test.ts +199 -0
  252. package/src/downloader/queue.ts +118 -0
  253. package/src/downloader/vimeoDownloader.test.ts +62 -0
  254. package/src/downloader/vimeoDownloader.ts +722 -0
  255. package/src/scraper/extractor.test.ts +124 -0
  256. package/src/scraper/extractor.ts +757 -0
  257. package/src/scraper/highlevel/__snapshots__/extractor.test.ts.snap +41 -0
  258. package/src/scraper/highlevel/extractor.test.ts +134 -0
  259. package/src/scraper/highlevel/extractor.ts +537 -0
  260. package/src/scraper/highlevel/index.ts +2 -0
  261. package/src/scraper/highlevel/navigator.test.ts +110 -0
  262. package/src/scraper/highlevel/navigator.ts +668 -0
  263. package/src/scraper/highlevel/schemas.ts +183 -0
  264. package/src/scraper/navigator.test.ts +122 -0
  265. package/src/scraper/navigator.ts +355 -0
  266. package/src/scraper/schemas.ts +177 -0
  267. package/src/scraper/videoInterceptor.ts +435 -0
  268. package/src/shared/auth.test.ts +58 -0
  269. package/src/shared/auth.ts +251 -0
  270. package/src/shared/firebase.ts +151 -0
  271. package/src/shared/fs.ts +80 -0
  272. package/src/shared/http.ts +34 -0
  273. package/src/shared/index.ts +6 -0
  274. package/src/shared/slug.ts +26 -0
  275. package/src/shared/url.test.ts +122 -0
  276. package/src/shared/url.ts +57 -0
  277. package/src/state/database.test.ts +49 -0
  278. package/src/state/database.ts +919 -0
  279. package/src/state/index.ts +14 -0
  280. package/src/storage/fileSystem.test.ts +64 -0
  281. package/src/storage/fileSystem.ts +175 -0
  282. package/tsconfig.json +28 -0
  283. package/vitest.config.ts +29 -0
  284. package/cli.js +0 -45
@@ -0,0 +1,492 @@
1
+ /**
2
+ * Extracts the location ID from the HighLevel portal.
3
+ * The location ID is used in all API calls.
4
+ */
5
+ export async function extractLocationId(page) {
6
+ // Wait for API calls that contain the location ID
7
+ const locationId = await page.evaluate(() => {
8
+ // Try to find it in the URL of any API call
9
+ const scripts = Array.from(document.querySelectorAll("script"));
10
+ for (const script of scripts) {
11
+ const content = script.textContent ?? "";
12
+ // Look for location ID pattern in HighLevel (typically in API URLs)
13
+ const match = /locations\/([A-Za-z0-9]+)/.exec(content);
14
+ if (match?.[1] && match[1].length > 10) {
15
+ return match[1];
16
+ }
17
+ }
18
+ // Try to find it in localStorage or sessionStorage
19
+ for (const storage of [localStorage, sessionStorage]) {
20
+ for (let i = 0; i < storage.length; i++) {
21
+ const key = storage.key(i);
22
+ if (key) {
23
+ const value = storage.getItem(key);
24
+ if (value) {
25
+ const match = /"locationId":\s*"([A-Za-z0-9]+)"/.exec(value);
26
+ if (match?.[1])
27
+ return match[1];
28
+ }
29
+ }
30
+ }
31
+ }
32
+ return null;
33
+ });
34
+ return locationId;
35
+ }
36
+ /**
37
+ * Extracts portal settings including location ID from the API.
38
+ */
39
+ export async function extractPortalSettings(page, domain) {
40
+ try {
41
+ // Intercept the portal-settings API call
42
+ const response = await page.evaluate(async (domain) => {
43
+ const res = await fetch(`https://services.leadconnectorhq.com/clientclub/portal-settings?domain=${domain}`);
44
+ if (!res.ok)
45
+ return null;
46
+ return res.json();
47
+ }, domain);
48
+ if (response?.locationId) {
49
+ return {
50
+ locationId: response.locationId,
51
+ portalName: response.portalName ?? response.name ?? "HighLevel Course",
52
+ };
53
+ }
54
+ }
55
+ catch {
56
+ // Fall through
57
+ }
58
+ return null;
59
+ }
60
+ /**
61
+ * Extracts course list from the courses library page.
62
+ */
63
+ export async function extractCourses(page) {
64
+ // Wait for the course cards to load
65
+ await page.waitForTimeout(2000);
66
+ const courses = await page.evaluate(() => {
67
+ const results = [];
68
+ // Find course cards - HighLevel uses various patterns
69
+ const courseCards = document.querySelectorAll('[class*="course-card"], [class*="CourseCard"], [data-product-id], [class*="product-card"]');
70
+ // If no specific cards found, try to find links to course pages
71
+ if (courseCards.length === 0) {
72
+ const courseLinks = document.querySelectorAll('a[href*="/courses/products/"]');
73
+ const seen = new Set();
74
+ courseLinks.forEach((link) => {
75
+ const href = link.href;
76
+ const match = /\/courses\/products\/([a-f0-9-]+)/.exec(href);
77
+ if (match?.[1] && !seen.has(match[1])) {
78
+ seen.add(match[1]);
79
+ const title = link.querySelector("h3, h4, [class*='title']")?.textContent?.trim() ||
80
+ link.textContent?.trim() ||
81
+ `Course ${results.length + 1}`;
82
+ results.push({
83
+ id: match[1],
84
+ title,
85
+ description: "",
86
+ slug: match[1],
87
+ thumbnailUrl: link.querySelector("img")?.src ?? null,
88
+ instructor: null,
89
+ totalLessons: 0,
90
+ progress: 0,
91
+ });
92
+ }
93
+ });
94
+ }
95
+ return results;
96
+ });
97
+ return courses;
98
+ }
99
+ /**
100
+ * Extracts course details from the course overview page via API.
101
+ */
102
+ export async function extractCourseDetails(page, courseUrl, locationId) {
103
+ // Extract product ID from provided courseUrl first
104
+ let productId;
105
+ const courseUrlMatch = /\/courses\/products\/([a-f0-9-]+)/.exec(courseUrl);
106
+ if (courseUrlMatch?.[1]) {
107
+ productId = courseUrlMatch[1];
108
+ }
109
+ // Fallback: try from current page URL
110
+ if (!productId) {
111
+ const pageUrlMatch = /\/courses\/products\/([a-f0-9-]+)/.exec(page.url());
112
+ productId = pageUrlMatch?.[1];
113
+ }
114
+ if (!productId) {
115
+ console.error("Could not extract product ID from URL:", courseUrl, "page:", page.url());
116
+ return null;
117
+ }
118
+ // Try direct API call first (most reliable)
119
+ if (locationId) {
120
+ try {
121
+ const apiUrl = `https://services.leadconnectorhq.com/membership/locations/${locationId}/products/${productId}`;
122
+ // Get auth token from the page context
123
+ const authToken = await page.evaluate(() => {
124
+ const tokenKey = Object.keys(localStorage).find((k) => k.includes("firebase:authUser"));
125
+ const tokenData = tokenKey ? JSON.parse(localStorage.getItem(tokenKey) ?? "{}") : null;
126
+ return tokenData?.stsTokenManager?.accessToken ?? null;
127
+ });
128
+ if (authToken) {
129
+ // Use page.request to make the API call (bypasses CORS)
130
+ const response = await page.request.get(apiUrl, {
131
+ headers: {
132
+ Authorization: `Bearer ${authToken}`,
133
+ },
134
+ });
135
+ if (response.ok()) {
136
+ const data = await response.json();
137
+ // The API returns the product directly, not wrapped in a "product" property
138
+ const product = data.product ?? data;
139
+ const title = product.title;
140
+ if (title && title !== "Unknown Course") {
141
+ return {
142
+ id: product.id ?? productId,
143
+ title,
144
+ description: product.description ?? "",
145
+ slug: product.id ?? productId,
146
+ thumbnailUrl: product.posterImage ?? null,
147
+ instructor: product.instructor ?? null,
148
+ totalLessons: product.postCount ?? 0,
149
+ progress: 0,
150
+ };
151
+ }
152
+ }
153
+ }
154
+ }
155
+ catch {
156
+ // Continue to DOM fallback silently
157
+ }
158
+ }
159
+ // Fallback to DOM extraction if API fails
160
+ const domCourse = await page.evaluate(() => {
161
+ const urlMatch = /\/courses\/products\/([a-f0-9-]+)/.exec(window.location.href);
162
+ const id = urlMatch?.[1] ?? "";
163
+ // Look for the course title in various places
164
+ let title = "";
165
+ // Method 1: Look for a large heading that's not navigation
166
+ const headings = Array.from(document.querySelectorAll("h1, h2, h3"));
167
+ for (const h of headings) {
168
+ const text = h.textContent?.trim() ?? "";
169
+ const parent = h.closest("nav, header, [class*='nav'], [class*='Nav']");
170
+ // Skip if in navigation, or if it's a generic title
171
+ if (parent)
172
+ continue;
173
+ if (text.length < 4)
174
+ continue;
175
+ if (text.toLowerCase().includes("menu"))
176
+ continue;
177
+ if (text.toLowerCase().includes("login"))
178
+ continue;
179
+ if (text === "HighLevel")
180
+ continue;
181
+ if (text === "Courses")
182
+ continue;
183
+ // Found a good candidate
184
+ title = text;
185
+ break;
186
+ }
187
+ // Method 2: Look for text with "lesson" count indicator nearby
188
+ if (!title) {
189
+ const lessonIndicators = Array.from(document.querySelectorAll("[class*='lesson'], [class*='Lesson']"));
190
+ for (const indicator of lessonIndicators) {
191
+ const parent = indicator.closest("[class*='card'], [class*='Card'], [class*='product'], [class*='Product']");
192
+ if (parent) {
193
+ const heading = parent.querySelector("h1, h2, h3, h4");
194
+ if (heading?.textContent?.trim()) {
195
+ title = heading.textContent.trim();
196
+ break;
197
+ }
198
+ }
199
+ }
200
+ }
201
+ if (!title || title.length < 3) {
202
+ title = "Unknown Course";
203
+ }
204
+ return {
205
+ id,
206
+ title,
207
+ description: "",
208
+ slug: id,
209
+ thumbnailUrl: null,
210
+ instructor: null,
211
+ totalLessons: 0,
212
+ progress: 0,
213
+ };
214
+ });
215
+ return domCourse.id ? domCourse : null;
216
+ }
217
+ /**
218
+ * Extracts categories (modules) from a course page.
219
+ */
220
+ export async function extractCategories(page, productId, locationId) {
221
+ // Try to get categories via API
222
+ const categories = await page.evaluate(async ({ productId, locationId }) => {
223
+ try {
224
+ // Get auth token from Firebase
225
+ const tokenKey = Object.keys(localStorage).find((k) => k.includes("firebase:authUser"));
226
+ const tokenData = tokenKey ? JSON.parse(localStorage.getItem(tokenKey) ?? "{}") : null;
227
+ const token = tokenData?.stsTokenManager?.accessToken;
228
+ if (!token) {
229
+ console.warn("No auth token found");
230
+ return [];
231
+ }
232
+ const res = await fetch(`https://services.leadconnectorhq.com/membership/locations/${locationId}/user-purchase/categories?product_id=${productId}&source=courses`, {
233
+ headers: {
234
+ Authorization: `Bearer ${token}`,
235
+ },
236
+ });
237
+ if (!res.ok) {
238
+ console.warn("Categories API returned", res.status);
239
+ return [];
240
+ }
241
+ const data = await res.json();
242
+ if (Array.isArray(data.categories)) {
243
+ return data.categories.map((cat) => ({
244
+ id: cat.id,
245
+ title: cat.title,
246
+ description: cat.description ?? null,
247
+ position: cat.position ?? 0,
248
+ postCount: cat.postCount ?? 0,
249
+ isLocked: cat.visibility === "locked",
250
+ }));
251
+ }
252
+ return [];
253
+ }
254
+ catch (error) {
255
+ console.error("Failed to fetch categories:", error);
256
+ return [];
257
+ }
258
+ }, { productId, locationId });
259
+ return categories;
260
+ }
261
+ /**
262
+ * Extracts posts (lessons) from a category.
263
+ */
264
+ export async function extractPosts(page, productId, categoryId, locationId) {
265
+ const posts = await page.evaluate(async ({ productId, categoryId, locationId }) => {
266
+ try {
267
+ // Get auth token
268
+ const tokenKey = Object.keys(localStorage).find((k) => k.includes("firebase:authUser"));
269
+ const tokenData = tokenKey ? JSON.parse(localStorage.getItem(tokenKey) ?? "{}") : null;
270
+ const token = tokenData?.stsTokenManager?.accessToken;
271
+ if (!token) {
272
+ return [];
273
+ }
274
+ const res = await fetch(`https://services.leadconnectorhq.com/membership/locations/${locationId}/user-purchase/categories/${categoryId}?product_id=${productId}&visibility=published&published_posts=true&source=courses`, {
275
+ headers: {
276
+ Authorization: `Bearer ${token}`,
277
+ },
278
+ });
279
+ if (!res.ok) {
280
+ return [];
281
+ }
282
+ const data = await res.json();
283
+ if (data.category?.posts && Array.isArray(data.category.posts)) {
284
+ return data.category.posts.map((post, index) => ({
285
+ id: post.id,
286
+ title: post.title,
287
+ position: post.indexPosition ?? index,
288
+ categoryId,
289
+ isLocked: post.visibility === "locked",
290
+ isCompleted: false,
291
+ }));
292
+ }
293
+ return [];
294
+ }
295
+ catch (error) {
296
+ console.error("Failed to fetch posts:", error);
297
+ return [];
298
+ }
299
+ }, { productId, categoryId, locationId });
300
+ return posts;
301
+ }
302
+ /**
303
+ * Builds the complete course structure.
304
+ */
305
+ export async function buildHighLevelCourseStructure(page, courseUrl, onProgress) {
306
+ // Extract domain and product ID from URL
307
+ const urlObj = new URL(courseUrl);
308
+ const domain = urlObj.hostname;
309
+ const productMatch = /\/courses\/products\/([a-f0-9-]+)/.exec(courseUrl);
310
+ const productId = productMatch?.[1];
311
+ // Get portal settings (includes location ID)
312
+ onProgress?.({ phase: "init" });
313
+ let locationId = null;
314
+ // Try to get location ID from portal settings API
315
+ const settings = await extractPortalSettings(page, domain);
316
+ if (settings) {
317
+ locationId = settings.locationId;
318
+ }
319
+ // Fallback: try to extract from page
320
+ if (!locationId) {
321
+ locationId = await extractLocationId(page);
322
+ }
323
+ if (!locationId) {
324
+ console.error("Could not determine location ID");
325
+ return null;
326
+ }
327
+ // Set up response interception to capture product data BEFORE navigation
328
+ let capturedCourseTitle = null;
329
+ const responseHandler = async (response) => {
330
+ const url = response.url();
331
+ if (productId &&
332
+ url.includes(`/products/${productId}`) &&
333
+ url.includes("leadconnectorhq.com")) {
334
+ try {
335
+ const data = await response.json();
336
+ if (data.product?.title) {
337
+ capturedCourseTitle = data.product.title;
338
+ }
339
+ }
340
+ catch {
341
+ // Ignore JSON parse errors
342
+ }
343
+ }
344
+ };
345
+ page.on("response", responseHandler);
346
+ // Navigate to course page (force reload to ensure we capture API responses)
347
+ // Using waitUntil: "networkidle" to ensure all API calls complete
348
+ await page.goto(courseUrl, {
349
+ timeout: 30000,
350
+ waitUntil: "networkidle",
351
+ });
352
+ await page.waitForTimeout(1000);
353
+ // Remove the handler
354
+ page.off("response", responseHandler);
355
+ // Extract course details
356
+ onProgress?.({ phase: "course" });
357
+ const course = await extractCourseDetails(page, courseUrl, locationId);
358
+ if (!course) {
359
+ console.error("Could not extract course details");
360
+ return null;
361
+ }
362
+ // Use captured title if available and course title is unknown
363
+ if (capturedCourseTitle && (course.title === "Unknown Course" || !course.title)) {
364
+ course.title = capturedCourseTitle;
365
+ }
366
+ // Fallback: Try to get title from DOM after page is fully loaded
367
+ if (course.title === "Unknown Course" || !course.title) {
368
+ const domTitle = await page.evaluate(() => {
369
+ // Look for product title in common HighLevel selectors
370
+ const selectors = [
371
+ "[class*='product-title']",
372
+ "[class*='ProductTitle']",
373
+ "[class*='course-title']",
374
+ "[class*='CourseTitle']",
375
+ "h1.title",
376
+ "h2.title",
377
+ "[data-testid='product-title']",
378
+ ".product-header h1",
379
+ ".product-header h2",
380
+ ];
381
+ for (const selector of selectors) {
382
+ const el = document.querySelector(selector);
383
+ const text = el?.textContent?.trim();
384
+ if (text && text.length > 2 && text.length < 200) {
385
+ return text;
386
+ }
387
+ }
388
+ // Try to find a heading that's not generic
389
+ const headings = Array.from(document.querySelectorAll("h1, h2, h3"));
390
+ for (const h of headings) {
391
+ const text = h.textContent?.trim() ?? "";
392
+ if (text.length > 3 &&
393
+ text.length < 150 &&
394
+ !text.toLowerCase().includes("menu") &&
395
+ !text.toLowerCase().includes("login") &&
396
+ text !== "Memberships" &&
397
+ text !== "Courses" &&
398
+ text !== "Unknown Course") {
399
+ return text;
400
+ }
401
+ }
402
+ return null;
403
+ });
404
+ if (domTitle) {
405
+ course.title = domTitle;
406
+ }
407
+ }
408
+ onProgress?.({ phase: "course", courseName: course.title });
409
+ // Extract categories
410
+ onProgress?.({ phase: "categories" });
411
+ const categories = await extractCategories(page, course.id, locationId);
412
+ onProgress?.({ phase: "categories", totalCategories: categories.length });
413
+ // Extract posts for each category
414
+ const categoriesWithPosts = [];
415
+ for (let i = 0; i < categories.length; i++) {
416
+ const category = categories[i];
417
+ if (category.isLocked) {
418
+ onProgress?.({
419
+ phase: "posts",
420
+ currentCategory: category.title,
421
+ currentCategoryIndex: i,
422
+ skippedLocked: true,
423
+ });
424
+ continue;
425
+ }
426
+ onProgress?.({
427
+ phase: "posts",
428
+ currentCategory: category.title,
429
+ currentCategoryIndex: i,
430
+ });
431
+ const posts = await extractPosts(page, course.id, category.id, locationId);
432
+ onProgress?.({
433
+ phase: "posts",
434
+ currentCategory: category.title,
435
+ currentCategoryIndex: i,
436
+ postsFound: posts.length,
437
+ });
438
+ categoriesWithPosts.push({
439
+ ...category,
440
+ posts,
441
+ });
442
+ }
443
+ onProgress?.({ phase: "done" });
444
+ // Update total lessons count
445
+ course.totalLessons = categoriesWithPosts.reduce((total, cat) => total + cat.posts.length, 0);
446
+ return {
447
+ course,
448
+ categories: categoriesWithPosts,
449
+ locationId,
450
+ domain,
451
+ };
452
+ }
453
+ /**
454
+ * Creates a filesystem-safe name from a string.
455
+ */
456
+ export function slugify(name) {
457
+ return name
458
+ .toLowerCase()
459
+ .replace(/[äöüß]/g, (char) => {
460
+ const replacements = {
461
+ ä: "ae",
462
+ ö: "oe",
463
+ ü: "ue",
464
+ ß: "ss",
465
+ };
466
+ return replacements[char] ?? char;
467
+ })
468
+ .replace(/[^a-z0-9]+/g, "-")
469
+ .replace(/^-+|-+$/g, "")
470
+ .substring(0, 100);
471
+ }
472
+ /**
473
+ * Creates a folder name with index prefix.
474
+ */
475
+ export function createFolderName(index, name) {
476
+ const prefix = String(index + 1).padStart(2, "0");
477
+ const slug = slugify(name);
478
+ return `${prefix}-${slug}`;
479
+ }
480
+ /**
481
+ * Constructs the URL for a HighLevel course page.
482
+ */
483
+ export function getHighLevelCourseUrl(domain, productId) {
484
+ return `https://${domain}/courses/products/${productId}?source=courses`;
485
+ }
486
+ /**
487
+ * Constructs the URL for a HighLevel lesson (post) page.
488
+ */
489
+ export function getHighLevelPostUrl(domain, productId, categoryId, postId) {
490
+ return `https://${domain}/courses/products/${productId}/categories/${categoryId}/posts/${postId}?source=courses`;
491
+ }
492
+ //# sourceMappingURL=navigator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.js","sourceRoot":"","sources":["../../../src/scraper/highlevel/navigator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=navigator.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.test.d.ts","sourceRoot":"","sources":["../../../src/scraper/highlevel/navigator.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,78 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { slugify, createFolderName, getHighLevelCourseUrl, getHighLevelPostUrl, } from "./navigator.js";
3
+ describe("slugify", () => {
4
+ it("converts to lowercase", () => {
5
+ expect(slugify("Hello World")).toBe("hello-world");
6
+ });
7
+ it("replaces spaces with hyphens", () => {
8
+ expect(slugify("my test string")).toBe("my-test-string");
9
+ });
10
+ it("removes special characters", () => {
11
+ expect(slugify("Hello! How are you?")).toBe("hello-how-are-you");
12
+ });
13
+ it("replaces German umlauts", () => {
14
+ expect(slugify("Größe")).toBe("groesse");
15
+ expect(slugify("Äpfel")).toBe("aepfel");
16
+ expect(slugify("Übung")).toBe("uebung");
17
+ expect(slugify("Straße")).toBe("strasse");
18
+ });
19
+ it("removes leading and trailing hyphens", () => {
20
+ expect(slugify("--hello--")).toBe("hello");
21
+ expect(slugify(" hello ")).toBe("hello");
22
+ });
23
+ it("collapses multiple hyphens", () => {
24
+ expect(slugify("hello world")).toBe("hello-world");
25
+ expect(slugify("hello---world")).toBe("hello-world");
26
+ });
27
+ it("truncates to 100 characters", () => {
28
+ const longName = "a".repeat(150);
29
+ expect(slugify(longName).length).toBe(100);
30
+ });
31
+ it("handles empty string", () => {
32
+ expect(slugify("")).toBe("");
33
+ });
34
+ it("handles numbers", () => {
35
+ expect(slugify("Lesson 1: Introduction")).toBe("lesson-1-introduction");
36
+ });
37
+ it("handles mixed content", () => {
38
+ expect(slugify("🚀 Getting Started! (Part 1)")).toBe("getting-started-part-1");
39
+ });
40
+ });
41
+ describe("createFolderName", () => {
42
+ it("creates folder name with padded index", () => {
43
+ expect(createFolderName(0, "Introduction")).toBe("01-introduction");
44
+ expect(createFolderName(9, "Conclusion")).toBe("10-conclusion");
45
+ });
46
+ it("handles double-digit indices", () => {
47
+ expect(createFolderName(99, "Final")).toBe("100-final");
48
+ });
49
+ it("slugifies the name", () => {
50
+ expect(createFolderName(0, "Hello World!")).toBe("01-hello-world");
51
+ });
52
+ it("handles German umlauts in name", () => {
53
+ expect(createFolderName(2, "Größe & Übung")).toBe("03-groesse-uebung");
54
+ });
55
+ });
56
+ describe("getHighLevelCourseUrl", () => {
57
+ it("constructs correct course URL", () => {
58
+ const url = getHighLevelCourseUrl("member.example.com", "abc-123");
59
+ expect(url).toBe("https://member.example.com/courses/products/abc-123?source=courses");
60
+ });
61
+ it("handles UUID product IDs", () => {
62
+ const url = getHighLevelCourseUrl("portal.school.com", "e5f64bf3-9d88-4d02-b10e-516f47866094");
63
+ expect(url).toBe("https://portal.school.com/courses/products/e5f64bf3-9d88-4d02-b10e-516f47866094?source=courses");
64
+ });
65
+ });
66
+ describe("getHighLevelPostUrl", () => {
67
+ it("constructs correct post URL", () => {
68
+ const url = getHighLevelPostUrl("member.example.com", "product-1", "category-2", "post-3");
69
+ expect(url).toBe("https://member.example.com/courses/products/product-1/categories/category-2/posts/post-3?source=courses");
70
+ });
71
+ it("handles UUID IDs", () => {
72
+ const url = getHighLevelPostUrl("member.test.com", "e5f64bf3-9d88-4d02-b10e-516f47866094", "cat-abc-123", "post-xyz-789");
73
+ expect(url).toContain("products/e5f64bf3-9d88-4d02-b10e-516f47866094");
74
+ expect(url).toContain("categories/cat-abc-123");
75
+ expect(url).toContain("posts/post-xyz-789");
76
+ });
77
+ });
78
+ //# sourceMappingURL=navigator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"navigator.test.js","sourceRoot":"","sources":["../../../src/scraper/highlevel/navigator.test.ts"],"names":[],"mappings":"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"}