@churchapps/content-providers 0.4.1 → 0.5.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.
package/dist/index.js CHANGED
@@ -18,33 +18,81 @@ function navigateToPath(instructions, path) {
18
18
  return current;
19
19
  }
20
20
 
21
+ // src/helpers/TokenHelper.ts
22
+ function toAuthData(data, fallbacks) {
23
+ return {
24
+ access_token: data.access_token,
25
+ refresh_token: data.refresh_token || fallbacks?.refreshToken || "",
26
+ token_type: data.token_type || "Bearer",
27
+ created_at: Math.floor(Date.now() / 1e3),
28
+ expires_in: data.expires_in,
29
+ scope: data.scope || fallbacks?.scope || ""
30
+ };
31
+ }
32
+ var TokenHelper = class {
33
+ isAuthValid(auth) {
34
+ if (!auth) return false;
35
+ return !this.isTokenExpired(auth);
36
+ }
37
+ isTokenExpired(auth) {
38
+ if (!auth.created_at || !auth.expires_in) return true;
39
+ const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
40
+ return Date.now() > expiresAt - 5 * 60 * 1e3;
41
+ }
42
+ async refreshToken(config, auth) {
43
+ if (!auth.refresh_token) return null;
44
+ try {
45
+ const params = new URLSearchParams({
46
+ grant_type: "refresh_token",
47
+ refresh_token: auth.refresh_token,
48
+ client_id: config.clientId
49
+ });
50
+ const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
51
+ if (!response.ok) {
52
+ console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
53
+ return null;
54
+ }
55
+ const data = await response.json();
56
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
57
+ } catch {
58
+ return null;
59
+ }
60
+ }
61
+ };
62
+
21
63
  // src/helpers/OAuthHelper.ts
