@churchapps/content-providers 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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,42 +592,9 @@ 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] };
619
- }
620
- // Auth methods
621
- supportsDeviceFlow() {
622
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
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 }] };
623
598
  }
624
599
  generateCodeVerifier() {
625
600
  return this.oauthHelper.generateCodeVerifier();
@@ -667,6 +642,23 @@ function truncateForLabel(text) {
667
642
  const truncated = text.length > cutoff ? text.substring(0, cutoff) : text;
668
643
  return stripMarkdown(truncated) + (text.length > cutoff ? "..." : "");
669
644
  }
645
+ function getEmbedUrl(apiType, relatedId) {
646
+ if (!relatedId) return void 0;
647
+ const baseUrl = "https://lessons.church";
648
+ switch (apiType) {
649
+ case "action":
650
+ case "lessonAction":
651
+ return `${baseUrl}/embed/action/${relatedId}`;
652
+ case "addon":
653
+ case "lessonAddOn":
654
+ return `${baseUrl}/embed/addon/${relatedId}`;
655
+ case "section":
656
+ case "lessonSection":
657
+ return `${baseUrl}/embed/section/${relatedId}`;
658
+ default:
659
+ return void 0;
660
+ }
661
+ }
670
662
  async function convertAddOnToFile(addOn) {
671
663
  const apiPath = `/addOns/public/${addOn.id}`;
672
664
  const detail = await apiRequest(apiPath);
@@ -813,12 +805,13 @@ async function convertAddOnCategoryToInstructions(category) {
813
805
  }
814
806
 
815
807
  // src/providers/lessonsChurch/LessonsChurchProvider.ts
816
- var LessonsChurchProvider = class {
808
+ var LessonsChurchProvider = class extends BaseProvider {
817
809
  constructor() {
810
+ super(...arguments);
818
811
  this.id = "lessonschurch";
819
812
  this.name = "Lessons.church";
820
813
  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}` } };
814
+ this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [] };
822
815
  this.requiresAuth = false;
823
816
  this.authTypes = ["none"];
824
817
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
@@ -874,28 +867,25 @@ var LessonsChurchProvider = class {
874
867
  return [];
875
868
  }
876
869
  async getPrograms() {
877
- const response = await apiRequest(this.config.endpoints.programs);
870
+ const response = await apiRequest("/programs/public");
878
871
  if (!response) return [];
879
872
  const programs = Array.isArray(response) ? response : [];
880
873
  return programs.map((p) => createFolder(p.id, p.name, `/lessons/${p.id}`, p.image));
881
874
  }
882
875
  async getStudies(programId, currentPath) {
883
- const pathFn = this.config.endpoints.studies;
884
- const response = await apiRequest(pathFn(programId));
876
+ const response = await apiRequest(`/studies/public/program/${programId}`);
885
877
  if (!response) return [];
886
878
  const studies = Array.isArray(response) ? response : [];
887
879
  return studies.map((s) => createFolder(s.id, s.name, `${currentPath}/${s.id}`, s.image)).sort((a, b) => a.title.localeCompare(b.title));
888
880
  }
889
881
  async getLessons(studyId, currentPath) {
890
- const pathFn = this.config.endpoints.lessons;
891
- const response = await apiRequest(pathFn(studyId));
882
+ const response = await apiRequest(`/lessons/public/study/${studyId}`);
892
883
  if (!response) return [];
893
884
  const lessons = Array.isArray(response) ? response : [];
894
885
  return lessons.map((l) => createFolder(l.id, l.name || l.title, `${currentPath}/${l.id}`, l.image));
895
886
  }
896
887
  async getVenues(lessonId, currentPath) {
897
- const pathFn = this.config.endpoints.venues;
898
- const response = await apiRequest(pathFn(lessonId));
888
+ const response = await apiRequest(`/venues/public/lesson/${lessonId}`);
899
889
  if (!response) return [];
900
890
  const lessonResponse = await apiRequest(`/lessons/public/${lessonId}`);
901
891
  const lessonImage = lessonResponse?.image;
@@ -918,7 +908,7 @@ var LessonsChurchProvider = class {
918
908
  return file ? [file] : [];
919
909
  }
920
910
  async getAddOnCategories() {
921
- const response = await apiRequest(this.config.endpoints.addOns);
911
+ const response = await apiRequest("/addOns/public");
922
912
  if (!response) return [];
923
913
  const addOns = Array.isArray(response) ? response : [];
924
914
  const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
@@ -926,7 +916,7 @@ var LessonsChurchProvider = class {
926
916
  }
927
917
  async getAddOnsByCategory(category, currentPath) {
928
918
  const decodedCategory = decodeURIComponent(category);
929
- const response = await apiRequest(this.config.endpoints.addOns);
919
+ const response = await apiRequest("/addOns/public");
930
920
  if (!response) return [];
931
921
  const allAddOns = Array.isArray(response) ? response : [];
932
922
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
@@ -969,9 +959,6 @@ var LessonsChurchProvider = class {
969
959
  }
970
960
  return null;
971
961
  }
972
- supportsDeviceFlow() {
973
- return false;
974
- }
975
962
  };
976
963
 
977
964
  // src/providers/registry.ts
@@ -987,18 +974,6 @@ function registerProvider(provider) {
987
974
  }
988
975
 
989
976
  // 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
977
  async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
1003
978
  const codeChallenge = await generateCodeChallenge(codeVerifier);
1004
979
  const oauthParams = new URLSearchParams({
@@ -1022,7 +997,7 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
1022
997
  return null;
1023
998
  }
1024
999
  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(" ") };
1000
+ return toAuthData(data, { scope: config.scopes.join(" ") });
1026
1001
  } catch {
1027
1002
  return null;
1028
1003
  }
@@ -1036,7 +1011,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
1036
1011
  return null;
1037
1012
  }
1038
1013
  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(" ") };
1014
+ return toAuthData(data, { scope: config.scopes.join(" ") });
1040
1015
  } catch {
1041
1016
  return null;
1042
1017
  }
@@ -1048,7 +1023,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
1048
1023
  const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1049
1024
  if (!response.ok) return null;
1050
1025
  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 };
1026
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
1052
1027
  } catch {
1053
1028
  return null;
1054
1029
  }
@@ -1073,78 +1048,32 @@ async function authFetch(url, auth) {
1073
1048
  }
1074
1049
  }
1075
1050
  async function fetchMinistries(auth) {
1076
- const result = await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth);
1077
- return result || [];
1051
+ return await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth) || [];
1078
1052
  }
1079
1053
  async function fetchPlanTypes(ministryId, auth) {
1080
- const result = await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth);
1081
- return result || [];
1054
+ return await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth) || [];
1082
1055
  }
1083
1056
  async function fetchPlans(planTypeId, auth) {
1084
- const result = await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth);
1085
- return result || [];
1057
+ return await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth) || [];
1086
1058
  }
1087
- var DOING_API_BASE = "https://api.churchapps.org/doing";
1088
1059
  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
- }
1060
+ return authFetch(`${API_BASE3}/doing/plans/public/current/${planTypeId}`);
1100
1061
  }
1101
1062
  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
- }
1063
+ return await authFetch(`${API_BASE3}/doing/planItems/presenter/${churchId}/${planId}`) || [];
1109
1064
  }
1110
1065
  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
- }
1066
+ return authFetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`);
1122
1067
  }
1123
1068
  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
- }
1069
+ return authFetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`);
1135
1070
  }
1136
1071
  async function fetchVenueImages(venueIds) {
1137
1072
  const map = /* @__PURE__ */ new Map();
1138
1073
  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 {
1074
+ const data = await authFetch(`${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`);
1075
+ for (const row of data || []) {
1076
+ if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
1148
1077
  }
1149
1078
  return map;
1150
1079
  }
@@ -1233,18 +1162,7 @@ function getFileFromProviderFileItem(item) {
1233
1162
  };
1234
1163
  }
1235
1164
  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
- }
1165
+ const itemType = normalizeItemType(item.itemType);
1248
1166
  const isFileType = itemType === "file" || item.link && !item.children?.length;
1249
1167
  return {
1250
1168
  id: item.id,
@@ -1258,23 +1176,6 @@ function planItemToInstruction(item, thumbnail) {
1258
1176
  children: item.children?.map((child) => planItemToInstruction(child, thumbnail))
1259
1177
  };
1260
1178
  }
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
1179
  function buildSectionActionsMap2(actionsResponse, thumbnail) {
1279
1180
  const sectionActionsMap = /* @__PURE__ */ new Map();
1280
1181
  if (actionsResponse?.sections) {
@@ -1388,9 +1289,9 @@ function isExternalProviderItem(item) {
1388
1289
  const itemType = item.itemType || "";
1389
1290
  return itemType.startsWith("provider");
1390
1291
  }
1391
- var B1ChurchProvider = class {
1292
+ var B1ChurchProvider = class extends BaseProvider {
1392
1293
  constructor() {
1393
- this.apiHelper = new ApiHelper();
1294
+ super(...arguments);
1394
1295
  this.deviceFlowHelper = new DeviceFlowHelper();
1395
1296
  // Unified cache for external provider data to avoid duplicate calls across methods
1396
1297
  this.externalContentCache = {
@@ -1401,16 +1302,13 @@ var B1ChurchProvider = class {
1401
1302
  this.id = "b1church";
1402
1303
  this.name = "B1.Church";
1403
1304
  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}` } };
