@churchapps/content-providers 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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,43 +542,11 @@ 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] };
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 }] };
573
548
  }
574
549
  // Auth methods
575
- supportsDeviceFlow() {
576
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
577
- }
578
550
  generateCodeVerifier() {
579
551
  return this.oauthHelper.generateCodeVerifier();
580
552
  }
@@ -621,6 +593,23 @@ function truncateForLabel(text) {
621
593
  const truncated = text.length > cutoff ? text.substring(0, cutoff) : text;
622
594
  return stripMarkdown(truncated) + (text.length > cutoff ? "..." : "");
623
595
  }
596
+ function getEmbedUrl(apiType, relatedId) {
597
+ if (!relatedId) return void 0;
598
+ const baseUrl = "https://lessons.church";
599
+ switch (apiType) {
600
+ case "action":
601
+ case "lessonAction":
602
+ return `${baseUrl}/embed/action/${relatedId}`;
603
+ case "addon":
604
+ case "lessonAddOn":
605
+ return `${baseUrl}/embed/addon/${relatedId}`;
606
+ case "section":
607
+ case "lessonSection":
608
+ return `${baseUrl}/embed/section/${relatedId}`;
609
+ default:
610
+ return void 0;
611
+ }
612
+ }
624
613
  async function convertAddOnToFile(addOn) {
625
614
  const apiPath = `/addOns/public/${addOn.id}`;
626
615
  const detail = await apiRequest(apiPath);
@@ -767,12 +756,13 @@ async function convertAddOnCategoryToInstructions(category) {
767
756
  }
768
757
 
769
758
  // src/providers/lessonsChurch/LessonsChurchProvider.ts
770
- var LessonsChurchProvider = class {
759
+ var LessonsChurchProvider = class extends BaseProvider {
771
760
  constructor() {
761
+ super(...arguments);
772
762
  this.id = "lessonschurch";
773
763
  this.name = "Lessons.church";
774
764
  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}` } };
765
+ this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [] };
776
766
  this.requiresAuth = false;
777
767
  this.authTypes = ["none"];
778
768
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
@@ -828,28 +818,25 @@ var LessonsChurchProvider = class {
828
818
  return [];
829
819
  }
830
820
  async getPrograms() {
831
- const response = await apiRequest(this.config.endpoints.programs);
821
+ const response = await apiRequest("/programs/public");
832
822
  if (!response) return [];
833
823
  const programs = Array.isArray(response) ? response : [];
834
824
  return programs.map((p) => createFolder(p.id, p.name, `/lessons/${p.id}`, p.image));
835
825
  }
836
826
  async getStudies(programId, currentPath) {
837
- const pathFn = this.config.endpoints.studies;
838
- const response = await apiRequest(pathFn(programId));
827
+ const response = await apiRequest(`/studies/public/program/${programId}`);
839
828
  if (!response) return [];
840
829
  const studies = Array.isArray(response) ? response : [];
841
830
  return studies.map((s) => createFolder(s.id, s.name, `${currentPath}/${s.id}`, s.image)).sort((a, b) => a.title.localeCompare(b.title));
842
831
  }
843
832
  async getLessons(studyId, currentPath) {
844
- const pathFn = this.config.endpoints.lessons;
845
- const response = await apiRequest(pathFn(studyId));
833
+ const response = await apiRequest(`/lessons/public/study/${studyId}`);
846
834
  if (!response) return [];
847
835
  const lessons = Array.isArray(response) ? response : [];
848
836
  return lessons.map((l) => createFolder(l.id, l.name || l.title, `${currentPath}/${l.id}`, l.image));
849
837
  }
850
838
  async getVenues(lessonId, currentPath) {
851
- const pathFn = this.config.endpoints.venues;
852
- const response = await apiRequest(pathFn(lessonId));
839
+ const response = await apiRequest(`/venues/public/lesson/${lessonId}`);
853
840
  if (!response) return [];
854
841
  const lessonResponse = await apiRequest(`/lessons/public/${lessonId}`);
855
842
  const lessonImage = lessonResponse?.image;
@@ -872,7 +859,7 @@ var LessonsChurchProvider = class {
872
859
  return file ? [file] : [];
873
860
  }
874
861
  async getAddOnCategories() {
875
- const response = await apiRequest(this.config.endpoints.addOns);
862
+ const response = await apiRequest("/addOns/public");
876
863
  if (!response) return [];
877
864
  const addOns = Array.isArray(response) ? response : [];
878
865
  const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
@@ -880,7 +867,7 @@ var LessonsChurchProvider = class {
880
867
  }
881
868
  async getAddOnsByCategory(category, currentPath) {
882
869
  const decodedCategory = decodeURIComponent(category);
883
- const response = await apiRequest(this.config.endpoints.addOns);
870
+ const response = await apiRequest("/addOns/public");
884
871
  if (!response) return [];
885
872
  const allAddOns = Array.isArray(response) ? response : [];
886
873
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
@@ -923,9 +910,6 @@ var LessonsChurchProvider = class {
923
910
  }
924
911
  return null;
925
912
  }
926
- supportsDeviceFlow() {
927
- return false;
928
- }
929
913
  };
930
914
 
931
915
  // src/providers/registry.ts
@@ -941,18 +925,6 @@ function registerProvider(provider) {
941
925
  }
942
926
 
943
927
  // 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
928
  async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
957
929
  const codeChallenge = await generateCodeChallenge(codeVerifier);
958
930
  const oauthParams = new URLSearchParams({
@@ -976,7 +948,7 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
976
948
  return null;
977
949
  }
978
950
  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(" ") };
951
+ return toAuthData(data, { scope: config.scopes.join(" ") });
980
952
  } catch {
981
953
  return null;
982
954
  }
@@ -990,7 +962,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
990
962
  return null;
991
963
  }
992
964
  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(" ") };
965
+ return toAuthData(data, { scope: config.scopes.join(" ") });
994
966
  } catch {
995
967
  return null;
996
968
  }
@@ -1002,7 +974,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
1002
974
  const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1003
975
  if (!response.ok) return null;
1004
976
  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 };
977
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
1006
978
  } catch {
1007
979
  return null;
1008
980
  }
@@ -1027,78 +999,32 @@ async function authFetch(url, auth) {
1027
999
  }
1028
1000
  }
1029
1001
  async function fetchMinistries(auth) {
1030
- const result = await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth);
1031
- return result || [];
1002
+ return await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth) || [];
1032
1003
  }
1033
1004
  async function fetchPlanTypes(ministryId, auth) {
1034
- const result = await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth);
1035
- return result || [];
1005
+ return await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth) || [];
1036
1006
  }
1037
1007
  async function fetchPlans(planTypeId, auth) {
1038
- const result = await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth);
1039
- return result || [];
1008
+ return await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth) || [];
1040
1009
  }
1041
- var DOING_API_BASE = "https://api.churchapps.org/doing";
1042
1010
  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
- }
1011
+ return authFetch(`${API_BASE3}/doing/plans/public/current/${planTypeId}`);
1054
1012
  }
1055
1013
  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
- }
1014
+ return await authFetch(`${API_BASE3}/doing/planItems/presenter/${churchId}/${planId}`) || [];
1063
1015
  }
1064
1016
  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
- }
1017
+ return authFetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`);
1076
1018
  }
1077
1019
  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
- }
1020
+ return authFetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`);
1089
1021
  }
1090
1022
  async function fetchVenueImages(venueIds) {
1091
1023
  const map = /* @__PURE__ */ new Map();
1092
1024
  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 {
1025
+ const data = await authFetch(`${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`);
1026
+ for (const row of data || []) {
1027
+ if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1102
1028
  }
1103
1029
  return map;
1104
1030
  }
@@ -1187,18 +1113,7 @@ function getFileFromProviderFileItem(item) {
1187
1113
  };
1188
1114
  }
1189
1115
  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
- }
1116
+ const itemType = normalizeItemType(item.itemType);
1202
1117
  const isFileType = itemType === "file" || item.link && !item.children?.length;
1203
1118
  return {
1204
1119
  id: item.id,
@@ -1212,23 +1127,6 @@ function planItemToInstruction(item, thumbnail) {
1212
1127
  children: item.children?.map((child) => planItemToInstruction(child, thumbnail))
1213
1128
  };
1214
1129
  }
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
1130
  function buildSectionActionsMap2(actionsResponse, thumbnail) {
1233
1131
  const sectionActionsMap = /* @__PURE__ */ new Map();
1234
1132
  if (actionsResponse?.sections) {
@@ -1342,9 +1240,9 @@ function isExternalProviderItem(item) {
1342
1240
  const itemType = item.itemType || "";
1343
1241
  return itemType.startsWith("provider");
1344
1242
  }
1345
- var B1ChurchProvider = class {
1243
+ var B1ChurchProvider = class extends BaseProvider {
1346
1244
  constructor() {
1347
- this.apiHelper = new ApiHelper();
1245
+ super(...arguments);
1348
1246
  this.deviceFlowHelper = new DeviceFlowHelper();
1349
1247
  // Unified cache for external provider data to avoid duplicate calls across methods
1350
1248
  this.externalContentCache = {
@@ -1355,16 +1253,13 @@ var B1ChurchProvider = class {
1355
1253
  this.id = "b1church";
1356
1254
  this.name = "B1.Church";
1357
1255
  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}` } };
