@churchapps/content-providers 0.4.1 → 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.cjs CHANGED
@@ -22,13 +22,16 @@ var index_exports = {};
22
22
  __export(index_exports, {
23
23
  APlayProvider: () => APlayProvider,
24
24
  B1ChurchProvider: () => B1ChurchProvider,
25
+ BaseProvider: () => BaseProvider,
25
26
  BibleProjectProvider: () => BibleProjectProvider,
26
27
  CbnProvider: () => CbnProvider,
28
+ DeviceFlowHelper: () => DeviceFlowHelper,
27
29
  DropboxProvider: () => DropboxProvider,
28
30
  HighVoltageKidsProvider: () => HighVoltageKidsProvider,
29
31
  JesusFilmProvider: () => JesusFilmProvider,
30
32
  LessonsChurchProvider: () => LessonsChurchProvider,
31
33
  LifeChurchProvider: () => LifeChurchProvider,
34
+ OAuthHelper: () => OAuthHelper,
32
35
  PlanningCenterProvider: () => PlanningCenterProvider,
33
36
  SignPresenterProvider: () => SignPresenterProvider,
34
37
  TokenHelper: () => TokenHelper,
@@ -40,7 +43,8 @@ __export(index_exports, {
40
43
  isContentFile: () => isContentFile,
41
44
  isContentFolder: () => isContentFolder,
42
45
  navigateToPath: () => navigateToPath,
43
- registerProvider: () => registerProvider
46
+ registerProvider: () => registerProvider,
47
+ toAuthData: () => toAuthData
44
48
  });
45
49
  module.exports = __toCommonJS(index_exports);
46
50
 
@@ -64,33 +68,81 @@ function navigateToPath(instructions, path) {
64
68
  return current;
65
69
  }
66
70
 
71
+ // src/helpers/TokenHelper.ts
72
+ function toAuthData(data, fallbacks) {
73
+ return {
74
+ access_token: data.access_token,
75
+ refresh_token: data.refresh_token || fallbacks?.refreshToken || "",
76
+ token_type: data.token_type || "Bearer",
77
+ created_at: Math.floor(Date.now() / 1e3),
78
+ expires_in: data.expires_in,
79
+ scope: data.scope || fallbacks?.scope || ""
80
+ };
81
+ }
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 toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
107
+ } catch {
108
+ return null;
109
+ }
110
+ }
111
+ };
112
+
67
113
  // src/helpers/OAuthHelper.ts
114
+ function generateCodeVerifier() {
115
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
116
+ const length = 64;
117
+ const array = new Uint8Array(length);
118
+ crypto.getRandomValues(array);
119
+ let result = "";
120
+ for (let i = 0; i < length; i++) {
121
+ result += chars.charAt(array[i] % chars.length);
122
+ }
123
+ return result;
124
+ }
125
+ async function generateCodeChallenge(verifier) {
126
+ const encoder = new TextEncoder();
127
+ const data = encoder.encode(verifier);
128
+ const hashBuffer = await crypto.subtle.digest("SHA-256", data);
129
+ const hashArray = new Uint8Array(hashBuffer);
130
+ let binary = "";
131
+ for (let i = 0; i < hashArray.length; i++) {
132
+ binary += String.fromCharCode(hashArray[i]);
133
+ }
134
+ const base64 = btoa(binary);
135
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
136
+ }
68
137
  var OAuthHelper = class {
69
138
  generateCodeVerifier() {
70
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
71
- const length = 64;
72
- const array = new Uint8Array(length);
73
- crypto.getRandomValues(array);
74
- let result = "";
75
- for (let i = 0; i < length; i++) {
76
- result += chars.charAt(array[i] % chars.length);
77
- }
78
- return result;
139
+ return generateCodeVerifier();
79
140
  }
80
141
  async generateCodeChallenge(verifier) {
81
- const encoder = new TextEncoder();
82
- const data = encoder.encode(verifier);
83
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
84
- const hashArray = new Uint8Array(hashBuffer);
85
- let binary = "";
86
- for (let i = 0; i < hashArray.length; i++) {
87
- binary += String.fromCharCode(hashArray[i]);
88
- }
89
- const base64 = btoa(binary);
90
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
142
+ return generateCodeChallenge(verifier);
91
143
  }
92
144
  async buildAuthUrl(config, codeVerifier, redirectUri, state) {
93
- const codeChallenge = await this.generateCodeChallenge(codeVerifier);
145
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
94
146
  const params = new URLSearchParams({
95
147
  response_type: "code",
96
148
  client_id: config.clientId,
@@ -117,39 +169,7 @@ var OAuthHelper = class {
117
169
  return null;
118
170
  }
119
171
  const data = await response.json();
120
- 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(" ") };
121
- } catch {
122
- return null;
123
- }
124
- }
125
- };
126
-
127
- // src/helpers/TokenHelper.ts
128
- var TokenHelper = class {
129
- isAuthValid(auth) {
130
- if (!auth) return false;
131
- return !this.isTokenExpired(auth);
132
- }
133
- isTokenExpired(auth) {
134
- if (!auth.created_at || !auth.expires_in) return true;
135
- const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
136
- return Date.now() > expiresAt - 5 * 60 * 1e3;
137
- }
138
- async refreshToken(config, auth) {
139
- if (!auth.refresh_token) return null;
140
- try {
141
- const params = new URLSearchParams({
142
- grant_type: "refresh_token",
143
- refresh_token: auth.refresh_token,
144
- client_id: config.clientId
145
- });
146
- const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
147
- if (!response.ok) {
148
- console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
149
- return null;
150
- }
151
- const data = await response.json();
152
- 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 };
172
+ return toAuthData(data, { scope: config.scopes.join(" ") });
153
173
  } catch {
154
174
  return null;
155
175
  }
@@ -176,7 +196,7 @@ var DeviceFlowHelper = class {
176
196
  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 }) });
177
197
  if (response.ok) {
178
198
  const data = await response.json();
179
- 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(" ") };
199
+ return toAuthData(data, { scope: config.scopes.join(" ") });
180
200
  }
181
201
  const errorData = await response.json();
182
202
  switch (errorData.error) {
@@ -226,6 +246,19 @@ var ApiHelper = class {
226
246
  }
227
247
  };
228
248
 
249
+ // src/providers/BaseProvider.ts
250
+ var BaseProvider = class {
251
+ constructor() {
252
+ this.apiHelper = new ApiHelper();
253
+ }
254
+ apiRequest(path, auth, method = "GET", body) {
255
+ return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
256
+ }
257
+ supportsDeviceFlow() {
258
+ return !!this.config.supportsDeviceFlow && !!this.config.deviceAuthEndpoint;
259
+ }
260
+ };
261
+
229
262
  // src/pathUtils.ts
230
263
  function parsePath(path) {
231
264
  if (!path || path === "/" || path === "") {
@@ -239,26 +272,6 @@ function getSegment(path, index) {
239
272
  return segments[index] ?? null;
240
273
  }
241
274
 
242
- // src/providers/aPlay/APlayApi.ts
243
- var API_BASE = "https://api-prod.amazingkids.app";
244
- async function checkMediaLicense(mediaId, auth) {
245
- if (!auth) return null;
246
- try {
247
- const url = `${API_BASE}/prod/reports/media/license-check`;
248
- 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] }) });
249
- if (!response.ok) return null;
250
- const data = await response.json();
251
- const licenseData = Array.isArray(data) ? data : data.data || [];
252
- const result = licenseData.find((item) => item.mediaId === mediaId);
253
- if (result?.isLicensed) {
254
- return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
255
- }
256
- return { mediaId, status: "not_licensed", message: "Media is not licensed" };
257
- } catch {
258
- return { mediaId, status: "unknown", message: "Unable to verify license status" };
259
- }
260
- }
261
-
262
275
  // src/utils.ts
263
276
  var IMAGE_DURATION_SECONDS = 15;
264
277
  function slugify(text) {
@@ -296,6 +309,31 @@ function createFile(id, title, url, options) {
296
309
  function generateId() {
297
310
  return "gen-" + Math.random().toString(36).substring(2, 11);
298
311
  }
312
+ function fileToActionItem(file) {
313
+ return {
314
+ id: file.id + "-action",
315
+ itemType: "action",
316
+ label: file.title,
317
+ actionType: "play",
318
+ seconds: file.seconds || void 0,
319
+ children: [
320
+ {
321
+ id: file.id,
322
+ itemType: "file",
323
+ label: file.title,
324
+ seconds: file.seconds || void 0,
325
+ downloadUrl: file.downloadUrl || file.url,
326
+ thumbnail: file.thumbnail,
327
+ mediaType: file.mediaType
328
+ }
329
+ ]
330
+ };
331
+ }
332
+ function filesToInstructions(name, files, section) {
333
+ const actions = files.map(fileToActionItem);
334
+ const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
335
+ return { name, items };
336
+ }
299
337
  function instructionsToPlaylist(instructions) {
300
338
  const files = [];
301
339
  function extractFiles(items) {
@@ -310,6 +348,26 @@ function instructionsToPlaylist(instructions) {
310
348
  return files;
311
349
  }
312
350
 
351
+ // src/providers/aPlay/APlayApi.ts
352
+ var API_BASE = "https://api-prod.amazingkids.app";
353
+ async function checkMediaLicense(mediaId, auth) {
354
+ if (!auth) return null;
355
+ try {
356
+ const url = `${API_BASE}/prod/reports/media/license-check`;
357
+ 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] }) });
358
+ if (!response.ok) return null;
359
+ const data = await response.json();
360
+ const licenseData = Array.isArray(data) ? data : data.data || [];
361
+ const result = licenseData.find((item) => item.mediaId === mediaId);
362
+ if (result?.isLicensed) {
363
+ return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
364
+ }
365
+ return { mediaId, status: "not_licensed", message: "Media is not licensed" };
366
+ } catch {
367
+ return { mediaId, status: "unknown", message: "Unable to verify license status" };
368
+ }
369
+ }
370
+
313
371
  // src/providers/aPlay/APlayConverters.ts
314
372
  function extractLibraryId(path) {
315
373
  const { segments, depth } = parsePath(path);
@@ -376,25 +434,6 @@ function convertProductsToFolders(products, currentPath) {
376
434
  return createFolder(id, p.title || p.name, `${currentPath}/products/${id}`, p.image);
377
435
  });
378
436
  }
379
- function convertFilesToInstructions(files, _libraryId) {
380
- const items = files.map((file) => ({
381
- id: file.id + "-action",
382
- itemType: "action",
383
- label: file.title,
384
- actionType: "play",
385
- children: [
386
- {
387
- id: file.id,
388
- itemType: "file",
389
- label: file.title,
390
- seconds: file.seconds,
391
- downloadUrl: file.url,
392
- thumbnail: file.thumbnail
393
- }
394
- ]
395
- }));
396
- return { name: "Library", items };
397
- }
398
437
 
399
438
  // src/providers/aPlay/APlayProvider.ts
400
439
  function extractArray(response, ...keys) {
@@ -407,20 +446,17 @@ function extractArray(response, ...keys) {
407
446
  }
408
447
  return [];
409
448
  }