1305
+ 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
1306
  this.appBase = "https://admin.b1.church";
1406
1307
  this.requiresAuth = true;
1407
1308
  this.authTypes = ["oauth_pkce", "device_flow"];
1408
1309
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
1409
1310
  this.planTypeId = null;
1410
1311
  }
1411
- async apiRequest(path, authData) {
1412
- return this.apiHelper.apiRequest(this.config, this.id, path, authData);
1413
- }
1414
1312
  async buildAuthUrl(codeVerifier, redirectUri, state) {
1415
1313
  return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
1416
1314
  }
@@ -1556,8 +1454,7 @@ var B1ChurchProvider = class {
1556
1454
  console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
1557
1455
  return null;
1558
1456
  }
1559
- const pathFn = this.config.endpoints.planItems;
1560
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1457
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1561
1458
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1562
1459
  const externalInstructions = await fetchFromProviderProxy(
1563
1460
  "getInstructions",
@@ -1665,8 +1562,7 @@ var B1ChurchProvider = class {
1665
1562
  console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
1666
1563
  return null;
1667
1564
  }
1668
- const pathFn = this.config.endpoints.planItems;
1669
- const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1565
+ const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
1670
1566
  if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1671
1567
  const externalFiles = await fetchFromProviderProxy(
1672
1568
  "getPlaylist",
@@ -1761,9 +1657,6 @@ var B1ChurchProvider = class {
1761
1657
  }
1762
1658
  return files;
1763
1659
  }
1764
- supportsDeviceFlow() {
1765
- return !!this.config.supportsDeviceFlow;
1766
- }
1767
1660
  };
1768
1661
 
1769
1662
  // src/providers/dropbox/DropboxConverters.ts
@@ -1789,38 +1682,13 @@ function filterMediaEntries(entries) {
1789
1682
  }
1790
1683
  return { folders, mediaFiles };
1791
1684
  }
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
1685
 
1818
1686
  // src/providers/dropbox/DropboxProvider.ts
1819
1687
  var MAX_PAGINATION_CALLS = 50;