1256
+ 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
1257
  this.appBase = "https://admin.b1.church";
1360
1258
  this.requiresAuth = true;
1361
1259
  this.authTypes = ["oauth_pkce", "device_flow"];
1362
1260
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1363
1261
  this.planTypeId = null;
1364
1262
  }
1365
- async apiRequest(path, authData) {
1366
- return this.apiHelper.apiRequest(this.config, this.id, path, authData);
1367
- }
1368
1263
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1369
1264
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1370
1265
  }
@@ -1510,8 +1405,7 @@ var B1ChurchProvider = class {
1510
1405
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
1511
1406
  return null;
1512
1407
  }
1513
- const pathFn = this.config.endpoints.planItems;
1514
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1408
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1515
1409
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1516
1410
  const externalInstructions = await fetchFromProviderProxy(
1517
1411
  "getInstructions",
@@ -1619,8 +1513,7 @@ var B1ChurchProvider = class {
1619
1513
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1620
1514
  return null;
1621
1515
  }
1622
- const pathFn = this.config.endpoints.planItems;
1623
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1516
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1624
1517
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1625
1518
  const externalFiles = await fetchFromProviderProxy(
1626
1519
  "getPlaylist",
@@ -1715,9 +1608,6 @@ var B1ChurchProvider = class {
1715
1608
  }
1716
1609
  return files;
1717
1610
  }
1718
- supportsDeviceFlow() {
1719
- return !!this.config.supportsDeviceFlow;
1720
- }
1721
1611
  };
1722
1612
 
1723
1613
  // src/providers/dropbox/DropboxConverters.ts
@@ -1743,38 +1633,13 @@ function filterMediaEntries(entries) {
1743
1633
  }
1744
1634
  return { folders, mediaFiles };
1745
1635
  }
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
1636
 
1772
1637
  // src/providers/dropbox/DropboxProvider.ts
1773
1638
  var MAX_PAGINATION_CALLS = 50;