64
+ function generateCodeVerifier() {
65
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
66
+ const length = 64;
67
+ const array = new Uint8Array(length);
68
+ crypto.getRandomValues(array);
69
+ let result = "";
70
+ for (let i = 0; i < length; i++) {
71
+ result += chars.charAt(array[i] % chars.length);
72
+ }
73
+ return result;
74
+ }
75
+ async function generateCodeChallenge(verifier) {
76
+ const encoder = new TextEncoder();
77
+ const data = encoder.encode(verifier);
78
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
79
+ const hashArray = new Uint8Array(hashBuffer);
80
+ let binary = "";
81
+ for (let i = 0; i < hashArray.length; i++) {
82
+ binary += String.fromCharCode(hashArray[i]);
83
+ }
84
+ const base64 = btoa(binary);
85
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
86
+ }
22
87
  var OAuthHelper = class {
23
88
  generateCodeVerifier() {
24
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
25
- const length = 64;
26
- const array = new Uint8Array(length);
27
- crypto.getRandomValues(array);
28
- let result = "";
29
- for (let i = 0; i < length; i++) {
30
- result += chars.charAt(array[i] % chars.length);
31
- }
32
- return result;
89
+ return generateCodeVerifier();
33
90
  }
34
91
  async generateCodeChallenge(verifier) {
35
- const encoder = new TextEncoder();
36
- const data = encoder.encode(verifier);
37
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
38
- const hashArray = new Uint8Array(hashBuffer);
39
- let binary = "";
40
- for (let i = 0; i < hashArray.length; i++) {
41
- binary += String.fromCharCode(hashArray[i]);
42
- }
43
- const base64 = btoa(binary);
44
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
92
+ return generateCodeChallenge(verifier);
45
93
  }
46
94
  async buildAuthUrl(config, codeVerifier, redirectUri, state) {
47
- const codeChallenge = await this.generateCodeChallenge(codeVerifier);
95
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
48
96
  const params = new URLSearchParams({
49
97
  response_type: "code",
50
98
  client_id: config.clientId,
@@ -71,39 +119,7 @@ var OAuthHelper = class {
71
119
  return null;
72
120
  }
73
121
  const data = await response.json();
74
- return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
75
- } catch {
76
- return null;
77
- }
78
- }
79
- };
80
-
81
- // src/helpers/TokenHelper.ts
82
- var TokenHelper = class {
83
- isAuthValid(auth) {
84
- if (!auth) return false;
85
- return !this.isTokenExpired(auth);
86
- }
87
- isTokenExpired(auth) {
88
- if (!auth.created_at || !auth.expires_in) return true;
89
- const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
90
- return Date.now() > expiresAt - 5 * 60 * 1e3;
91
- }
92
- async refreshToken(config, auth) {
93
- if (!auth.refresh_token) return null;
94
- try {
95
- const params = new URLSearchParams({
96
- grant_type: "refresh_token",
97
- refresh_token: auth.refresh_token,
98
- client_id: config.clientId
99
- });
100
- const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
101
- if (!response.ok) {
102
- console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
103
- return null;
104
- }
105
- const data = await response.json();
106
- return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
122
+ return toAuthData(data, { scope: config.scopes.join(" ") });
107
123
  } catch {
108
124
  return null;
109
125
  }
@@ -130,7 +146,7 @@ var DeviceFlowHelper = class {
130
146
  const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
131
147
  if (response.ok) {
132
148
  const data = await response.json();
133
- return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
149
+ return toAuthData(data, { scope: config.scopes.join(" ") });
134
150
  }
135
151
  const errorData = await response.json();
136
152
  switch (errorData.error) {
@@ -180,6 +196,19 @@ var ApiHelper = class {
180
196
  }
181
197
  };
182
198
 
199
+ // src/providers/BaseProvider.ts
200
+ var BaseProvider = class {
201
+ constructor() {
202
+ this.apiHelper = new ApiHelper();
203
+ }
204
+ apiRequest(path, auth, method = "GET", body) {
205
+ return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
206
+ }
207
+ supportsDeviceFlow() {
208
+ return !!this.config.supportsDeviceFlow && !!this.config.deviceAuthEndpoint;
209
+ }
210
+ };
211
+
183
212
  // src/pathUtils.ts
184
213
  function parsePath(path) {
185
214
  if (!path || path === "/" || path === "") {
@@ -193,26 +222,6 @@ function getSegment(path, index) {
193
222
  return segments[index] ?? null;
194
223
  }
195
224
 
196
- // src/providers/aPlay/APlayApi.ts
197
- var API_BASE = "https://api-prod.amazingkids.app";
198
- async function checkMediaLicense(mediaId, auth) {
199
- if (!auth) return null;
200
- try {
201
- const url = `${API_BASE}/prod/reports/media/license-check`;
202
- const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${auth.access_token}`, "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ mediaIds: [mediaId] }) });
203
- if (!response.ok) return null;
204
- const data = await response.json();
205
- const licenseData = Array.isArray(data) ? data : data.data || [];
206
- const result = licenseData.find((item) => item.mediaId === mediaId);
207
- if (result?.isLicensed) {
208
- return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
209
- }
210
- return { mediaId, status: "not_licensed", message: "Media is not licensed" };
211
- } catch {
212
- return { mediaId, status: "unknown", message: "Unable to verify license status" };
213
- }
214
- }
215
-
216
225
  // src/utils.ts
217
226
  var IMAGE_DURATION_SECONDS = 15;
218
227
  function slugify(text) {
@@ -250,6 +259,31 @@ function createFile(id, title, url, options) {
250
259
  function generateId() {
251
260
  return "gen-" + Math.random().toString(36).substring(2, 11);
252
261
  }
262
+ function fileToActionItem(file) {
263
+ return {
264
+ id: file.id + "-action",
265
+ itemType: "action",
266
+ label: file.title,
267
+ actionType: "play",
268
+ seconds: file.seconds || void 0,
269
+ children: [
270
+ {
271
+ id: file.id,
272
+ itemType: "file",
273
+ label: file.title,
274
+ seconds: file.seconds || void 0,
275
+ downloadUrl: file.downloadUrl || file.url,
276
+ thumbnail: file.thumbnail,
277
+ mediaType: file.mediaType
278
+ }
279
+ ]
280
+ };
281
+ }
282
+ function filesToInstructions(name, files, section) {
283
+ const actions = files.map(fileToActionItem);
284
+ const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
285
+ return { name, items };
286
+ }
253
287
  function instructionsToPlaylist(instructions) {
254
288
  const files = [];
255
289
  function extractFiles(items) {
@@ -264,6 +298,26 @@ function instructionsToPlaylist(instructions) {
264
298
  return files;
265
299
  }
266
300
 
301
+ // src/providers/aPlay/APlayApi.ts
302
+ var API_BASE = "https://api-prod.amazingkids.app";
303
+ async function checkMediaLicense(mediaId, auth) {
304
+ if (!auth) return null;
305
+ try {
306
+ const url = `${API_BASE}/prod/reports/media/license-check`;
307
+ const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${auth.access_token}`, "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ mediaIds: [mediaId] }) });
308
+ if (!response.ok) return null;
309
+ const data = await response.json();
310
+ const licenseData = Array.isArray(data) ? data : data.data || [];
311
+ const result = licenseData.find((item) => item.mediaId === mediaId);
312
+ if (result?.isLicensed) {
313
+ return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
314
+ }
315
+ return { mediaId, status: "not_licensed", message: "Media is not licensed" };
316
+ } catch {
317
+ return { mediaId, status: "unknown", message: "Unable to verify license status" };
318
+ }
319
+ }
320
+
267
321
  // src/providers/aPlay/APlayConverters.ts
268
322
  function extractLibraryId(path) {
269
323
  const { segments, depth } = parsePath(path);
@@ -330,25 +384,6 @@ function convertProductsToFolders(products, currentPath) {
330
384
  return createFolder(id, p.title || p.name, `${currentPath}/products/${id}`, p.image);
331
385
  });
332
386
  }
333
- function convertFilesToInstructions(files, _libraryId) {
334
- const items = files.map((file) => ({
335
- id: file.id + "-action",
336
- itemType: "action",
337
- label: file.title,
338
- actionType: "play",
339
- children: [
340
- {
341
- id: file.id,
342
- itemType: "file",
343
- label: file.title,
344
- seconds: file.seconds,
345
- downloadUrl: file.url,
346
- thumbnail: file.thumbnail
347
- }
348
- ]
349
- }));
350
- return { name: "Library", items };
351
- }
352
387
 
353
388
  // src/providers/aPlay/APlayProvider.ts
354
389
  function extractArray(response, ...keys) {
@@ -361,20 +396,17 @@ function extractArray(response, ...keys) {
361
396
  }
362
397
  return [];
363
398
  }
364
- var APlayProvider = class {
399
+ var APlayProvider = class extends BaseProvider {
365
400
  constructor() {
366
- this.apiHelper = new ApiHelper();
401
+ super(...arguments);
367
402
  this.id = "aplay";
368
403
  this.name = "APlay";
369
404
  this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
370
- this.config = { id: "aplay", name: "APlay", apiBase: API_BASE, oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"], endpoints: { modules: "/prod/curriculum/modules", productLibraries: (productId) => `/prod/curriculum/modules/products/${productId}/libraries`, libraryMedia: (libraryId) => `/prod/creators/libraries/${libraryId}/media` } };
405
+ this.config = { id: "aplay", name: "APlay", apiBase: API_BASE, oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"] };
371
406
  this.requiresAuth = true;
372
407
  this.authTypes = ["oauth_pkce"];
373
408
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: true };
374
409
  }
375
- async apiRequest(path, auth) {
376
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
377
- }
378
410
  async browse(path, auth) {
379
411
  const { segments, depth } = parsePath(path);
380
412
  if (depth === 0) {
@@ -390,12 +422,12 @@ var APlayProvider = class {
390
422
  return [];
391
423
  }
392
424
  async getModules(auth) {
393
- const response = await this.apiRequest(this.config.endpoints.modules, auth);
425
+ const response = await this.apiRequest("/prod/curriculum/modules", auth);
394
426
  const modules = extractArray(response, "data", "modules");
395
427
  return convertModulesToFolders(modules);
396
428
  }
397
429
  async getModuleContent(moduleId, currentPath, auth) {
398
- const response = await this.apiRequest(this.config.endpoints.modules, auth);
430
+ const response = await this.apiRequest("/prod/curriculum/modules", auth);
399
431
  const modules = extractArray(response, "data", "modules");
400
432
  const module = modules.find((m) => (m.id || m.moduleId) === moduleId);
401
433
  if (!module) return [];
@@ -411,14 +443,12 @@ var APlayProvider = class {
411
443
  }
412
444
  }
413
445
  async getLibraryFolders(productId, currentPath, auth) {
414
- const pathFn = this.config.endpoints.productLibraries;
415
- const response = await this.apiRequest(pathFn(productId), auth);
446
+ const response = await this.apiRequest(`/prod/curriculum/modules/products/${productId}/libraries`, auth);
416
447
  const libraries = extractArray(response, "data", "libraries");
417
448
  return convertLibrariesToFolders(libraries, currentPath);
418
449
  }
419
450
  async getMediaFiles(libraryId, auth) {
420
- const pathFn = this.config.endpoints.libraryMedia;
421
- const response = await this.apiRequest(pathFn(libraryId), auth);
451
+ const response = await this.apiRequest(`/prod/creators/libraries/${libraryId}/media`, auth);
422
452
  const mediaItems = extractArray(response, "data", "media");
423
453
  return convertMediaToFiles(mediaItems);
424
454
  }
@@ -433,63 +463,47 @@ var APlayProvider = class {
433
463
  if (!libraryId) return null;
434
464
  const files = await this.getMediaFiles(libraryId, auth);
435
465
  if (files.length === 0) return null;
436
- return convertFilesToInstructions(files, libraryId);
466
+ return filesToInstructions("Library", files);
437
467
  }
438
468
  async checkMediaLicense(mediaId, auth) {
439
469
  return checkMediaLicense(mediaId, auth);
440
470
  }
441
- supportsDeviceFlow() {
442
- return false;
443
- }
444
471
  };
445
472
 
446
473
  // src/providers/signPresenter/SignPresenterProvider.ts
447
- var SignPresenterProvider = class {
474
+ var SignPresenterProvider = class extends BaseProvider {
448
475
  constructor() {
449
- this.apiHelper = new ApiHelper();
476
+ super(...arguments);
450
477
  this.oauthHelper = new OAuthHelper();
451
478
  this.deviceFlowHelper = new DeviceFlowHelper();
452
479
  this.id = "signpresenter";
453
480
  this.name = "SignPresenter";
454
481
  this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
455
- this.config = { id: "signpresenter", name: "SignPresenter", apiBase: "https://api.signpresenter.com", oauthBase: "https://api.signpresenter.com/oauth", clientId: "lessonsscreen-tv", scopes: ["openid", "profile", "content"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { playlists: "/content/playlists", messages: (playlistId) => `/content/playlists/${playlistId}/messages` } };
482
+ this.config = { id: "signpresenter", name: "SignPresenter", apiBase: "https://api.signpresenter.com", oauthBase: "https://api.signpresenter.com/oauth", clientId: "lessonsscreen-tv", scopes: ["openid", "profile", "content"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize" };
456
483
  this.requiresAuth = true;
457
484
  this.authTypes = ["oauth_pkce", "device_flow"];
458
485
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
459
486
  }
460
- async apiRequest(path, auth) {
461
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
462
- }
463
487
  async browse(path, auth) {
464
488
  const { segments, depth } = parsePath(path);
465
489
  if (depth === 0) {
466
- return [
467
- {
468
- type: "folder",
469
- id: "playlists-root",
470
- title: "Playlists",
471
- path: "/playlists"
472
- }
473
- ];
474
- }
475
- const root = segments[0];
476
- if (root !== "playlists") return [];
477
- if (depth === 1) {
478
- return this.getPlaylists(auth);
479
- }
480
- if (depth === 2) {
481
- const playlistId = segments[1];
482
- return this.getMessages(playlistId, auth);
490
+ return [{ type: "folder", id: "playlists-root", title: "Playlists", path: "/playlists" }];
483
491
  }
492
+ if (segments[0] !== "playlists") return [];
493
+ if (depth === 1) return this.getPlaylists(auth);
494
+ if (depth === 2) return this.getMessages(segments[1], auth);
484
495
  return [];
485
496
  }
497
+ extractList(response, key) {
498
+ if (Array.isArray(response)) return response;
499
+ const record = response;
500
+ const list = record?.data || record?.[key] || [];
501
+ return Array.isArray(list) ? list : [];
502
+ }
486
503
  async getPlaylists(auth) {
487
- const apiPath = this.config.endpoints.playlists;
488
- const response = await this.apiRequest(apiPath, auth);
504
+ const response = await this.apiRequest("/content/playlists", auth);
489
505
  if (!response) return [];
490
- const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
491
- if (!Array.isArray(playlists)) return [];
492
- return playlists.map((p) => ({
506
+ return this.extractList(response, "playlists").map((p) => ({
493
507
  type: "folder",
494
508
  id: p.id,
495
509
  title: p.name,
@@ -499,26 +513,17 @@ var SignPresenterProvider = class {
499
513
  }));
500
514
  }
501
515
  async getMessages(playlistId, auth) {
502
- const pathFn = this.config.endpoints.messages;
503
- const response = await this.apiRequest(pathFn(playlistId), auth);
516
+ const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
504
517
  if (!response) return [];
505
- const messages = Array.isArray(response) ? response : response.data || response.messages || [];
506
- if (!Array.isArray(messages)) return [];
507
518
  const files = [];
508
- for (const msg of messages) {
519
+ for (const msg of this.extractList(response, "messages")) {
509
520
  if (!msg.url) continue;
510
521
  const url = msg.url;
511
- const seconds = msg.seconds;
512
- const file = createFile(
513
- msg.id,
514
- msg.name,
515
- url,
516
- {
517
- mediaType: detectMediaType(url, msg.mediaType),
518
- thumbnail: msg.thumbnail || msg.image,
519
- seconds
520
- }
521
- );
522
+ const file = createFile(msg.id, msg.name, url, {
523
+ mediaType: detectMediaType(url, msg.mediaType),
524
+ thumbnail: msg.thumbnail || msg.image,
525
+ seconds: msg.seconds
526
+ });
522
527
  file.downloadUrl = url;
523
528
  files.push(file);
524
529
  }
@@ -527,8 +532,7 @@ var SignPresenterProvider = class {
527
532
  async getPlaylist(path, auth, _resolution) {
528
533
  const { segments, depth } = parsePath(path);
529
534
  if (depth < 2 || segments[0] !== "playlists") return null;
530
- const playlistId = segments[1];
531
- const files = await this.getMessages(playlistId, auth);
535
+ const files = await this.getMessages(segments[1], auth);
532
536
  return files.length > 0 ? files : null;
533
537
  }
534
538
  async getInstructions(path, auth) {
@@ -538,42 +542,9 @@ var SignPresenterProvider = class {
538
542
  const files = await this.getMessages(playlistId, auth);
539
543
  if (files.length === 0) return null;
540
544
  const playlists = await this.getPlaylists(auth);
541
- const playlist = playlists.find((p) => p.id === playlistId);
542
- const title = playlist?.title || "Playlist";
543
- const actionItems = files.map((file) => ({
544
- id: file.id + "-action",
545
- itemType: "action",
546
- label: file.title,
547
- actionType: "play",
548
- seconds: file.seconds,
549
- children: [
550
- {
551
- id: file.id,
552
- itemType: "file",
553
- label: file.title,
554
- seconds: file.seconds,
555
- downloadUrl: file.downloadUrl || file.url,
556
- thumbnail: file.thumbnail
557
- }
558
- ]
559
- }));
560
- const sectionItem = {
561
- id: playlistId + "-section",
562
- itemType: "section",
563
- label: title,
564
- children: actionItems
565
- };
566
- const headerItem = {
567
- id: playlistId + "-header",
568
- itemType: "header",
569
- label: title,
570
- children: [sectionItem]
571
- };
572
- return { name: title, items: [headerItem] };
573
- }
574
- // Auth methods
575
- supportsDeviceFlow() {
576
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
545
+ const title = playlists.find((p) => p.id === playlistId)?.title || "Playlist";
546
+ const instructions = filesToInstructions(title, files, { id: playlistId + "-section", label: title });
547
+ return { name: title, items: [{ id: playlistId + "-header", itemType: "header", label: title, children: instructions.items }] };
577
548
  }
578
549
  generateCodeVerifier() {
579
550
  return this.oauthHelper.generateCodeVerifier();
@@ -621,6 +592,23 @@ function truncateForLabel(text) {
621
592
  const truncated = text.length > cutoff ? text.substring(0, cutoff) : text;
622
593
  return stripMarkdown(truncated) + (text.length > cutoff ? "..." : "");
623
594
  }
595
+ function getEmbedUrl(apiType, relatedId) {
596
+ if (!relatedId) return void 0;
597
+ const baseUrl = "https://lessons.church";
598
+ switch (apiType) {
599
+ case "action":
600
+ case "lessonAction":
601
+ return `${baseUrl}/embed/action/${relatedId}`;
602
+ case "addon":
603
+ case "lessonAddOn":
604
+ return `${baseUrl}/embed/addon/${relatedId}`;
605
+ case "section":
606
+ case "lessonSection":
607
+ return `${baseUrl}/embed/section/${relatedId}`;
608
+ default:
609
+ return void 0;
610
+ }
611
+ }
624
612
  async function convertAddOnToFile(addOn) {
625
613
  const apiPath = `/addOns/public/${addOn.id}`;
626
614
  const detail = await apiRequest(apiPath);
@@ -767,12 +755,13 @@ async function convertAddOnCategoryToInstructions(category) {
767
755
  }
768
756
 
769
757
  // src/providers/lessonsChurch/LessonsChurchProvider.ts
770
- var LessonsChurchProvider = class {
758
+ var LessonsChurchProvider = class extends BaseProvider {
771
759
  constructor() {
760
+ super(...arguments);
772
761
  this.id = "lessonschurch";
773
762
  this.name = "Lessons.church";
774
763
  this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
775
- this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [], endpoints: { programs: "/programs/public", studies: (programId) => `/studies/public/program/${programId}`, lessons: (studyId) => `/lessons/public/study/${studyId}`, venues: (lessonId) => `/venues/public/lesson/${lessonId}`, playlist: (venueId) => `/venues/playlist/${venueId}`, feed: (venueId) => `/venues/public/feed/${venueId}`, addOns: "/addOns/public", addOnDetail: (id) => `/addOns/public/${id}` } };
764
+ this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [] };
776
765
  this.requiresAuth = false;
777
766
  this.authTypes = ["none"];
778
767
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
@@ -828,28 +817,25 @@ var LessonsChurchProvider = class {
828
817
  return [];
829
818
  }
830
819
  async getPrograms() {
831
- const response = await apiRequest(this.config.endpoints.programs);
820
+ const response = await apiRequest("/programs/public");
832
821
  if (!response) return [];
833
822
  const programs = Array.isArray(response) ? response : [];
834
823
  return programs.map((p) => createFolder(p.id, p.name, `/lessons/${p.id}`, p.image));
835
824
  }
836
825
  async getStudies(programId, currentPath) {
837
- const pathFn = this.config.endpoints.studies;
838
- const response = await apiRequest(pathFn(programId));
826
+ const response = await apiRequest(`/studies/public/program/${programId}`);
839
827
  if (!response) return [];
840
828
  const studies = Array.isArray(response) ? response : [];
841
829
  return studies.map((s) => createFolder(s.id, s.name, `${currentPath}/${s.id}`, s.image)).sort((a, b) => a.title.localeCompare(b.title));
842
830
  }
843
831
  async getLessons(studyId, currentPath) {
844
- const pathFn = this.config.endpoints.lessons;
845
- const response = await apiRequest(pathFn(studyId));
832
+ const response = await apiRequest(`/lessons/public/study/${studyId}`);
846
833
  if (!response) return [];
847
834
  const lessons = Array.isArray(response) ? response : [];
848
835
  return lessons.map((l) => createFolder(l.id, l.name || l.title, `${currentPath}/${l.id}`, l.image));
849
836
  }
850
837
  async getVenues(lessonId, currentPath) {
851
- const pathFn = this.config.endpoints.venues;
852
- const response = await apiRequest(pathFn(lessonId));
838
+ const response = await apiRequest(`/venues/public/lesson/${lessonId}`);
853
839
  if (!response) return [];
854
840
  const lessonResponse = await apiRequest(`/lessons/public/${lessonId}`);
855
841
  const lessonImage = lessonResponse?.image;
@@ -872,7 +858,7 @@ var LessonsChurchProvider = class {
872
858
  return file ? [file] : [];
873
859
  }
874
860
  async getAddOnCategories() {
875
- const response = await apiRequest(this.config.endpoints.addOns);
861
+ const response = await apiRequest("/addOns/public");
876
862
  if (!response) return [];
877
863
  const addOns = Array.isArray(response) ? response : [];
878
864
  const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
@@ -880,7 +866,7 @@ var LessonsChurchProvider = class {
880
866
  }
881
867
  async getAddOnsByCategory(category, currentPath) {
882
868
  const decodedCategory = decodeURIComponent(category);
883
- const response = await apiRequest(this.config.endpoints.addOns);
869
+ const response = await apiRequest("/addOns/public");
884
870
  if (!response) return [];
885
871
  const allAddOns = Array.isArray(response) ? response : [];
886
872
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
@@ -923,9 +909,6 @@ var LessonsChurchProvider = class {
923
909
  }
924
910
  return null;
925
911
  }
926
- supportsDeviceFlow() {
927
- return false;
928
- }
929
912
  };
930
913
 
931
914
  // src/providers/registry.ts
@@ -941,18 +924,6 @@ function registerProvider(provider) {
941
924
  }
942
925
 
943
926
  // src/providers/b1Church/B1ChurchAuth.ts
944
- async function generateCodeChallenge(verifier) {
945
- const encoder = new TextEncoder();
946
- const data = encoder.encode(verifier);
947
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
948
- const hashArray = new Uint8Array(hashBuffer);
949
- let binary = "";
950
- for (let i = 0; i < hashArray.length; i++) {
951
- binary += String.fromCharCode(hashArray[i]);
952
- }
953
- const base64 = btoa(binary);
954
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
955
- }
956
927
  async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
957
928
  const codeChallenge = await generateCodeChallenge(codeVerifier);
958
929
  const oauthParams = new URLSearchParams({
@@ -976,7 +947,7 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
976
947
  return null;
977
948
  }
978
949
  const data = await response.json();
979
- return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
950
+ return toAuthData(data, { scope: config.scopes.join(" ") });
980
951
  } catch {
981
952
  return null;
982
953
  }
@@ -990,7 +961,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
990
961
  return null;
991
962
  }
992
963
  const data = await response.json();
993
- return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
964
+ return toAuthData(data, { scope: config.scopes.join(" ") });
994
965
  } catch {
995
966
  return null;
996
967
  }
@@ -1002,7 +973,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
1002
973
  const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1003
974
  if (!response.ok) return null;
1004
975
  const data = await response.json();
1005
- return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
976
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
1006
977
  } catch {
1007
978
  return null;
1008
979
  }
@@ -1027,78 +998,32 @@ async function authFetch(url, auth) {
1027
998
  }
1028
999
  }
1029
1000
  async function fetchMinistries(auth) {
1030
- const result = await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth);
1031
- return result || [];
1001
+ return await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth) || [];
1032
1002
  }
1033
1003
  async function fetchPlanTypes(ministryId, auth) {
1034
- const result = await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth);
1035
- return result || [];
1004
+ return await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth) || [];
1036
1005
  }
1037
1006
  async function fetchPlans(planTypeId, auth) {
1038
- const result = await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth);
1039
- return result || [];
1007
+ return await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth) || [];
1040
1008
  }
1041
- var DOING_API_BASE = "https://api.churchapps.org/doing";
1042
1009
  async function fetchCurrentPlanByType(planTypeId) {
1043
- try {
1044
- const response = await fetch(`${DOING_API_BASE}/plans/public/current/${planTypeId}`, { method: "GET", headers: { Accept: "application/json" } });
1045
- if (!response.ok) {
1046
- console.warn(`[B1Church] fetchCurrentPlanByType failed: HTTP ${response.status} for planTypeId=${planTypeId}`);
1047
- return null;
1048
- }
1049
- return await response.json();
1050
- } catch (err) {
1051
- console.error(`[B1Church] fetchCurrentPlanByType error: planTypeId=${planTypeId}`, err);
1052
- return null;
1053
- }
1010
+ return authFetch(`${API_BASE3}/doing/plans/public/current/${planTypeId}`);
1054
1011
  }
1055
1012
  async function fetchPlanItems(churchId, planId) {
1056
- try {
1057
- const response = await fetch(`${DOING_API_BASE}/planItems/presenter/${churchId}/${planId}`, { method: "GET", headers: { Accept: "application/json" } });
1058
- if (!response.ok) return [];
1059
- return await response.json() || [];
1060
- } catch {
1061
- return [];
1062
- }
1013
+ return await authFetch(`${API_BASE3}/doing/planItems/presenter/${churchId}/${planId}`) || [];
1063
1014
  }
1064
1015
  async function fetchVenueFeed(venueId) {
1065
- try {
1066
- const response = await fetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
1067
- if (!response.ok) {
1068
- console.warn(`[B1Church] fetchVenueFeed failed: HTTP ${response.status} for venueId=${venueId}`);
1069
- return null;
1070
- }
1071
- return await response.json();
1072
- } catch (err) {
1073
- console.error(`[B1Church] fetchVenueFeed error: venueId=${venueId}`, err);
1074
- return null;
1075
- }
1016
+ return authFetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`);
1076
1017
  }
1077
1018
  async function fetchVenueActions(venueId) {
1078
- try {
1079
- const response = await fetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
1080
- if (!response.ok) {
1081
- console.warn(`[B1Church] fetchVenueActions failed: HTTP ${response.status} for venueId=${venueId}`);
1082
- return null;
1083
- }
1084
- return await response.json();
1085
- } catch (err) {
1086
- console.error(`[B1Church] fetchVenueActions error: venueId=${venueId}`, err);
1087
- return null;
1088
- }
1019
+ return authFetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`);
1089
1020
  }
1090
1021
  async function fetchVenueImages(venueIds) {
1091
1022
  const map = /* @__PURE__ */ new Map();
1092
1023
  if (venueIds.length === 0) return map;
1093
- try {
1094
- const url = `${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`;
1095
- const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
1096
- if (!response.ok) return map;
1097
- const data = await response.json();
1098
- for (const row of data) {
1099
- if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1100
- }
1101
- } catch {
1024
+ const data = await authFetch(`${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`);
1025
+ for (const row of data || []) {
1026
+ if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1102
1027
  }
1103
1028
  return map;
1104
1029
  }
@@ -1187,18 +1112,7 @@ function getFileFromProviderFileItem(item) {
1187
1112
  };
1188
1113
  }
1189
1114
  function planItemToInstruction(item, thumbnail) {
1190
- let itemType = item.itemType;
1191
- switch (item.itemType) {
1192
- case "lessonSection":
1193
- itemType = "section";
1194
- break;
1195
- case "lessonAction":
1196
- itemType = "action";
1197
- break;
1198
- case "lessonAddOn":
1199
- itemType = "action";
1200
- break;
1201
- }
1115
+ const itemType = normalizeItemType(item.itemType);
1202
1116
  const isFileType = itemType === "file" || item.link && !item.children?.length;
1203
1117
  return {
1204
1118
  id: item.id,
@@ -1212,23 +1126,6 @@ function planItemToInstruction(item, thumbnail) {
1212
1126
  children: item.children?.map((child) => planItemToInstruction(child, thumbnail))
1213
1127
  };
1214
1128
  }
1215
- function getEmbedUrl(apiType, relatedId) {
1216
- if (!relatedId) return void 0;
1217
- const baseUrl = "https://lessons.church";
1218
- switch (apiType) {
1219
- case "action":
1220
- case "lessonAction":
1221
- return `${baseUrl}/embed/action/${relatedId}`;
1222
- case "addon":
1223
- case "lessonAddOn":
1224
- return `${baseUrl}/embed/addon/${relatedId}`;
1225
- case "section":
1226
- case "lessonSection":
1227
- return `${baseUrl}/embed/section/${relatedId}`;
1228
- default:
1229
- return void 0;
1230
- }
1231
- }
1232
1129
  function buildSectionActionsMap2(actionsResponse, thumbnail) {
1233
1130
  const sectionActionsMap = /* @__PURE__ */ new Map();
1234
1131
  if (actionsResponse?.sections) {
@@ -1342,9 +1239,9 @@ function isExternalProviderItem(item) {
1342
1239
  const itemType = item.itemType || "";
1343
1240
  return itemType.startsWith("provider");
1344
1241
  }
1345
- var B1ChurchProvider = class {
1242
+ var B1ChurchProvider = class extends BaseProvider {
1346
1243
  constructor() {
1347
- this.apiHelper = new ApiHelper();
1244
+ super(...arguments);
1348
1245
  this.deviceFlowHelper = new DeviceFlowHelper();
1349
1246
  // Unified cache for external provider data to avoid duplicate calls across methods
1350
1247
  this.externalContentCache = {
@@ -1355,16 +1252,13 @@ var B1ChurchProvider = class {
1355
1252
  this.id = "b1church";
1356
1253
  this.name = "B1.Church";
1357
1254
  this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
1358
- this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE3}/doing`, oauthBase: `${API_BASE3}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { planItems: (churchId, planId) => `/planFeed/presenter/${churchId}/${planId}` } };
1255
+ this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE3}/doing`, oauthBase: `${API_BASE3}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize" };
1359
1256
  this.appBase = "https://admin.b1.church";
1360
1257
  this.requiresAuth = true;
1361
1258
  this.authTypes = ["oauth_pkce", "device_flow"];
1362
1259
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1363
1260
  this.planTypeId = null;
1364
1261
  }
1365
- async apiRequest(path, authData) {
1366
- return this.apiHelper.apiRequest(this.config, this.id, path, authData);
1367
- }
1368
1262
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1369
1263
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1370
1264
  }
@@ -1510,8 +1404,7 @@ var B1ChurchProvider = class {
1510
1404
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
1511
1405
  return null;
1512
1406
  }
1513
- const pathFn = this.config.endpoints.planItems;
1514
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1407
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1515
1408
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1516
1409
  const externalInstructions = await fetchFromProviderProxy(
1517
1410
  "getInstructions",
@@ -1619,8 +1512,7 @@ var B1ChurchProvider = class {
1619
1512
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1620
1513
  return null;
1621
1514
  }
1622
- const pathFn = this.config.endpoints.planItems;
1623
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1515
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1624
1516
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1625
1517
  const externalFiles = await fetchFromProviderProxy(
1626
1518
  "getPlaylist",
@@ -1715,9 +1607,6 @@ var B1ChurchProvider = class {
1715
1607
  }
1716
1608
  return files;
1717
1609
  }
1718
- supportsDeviceFlow() {
1719
- return !!this.config.supportsDeviceFlow;
1720
- }
1721
1610
  };
1722
1611
 
1723
1612
  // src/providers/dropbox/DropboxConverters.ts
@@ -1743,38 +1632,13 @@ function filterMediaEntries(entries) {
1743
1632
  }
1744
1633
  return { folders, mediaFiles };
1745
1634
  }
1746
- function buildInstructionsFromFiles(files, folderName) {
1747
- const actionItems = files.map((file) => ({
1748
- id: file.id + "-action",
1749
- itemType: "action",
1750
- label: file.title,
1751
- actionType: "play",
1752
- children: [
1753
- {
1754
- id: file.id,
1755
- itemType: "file",
1756
- label: file.title,
1757
- seconds: file.seconds,
1758
- downloadUrl: file.downloadUrl || file.url,
1759
- thumbnail: file.thumbnail
1760
- }
1761
- ]
1762
- }));
1763
- const sectionItem = {
1764
- id: "dropbox-section",
1765
- itemType: "section",
1766
- label: folderName,
1767
- children: actionItems
1768
- };
1769
- return { name: folderName, items: [sectionItem] };
1770
- }
1771
1635
 
1772
1636
  // src/providers/dropbox/DropboxProvider.ts
1773
1637
  var MAX_PAGINATION_CALLS = 50;
1774
- var DropboxProvider = class {
1638
+ var DropboxProvider = class extends BaseProvider {
1775
1639
  constructor() {
1640
+ super(...arguments);
1776
1641
  this.oauthHelper = new OAuthHelper();
1777
- this.apiHelper = new ApiHelper();
1778
1642
  this.id = "dropbox";
1779
1643
  this.name = "Dropbox";
1780
1644
  this.logos = {
@@ -1787,12 +1651,7 @@ var DropboxProvider = class {
1787
1651
  apiBase: "https://api.dropboxapi.com",
1788
1652
  oauthBase: "https://www.dropbox.com/oauth2",
1789
1653
  clientId: "9io0q0q9angdz9j",
1790
- scopes: [],
1791
- endpoints: {
1792
- listFolder: "/2/files/list_folder",
1793
- listFolderContinue: "/2/files/list_folder/continue",
1794
- getTemporaryLink: "/2/files/get_temporary_link"
1795
- }
1654
+ scopes: []
1796
1655
  };
1797
1656
  this.requiresAuth = true;
1798
1657
  this.authTypes = ["oauth_pkce"];
@@ -1803,18 +1662,17 @@ var DropboxProvider = class {
1803
1662
  mediaLicensing: false
1804
1663
  };
1805
1664
  }
1806
- // -- Pagination helper --
1807
1665
  dropboxPost(endpoint, body, auth) {
1808
1666
  if (!auth) {
1809
1667
  console.error("[Dropbox] No auth token provided");
1810
1668
  return Promise.resolve(null);
1811
1669
  }
1812
- return this.apiHelper.apiRequest(this.config, this.id, endpoint, auth, "POST", body);
1670
+ return this.apiRequest(endpoint, auth, "POST", body);
1813
1671
  }
1814
1672
  async listAllEntries(dropboxPath, auth) {
1815
1673
  const allEntries = [];
1816
1674
  const response = await this.dropboxPost(
1817
- this.config.endpoints.listFolder,
1675
+ "/2/files/list_folder",
1818
1676
  { path: dropboxPath, recursive: false, include_media_info: false },
1819
1677
  auth
1820
1678
  );
@@ -1825,7 +1683,7 @@ var DropboxProvider = class {
1825
1683
  let calls = 0;
1826
1684
  while (hasMore && calls < MAX_PAGINATION_CALLS) {
1827
1685
  const cont = await this.dropboxPost(
1828
- this.config.endpoints.listFolderContinue,
1686
+ "/2/files/list_folder/continue",
1829
1687
  { cursor },
1830
1688
  auth
1831
1689
  );
@@ -1837,7 +1695,6 @@ var DropboxProvider = class {
1837
1695
  }
1838
1696
  return allEntries;
1839
1697
  }
1840
- // -- File link helpers --
1841
1698
  async getSharedLink(filePath, auth) {
1842
1699
  if (!auth) return null;
1843
1700
  try {
@@ -1875,7 +1732,7 @@ var DropboxProvider = class {
1875
1732
  }
1876
1733
  async getTemporaryLink(filePath, auth) {
1877
1734
  const response = await this.dropboxPost(
1878
- this.config.endpoints.getTemporaryLink,
1735
+ "/2/files/get_temporary_link",
1879
1736
  { path: filePath },
1880
1737
  auth
1881
1738
  );
@@ -1893,17 +1750,15 @@ var DropboxProvider = class {
1893
1750
  );
1894
1751
  return results.filter((r) => r.viewUrl || r.downloadUrl).map((r) => fileEntryToContentFile(r.entry, r.viewUrl || r.downloadUrl, r.downloadUrl));
1895
1752
  }
1896
- // -- Leaf detection --
1897
1753
  async checkIsLeaf(folderPath, auth) {
1898
1754
  const response = await this.dropboxPost(
1899
- this.config.endpoints.listFolder,
1755
+ "/2/files/list_folder",
1900
1756
  { path: folderPath, recursive: false, include_media_info: false },
1901
1757
  auth
1902
1758
  );
1903
1759
  if (!response) return false;
1904
1760
  return !response.entries.some((e) => e[".tag"] === "folder");
1905
1761
  }
1906
- // -- IProvider methods --
1907
1762
  async browse(path, auth) {
1908
1763
  const dropboxPath = path && path !== "/" ? path.startsWith("/") ? path : "/" + path : "";
1909
1764
  const allEntries = await this.listAllEntries(dropboxPath, auth);
@@ -1915,30 +1770,26 @@ var DropboxProvider = class {
1915
1770
  const fileItems = await this.resolveFileLinks(mediaFiles, auth);
1916
1771
  return [...folderItems, ...fileItems];
1917
1772
  }
1918
- async getPlaylist(path, auth, _resolution) {
1773
+ async resolveMediaFiles(path, auth) {
1919
1774
  if (!path || path === "/") return null;
1920
1775
  const dropboxPath = path.startsWith("/") ? path : "/" + path;
1921
1776
  const allEntries = await this.listAllEntries(dropboxPath, auth);
1922
1777
  const { mediaFiles } = filterMediaEntries(allEntries);
1923
1778
  if (mediaFiles.length === 0) return null;
1924
1779
  const files = await this.resolveFileLinks(mediaFiles, auth);
1925
- return files.length > 0 ? files : null;
1780
+ if (files.length === 0) return null;
1781
+ return { files, dropboxPath };
1782
+ }
1783
+ async getPlaylist(path, auth, _resolution) {
1784
+ const resolved = await this.resolveMediaFiles(path, auth);
1785
+ return resolved?.files ?? null;
1926
1786
  }
1927
1787
  async getInstructions(path, auth) {
1928
- if (!path || path === "/") return null;
1929
- const dropboxPath = path.startsWith("/") ? path : "/" + path;
1930
- const allEntries = await this.listAllEntries(dropboxPath, auth);
1931
- const { mediaFiles } = filterMediaEntries(allEntries);
1932
- if (mediaFiles.length === 0) return null;
1933
- const files = await this.resolveFileLinks(mediaFiles, auth);
1934
- if (files.length === 0) return null;
1935
- const segments = dropboxPath.split("/").filter(Boolean);
1788
+ const resolved = await this.resolveMediaFiles(path, auth);
1789
+ if (!resolved) return null;
1790
+ const segments = resolved.dropboxPath.split("/").filter(Boolean);
1936
1791
  const folderName = segments[segments.length - 1] || "Dropbox";
1937
- return buildInstructionsFromFiles(files, folderName);
1938
- }
1939
- // -- Auth methods --
1940
- supportsDeviceFlow() {
1941
- return false;
1792
+ return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
1942
1793
  }
1943
1794
  generateCodeVerifier() {
1944
1795
  return this.oauthHelper.generateCodeVerifier();
@@ -1976,61 +1827,43 @@ var DropboxProvider = class {
1976
1827
  body: params.toString()
1977
1828
  });
1978
1829
  if (!response.ok) return null;
1979
- const data = await response.json();
1980
- return {
1981
- access_token: data.access_token,
1982
- refresh_token: data.refresh_token,
1983
- token_type: data.token_type || "Bearer",
1984
- created_at: Math.floor(Date.now() / 1e3),
1985
- expires_in: data.expires_in,
1986
- scope: data.scope || ""
1987
- };
1830
+ return toAuthData(await response.json());
1988
1831
  } catch {
1989
1832
  return null;
1990
1833
  }
1991
1834
  }
1992
1835
  };
1993
1836
 
1994
- // src/providers/planningCenter/PlanningCenterConverters.ts
1995
- var apiHelper = new ApiHelper();
1837
+ // src/providers/planningCenter/PlanningCenterProvider.ts
1838
+ var ONE_WEEK_MS = 6048e5;
1996
1839
  function formatDate(dateString) {
1997
- const date = new Date(dateString);
1998
- return date.toISOString().slice(0, 10);
1840
+ return new Date(dateString).toISOString().slice(0, 10);
1999
1841
  }
2000
-
2001
- // src/providers/planningCenter/PlanningCenterProvider.ts
2002
- var PlanningCenterProvider = class {
1842
+ var PlanningCenterProvider = class extends BaseProvider {
2003
1843
  constructor() {
2004
- this.apiHelper = new ApiHelper();
1844
+ super(...arguments);
2005
1845
  this.id = "planningcenter";
2006
1846
  this.name = "Planning Center";
2007
1847
  this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
2008
- this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"], endpoints: { serviceTypes: "/services/v2/service_types", plans: (serviceTypeId) => `/services/v2/service_types/${serviceTypeId}/plans`, planItems: (serviceTypeId, planId) => `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items`, song: (itemId) => `/services/v2/songs/${itemId}`, arrangement: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}`, arrangementSections: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}/sections`, media: (mediaId) => `/services/v2/media/${mediaId}`, mediaAttachments: (mediaId) => `/services/v2/media/${mediaId}/attachments` } };
2009
- this.ONE_WEEK_MS = 6048e5;
1848
+ this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
2010
1849
  this.requiresAuth = true;
2011
1850
  this.authTypes = ["oauth_pkce"];
2012
- this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
2013
- }
2014
- async apiRequest(path, auth) {
2015
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
1851
+ // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1852
+ this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
2016
1853
  }
2017
1854
  async browse(path, auth) {
2018
1855
  const { segments, depth } = parsePath(path);
2019
1856
  if (depth === 0) {
2020
1857
  return [{ type: "folder", id: "serviceTypes-root", title: "Service Types", path: "/serviceTypes" }];
2021
1858
  }
2022
- const root = segments[0];
2023
- if (root !== "serviceTypes") return [];
1859
+ if (segments[0] !== "serviceTypes") return [];
2024
1860
  if (depth === 1) return this.getServiceTypes(auth);
2025
1861
  if (depth === 2) return this.getPlans(segments[1], path, auth);
2026
1862
  if (depth === 3) return this.getPlanItems(segments[1], segments[2], auth);
2027
1863
  return [];
2028
1864
  }
2029
1865
  async getServiceTypes(auth) {
2030
- const response = await this.apiRequest(
2031
- this.config.endpoints.serviceTypes,
2032
- auth
2033
- );
1866
+ const response = await this.apiRequest("/services/v2/service_types", auth);
2034
1867
  if (!response?.data) return [];
2035
1868
  return response.data.map((serviceType) => ({
2036
1869
  type: "folder",
@@ -2040,9 +1873,8 @@ var PlanningCenterProvider = class {
2040
1873
  }));
2041
1874
  }
2042
1875
  async getPlans(serviceTypeId, currentPath, auth) {
2043
- const pathFn = this.config.endpoints.plans;
2044
1876
  const response = await this.apiRequest(
2045
- `${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
1877
+ `/services/v2/service_types/${serviceTypeId}/plans?filter=future&order=sort_date`,
2046
1878
  auth
2047
1879
  );
2048
1880
  if (!response?.data) return [];
@@ -2050,7 +1882,7 @@ var PlanningCenterProvider = class {
2050
1882
  const filteredPlans = response.data.filter((plan) => {
2051
1883
  if (plan.attributes.items_count === 0) return false;
2052
1884
  const planDate = new Date(plan.attributes.sort_date).getTime();
2053
- return planDate < now + this.ONE_WEEK_MS;
1885
+ return planDate < now + ONE_WEEK_MS;
2054
1886
  });
2055
1887
  return filteredPlans.map((plan) => ({
2056
1888
  type: "folder",
@@ -2061,17 +1893,13 @@ var PlanningCenterProvider = class {
2061
1893
  }));
2062
1894
  }
2063
1895
  async getPlanItems(serviceTypeId, planId, auth) {
2064
- const pathFn = this.config.endpoints.planItems;
2065
1896
  const response = await this.apiRequest(
2066
- `${pathFn(serviceTypeId, planId)}?per_page=100`,
1897
+ `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items?per_page=100`,
2067
1898
  auth
2068
1899
  );
2069
1900
  if (!response?.data) return [];
2070
1901
  return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
2071
1902
  }
2072
- supportsDeviceFlow() {
2073
- return false;
2074
- }
2075
1903
  };
2076
1904
 
2077
1905
  // src/providers/bibleProject/data.json
@@ -4466,8 +4294,9 @@ var data_default = {
4466
4294
  };
4467
4295
 
4468
4296
  // src/providers/bibleProject/BibleProjectProvider.ts
4469
- var BibleProjectProvider = class {
4297
+ var BibleProjectProvider = class extends BaseProvider {
4470
4298
  constructor() {
4299
+ super(...arguments);
4471
4300
  this.id = "bibleproject";
4472
4301
  this.name = "The Bible Project";
4473
4302
  this.logos = {
@@ -4480,10 +4309,8 @@ var BibleProjectProvider = class {
4480
4309
  apiBase: "https://bibleproject.com",
4481
4310
  oauthBase: "",
4482
4311
  clientId: "",
4483
- scopes: [],
4484
- endpoints: { downloads: "/downloads/" }
4312
+ scopes: []
4485
4313
  };
4486
- this.data = data_default;
4487
4314
  this.requiresAuth = false;
4488
4315
  this.authTypes = ["none"];
4489
4316
  this.capabilities = {
@@ -4492,133 +4319,71 @@ var BibleProjectProvider = class {
4492
4319
  instructions: true,
4493
4320
  mediaLicensing: false
4494
4321
  };
4322
+ this.data = data_default;
4495
4323
  }
4496
4324
  getMuxThumbnail(muxPlaybackId, width = 400, height = 225) {
4497
4325
  return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=${width}&height=${height}`;
4498
4326
  }
4499
- async browse(path, _auth) {
4500
- const { segments, depth } = parsePath(path);
4501
- if (depth === 0) {
4502
- return this.getCollections();
4503
- }
4504
- if (depth === 1) {
4505
- const collectionSlug = segments[0];
4506
- return this.getLessonFolders(collectionSlug, path);
4507
- }
4508
- if (depth === 2) {
4509
- const collectionSlug = segments[0];
4510
- const videoId = segments[1];
4511
- return this.getVideoFile(collectionSlug, videoId);
4512
- }
4513
- return [];
4514
- }
4515
- getCollections() {
4516
- return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4517
- type: "folder",
4518
- id: slugify(collection.name),
4519
- title: collection.name,
4520
- thumbnail: collection.image || void 0,
4521
- path: `/${slugify(collection.name)}`
4522
- }));
4523
- }
4524
- getLessonFolders(collectionSlug, currentPath) {
4525
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4526
- if (!collection) return [];
4527
- return collection.videos.map((video) => ({
4528
- type: "folder",
4529
- id: video.id,
4530
- title: video.title,
4327
+ videoToFile(video) {
4328
+ return createFile(video.id, video.title, video.videoUrl, {
4329
+ mediaType: "video",
4531
4330
  thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4532
- isLeaf: true,
4533
- path: `${currentPath}/${video.id}`
4534
- }));
4535
- }
4536
- getVideoFile(collectionSlug, videoId) {
4537
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4538
- if (!collection) return [];
4539
- const video = collection.videos.find((v) => v.id === videoId);
4540
- if (!video) return [];
4541
- return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId, seconds: 0 })];
4331
+ muxPlaybackId: video.muxPlaybackId,
4332
+ seconds: 0
4333
+ });
4542
4334
  }
4543
- async getPlaylist(path, _auth, _resolution) {
4335
+ /** Resolve a path to its collection and files: all videos at depth 1, a single video at depth 2. */
4336
+ resolve(path) {
4544
4337
  const { segments, depth } = parsePath(path);
4545
- if (depth < 1) return null;
4546
- const collectionSlug = segments[0];
4547
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4338
+ if (depth < 1 || depth > 2) return null;
4339
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4548
4340
  if (!collection) return null;
4549
4341
  if (depth === 1) {
4550
- const files = collection.videos.map((video) => ({ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, thumbnail: this.getMuxThumbnail(video.muxPlaybackId), muxPlaybackId: video.muxPlaybackId, seconds: 0 }));
4551
- return files.length > 0 ? files : null;
4342
+ return { collection, files: collection.videos.map((v) => this.videoToFile(v)), name: collection.name };
4552
4343
  }
4553
- if (depth === 2) {
4554
- const videoId = segments[1];
4555
- const video = collection.videos.find((v) => v.id === videoId);
4556
- if (!video) return null;
4557
- return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, thumbnail: this.getMuxThumbnail(video.muxPlaybackId), muxPlaybackId: video.muxPlaybackId, seconds: 0 }];
4558
- }
4559
- return null;
4344
+ const video = collection.videos.find((v) => v.id === segments[1]);
4345
+ if (!video) return null;
4346
+ return { collection, files: [this.videoToFile(video)], name: video.title };
4560
4347
  }
4561
- async getInstructions(path, _auth) {
4348
+ async browse(path, _auth) {
4562
4349
  const { segments, depth } = parsePath(path);
4563
- if (depth < 1) return null;
4564
- const collectionSlug = segments[0];
4565
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4566
- if (!collection) return null;
4350
+ if (depth === 0) {
4351
+ return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4352
+ type: "folder",
4353
+ id: slugify(collection.name),
4354
+ title: collection.name,
4355
+ thumbnail: collection.image || void 0,
4356
+ path: `/${slugify(collection.name)}`
4357
+ }));
4358
+ }
4567
4359
  if (depth === 1) {
4568
- const actionItems = collection.videos.map((video) => ({
4569
- id: video.id + "-action",
4570
- itemType: "action",
4571
- label: video.title,
4572
- actionType: "play",
4573
- children: [
4574
- {
4575
- id: video.id,
4576
- itemType: "file",
4577
- label: video.title,
4578
- downloadUrl: video.videoUrl,
4579
- thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
4580
- }
4581
- ]
4360
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4361
+ if (!collection) return [];
4362
+ return collection.videos.map((video) => ({
4363
+ type: "folder",
4364
+ id: video.id,
4365
+ title: video.title,
4366
+ thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4367
+ isLeaf: true,
4368
+ path: `${path}/${video.id}`
4582
4369
  }));
4583
- const sectionItem = {
4584
- id: slugify(collection.name) + "-section",
4585
- itemType: "section",
4586
- label: collection.name,
4587
- children: actionItems
4588
- };
4589
- return { name: collection.name, items: [sectionItem] };
4590
4370
  }
4591
4371
  if (depth === 2) {
4592
- const videoId = segments[1];
4593
- const video = collection.videos.find((v) => v.id === videoId);
4594
- if (!video) return null;
4595
- const actionItem = {
4596
- id: video.id + "-action",
4597
- itemType: "action",
4598
- label: video.title,
4599
- actionType: "play",
4600
- children: [
4601
- {
4602
- id: video.id,
4603
- itemType: "file",
4604
- label: video.title,
4605
- downloadUrl: video.videoUrl,
4606
- thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
4607
- }
4608
- ]
4609
- };
4610
- const sectionItem = {
4611
- id: slugify(collection.name) + "-section",
4612
- itemType: "section",
4613
- label: collection.name,
4614
- children: [actionItem]
4615
- };
4616
- return { name: video.title, items: [sectionItem] };
4372
+ return this.resolve(path)?.files ?? [];
4617
4373
  }
4618
- return null;
4374
+ return [];
4619
4375
  }
4620
- supportsDeviceFlow() {
4621
- return false;
4376
+ async getPlaylist(path, _auth, _resolution) {
4377
+ const resolved = this.resolve(path);
4378
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
4379
+ }
4380
+ async getInstructions(path, _auth) {
4381
+ const resolved = this.resolve(path);
4382
+ if (!resolved) return null;
4383
+ return filesToInstructions(resolved.name, resolved.files, {
4384
+ id: slugify(resolved.collection.name) + "-section",
4385
+ label: resolved.collection.name
4386
+ });
4622
4387
  }
4623
4388
  };
4624
4389
 
@@ -12242,8 +12007,9 @@ function buildLessonInstructions(lesson) {
12242
12007
  }
12243
12008
 
12244
12009
  // src/providers/highVoltage/HighVoltageKidsProvider.ts
12245
- var HighVoltageKidsProvider = class {
12010
+ var HighVoltageKidsProvider = class extends BaseProvider {
12246
12011
  constructor() {
12012
+ super(...arguments);
12247
12013
  this.id = "highvoltagekids";
12248
12014
  this.name = "High Voltage Kids";
12249
12015
  this.logos = {
@@ -12256,8 +12022,7 @@ var HighVoltageKidsProvider = class {
12256
12022
  apiBase: "https://highvoltagekids.com",
12257
12023
  oauthBase: "",
12258
12024
  clientId: "",
12259
- scopes: [],
12260
- endpoints: { downloads: "/membership-downloads/" }
12025
+ scopes: []
12261
12026
  };
12262
12027
  this.data = data_default2;
12263
12028
  this.requiresAuth = false;
@@ -12307,9 +12072,6 @@ var HighVoltageKidsProvider = class {
12307
12072
  }
12308
12073
  return null;
12309
12074
  }
12310
- supportsDeviceFlow() {
12311
- return false;
12312
- }
12313
12075
  };
12314
12076
 
12315
12077
  // src/providers/jesusFilm/JesusFilmProvider.ts
@@ -12321,8 +12083,14 @@ var CATEGORY_MAP = {
12321
12083
  "series": "series",
12322
12084
  "collections": "collection"
12323
12085
  };
12324
- var JesusFilmProvider = class {
12086
+ var CATEGORY_NAMES = {
12087
+ "feature-films": "Feature Films",
12088
+ "series": "Series",
12089
+ "collections": "Collections"
12090
+ };
12091
+ var JesusFilmProvider = class extends BaseProvider {
12325
12092
  constructor() {
12093
+ super(...arguments);
12326
12094
  this.id = "jesusfilm";
12327
12095
  this.name = "Jesus Film Project";
12328
12096
  this.logos = {
@@ -12335,8 +12103,7 @@ var JesusFilmProvider = class {
12335
12103
  apiBase: API_BASE4,
12336
12104
  oauthBase: "",
12337
12105
  clientId: "",
12338
- scopes: [],
12339
- endpoints: { watch: "/watch" }
12106
+ scopes: []
12340
12107
  };
12341
12108
  this.requiresAuth = false;
12342
12109
  this.authTypes = ["none"];
@@ -12347,6 +12114,7 @@ var JesusFilmProvider = class {
12347
12114
  mediaLicensing: false
12348
12115
  };
12349
12116
  }
12117
+ // Not ApiHelper: Arclight needs the apiKey query param and callers rely on throw-on-error.
12350
12118
  async fetchApi(path) {
12351
12119
  const separator = path.includes("?") ? "&" : "?";
12352
12120
  const url = `${API_BASE4}${path}${separator}apiKey=${API_KEY}`;
@@ -12354,6 +12122,12 @@ var JesusFilmProvider = class {
12354
12122
  if (!response.ok) throw new Error(`Arclight API error: ${response.status}`);
12355
12123
  return response.json();
12356
12124
  }
12125
+ async fetchCategoryComponents(subType) {
12126
+ const data = await this.fetchApi(
12127
+ `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12128
+ );
12129
+ return data._embedded.mediaComponents;
12130
+ }
12357
12131
  async browse(path, _auth) {
12358
12132
  const { segments, depth } = parsePath(path);
12359
12133
  if (depth === 0) return this.getCategories();
@@ -12376,19 +12150,14 @@ var JesusFilmProvider = class {
12376
12150
  async getItemsInCategory(category) {
12377
12151
  const subType = CATEGORY_MAP[category];
12378
12152
  if (!subType) return [];
12379
- const data = await this.fetchApi(
12380
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12381
- );
12382
- return data._embedded.mediaComponents.map((item) => {
12383
- const isLeaf = item.containsCount === 0;
12384
- return createFolder(
12385
- item.mediaComponentId,
12386
- item.title,
12387
- `/${category}/${item.mediaComponentId}`,
12388
- item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12389
- isLeaf
12390
- );
12391
- });
12153
+ const components = await this.fetchCategoryComponents(subType);
12154
+ return components.map((item) => createFolder(
12155
+ item.mediaComponentId,
12156
+ item.title,
12157
+ `/${category}/${item.mediaComponentId}`,
12158
+ item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12159
+ item.containsCount === 0
12160
+ ));
12392
12161
  }
12393
12162
  async getVideoFile(mediaComponentId) {
12394
12163
  const variant = await this.fetchApi(
@@ -12403,11 +12172,10 @@ var JesusFilmProvider = class {
12403
12172
  const title = component?.title || mediaComponentId;
12404
12173
  const seconds = component ? Math.round(component.lengthInMilliseconds / 1e3) : 0;
12405
12174
  const thumbnail = component?.imageUrls.mobileCinematicHigh || component?.imageUrls.videoStill;
12406
- const muxPlaybackId = this.extractMuxPlaybackId(downloadUrl);
12407
12175
  return [
12408
12176
  createFile(mediaComponentId, title, downloadUrl, {
12409
12177
  mediaType: "video",
12410
- muxPlaybackId,
12178
+ muxPlaybackId: this.extractMuxPlaybackId(downloadUrl),
12411
12179
  seconds,
12412
12180
  thumbnail
12413
12181
  })
@@ -12419,9 +12187,8 @@ var JesusFilmProvider = class {
12419
12187
  );
12420
12188
  const childIds = linksData.linkedMediaComponentIds?.contains;
12421
12189
  if (!childIds || childIds.length === 0) return [];
12422
- const idsParam = childIds.join(",");
12423
12190
  const childrenData = await this.fetchApi(
12424
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12191
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12425
12192
  );
12426
12193
  if (!childrenData._embedded?.mediaComponents) return [];
12427
12194
  return childrenData._embedded.mediaComponents.map((item) => createFolder(
@@ -12436,36 +12203,6 @@ var JesusFilmProvider = class {
12436
12203
  const match = url.match(/stream\.mux\.com\/([^/]+)/);
12437
12204
  return match ? match[1] : void 0;
12438
12205
  }
12439
- async getPlaylist(path, _auth, _resolution) {
12440
- const { segments, depth } = parsePath(path);
12441
- if (depth < 1) return null;
12442
- const category = segments[0];
12443
- if (depth >= 3) {
12444
- const lastId = segments[depth - 1];
12445
- const items = await this.getVideoFile(lastId);
12446
- const files2 = items.filter((item) => item.type === "file");
12447
- if (files2.length > 0) return files2;
12448
- const containerFiles = await this.fetchContainerVideoFiles(lastId);
12449
- return containerFiles.length > 0 ? containerFiles : null;
12450
- }
12451
- if (depth === 2) {
12452
- if (category === "feature-films") {
12453
- const items = await this.getVideoFile(segments[1]);
12454
- if (items.length === 0) return null;
12455
- return items.filter((item) => item.type === "file");
12456
- }
12457
- const files2 = await this.fetchContainerVideoFiles(segments[1]);
12458
- return files2.length > 0 ? files2 : null;
12459
- }
12460
- const subType = CATEGORY_MAP[category];
12461
- if (!subType) return null;
12462
- const data = await this.fetchApi(
12463
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12464
- );
12465
- const contentComponents = data._embedded.mediaComponents.filter((item) => item.containsCount === 0);
12466
- const files = await this.fetchVideoFiles(contentComponents);
12467
- return files.length > 0 ? files : null;
12468
- }
12469
12206
  async fetchVideoFiles(components) {
12470
12207
  const files = [];
12471
12208
  for (const component of components) {
@@ -12496,9 +12233,8 @@ var JesusFilmProvider = class {
12496
12233
  );
12497
12234
  const childIds = linksData.linkedMediaComponentIds?.contains;
12498
12235
  if (!childIds || childIds.length === 0) return [];
12499
- const idsParam = childIds.join(",");
12500
12236
  const childrenData = await this.fetchApi(
12501
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12237
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12502
12238
  );
12503
12239
  if (!childrenData._embedded?.mediaComponents) return [];
12504
12240
  const contentItems = childrenData._embedded.mediaComponents.filter((c) => c.containsCount === 0);
@@ -12510,64 +12246,47 @@ var JesusFilmProvider = class {
12510
12246
  }
12511
12247
  return files;
12512
12248
  }
12249
+ async getPlaylist(path, _auth, _resolution) {
12250
+ const { segments, depth } = parsePath(path);
12251
+ if (depth < 1) return null;
12252
+ const category = segments[0];
12253
+ if (depth >= 3) {
12254
+ const lastId = segments[depth - 1];
12255
+ const items = await this.getVideoFile(lastId);
12256
+ const files2 = items.filter((item) => item.type === "file");
12257
+ if (files2.length > 0) return files2;
12258
+ const containerFiles = await this.fetchContainerVideoFiles(lastId);
12259
+ return containerFiles.length > 0 ? containerFiles : null;
12260
+ }
12261
+ if (depth === 2) {
12262
+ if (category === "feature-films") {
12263
+ const items = await this.getVideoFile(segments[1]);
12264
+ if (items.length === 0) return null;
12265
+ return items.filter((item) => item.type === "file");
12266
+ }
12267
+ const files2 = await this.fetchContainerVideoFiles(segments[1]);
12268
+ return files2.length > 0 ? files2 : null;
12269
+ }
12270
+ const subType = CATEGORY_MAP[category];
12271
+ if (!subType) return null;
12272
+ const components = await this.fetchCategoryComponents(subType);
12273
+ const files = await this.fetchVideoFiles(components.filter((item) => item.containsCount === 0));
12274
+ return files.length > 0 ? files : null;
12275
+ }
12513
12276
  async buildVideoInstructions(mediaComponentId, category) {
12514
12277
  const items = await this.getVideoFile(mediaComponentId);
12515
- if (items.length === 0) return null;
12516
- const file = items[0];
12517
- if (file.type !== "file") return null;
12518
- const actionItem = {
12519
- id: mediaComponentId + "-action",
12520
- itemType: "action",
12521
- label: file.title,
12522
- actionType: "play",
12523
- children: [
12524
- {
12525
- id: mediaComponentId,
12526
- itemType: "file",
12527
- label: file.title,
12528
- downloadUrl: file.url,
12529
- thumbnail: file.thumbnail
12530
- }
12531
- ]
12532
- };
12533
- const sectionItem = {
12534
- id: category + "-section",
12535
- itemType: "section",
12536
- label: file.title,
12537
- children: [actionItem]
12538
- };
12539
- return { name: file.title, items: [sectionItem] };
12278
+ const file = items.find((item) => item.type === "file");
12279
+ if (!file) return null;
12280
+ return filesToInstructions(file.title, [file], { id: category + "-section", label: file.title });
12540
12281
  }
12541
- async buildContainerInstructions(containerId, category) {
12282
+ async buildContainerInstructions(containerId) {
12542
12283
  const containerData = await this.fetchApi(
12543
12284
  `/media-components?ids=${containerId}&languageIds=${LANGUAGE_ID}`
12544
12285
  );
12545
- const container = containerData._embedded?.mediaComponents?.[0];
12546
- const containerName = container?.title || containerId;
12286
+ const containerName = containerData._embedded?.mediaComponents?.[0]?.title || containerId;
12547
12287
  const videoFiles = await this.fetchContainerVideoFiles(containerId);
12548
12288
  if (videoFiles.length === 0) return { name: containerName, items: [] };
12549
- const actionItems = videoFiles.map((file) => ({
12550
- id: file.id + "-action",
12551
- itemType: "action",
12552
- label: file.title,
12553
- actionType: "play",
12554
- children: [
12555
- {
12556
- id: file.id,
12557
- itemType: "file",
12558
- label: file.title,
12559
- downloadUrl: file.url,
12560
- thumbnail: file.thumbnail
12561
- }
12562
- ]
12563
- }));
12564
- const sectionItem = {
12565
- id: containerId + "-section",
12566
- itemType: "section",
12567
- label: containerName,
12568
- children: actionItems
12569
- };
12570
- return { name: containerName, items: [sectionItem] };
12289
+ return filesToInstructions(containerName, videoFiles, { id: containerId + "-section", label: containerName });
12571
12290
  }
12572
12291
  async getInstructions(path, _auth) {
12573
12292
  const { segments, depth } = parsePath(path);
@@ -12576,61 +12295,19 @@ var JesusFilmProvider = class {
12576
12295
  const subType = CATEGORY_MAP[category];
12577
12296
  if (!subType) return null;
12578
12297
  if (depth === 1) {
12579
- const data = await this.fetchApi(
12580
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12581
- );
12582
- const categoryName = category === "feature-films" ? "Feature Films" : category === "series" ? "Series" : "Collections";
12583
- const actionItems = await Promise.all(
12584
- data._embedded.mediaComponents.map(async (component) => {
12585
- let downloadUrl;
12586
- if (component.containsCount === 0) {
12587
- try {
12588
- const variant = await this.fetchApi(
12589
- `/media-components/${component.mediaComponentId}/languages/${LANGUAGE_ID}?platform=web`
12590
- );
12591
- downloadUrl = variant.downloadUrls.high?.url || variant.downloadUrls.low?.url;
12592
- } catch {
12593
- }
12594
- }
12595
- return {
12596
- id: component.mediaComponentId + "-action",
12597
- itemType: "action",
12598
- label: component.title,
12599
- actionType: "play",
12600
- children: downloadUrl ? [
12601
- {
12602
- id: component.mediaComponentId,
12603
- itemType: "file",
12604
- label: component.title,
12605
- downloadUrl,
12606
- thumbnail: component.imageUrls.mobileCinematicHigh || component.imageUrls.videoStill
12607
- }
12608
- ] : []
12609
- };
12610
- })
12611
- );
12612
- const sectionItem = {
12613
- id: category + "-section",
12614
- itemType: "section",
12615
- label: categoryName,
12616
- children: actionItems.filter((a) => a.children && a.children.length > 0)
12617
- };
12618
- return { name: categoryName, items: [sectionItem] };
12298
+ const categoryName = CATEGORY_NAMES[category];
12299
+ const components = await this.fetchCategoryComponents(subType);
12300
+ const files = await this.fetchVideoFiles(components.filter((c) => c.containsCount === 0));
12301
+ return filesToInstructions(categoryName, files, { id: category + "-section", label: categoryName });
12619
12302
  }
12620
12303
  if (depth === 2) {
12621
12304
  if (category === "feature-films") return this.buildVideoInstructions(segments[1], category);
12622
- return this.buildContainerInstructions(segments[1], category);
12623
- }
12624
- if (depth >= 3) {
12625
- const lastId = segments[depth - 1];
12626
- const videoResult = await this.buildVideoInstructions(lastId, category);
12627
- if (videoResult) return videoResult;
12628
- return this.buildContainerInstructions(lastId, category);
12305
+ return this.buildContainerInstructions(segments[1]);
12629
12306
  }
12630
- return null;
12631
- }
12632
- supportsDeviceFlow() {
12633
- return false;
12307
+ const lastId = segments[depth - 1];
12308
+ const videoResult = await this.buildVideoInstructions(lastId, category);
12309
+ if (videoResult) return videoResult;
12310
+ return this.buildContainerInstructions(lastId);
12634
12311
  }
12635
12312
  };
12636
12313
 
@@ -12674,35 +12351,15 @@ function convertPlaylistToFiles(playlist) {
12674
12351
  return file;
12675
12352
  });
12676
12353
  }
12677
- function convertPlaylistToInstructions(playlist) {
12678
- const files = convertPlaylistToFiles(playlist);
12679
- const items = files.map((file) => ({
12680
- id: file.id + "-action",
12681
- itemType: "action",
12682
- label: file.title,
12683
- actionType: "play",
12684
- children: [
12685
- {
12686
- id: file.id,
12687
- itemType: "file",
12688
- label: file.title,
12689
- seconds: file.seconds,
12690
- downloadUrl: file.url,
12691
- thumbnail: file.thumbnail
12692
- }
12693
- ]
12694
- }));
12695
- return { name: playlist.lesson_title || "Lesson", items };
12696
- }
12697
12354
 
12698
12355
  // src/providers/cbn/logo.ts
12699
12356
  var CBN_LOGO = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/tpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMi1jMDAwIDc5LjFiNjVhNzliNCwgMjAyMi8wNi8xMy0yMjowMTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjczNTFDMjdFNENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjczNTFDMjdENENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENDIDIwMTcgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0idXVpZDo4NGJiYzc1OC1iNjBhLTFlNGQtODJlMS1mYTQyNzhlMmZhMTciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OTQzMjNlNWMtYjBlMS00YTliLWJjZWUtODljYjRlOGVjZjAwIi8+IDxkYzp0aXRsZT4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+Q0JOLUZMQU1FPC9yZGY6bGk+IDwvcmRmOkFsdD4gPC9kYzp0aXRsZT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz40pvOUAAAC2VBMVEUdmtYhnNf8/v/7/f4emtb9/v8fm9by+f0nntjU7Pf6/f4gm9b2+/0lntfk8/q43/L3+/5Nr9/5/P4poNg1pdovotnj8voknddQsd84ptsqoNjp9fve8PnX7fgpn9hRsd/1+/3M6Pba7vhmuuO/4vMinNfG5vUtodmGyenm9Pq64PKOzOrz+f2c0+15w+fw+PyVz+zx+fyq2fCl1+9tvuREq91ateGx3PHv+Pz4/P6d0+0mnthxwOXf8fng8fkroNlhuOI3pduk1u7B4/Ts9/zq9ftAqdxHrd54w+ft9/yu2/CKy+pOsN88qNxft+I+qdy74fNIrd7n9PtDq93z+v1Ws+BrveTS6/eW0OxCq91/xuiz3fFvvuXN6fYzpNqZ0e2Ly+oxo9q94fO94vPc7/nF5fSn2O/o9ft2wuYwotk2pdtVs+Du9/ySzutqvOQjnddPsN8uotl3wubY7vjC5PSEyOl1weZpvORSsuBctuHl9Po5p9vT6/eMzOo7p9uBx+iv2/GTz+vh8vq03fFguOLs9vvV7Pe23/JXtOBkuuMon9i+4vOQzetfuOKg1e6Jyurr9vvi8vp+xuhnu+Op2e/R6vf0+v3c8Pnb7/h+xehLr946p9ssodmj1u40pNqUz+zd8PlJrt57xOdXtOFdt+Kw3PFAqtxbtuG33/J8xOd6xOet2vDI5vW54PJwv+VBqt1FrN2a0u1luuOAxuhsveSh1e6s2vCNzOqt2/Ci1e7E5PTJ5/XH5vXL6PY9qNx0weZ9xedYteHK5/WFyOmCx+hnu+TQ6vZovOS13vLD5PSb0u1Mr9/O6fay3fG84fPZ7vhiueNKrt5zwObP6vZZteFjueOm1+9TsuCf1O6o2O+DyOhUs+DF5fWPzeuX0ezW7fhGrN0wo9qOzetet+IypNqr2fC13vGY0exvv+WRzuue1O2IyumHyulywOVuvuU/qdye0+3A4/T///96A0GMAAAA83RSTlP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wB12P98AAAWOklEQVR42uzd5YMUx9YG8JpZN3ZZdHFblg3u7u7uGtzdITgkwUJCsBCc4CRA8BASEgJxtxtPbpKrr52/4EUvK3VqWk5V9+ye5+vOdE3Xb2e6u1QAx1cRXAUMwmEQBuEwCINwGIRBOAzCIBwG4TAIg3AYhEE4DMIgHAZhEA6DcBiEQTgMwiAcBmEQDoMwCIdBOAzCIBwGYRAOgzAIh0EYhMMgHAZhEA6DMAiHQRiEwyAMwmEQDoMwCIdBsmdfcwbxVeLWt2EQX2WT2BrDID7KcCGWDWQQH2WKEOnjGcQ/eU/cyZAgg/gm9e+KfFmTQfyS8XdBRM/9DOKTpBS5J1KkPIP4JFXugYikxxjEH1kVeV8k+iKD+COHxYM0YRBf5PRDEFGLQfyQ4F7/i+Sv5vd/C9+L5C+Q/Y9AxE8M4oOUzSJyhUG8z1+ygIgVDOJ5+jXNAhJoyyCeZ0DWr0jsLQbxOqOzgohfizKIx4kokk1kcAyDeJwT2UDE0SCDeJvy2UHEmwzicdJyiFxkEG8zJAdIsYEM4ml+yAEi0qYyiKf3WS/mFNnEIJ7mXE4QMZJBvMzIXCCBWwziYT7MBSKWVWMQDy8igdwiExnEwwzODSJGM4h3eUcCMmIqg3iWJyQgoh6DeJY2MhCxgUG8ymkpSFoig3iU4VIQ8TaDeJSf5SAJcQziTYrKQcQ3DOIvELGaQfwFsqQxg3iRChiIWMkgXmQgChLbjkE8yHsoiBjCIB7kJA5SLJNBzGcfDiI6MYj5dFKABJoziPFMUYCI+QxiPBkqkEBJBjGckkKZ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";
12700
12357
 
12701
12358
  // src/providers/cbn/CbnProvider.ts
12702
12359
  var API_BASE5 = "https://sbamemberdev.wpenginepowered.com/wp-json/superbook/v1";
12703
- var CbnProvider = class {
12360
+ var CbnProvider = class extends BaseProvider {
12704
12361
  constructor() {
12705
- this.apiHelper = new ApiHelper();
12362
+ super(...arguments);
12706
12363
  this.deviceFlowHelper = new DeviceFlowHelper();
12707
12364
  this.id = "cbn";
12708
12365
  this.name = "CBN";
@@ -12715,66 +12372,48 @@ var CbnProvider = class {
12715
12372
  clientId: "freeplay",
12716
12373
  scopes: [],
12717
12374
  supportsDeviceFlow: true,
12718
- deviceAuthEndpoint: "/device",
12719
- endpoints: {
12720
- catalog: "/catalog",
12721
- courseDetail: (courseId) => `/catalog/${courseId}`,
12722
- lessonPlaylist: (lessonId) => `/lesson-playlist/${lessonId}`
12723
- }
12375
+ deviceAuthEndpoint: "/device"
12724
12376
  };
12725
12377
  this.requiresAuth = true;
12726
12378
  this.authTypes = ["device_flow"];
12727
12379
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
12728
12380
  }
12729
- async apiRequest(path, auth) {
12730
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
12731
- }
12732
12381
  async browse(path, auth) {
12733
12382
  const { segments, depth } = parsePath(path);
12734
12383
  if (depth === 0) {
12735
12384
  return [{ type: "folder", id: "catalog-root", title: "Catalog", path: "/catalog" }];
12736
12385
  }
12737
12386
  if (segments[0] !== "catalog") return [];
12738
- if (depth === 1) return this.getCourses(auth);
12739
- if (depth === 2) return this.getLessons(segments[1], path, auth);
12740
- if (depth === 3) return this.getVideos(segments[2], auth);
12387
+ if (depth === 1) {
12388
+ const response = await this.apiRequest("/catalog", auth);
12389
+ return Array.isArray(response) ? convertCoursesToFolders(response) : [];
12390
+ }
12391
+ if (depth === 2) {
12392
+ const response = await this.apiRequest(`/catalog/${segments[1]}`, auth);
12393
+ return response?.lessons ? convertLessonsToFolders(response.lessons, path) : [];
12394
+ }
12395
+ if (depth === 3) {
12396
+ const resolved = await this.resolveFiles(path, auth);
12397
+ return resolved?.files ?? [];
12398
+ }
12741
12399
  return [];
12742
12400
  }
12743
- async getCourses(auth) {
12744
- const response = await this.apiRequest(this.config.endpoints.catalog, auth);
12745
- if (!Array.isArray(response)) return [];
12746
- return convertCoursesToFolders(response);
12747
- }
12748
- async getLessons(courseId, coursePath, auth) {
12749
- const pathFn = this.config.endpoints.courseDetail;
12750
- const response = await this.apiRequest(pathFn(courseId), auth);
12751
- if (!response?.lessons) return [];
12752
- return convertLessonsToFolders(response.lessons, coursePath);
12753
- }
12754
- async getVideos(lessonId, auth) {
12755
- const playlist = await this.fetchPlaylist(lessonId, auth);
12756
- return playlist ? convertPlaylistToFiles(playlist) : [];
12757
- }
12758
- async fetchPlaylist(lessonId, auth) {
12759
- const pathFn = this.config.endpoints.lessonPlaylist;
12760
- return this.apiRequest(pathFn(lessonId), auth);
12761
- }
12762
- async getPlaylist(path, auth, _resolution) {
12401
+ /** Resolve a leaf path (/catalog/{courseId}/{lessonId}) to its playlist files. */
12402
+ async resolveFiles(path, auth) {
12763
12403
  const { segments, depth } = parsePath(path);
12764
12404
  if (depth !== 3 || segments[0] !== "catalog") return null;
12765
- const playlist = await this.fetchPlaylist(segments[2], auth);
12405
+ const playlist = await this.apiRequest(`/lesson-playlist/${segments[2]}`, auth);
12766
12406
  if (!playlist || playlist.playlist.length === 0) return null;
12767
- return convertPlaylistToFiles(playlist);
12407
+ return { files: convertPlaylistToFiles(playlist), lessonTitle: playlist.lesson_title };
12768
12408
  }
12769
- async getInstructions(path, auth) {
12770
- const { segments, depth } = parsePath(path);
12771
- if (depth !== 3 || segments[0] !== "catalog") return null;
12772
- const playlist = await this.fetchPlaylist(segments[2], auth);
12773
- if (!playlist || playlist.playlist.length === 0) return null;
12774
- return convertPlaylistToInstructions(playlist);
12409
+ async getPlaylist(path, auth, _resolution) {
12410
+ const resolved = await this.resolveFiles(path, auth);
12411
+ return resolved?.files ?? null;
12775
12412
  }
12776
- supportsDeviceFlow() {
12777
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
12413
+ async getInstructions(path, auth) {
12414
+ const resolved = await this.resolveFiles(path, auth);
12415
+ if (!resolved) return null;
12416
+ return filesToInstructions(resolved.lessonTitle || "Lesson", resolved.files);
12778
12417
  }
12779
12418
  async initiateDeviceFlow() {
12780
12419
  return this.deviceFlowHelper.initiateDeviceFlow(this.config);
@@ -16405,8 +16044,9 @@ var data_default3 = {
16405
16044
  };
16406
16045
 
16407
16046
  // src/providers/lifeChurch/LifeChurchProvider.ts
16408
- var LifeChurchProvider = class {
16047
+ var LifeChurchProvider = class extends BaseProvider {
16409
16048
  constructor() {
16049
+ super(...arguments);
16410
16050
  this.id = "lifechurch";
16411
16051
  this.name = "Life.Church";
16412
16052
  this.logos = {
@@ -16419,8 +16059,7 @@ var LifeChurchProvider = class {
16419
16059
  apiBase: "https://www.life.church",
16420
16060
  oauthBase: "",
16421
16061
  clientId: "",
16422
- scopes: [],
16423
- endpoints: {}
16062
+ scopes: []
16424
16063
  };
16425
16064
  this.requiresAuth = false;
16426
16065
  this.authTypes = ["none"];
@@ -16435,20 +16074,14 @@ var LifeChurchProvider = class {
16435
16074
  findSeries(seriesId) {
16436
16075
  return this.data.series.find((s) => s.id === seriesId);
16437
16076
  }
16438
- findUnit(series, unitId) {
16439
- return series.units.find((u) => u.id === unitId);
16440
- }
16441
- findLesson(unit, lessonId) {
16442
- return unit.lessons.find((l) => l.id === lessonId);
16443
- }
16444
16077
  findScheduled(scheduledId) {
16445
16078
  return this.data.scheduledLessons?.find((s) => s.id === scheduledId);
16446
16079
  }
16447
16080
  /** Resolve a scheduled-lesson reference to the underlying lesson + unit + series. */
16448
16081
  resolveScheduled(sched) {
16449
16082
  const series = this.findSeries(sched.seriesId);
16450
- const unit = series && this.findUnit(series, sched.unitId);
16451
- const lesson = unit && this.findLesson(unit, sched.lessonId);
16083
+ const unit = series?.units.find((u) => u.id === sched.unitId);
16084
+ const lesson = unit?.lessons.find((l) => l.id === sched.lessonId);
16452
16085
  if (!series || !unit || !lesson) return null;
16453
16086
  return { series, unit, lesson };
16454
16087
  }
@@ -16463,17 +16096,40 @@ var LifeChurchProvider = class {
16463
16096
  providerData: { sourceUrl: lesson.sourceUrl }
16464
16097
  };
16465
16098
  }
16099
+ /** Resolve a leaf-or-unit path to its files plus the name/section used for instructions. */
16100
+ resolve(path) {
16101
+ const { segments, depth } = parsePath(path);
16102
+ if (segments[0] === "scheduled") {
16103
+ if (depth !== 2) return null;
16104
+ const sched = this.findScheduled(segments[1]);
16105
+ const resolved = sched && this.resolveScheduled(sched);
16106
+ if (!sched || !resolved) return null;
16107
+ const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16108
+ return {
16109
+ files: [this.lessonToFile(resolved.lesson, label)],
16110
+ name: label,
16111
+ section: { id: `scheduled-${sched.id}-section`, label: "This Sunday" }
16112
+ };
16113
+ }
16114
+ const series = this.findSeries(segments[0]);
16115
+ const unit = series?.units.find((u) => u.id === segments[1]);
16116
+ if (!unit) return null;
16117
+ if (depth === 2) {
16118
+ return { files: unit.lessons.map((l) => this.lessonToFile(l)), name: unit.name, section: { id: `${unit.id}-section`, label: unit.name } };
16119
+ }
16120
+ if (depth === 3) {
16121
+ const lesson = unit.lessons.find((l) => l.id === segments[2]);
16122
+ if (!lesson) return null;
16123
+ return { files: [this.lessonToFile(lesson)], name: lesson.title, section: { id: `${unit.id}-section`, label: unit.name } };
16124
+ }
16125
+ return null;
16126
+ }
16466
16127
  async browse(path, _auth) {
16467
16128
  const { segments, depth } = parsePath(path);
16468
16129
  if (depth === 0) {
16469
16130
  const items = [];
16470
16131
  if (this.data.scheduledLessons && this.data.scheduledLessons.length > 0) {
16471
- items.push({
16472
- type: "folder",
16473
- id: "scheduled",
16474
- title: "This Sunday",
16475
- path: "/scheduled"
16476
- });
16132
+ items.push({ type: "folder", id: "scheduled", title: "This Sunday", path: "/scheduled" });
16477
16133
  }
16478
16134
  for (const s of this.data.series) {
16479
16135
  items.push({ type: "folder", id: s.id, title: s.name, thumbnail: s.thumbnail, path: `/${s.id}` });
@@ -16494,13 +16150,7 @@ var LifeChurchProvider = class {
16494
16150
  };
16495
16151
  });
16496
16152
  }
16497
- if (depth === 2) {
16498
- const sched = this.findScheduled(segments[1]);
16499
- const resolved = sched && this.resolveScheduled(sched);
16500
- if (!sched || !resolved) return [];
16501
- return [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)];
16502
- }
16503
- return [];
16153
+ return this.resolve(path)?.files ?? [];
16504
16154
  }
16505
16155
  if (depth === 1) {
16506
16156
  const series = this.findSeries(segments[0]);
@@ -16515,8 +16165,8 @@ var LifeChurchProvider = class {
16515
16165
  }
16516
16166
  if (depth === 2) {
16517
16167
  const series = this.findSeries(segments[0]);
16518
- const unit = series && this.findUnit(series, segments[1]);
16519
- if (!series || !unit) return [];
16168
+ const unit = series?.units.find((u) => u.id === segments[1]);
16169
+ if (!unit) return [];
16520
16170
  return unit.lessons.map((l) => ({
16521
16171
  type: "folder",
16522
16172
  id: l.id,
@@ -16527,78 +16177,18 @@ var LifeChurchProvider = class {
16527
16177
  }));
16528
16178
  }
16529
16179
  if (depth === 3) {
16530
- const series = this.findSeries(segments[0]);
16531
- const unit = series && this.findUnit(series, segments[1]);
16532
- const lesson = unit && this.findLesson(unit, segments[2]);
16533
- if (!lesson) return [];
16534
- return [this.lessonToFile(lesson)];
16180
+ return this.resolve(path)?.files ?? [];
16535
16181
  }
16536
16182
  return [];
16537
16183
  }
16538
16184
  async getPlaylist(path, _auth, _resolution) {
16539
- const { segments, depth } = parsePath(path);
16540
- if (segments[0] === "scheduled" && depth === 2) {
16541
- const sched = this.findScheduled(segments[1]);
16542
- const resolved = sched && this.resolveScheduled(sched);
16543
- return resolved ? [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)] : null;
16544
- }
16545
- if (depth === 3) {
16546
- const series = this.findSeries(segments[0]);
16547
- const unit = series && this.findUnit(series, segments[1]);
16548
- const lesson = unit && this.findLesson(unit, segments[2]);
16549
- return lesson ? [this.lessonToFile(lesson)] : null;
16550
- }
16551
- if (depth === 2 && segments[0] !== "scheduled") {
16552
- const series = this.findSeries(segments[0]);
16553
- const unit = series && this.findUnit(series, segments[1]);
16554
- if (!unit) return null;
16555
- const files = unit.lessons.map((l) => this.lessonToFile(l));
16556
- return files.length > 0 ? files : null;
16557
- }
16558
- return null;
16185
+ const resolved = this.resolve(path);
16186
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
16559
16187
  }
16560
16188
  async getInstructions(path, _auth) {
16561
- const { segments, depth } = parsePath(path);
16562
- const buildItem = (lesson, label) => ({
16563
- id: `${lesson.id}-action`,
16564
- itemType: "action",
16565
- label: label ?? lesson.title,
16566
- actionType: "play",
16567
- children: [
16568
- {
16569
- id: lesson.id,
16570
- itemType: "file",
16571
- label: label ?? lesson.title,
16572
- downloadUrl: lesson.videoUrl,
16573
- thumbnail: lesson.thumbnail,
16574
- mediaType: "video"
16575
- }
16576
- ]
16577
- });
16578
- if (segments[0] === "scheduled" && depth === 2) {
16579
- const sched = this.findScheduled(segments[1]);
16580
- const resolved = sched && this.resolveScheduled(sched);
16581
- if (!sched || !resolved) return null;
16582
- const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16583
- return {
16584
- name: label,
16585
- items: [{ id: `scheduled-${sched.id}-section`, itemType: "section", label: "This Sunday", children: [buildItem(resolved.lesson, label)] }]
16586
- };
16587
- }
16588
- if (depth === 3) {
16589
- const series = this.findSeries(segments[0]);
16590
- const unit = series && this.findUnit(series, segments[1]);
16591
- const lesson = unit && this.findLesson(unit, segments[2]);
16592
- if (!series || !unit || !lesson) return null;
16593
- return { name: lesson.title, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: [buildItem(lesson)] }] };
16594
- }
16595
- if (depth === 2 && segments[0] !== "scheduled") {
16596
- const series = this.findSeries(segments[0]);
16597
- const unit = series && this.findUnit(series, segments[1]);
16598
- if (!unit) return null;
16599
- return { name: unit.name, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: unit.lessons.map((l) => buildItem(l)) }] };
16600
- }
16601
- return null;
16189
+ const resolved = this.resolve(path);
16190
+ if (!resolved) return null;
16191
+ return filesToInstructions(resolved.name, resolved.files, resolved.section);
16602
16192
  }
16603
16193
  /**
16604
16194
  * Returns the most recent scheduled lesson from data.scheduledLessons.
@@ -16626,9 +16216,6 @@ var LifeChurchProvider = class {
16626
16216
  ]
16627
16217
  };
16628
16218
  }
16629
- supportsDeviceFlow() {
16630
- return false;
16631
- }
16632
16219
  };
16633
16220
 
16634
16221
  // src/providers/index.ts
@@ -16723,17 +16310,20 @@ function getAvailableProviders(ids) {
16723
16310
  }
16724
16311
 
16725
16312
  // src/index.ts
16726
- var VERSION = true ? "0.4.1" : "dev";
16313
+ var VERSION = true ? "0.5.1" : "dev";
16727
16314
  export {
16728
16315
  APlayProvider,
16729
16316
  B1ChurchProvider,
16317
+ BaseProvider,
16730
16318
  BibleProjectProvider,
16731
16319
  CbnProvider,
16320
+ DeviceFlowHelper,
16732
16321
  DropboxProvider,
16733
16322
  HighVoltageKidsProvider,
16734
16323
  JesusFilmProvider,
16735
16324
  LessonsChurchProvider,
16736
16325
  LifeChurchProvider,
16326
+ OAuthHelper,
16737
16327
  PlanningCenterProvider,
16738
16328
  SignPresenterProvider,
16739
16329
  TokenHelper,
@@ -16745,6 +16335,7 @@ export {
16745
16335
  isContentFile,
16746
16336
  isContentFolder,
16747
16337
  navigateToPath,
16748
- registerProvider
16338
+ registerProvider,
16339
+ toAuthData
16749
16340
  };
16750
16341
  //# sourceMappingURL=index.js.map