1820
- var DropboxProvider = class {
1688
+ var DropboxProvider = class extends BaseProvider {
1821
1689
  constructor() {
1690
+ super(...arguments);
1822
1691
  this.oauthHelper = new OAuthHelper();
1823
- this.apiHelper = new ApiHelper();
1824
1692
  this.id = "dropbox";
1825
1693
  this.name = "Dropbox";
1826
1694
  this.logos = {
@@ -1833,12 +1701,7 @@ var DropboxProvider = class {
1833
1701
  apiBase: "https://api.dropboxapi.com",
1834
1702
  oauthBase: "https://www.dropbox.com/oauth2",
1835
1703
  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
- }
1704
+ scopes: []
1842
1705
  };
1843
1706
  this.requiresAuth = true;
1844
1707
  this.authTypes = ["oauth_pkce"];
@@ -1849,18 +1712,17 @@ var DropboxProvider = class {
1849
1712
  mediaLicensing: false
1850
1713
  };
1851
1714
  }
1852
- // -- Pagination helper --
1853
1715
  dropboxPost(endpoint, body, auth) {
1854
1716
  if (!auth) {
1855
1717
  console.error("[Dropbox] No auth token provided");
1856
1718
  return Promise.resolve(null);
1857
1719
  }
1858
- return this.apiHelper.apiRequest(this.config, this.id, endpoint, auth, "POST", body);
1720
+ return this.apiRequest(endpoint, auth, "POST", body);
1859
1721
  }
1860
1722
  async listAllEntries(dropboxPath, auth) {
1861
1723
  const allEntries = [];
1862
1724
  const response = await this.dropboxPost(
1863
- this.config.endpoints.listFolder,
1725
+ "/2/files/list_folder",
1864
1726
  { path: dropboxPath, recursive: false, include_media_info: false },
1865
1727
  auth
1866
1728
  );
@@ -1871,7 +1733,7 @@ var DropboxProvider = class {
1871
1733
  let calls = 0;
1872
1734
  while (hasMore && calls < MAX_PAGINATION_CALLS) {
1873
1735
  const cont = await this.dropboxPost(
1874
- this.config.endpoints.listFolderContinue,
1736
+ "/2/files/list_folder/continue",
1875
1737
  { cursor },
1876
1738
  auth
1877
1739
  );
@@ -1883,7 +1745,6 @@ var DropboxProvider = class {
1883
1745
  }
1884
1746
  return allEntries;
1885
1747
  }
1886
- // -- File link helpers --
1887
1748
  async getSharedLink(filePath, auth) {
1888
1749
  if (!auth) return null;
1889
1750
  try {
@@ -1921,7 +1782,7 @@ var DropboxProvider = class {
1921
1782
  }
1922
1783
  async getTemporaryLink(filePath, auth) {
1923
1784
  const response = await this.dropboxPost(
1924
- this.config.endpoints.getTemporaryLink,
1785
+ "/2/files/get_temporary_link",
1925
1786
  { path: filePath },
1926
1787
  auth
1927
1788
  );
@@ -1939,17 +1800,15 @@ var DropboxProvider = class {
1939
1800
  );
1940
1801
  return results.filter((r) => r.viewUrl || r.downloadUrl).map((r) => fileEntryToContentFile(r.entry, r.viewUrl || r.downloadUrl, r.downloadUrl));
1941
1802
  }
1942
- // -- Leaf detection --
1943
1803
  async checkIsLeaf(folderPath, auth) {
1944
1804
  const response = await this.dropboxPost(
1945
- this.config.endpoints.listFolder,
1805
+ "/2/files/list_folder",
1946
1806
  { path: folderPath, recursive: false, include_media_info: false },
1947
1807
  auth
1948
1808
  );
1949
1809
  if (!response) return false;
1950
1810
  return !response.entries.some((e) => e[".tag"] === "folder");
1951
1811
  }
1952
- // -- IProvider methods --
1953
1812
  async browse(path, auth) {
1954
1813
  const dropboxPath = path && path !== "/" ? path.startsWith("/") ? path : "/" + path : "";
1955
1814
  const allEntries = await this.listAllEntries(dropboxPath, auth);
@@ -1961,30 +1820,26 @@ var DropboxProvider = class {
1961
1820
  const fileItems = await this.resolveFileLinks(mediaFiles, auth);
1962
1821
  return [...folderItems, ...fileItems];
1963
1822
  }
1964
- async getPlaylist(path, auth, _resolution) {
1823
+ async resolveMediaFiles(path, auth) {
1965
1824
  if (!path || path === "/") return null;
1966
1825
  const dropboxPath = path.startsWith("/") ? path : "/" + path;
1967
1826
  const allEntries = await this.listAllEntries(dropboxPath, auth);
1968
1827
  const { mediaFiles } = filterMediaEntries(allEntries);
1969
1828
  if (mediaFiles.length === 0) return null;
1970
1829
  const files = await this.resolveFileLinks(mediaFiles, auth);
1971
- return files.length > 0 ? files : null;
1830
+ if (files.length === 0) return null;
1831
+ return { files, dropboxPath };
1832
+ }
1833
+ async getPlaylist(path, auth, _resolution) {
1834
+ const resolved = await this.resolveMediaFiles(path, auth);
1835
+ return resolved?.files ?? null;
1972
1836
  }
1973
1837
  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);
1838
+ const resolved = await this.resolveMediaFiles(path, auth);
1839
+ if (!resolved) return null;
1840
+ const segments = resolved.dropboxPath.split("/").filter(Boolean);
1982
1841
  const folderName = segments[segments.length - 1] || "Dropbox";
1983
- return buildInstructionsFromFiles(files, folderName);
1984
- }
1985
- // -- Auth methods --
1986
- supportsDeviceFlow() {
1987
- return false;
1842
+ return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
1988
1843
  }
1989
1844
  generateCodeVerifier() {
1990
1845
  return this.oauthHelper.generateCodeVerifier();
@@ -2022,61 +1877,43 @@ var DropboxProvider = class {
2022
1877
  body: params.toString()
2023
1878
  });
2024
1879
  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
- };
1880
+ return toAuthData(await response.json());
2034
1881
  } catch {
2035
1882
  return null;
2036
1883
  }
2037
1884
  }
2038
1885
  };
2039
1886
 
2040
- // src/providers/planningCenter/PlanningCenterConverters.ts
2041
- var apiHelper = new ApiHelper();
1887
+ // src/providers/planningCenter/PlanningCenterProvider.ts
1888
+ var ONE_WEEK_MS = 6048e5;
2042
1889
  function formatDate(dateString) {
2043
- const date = new Date(dateString);
2044
- return date.toISOString().slice(0, 10);
1890
+ return new Date(dateString).toISOString().slice(0, 10);
2045
1891
  }
2046
-
2047
- // src/providers/planningCenter/PlanningCenterProvider.ts
2048
- var PlanningCenterProvider = class {
1892
+ var PlanningCenterProvider = class extends BaseProvider {
2049
1893
  constructor() {
2050
- this.apiHelper = new ApiHelper();
1894
+ super(...arguments);
2051
1895
  this.id = "planningcenter";
2052
1896
  this.name = "Planning Center";
2053
1897
  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;
1898
+ this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
2056
1899
  this.requiresAuth = true;
2057
1900
  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);
1901
+ // Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
1902
+ this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
2062
1903
  }
2063
1904
  async browse(path, auth) {
2064
1905
  const { segments, depth } = parsePath(path);
2065
1906
  if (depth === 0) {
2066
1907
  return [{ type: "folder", id: "serviceTypes-root", title: "Service Types", path: "/serviceTypes" }];
2067
1908
  }
2068
- const root = segments[0];
2069
- if (root !== "serviceTypes") return [];
1909
+ if (segments[0] !== "serviceTypes") return [];
2070
1910
  if (depth === 1) return this.getServiceTypes(auth);
2071
1911
  if (depth === 2) return this.getPlans(segments[1], path, auth);
2072
1912
  if (depth === 3) return this.getPlanItems(segments[1], segments[2], auth);
2073
1913
  return [];
2074
1914
  }
2075
1915
  async getServiceTypes(auth) {
2076
- const response = await this.apiRequest(
2077
- this.config.endpoints.serviceTypes,
2078
- auth
2079
- );
1916
+ const response = await this.apiRequest("/services/v2/service_types", auth);
2080
1917
  if (!response?.data) return [];
2081
1918
  return response.data.map((serviceType) => ({
2082
1919
  type: "folder",
@@ -2086,9 +1923,8 @@ var PlanningCenterProvider = class {
2086
1923
  }));
2087
1924
  }
2088
1925
  async getPlans(serviceTypeId, currentPath, auth) {
2089
- const pathFn = this.config.endpoints.plans;
2090
1926
  const response = await this.apiRequest(
2091
- `${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
1927
+ `/services/v2/service_types/${serviceTypeId}/plans?filter=future&order=sort_date`,
2092
1928
  auth
2093
1929
  );
2094
1930
  if (!response?.data) return [];
@@ -2096,7 +1932,7 @@ var PlanningCenterProvider = class {
2096
1932
  const filteredPlans = response.data.filter((plan) => {
2097
1933
  if (plan.attributes.items_count === 0) return false;
2098
1934
  const planDate = new Date(plan.attributes.sort_date).getTime();
2099
- return planDate < now + this.ONE_WEEK_MS;
1935
+ return planDate < now + ONE_WEEK_MS;
2100
1936
  });
2101
1937
  return filteredPlans.map((plan) => ({
2102
1938
  type: "folder",
@@ -2107,17 +1943,13 @@ var PlanningCenterProvider = class {
2107
1943
  }));
2108
1944
  }
2109
1945
  async getPlanItems(serviceTypeId, planId, auth) {
2110
- const pathFn = this.config.endpoints.planItems;
2111
1946
  const response = await this.apiRequest(
2112
- `${pathFn(serviceTypeId, planId)}?per_page=100`,
1947
+ `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items?per_page=100`,
2113
1948
  auth
2114
1949
  );
2115
1950
  if (!response?.data) return [];
2116
1951
  return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
2117
1952
  }
2118
- supportsDeviceFlow() {
2119
- return false;
2120
- }
2121
1953
  };
2122
1954
 
2123
1955
  // src/providers/bibleProject/data.json
@@ -4512,8 +4344,9 @@ var data_default = {
4512
4344
  };
4513
4345
 
4514
4346
  // src/providers/bibleProject/BibleProjectProvider.ts
4515
- var BibleProjectProvider = class {
4347
+ var BibleProjectProvider = class extends BaseProvider {
4516
4348
  constructor() {
4349
+ super(...arguments);
4517
4350
  this.id = "bibleproject";
4518
4351
  this.name = "The Bible Project";
4519
4352
  this.logos = {
@@ -4526,10 +4359,8 @@ var BibleProjectProvider = class {
4526
4359
  apiBase: "https://bibleproject.com",
4527
4360
  oauthBase: "",
4528
4361
  clientId: "",
4529
- scopes: [],
4530
- endpoints: { downloads: "/downloads/" }
4362
+ scopes: []
4531
4363
  };
4532
- this.data = data_default;
4533
4364
  this.requiresAuth = false;
4534
4365
  this.authTypes = ["none"];
4535
4366
  this.capabilities = {
@@ -4538,133 +4369,71 @@ var BibleProjectProvider = class {
4538
4369
  instructions: true,
4539
4370
  mediaLicensing: false
4540
4371
  };
4372
+ this.data = data_default;
4541
4373
  }
4542
4374
  getMuxThumbnail(muxPlaybackId, width = 400, height = 225) {
4543
4375
  return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=${width}&height=${height}`;
4544
4376
  }
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,
4377
+ videoToFile(video) {
4378
+ return createFile(video.id, video.title, video.videoUrl, {
4379
+ mediaType: "video",
4577
4380
  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 })];
4381
+ muxPlaybackId: video.muxPlaybackId,
4382
+ seconds: 0
4383
+ });
4588
4384
  }
4589
- async getPlaylist(path, _auth, _resolution) {
4385
+ /** Resolve a path to its collection and files: all videos at depth 1, a single video at depth 2. */
4386
+ resolve(path) {
4590
4387
  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);
4388
+ if (depth < 1 || depth > 2) return null;
4389
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4594
4390
  if (!collection) return null;
4595
4391
  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;
4392
+ return { collection, files: collection.videos.map((v) => this.videoToFile(v)), name: collection.name };
4598
4393
  }
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;
4394
+ const video = collection.videos.find((v) => v.id === segments[1]);
4395
+ if (!video) return null;
4396
+ return { collection, files: [this.videoToFile(video)], name: video.title };
4606
4397
  }
4607
- async getInstructions(path, _auth) {
4398
+ async browse(path, _auth) {
4608
4399
  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;
4400
+ if (depth === 0) {
4401
+ return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
4402
+ type: "folder",
4403
+ id: slugify(collection.name),
4404
+ title: collection.name,
4405
+ thumbnail: collection.image || void 0,
4406
+ path: `/${slugify(collection.name)}`
4407
+ }));
4408
+ }
4613
4409
  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