410
- var APlayProvider = class {
449
+ var APlayProvider = class extends BaseProvider {
411
450
  constructor() {
412
- this.apiHelper = new ApiHelper();
451
+ super(...arguments);
413
452
  this.id = "aplay";
414
453
  this.name = "APlay";
415
454
  this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
416
- 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` } };
455
+ this.config = { id: "aplay", name: "APlay", apiBase: API_BASE, oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"] };
417
456
  this.requiresAuth = true;
418
457
  this.authTypes = ["oauth_pkce"];
419
458
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: true };
420
459
  }
421
- async apiRequest(path, auth) {
422
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
423
- }
424
460
  async browse(path, auth) {
425
461
  const { segments, depth } = parsePath(path);
426
462
  if (depth === 0) {
@@ -436,12 +472,12 @@ var APlayProvider = class {
436
472
  return [];
437
473
  }
438
474
  async getModules(auth) {
439
- const response = await this.apiRequest(this.config.endpoints.modules, auth);
475
+ const response = await this.apiRequest("/prod/curriculum/modules", auth);
440
476
  const modules = extractArray(response, "data", "modules");
441
477
  return convertModulesToFolders(modules);
442
478
  }
443
479
  async getModuleContent(moduleId, currentPath, auth) {
444
- const response = await this.apiRequest(this.config.endpoints.modules, auth);
480
+ const response = await this.apiRequest("/prod/curriculum/modules", auth);
445
481
  const modules = extractArray(response, "data", "modules");
446
482
  const module2 = modules.find((m) => (m.id || m.moduleId) === moduleId);
447
483
  if (!module2) return [];
@@ -457,14 +493,12 @@ var APlayProvider = class {
457
493
  }
458
494
  }
459
495
  async getLibraryFolders(productId, currentPath, auth) {
460
- const pathFn = this.config.endpoints.productLibraries;
461
- const response = await this.apiRequest(pathFn(productId), auth);
496
+ const response = await this.apiRequest(`/prod/curriculum/modules/products/${productId}/libraries`, auth);
462
497
  const libraries = extractArray(response, "data", "libraries");
463
498
  return convertLibrariesToFolders(libraries, currentPath);
464
499
  }
465
500
  async getMediaFiles(libraryId, auth) {
466
- const pathFn = this.config.endpoints.libraryMedia;
467
- const response = await this.apiRequest(pathFn(libraryId), auth);
501
+ const response = await this.apiRequest(`/prod/creators/libraries/${libraryId}/media`, auth);
468
502
  const mediaItems = extractArray(response, "data", "media");
469
503
  return convertMediaToFiles(mediaItems);
470
504
  }
@@ -479,63 +513,47 @@ var APlayProvider = class {
479
513
  if (!libraryId) return null;
480
514
  const files = await this.getMediaFiles(libraryId, auth);
481
515
  if (files.length === 0) return null;
482
- return convertFilesToInstructions(files, libraryId);
516
+ return filesToInstructions("Library", files);
483
517
  }
484
518
  async checkMediaLicense(mediaId, auth) {
485
519
  return checkMediaLicense(mediaId, auth);
486
520
  }
487
- supportsDeviceFlow() {
488
- return false;
489
- }
490
521
  };
491
522
 
492
523
  // src/providers/signPresenter/SignPresenterProvider.ts
493
- var SignPresenterProvider = class {
524
+ var SignPresenterProvider = class extends BaseProvider {
494
525
  constructor() {
495
- this.apiHelper = new ApiHelper();
526
+ super(...arguments);
496
527
  this.oauthHelper = new OAuthHelper();
497
528
  this.deviceFlowHelper = new DeviceFlowHelper();
498
529
  this.id = "signpresenter";
499
530
  this.name = "SignPresenter";
500
531
  this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
501
- 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` } };
532
+ 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" };
502
533
  this.requiresAuth = true;
503
534
  this.authTypes = ["oauth_pkce", "device_flow"];
504
535
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
505
536
  }
506
- async apiRequest(path, auth) {
507
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
508
- }
509
537
  async browse(path, auth) {
510
538
  const { segments, depth } = parsePath(path);
511
539
  if (depth === 0) {
512
- return [
513
- {
514
- type: "folder",
515
- id: "playlists-root",
516
- title: "Playlists",
517
- path: "/playlists"
518
- }
519
- ];
520
- }
521
- const root = segments[0];
522
- if (root !== "playlists") return [];
523
- if (depth === 1) {
524
- return this.getPlaylists(auth);
525
- }
526
- if (depth === 2) {
527
- const playlistId = segments[1];
528
- return this.getMessages(playlistId, auth);
540
+ return [{ type: "folder", id: "playlists-root", title: "Playlists", path: "/playlists" }];
529
541
  }
542
+ if (segments[0] !== "playlists") return [];
543
+ if (depth === 1) return this.getPlaylists(auth);
544
+ if (depth === 2) return this.getMessages(segments[1], auth);
530
545
  return [];
531
546
  }