1774
- var DropboxProvider = class {
1639
+ var DropboxProvider = class extends BaseProvider {
1775
1640
  constructor() {
1641
+ super(...arguments);
1776
1642
  this.oauthHelper = new OAuthHelper();
1777
- this.apiHelper = new ApiHelper();
1778
1643
  this.id = "dropbox";
1779
1644
  this.name = "Dropbox";
1780
1645
  this.logos = {
@@ -1787,12 +1652,7 @@ var DropboxProvider = class {
1787
1652
  apiBase: "https://api.dropboxapi.com",
1788
1653
  oauthBase: "https://www.dropbox.com/oauth2",
1789
1654
  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
- }
1655
+ scopes: []
1796
1656
  };
1797
1657
  this.requiresAuth = true;
1798
1658
  this.authTypes = ["oauth_pkce"];
@@ -1809,12 +1669,12 @@ var DropboxProvider = class {
1809
1669
  console.error("[Dropbox] No auth token provided");
1810
1670
  return Promise.resolve(null);
1811
1671
  }
1812
- return this.apiHelper.apiRequest(this.config, this.id, endpoint, auth, "POST", body);
1672
+ return this.apiRequest(endpoint, auth, "POST", body);
1813
1673
  }
1814
1674
  async listAllEntries(dropboxPath, auth) {
1815
1675
  const allEntries = [];
1816
1676
  const response = await this.dropboxPost(
1817
- this.config.endpoints.listFolder,
1677
+ "/2/files/list_folder",
1818
1678
  { path: dropboxPath, recursive: false, include_media_info: false },
1819
1679
  auth
1820
1680
  );
@@ -1825,7 +1685,7 @@ var DropboxProvider = class {
1825
1685
  let calls = 0;
1826
1686
  while (hasMore && calls < MAX_PAGINATION_CALLS) {
1827
1687
  const cont = await this.dropboxPost(
1828
- this.config.endpoints.listFolderContinue,
1688
+ "/2/files/list_folder/continue",
1829
1689
  { cursor },
1830
1690
  auth
1831
1691
  );
@@ -1875,7 +1735,7 @@ var DropboxProvider = class {
1875
1735
  }
1876
1736
  async getTemporaryLink(filePath, auth) {
1877
1737
  const response = await this.dropboxPost(
1878
- this.config.endpoints.getTemporaryLink,
1738
+ "/2/files/get_temporary_link",
1879
1739
  { path: filePath },
1880
1740
  auth
1881
1741
  );
@@ -1896,7 +1756,7 @@ var DropboxProvider = class {
1896
1756
  // -- Leaf detection --
1897
1757
  async checkIsLeaf(folderPath, auth) {
1898
1758
  const response = await this.dropboxPost(
1899
- this.config.endpoints.listFolder,
1759
+ "/2/files/list_folder",
1900
1760
  { path: folderPath, recursive: false, include_media_info: false },
1901
1761
  auth
1902
1762
  );
@@ -1915,31 +1775,29 @@ var DropboxProvider = class {
1915
1775
  const fileItems = await this.resolveFileLinks(mediaFiles, auth);
1916
1776
  return [...folderItems, ...fileItems];
1917
1777
  }
1918
- async getPlaylist(path, auth, _resolution) {
1778
+ /** Resolve a folder path to playable files (list + media filter + link resolution). */
1779
+ async resolveMediaFiles(path, auth) {
1919
1780
  if (!path || path === "/") return null;
1920
1781
  const dropboxPath = path.startsWith("/") ? path : "/" + path;
1921
1782
  const allEntries = await this.listAllEntries(dropboxPath, auth);
1922
1783
  const { mediaFiles } = filterMediaEntries(allEntries);
1923
1784
  if (mediaFiles.length === 0) return null;
1924
1785
  const files = await this.resolveFileLinks(mediaFiles, auth);
1925
- return files.length > 0 ? files : null;
1786
+ if (files.length === 0) return null;
1787
+ return { files, dropboxPath };
1788
+ }
1789
+ async getPlaylist(path, auth, _resolution) {
1790
+ const resolved = await this.resolveMediaFiles(path, auth);
1791
+ return resolved?.files ?? null;
1926
1792
  }
1927
1793
  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);
1794
+ const resolved = await this.resolveMediaFiles(path, auth);
1795
+ if (!resolved) return null;
1796
+ const segments = resolved.dropboxPath.split("/").filter(Boolean);
1936
1797
  const folderName = segments[segments.length - 1] || "Dropbox";
1937
- return buildInstructionsFromFiles(files, folderName);
1798
+ return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
1938
1799
  }
1939
1800
  // -- Auth methods --
1940
- supportsDeviceFlow() {
1941
- return false;
1942
- }
1943
1801
  generateCodeVerifier() {
1944
1802
  return this.oauthHelper.generateCodeVerifier();
1945
1803
  }
@@ -1976,61 +1834,43 @@ var DropboxProvider = class {
1976
1834
  body: params.toString()
1977
1835
  });
1978
1836
  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
- };
1837
+ return toAuthData(await response.json());
1988
1838
  } catch {
1989
1839
  return null;
1990
1840
  }
1991
1841
  }
1992
1842
  };
1993
1843
 
1994
- // src/providers/planningCenter/PlanningCenterConverters.ts
1995
- var apiHelper = new ApiHelper();
1844
+ // src/providers/planningCenter/PlanningCenterProvider.ts
1845
+ var ONE_WEEK_MS = 6048e5;
1996
1846
  function formatDate(dateString) {
1997
- const date = new Date(dateString);
1998
- return date.toISOString().slice(0, 10);
1847
+ return new Date(dateString).toISOString().slice(0, 10);
1999
1848
  }
2000
-
2001
- // src/providers/planningCenter/PlanningCenterProvider.ts
2002
- var PlanningCenterProvider = class {
1849
+ var PlanningCenterProvider = class extends BaseProvider {
2003
1850
  constructor() {
2004
- this.apiHelper = new ApiHelper();
1851
+ super(...arguments);
2005
1852
  this.id = "planningcenter";
2006
1853
  this.name = "Planning Center";
2007
1854
  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;
1855
+ this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
2010
1856
  this.requiresAuth = true;
2011
1857
  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);
1858
+ // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1859
+ this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
2016
1860
  }
2017
1861
  async browse(path, auth) {
2018
1862
  const { segments, depth } = parsePath(path);
2019
1863
  if (depth === 0) {
2020
1864
  return [{ type: "folder", id: "serviceTypes-root", title: "Service Types", path: "/serviceTypes" }];
2021
1865
  }
2022
- const root = segments[0];
2023
- if (root !== "serviceTypes") return [];
1866
+ if (segments[0] !== "serviceTypes") return [];
2024
1867
  if (depth === 1) return this.getServiceTypes(auth);
2025
1868
  if (depth === 2) return this.getPlans(segments[1], path, auth);
2026
1869
  if (depth === 3) return this.getPlanItems(segments[1], segments[2], auth);
2027
1870
  return [];
2028
1871
  }
2029
1872
  async getServiceTypes(auth) {
2030
- const response = await this.apiRequest(
2031
- this.config.endpoints.serviceTypes,
2032
- auth
2033
- );
1873
+ const response = await this.apiRequest("/services/v2/service_types", auth);
2034
1874
  if (!response?.data) return [];
2035
1875
  return response.data.map((serviceType) => ({
2036
1876
  type: "folder",
@@ -2040,9 +1880,8 @@ var PlanningCenterProvider = class {
2040
1880
  }));
2041
1881
  }
2042
1882
  async getPlans(serviceTypeId, currentPath, auth) {
2043
- const pathFn = this.config.endpoints.plans;
2044
1883
  const response = await this.apiRequest(
2045
- `${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
1884
+ `/services/v2/service_types/${serviceTypeId}/plans?filter=future&order=sort_date`,
2046
1885
  auth
2047
1886
  );
2048
1887
  if (!response?.data) return [];
@@ -2050,7 +1889,7 @@ var PlanningCenterProvider = class {
2050
1889
  const filteredPlans = response.data.filter((plan) => {
2051
1890
  if (plan.attributes.items_count === 0) return false;
2052
1891
  const planDate = new Date(plan.attributes.sort_date).getTime();
2053
- return planDate < now + this.ONE_WEEK_MS;
1892
+ return planDate < now + ONE_WEEK_MS;
2054
1893
  });
2055
1894
  return filteredPlans.map((plan) => ({
2056
1895
  type: "folder",
@@ -2061,17 +1900,13 @@ var PlanningCenterProvider = class {
2061
1900
  }));
2062
1901
  }
2063
1902
  async getPlanItems(serviceTypeId, planId, auth) {
2064
- const pathFn = this.config.endpoints.planItems;
2065
1903
  const response = await this.apiRequest(
2066
- `${pathFn(serviceTypeId, planId)}?per_page=100`,
1904
+ `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items?per_page=100`,
2067
1905
  auth
2068
1906
  );
2069
1907
  if (!response?.data) return [];
2070
1908
  return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
2071
1909
  }
2072
- supportsDeviceFlow() {
2073
- return false;
2074
- }
2075
1910
  };
2076
1911
 
2077
1912
  // src/providers/bibleProject/data.json
@@ -4466,8 +4301,9 @@ var data_default = {
4466
4301
  };
4467
4302
 
4468
4303
  // src/providers/bibleProject/BibleProjectProvider.ts
4469
- var BibleProjectProvider = class {
4304
+ var BibleProjectProvider = class extends BaseProvider {
4470
4305
  constructor() {
4306
+ super(...arguments);
4471
4307
  this.id = "bibleproject";
4472
4308
  this.name = "The Bible Project";
4473
4309
  this.logos = {
@@ -4480,10 +4316,8 @@ var BibleProjectProvider = class {
4480
4316
  apiBase: "https://bibleproject.com",
4481
4317
  oauthBase: "",
4482
4318
  clientId: "",
4483
- scopes: [],
4484
- endpoints: { downloads: "/downloads/" }
4319
+ scopes: []
4485
4320
  };
4486
- this.data = data_default;
4487
4321
  this.requiresAuth = false;
4488
4322
  this.authTypes = ["none"];
4489
4323
  this.capabilities = {
@@ -4492,133 +4326,71 @@ var BibleProjectProvider = class {
4492
4326
  instructions: true,
4493
4327
  mediaLicensing: false
4494
4328
  };
4329
+ this.data = data_default;
4495
4330
  }
4496
4331
  getMuxThumbnail(muxPlaybackId, width = 400, height = 225) {
4497
4332
  return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=${width}&height=${height}`;
4498
4333
  }
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,
4334
+ videoToFile(video) {
4335
+ return createFile(video.id, video.title, video.videoUrl, {
4336
+ mediaType: "video",
4531
4337
  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 })];
4338
+ muxPlaybackId: video.muxPlaybackId,
4339
+ seconds: 0
4340
+ });
4542
4341
  }
4543
- async getPlaylist(path, _auth, _resolution) {
4342
+ /** Resolve a path to its collection and files: all videos at depth 1, a single video at depth 2. */
4343
+ resolve(path) {
4544
4344
  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);
4345
+ if (depth < 1 || depth > 2) return null;
4346
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4548
4347
  if (!collection) return null;
4549
4348
  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;
4349
+ return { collection, files: collection.videos.map((v) => this.videoToFile(v)), name: collection.name };
4552
4350
  }
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;
4351
+ const video = collection.videos.find((v) => v.id === segments[1]);
4352
+ if (!video) return null;
4353
+ return { collection, files: [this.videoToFile(video)], name: video.title };
4560
4354
  }
4561
- async getInstructions(path, _auth) {
4355
+ async browse(path, _auth) {
4562
4356
  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;
4357
+ if (depth === 0) {
4358
+ return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4359
+ type: "folder",
4360
+ id: slugify(collection.name),
4361
+ title: collection.name,
4362
+ thumbnail: collection.image || void 0,
4363
+ path: `/${slugify(collection.name)}`
4364
+ }));
4365
+ }
4567
4366
  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