- ]
4410
+ const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
4411
+ if (!collection) return [];
4412
+ return collection.videos.map((video) => ({
4413
+ type: "folder",
4414
+ id: video.id,
4415
+ title: video.title,
4416
+ thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
4417
+ isLeaf: true,
4418
+ path: `${path}/${video.id}`
4628
4419
  }));
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
4420
  }
4637
4421
  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] };
4422
+ return this.resolve(path)?.files ?? [];
4663
4423
  }
4664
- return null;
4424
+ return [];
4665
4425
  }
4666
- supportsDeviceFlow() {
4667
- return false;
4426
+ async getPlaylist(path, _auth, _resolution) {
4427
+ const resolved = this.resolve(path);
4428
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
4429
+ }
4430
+ async getInstructions(path, _auth) {
4431
+ const resolved = this.resolve(path);
4432
+ if (!resolved) return null;
4433
+ return filesToInstructions(resolved.name, resolved.files, {
4434
+ id: slugify(resolved.collection.name) + "-section",
4435
+ label: resolved.collection.name
4436
+ });
4668
4437
  }
4669
4438
  };
4670
4439
 
@@ -12288,8 +12057,9 @@ function buildLessonInstructions(lesson) {
12288
12057
  }
12289
12058
 
12290
12059
  // src/providers/highVoltage/HighVoltageKidsProvider.ts