547
+ extractList(response, key) {
548
+ if (Array.isArray(response)) return response;
549
+ const record = response;
550
+ const list = record?.data || record?.[key] || [];
551
+ return Array.isArray(list) ? list : [];
552
+ }
532
553
  async getPlaylists(auth) {
533
- const apiPath = this.config.endpoints.playlists;
534
- const response = await this.apiRequest(apiPath, auth);
554
+ const response = await this.apiRequest("/content/playlists", auth);
535
555
  if (!response) return [];
536
- const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
537
- if (!Array.isArray(playlists)) return [];
538
- return playlists.map((p) => ({
556
+ return this.extractList(response, "playlists").map((p) => ({
539
557
  type: "folder",
540
558
  id: p.id,
541
559
  title: p.name,
@@ -545,26 +563,17 @@ var SignPresenterProvider = class {
545
563
  }));
546
564
  }
547
565
  async getMessages(playlistId, auth) {
548
- const pathFn = this.config.endpoints.messages;
549
- const response = await this.apiRequest(pathFn(playlistId), auth);
566
+ const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
550
567
  if (!response) return [];
551
- const messages = Array.isArray(response) ? response : response.data || response.messages || [];
552
- if (!Array.isArray(messages)) return [];
553
568
  const files = [];
554
- for (const msg of messages) {
569
+ for (const msg of this.extractList(response, "messages")) {
555
570
  if (!msg.url) continue;
556
571
  const url = msg.url;
557
- const seconds = msg.seconds;
558
- const file = createFile(
559
- msg.id,
560
- msg.name,
561
- url,
562
- {
563
- mediaType: detectMediaType(url, msg.mediaType),
564
- thumbnail: msg.thumbnail || msg.image,
565
- seconds
566
- }
567
- );
572
+ const file = createFile(msg.id, msg.name, url, {
573
+ mediaType: detectMediaType(url, msg.mediaType),
574
+ thumbnail: msg.thumbnail || msg.image,
575
+ seconds: msg.seconds
576
+ });
568
577
  file.downloadUrl = url;
569
578
  files.push(file);
570
579
  }
@@ -573,8 +582,7 @@ var SignPresenterProvider = class {
573
582
  async getPlaylist(path, auth, _resolution) {
574
583
  const { segments, depth } = parsePath(path);
575
584
  if (depth < 2 || segments[0] !== "playlists") return null;
576
- const playlistId = segments[1];
577
- const files = await this.getMessages(playlistId, auth);
585
+ const files = await this.getMessages(segments[1], auth);
578
586
  return files.length > 0 ? files : null;
579
587
  }
580
588
  async getInstructions(path, auth) {
@@ -584,43 +592,11 @@ var SignPresenterProvider = class {
584
592
  const files = await this.getMessages(playlistId, auth);
585
593
  if (files.length === 0) return null;
586
594
  const playlists = await this.getPlaylists(auth);
587
- const playlist = playlists.find((p) => p.id === playlistId);
588
- const title = playlist?.title || "Playlist";
589
- const actionItems = files.map((file) => ({
590
- id: file.id + "-action",
591
- itemType: "action",
592
- label: file.title,
593
- actionType: "play",
594
- seconds: file.seconds,
595
- children: [
596
- {
597
- id: file.id,
598
- itemType: "file",
599
- label: file.title,
600
- seconds: file.seconds,
601
- downloadUrl: file.downloadUrl || file.url,
602
- thumbnail: file.thumbnail
603
- }
604
- ]
605
- }));
606
- const sectionItem = {
607
- id: playlistId + "-section",
608
- itemType: "section",
609
- label: title,
610
- children: actionItems
611
- };
612
- const headerItem = {
613
- id: playlistId + "-header",
614
- itemType: "header",
615
- label: title,
616
- children: [sectionItem]
617
- };
618
- return { name: title, items: [headerItem] };
595
+ const title = playlists.find((p) => p.id === playlistId)?.title || "Playlist";
596
+ const instructions = filesToInstructions(title, files, { id: playlistId + "-section", label: title });
597
+ return { name: title, items: [{ id: playlistId + "-header", itemType: "header", label: title, children: instructions.items }] };
619
598
  }
620
599
  // Auth methods
621
- supportsDeviceFlow() {
622
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
623
- }
624
600
  generateCodeVerifier() {
625
601
  return this.oauthHelper.generateCodeVerifier();
626
602
  }
@@ -667,6 +643,23 @@ function truncateForLabel(text) {
667
643
  const truncated = text.length > cutoff ? text.substring(0, cutoff) : text;
668
644
  return stripMarkdown(truncated) + (text.length > cutoff ? "..." : "");
669
645
  }
646
+ function getEmbedUrl(apiType, relatedId) {
647
+ if (!relatedId) return void 0;
648
+ const baseUrl = "https://lessons.church";
649
+ switch (apiType) {
650
+ case "action":
651
+ case "lessonAction":
652
+ return `${baseUrl}/embed/action/${relatedId}`;
653
+ case "addon":
654
+ case "lessonAddOn":
655
+ return `${baseUrl}/embed/addon/${relatedId}`;
656
+ case "section":
657
+ case "lessonSection":
658
+ return `${baseUrl}/embed/section/${relatedId}`;
659
+ default:
660
+ return void 0;
661
+ }
662
+ }
670
663
  async function convertAddOnToFile(addOn) {
671
664
  const apiPath = `/addOns/public/${addOn.id}`;
672
665
  const detail = await apiRequest(apiPath);
@@ -813,12 +806,13 @@ async function convertAddOnCategoryToInstructions(category) {
813
806
  }
814
807
 
815
808
  // src/providers/lessonsChurch/LessonsChurchProvider.ts
816
- var LessonsChurchProvider = class {
809
+ var LessonsChurchProvider = class extends BaseProvider {
817
810
  constructor() {
811
+ super(...arguments);
818
812
  this.id = "lessonschurch";
819
813
  this.name = "Lessons.church";
820
814
  this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
821
- 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}` } };
815
+ this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [] };
822
816
  this.requiresAuth = false;
823
817
  this.authTypes = ["none"];
824
818
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
@@ -874,28 +868,25 @@ var LessonsChurchProvider = class {
874
868
  return [];
875
869
  }
876
870
  async getPrograms() {
877
- const response = await apiRequest(this.config.endpoints.programs);
871
+ const response = await apiRequest("/programs/public");
878
872
  if (!response) return [];
879
873
  const programs = Array.isArray(response) ? response : [];
880
874
  return programs.map((p) => createFolder(p.id, p.name, `/lessons/${p.id}`, p.image));
881
875
  }
882
876
  async getStudies(programId, currentPath) {
883
- const pathFn = this.config.endpoints.studies;
884
- const response = await apiRequest(pathFn(programId));
877
+ const response = await apiRequest(`/studies/public/program/${programId}`);
885
878
  if (!response) return [];
886
879
  const studies = Array.isArray(response) ? response : [];
887
880
  return studies.map((s) => createFolder(s.id, s.name, `${currentPath}/${s.id}`, s.image)).sort((a, b) => a.title.localeCompare(b.title));
888
881
  }
889
882
  async getLessons(studyId, currentPath) {
890
- const pathFn = this.config.endpoints.lessons;
891
- const response = await apiRequest(pathFn(studyId));
883
+ const response = await apiRequest(`/lessons/public/study/${studyId}`);
892
884
  if (!response) return [];
893
885
  const lessons = Array.isArray(response) ? response : [];
894
886
  return lessons.map((l) => createFolder(l.id, l.name || l.title, `${currentPath}/${l.id}`, l.image));
895
887
  }
896
888
  async getVenues(lessonId, currentPath) {
897
- const pathFn = this.config.endpoints.venues;
898
- const response = await apiRequest(pathFn(lessonId));
889
+ const response = await apiRequest(`/venues/public/lesson/${lessonId}`);
899
890
  if (!response) return [];
900
891
  const lessonResponse = await apiRequest(`/lessons/public/${lessonId}`);
901
892
  const lessonImage = lessonResponse?.image;
@@ -918,7 +909,7 @@ var LessonsChurchProvider = class {
918
909
  return file ? [file] : [];
919
910
  }
920
911
  async getAddOnCategories() {
921
- const response = await apiRequest(this.config.endpoints.addOns);
912
+ const response = await apiRequest("/addOns/public");
922
913
  if (!response) return [];
923
914
  const addOns = Array.isArray(response) ? response : [];
924
915
  const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
@@ -926,7 +917,7 @@ var LessonsChurchProvider = class {
926
917
  }
927
918
  async getAddOnsByCategory(category, currentPath) {
928
919
  const decodedCategory = decodeURIComponent(category);
929
- const response = await apiRequest(this.config.endpoints.addOns);
920
+ const response = await apiRequest("/addOns/public");
930
921
  if (!response) return [];
931
922
  const allAddOns = Array.isArray(response) ? response : [];
932
923
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
@@ -969,9 +960,6 @@ var LessonsChurchProvider = class {
969
960
  }
970
961
  return null;
971
962
  }
972
- supportsDeviceFlow() {
973
- return false;
974
- }
975
963
  };
976
964
 
977
965
  // src/providers/registry.ts
@@ -987,18 +975,6 @@ function registerProvider(provider) {
987
975
  }
988
976
 
989
977
  // src/providers/b1Church/B1ChurchAuth.ts
990
- async function generateCodeChallenge(verifier) {
991
- const encoder = new TextEncoder();
992
- const data = encoder.encode(verifier);
993
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
994
- const hashArray = new Uint8Array(hashBuffer);
995
- let binary = "";
996
- for (let i = 0; i < hashArray.length; i++) {
997
- binary += String.fromCharCode(hashArray[i]);
998
- }
999
- const base64 = btoa(binary);
1000
- return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
1001
- }
1002
978
  async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
1003
979
  const codeChallenge = await generateCodeChallenge(codeVerifier);
1004
980
  const oauthParams = new URLSearchParams({
@@ -1022,7 +998,7 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
1022
998
  return null;
1023
999
  }
1024
1000
  const data = await response.json();
1025
- 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(" ") };
1001
+ return toAuthData(data, { scope: config.scopes.join(" ") });
1026
1002
  } catch {
1027
1003
  return null;
1028
1004
  }
@@ -1036,7 +1012,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
1036
1012
  return null;
1037
1013
  }
1038
1014
  const data = await response.json();
1039
- 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(" ") };
1015
+ return toAuthData(data, { scope: config.scopes.join(" ") });
1040
1016
  } catch {
1041
1017
  return null;
1042
1018
  }
@@ -1048,7 +1024,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
1048
1024
  const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1049
1025
  if (!response.ok) return null;
1050
1026
  const data = await response.json();
1051
- 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 };
1027
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
1052
1028
  } catch {
1053
1029
  return null;
1054
1030
  }
@@ -1073,78 +1049,32 @@ async function authFetch(url, auth) {
1073
1049
  }
1074
1050
  }
1075
1051
  async function fetchMinistries(auth) {
1076
- const result = await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth);
1077
- return result || [];
1052
+ return await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth) || [];
1078
1053
  }
1079
1054
  async function fetchPlanTypes(ministryId, auth) {
1080
- const result = await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth);
1081
- return result || [];
1055
+ return await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth) || [];
1082
1056
  }
1083
1057
  async function fetchPlans(planTypeId, auth) {
1084
- const result = await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth);
1085
- return result || [];
1058
+ return await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth) || [];
1086
1059
  }
1087
- var DOING_API_BASE = "https://api.churchapps.org/doing";
1088
1060
  async function fetchCurrentPlanByType(planTypeId) {
1089
- try {
1090
- const response = await fetch(`${DOING_API_BASE}/plans/public/current/${planTypeId}`, { method: "GET", headers: { Accept: "application/json" } });
1091
- if (!response.ok) {
1092
- console.warn(`[B1Church] fetchCurrentPlanByType failed: HTTP ${response.status} for planTypeId=${planTypeId}`);
1093
- return null;
1094
- }
1095
- return await response.json();
1096
- } catch (err) {
1097
- console.error(`[B1Church] fetchCurrentPlanByType error: planTypeId=${planTypeId}`, err);
1098
- return null;
1099
- }
1061
+ return authFetch(`${API_BASE3}/doing/plans/public/current/${planTypeId}`);
1100
1062
  }
1101
1063
  async function fetchPlanItems(churchId, planId) {
1102
- try {
1103
- const response = await fetch(`${DOING_API_BASE}/planItems/presenter/${churchId}/${planId}`, { method: "GET", headers: { Accept: "application/json" } });
1104
- if (!response.ok) return [];
1105
- return await response.json() || [];
1106
- } catch {
1107
- return [];
1108
- }
1064
+ return await authFetch(`${API_BASE3}/doing/planItems/presenter/${churchId}/${planId}`) || [];
1109
1065
  }
1110
1066
  async function fetchVenueFeed(venueId) {
1111
- try {
1112
- const response = await fetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
1113
- if (!response.ok) {
1114
- console.warn(`[B1Church] fetchVenueFeed failed: HTTP ${response.status} for venueId=${venueId}`);
1115
- return null;
1116
- }
1117
- return await response.json();
1118
- } catch (err) {
1119
- console.error(`[B1Church] fetchVenueFeed error: venueId=${venueId}`, err);
1120
- return null;
1121
- }
1067
+ return authFetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`);
1122
1068
  }
1123
1069
  async function fetchVenueActions(venueId) {
1124
- try {
1125
- const response = await fetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
1126
- if (!response.ok) {
1127
- console.warn(`[B1Church] fetchVenueActions failed: HTTP ${response.status} for venueId=${venueId}`);
1128
- return null;
1129
- }
1130
- return await response.json();
1131
- } catch (err) {
1132
- console.error(`[B1Church] fetchVenueActions error: venueId=${venueId}`, err);
1133
- return null;
1134
- }
1070
+ return authFetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`);
1135
1071
  }
1136
1072
  async function fetchVenueImages(venueIds) {
1137
1073
  const map = /* @__PURE__ */ new Map();
1138
1074
  if (venueIds.length === 0) return map;
1139
- try {
1140
- const url = `${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`;
1141
- const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
1142
- if (!response.ok) return map;
1143
- const data = await response.json();
1144
- for (const row of data) {
1145
- if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1146
- }
1147
- } catch {
1075
+ const data = await authFetch(`${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`);
1076
+ for (const row of data || []) {
1077
+ if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1148
1078
  }
1149
1079
  return map;
1150
1080
  }
@@ -1233,18 +1163,7 @@ function getFileFromProviderFileItem(item) {
1233
1163
  };
1234
1164
  }
1235
1165
  function planItemToInstruction(item, thumbnail) {
1236
- let itemType = item.itemType;
1237
- switch (item.itemType) {
1238
- case "lessonSection":
1239
- itemType = "section";
1240
- break;
1241
- case "lessonAction":
1242
- itemType = "action";
1243
- break;
1244
- case "lessonAddOn":
1245
- itemType = "action";
1246
- break;
1247
- }
1166
+ const itemType = normalizeItemType(item.itemType);
1248
1167
  const isFileType = itemType === "file" || item.link && !item.children?.length;
1249
1168
  return {
1250
1169
  id: item.id,
@@ -1258,23 +1177,6 @@ function planItemToInstruction(item, thumbnail) {
1258
1177
  children: item.children?.map((child) => planItemToInstruction(child, thumbnail))
1259
1178
  };
1260
1179
  }
1261
- function getEmbedUrl(apiType, relatedId) {
1262
- if (!relatedId) return void 0;
1263
- const baseUrl = "https://lessons.church";
1264
- switch (apiType) {
1265
- case "action":
1266
- case "lessonAction":
1267
- return `${baseUrl}/embed/action/${relatedId}`;
1268
- case "addon":
1269
- case "lessonAddOn":
1270
- return `${baseUrl}/embed/addon/${relatedId}`;
1271
- case "section":
1272
- case "lessonSection":
1273
- return `${baseUrl}/embed/section/${relatedId}`;
1274
- default:
1275
- return void 0;
1276
- }
1277
- }
1278
1180
  function buildSectionActionsMap2(actionsResponse, thumbnail) {
1279
1181
  const sectionActionsMap = /* @__PURE__ */ new Map();
1280
1182
  if (actionsResponse?.sections) {
@@ -1388,9 +1290,9 @@ function isExternalProviderItem(item) {
1388
1290
  const itemType = item.itemType || "";
1389
1291
  return itemType.startsWith("provider");
1390
1292
  }
1391
- var B1ChurchProvider = class {
1293
+ var B1ChurchProvider = class extends BaseProvider {
1392
1294
  constructor() {
1393
- this.apiHelper = new ApiHelper();
1295
+ super(...arguments);
1394
1296
  this.deviceFlowHelper = new DeviceFlowHelper();
1395
1297
  // Unified cache for external provider data to avoid duplicate calls across methods
1396
1298
  this.externalContentCache = {
@@ -1401,16 +1303,13 @@ var B1ChurchProvider = class {
1401
1303
  this.id = "b1church";
1402
1304
  this.name = "B1.Church";
1403
1305
  this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
1404
- 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}` } };
1306
+ 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" };
1405
1307
  this.appBase = "https://admin.b1.church";
1406
1308
  this.requiresAuth = true;
1407
1309
  this.authTypes = ["oauth_pkce", "device_flow"];
1408
1310
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1409
1311
  this.planTypeId = null;
1410
1312
  }
1411
- async apiRequest(path, authData) {
1412
- return this.apiHelper.apiRequest(this.config, this.id, path, authData);
1413
- }
1414
1313
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1415
1314
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1416
1315
  }
@@ -1556,8 +1455,7 @@ var B1ChurchProvider = class {
1556
1455
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
1557
1456
  return null;
1558
1457
  }
1559
- const pathFn = this.config.endpoints.planItems;
1560
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1458
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1561
1459
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1562
1460
  const externalInstructions = await fetchFromProviderProxy(
1563
1461
  "getInstructions",
@@ -1665,8 +1563,7 @@ var B1ChurchProvider = class {
1665
1563
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1666
1564
  return null;
1667
1565
  }
1668
- const pathFn = this.config.endpoints.planItems;
1669
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1566
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1670
1567
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1671
1568
  const externalFiles = await fetchFromProviderProxy(
1672
1569
  "getPlaylist",
@@ -1761,9 +1658,6 @@ var B1ChurchProvider = class {
1761
1658
  }
1762
1659
  return files;
1763
1660
  }
1764
- supportsDeviceFlow() {
1765
- return !!this.config.supportsDeviceFlow;
1766
- }
1767
1661
  };
1768
1662
 
1769
1663
  // src/providers/dropbox/DropboxConverters.ts
@@ -1789,38 +1683,13 @@ function filterMediaEntries(entries) {
1789
1683
  }
1790
1684
  return { folders, mediaFiles };
1791
1685
  }
1792
- function buildInstructionsFromFiles(files, folderName) {
1793
- const actionItems = files.map((file) => ({
1794
- id: file.id + "-action",
1795
- itemType: "action",
1796
- label: file.title,
1797
- actionType: "play",
1798
- children: [
1799
- {
1800
- id: file.id,
1801
- itemType: "file",
1802
- label: file.title,
1803
- seconds: file.seconds,
1804
- downloadUrl: file.downloadUrl || file.url,
1805
- thumbnail: file.thumbnail
1806
- }
1807
- ]
1808
- }));
1809
- const sectionItem = {
1810
- id: "dropbox-section",
1811
- itemType: "section",
1812
- label: folderName,
1813
- children: actionItems
1814
- };
1815
- return { name: folderName, items: [sectionItem] };
1816
- }
1817
1686
 
1818
1687
  // src/providers/dropbox/DropboxProvider.ts
1819
1688
  var MAX_PAGINATION_CALLS = 50;
1820
- var DropboxProvider = class {
1689
+ var DropboxProvider = class extends BaseProvider {
1821
1690
  constructor() {
1691
+ super(...arguments);
1822
1692
  this.oauthHelper = new OAuthHelper();
1823
- this.apiHelper = new ApiHelper();
1824
1693
  this.id = "dropbox";
1825
1694
  this.name = "Dropbox";
1826
1695
  this.logos = {
@@ -1833,12 +1702,7 @@ var DropboxProvider = class {
1833
1702
  apiBase: "https://api.dropboxapi.com",
1834
1703
  oauthBase: "https://www.dropbox.com/oauth2",
1835
1704
  clientId: "9io0q0q9angdz9j",
1836
- scopes: [],
1837
- endpoints: {
1838
- listFolder: "/2/files/list_folder",
1839
- listFolderContinue: "/2/files/list_folder/continue",
1840
- getTemporaryLink: "/2/files/get_temporary_link"
1841
- }
1705
+ scopes: []
1842
1706
  };
1843
1707
  this.requiresAuth = true;
1844
1708
  this.authTypes = ["oauth_pkce"];
@@ -1855,12 +1719,12 @@ var DropboxProvider = class {
1855
1719
  console.error("[Dropbox] No auth token provided");
1856
1720
  return Promise.resolve(null);
1857
1721
  }
1858
- return this.apiHelper.apiRequest(this.config, this.id, endpoint, auth, "POST", body);
1722
+ return this.apiRequest(endpoint, auth, "POST", body);
1859
1723
  }
1860
1724
  async listAllEntries(dropboxPath, auth) {
1861
1725
  const allEntries = [];
1862
1726
  const response = await this.dropboxPost(
1863
- this.config.endpoints.listFolder,
1727
+ "/2/files/list_folder",
1864
1728
  { path: dropboxPath, recursive: false, include_media_info: false },
1865
1729
  auth
1866
1730
  );
@@ -1871,7 +1735,7 @@ var DropboxProvider = class {
1871
1735
  let calls = 0;
1872
1736
  while (hasMore && calls < MAX_PAGINATION_CALLS) {
1873
1737
  const cont = await this.dropboxPost(
1874
- this.config.endpoints.listFolderContinue,
1738
+ "/2/files/list_folder/continue",
1875
1739
  { cursor },
1876
1740
  auth
1877
1741
  );
@@ -1921,7 +1785,7 @@ var DropboxProvider = class {
1921
1785
  }
1922
1786
  async getTemporaryLink(filePath, auth) {
1923
1787
  const response = await this.dropboxPost(
1924
- this.config.endpoints.getTemporaryLink,
1788
+ "/2/files/get_temporary_link",
1925
1789
  { path: filePath },
1926
1790
  auth
1927
1791
  );
@@ -1942,7 +1806,7 @@ var DropboxProvider = class {
1942
1806
  // -- Leaf detection --
1943
1807
  async checkIsLeaf(folderPath, auth) {
1944
1808
  const response = await this.dropboxPost(
1945
- this.config.endpoints.listFolder,
1809
+ "/2/files/list_folder",
1946
1810
  { path: folderPath, recursive: false, include_media_info: false },
1947
1811
  auth
1948
1812
  );
@@ -1961,31 +1825,29 @@ var DropboxProvider = class {
1961
1825
  const fileItems = await this.resolveFileLinks(mediaFiles, auth);
1962
1826
  return [...folderItems, ...fileItems];
1963
1827
  }
1964
- async getPlaylist(path, auth, _resolution) {
1828
+ /** Resolve a folder path to playable files (list + media filter + link resolution). */
1829
+ async resolveMediaFiles(path, auth) {
1965
1830
  if (!path || path === "/") return null;
1966
1831
  const dropboxPath = path.startsWith("/") ? path : "/" + path;
1967
1832
  const allEntries = await this.listAllEntries(dropboxPath, auth);
1968
1833
  const { mediaFiles } = filterMediaEntries(allEntries);
1969
1834
  if (mediaFiles.length === 0) return null;
1970
1835
  const files = await this.resolveFileLinks(mediaFiles, auth);
1971
- return files.length > 0 ? files : null;
1836
+ if (files.length === 0) return null;
1837
+ return { files, dropboxPath };
1838
+ }
1839
+ async getPlaylist(path, auth, _resolution) {
1840
+ const resolved = await this.resolveMediaFiles(path, auth);
1841
+ return resolved?.files ?? null;
1972
1842
  }
1973
1843
  async getInstructions(path, auth) {
1974
- if (!path || path === "/") return null;
1975
- const dropboxPath = path.startsWith("/") ? path : "/" + path;
1976
- const allEntries = await this.listAllEntries(dropboxPath, auth);
1977
- const { mediaFiles } = filterMediaEntries(allEntries);
1978
- if (mediaFiles.length === 0) return null;
1979
- const files = await this.resolveFileLinks(mediaFiles, auth);
1980
- if (files.length === 0) return null;
1981
- const segments = dropboxPath.split("/").filter(Boolean);
1844
+ const resolved = await this.resolveMediaFiles(path, auth);
1845
+ if (!resolved) return null;
1846
+ const segments = resolved.dropboxPath.split("/").filter(Boolean);
1982
1847
  const folderName = segments[segments.length - 1] || "Dropbox";
1983
- return buildInstructionsFromFiles(files, folderName);
1848
+ return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
1984
1849
  }
1985
1850
  // -- Auth methods --
1986
- supportsDeviceFlow() {
1987
- return false;
1988
- }
1989
1851
  generateCodeVerifier() {
1990
1852
  return this.oauthHelper.generateCodeVerifier();
1991
1853
  }
@@ -2022,61 +1884,43 @@ var DropboxProvider = class {
2022
1884
  body: params.toString()
2023
1885
  });
2024
1886
  if (!response.ok) return null;
2025
- const data = await response.json();
2026
- return {
2027
- access_token: data.access_token,
2028
- refresh_token: data.refresh_token,
2029
- token_type: data.token_type || "Bearer",
2030
- created_at: Math.floor(Date.now() / 1e3),
2031
- expires_in: data.expires_in,
2032
- scope: data.scope || ""
2033
- };
1887
+ return toAuthData(await response.json());
2034
1888
  } catch {
2035
1889
  return null;
2036
1890
  }
2037
1891
  }
2038
1892
  };
2039
1893
 
2040
- // src/providers/planningCenter/PlanningCenterConverters.ts
2041
- var apiHelper = new ApiHelper();
1894
+ // src/providers/planningCenter/PlanningCenterProvider.ts
1895
+ var ONE_WEEK_MS = 6048e5;
2042
1896
  function formatDate(dateString) {
2043
- const date = new Date(dateString);
2044
- return date.toISOString().slice(0, 10);
1897
+ return new Date(dateString).toISOString().slice(0, 10);
2045
1898
  }
2046
-
2047
- // src/providers/planningCenter/PlanningCenterProvider.ts
2048
- var PlanningCenterProvider = class {
1899
+ var PlanningCenterProvider = class extends BaseProvider {
2049
1900
  constructor() {
2050
- this.apiHelper = new ApiHelper();
1901
+ super(...arguments);
2051
1902
  this.id = "planningcenter";
2052
1903
  this.name = "Planning Center";
2053
1904
  this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
2054
- 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` } };
2055
- this.ONE_WEEK_MS = 6048e5;
1905
+ this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
2056
1906
  this.requiresAuth = true;
2057
1907
  this.authTypes = ["oauth_pkce"];
2058
- this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
2059
- }
2060
- async apiRequest(path, auth) {
2061
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
1908
+ // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1909
+ this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
2062
1910
  }
2063
1911
  async browse(path, auth) {
2064
1912
  const { segments, depth } = parsePath(path);
2065
1913
  if (depth === 0) {
2066
1914
  return [{ type: "folder", id: "serviceTypes-root", title: "Service Types", path: "/serviceTypes" }];
2067
1915
  }
2068
- const root = segments[0];
2069
- if (root !== "serviceTypes") return [];
1916
+ if (segments[0] !== "serviceTypes") return [];
2070
1917
  if (depth === 1) return this.getServiceTypes(auth);
2071
1918
  if (depth === 2) return this.getPlans(segments[1], path, auth);
2072
1919
  if (depth === 3) return this.getPlanItems(segments[1], segments[2], auth);
2073
1920
  return [];
2074
1921
  }
2075
1922
  async getServiceTypes(auth) {
2076
- const response = await this.apiRequest(
2077
- this.config.endpoints.serviceTypes,
2078
- auth
2079
- );
1923
+ const response = await this.apiRequest("/services/v2/service_types", auth);
2080
1924
  if (!response?.data) return [];
2081
1925
  return response.data.map((serviceType) => ({
2082
1926
  type: "folder",
@@ -2086,9 +1930,8 @@ var PlanningCenterProvider = class {
2086
1930
  }));
2087
1931
  }
2088
1932
  async getPlans(serviceTypeId, currentPath, auth) {
2089
- const pathFn = this.config.endpoints.plans;
2090
1933
  const response = await this.apiRequest(
2091
- `${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
1934
+ `/services/v2/service_types/${serviceTypeId}/plans?filter=future&order=sort_date`,
2092
1935
  auth
2093
1936
  );
2094
1937
  if (!response?.data) return [];
@@ -2096,7 +1939,7 @@ var PlanningCenterProvider = class {
2096
1939
  const filteredPlans = response.data.filter((plan) => {
2097
1940
  if (plan.attributes.items_count === 0) return false;
2098
1941
  const planDate = new Date(plan.attributes.sort_date).getTime();
2099
- return planDate < now + this.ONE_WEEK_MS;
1942
+ return planDate < now + ONE_WEEK_MS;
2100
1943
  });
2101
1944
  return filteredPlans.map((plan) => ({
2102
1945
  type: "folder",
@@ -2107,17 +1950,13 @@ var PlanningCenterProvider = class {
2107
1950
  }));
2108
1951
  }
2109
1952
  async getPlanItems(serviceTypeId, planId, auth) {
2110
- const pathFn = this.config.endpoints.planItems;
2111
1953
  const response = await this.apiRequest(
2112
- `${pathFn(serviceTypeId, planId)}?per_page=100`,
1954
+ `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items?per_page=100`,
2113
1955
  auth
2114
1956
  );
2115
1957
  if (!response?.data) return [];
2116
1958
  return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
2117
1959
  }
2118
- supportsDeviceFlow() {
2119
- return false;
2120
- }
2121
1960
  };
2122
1961
 
2123
1962
  // src/providers/bibleProject/data.json
@@ -4512,8 +4351,9 @@ var data_default = {
4512
4351
  };
4513
4352
 
4514
4353
  // src/providers/bibleProject/BibleProjectProvider.ts
4515
- var BibleProjectProvider = class {
4354
+ var BibleProjectProvider = class extends BaseProvider {
4516
4355
  constructor() {
4356
+ super(...arguments);
4517
4357
  this.id = "bibleproject";
4518
4358
  this.name = "The Bible Project";
4519
4359
  this.logos = {
@@ -4526,10 +4366,8 @@ var BibleProjectProvider = class {
4526
4366
  apiBase: "https://bibleproject.com",
4527
4367
  oauthBase: "",
4528
4368
  clientId: "",
4529
- scopes: [],
4530
- endpoints: { downloads: "/downloads/" }
4369
+ scopes: []
4531
4370
  };
4532
- this.data = data_default;
4533
4371
  this.requiresAuth = false;
4534
4372
  this.authTypes = ["none"];
4535
4373
  this.capabilities = {
@@ -4538,133 +4376,71 @@ var BibleProjectProvider = class {
4538
4376
  instructions: true,
4539
4377
  mediaLicensing: false
4540
4378
  };
4379
+ this.data = data_default;
4541
4380
  }
4542
4381
  getMuxThumbnail(muxPlaybackId, width = 400, height = 225) {
4543
4382
  return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=${width}&height=${height}`;
4544
4383
  }
4545
- async browse(path, _auth) {
4546
- const { segments, depth } = parsePath(path);
4547
- if (depth === 0) {
4548
- return this.getCollections();
4549
- }
4550
- if (depth === 1) {
4551
- const collectionSlug = segments[0];
4552
- return this.getLessonFolders(collectionSlug, path);
4553
- }
4554
- if (depth === 2) {
4555
- const collectionSlug = segments[0];
4556
- const videoId = segments[1];
4557
- return this.getVideoFile(collectionSlug, videoId);
4558
- }
4559
- return [];
4560
- }
4561
- getCollections() {
4562
- return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4563
- type: "folder",
4564
- id: slugify(collection.name),
4565
- title: collection.name,
4566
- thumbnail: collection.image || void 0,
4567
- path: `/${slugify(collection.name)}`
4568
- }));
4569
- }
4570
- getLessonFolders(collectionSlug, currentPath) {
4571
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4572
- if (!collection) return [];
4573
- return collection.videos.map((video) => ({
4574
- type: "folder",
4575
- id: video.id,
4576
- title: video.title,
4384
+ videoToFile(video) {
4385
+ return createFile(video.id, video.title, video.videoUrl, {
4386
+ mediaType: "video",
4577
4387
  thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4578
- isLeaf: true,
4579
- path: `${currentPath}/${video.id}`
4580
- }));
4581
- }
4582
- getVideoFile(collectionSlug, videoId) {
4583
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4584
- if (!collection) return [];
4585
- const video = collection.videos.find((v) => v.id === videoId);
4586
- if (!video) return [];
4587
- return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId, seconds: 0 })];
4388
+ muxPlaybackId: video.muxPlaybackId,
4389
+ seconds: 0
4390
+ });
4588
4391
  }
4589
- async getPlaylist(path, _auth, _resolution) {
4392
+ /** Resolve a path to its collection and files: all videos at depth 1, a single video at depth 2. */
4393
+ resolve(path) {
4590
4394
  const { segments, depth } = parsePath(path);
4591
- if (depth < 1) return null;
4592
- const collectionSlug = segments[0];
4593
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4395
+ if (depth < 1 || depth > 2) return null;
4396
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4594
4397
  if (!collection) return null;
4595
4398
  if (depth === 1) {
4596
- 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 }));
4597
- return files.length > 0 ? files : null;
4399
+ return { collection, files: collection.videos.map((v) => this.videoToFile(v)), name: collection.name };
4598
4400
  }
4599
- if (depth === 2) {
4600
- const videoId = segments[1];
4601
- const video = collection.videos.find((v) => v.id === videoId);
4602
- if (!video) return null;
4603
- return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, thumbnail: this.getMuxThumbnail(video.muxPlaybackId), muxPlaybackId: video.muxPlaybackId, seconds: 0 }];
4604
- }
4605
- return null;
4401
+ const video = collection.videos.find((v) => v.id === segments[1]);
4402
+ if (!video) return null;
4403
+ return { collection, files: [this.videoToFile(video)], name: video.title };
4606
4404
  }
4607
- async getInstructions(path, _auth) {
4405
+ async browse(path, _auth) {
4608
4406
  const { segments, depth } = parsePath(path);
4609
- if (depth < 1) return null;
4610
- const collectionSlug = segments[0];
4611
- const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
4612
- if (!collection) return null;
4407
+ if (depth === 0) {
4408
+ return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4409
+ type: "folder",
4410
+ id: slugify(collection.name),
4411
+ title: collection.name,
4412
+ thumbnail: collection.image || void 0,
4413
+ path: `/${slugify(collection.name)}`
4414
+ }));
4415
+ }
4613
4416
  if (depth === 1) {
4614
- const actionItems = collection.videos.map((video) => ({
4615
- id: video.id + "-action",
4616
- itemType: "action",
4617
- label: video.title,
4618
- actionType: "play",
4619
- children: [
4620
- {
4621
- id: video.id,
4622
- itemType: "file",
4623
- label: video.title,
4624
- downloadUrl: video.videoUrl,
4625
- thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
4626
- }
4627
- ]
4417
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4418
+ if (!collection) return [];
4419
+ return collection.videos.map((video) => ({
4420
+ type: "folder",
4421
+ id: video.id,
4422
+ title: video.title,
4423
+ thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4424
+ isLeaf: true,
4425
+ path: `${path}/${video.id}`
4628
4426
  }));
4629
- const sectionItem = {
4630
- id: slugify(collection.name) + "-section",
4631
- itemType: "section",
4632
- label: collection.name,
4633
- children: actionItems
4634
- };
4635
- return { name: collection.name, items: [sectionItem] };
4636
4427
  }
4637
4428
  if (depth === 2) {
4638
- const videoId = segments[1];
4639
- const video = collection.videos.find((v) => v.id === videoId);
4640
- if (!video) return null;
4641
- const actionItem = {
4642
- id: video.id + "-action",
4643
- itemType: "action",
4644
- label: video.title,
4645
- actionType: "play",
4646
- children: [
4647
- {
4648
- id: video.id,
4649
- itemType: "file",
4650
- label: video.title,
4651
- downloadUrl: video.videoUrl,
4652
- thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
4653
- }
4654
- ]
4655
- };
4656
- const sectionItem = {
4657
- id: slugify(collection.name) + "-section",
4658
- itemType: "section",
4659
- label: collection.name,
4660
- children: [actionItem]
4661
- };
4662
- return { name: video.title, items: [sectionItem] };
4429
+ return this.resolve(path)?.files ?? [];
4663
4430
  }
4664
- return null;
4431
+ return [];
4665
4432
  }
4666
- supportsDeviceFlow() {
4667
- return false;
4433
+ async getPlaylist(path, _auth, _resolution) {
4434
+ const resolved = this.resolve(path);
4435
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
4436
+ }
4437
+ async getInstructions(path, _auth) {
4438
+ const resolved = this.resolve(path);
4439
+ if (!resolved) return null;
4440
+ return filesToInstructions(resolved.name, resolved.files, {
4441
+ id: slugify(resolved.collection.name) + "-section",
4442
+ label: resolved.collection.name
4443
+ });
4668
4444
  }
4669
4445
  };
4670
4446
 
@@ -12288,8 +12064,9 @@ function buildLessonInstructions(lesson) {
12288
12064
  }
12289
12065
 
12290
12066
  // src/providers/highVoltage/HighVoltageKidsProvider.ts
12291
- var HighVoltageKidsProvider = class {
12067
+ var HighVoltageKidsProvider = class extends BaseProvider {
12292
12068
  constructor() {
12069
+ super(...arguments);
12293
12070
  this.id = "highvoltagekids";
12294
12071
  this.name = "High Voltage Kids";
12295
12072
  this.logos = {
@@ -12302,8 +12079,7 @@ var HighVoltageKidsProvider = class {
12302
12079
  apiBase: "https://highvoltagekids.com",
12303
12080
  oauthBase: "",
12304
12081
  clientId: "",
12305
- scopes: [],
12306
- endpoints: { downloads: "/membership-downloads/" }
12082
+ scopes: []
12307
12083
  };
12308
12084
  this.data = data_default2;
12309
12085
  this.requiresAuth = false;
@@ -12353,9 +12129,6 @@ var HighVoltageKidsProvider = class {
12353
12129
  }
12354
12130
  return null;
12355
12131
  }
12356
- supportsDeviceFlow() {
12357
- return false;
12358
- }
12359
12132
  };
12360
12133
 
12361
12134
  // src/providers/jesusFilm/JesusFilmProvider.ts
@@ -12367,8 +12140,14 @@ var CATEGORY_MAP = {
12367
12140
  "series": "series",
12368
12141
  "collections": "collection"
12369
12142
  };
12370
- var JesusFilmProvider = class {
12143
+ var CATEGORY_NAMES = {
12144
+ "feature-films": "Feature Films",
12145
+ "series": "Series",
12146
+ "collections": "Collections"
12147
+ };
12148
+ var JesusFilmProvider = class extends BaseProvider {
12371
12149
  constructor() {
12150
+ super(...arguments);
12372
12151
  this.id = "jesusfilm";
12373
12152
  this.name = "Jesus Film Project";
12374
12153
  this.logos = {
@@ -12381,8 +12160,7 @@ var JesusFilmProvider = class {
12381
12160
  apiBase: API_BASE4,
12382
12161
  oauthBase: "",
12383
12162
  clientId: "",
12384
- scopes: [],
12385
- endpoints: { watch: "/watch" }
12163
+ scopes: []
12386
12164
  };
12387
12165
  this.requiresAuth = false;
12388
12166
  this.authTypes = ["none"];
@@ -12393,6 +12171,7 @@ var JesusFilmProvider = class {
12393
12171
  mediaLicensing: false
12394
12172
  };
12395
12173
  }
12174
+ // Not ApiHelper: Arclight needs the apiKey query param and callers rely on throw-on-error.
12396
12175
  async fetchApi(path) {
12397
12176
  const separator = path.includes("?") ? "&" : "?";
12398
12177
  const url = `${API_BASE4}${path}${separator}apiKey=${API_KEY}`;
@@ -12400,6 +12179,12 @@ var JesusFilmProvider = class {
12400
12179
  if (!response.ok) throw new Error(`Arclight API error: ${response.status}`);
12401
12180
  return response.json();
12402
12181
  }
12182
+ async fetchCategoryComponents(subType) {
12183
+ const data = await this.fetchApi(
12184
+ `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12185
+ );
12186
+ return data._embedded.mediaComponents;
12187
+ }
12403
12188
  async browse(path, _auth) {
12404
12189
  const { segments, depth } = parsePath(path);
12405
12190
  if (depth === 0) return this.getCategories();
@@ -12422,19 +12207,14 @@ var JesusFilmProvider = class {
12422
12207
  async getItemsInCategory(category) {
12423
12208
  const subType = CATEGORY_MAP[category];
12424
12209
  if (!subType) return [];
12425
- const data = await this.fetchApi(
12426
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12427
- );
12428
- return data._embedded.mediaComponents.map((item) => {
12429
- const isLeaf = item.containsCount === 0;
12430
- return createFolder(
12431
- item.mediaComponentId,
12432
- item.title,
12433
- `/${category}/${item.mediaComponentId}`,
12434
- item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12435
- isLeaf
12436
- );
12437
- });
12210
+ const components = await this.fetchCategoryComponents(subType);
12211
+ return components.map((item) => createFolder(
12212
+ item.mediaComponentId,
12213
+ item.title,
12214
+ `/${category}/${item.mediaComponentId}`,
12215
+ item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12216
+ item.containsCount === 0
12217
+ ));
12438
12218
  }
12439
12219
  async getVideoFile(mediaComponentId) {
12440
12220
  const variant = await this.fetchApi(
@@ -12449,11 +12229,10 @@ var JesusFilmProvider = class {
12449
12229
  const title = component?.title || mediaComponentId;
12450
12230
  const seconds = component ? Math.round(component.lengthInMilliseconds / 1e3) : 0;
12451
12231
  const thumbnail = component?.imageUrls.mobileCinematicHigh || component?.imageUrls.videoStill;
12452
- const muxPlaybackId = this.extractMuxPlaybackId(downloadUrl);
12453
12232
  return [
12454
12233
  createFile(mediaComponentId, title, downloadUrl, {
12455
12234
  mediaType: "video",
12456
- muxPlaybackId,
12235
+ muxPlaybackId: this.extractMuxPlaybackId(downloadUrl),
12457
12236
  seconds,
12458
12237
  thumbnail
12459
12238
  })
@@ -12465,9 +12244,8 @@ var JesusFilmProvider = class {
12465
12244
  );
12466
12245
  const childIds = linksData.linkedMediaComponentIds?.contains;
12467
12246
  if (!childIds || childIds.length === 0) return [];
12468
- const idsParam = childIds.join(",");
12469
12247
  const childrenData = await this.fetchApi(
12470
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12248
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12471
12249
  );
12472
12250
  if (!childrenData._embedded?.mediaComponents) return [];
12473
12251
  return childrenData._embedded.mediaComponents.map((item) => createFolder(
@@ -12482,36 +12260,6 @@ var JesusFilmProvider = class {
12482
12260
  const match = url.match(/stream\.mux\.com\/([^/]+)/);
12483
12261
  return match ? match[1] : void 0;
12484
12262
  }
12485
- async getPlaylist(path, _auth, _resolution) {
12486
- const { segments, depth } = parsePath(path);
12487
- if (depth < 1) return null;
12488
- const category = segments[0];
12489
- if (depth >= 3) {
12490
- const lastId = segments[depth - 1];
12491
- const items = await this.getVideoFile(lastId);
12492
- const files2 = items.filter((item) => item.type === "file");
12493
- if (files2.length > 0) return files2;
12494
- const containerFiles = await this.fetchContainerVideoFiles(lastId);
12495
- return containerFiles.length > 0 ? containerFiles : null;
12496
- }
12497
- if (depth === 2) {
12498
- if (category === "feature-films") {
12499
- const items = await this.getVideoFile(segments[1]);
12500
- if (items.length === 0) return null;
12501
- return items.filter((item) => item.type === "file");
12502
- }
12503
- const files2 = await this.fetchContainerVideoFiles(segments[1]);
12504
- return files2.length > 0 ? files2 : null;
12505
- }
12506
- const subType = CATEGORY_MAP[category];
12507
- if (!subType) return null;
12508
- const data = await this.fetchApi(
12509
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12510
- );
12511
- const contentComponents = data._embedded.mediaComponents.filter((item) => item.containsCount === 0);
12512
- const files = await this.fetchVideoFiles(contentComponents);
12513
- return files.length > 0 ? files : null;
12514
- }
12515
12263
  async fetchVideoFiles(components) {
12516
12264
  const files = [];
12517
12265
  for (const component of components) {
@@ -12542,9 +12290,8 @@ var JesusFilmProvider = class {
12542
12290
  );
12543
12291
  const childIds = linksData.linkedMediaComponentIds?.contains;
12544
12292
  if (!childIds || childIds.length === 0) return [];
12545
- const idsParam = childIds.join(",");
12546
12293
  const childrenData = await this.fetchApi(
12547
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12294
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12548
12295
  );
12549
12296
  if (!childrenData._embedded?.mediaComponents) return [];
12550
12297
  const contentItems = childrenData._embedded.mediaComponents.filter((c) => c.containsCount === 0);
@@ -12556,64 +12303,47 @@ var JesusFilmProvider = class {
12556
12303
  }
12557
12304
  return files;
12558
12305
  }
12306
+ async getPlaylist(path, _auth, _resolution) {
12307
+ const { segments, depth } = parsePath(path);
12308
+ if (depth < 1) return null;
12309
+ const category = segments[0];
12310
+ if (depth >= 3) {
12311
+ const lastId = segments[depth - 1];
12312
+ const items = await this.getVideoFile(lastId);
12313
+ const files2 = items.filter((item) => item.type === "file");
12314
+ if (files2.length > 0) return files2;
12315
+ const containerFiles = await this.fetchContainerVideoFiles(lastId);
12316
+ return containerFiles.length > 0 ? containerFiles : null;
12317
+ }
12318
+ if (depth === 2) {
12319
+ if (category === "feature-films") {
12320
+ const items = await this.getVideoFile(segments[1]);
12321
+ if (items.length === 0) return null;
12322
+ return items.filter((item) => item.type === "file");
12323
+ }
12324
+ const files2 = await this.fetchContainerVideoFiles(segments[1]);
12325
+ return files2.length > 0 ? files2 : null;
12326
+ }
12327
+ const subType = CATEGORY_MAP[category];
12328
+ if (!subType) return null;
12329
+ const components = await this.fetchCategoryComponents(subType);
12330
+ const files = await this.fetchVideoFiles(components.filter((item) => item.containsCount === 0));
12331
+ return files.length > 0 ? files : null;
12332
+ }
12559
12333
  async buildVideoInstructions(mediaComponentId, category) {
12560
12334
  const items = await this.getVideoFile(mediaComponentId);
12561
- if (items.length === 0) return null;
12562
- const file = items[0];
12563
- if (file.type !== "file") return null;
12564
- const actionItem = {
12565
- id: mediaComponentId + "-action",
12566
- itemType: "action",
12567
- label: file.title,
12568
- actionType: "play",
12569
- children: [
12570
- {
12571
- id: mediaComponentId,
12572
- itemType: "file",
12573
- label: file.title,
12574
- downloadUrl: file.url,
12575
- thumbnail: file.thumbnail
12576
- }
12577
- ]
12578
- };
12579
- const sectionItem = {
12580
- id: category + "-section",
12581
- itemType: "section",
12582
- label: file.title,
12583
- children: [actionItem]
12584
- };
12585
- return { name: file.title, items: [sectionItem] };
12335
+ const file = items.find((item) => item.type === "file");
12336
+ if (!file) return null;
12337
+ return filesToInstructions(file.title, [file], { id: category + "-section", label: file.title });
12586
12338
  }
12587
- async buildContainerInstructions(containerId, category) {
12339
+ async buildContainerInstructions(containerId) {
12588
12340
  const containerData = await this.fetchApi(
12589
12341
  `/media-components?ids=${containerId}&languageIds=${LANGUAGE_ID}`
12590
12342
  );
12591
- const container = containerData._embedded?.mediaComponents?.[0];
12592
- const containerName = container?.title || containerId;
12343
+ const containerName = containerData._embedded?.mediaComponents?.[0]?.title || containerId;
12593
12344
  const videoFiles = await this.fetchContainerVideoFiles(containerId);
12594
12345
  if (videoFiles.length === 0) return { name: containerName, items: [] };
12595
- const actionItems = videoFiles.map((file) => ({
12596
- id: file.id + "-action",
12597
- itemType: "action",
12598
- label: file.title,
12599
- actionType: "play",
12600
- children: [
12601
- {
12602
- id: file.id,
12603
- itemType: "file",
12604
- label: file.title,
12605
- downloadUrl: file.url,
12606
- thumbnail: file.thumbnail
12607
- }
12608
- ]
12609
- }));
12610
- const sectionItem = {
12611
- id: containerId + "-section",
12612
- itemType: "section",
12613
- label: containerName,
12614
- children: actionItems
12615
- };
12616
- return { name: containerName, items: [sectionItem] };
12346
+ return filesToInstructions(containerName, videoFiles, { id: containerId + "-section", label: containerName });
12617
12347
  }
12618
12348
  async getInstructions(path, _auth) {
12619
12349
  const { segments, depth } = parsePath(path);
@@ -12622,61 +12352,19 @@ var JesusFilmProvider = class {
12622
12352
  const subType = CATEGORY_MAP[category];
12623
12353
  if (!subType) return null;
12624
12354
  if (depth === 1) {
12625
- const data = await this.fetchApi(
12626
- `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12627
- );
12628
- const categoryName = category === "feature-films" ? "Feature Films" : category === "series" ? "Series" : "Collections";
12629
- const actionItems = await Promise.all(
12630
- data._embedded.mediaComponents.map(async (component) => {
12631
- let downloadUrl;
12632
- if (component.containsCount === 0) {
12633
- try {
12634
- const variant = await this.fetchApi(
12635
- `/media-components/${component.mediaComponentId}/languages/${LANGUAGE_ID}?platform=web`
12636
- );
12637
- downloadUrl = variant.downloadUrls.high?.url || variant.downloadUrls.low?.url;
12638
- } catch {
12639
- }
12640
- }
12641
- return {
12642
- id: component.mediaComponentId + "-action",
12643
- itemType: "action",
12644
- label: component.title,
12645
- actionType: "play",
12646
- children: downloadUrl ? [
12647
- {
12648
- id: component.mediaComponentId,
12649
- itemType: "file",
12650
- label: component.title,
12651
- downloadUrl,
12652
- thumbnail: component.imageUrls.mobileCinematicHigh || component.imageUrls.videoStill
12653
- }
12654
- ] : []
12655
- };
12656
- })
12657
- );
12658
- const sectionItem = {
12659
- id: category + "-section",
12660
- itemType: "section",
12661
- label: categoryName,
12662
- children: actionItems.filter((a) => a.children && a.children.length > 0)
12663
- };
12664
- return { name: categoryName, items: [sectionItem] };
12355
+ const categoryName = CATEGORY_NAMES[category];
12356
+ const components = await this.fetchCategoryComponents(subType);
12357
+ const files = await this.fetchVideoFiles(components.filter((c) => c.containsCount === 0));
12358
+ return filesToInstructions(categoryName, files, { id: category + "-section", label: categoryName });
12665
12359
  }
12666
12360
  if (depth === 2) {
12667
12361
  if (category === "feature-films") return this.buildVideoInstructions(segments[1], category);
12668
- return this.buildContainerInstructions(segments[1], category);
12669
- }
12670
- if (depth >= 3) {
12671
- const lastId = segments[depth - 1];
12672
- const videoResult = await this.buildVideoInstructions(lastId, category);
12673
- if (videoResult) return videoResult;
12674
- return this.buildContainerInstructions(lastId, category);
12362
+ return this.buildContainerInstructions(segments[1]);
12675
12363
  }
12676
- return null;
12677
- }
12678
- supportsDeviceFlow() {
12679
- return false;
12364
+ const lastId = segments[depth - 1];
12365
+ const videoResult = await this.buildVideoInstructions(lastId, category);
12366
+ if (videoResult) return videoResult;
12367
+ return this.buildContainerInstructions(lastId);
12680
12368
  }
12681
12369
  };
12682
12370
 
@@ -12720,35 +12408,15 @@ function convertPlaylistToFiles(playlist) {
12720
12408
  return file;
12721
12409
  });
12722
12410
  }
12723
- function convertPlaylistToInstructions(playlist) {
12724
- const files = convertPlaylistToFiles(playlist);
12725
- const items = files.map((file) => ({
12726
- id: file.id + "-action",
12727
- itemType: "action",
12728
- label: file.title,
12729
- actionType: "play",
12730
- children: [
12731
- {
12732
- id: file.id,
12733
- itemType: "file",
12734
- label: file.title,
12735
- seconds: file.seconds,
12736
- downloadUrl: file.url,
12737
- thumbnail: file.thumbnail
12738
- }
12739
- ]
12740
- }));
12741
- return { name: playlist.lesson_title || "Lesson", items };
12742
- }
12743
12411
 
12744
12412
  // src/providers/cbn/logo.ts
12745
12413
  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+QxiPBkqkEBJBjGckkKZtxnEcJLVIGVqM4jZ3FSDeDw/Nx+CFAwBsjeCQUymZnQIEPEkg5jMilAe4gaDmMz5kCCiFIOYS2qJ0CDLGcRcjoX2EBmpDGIsWy2AiGMMYiqN0q2AbGUQUxlpxUOkxzCIoXS3BCK+ZhAzqWTNQ/RnEDM5ZBEkujODmMgtYTV1GMREvrIMco5BDCTOsoeITWUQ/elhHcSz+Tv5CaS8DQ9xkEG0Z6YdkA4MojsvCVuZwCB6k9LaHshlBtGbZ+x5eDViLt+A7AzYBOnOIFrTX9hNHwbRmLq2PTwafJJPQDLL2Ad5hkH88Yz+MIsYxE8/WEI8ziC6UirWCYg3V/X8ABJR0JGHeIxB9KSrMw+xkUGcp8I0dFpH20iHIC8ziMMsHtRRtMH+2P5Xhx6iOIM4SaM239/5CryD/TnY36mHyGAQ24k52eNeG1VPdOHdt4Xz1GQQWynXsOKDgaGxn2CvedKFh/iCQawn9dj0R08Xf2Kv2lnGDUgXBrH6ZDF6WCEr96c1e7rxEIMYxEqCc9/IvuPEAPSVi1x5iJsMEjJR1zpNylFtZdF14G6688ClGeRByr85IvfwkEbYq+u69BCHGESVgTdlwxQKP4u9flzALcgRBkFTusoyaZ0VQW94d7Z06+HJk2FYgOyo2gGpskLbsPeUTBPuE8EgudNn5AG0wpKuYe+q1pvAQ9RmkJwNVXWnKNpqA+g2LPFHKTzEYgbJ3jSyRnldDuA9SM1IPLxY0sG3IMG2w0J0vCo8RtF4iDgGeZB180aEqiyFxxUiD/Eag9zNhJ9+CV1XCo+PoqlA5jIIbP/jSyv1qfCoFKDyEMn5HuS1z62N2JmBe5RPEgxC9MSx8rrFikoahz/SFxIMQpHU1Yct//QXwi+2zR8XDEKQuF0Z1qup5YfocTJ3C+0gmdvzOsj2ugfs1FKR0uiR2l0X+kGSC8bkaZC4E/b6vQvjy+W3q0/rISpJQYROEY9BEut0szvTDG9fivmA2ENUlhVzUoiyiXkTpMKuEnarqP4WHLcbtYcoLyunzp0/PJWY90AiGi60X0MFG+EeT5F7iHWygsYLnSJegXReWdhBBU3Bf7zje9B7iKKykqrf+1OP+LwE8vMbxZzUTwP83zK+lQYPId1k/cf7f2sVkVdAopIdjn9uFW/YQ96F+3C0cL1gngCJGbnEYe2cwz2CFbV4FJIWdkLrWg+GQQoMiXVaO4p/yGA9LR5imbS0Yf/5+3fhDrKuXoIIIw/xgbS4LL+O08IaZFwDN6M6PfAQm6TlFc/yigvhC9LL1WOb4noO83V5iNnS8rKOqI8cGp4gUQ1/cVUzhxUeNbR5iKrSArO1LgQqhyFIVMNX3FWM6rFYo4d8Iml8jjuxdeEG4ppDFPfIQ/yfrMQJOV40omhYgbjnUDZ2z9PpIfbLityWaz5EzTACGd3Rda3UV3hU0eohUmVl5p5G2j8+XEBOlXVfKfXbeeaxF/C2xWxpFh4gcyjaX7sr9gp+Rq+HeEpa6quSV04LA5D2sxMoPDLxEmpp9hC7pMVKm82G+h0kcWMsRZUo+s/1e4g90nI/kw4Ui/M3SMPJJDUywlMPeQcuyIdkpK31McjP39NUiGK8DzTR75EuvckqgLz6m3i/gtQ8nkBTIYWW4oVc0e8hZkpLRnexquFTkBW/EtVH0lt4Ic8b8BDzpEWfRV//hx9BWjSgqo5AJbyULtEmQN6Tlo1Plksq7TuQiH3FqGojejVeTLIRj6bSSwgoBkde7+czkDmf0VXHX/BiHguY8BCfSguvrVrAcauvQCKmpdPVxvuKjscZRjzESWnpG5TvueAjkE8Ivx7iCbyc15PMeCDb4Y5SvqnYJ74B+Zby//YNvJy4QmY8REV5+SGGlJ1p7A+QtaTjONfgAxr2FzHkIXpJyy8X6mf5OV+AtCWdR6boIFxc2JRHWpT8Bi/kG9t6DxJ1KZKyKo5MRUtq192UB7aM48SQb2xdzWuQqUdJa6JwAbSkmDPGPIohu0Vb+IZO9Bjki9akNVEIf9xNLG7MQxyXf4SBVt7b1lOQLrRPBQF8AnpwjTmPBGTdwO+svNndj5ZLkFHENVEHL2q+OQ9kyCJEjbD07uc8A0m8TVwRo/CyLhn0KIZcx65a/H6t8whkKvUsS8U6uXsMeojlyIf43OqNuzcgmX2J6+GDFLSsY9EGPYogSy3GNCVoGtUHUiqNuB4K4yN+3koy+QVp47rbeFZt8yClqRsxig3Eu71mmfT4LAr5GB2sH6OKcRByD6S5+14fRAeTHglYV/5cO/9diw2DzCH3GIJ3tNww6YF0pd+Jra0W/m4WpDl5I19/fNb3bKMeHbCmzThbh4lcZxJkyxLqaiiMjzP7m1GPBHQtLpszrz81CFLuAHU1BF7He9CjjYJ0xT5HBbtN2nHGQIJ/Ja8GvAt9TqxRj5noL+cCu4daYAzkOHk1PI2W1WiZUY8S6PS0bbaPFVnBEMhl8mqYjK65FGxg1EO8i571IfsHe9kMSOmm1LUQja8xWsWsx3L0g1RycmEsaQIkhf4hrSpa2GmzHgvRC0iEo2a730yAPEdeDd3QMSYtShj1mNwePeuzjg44IkI/yA/k1ZCE7tHRuKNRjyR89kNJh/uFrtYOkkL+RCg2o4XVMPuD9RF+2gMcHnKKdhD6ea8N0LLeNevRFT/rMY4PWkAzSKl06mqIRW/8i7Y06vEVftbVnI+rqaUZhH7hz+roE0hZox69Feu5T3R+2DN6QU7R32FhfUGwz+wN1gT8rMe5OfAcrSDkA9US0FFxcQGTHorRedCosKYrk3uQ0eaejFP7mvQIqMYavuPq0B11gpB/QX6vZu5uThXVGBG3833b6wMpT14RXbCiliaY9FD9rIx12/pfRx/I0+R9D2jLkdFHdFWjbIrrDUkWaAMpGkldE2gv4U8mPRqolsT43PXhM7SBjKKuiYpYSQWaGvToqNospw5BAWM1gQQnE9dEAG1U/NSgR2vFAwgMpFgG4aQmkDHUVdEJK2mDQY8Sqm7WdiRjZY9rAhlG/QXB2t0ijhh8ADmlOOOI/iRlFNQDEvGiqS9IdYNfEOXCfG/SlJEeoQWkkqkvSG2DjbwjVWdMNiFlvxaQTqZu/v9tzuM51QmPI2tM66UFhHp4FNaaV9LcRJCKqh2LdtD9RL+vA6QocWUc9r7btniK4nw7E65QUEMHyNfEtTEXKWeCsVb37p0Vp5tK2Tt2SAfIy7S10RMrZ4gpj5bKB+jzlEUd0QEy2MztTedihjwCP6jOlna8ZIYGkHK0UwLSsZ+LJ0x9QcarzvYCcWHx9CCv037C89hP9yRDHsopmaepJ6T0oQepS/sJx2jqnbOac6pzHU5+4x1HD0L7ozoLa0zobajFXbXRcyn6Jesq04PQbmyK3ZgPNOORodozSscSaRpAaLtUsSbWg0Y8Eq4qTjSxoIYSk+lBSJdUzECaLBqbmXxQXdULt0mEBwjp8/MwTwdXD1Odp54Z8S+Rg8QY6YSYbsLjBVUL1kY9ZdJ/Q0ibFhOQUQUpJuY/T1qseUiDnbt85yAVKD8e1qX5pAGP6LmKs5yrq2GT/i4rjvLjfadtCFToVFWc5Bxta5gP9zfIx0ghBtasbhCFn2PJ1tqK3eFrkEhk49jS+j1GKDZSK6exlaAaOcgOwk/3P0gZ+nddi3zP9API/STRN7/3Ifx42ODjBdpB/qV/yI80PelBqhF+vFpeXUJmKsZHtdFZ8BR6ECCcroHsvZap2yO2BX56lbX25B/UAEJ4B4IMkOulG6QufnYtMrSWvFkDyEKyTxdA7jx172zbStEwpHkwcWUNIPW0X+GG6a2UIu3wG6y/av5fmKoBZBTZp8NGyB3QWymrTZybPB10jH7/k+zjYb2Feoc3vIOfmvaL10QdIC3IPh6yH0Vjr36wmmvvFftDBwiQNbz9LzK0QGuddMFbTOrr9hBFtYA8pblu3tJZJYvw8xqg3eMX0AIyTfNz4WiNVZK0Cj2tK/pbNJ/QA/KF5pvylzRWySC8C8TAUOKlekCCVNe+4cYbk86gbVgpBla46Q56QMhGICDDKjXO9LwGXk4OWqkLZGi4gpxHT8lIJ/4EXSCJZcITZD069LyAiem+a0AXCNW2jsg+U5vNX9H7G/AQV/WBLNV6l9VFU4XsRifLNDHh8QLoA3GyPYAkG+QH/1hTjaBLNewwMv36XZ0gNKvPJBt9Ukd33IwysvF03yidIDQTP5GOu516aqQ8di5mFlQZA1pBSG4T/yE/9nazre7PGtlH6QboBQGK2fQnkGPr2MozYSd2Jkb27YmO0w1CsSwptq6fjoGDzbATecnID9Zy0A1C0ViNrUR6m75C0BWgYn434bGknH6Q9u7Hy8xCDv0P+hpB19YzM5exLegHoZjS0shU13YxbEuudUZ2qtwFJkAIrobInWgB8hpB1xD83oRH71QzIItdr+v1vKFhJ+gVpKEJj9gdYAbE/U8Ltjn2VkO3WCmtTYCcBlMg8KrLj4qNBf+WuEqw9bBqmfCYBuZA4l2udbAeWTYgjrZKeiAfv10hAx7nwSAIlHQ5kwPZBCiKdLUIdA3B4wY8jsYbBYGl7tbKx9YOm0hZJ/WRQlYZWNOxWyKYBYFjrj7vfDDQJXIBKaSZgRveRmAaxF1vWxp2+0O4SUUsMvG1QqR2j4IxYB4Eqrr5yNg2FYQ7+MxGiqjofw9nIK72jN6seaTRnWzz6guyKRG8AXGzkNHTyCFTycblvKJ5tB+aiUHwCsTFsL8yWDPPCap62Sc//n7NX5DofQDegcAuxx+8l+YR3ZEFNIPLU+Jj8BTE+VKlb2gdRiHEQvnRO+sd+XNkLHgMAu87/OgvYuPRicbAN5EffaVWj2blwHMQGBqg/c1KoRnqIP/FCqZp5Ii9COADELjmrKnuU+x4XUkeBeTHPq3Ro2xz8AcIjO3pqPMI27G3fTpB7fwoP3YrbRzpg4LgFxCo+aWTU0DHpFNsdSV/Kmyvbb/jsjsB/AMCQSc9VmnYZb2P+1l/k+QDas/qutltA+ArEIDLDsZlfqSrQ1KI/5YfuJsej//aAr4DgQr2nx/QiROdXY+6/drQqJZ7pzEcwIcgUM7+Wj7omkWX3FZSc/oeA+yH93IU+BMEoM56myfzDXaklCXuammv/LAL6S8eg1IAfAsCLWbaPJ9K2JGS3dXTVulBt1C3K8Y+URvAzyAQccnefWU39EiLXNWU/I6aeLz7+iqdAXwOAnDL3sII6CYOzV01AsovTvMpOWZtrA0QBiCQ+oydL0lhtDXO1XwzeRfqEjqOydXLAYQHCMA2O1+SrthRoqa4uPORHvFZMo7BX8cDhA8IRKy0/qg9Axvt4GYKoHzphItEPV9r2oL2COLjtbB+TV6I3sY7n4cyj3xMxqOu51dbAIQfCEDDvS57ksDFgDb5tOtv3HPU/7YfQHiCQL8qFjuuYtG13lKcroUo3XQ4ar1LjaRm5cFUhI6DlrK4OHE3dHGxsQ5XH5L2FrpcYrPjP2sDhDcIwJi+Ln7y72aDo5mAkVJhN6uClKhxC4xGEwhE1LU0v2ADegBHzYGPSw/leDJQ5NGGjQHyBsidS0lXC3evGc8CZe9hb+mRljvj2D0tE8xHaDx2yTdDX93rozcvQQejrxtIj+RkGHfg9tUogDwGAlC0Wchm1lboecfbX755uvRA9tdimlR1C3gUofn4FaaHIvkOfe922/MZ5YMiO9htHxnfGCCvglggGYm+NcauyBDpYezNgX8hGbyMMFBGhXrqZuA6ZCJyEDuN0MUrAeR5EIBVnWYoKiH6MVzEXt+rfKV16+8/4DWHKZA7d1xvK569A7hIoq0tH6u6AhmcDJBvQACq/W0yLoLvSRSc6BYkaO29v9eNgHwFcufpvQu61VQ0vukK/GbiG9L0x3IA+Q3kTl67jVxhI6vjbxpquZddvqzejNBvHNYHIF+CAGT+q4i8Tm7i74mzOrdDvmxYyNmkBYcD5FsQgNQu8lun6fjj2FqLK6fJF2W6rn5TyzZRkK9B7qT0QdkOMS/gjXnBqpaa429L36xe3/bltQD5HgSg8YobuR/gH38Lf8NVKz3DZaVvVW0N/kplAAa5nwI/7s51+6vYyXeqhS3X5Tu74vOFk1bGA4NkSeW/51xypmJN/NUrQu6E1VT6vuexl3+5CoBBcjwuXlyUvdMk7XX8xe1Drh4j7V35EBnXUxeAQSRptOdQ1it2wiXFE/OxEOvZSddbbyztJ+uRCQyCZcI/s05n6KhYxr7fPGUvpHyu3AeSvuM6AAyivMQ3ufGfqg5UVXxJxh5VgPwmfUvu+e+t2gODhEztFQserkTQ95ridZXOoCADpG84lXN+x3gABrGU+LYHH9wL1yuJvyrq+da27nsjsrfWlF0FDGIjLa6cu9v6VOas4ncrfs8IuYj8rnlI1medWkFgEJsJDt/4/Qxx/U9Vi9ie3TIQ+YK9pR61CrwSB8Agjn69ho+s+NUpxQsiLve22on7aOLi7BRgEDc/XzWVfx7XKtpSa9bDDdtbvgvAIFqzuGr2juEE+UjIqGX3tAoAg+i/3vRak/VZEemdPytEZNcgMIiRrN38aLRoPflLajad1TYsziVPgNx90B9U/P6dVBnkqn0hExjEbLaMf/rug/6K8D6LPARy9064/LTDXzEIh0EYhMMgDMJhEA6DMAiHQRiEwyAMwmEQBuEwCIdBGITDIAzCYRAG4TAIg3AYhMMgDMJhEAbhMAiDcBiEQTgMwmEQBuEwCINwGIRBOAzCIBwG4TAIg3AYhEE4DMIgHAZhEA6DcOD/BRgAOvPhCyyiro8AAAAASUVORK5CYIIA";
12746
12414
 
12747
12415
  // src/providers/cbn/CbnProvider.ts
12748
12416
  var API_BASE5 = "https://sbamemberdev.wpenginepowered.com/wp-json/superbook/v1";
12749
- var CbnProvider = class {
12417
+ var CbnProvider = class extends BaseProvider {
12750
12418
  constructor() {
12751
- this.apiHelper = new ApiHelper();
12419
+ super(...arguments);
12752
12420
  this.deviceFlowHelper = new DeviceFlowHelper();
12753
12421
  this.id = "cbn";
12754
12422
  this.name = "CBN";
@@ -12761,66 +12429,48 @@ var CbnProvider = class {
12761
12429
  clientId: "freeplay",
12762
12430
  scopes: [],
12763
12431
  supportsDeviceFlow: true,
12764
- deviceAuthEndpoint: "/device",
12765
- endpoints: {
12766
- catalog: "/catalog",
12767
- courseDetail: (courseId) => `/catalog/${courseId}`,
12768
- lessonPlaylist: (lessonId) => `/lesson-playlist/${lessonId}`
12769
- }
12432
+ deviceAuthEndpoint: "/device"
12770
12433
  };
12771
12434
  this.requiresAuth = true;
12772
12435
  this.authTypes = ["device_flow"];
12773
12436
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
12774
12437
  }
12775
- async apiRequest(path, auth) {
12776
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
12777
- }
12778
12438
  async browse(path, auth) {
12779
12439
  const { segments, depth } = parsePath(path);
12780
12440
  if (depth === 0) {
12781
12441
  return [{ type: "folder", id: "catalog-root", title: "Catalog", path: "/catalog" }];
12782
12442
  }
12783
12443
  if (segments[0] !== "catalog") return [];
12784
- if (depth === 1) return this.getCourses(auth);
12785
- if (depth === 2) return this.getLessons(segments[1], path, auth);
12786
- if (depth === 3) return this.getVideos(segments[2], auth);
12444
+ if (depth === 1) {
12445
+ const response = await this.apiRequest("/catalog", auth);
12446
+ return Array.isArray(response) ? convertCoursesToFolders(response) : [];
12447
+ }
12448
+ if (depth === 2) {
12449
+ const response = await this.apiRequest(`/catalog/${segments[1]}`, auth);
12450
+ return response?.lessons ? convertLessonsToFolders(response.lessons, path) : [];
12451
+ }
12452
+ if (depth === 3) {
12453
+ const resolved = await this.resolveFiles(path, auth);
12454
+ return resolved?.files ?? [];
12455
+ }
12787
12456
  return [];
12788
12457
  }
12789
- async getCourses(auth) {
12790
- const response = await this.apiRequest(this.config.endpoints.catalog, auth);
12791
- if (!Array.isArray(response)) return [];
12792
- return convertCoursesToFolders(response);
12793
- }
12794
- async getLessons(courseId, coursePath, auth) {
12795
- const pathFn = this.config.endpoints.courseDetail;
12796
- const response = await this.apiRequest(pathFn(courseId), auth);
12797
- if (!response?.lessons) return [];
12798
- return convertLessonsToFolders(response.lessons, coursePath);
12799
- }
12800
- async getVideos(lessonId, auth) {
12801
- const playlist = await this.fetchPlaylist(lessonId, auth);
12802
- return playlist ? convertPlaylistToFiles(playlist) : [];
12803
- }
12804
- async fetchPlaylist(lessonId, auth) {
12805
- const pathFn = this.config.endpoints.lessonPlaylist;
12806
- return this.apiRequest(pathFn(lessonId), auth);
12807
- }
12808
- async getPlaylist(path, auth, _resolution) {
12458
+ /** Resolve a leaf path (/catalog/{courseId}/{lessonId}) to its playlist files. */
12459
+ async resolveFiles(path, auth) {
12809
12460
  const { segments, depth } = parsePath(path);
12810
12461
  if (depth !== 3 || segments[0] !== "catalog") return null;
12811
- const playlist = await this.fetchPlaylist(segments[2], auth);
12462
+ const playlist = await this.apiRequest(`/lesson-playlist/${segments[2]}`, auth);
12812
12463
  if (!playlist || playlist.playlist.length === 0) return null;
12813
- return convertPlaylistToFiles(playlist);
12464
+ return { files: convertPlaylistToFiles(playlist), lessonTitle: playlist.lesson_title };
12814
12465
  }
12815
- async getInstructions(path, auth) {
12816
- const { segments, depth } = parsePath(path);
12817
- if (depth !== 3 || segments[0] !== "catalog") return null;
12818
- const playlist = await this.fetchPlaylist(segments[2], auth);
12819
- if (!playlist || playlist.playlist.length === 0) return null;
12820
- return convertPlaylistToInstructions(playlist);
12466
+ async getPlaylist(path, auth, _resolution) {
12467
+ const resolved = await this.resolveFiles(path, auth);
12468
+ return resolved?.files ?? null;
12821
12469
  }
12822
- supportsDeviceFlow() {
12823
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
12470
+ async getInstructions(path, auth) {
12471
+ const resolved = await this.resolveFiles(path, auth);
12472
+ if (!resolved) return null;
12473
+ return filesToInstructions(resolved.lessonTitle || "Lesson", resolved.files);
12824
12474
  }
12825
12475
  async initiateDeviceFlow() {
12826
12476
  return this.deviceFlowHelper.initiateDeviceFlow(this.config);
@@ -16451,8 +16101,9 @@ var data_default3 = {
16451
16101
  };
16452
16102
 
16453
16103
  // src/providers/lifeChurch/LifeChurchProvider.ts
16454
- var LifeChurchProvider = class {
16104
+ var LifeChurchProvider = class extends BaseProvider {
16455
16105
  constructor() {
16106
+ super(...arguments);
16456
16107
  this.id = "lifechurch";
16457
16108
  this.name = "Life.Church";
16458
16109
  this.logos = {
@@ -16465,8 +16116,7 @@ var LifeChurchProvider = class {
16465
16116
  apiBase: "https://www.life.church",
16466
16117
  oauthBase: "",
16467
16118
  clientId: "",
16468
- scopes: [],
16469
- endpoints: {}
16119
+ scopes: []
16470
16120
  };
16471
16121
  this.requiresAuth = false;
16472
16122
  this.authTypes = ["none"];
@@ -16481,20 +16131,14 @@ var LifeChurchProvider = class {
16481
16131
  findSeries(seriesId) {
16482
16132
  return this.data.series.find((s) => s.id === seriesId);
16483
16133
  }
16484
- findUnit(series, unitId) {
16485
- return series.units.find((u) => u.id === unitId);
16486
- }
16487
- findLesson(unit, lessonId) {
16488
- return unit.lessons.find((l) => l.id === lessonId);
16489
- }
16490
16134
  findScheduled(scheduledId) {
16491
16135
  return this.data.scheduledLessons?.find((s) => s.id === scheduledId);
16492
16136
  }
16493
16137
  /** Resolve a scheduled-lesson reference to the underlying lesson + unit + series. */
16494
16138
  resolveScheduled(sched) {
16495
16139
  const series = this.findSeries(sched.seriesId);
16496
- const unit = series && this.findUnit(series, sched.unitId);
16497
- const lesson = unit && this.findLesson(unit, sched.lessonId);
16140
+ const unit = series?.units.find((u) => u.id === sched.unitId);
16141
+ const lesson = unit?.lessons.find((l) => l.id === sched.lessonId);
16498
16142
  if (!series || !unit || !lesson) return null;
16499
16143
  return { series, unit, lesson };
16500
16144
  }
@@ -16509,17 +16153,40 @@ var LifeChurchProvider = class {
16509
16153
  providerData: { sourceUrl: lesson.sourceUrl }
16510
16154
  };
16511
16155
  }
16156
+ /** Resolve a leaf-or-unit path to its files plus the name/section used for instructions. */
16157
+ resolve(path) {
16158
+ const { segments, depth } = parsePath(path);
16159
+ if (segments[0] === "scheduled") {
16160
+ if (depth !== 2) return null;
16161
+ const sched = this.findScheduled(segments[1]);
16162
+ const resolved = sched && this.resolveScheduled(sched);
16163
+ if (!sched || !resolved) return null;
16164
+ const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16165
+ return {
16166
+ files: [this.lessonToFile(resolved.lesson, label)],
16167
+ name: label,
16168
+ section: { id: `scheduled-${sched.id}-section`, label: "This Sunday" }
16169
+ };
16170
+ }
16171
+ const series = this.findSeries(segments[0]);
16172
+ const unit = series?.units.find((u) => u.id === segments[1]);
16173
+ if (!unit) return null;
16174
+ if (depth === 2) {
16175
+ return { files: unit.lessons.map((l) => this.lessonToFile(l)), name: unit.name, section: { id: `${unit.id}-section`, label: unit.name } };
16176
+ }
16177
+ if (depth === 3) {
16178
+ const lesson = unit.lessons.find((l) => l.id === segments[2]);
16179
+ if (!lesson) return null;
16180
+ return { files: [this.lessonToFile(lesson)], name: lesson.title, section: { id: `${unit.id}-section`, label: unit.name } };
16181
+ }
16182
+ return null;
16183
+ }
16512
16184
  async browse(path, _auth) {
16513
16185
  const { segments, depth } = parsePath(path);
16514
16186
  if (depth === 0) {
16515
16187
  const items = [];
16516
16188
  if (this.data.scheduledLessons && this.data.scheduledLessons.length > 0) {
16517
- items.push({
16518
- type: "folder",
16519
- id: "scheduled",
16520
- title: "This Sunday",
16521
- path: "/scheduled"
16522
- });
16189
+ items.push({ type: "folder", id: "scheduled", title: "This Sunday", path: "/scheduled" });
16523
16190
  }
16524
16191
  for (const s of this.data.series) {
16525
16192
  items.push({ type: "folder", id: s.id, title: s.name, thumbnail: s.thumbnail, path: `/${s.id}` });
@@ -16540,13 +16207,7 @@ var LifeChurchProvider = class {
16540
16207
  };
16541
16208
  });
16542
16209
  }
16543
- if (depth === 2) {
16544
- const sched = this.findScheduled(segments[1]);
16545
- const resolved = sched && this.resolveScheduled(sched);
16546
- if (!sched || !resolved) return [];
16547
- return [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)];
16548
- }
16549
- return [];
16210
+ return this.resolve(path)?.files ?? [];
16550
16211
  }
16551
16212
  if (depth === 1) {
16552
16213
  const series = this.findSeries(segments[0]);
@@ -16561,8 +16222,8 @@ var LifeChurchProvider = class {
16561
16222
  }
16562
16223
  if (depth === 2) {
16563
16224
  const series = this.findSeries(segments[0]);
16564
- const unit = series && this.findUnit(series, segments[1]);
16565
- if (!series || !unit) return [];
16225
+ const unit = series?.units.find((u) => u.id === segments[1]);
16226
+ if (!unit) return [];
16566
16227
  return unit.lessons.map((l) => ({
16567
16228
  type: "folder",
16568
16229
  id: l.id,
@@ -16573,78 +16234,18 @@ var LifeChurchProvider = class {
16573
16234
  }));
16574
16235
  }
16575
16236
  if (depth === 3) {
16576
- const series = this.findSeries(segments[0]);
16577
- const unit = series && this.findUnit(series, segments[1]);
16578
- const lesson = unit && this.findLesson(unit, segments[2]);
16579
- if (!lesson) return [];
16580
- return [this.lessonToFile(lesson)];
16237
+ return this.resolve(path)?.files ?? [];
16581
16238
  }
16582
16239
  return [];
16583
16240
  }
16584
16241
  async getPlaylist(path, _auth, _resolution) {
16585
- const { segments, depth } = parsePath(path);
16586
- if (segments[0] === "scheduled" && depth === 2) {
16587
- const sched = this.findScheduled(segments[1]);
16588
- const resolved = sched && this.resolveScheduled(sched);
16589
- return resolved ? [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)] : null;
16590
- }
16591
- if (depth === 3) {
16592
- const series = this.findSeries(segments[0]);
16593
- const unit = series && this.findUnit(series, segments[1]);
16594
- const lesson = unit && this.findLesson(unit, segments[2]);
16595
- return lesson ? [this.lessonToFile(lesson)] : null;
16596
- }
16597
- if (depth === 2 && segments[0] !== "scheduled") {
16598
- const series = this.findSeries(segments[0]);
16599
- const unit = series && this.findUnit(series, segments[1]);
16600
- if (!unit) return null;
16601
- const files = unit.lessons.map((l) => this.lessonToFile(l));
16602
- return files.length > 0 ? files : null;
16603
- }
16604
- return null;
16242
+ const resolved = this.resolve(path);
16243
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
16605
16244
  }
16606
16245
  async getInstructions(path, _auth) {
16607
- const { segments, depth } = parsePath(path);
16608
- const buildItem = (lesson, label) => ({
16609
- id: `${lesson.id}-action`,
16610
- itemType: "action",
16611
- label: label ?? lesson.title,
16612
- actionType: "play",
16613
- children: [
16614
- {
16615
- id: lesson.id,
16616
- itemType: "file",
16617
- label: label ?? lesson.title,
16618
- downloadUrl: lesson.videoUrl,
16619
- thumbnail: lesson.thumbnail,
16620
- mediaType: "video"
16621
- }
16622
- ]
16623
- });
16624
- if (segments[0] === "scheduled" && depth === 2) {
16625
- const sched = this.findScheduled(segments[1]);
16626
- const resolved = sched && this.resolveScheduled(sched);
16627
- if (!sched || !resolved) return null;
16628
- const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16629
- return {
16630
- name: label,
16631
- items: [{ id: `scheduled-${sched.id}-section`, itemType: "section", label: "This Sunday", children: [buildItem(resolved.lesson, label)] }]
16632
- };
16633
- }
16634
- if (depth === 3) {
16635
- const series = this.findSeries(segments[0]);
16636
- const unit = series && this.findUnit(series, segments[1]);
16637
- const lesson = unit && this.findLesson(unit, segments[2]);
16638
- if (!series || !unit || !lesson) return null;
16639
- return { name: lesson.title, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: [buildItem(lesson)] }] };
16640
- }
16641
- if (depth === 2 && segments[0] !== "scheduled") {
16642
- const series = this.findSeries(segments[0]);
16643
- const unit = series && this.findUnit(series, segments[1]);
16644
- if (!unit) return null;
16645
- return { name: unit.name, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: unit.lessons.map((l) => buildItem(l)) }] };
16646
- }
16647
- return null;
16246
+ const resolved = this.resolve(path);
16247
+ if (!resolved) return null;
16248
+ return filesToInstructions(resolved.name, resolved.files, resolved.section);
16648
16249
  }
16649
16250
  /**
16650
16251
  * Returns the most recent scheduled lesson from data.scheduledLessons.
@@ -16672,9 +16273,6 @@ var LifeChurchProvider = class {
16672
16273
  ]
16673
16274
  };
16674
16275
  }
16675
- supportsDeviceFlow() {
16676
- return false;
16677
- }
16678
16276
  };
16679
16277
 
16680
16278
  // src/providers/index.ts
@@ -16769,18 +16367,21 @@ function getAvailableProviders(ids) {
16769
16367
  }
16770
16368
 
16771
16369
  // src/index.ts
16772
- var VERSION = true ? "0.4.1" : "dev";
16370
+ var VERSION = true ? "0.5.0" : "dev";
16773
16371
  // Annotate the CommonJS export names for ESM import in node:
16774
16372
  0 && (module.exports = {
16775
16373
  APlayProvider,
16776
16374
  B1ChurchProvider,
16375
+ BaseProvider,
16777
16376
  BibleProjectProvider,
16778
16377
  CbnProvider,
16378
+ DeviceFlowHelper,
16779
16379
  DropboxProvider,
16780
16380
  HighVoltageKidsProvider,
16781
16381
  JesusFilmProvider,
16782
16382
  LessonsChurchProvider,
16783
16383
  LifeChurchProvider,
16384
+ OAuthHelper,
16784
16385
  PlanningCenterProvider,
16785
16386
  SignPresenterProvider,
16786
16387
  TokenHelper,
@@ -16792,6 +16393,7 @@ var VERSION = true ? "0.4.1" : "dev";
16792
16393
  isContentFile,
16793
16394
  isContentFolder,
16794
16395
  navigateToPath,
16795
- registerProvider
16396
+ registerProvider,
16397
+ toAuthData
16796
16398
  });
16797
16399
  //# sourceMappingURL=index.cjs.map