- ]
4367
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4368
+ if (!collection) return [];
4369
+ return collection.videos.map((video) => ({
4370
+ type: "folder",
4371
+ id: video.id,
4372
+ title: video.title,
4373
+ thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4374
+ isLeaf: true,
4375
+ path: `${path}/${video.id}`
4582
4376
  }));
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
4377
  }
4591
4378
  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] };
4379
+ return this.resolve(path)?.files ?? [];
4617
4380
  }
4618
- return null;
4381
+ return [];
4619
4382
  }
4620
- supportsDeviceFlow() {
4621
- return false;
4383
+ async getPlaylist(path, _auth, _resolution) {
4384
+ const resolved = this.resolve(path);
4385
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
4386
+ }
4387
+ async getInstructions(path, _auth) {
4388
+ const resolved = this.resolve(path);
4389
+ if (!resolved) return null;
4390
+ return filesToInstructions(resolved.name, resolved.files, {
4391
+ id: slugify(resolved.collection.name) + "-section",
4392
+ label: resolved.collection.name
4393
+ });
4622
4394
  }
4623
4395
  };
4624
4396
 
@@ -12242,8 +12014,9 @@ function buildLessonInstructions(lesson) {
12242
12014
  }
12243
12015
 
12244
12016
  // src/providers/highVoltage/HighVoltageKidsProvider.ts