12291
- var HighVoltageKidsProvider = class {
12060
+ var HighVoltageKidsProvider = class extends BaseProvider {
12292
12061
  constructor() {
12062
+ super(...arguments);
12293
12063
  this.id = "highvoltagekids";
12294
12064
  this.name = "High Voltage Kids";
12295
12065
  this.logos = {
@@ -12302,8 +12072,7 @@ var HighVoltageKidsProvider = class {
12302
12072
  apiBase: "https://highvoltagekids.com",
12303
12073
  oauthBase: "",
12304
12074
  clientId: "",
12305
- scopes: [],
12306
- endpoints: { downloads: "/membership-downloads/" }
12075
+ scopes: []
12307
12076
  };
12308
12077
  this.data = data_default2;
12309
12078
  this.requiresAuth = false;
@@ -12353,9 +12122,6 @@ var HighVoltageKidsProvider = class {
12353
12122
  }
12354
12123
  return null;
12355
12124
  }
12356
- supportsDeviceFlow() {
12357
- return false;
12358
- }
12359
12125
  };
12360
12126
 
12361
12127
  // src/providers/jesusFilm/JesusFilmProvider.ts
@@ -12367,8 +12133,14 @@ var CATEGORY_MAP = {
12367
12133
  "series": "series",
12368
12134
  "collections": "collection"
12369
12135
  };
12370
- var JesusFilmProvider = class {
12136
+ var CATEGORY_NAMES = {
12137
+ "feature-films": "Feature Films",
12138
+ "series": "Series",
12139
+ "collections": "Collections"
12140
+ };
12141
+ var JesusFilmProvider = class extends BaseProvider {
12371
12142
  constructor() {
12143
+ super(...arguments);
12372
12144
  this.id = "jesusfilm";
12373
12145
  this.name = "Jesus Film Project";
12374
12146
  this.logos = {
@@ -12381,8 +12153,7 @@ var JesusFilmProvider = class {
12381
12153
  apiBase: API_BASE4,
12382
12154
  oauthBase: "",
12383
12155
  clientId: "",
12384
- scopes: [],
12385
- endpoints: { watch: "/watch" }
12156
+ scopes: []
12386
12157
  };
12387
12158
  this.requiresAuth = false;
12388
12159
  this.authTypes = ["none"];
@@ -12393,6 +12164,7 @@ var JesusFilmProvider = class {
12393
12164
  mediaLicensing: false
12394
12165
  };
12395
12166
  }
12167
+ // Not ApiHelper: Arclight needs the apiKey query param and callers rely on throw-on-error.
12396
12168
  async fetchApi(path) {
12397
12169
  const separator = path.includes("?") ? "&" : "?";
12398
12170
  const url = `${API_BASE4}${path}${separator}apiKey=${API_KEY}`;
@@ -12400,6 +12172,12 @@ var JesusFilmProvider = class {
12400
12172
  if (!response.ok) throw new Error(`Arclight API error: ${response.status}`);
12401
12173
  return response.json();
12402
12174
  }
12175
+ async fetchCategoryComponents(subType) {
12176
+ const data = await this.fetchApi(
12177
+ `/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
12178
+ );
12179
+ return data._embedded.mediaComponents;
12180
+ }
12403
12181
  async browse(path, _auth) {
12404
12182
  const { segments, depth } = parsePath(path);
12405
12183
  if (depth === 0) return this.getCategories();
@@ -12422,19 +12200,14 @@ var JesusFilmProvider = class {
12422
12200
  async getItemsInCategory(category) {
12423
12201
  const subType = CATEGORY_MAP[category];
12424
12202
  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
- });
12203
+ const components = await this.fetchCategoryComponents(subType);
12204
+ return components.map((item) => createFolder(
12205
+ item.mediaComponentId,
12206
+ item.title,
12207
+ `/${category}/${item.mediaComponentId}`,
12208
+ item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
12209
+ item.containsCount === 0
12210
+ ));
12438
12211
  }
12439
12212
  async getVideoFile(mediaComponentId) {
12440
12213
  const variant = await this.fetchApi(
@@ -12449,11 +12222,10 @@ var JesusFilmProvider = class {
12449
12222
  const title = component?.title || mediaComponentId;
12450
12223
  const seconds = component ? Math.round(component.lengthInMilliseconds / 1e3) : 0;
12451
12224
  const thumbnail = component?.imageUrls.mobileCinematicHigh || component?.imageUrls.videoStill;
12452
- const muxPlaybackId = this.extractMuxPlaybackId(downloadUrl);
12453
12225
  return [
12454
12226
  createFile(mediaComponentId, title, downloadUrl, {
12455
12227
  mediaType: "video",
12456
- muxPlaybackId,
12228
+ muxPlaybackId: this.extractMuxPlaybackId(downloadUrl),
12457
12229
  seconds,
12458
12230
  thumbnail
12459
12231
  })
@@ -12465,9 +12237,8 @@ var JesusFilmProvider = class {
12465
12237
  );
12466
12238
  const childIds = linksData.linkedMediaComponentIds?.contains;
12467
12239
  if (!childIds || childIds.length === 0) return [];
12468
- const idsParam = childIds.join(",");
12469
12240
  const childrenData = await this.fetchApi(
12470
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12241
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12471
12242
  );
12472
12243
  if (!childrenData._embedded?.mediaComponents) return [];
12473
12244
  return childrenData._embedded.mediaComponents.map((item) => createFolder(
@@ -12482,36 +12253,6 @@ var JesusFilmProvider = class {
12482
12253
  const match = url.match(/stream\.mux\.com\/([^/]+)/);
12483
12254
  return match ? match[1] : void 0;
12484
12255
  }
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
12256
  async fetchVideoFiles(components) {
12516
12257
  const files = [];
12517
12258
  for (const component of components) {
@@ -12542,9 +12283,8 @@ var JesusFilmProvider = class {
12542
12283
  );
12543
12284
  const childIds = linksData.linkedMediaComponentIds?.contains;
12544
12285
  if (!childIds || childIds.length === 0) return [];
12545
- const idsParam = childIds.join(",");
12546
12286
  const childrenData = await this.fetchApi(
12547
- `/media-components?ids=${idsParam}&languageIds=${LANGUAGE_ID}`
12287
+ `/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
12548
12288
  );
12549
12289
  if (!childrenData._embedded?.mediaComponents) return [];
12550
12290
  const contentItems = childrenData._embedded.mediaComponents.filter((c) => c.containsCount === 0);
@@ -12556,64 +12296,47 @@ var JesusFilmProvider = class {
12556
12296
  }
12557
12297
  return files;
12558
12298
  }
12299
+ async getPlaylist(path, _auth, _resolution) {
12300
+ const { segments, depth } = parsePath(path);
12301
+ if (depth < 1) return null;
12302
+ const category = segments[0];
12303
+ if (depth >= 3) {
12304
+ const lastId = segments[depth - 1];
12305
+ const items = await this.getVideoFile(lastId);
12306
+ const files2 = items.filter((item) => item.type === "file");
12307
+ if (files2.length > 0) return files2;
12308
+ const containerFiles = await this.fetchContainerVideoFiles(lastId);
12309
+ return containerFiles.length > 0 ? containerFiles : null;
12310
+ }
12311
+ if (depth === 2) {
12312
+ if (category === "feature-films") {
12313
+ const items = await this.getVideoFile(segments[1]);
12314
+ if (items.length === 0) return null;
12315
+ return items.filter((item) => item.type === "file");
12316
+ }
12317
+ const files2 = await this.fetchContainerVideoFiles(segments[1]);
12318
+ return files2.length > 0 ? files2 : null;
12319
+ }
12320
+ const subType = CATEGORY_MAP[category];
12321
+ if (!subType) return null;
12322
+ const components = await this.fetchCategoryComponents(subType);
12323
+ const files = await this.fetchVideoFiles(components.filter((item) => item.containsCount === 0));
12324
+ return files.length > 0 ? files : null;
12325
+ }
12559
12326
  async buildVideoInstructions(mediaComponentId, category) {
12560
12327
  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] };
12328
+ const file = items.find((item) => item.type === "file");
12329
+ if (!file) return null;
12330
+ return filesToInstructions(file.title, [file], { id: category + "-section", label: file.title });
12586
12331
  }
12587
- async buildContainerInstructions(containerId, category) {
12332
+ async buildContainerInstructions(containerId) {
12588
12333
  const containerData = await this.fetchApi(
12589
12334
  `/media-components?ids=${containerId}&languageIds=${LANGUAGE_ID}`
12590
12335
  );
12591
- const container = containerData._embedded?.mediaComponents?.[0];
12592
- const containerName = container?.title || containerId;
12336
+ const containerName = containerData._embedded?.mediaComponents?.[0]?.title || containerId;
12593
12337
  const videoFiles = await this.fetchContainerVideoFiles(containerId);
12594
12338
  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] };
12339
+ return filesToInstructions(containerName, videoFiles, { id: containerId + "-section", label: containerName });
12617
12340
  }
12618
12341
  async getInstructions(path, _auth) {
12619
12342
  const { segments, depth } = parsePath(path);
@@ -12622,61 +12345,19 @@ var JesusFilmProvider = class {
12622
12345
  const subType = CATEGORY_MAP[category];
12623
12346
  if (!subType) return null;
12624
12347
  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] };
12348
+ const categoryName = CATEGORY_NAMES[category];
12349
+ const components = await this.fetchCategoryComponents(subType);
12350
+ const files = await this.fetchVideoFiles(components.filter((c) => c.containsCount === 0));
12351
+ return filesToInstructions(categoryName, files, { id: category + "-section", label: categoryName });
12665
12352
  }
12666
12353
  if (depth === 2) {
12667
12354
  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);
12355
+ return this.buildContainerInstructions(segments[1]);
12675
12356
  }
12676
- return null;
12677
- }
12678
- supportsDeviceFlow() {
12679
- return false;
12357
+ const lastId = segments[depth - 1];
12358
+ const videoResult = await this.buildVideoInstructions(lastId, category);
12359
+ if (videoResult) return videoResult;
12360
+ return this.buildContainerInstructions(lastId);
12680
12361
  }
12681
12362
  };
12682
12363
 
@@ -12720,35 +12401,15 @@ function convertPlaylistToFiles(playlist) {
12720
12401
  return file;
12721
12402
  });
12722
12403
  }
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
12404
 
12744
12405
  // src/providers/cbn/logo.ts
12745
12406
  var CBN_LOGO = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/tpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMi1jMDAwIDc5LjFiNjVhNzliNCwgMjAyMi8wNi8xMy0yMjowMTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjczNTFDMjdFNENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjczNTFDMjdENENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENDIDIwMTcgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0idXVpZDo4NGJiYzc1OC1iNjBhLTFlNGQtODJlMS1mYTQyNzhlMmZhMTciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OTQzMjNlNWMtYjBlMS00YTliLWJjZWUtODljYjRlOGVjZjAwIi8+IDxkYzp0aXRsZT4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+Q0JOLUZMQU1FPC9yZGY6bGk+IDwvcmRmOkFsdD4gPC9kYzp0aXRsZT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz40pvOUAAAC2VBMVEUdmtYhnNf8/v/7/f4emtb9/v8fm9by+f0nntjU7Pf6/f4gm9b2+/0lntfk8/q43/L3+/5Nr9/5/P4poNg1pdovotnj8voknddQsd84ptsqoNjp9fve8PnX7fgpn9hRsd/1+/3M6Pba7vhmuuO/4vMinNfG5vUtodmGyenm9Pq64PKOzOrz+f2c0+15w+fw+PyVz+zx+fyq2fCl1+9tvuREq91ateGx3PHv+Pz4/P6d0+0mnthxwOXf8fng8fkroNlhuOI3pduk1u7B4/Ts9/zq9ftAqdxHrd54w+ft9/yu2/CKy+pOsN88qNxft+I+qdy74fNIrd7n9PtDq93z+v1Ws+BrveTS6/eW0OxCq91/xuiz3fFvvuXN6fYzpNqZ0e2Ly+oxo9q94fO94vPc7/nF5fSn2O/o9ft2wuYwotk2pdtVs+Du9/ySzutqvOQjnddPsN8uotl3wubY7vjC5PSEyOl1weZpvORSsuBctuHl9Po5p9vT6/eMzOo7p9uBx+iv2/GTz+vh8vq03fFguOLs9vvV7Pe23/JXtOBkuuMon9i+4vOQzetfuOKg1e6Jyurr9vvi8vp+xuhnu+Op2e/R6vf0+v3c8Pnb7/h+xehLr946p9ssodmj1u40pNqUz+zd8PlJrt57xOdXtOFdt+Kw3PFAqtxbtuG33/J8xOd6xOet2vDI5vW54PJwv+VBqt1FrN2a0u1luuOAxuhsveSh1e6s2vCNzOqt2/Ci1e7E5PTJ5/XH5vXL6PY9qNx0weZ9xedYteHK5/WFyOmCx+hnu+TQ6vZovOS13vLD5PSb0u1Mr9/O6fay3fG84fPZ7vhiueNKrt5zwObP6vZZteFjueOm1+9TsuCf1O6o2O+DyOhUs+DF5fWPzeuX0ezW7fhGrN0wo9qOzetet+IypNqr2fC13vGY0exvv+WRzuue1O2IyumHyulywOVuvuU/qdye0+3A4/T///96A0GMAAAA83RSTlP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wB12P98AAAWOklEQVR42uzd5YMUx9YG8JpZN3ZZdHFblg3u7u7uGtzdITgkwUJCsBCc4CRA8BASEgJxtxtPbpKrr52/4EUvK3VqWk5V9+ye5+vOdE3Xb2e6u1QAx1cRXAUMwmEQBuEwCINwGIRBOAzCIBwG4TAIg3AYhEE4DMIgHAZhEA6DcBiEQTgMwiAcBmEQDoMwCIdBOAzCIBwGYRAOgzAIh0EYhMMgHAZhEA6DMAiHQRiEwyAMwmEQDoMwCIdBsmdfcwbxVeLWt2EQX2WT2BrDID7KcCGWDWQQH2WKEOnjGcQ/eU/cyZAgg/gm9e+KfFmTQfyS8XdBRM/9DOKTpBS5J1KkPIP4JFXugYikxxjEH1kVeV8k+iKD+COHxYM0YRBf5PRDEFGLQfyQ4F7/i+Sv5vd/C9+L5C+Q/Y9AxE8M4oOUzSJyhUG8z1+ygIgVDOJ5+jXNAhJoyyCeZ0DWr0jsLQbxOqOzgohfizKIx4kokk1kcAyDeJwT2UDE0SCDeJvy2UHEmwzicdJyiFxkEG8zJAdIsYEM4ml+yAEi0qYyiKf3WS/mFNnEIJ7mXE4QMZJBvMzIXCCBWwziYT7MBSKWVWMQDy8igdwiExnEwwzODSJGM4h3eUcCMmIqg3iWJyQgoh6DeJY2MhCxgUG8ymkpSFoig3iU4VIQ8TaDeJSf5SAJcQziTYrKQcQ3DOIvELGaQfwFsqQxg3iRChiIWMkgXmQgChLbjkE8yHsoiBjCIB7kJA5SLJNBzGcfDiI6MYj5dFKABJoziPFMUYCI+QxiPBkqkEBJBjGckkKZ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";