12245
- var HighVoltageKidsProvider = class {
12017
+ var HighVoltageKidsProvider = class extends BaseProvider {
12246
12018
  constructor() {
12019
+ super(...arguments);
12247
12020
  this.id = "highvoltagekids";
12248
12021
  this.name = "High Voltage Kids";
12249
12022
  this.logos = {
@@ -12256,8 +12029,7 @@ var HighVoltageKidsProvider = class {
12256
12029
  apiBase: "https://highvoltagekids.com",
12257
12030
  oauthBase: "",
12258
12031
  clientId: "",
12259
- scopes: [],
12260
- endpoints: { downloads: "/membership-downloads/" }
12032
+ scopes: []
12261
12033
  };
12262
12034
  this.data = data_default2;
12263
12035
  this.requiresAuth = false;
@@ -12307,9 +12079,6 @@ var HighVoltageKidsProvider = class {
12307
12079
  }
12308
12080
  return null;
12309
12081
  }
12310
- supportsDeviceFlow() {
12311
- return false;
12312
- }
12313
12082
  };
12314
12083
 
12315
12084
  // src/providers/jesusFilm/JesusFilmProvider.ts
@@ -12321,8 +12090,14 @@ var CATEGORY_MAP = {
12321
12090
  "series": "series",
12322
12091
  "collections": "collection"
12323
12092
  };
12324
- var JesusFilmProvider = class {
12093
+ var CATEGORY_NAMES = {
12094
+ "feature-films": "Feature Films",
12095
+ "series": "Series",
12096
+ "collections": "Collections"
12097
+ };
12098
+ var JesusFilmProvider = class extends BaseProvider {
12325
12099
  constructor() {
12100
+ super(...arguments);
12326
12101
  this.id = "jesusfilm";
12327
12102
  this.name = "Jesus Film Project";
12328
12103
  this.logos = {
@@ -12335,8 +12110,7 @@ var JesusFilmProvider = class {
12335
12110
  apiBase: API_BASE4,
12336
12111
  oauthBase: "",
12337
12112
  clientId: "",
12338
- scopes: [],
12339
- endpoints: { watch: "/watch" }
12113
+ scopes: []
12340
12114
  };
12341
12115
  this.requiresAuth = false;
12342
12116
  this.authTypes = ["none"];
@@ -12347,6 +12121,7 @@ var JesusFilmProvider = class {
12347
12121
  mediaLicensing: false
12348
12122
  };
12349
12123
  }
12124
+ // Not ApiHelper: Arclight needs the apiKey query param and callers rely on throw-on-error.
12350
12125
  async fetchApi(path) {
12351
12126
  const separator = path.includes("?") ? "&" : "?";
12352
12127
  const url = `${API_BASE4}${path}${separator}apiKey=${API_KEY}`;
@@ -12354,6 +12129,12 @@ var JesusFilmProvider = class {
12354
12129
  if (!response.ok) throw new Error(`Arclight API error: ${response.status}`);
12355
12130
  return response.json();
12356
12131
  }
12132
+ async fetchCategoryComponents(subType) {
12133
+ const data = await this.fetchApi(
12134
+ `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12135
+ );
12136
+ return data._embedded.mediaComponents;
12137
+ }
12357
12138
  async browse(path, _auth) {
12358
12139
  const { segments, depth } = parsePath(path);
12359
12140
  if (depth === 0) return this.getCategories();
@@ -12376,19 +12157,14 @@ var JesusFilmProvider = class {
12376
12157
  async getItemsInCategory(category) {
12377
12158
  const subType = CATEGORY_MAP[category];
12378
12159
  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
- });
12160
+ const components = await this.fetchCategoryComponents(subType);
12161
+ return components.map((item) => createFolder(
12162
+ item.mediaComponentId,
12163
+ item.title,
12164
+ `/${category}/${item.mediaComponentId}`,
12165
+ item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12166
+ item.containsCount === 0
12167
+ ));
12392
12168
  }
12393
12169
  async getVideoFile(mediaComponentId) {
12394
12170
  const variant = await this.fetchApi(
@@ -12403,11 +12179,10 @@ var JesusFilmProvider = class {
12403
12179
  const title = component?.title || mediaComponentId;
12404
12180
  const seconds = component ? Math.round(component.lengthInMilliseconds / 1e3) : 0;
12405
12181
  const thumbnail = component?.imageUrls.mobileCinematicHigh || component?.imageUrls.videoStill;
12406
- const muxPlaybackId = this.extractMuxPlaybackId(downloadUrl);
12407
12182
  return [
12408
12183
  createFile(mediaComponentId, title, downloadUrl, {
12409
12184
  mediaType: "video",
12410
- muxPlaybackId,
12185
+ muxPlaybackId: this.extractMuxPlaybackId(downloadUrl),
12411
12186
  seconds,
12412
12187
  thumbnail
12413
12188
  })
@@ -12419,9 +12194,8 @@ var JesusFilmProvider = class {
12419
12194
  );
12420
12195
  const childIds = linksData.linkedMediaComponentIds?.contains;
12421
12196
  if (!childIds || childIds.length === 0) return [];
12422
- const idsParam = childIds.join(",");
12423
12197
  const childrenData = await this.fetchApi(
12424
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12198
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12425
12199
  );
12426
12200
  if (!childrenData._embedded?.mediaComponents) return [];
12427
12201
  return childrenData._embedded.mediaComponents.map((item) => createFolder(
@@ -12436,36 +12210,6 @@ var JesusFilmProvider = class {
12436
12210
  const match = url.match(/stream\.mux\.com\/([^/]+)/);
12437
12211
  return match ? match[1] : void 0;
12438
12212
  }
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
12213
  async fetchVideoFiles(components) {
12470
12214
  const files = [];
12471
12215
  for (const component of components) {
@@ -12496,9 +12240,8 @@ var JesusFilmProvider = class {
12496
12240
  );
12497
12241
  const childIds = linksData.linkedMediaComponentIds?.contains;
12498
12242
  if (!childIds || childIds.length === 0) return [];
12499
- const idsParam = childIds.join(",");
12500
12243
  const childrenData = await this.fetchApi(
12501
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12244
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12502
12245
  );
12503
12246
  if (!childrenData._embedded?.mediaComponents) return [];
12504
12247
  const contentItems = childrenData._embedded.mediaComponents.filter((c) => c.containsCount === 0);
@@ -12510,64 +12253,47 @@ var JesusFilmProvider = class {
12510
12253
  }
12511
12254
  return files;
12512
12255
  }
12256
+ async getPlaylist(path, _auth, _resolution) {
12257
+ const { segments, depth } = parsePath(path);
12258
+ if (depth < 1) return null;
12259
+ const category = segments[0];
12260
+ if (depth >= 3) {
12261
+ const lastId = segments[depth - 1];
12262
+ const items = await this.getVideoFile(lastId);
12263
+ const files2 = items.filter((item) => item.type === "file");
12264
+ if (files2.length > 0) return files2;
12265
+ const containerFiles = await this.fetchContainerVideoFiles(lastId);
12266
+ return containerFiles.length > 0 ? containerFiles : null;
12267
+ }
12268
+ if (depth === 2) {
12269
+ if (category === "feature-films") {
12270
+ const items = await this.getVideoFile(segments[1]);
12271
+ if (items.length === 0) return null;
12272
+ return items.filter((item) => item.type === "file");
12273
+ }
12274
+ const files2 = await this.fetchContainerVideoFiles(segments[1]);
12275
+ return files2.length > 0 ? files2 : null;
12276
+ }
12277
+ const subType = CATEGORY_MAP[category];
12278
+ if (!subType) return null;
12279
+ const components = await this.fetchCategoryComponents(subType);
12280
+ const files = await this.fetchVideoFiles(components.filter((item) => item.containsCount === 0));
12281
+ return files.length > 0 ? files : null;
12282
+ }
12513
12283
  async buildVideoInstructions(mediaComponentId, category) {
12514
12284
  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] };
12285
+ const file = items.find((item) => item.type === "file");
12286
+ if (!file) return null;
12287
+ return filesToInstructions(file.title, [file], { id: category + "-section", label: file.title });
12540
12288
  }
12541
- async buildContainerInstructions(containerId, category) {
12289
+ async buildContainerInstructions(containerId) {
12542
12290
  const containerData = await this.fetchApi(
12543
12291
  `/media-components?ids=${containerId}&languageIds=${LANGUAGE_ID}`
12544
12292
  );
12545
- const container = containerData._embedded?.mediaComponents?.[0];
12546
- const containerName = container?.title || containerId;
12293
+ const containerName = containerData._embedded?.mediaComponents?.[0]?.title || containerId;
12547
12294
  const videoFiles = await this.fetchContainerVideoFiles(containerId);
12548
12295
  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] };
12296
+ return filesToInstructions(containerName, videoFiles, { id: containerId + "-section", label: containerName });
12571
12297
  }
12572
12298
  async getInstructions(path, _auth) {
12573
12299
  const { segments, depth } = parsePath(path);
@@ -12576,61 +12302,19 @@ var JesusFilmProvider = class {
12576
12302
  const subType = CATEGORY_MAP[category];
12577
12303
  if (!subType) return null;
12578
12304
  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] };
12305
+ const categoryName = CATEGORY_NAMES[category];
12306
+ const components = await this.fetchCategoryComponents(subType);
12307
+ const files = await this.fetchVideoFiles(components.filter((c) => c.containsCount === 0));
12308
+ return filesToInstructions(categoryName, files, { id: category + "-section", label: categoryName });
12619
12309
  }
12620
12310
  if (depth === 2) {
12621
12311
  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);
12312
+ return this.buildContainerInstructions(segments[1]);
12629
12313
  }
12630
- return null;
12631
- }
12632
- supportsDeviceFlow() {
12633
- return false;
12314
+ const lastId = segments[depth - 1];
12315
+ const videoResult = await this.buildVideoInstructions(lastId, category);
12316
+ if (videoResult) return videoResult;
12317
+ return this.buildContainerInstructions(lastId);
12634
12318
  }
12635
12319
  };
12636
12320
 
@@ -12674,35 +12358,15 @@ function convertPlaylistToFiles(playlist) {
12674
12358
  return file;
12675
12359
  });
12676
12360
  }
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
12361
 
12698
12362
  // src/providers/cbn/logo.ts
12699
12363
  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
12364
 
12701
12365
  // src/providers/cbn/CbnProvider.ts
12702
12366
  var API_BASE5 = "https://sbamemberdev.wpenginepowered.com/wp-json/superbook/v1";
12703
- var CbnProvider = class {
12367
+ var CbnProvider = class extends BaseProvider {
12704
12368
  constructor() {
12705
- this.apiHelper = new ApiHelper();
12369
+ super(...arguments);
12706
12370
  this.deviceFlowHelper = new DeviceFlowHelper();
12707
12371
  this.id = "cbn";
12708
12372
  this.name = "CBN";
@@ -12715,66 +12379,48 @@ var CbnProvider = class {
12715
12379
  clientId: "freeplay",
12716
12380
  scopes: [],
12717
12381
  supportsDeviceFlow: true,
12718
- deviceAuthEndpoint: "/device",
12719
- endpoints: {
12720
- catalog: "/catalog",
12721
- courseDetail: (courseId) => `/catalog/${courseId}`,
12722
- lessonPlaylist: (lessonId) => `/lesson-playlist/${lessonId}`
12723
- }
12382
+ deviceAuthEndpoint: "/device"
12724
12383
  };
12725
12384
  this.requiresAuth = true;
12726
12385
  this.authTypes = ["device_flow"];
12727
12386
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
12728
12387
  }
12729
- async apiRequest(path, auth) {
12730
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
12731
- }
12732
12388
  async browse(path, auth) {
12733
12389
  const { segments, depth } = parsePath(path);
12734
12390
  if (depth === 0) {
12735
12391
  return [{ type: "folder", id: "catalog-root", title: "Catalog", path: "/catalog" }];
12736
12392
  }
12737
12393
  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);
12394
+ if (depth === 1) {
12395
+ const response = await this.apiRequest("/catalog", auth);
12396
+ return Array.isArray(response) ? convertCoursesToFolders(response) : [];
12397
+ }
12398
+ if (depth === 2) {
12399
+ const response = await this.apiRequest(`/catalog/${segments[1]}`, auth);
12400
+ return response?.lessons ? convertLessonsToFolders(response.lessons, path) : [];
12401
+ }
12402
+ if (depth === 3) {
12403
+ const resolved = await this.resolveFiles(path, auth);
12404
+ return resolved?.files ?? [];
12405
+ }
12741
12406
  return [];
12742
12407
  }
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) {
12408
+ /** Resolve a leaf path (/catalog/{courseId}/{lessonId}) to its playlist files. */
12409
+ async resolveFiles(path, auth) {
12763
12410
  const { segments, depth } = parsePath(path);
12764
12411
  if (depth !== 3 || segments[0] !== "catalog") return null;
12765
- const playlist = await this.fetchPlaylist(segments[2], auth);
12412
+ const playlist = await this.apiRequest(`/lesson-playlist/${segments[2]}`, auth);
12766
12413
  if (!playlist || playlist.playlist.length === 0) return null;
12767
- return convertPlaylistToFiles(playlist);
12414
+ return { files: convertPlaylistToFiles(playlist), lessonTitle: playlist.lesson_title };
12768
12415
  }
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);
12416
+ async getPlaylist(path, auth, _resolution) {
12417
+ const resolved = await this.resolveFiles(path, auth);
12418
+ return resolved?.files ?? null;
12775
12419
  }
12776
- supportsDeviceFlow() {
12777
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
12420
+ async getInstructions(path, auth) {
12421
+ const resolved = await this.resolveFiles(path, auth);
12422
+ if (!resolved) return null;
12423
+ return filesToInstructions(resolved.lessonTitle || "Lesson", resolved.files);
12778
12424
  }
12779
12425
  async initiateDeviceFlow() {
12780
12426
  return this.deviceFlowHelper.initiateDeviceFlow(this.config);
@@ -16405,8 +16051,9 @@ var data_default3 = {
16405
16051
  };
16406
16052
 
16407
16053
  // src/providers/lifeChurch/LifeChurchProvider.ts
16408
- var LifeChurchProvider = class {
16054
+ var LifeChurchProvider = class extends BaseProvider {
16409
16055
  constructor() {
16056
+ super(...arguments);
16410
16057
  this.id = "lifechurch";
16411
16058
  this.name = "Life.Church";
16412
16059
  this.logos = {
@@ -16419,8 +16066,7 @@ var LifeChurchProvider = class {
16419
16066
  apiBase: "https://www.life.church",
16420
16067
  oauthBase: "",
16421
16068
  clientId: "",
16422
- scopes: [],
16423
- endpoints: {}
16069
+ scopes: []
16424
16070
  };
16425
16071
  this.requiresAuth = false;
16426
16072
  this.authTypes = ["none"];
@@ -16435,20 +16081,14 @@ var LifeChurchProvider = class {
16435
16081
  findSeries(seriesId) {
16436
16082
  return this.data.series.find((s) => s.id === seriesId);
16437
16083
  }
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
16084
  findScheduled(scheduledId) {
16445
16085
  return this.data.scheduledLessons?.find((s) => s.id === scheduledId);
16446
16086
  }
16447
16087
  /** Resolve a scheduled-lesson reference to the underlying lesson + unit + series. */
16448
16088
  resolveScheduled(sched) {
16449
16089
  const series = this.findSeries(sched.seriesId);
16450
- const unit = series && this.findUnit(series, sched.unitId);
16451
- const lesson = unit && this.findLesson(unit, sched.lessonId);
16090
+ const unit = series?.units.find((u) => u.id === sched.unitId);
16091
+ const lesson = unit?.lessons.find((l) => l.id === sched.lessonId);
16452
16092
  if (!series || !unit || !lesson) return null;
16453
16093
  return { series, unit, lesson };
16454
16094
  }
@@ -16463,17 +16103,40 @@ var LifeChurchProvider = class {
16463
16103
  providerData: { sourceUrl: lesson.sourceUrl }
16464
16104
  };
16465
16105
  }
16106
+ /** Resolve a leaf-or-unit path to its files plus the name/section used for instructions. */
16107
+ resolve(path) {
16108
+ const { segments, depth } = parsePath(path);
16109
+ if (segments[0] === "scheduled") {
16110
+ if (depth !== 2) return null;
16111
+ const sched = this.findScheduled(segments[1]);
16112
+ const resolved = sched && this.resolveScheduled(sched);
16113
+ if (!sched || !resolved) return null;
16114
+ const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16115
+ return {
16116
+ files: [this.lessonToFile(resolved.lesson, label)],
16117
+ name: label,
16118
+ section: { id: `scheduled-${sched.id}-section`, label: "This Sunday" }
16119
+ };
16120
+ }
16121
+ const series = this.findSeries(segments[0]);
16122
+ const unit = series?.units.find((u) => u.id === segments[1]);
16123
+ if (!unit) return null;
16124
+ if (depth === 2) {
16125
+ return { files: unit.lessons.map((l) => this.lessonToFile(l)), name: unit.name, section: { id: `${unit.id}-section`, label: unit.name } };
16126
+ }
16127
+ if (depth === 3) {
16128
+ const lesson = unit.lessons.find((l) => l.id === segments[2]);
16129
+ if (!lesson) return null;
16130
+ return { files: [this.lessonToFile(lesson)], name: lesson.title, section: { id: `${unit.id}-section`, label: unit.name } };
16131
+ }
16132
+ return null;
16133
+ }
16466
16134
  async browse(path, _auth) {
16467
16135
  const { segments, depth } = parsePath(path);
16468
16136
  if (depth === 0) {
16469
16137
  const items = [];
16470
16138
  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
- });
16139
+ items.push({ type: "folder", id: "scheduled", title: "This Sunday", path: "/scheduled" });
16477
16140
  }
16478
16141
  for (const s of this.data.series) {
16479
16142
  items.push({ type: "folder", id: s.id, title: s.name, thumbnail: s.thumbnail, path: `/${s.id}` });
@@ -16494,13 +16157,7 @@ var LifeChurchProvider = class {
16494
16157
  };
16495
16158
  });
16496
16159
  }
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 [];
16160
+ return this.resolve(path)?.files ?? [];
16504
16161
  }
16505
16162
  if (depth === 1) {
16506
16163
  const series = this.findSeries(segments[0]);
@@ -16515,8 +16172,8 @@ var LifeChurchProvider = class {
16515
16172
  }
16516
16173
  if (depth === 2) {
16517
16174
  const series = this.findSeries(segments[0]);
16518
- const unit = series && this.findUnit(series, segments[1]);
16519
- if (!series || !unit) return [];
16175
+ const unit = series?.units.find((u) => u.id === segments[1]);
16176
+ if (!unit) return [];
16520
16177
  return unit.lessons.map((l) => ({
16521
16178
  type: "folder",
16522
16179
  id: l.id,
@@ -16527,78 +16184,18 @@ var LifeChurchProvider = class {
16527
16184
  }));
16528
16185
  }
16529
16186
  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)];
16187
+ return this.resolve(path)?.files ?? [];
16535
16188
  }
16536
16189
  return [];
16537
16190
  }
16538
16191
  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;
16192
+ const resolved = this.resolve(path);
16193
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
16559
16194
  }
16560
16195
  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;
16196
+ const resolved = this.resolve(path);
16197
+ if (!resolved) return null;
16198
+ return filesToInstructions(resolved.name, resolved.files, resolved.section);
16602
16199
  }
16603
16200
  /**
16604
16201
  * Returns the most recent scheduled lesson from data.scheduledLessons.
@@ -16626,9 +16223,6 @@ var LifeChurchProvider = class {
16626
16223
  ]
16627
16224
  };
16628
16225
  }
16629
- supportsDeviceFlow() {
16630
- return false;
16631
- }
16632
16226
  };
16633
16227
 
16634
16228
  // src/providers/index.ts
@@ -16723,17 +16317,20 @@ function getAvailableProviders(ids) {
16723
16317
  }
16724
16318
 
16725
16319
  // src/index.ts
16726
- var VERSION = "0.4.0";
16320
+ var VERSION = true ? "0.5.0" : "dev";
16727
16321
  export {
16728
16322
  APlayProvider,
16729
16323
  B1ChurchProvider,
16324
+ BaseProvider,
16730
16325
  BibleProjectProvider,
16731
16326
  CbnProvider,
16327
+ DeviceFlowHelper,
16732
16328
  DropboxProvider,
16733
16329
  HighVoltageKidsProvider,
16734
16330
  JesusFilmProvider,
16735
16331
  LessonsChurchProvider,
16736
16332
  LifeChurchProvider,
16333
+ OAuthHelper,
16737
16334
  PlanningCenterProvider,
16738
16335
  SignPresenterProvider,
16739
16336
  TokenHelper,
@@ -16745,6 +16342,7 @@ export {
16745
16342
  isContentFile,
16746
16343
  isContentFolder,
16747
16344
  navigateToPath,
16748
- registerProvider
16345
+ registerProvider,
16346
+ toAuthData
16749
16347
  };
16750
16348
  //# sourceMappingURL=index.js.map