12746
12407
 
12747
12408
  // src/providers/cbn/CbnProvider.ts
12748
12409
  var API_BASE5 = "https://sbamemberdev.wpenginepowered.com/wp-json/superbook/v1";
12749
- var CbnProvider = class {
12410
+ var CbnProvider = class extends BaseProvider {
12750
12411
  constructor() {
12751
- this.apiHelper = new ApiHelper();
12412
+ super(...arguments);
12752
12413
  this.deviceFlowHelper = new DeviceFlowHelper();
12753
12414
  this.id = "cbn";
12754
12415
  this.name = "CBN";
@@ -12761,66 +12422,48 @@ var CbnProvider = class {
12761
12422
  clientId: "freeplay",
12762
12423
  scopes: [],
12763
12424
  supportsDeviceFlow: true,
12764
- deviceAuthEndpoint: "/device",
12765
- endpoints: {
12766
- catalog: "/catalog",
12767
- courseDetail: (courseId) => `/catalog/${courseId}`,
12768
- lessonPlaylist: (lessonId) => `/lesson-playlist/${lessonId}`
12769
- }
12425
+ deviceAuthEndpoint: "/device"
12770
12426
  };
12771
12427
  this.requiresAuth = true;
12772
12428
  this.authTypes = ["device_flow"];
12773
12429
  this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
12774
12430
  }
12775
- async apiRequest(path, auth) {
12776
- return this.apiHelper.apiRequest(this.config, this.id, path, auth);
12777
- }
12778
12431
  async browse(path, auth) {
12779
12432
  const { segments, depth } = parsePath(path);
12780
12433
  if (depth === 0) {
12781
12434
  return [{ type: "folder", id: "catalog-root", title: "Catalog", path: "/catalog" }];
12782
12435
  }
12783
12436
  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);
12437
+ if (depth === 1) {
12438
+ const response = await this.apiRequest("/catalog", auth);
12439
+ return Array.isArray(response) ? convertCoursesToFolders(response) : [];
12440
+ }
12441
+ if (depth === 2) {
12442
+ const response = await this.apiRequest(`/catalog/${segments[1]}`, auth);
12443
+ return response?.lessons ? convertLessonsToFolders(response.lessons, path) : [];
12444
+ }
12445
+ if (depth === 3) {
12446
+ const resolved = await this.resolveFiles(path, auth);
12447
+ return resolved?.files ?? [];
12448
+ }
12787
12449
  return [];
12788
12450
  }
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) {
12451
+ /** Resolve a leaf path (/catalog/{courseId}/{lessonId}) to its playlist files. */
12452
+ async resolveFiles(path, auth) {
12809
12453
  const { segments, depth } = parsePath(path);
12810
12454
  if (depth !== 3 || segments[0] !== "catalog") return null;
12811
- const playlist = await this.fetchPlaylist(segments[2], auth);
12455
+ const playlist = await this.apiRequest(`/lesson-playlist/${segments[2]}`, auth);
12812
12456
  if (!playlist || playlist.playlist.length === 0) return null;
12813
- return convertPlaylistToFiles(playlist);
12457
+ return { files: convertPlaylistToFiles(playlist), lessonTitle: playlist.lesson_title };
12814
12458
  }
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);
12459
+ async getPlaylist(path, auth, _resolution) {
12460
+ const resolved = await this.resolveFiles(path, auth);
12461
+ return resolved?.files ?? null;
12821
12462
  }
12822
- supportsDeviceFlow() {
12823
- return this.deviceFlowHelper.supportsDeviceFlow(this.config);
12463
+ async getInstructions(path, auth) {
12464
+ const resolved = await this.resolveFiles(path, auth);
12465
+ if (!resolved) return null;
12466
+ return filesToInstructions(resolved.lessonTitle || "Lesson", resolved.files);
12824
12467
  }
12825
12468
  async initiateDeviceFlow() {
12826
12469
  return this.deviceFlowHelper.initiateDeviceFlow(this.config);
@@ -16451,8 +16094,9 @@ var data_default3 = {
16451
16094
  };
16452
16095
 
16453
16096
  // src/providers/lifeChurch/LifeChurchProvider.ts
16454
- var LifeChurchProvider = class {
16097
+ var LifeChurchProvider = class extends BaseProvider {
16455
16098
  constructor() {
16099
+ super(...arguments);
16456
16100
  this.id = "lifechurch";
16457
16101
  this.name = "Life.Church";
16458
16102
  this.logos = {
@@ -16465,8 +16109,7 @@ var LifeChurchProvider = class {
16465
16109
  apiBase: "https://www.life.church",
16466
16110
  oauthBase: "",
16467
16111
  clientId: "",
16468
- scopes: [],
16469
- endpoints: {}
16112
+ scopes: []
16470
16113
  };
16471
16114
  this.requiresAuth = false;
16472
16115
  this.authTypes = ["none"];
@@ -16481,20 +16124,14 @@ var LifeChurchProvider = class {
16481
16124
  findSeries(seriesId) {
16482
16125
  return this.data.series.find((s) => s.id === seriesId);
16483
16126
  }
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
16127
  findScheduled(scheduledId) {
16491
16128
  return this.data.scheduledLessons?.find((s) => s.id === scheduledId);
16492
16129
  }
16493
16130
  /** Resolve a scheduled-lesson reference to the underlying lesson + unit + series. */
16494
16131
  resolveScheduled(sched) {
16495
16132
  const series = this.findSeries(sched.seriesId);
16496
- const unit = series && this.findUnit(series, sched.unitId);
16497
- const lesson = unit && this.findLesson(unit, sched.lessonId);
16133
+ const unit = series?.units.find((u) => u.id === sched.unitId);
16134
+ const lesson = unit?.lessons.find((l) => l.id === sched.lessonId);
16498
16135
  if (!series || !unit || !lesson) return null;
16499
16136
  return { series, unit, lesson };
16500
16137
  }
@@ -16509,17 +16146,40 @@ var LifeChurchProvider = class {
16509
16146
  providerData: { sourceUrl: lesson.sourceUrl }
16510
16147
  };
16511
16148
  }
16149
+ /** Resolve a leaf-or-unit path to its files plus the name/section used for instructions. */
16150
+ resolve(path) {
16151
+ const { segments, depth } = parsePath(path);
16152
+ if (segments[0] === "scheduled") {
16153
+ if (depth !== 2) return null;
16154
+ const sched = this.findScheduled(segments[1]);
16155
+ const resolved = sched && this.resolveScheduled(sched);
16156
+ if (!sched || !resolved) return null;
16157
+ const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
16158
+ return {
16159
+ files: [this.lessonToFile(resolved.lesson, label)],
16160
+ name: label,
16161
+ section: { id: `scheduled-${sched.id}-section`, label: "This Sunday" }
16162
+ };
16163
+ }
16164
+ const series = this.findSeries(segments[0]);
16165
+ const unit = series?.units.find((u) => u.id === segments[1]);
16166
+ if (!unit) return null;
16167
+ if (depth === 2) {
16168
+ return { files: unit.lessons.map((l) => this.lessonToFile(l)), name: unit.name, section: { id: `${unit.id}-section`, label: unit.name } };
16169
+ }
16170
+ if (depth === 3) {
16171
+ const lesson = unit.lessons.find((l) => l.id === segments[2]);
16172
+ if (!lesson) return null;
16173
+ return { files: [this.lessonToFile(lesson)], name: lesson.title, section: { id: `${unit.id}-section`, label: unit.name } };
16174
+ }
16175
+ return null;
16176
+ }
16512
16177
  async browse(path, _auth) {
16513
16178
  const { segments, depth } = parsePath(path);
16514
16179
  if (depth === 0) {
16515
16180
  const items = [];
16516
16181
  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
- });
16182
+ items.push({ type: "folder", id: "scheduled", title: "This Sunday", path: "/scheduled" });
16523
16183
  }
16524
16184
  for (const s of this.data.series) {
16525
16185
  items.push({ type: "folder", id: s.id, title: s.name, thumbnail: s.thumbnail, path: `/${s.id}` });
@@ -16540,13 +16200,7 @@ var LifeChurchProvider = class {
16540
16200
  };
16541
16201
  });
16542
16202
  }
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 [];
16203
+ return this.resolve(path)?.files ?? [];
16550
16204
  }
16551
16205
  if (depth === 1) {
16552
16206
  const series = this.findSeries(segments[0]);
@@ -16561,8 +16215,8 @@ var LifeChurchProvider = class {
16561
16215
  }
16562
16216
  if (depth === 2) {
16563
16217
  const series = this.findSeries(segments[0]);
16564
- const unit = series && this.findUnit(series, segments[1]);
16565
- if (!series || !unit) return [];
16218
+ const unit = series?.units.find((u) => u.id === segments[1]);
16219
+ if (!unit) return [];
16566
16220
  return unit.lessons.map((l) => ({
16567
16221
  type: "folder",
16568
16222
  id: l.id,
@@ -16573,78 +16227,18 @@ var LifeChurchProvider = class {
16573
16227
  }));
16574
16228
  }
16575
16229
  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)];
16230
+ return this.resolve(path)?.files ?? [];
16581
16231
  }
16582
16232
  return [];
16583
16233
  }
16584
16234
  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;
16235
+ const resolved = this.resolve(path);
16236
+ return resolved && resolved.files.length > 0 ? resolved.files : null;
16605
16237
  }
16606
16238
  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;
16239
+ const resolved = this.resolve(path);
16240
+ if (!resolved) return null;
16241
+ return filesToInstructions(resolved.name, resolved.files, resolved.section);
16648
16242
  }
16649
16243
  /**
16650
16244
  * Returns the most recent scheduled lesson from data.scheduledLessons.
@@ -16672,9 +16266,6 @@ var LifeChurchProvider = class {
16672
16266
  ]
16673
16267
  };
16674
16268
  }
16675
- supportsDeviceFlow() {
16676
- return false;
16677
- }
16678
16269
  };
16679
16270
 
16680
16271
  // src/providers/index.ts
@@ -16769,18 +16360,21 @@ function getAvailableProviders(ids) {
16769
16360
  }
16770
16361
 
16771
16362
  // src/index.ts
16772
- var VERSION = true ? "0.4.1" : "dev";
16363
+ var VERSION = true ? "0.5.1" : "dev";
16773
16364
  // Annotate the CommonJS export names for ESM import in node:
16774
16365
  0 && (module.exports = {
16775
16366
  APlayProvider,
16776
16367
  B1ChurchProvider,
16368
+ BaseProvider,
16777
16369
  BibleProjectProvider,
16778
16370
  CbnProvider,
16371
+ DeviceFlowHelper,
16779
16372
  DropboxProvider,
16780
16373
  HighVoltageKidsProvider,
16781
16374
  JesusFilmProvider,
16782
16375
  LessonsChurchProvider,
16783
16376
  LifeChurchProvider,
16377
+ OAuthHelper,
16784
16378
  PlanningCenterProvider,
16785
16379
  SignPresenterProvider,
16786
16380
  TokenHelper,
@@ -16792,6 +16386,7 @@ var VERSION = true ? "0.4.1" : "dev";
16792
16386
  isContentFile,
16793
16387
  isContentFolder,
16794
16388
  navigateToPath,
16795
- registerProvider
16389
+ registerProvider,
16390
+ toAuthData
16796
16391
  });
16797
16392
  //# sourceMappingURL=index.cjs.map