@churchapps/content-provider-helper 0.0.3 → 0.0.4

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
@@ -25,6 +25,7 @@ __export(index_exports, {
25
25
  B1ChurchProvider: () => B1ChurchProvider,
26
26
  BibleProjectProvider: () => BibleProjectProvider,
27
27
  ContentProvider: () => ContentProvider,
28
+ DEFAULT_DURATION_CONFIG: () => DEFAULT_DURATION_CONFIG,
28
29
  DeviceFlowHelper: () => DeviceFlowHelper,
29
30
  FormatConverters: () => FormatConverters_exports,
30
31
  FormatResolver: () => FormatResolver,
@@ -37,12 +38,16 @@ __export(index_exports, {
37
38
  VERSION: () => VERSION,
38
39
  appendToPath: () => appendToPath,
39
40
  buildPath: () => buildPath,
40
- collapseInstructions: () => collapseInstructions,
41
+ countWords: () => countWords,
41
42
  createFile: () => createFile,
42
43
  createFolder: () => createFolder,
43
44
  detectMediaType: () => detectMediaType,
45
+ estimateDuration: () => estimateDuration,
46
+ estimateImageDuration: () => estimateImageDuration,
47
+ estimateTextDuration: () => estimateTextDuration,
44
48
  expandedInstructionsToPlaylist: () => expandedInstructionsToPlaylist,
45
49
  expandedInstructionsToPresentations: () => expandedInstructionsToPresentations,
50
+ generatePath: () => generatePath,
46
51
  getAllProviders: () => getAllProviders,
47
52
  getAvailableProviders: () => getAvailableProviders,
48
53
  getProvider: () => getProvider,
@@ -52,12 +57,12 @@ __export(index_exports, {
52
57
  instructionsToPresentations: () => instructionsToPresentations,
53
58
  isContentFile: () => isContentFile,
54
59
  isContentFolder: () => isContentFolder,
60
+ navigateToPath: () => navigateToPath,
55
61
  parsePath: () => parsePath,
56
62
  playlistToExpandedInstructions: () => playlistToExpandedInstructions,
57
63
  playlistToInstructions: () => playlistToInstructions,
58
64
  playlistToPresentations: () => playlistToPresentations,
59
65
  presentationsToExpandedInstructions: () => presentationsToExpandedInstructions,
60
- presentationsToInstructions: () => presentationsToInstructions,
61
66
  presentationsToPlaylist: () => presentationsToPlaylist,
62
67
  registerProvider: () => registerProvider
63
68
  });
@@ -77,11 +82,11 @@ function detectMediaType(url, explicitType) {
77
82
  const videoPatterns = [".mp4", ".webm", ".m3u8", ".mov", "stream.mux.com"];
78
83
  return videoPatterns.some((p) => url.includes(p)) ? "video" : "image";
79
84
  }
80
- function createFolder(id, title, path, image, providerData, isLeaf) {
81
- return { type: "folder", id, title, path, image, isLeaf, providerData };
85
+ function createFolder(id, title, path, image, isLeaf) {
86
+ return { type: "folder", id, title, path, image, isLeaf };
82
87
  }
83
88
  function createFile(id, title, url, options) {
84
- return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
89
+ return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, seconds: options?.seconds, loop: options?.loop, loopVideo: options?.loopVideo, streamUrl: options?.streamUrl };
85
90
  }
86
91
 
87
92
  // src/pathUtils.ts
@@ -108,10 +113,61 @@ function appendToPath(basePath, segment) {
108
113
  return cleanBase + "/" + segment;
109
114
  }
110
115
 
116
+ // src/instructionPathUtils.ts
117
+ function navigateToPath(instructions, path) {
118
+ if (!path || !instructions?.items) return null;
119
+ const indices = path.split(".").map(Number);
120
+ if (indices.some(isNaN)) return null;
121
+ let current = instructions.items[indices[0]] || null;
122
+ for (let i = 1; i < indices.length && current; i++) {
123
+ current = current.children?.[indices[i]] || null;
124
+ }
125
+ return current;
126
+ }
127
+ function generatePath(indices) {
128
+ return indices.join(".");
129
+ }
130
+
131
+ // src/durationUtils.ts
132
+ var DEFAULT_DURATION_CONFIG = {
133
+ secondsPerImage: 15,
134
+ wordsPerMinute: 150
135
+ };
136
+ function countWords(text) {
137
+ if (!text || !text.trim()) return 0;
138
+ return text.trim().split(/\s+/).length;
139
+ }
140
+ function estimateImageDuration(config = {}) {
141
+ return config.secondsPerImage ?? DEFAULT_DURATION_CONFIG.secondsPerImage;
142
+ }
143
+ function estimateTextDuration(text, config = {}) {
144
+ const words = countWords(text);
145
+ const wpm = config.wordsPerMinute ?? DEFAULT_DURATION_CONFIG.wordsPerMinute;
146
+ return Math.ceil(words / wpm * 60);
147
+ }
148
+ function estimateDuration(mediaType, options) {
149
+ const config = options?.config ?? {};
150
+ switch (mediaType) {
151
+ case "image":
152
+ return estimateImageDuration(config);
153
+ case "text":
154
+ if (options?.wordCount) {
155
+ const wpm = config.wordsPerMinute ?? DEFAULT_DURATION_CONFIG.wordsPerMinute;
156
+ return Math.ceil(options.wordCount / wpm * 60);
157
+ }
158
+ if (options?.text) {
159
+ return estimateTextDuration(options.text, config);
160
+ }
161
+ return 0;
162
+ case "video":
163
+ default:
164
+ return 0;
165
+ }
166
+ }
167
+
111
168
  // src/FormatConverters.ts
112
169
  var FormatConverters_exports = {};
113
170
  __export(FormatConverters_exports, {
114
- collapseInstructions: () => collapseInstructions,
115
171
  expandedInstructionsToPlaylist: () => expandedInstructionsToPlaylist,
116
172
  expandedInstructionsToPresentations: () => expandedInstructionsToPresentations,
117
173
  instructionsToPlaylist: () => instructionsToPlaylist,
@@ -120,7 +176,6 @@ __export(FormatConverters_exports, {
120
176
  playlistToInstructions: () => playlistToInstructions,
121
177
  playlistToPresentations: () => playlistToPresentations,
122
178
  presentationsToExpandedInstructions: () => presentationsToExpandedInstructions,
123
- presentationsToInstructions: () => presentationsToInstructions,
124
179
  presentationsToPlaylist: () => presentationsToPlaylist
125
180
  });
126
181
  function generateId() {
@@ -164,21 +219,15 @@ function presentationsToPlaylist(plan) {
164
219
  }
165
220
  return files;
166
221
  }
167
- function presentationsToInstructions(plan) {
168
- return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => {
169
- const totalSeconds = pres.files.reduce((sum, f) => sum + (f.providerData?.seconds || 0), 0);
170
- return { id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, seconds: totalSeconds || void 0, embedUrl: pres.files[0]?.embedUrl || pres.files[0]?.url };
171
- }) })) };
172
- }
173
222
  function presentationsToExpandedInstructions(plan) {
174
- return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => ({ id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, description: pres.actionType !== "other" ? pres.actionType : void 0, seconds: pres.files.reduce((sum, f) => sum + (f.providerData?.seconds || 0), 0) || void 0, children: pres.files.map((f) => ({ id: f.id, itemType: "file", label: f.title, seconds: f.providerData?.seconds || void 0, embedUrl: f.embedUrl || f.url })) })) })) };
223
+ return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => ({ id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, description: pres.actionType !== "other" ? pres.actionType : void 0, seconds: pres.files.reduce((sum, f) => sum + (f.seconds || 0), 0) || void 0, children: pres.files.map((f) => ({ id: f.id, itemType: "file", label: f.title, seconds: f.seconds, embedUrl: f.embedUrl || f.url })) })) })) };
175
224
  }
176
225
  function instructionsToPlaylist(instructions) {
177
226
  const files = [];
178
227
  function extractFiles(items) {
179
228
  for (const item of items) {
180
229
  if (item.embedUrl && (item.itemType === "file" || !item.children?.length)) {
181
- files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: detectMediaType(item.embedUrl), url: item.embedUrl, embedUrl: item.embedUrl, providerData: item.seconds ? { seconds: item.seconds } : void 0 });
230
+ files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: detectMediaType(item.embedUrl), url: item.embedUrl, embedUrl: item.embedUrl, seconds: item.seconds });
182
231
  }
183
232
  if (item.children) {
184
233
  extractFiles(item.children);
@@ -197,14 +246,14 @@ function instructionsToPresentations(instructions, planId) {
197
246
  if (presItem.children && presItem.children.length > 0) {
198
247
  for (const child of presItem.children) {
199
248
  if (child.embedUrl) {
200
- const file = { type: "file", id: child.id || child.relatedId || generateId(), title: child.label || "Untitled", mediaType: detectMediaType(child.embedUrl), url: child.embedUrl, embedUrl: child.embedUrl, providerData: child.seconds ? { seconds: child.seconds } : void 0 };
249
+ const file = { type: "file", id: child.id || child.relatedId || generateId(), title: child.label || "Untitled", mediaType: detectMediaType(child.embedUrl), url: child.embedUrl, embedUrl: child.embedUrl, seconds: child.seconds };
201
250
  allFiles.push(file);
202
251
  files.push(file);
203
252
  }
204
253
  }
205
254
  }
206
255
  if (files.length === 0 && presItem.embedUrl) {
207
- const file = { type: "file", id: presItem.id || presItem.relatedId || generateId(), title: presItem.label || "Untitled", mediaType: detectMediaType(presItem.embedUrl), url: presItem.embedUrl, embedUrl: presItem.embedUrl, providerData: presItem.seconds ? { seconds: presItem.seconds } : void 0 };
256
+ const file = { type: "file", id: presItem.id || presItem.relatedId || generateId(), title: presItem.label || "Untitled", mediaType: detectMediaType(presItem.embedUrl), url: presItem.embedUrl, embedUrl: presItem.embedUrl, seconds: presItem.seconds };
208
257
  allFiles.push(file);
209
258
  files.push(file);
210
259
  }
@@ -215,40 +264,12 @@ function instructionsToPresentations(instructions, planId) {
215
264
  return { id: planId || generateId(), name: instructions.venueName || "Plan", sections, allFiles };
216
265
  }
217
266
  var expandedInstructionsToPresentations = instructionsToPresentations;
218
- function collapseInstructions(instructions, maxDepth = 2) {
219
- function collapseItem(item, currentDepth) {
220
- if (currentDepth >= maxDepth || !item.children || item.children.length === 0) {
221
- const { children, ...rest } = item;
222
- if (children && children.length > 0) {
223
- const totalSeconds = children.reduce((sum, c) => sum + (c.seconds || 0), 0);
224
- if (totalSeconds > 0) {
225
- rest.seconds = totalSeconds;
226
- }
227
- if (!rest.embedUrl) {
228
- const firstWithUrl = children.find((c) => c.embedUrl);
229
- if (firstWithUrl) {
230
- rest.embedUrl = firstWithUrl.embedUrl;
231
- }
232
- }
233
- }
234
- return rest;
235
- }
236
- return {
237
- ...item,
238
- children: item.children.map((child) => collapseItem(child, currentDepth + 1))
239
- };
240
- }
241
- return {
242
- venueName: instructions.venueName,
243
- items: instructions.items.map((item) => collapseItem(item, 0))
244
- };
245
- }
246
267
  function playlistToPresentations(files, planName = "Playlist", sectionName = "Content") {
247
268
  const presentations = files.map((file, index) => ({ id: `pres-${index}-${file.id}`, name: file.title, actionType: "play", files: [file] }));
248
269
  return { id: "playlist-plan-" + generateId(), name: planName, sections: [{ id: "main-section", name: sectionName, presentations }], allFiles: [...files] };
249
270
  }
250
271
  function playlistToInstructions(files, venueName = "Playlist") {
251
- return { venueName, items: [{ id: "main-section", itemType: "section", label: "Content", children: files.map((file, index) => ({ id: file.id || `item-${index}`, itemType: "file", label: file.title, seconds: file.providerData?.seconds || void 0, embedUrl: file.embedUrl || file.url })) }] };
272
+ return { venueName, items: [{ id: "main-section", itemType: "section", label: "Content", children: files.map((file, index) => ({ id: file.id || `item-${index}`, itemType: "file", label: file.title, seconds: file.seconds, embedUrl: file.embedUrl || file.url })) }] };
252
273
  }
253
274
  var playlistToExpandedInstructions = playlistToInstructions;
254
275
 
@@ -276,14 +297,10 @@ var FormatResolver = class {
276
297
  const plan = await this.provider.getPresentations(path, auth);
277
298
  if (plan) return presentationsToPlaylist(plan);
278
299
  }
279
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
280
- const expanded = await this.provider.getExpandedInstructions(path, auth);
300
+ if (caps.instructions && this.provider.getInstructions) {
301
+ const expanded = await this.provider.getInstructions(path, auth);
281
302
  if (expanded) return instructionsToPlaylist(expanded);
282
303
  }
283
- if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
284
- const instructions = await this.provider.getInstructions(path, auth);
285
- if (instructions) return instructionsToPlaylist(instructions);
286
- }
287
304
  return null;
288
305
  }
289
306
  async getPlaylistWithMeta(path, auth) {
@@ -298,13 +315,9 @@ var FormatResolver = class {
298
315
  const plan = await this.provider.getPresentations(path, auth);
299
316
  if (plan) return { data: presentationsToPlaylist(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
300
317
  }
301
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
302
- const expanded = await this.provider.getExpandedInstructions(path, auth);
303
- if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
304
- }
305
- if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
306
- const instructions = await this.provider.getInstructions(path, auth);
307
- if (instructions) return { data: instructionsToPlaylist(instructions), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
318
+ if (caps.instructions && this.provider.getInstructions) {
319
+ const expanded = await this.provider.getInstructions(path, auth);
320
+ if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "instructions", isLossy: false } };
308
321
  }
309
322
  return { data: null, meta: { isNative: false, isLossy: false } };
310
323
  }
@@ -315,13 +328,9 @@ var FormatResolver = class {
315
328
  const result = await this.provider.getPresentations(path, auth);
316
329
  if (result) return result;
317
330
  }
318
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
319
- const expanded = await this.provider.getExpandedInstructions(path, auth);
320
- if (expanded) return instructionsToPresentations(expanded, fallbackId);
321
- }
322
331
  if (caps.instructions && this.provider.getInstructions) {
323
- const instructions = await this.provider.getInstructions(path, auth);
324
- if (instructions) return instructionsToPresentations(instructions, fallbackId);
332
+ const expanded = await this.provider.getInstructions(path, auth);
333
+ if (expanded) return instructionsToPresentations(expanded, fallbackId);
325
334
  }
326
335
  if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
327
336
  const playlist = await this.provider.getPlaylist(path, auth);
@@ -340,13 +349,9 @@ var FormatResolver = class {
340
349
  return { data: result, meta: { isNative: true, isLossy: false } };
341
350
  }
342
351
  }
343
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
344
- const expanded = await this.provider.getExpandedInstructions(path, auth);
345
- if (expanded) return { data: instructionsToPresentations(expanded, fallbackId), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
346
- }
347
352
  if (caps.instructions && this.provider.getInstructions) {
348
- const instructions = await this.provider.getInstructions(path, auth);
349
- if (instructions) return { data: instructionsToPresentations(instructions, fallbackId), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
353
+ const expanded = await this.provider.getInstructions(path, auth);
354
+ if (expanded) return { data: instructionsToPresentations(expanded, fallbackId), meta: { isNative: false, sourceFormat: "instructions", isLossy: false } };
350
355
  }
351
356
  if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
352
357
  const playlist = await this.provider.getPlaylist(path, auth);
@@ -361,13 +366,9 @@ var FormatResolver = class {
361
366
  const result = await this.provider.getInstructions(path, auth);
362
367
  if (result) return result;
363
368
  }
364
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
365
- const expanded = await this.provider.getExpandedInstructions(path, auth);
366
- if (expanded) return collapseInstructions(expanded);
367
- }
368
369
  if (caps.presentations) {
369
370
  const plan = await this.provider.getPresentations(path, auth);
370
- if (plan) return presentationsToInstructions(plan);
371
+ if (plan) return presentationsToExpandedInstructions(plan);
371
372
  }
372
373
  if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
373
374
  const playlist = await this.provider.getPlaylist(path, auth);
@@ -386,60 +387,10 @@ var FormatResolver = class {
386
387
  return { data: result, meta: { isNative: true, isLossy: false } };
387
388
  }
388
389
  }
389
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
390
- const expanded = await this.provider.getExpandedInstructions(path, auth);
391
- if (expanded) return { data: collapseInstructions(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: true } };
392
- }
393
- if (caps.presentations) {
394
- const plan = await this.provider.getPresentations(path, auth);
395
- if (plan) return { data: presentationsToInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
396
- }
397
- if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
398
- const playlist = await this.provider.getPlaylist(path, auth);
399
- if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
400
- }
401
- return { data: null, meta: { isNative: false, isLossy: false } };
402
- }
403
- async getExpandedInstructions(path, auth) {
404
- const caps = this.provider.capabilities;
405
- const fallbackTitle = this.getIdFromPath(path);
406
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
407
- const result = await this.provider.getExpandedInstructions(path, auth);
408
- if (result) return result;
409
- }
410
- if (caps.presentations) {
411
- const plan = await this.provider.getPresentations(path, auth);
412
- if (plan) return presentationsToExpandedInstructions(plan);
413
- }
414
- if (caps.instructions && this.provider.getInstructions) {
415
- const instructions = await this.provider.getInstructions(path, auth);
416
- if (instructions) return instructions;
417
- }
418
- if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
419
- const playlist = await this.provider.getPlaylist(path, auth);
420
- if (playlist && playlist.length > 0) {
421
- return playlistToInstructions(playlist, fallbackTitle);
422
- }
423
- }
424
- return null;
425
- }
426
- async getExpandedInstructionsWithMeta(path, auth) {
427
- const caps = this.provider.capabilities;
428
- const fallbackTitle = this.getIdFromPath(path);
429
- if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
430
- const result = await this.provider.getExpandedInstructions(path, auth);
431
- if (result) {
432
- return { data: result, meta: { isNative: true, isLossy: false } };
433
- }
434
- }
435
390
  if (caps.presentations) {
436
391
  const plan = await this.provider.getPresentations(path, auth);
437
392
  if (plan) return { data: presentationsToExpandedInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
438
393
  }
439
- if (caps.instructions && this.provider.getInstructions) {
440
- const instructions = await this.provider.getInstructions(path, auth);
441
- if (instructions) return { data: instructions, meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
442
- }
443
394
  if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
444
395
  const playlist = await this.provider.getPlaylist(path, auth);
445
396
  if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
@@ -496,22 +447,13 @@ var OAuthHelper = class {
496
447
  code_verifier: codeVerifier
497
448
  });
498
449
  const tokenUrl = `${config.oauthBase}/token`;
499
- console.log(`${providerId} token exchange request to: ${tokenUrl}`);
500
- console.log(` - client_id: ${config.clientId}`);
501
- console.log(` - redirect_uri: ${redirectUri}`);
502
- console.log(` - code: ${code.substring(0, 10)}...`);
503
450
  const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
504
- console.log(`${providerId} token response status: ${response.status}`);
505
451
  if (!response.ok) {
506
- const errorText = await response.text();
507
- console.error(`${providerId} token exchange failed: ${response.status} - ${errorText}`);
508
452
  return null;
509
453
  }
510
454
  const data = await response.json();
511
- console.log(`${providerId} token exchange successful, got access_token: ${!!data.access_token}`);
512
455
  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(" ") };
513
- } catch (error) {
514
- console.error(`${providerId} token exchange error:`, error);
456
+ } catch {
515
457
  return null;
516
458
  }
517
459
  }
@@ -596,25 +538,19 @@ var ApiHelper = class {
596
538
  if (!auth) return null;
597
539
  return { Authorization: `Bearer ${auth.access_token}`, Accept: "application/json" };
598
540
  }
599
- async apiRequest(config, providerId, path, auth, method = "GET", body) {
541
+ async apiRequest(config, _providerId, path, auth, method = "GET", body) {
600
542
  try {
601
543
  const url = `${config.apiBase}${path}`;
602
544
  const headers = { Accept: "application/json" };
603
545
  if (auth) headers["Authorization"] = `Bearer ${auth.access_token}`;
604
546
  if (body) headers["Content-Type"] = "application/json";
605
- console.log(`${providerId} API request: ${method} ${url}`);
606
- console.log(`${providerId} API auth present: ${!!auth}`);
607
547
  const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
608
548
  const response = await fetch(url, options);
609
- console.log(`${providerId} API response status: ${response.status}`);
610
549
  if (!response.ok) {
611
- const errorText = await response.text();
612
- console.error(`${providerId} API request failed: ${response.status} - ${errorText}`);
613
550
  return null;
614
551
  }
615
552
  return await response.json();
616
- } catch (error) {
617
- console.error(`${providerId} API request error:`, error);
553
+ } catch {
618
554
  return null;
619
555
  }
620
556
  }
@@ -637,18 +573,6 @@ var ContentProvider = class {
637
573
  return null;
638
574
  }
639
575
  async getInstructions(path, auth) {
640
- const caps = this.getCapabilities();
641
- if (caps.expandedInstructions) {
642
- const expanded = await this.getExpandedInstructions(path, auth);
643
- if (expanded) return collapseInstructions(expanded);
644
- }
645
- if (caps.presentations) {
646
- const plan = await this.getPresentations(path, auth);
647
- if (plan) return presentationsToInstructions(plan);
648
- }
649
- return null;
650
- }
651
- async getExpandedInstructions(path, auth) {
652
576
  const caps = this.getCapabilities();
653
577
  if (caps.presentations) {
654
578
  const plan = await this.getPresentations(path, auth);
@@ -660,7 +584,7 @@ var ContentProvider = class {
660
584
  return !!this.config.clientId;
661
585
  }
662
586
  getCapabilities() {
663
- return { browse: true, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
587
+ return { browse: true, presentations: false, playlist: false, instructions: false, mediaLicensing: false };
664
588
  }
665
589
  checkMediaLicense(_mediaId, _auth) {
666
590
  return Promise.resolve(null);
@@ -715,11 +639,11 @@ var ContentProvider = class {
715
639
  return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
716
640
  }
717
641
  // Content factories
718
- createFolder(id, title, path, image, providerData, isLeaf) {
719
- return { type: "folder", id, title, path, image, isLeaf, providerData };
642
+ createFolder(id, title, path, image, isLeaf) {
643
+ return { type: "folder", id, title, path, image, isLeaf };
720
644
  }
721
645
  createFile(id, title, url, options) {
722
- return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
646
+ return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, seconds: options?.seconds, loop: options?.loop, loopVideo: options?.loopVideo, streamUrl: options?.streamUrl };
723
647
  }
724
648
  };
725
649
 
@@ -733,14 +657,13 @@ var APlayProvider = class {
733
657
  this.config = { id: "aplay", name: "APlay", apiBase: "https://api-prod.amazingkids.app", 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` } };
734
658
  this.requiresAuth = true;
735
659
  this.authTypes = ["oauth_pkce"];
736
- this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: true };
660
+ this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: true };
737
661
  }
738
662
  async apiRequest(path, auth) {
739
663
  return this.apiHelper.apiRequest(this.config, this.id, path, auth);
740
664
  }
741
665
  async browse(path, auth) {
742
666
  const { segments, depth } = parsePath(path);
743
- console.log("APlay browse called with path:", path, "depth:", depth);
744
667
  if (depth === 0) {
745
668
  return [{
746
669
  type: "folder",
@@ -773,12 +696,9 @@ var APlayProvider = class {
773
696
  return [];
774
697
  }
775
698
  async getModules(auth) {
776
- console.log(`APlay fetching modules from: ${this.config.endpoints.modules}`);
777
699
  const response = await this.apiRequest(this.config.endpoints.modules, auth);
778
- console.log("APlay modules response:", response ? "received" : "null");
779
700
  if (!response) return [];
780
701
  const modules = response.data || response.modules || response;
781
- console.log("APlay modules count:", Array.isArray(modules) ? modules.length : "not an array");
782
702
  if (!Array.isArray(modules)) return [];
783
703
  const items = [];
784
704
  for (const m of modules) {
@@ -786,77 +706,46 @@ var APlayProvider = class {
786
706
  const moduleId = m.id || m.moduleId;
787
707
  const moduleTitle = m.title || m.name;
788
708
  const moduleImage = m.image;
789
- const allProducts = m.products || [];
790
- const products = allProducts.filter((p) => !p.isHidden);
791
- if (products.length === 0) {
792
- items.push({
793
- type: "folder",
794
- id: moduleId,
795
- title: moduleTitle,
796
- image: moduleImage,
797
- path: `/modules/${moduleId}`,
798
- providerData: { productCount: 0 }
799
- });
800
- } else if (products.length === 1) {
801
- const product = products[0];
802
- items.push({
803
- type: "folder",
804
- id: product.productId || product.id,
805
- title: moduleTitle,
806
- image: moduleImage || product.image,
807
- path: `/modules/${moduleId}`,
808
- providerData: { productCount: 1, productId: product.productId || product.id }
809
- });
810
- } else {
811
- items.push({
812
- type: "folder",
813
- id: moduleId,
814
- title: moduleTitle,
815
- image: moduleImage,
816
- path: `/modules/${moduleId}`,
817
- providerData: {
818
- productCount: products.length,
819
- products: products.map((p) => ({
820
- id: p.productId || p.id,
821
- title: p.title || p.name,
822
- image: p.image
823
- }))
824
- }
825
- });
826
- }
709
+ items.push({
710
+ type: "folder",
711
+ id: moduleId,
712
+ title: moduleTitle,
713
+ image: moduleImage,
714
+ path: `/modules/${moduleId}`
715
+ });
827
716
  }
828
717
  return items;
829
718
  }
830
719
  async getModuleContent(moduleId, currentPath, auth) {
831
- const modules = await this.getModules(auth);
832
- const module2 = modules.find((m) => m.id === moduleId || m.providerData?.productId === moduleId);
720
+ const response = await this.apiRequest(this.config.endpoints.modules, auth);
721
+ if (!response) return [];
722
+ const modules = response.data || response.modules || response;
723
+ if (!Array.isArray(modules)) return [];
724
+ const module2 = modules.find((m) => (m.id || m.moduleId) === moduleId);
833
725
  if (!module2) return [];
834
- const providerData = module2.providerData;
835
- const productCount = providerData?.productCount || 0;
836
- if (productCount === 0 || productCount === 1) {
837
- const productId = providerData?.productId || moduleId;
726
+ const allProducts = module2.products || [];
727
+ const products = allProducts.filter((p) => !p.isHidden);
728
+ if (products.length === 0) {
729
+ return this.getLibraryFolders(moduleId, `${currentPath}/libraries`, auth);
730
+ } else if (products.length === 1) {
731
+ const productId = products[0].productId || products[0].id;
838
732
  return this.getLibraryFolders(productId, `${currentPath}/libraries`, auth);
839
733
  } else {
840
- const products = providerData?.products || [];
841
734
  return products.map((p) => ({
842
735
  type: "folder",
843
- id: p.id,
844
- title: p.title,
736
+ id: p.productId || p.id,
737
+ title: p.title || p.name,
845
738
  image: p.image,
846
- path: `${currentPath}/products/${p.id}`
739
+ path: `${currentPath}/products/${p.productId || p.id}`
847
740
  }));
848
741
  }
849
742
  }
850
743
  async getLibraryFolders(productId, currentPath, auth) {
851
- console.log("APlay getLibraryFolders called with productId:", productId);
852
744
  const pathFn = this.config.endpoints.productLibraries;
853
745
  const apiPath = pathFn(productId);
854
- console.log(`APlay fetching libraries from: ${apiPath}`);
855
746
  const response = await this.apiRequest(apiPath, auth);
856
- console.log("APlay libraries response:", response ? "received" : "null");
857
747
  if (!response) return [];
858
748
  const libraries = response.data || response.libraries || response;
859
- console.log("APlay libraries count:", Array.isArray(libraries) ? libraries.length : "not an array");
860
749
  if (!Array.isArray(libraries)) return [];
861
750
  return libraries.map((l) => ({
862
751
  type: "folder",
@@ -868,12 +757,9 @@ var APlayProvider = class {
868
757
  }));
869
758
  }
870
759
  async getMediaFiles(libraryId, auth) {
871
- console.log("APlay getMediaFiles called with libraryId:", libraryId);
872
760
  const pathFn = this.config.endpoints.libraryMedia;
873
761
  const apiPath = pathFn(libraryId);
874
- console.log(`APlay fetching media from: ${apiPath}`);
875
762
  const response = await this.apiRequest(apiPath, auth);
876
- console.log("APlay media response:", response ? "received" : "null");
877
763
  if (!response) return [];
878
764
  const mediaItems = response.data || response.media || response;
879
765
  if (!Array.isArray(mediaItems)) return [];
@@ -924,6 +810,49 @@ var APlayProvider = class {
924
810
  const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
925
811
  return { id: libraryId, name: title, sections: [{ id: `section-${libraryId}`, name: title, presentations }], allFiles: files };
926
812
  }
813
+ async getPlaylist(path, auth, _resolution) {
814
+ const { segments, depth } = parsePath(path);
815
+ if (depth < 4 || segments[0] !== "modules") return null;
816
+ let libraryId;
817
+ if (segments[2] === "products" && depth === 5) {
818
+ libraryId = segments[4];
819
+ } else if (segments[2] === "libraries" && depth === 4) {
820
+ libraryId = segments[3];
821
+ } else {
822
+ return null;
823
+ }
824
+ const files = await this.getMediaFiles(libraryId, auth);
825
+ return files.length > 0 ? files : null;
826
+ }
827
+ async getInstructions(path, auth) {
828
+ const { segments, depth } = parsePath(path);
829
+ if (depth < 4 || segments[0] !== "modules") return null;
830
+ let libraryId;
831
+ if (segments[2] === "products" && depth === 5) {
832
+ libraryId = segments[4];
833
+ } else if (segments[2] === "libraries" && depth === 4) {
834
+ libraryId = segments[3];
835
+ } else {
836
+ return null;
837
+ }
838
+ const files = await this.getMediaFiles(libraryId, auth);
839
+ if (files.length === 0) return null;
840
+ const fileItems = files.map((file) => ({
841
+ id: file.id,
842
+ itemType: "file",
843
+ label: file.title,
844
+ embedUrl: file.url
845
+ }));
846
+ return {
847
+ venueName: "Library",
848
+ items: [{
849
+ id: `section-${libraryId}`,
850
+ itemType: "section",
851
+ label: "Content",
852
+ children: fileItems
853
+ }]
854
+ };
855
+ }
927
856
  async checkMediaLicense(mediaId, auth) {
928
857
  if (!auth) return null;
929
858
  try {
@@ -953,7 +882,7 @@ var SignPresenterProvider = class {
953
882
  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` } };
954
883
  this.requiresAuth = true;
955
884
  this.authTypes = ["oauth_pkce", "device_flow"];
956
- this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
885
+ this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
957
886
  }
958
887
  async apiRequest(path, auth) {
959
888
  return this.apiHelper.apiRequest(this.config, this.id, path, auth);
@@ -1004,7 +933,7 @@ var SignPresenterProvider = class {
1004
933
  if (!msg.url) continue;
1005
934
  const url = msg.url;
1006
935
  const seconds = msg.seconds;
1007
- files.push({ type: "file", id: msg.id, title: msg.name, mediaType: detectMediaType(url, msg.mediaType), image: msg.thumbnail || msg.image, url, embedUrl: url, providerData: seconds !== void 0 ? { seconds } : void 0 });
936
+ files.push({ type: "file", id: msg.id, title: msg.name, mediaType: detectMediaType(url, msg.mediaType), image: msg.thumbnail || msg.image, url, embedUrl: url, seconds });
1008
937
  }
1009
938
  return files;
1010
939
  }
@@ -1021,6 +950,39 @@ var SignPresenterProvider = class {
1021
950
  const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
1022
951
  return { id: playlistId, name: title, image, sections: [{ id: `section-${playlistId}`, name: title, presentations }], allFiles: files };
1023
952
  }
953
+ async getPlaylist(path, auth, _resolution) {
954
+ const { segments, depth } = parsePath(path);
955
+ if (depth < 2 || segments[0] !== "playlists") return null;
956
+ const playlistId = segments[1];
957
+ const files = await this.getMessages(playlistId, auth);
958
+ return files.length > 0 ? files : null;
959
+ }
960
+ async getInstructions(path, auth) {
961
+ const { segments, depth } = parsePath(path);
962
+ if (depth < 2 || segments[0] !== "playlists") return null;
963
+ const playlistId = segments[1];
964
+ const files = await this.getMessages(playlistId, auth);
965
+ if (files.length === 0) return null;
966
+ const playlists = await this.getPlaylists(auth);
967
+ const playlist = playlists.find((p) => p.id === playlistId);
968
+ const title = playlist?.title || "Playlist";
969
+ const fileItems = files.map((file) => ({
970
+ id: file.id,
971
+ itemType: "file",
972
+ label: file.title,
973
+ seconds: file.seconds,
974
+ embedUrl: file.embedUrl || file.url
975
+ }));
976
+ return {
977
+ venueName: title,
978
+ items: [{
979
+ id: `section-${playlistId}`,
980
+ itemType: "section",
981
+ label: title,
982
+ children: fileItems
983
+ }]
984
+ };
985
+ }
1024
986
  };
1025
987
 
1026
988
  // src/providers/lessonsChurch/LessonsChurchProvider.ts
@@ -1032,7 +994,7 @@ var LessonsChurchProvider = class {
1032
994
  this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: "https://api.lessons.church", 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}` } };
1033
995
  this.requiresAuth = false;
1034
996
  this.authTypes = ["none"];
1035
- this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
997
+ this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
1036
998
  }
1037
999
  async getPlaylist(path, _auth, resolution) {
1038
1000
  const venueId = getSegment(path, 4);
@@ -1051,7 +1013,7 @@ var LessonsChurchProvider = class {
1051
1013
  if (!f.url) continue;
1052
1014
  const url = f.url;
1053
1015
  const fileId = f.id || `playlist-${fileIndex++}`;
1054
- files.push({ type: "file", id: fileId, title: f.name || msg.name, mediaType: detectMediaType(url, f.fileType), image: response.lessonImage, url, providerData: { seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo } });
1016
+ files.push({ type: "file", id: fileId, title: f.name || msg.name, mediaType: detectMediaType(url, f.fileType), image: response.lessonImage, url, seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo });
1055
1017
  }
1056
1018
  }
1057
1019
  return files;
@@ -1068,7 +1030,6 @@ var LessonsChurchProvider = class {
1068
1030
  }
1069
1031
  async browse(path, _auth) {
1070
1032
  const { segments, depth } = parsePath(path);
1071
- console.log("[LessonsChurchProvider.browse] path:", path, "depth:", depth, "segments:", segments);
1072
1033
  if (depth === 0) {
1073
1034
  return [
1074
1035
  { type: "folder", id: "lessons-root", title: "Lessons", path: "/lessons" },
@@ -1125,9 +1086,7 @@ var LessonsChurchProvider = class {
1125
1086
  id: l.id,
1126
1087
  title: l.name || l.title,
1127
1088
  image: l.image,
1128
- path: `${currentPath}/${l.id}`,
1129
- providerData: { lessonImage: l.image }
1130
- // Keep for display on venues
1089
+ path: `${currentPath}/${l.id}`
1131
1090
  }));
1132
1091
  }
1133
1092
  async getVenues(lessonId, currentPath) {
@@ -1137,7 +1096,7 @@ var LessonsChurchProvider = class {
1137
1096
  const lessonResponse = await this.apiRequest(`/lessons/public/${lessonId}`);
1138
1097
  const lessonImage = lessonResponse?.image;
1139
1098
  const venues = Array.isArray(response) ? response : [];
1140
- const result = venues.map((v) => ({
1099
+ return venues.map((v) => ({
1141
1100
  type: "folder",
1142
1101
  id: v.id,
1143
1102
  title: v.name,
@@ -1145,8 +1104,6 @@ var LessonsChurchProvider = class {
1145
1104
  isLeaf: true,
1146
1105
  path: `${currentPath}/${v.id}`
1147
1106
  }));
1148
- console.log("[LessonsChurchProvider.getVenues] returning:", result.map((r) => ({ id: r.id, title: r.title, isLeaf: r.isLeaf })));
1149
- return result;
1150
1107
  }
1151
1108
  async getPlaylistFiles(venueId) {
1152
1109
  const files = await this.getPlaylist(`/lessons/_/_/_/${venueId}`, null);
@@ -1205,7 +1162,7 @@ var LessonsChurchProvider = class {
1205
1162
  } else {
1206
1163
  return null;
1207
1164
  }
1208
- return { type: "file", id: addOn.id, title: addOn.name, mediaType, image: addOn.image, url, embedUrl: `https://lessons.church/embed/addon/${addOn.id}`, providerData: { seconds, loopVideo: video?.loopVideo || false } };
1165
+ return { type: "file", id: addOn.id, title: addOn.name, mediaType, image: addOn.image, url, embedUrl: `https://lessons.church/embed/addon/${addOn.id}`, seconds, loopVideo: video?.loopVideo || false };
1209
1166
  }
1210
1167
  async getPresentations(path, _auth) {
1211
1168
  const venueId = getSegment(path, 4);
@@ -1216,18 +1173,6 @@ var LessonsChurchProvider = class {
1216
1173
  return this.convertVenueToPlan(venueData);
1217
1174
  }
1218
1175
  async getInstructions(path, _auth) {
1219
- const venueId = getSegment(path, 4);
1220
- if (!venueId) return null;
1221
- const response = await this.apiRequest(`/venues/public/planItems/${venueId}`);
1222
- if (!response) return null;
1223
- const processItem = (item) => {
1224
- const itemType = this.normalizeItemType(item.itemType);
1225
- const relatedId = item.relatedId;
1226
- return { id: item.id, itemType, relatedId, label: item.label, description: item.description, seconds: item.seconds, children: item.children?.map(processItem), embedUrl: this.getEmbedUrl(itemType, relatedId) };
1227
- };
1228
- return { venueName: response.venueName, items: (response.items || []).map(processItem) };
1229
- }
1230
- async getExpandedInstructions(path, _auth) {
1231
1176
  const venueId = getSegment(path, 4);
1232
1177
  if (!venueId) return null;
1233
1178
  const [planItemsResponse, actionsResponse] = await Promise.all([
@@ -1241,7 +1186,8 @@ var LessonsChurchProvider = class {
1241
1186
  if (section.id && section.actions) {
1242
1187
  sectionActionsMap.set(section.id, section.actions.map((action) => {
1243
1188
  const embedUrl = this.getEmbedUrl("action", action.id);
1244
- return { id: action.id, itemType: "action", relatedId: action.id, label: action.name, description: action.actionType, seconds: action.seconds, children: [{ id: action.id + "-file", itemType: "file", label: action.name, seconds: action.seconds, embedUrl }] };
1189
+ const seconds = action.seconds ?? estimateImageDuration();
1190
+ return { id: action.id, itemType: "action", relatedId: action.id, label: action.name, description: action.actionType, seconds, children: [{ id: action.id + "-file", itemType: "file", label: action.name, seconds, embedUrl }] };
1245
1191
  }));
1246
1192
  }
1247
1193
  }
@@ -1297,7 +1243,7 @@ var LessonsChurchProvider = class {
1297
1243
  for (const file of action.files || []) {
1298
1244
  if (!file.url) continue;
1299
1245
  const embedUrl = action.id ? `https://lessons.church/embed/action/${action.id}` : void 0;
1300
- const contentFile = { type: "file", id: file.id || "", title: file.name || "", mediaType: detectMediaType(file.url, file.fileType), image: venue.lessonImage, url: file.url, embedUrl, providerData: { seconds: file.seconds, streamUrl: file.streamUrl } };
1246
+ const contentFile = { type: "file", id: file.id || "", title: file.name || "", mediaType: detectMediaType(file.url, file.fileType), image: venue.lessonImage, url: file.url, embedUrl, seconds: file.seconds, streamUrl: file.streamUrl };
1301
1247
  files.push(contentFile);
1302
1248
  allFiles.push(contentFile);
1303
1249
  }
@@ -1344,22 +1290,13 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
1344
1290
  try {
1345
1291
  const params = { grant_type: "authorization_code", code, client_id: config.clientId, code_verifier: codeVerifier, redirect_uri: redirectUri };
1346
1292
  const tokenUrl = `${config.oauthBase}/token`;
1347
- console.log(`B1Church PKCE token exchange request to: ${tokenUrl}`);
1348
- console.log(` - client_id: ${config.clientId}`);
1349
- console.log(` - redirect_uri: ${redirectUri}`);
1350
- console.log(` - code: ${code.substring(0, 10)}...`);
1351
1293
  const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1352
- console.log(`B1Church token response status: ${response.status}`);
1353
1294
  if (!response.ok) {
1354
- const errorText = await response.text();
1355
- console.error(`B1Church token exchange failed: ${response.status} - ${errorText}`);
1356
1295
  return null;
1357
1296
  }
1358
1297
  const data = await response.json();
1359
- console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
1360
1298
  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(" ") };
1361
- } catch (error) {
1362
- console.error("B1Church token exchange error:", error);
1299
+ } catch {
1363
1300
  return null;
1364
1301
  }
1365
1302
  }
@@ -1367,22 +1304,13 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
1367
1304
  try {
1368
1305
  const params = { grant_type: "authorization_code", code, client_id: config.clientId, client_secret: clientSecret, redirect_uri: redirectUri };
1369
1306
  const tokenUrl = `${config.oauthBase}/token`;
1370
- console.log(`B1Church token exchange request to: ${tokenUrl}`);
1371
- console.log(` - client_id: ${config.clientId}`);
1372
- console.log(` - redirect_uri: ${redirectUri}`);
1373
- console.log(` - code: ${code.substring(0, 10)}...`);
1374
1307
  const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
1375
- console.log(`B1Church token response status: ${response.status}`);
1376
1308
  if (!response.ok) {
1377
- const errorText = await response.text();
1378
- console.error(`B1Church token exchange failed: ${response.status} - ${errorText}`);
1379
1309
  return null;
1380
1310
  }
1381
1311
  const data = await response.json();
1382
- console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
1383
1312
  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(" ") };
1384
- } catch (error) {
1385
- console.error("B1Church token exchange error:", error);
1313
+ } catch {
1386
1314
  return null;
1387
1315
  }
1388
1316
  }
@@ -1403,13 +1331,10 @@ async function initiateDeviceFlow(config) {
1403
1331
  try {
1404
1332
  const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: config.clientId, scope: config.scopes.join(" ") }) });
1405
1333
  if (!response.ok) {
1406
- const errorText = await response.text();
1407
- console.error(`B1Church device authorize failed: ${response.status} - ${errorText}`);
1408
1334
  return null;
1409
1335
  }
1410
1336
  return await response.json();
1411
- } catch (error) {
1412
- console.error("B1Church device flow initiation error:", error);
1337
+ } catch {
1413
1338
  return null;
1414
1339
  }
1415
1340
  }
@@ -1513,13 +1438,13 @@ async function fetchFromProviderProxy(method, ministryId, providerId, path, auth
1513
1438
 
1514
1439
  // src/providers/b1Church/converters.ts
1515
1440
  function ministryToFolder(ministry) {
1516
- return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl, providerData: { level: "ministry", ministryId: ministry.id, churchId: ministry.churchId } };
1441
+ return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl };
1517
1442
  }
1518
- function planTypeToFolder(planType, ministryId) {
1519
- return { type: "folder", id: planType.id, title: planType.name, path: "", providerData: { level: "planType", planTypeId: planType.id, ministryId, churchId: planType.churchId } };
1443
+ function planTypeToFolder(planType) {
1444
+ return { type: "folder", id: planType.id, title: planType.name, path: "" };
1520
1445
  }
1521
1446
  function planToFolder(plan) {
1522
- return { type: "folder", id: plan.id, title: plan.name, path: "", isLeaf: true, providerData: { level: "plan", planId: plan.id, planTypeId: plan.planTypeId, ministryId: plan.ministryId, churchId: plan.churchId, serviceDate: plan.serviceDate, contentType: plan.contentType, contentId: plan.contentId } };
1447
+ return { type: "folder", id: plan.id, title: plan.name, path: "", isLeaf: true };
1523
1448
  }
1524
1449
  async function planItemToPresentation(item, venueFeed) {
1525
1450
  const itemType = item.itemType;
@@ -1568,7 +1493,7 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
1568
1493
  return files;
1569
1494
  }
1570
1495
  function convertFeedFiles(feedFiles, thumbnailImage) {
1571
- return feedFiles.filter((f) => f.url).map((f) => ({ type: "file", id: f.id || "", title: f.name || "", mediaType: detectMediaType(f.url || "", f.fileType), image: thumbnailImage, url: f.url || "", providerData: { seconds: f.seconds, streamUrl: f.streamUrl } }));
1496
+ return feedFiles.filter((f) => f.url).map((f) => ({ type: "file", id: f.id || "", title: f.name || "", mediaType: detectMediaType(f.url || "", f.fileType), image: thumbnailImage, url: f.url || "", seconds: f.seconds, streamUrl: f.streamUrl }));
1572
1497
  }
1573
1498
  function planItemToInstruction(item) {
1574
1499
  let itemType = item.itemType;
@@ -1587,9 +1512,9 @@ function planItemToInstruction(item) {
1587
1512
  }
1588
1513
 
1589
1514
  // src/providers/b1Church/B1ChurchProvider.ts
1590
- var INTERNAL_PROVIDERS = ["b1church", "lessonschurch"];
1591
1515
  function isExternalProviderItem(item) {
1592
- if (!item.providerId || INTERNAL_PROVIDERS.includes(item.providerId)) return false;
1516
+ if (!item.providerId || item.providerId === "b1church") return false;
1517
+ if (item.providerPath) return true;
1593
1518
  const itemType = item.itemType || "";
1594
1519
  return itemType.startsWith("provider");
1595
1520
  }
@@ -1599,11 +1524,11 @@ var B1ChurchProvider = class {
1599
1524
  this.id = "b1church";
1600
1525
  this.name = "B1.Church";
1601
1526
  this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
1602
- this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE}/doing`, oauthBase: `${API_BASE}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { planItems: (churchId, planId) => `/planItems/presenter/${churchId}/${planId}` } };
1527
+ this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE}/doing`, oauthBase: `${API_BASE}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { planItems: (churchId, planId) => `/planFeed/presenter/${churchId}/${planId}` } };
1603
1528
  this.appBase = "https://admin.b1.church";
1604
1529
  this.requiresAuth = true;
1605
1530
  this.authTypes = ["oauth_pkce", "device_flow"];
1606
- this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
1531
+ this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
1607
1532
  }
1608
1533
  async apiRequest(path, authData) {
1609
1534
  return this.apiHelper.apiRequest(this.config, this.id, path, authData);
@@ -1642,17 +1567,15 @@ var B1ChurchProvider = class {
1642
1567
  const ministries = await fetchMinistries(authData);
1643
1568
  return ministries.map((m) => {
1644
1569
  const folder = ministryToFolder(m);
1645
- const ministryId = folder.providerData?.ministryId || folder.id;
1646
- return { ...folder, path: `/ministries/${ministryId}` };
1570
+ return { ...folder, path: `/ministries/${m.id}` };
1647
1571
  });
1648
1572
  }
1649
1573
  if (depth === 2) {
1650
1574
  const ministryId = segments[1];
1651
1575
  const planTypes = await fetchPlanTypes(ministryId, authData);
1652
1576
  return planTypes.map((pt) => {
1653
- const folder = planTypeToFolder(pt, ministryId);
1654
- const planTypeId = folder.providerData?.planTypeId || folder.id;
1655
- return { ...folder, path: `/ministries/${ministryId}/${planTypeId}` };
1577
+ const folder = planTypeToFolder(pt);
1578
+ return { ...folder, path: `/ministries/${ministryId}/${pt.id}` };
1656
1579
  });
1657
1580
  }
1658
1581
  if (depth === 3) {
@@ -1661,11 +1584,10 @@ var B1ChurchProvider = class {
1661
1584
  const plans = await fetchPlans(planTypeId, authData);
1662
1585
  return plans.map((p) => {
1663
1586
  const folder = planToFolder(p);
1664
- const planId = folder.providerData?.planId || folder.id;
1665
1587
  return {
1666
1588
  ...folder,
1667
1589
  isLeaf: true,
1668
- path: `/ministries/${ministryId}/${planTypeId}/${planId}`
1590
+ path: `/ministries/${ministryId}/${planTypeId}/${p.id}`
1669
1591
  };
1670
1592
  });
1671
1593
  }
@@ -1678,19 +1600,26 @@ var B1ChurchProvider = class {
1678
1600
  const planId = segments[3];
1679
1601
  const planTypeId = segments[2];
1680
1602
  const plans = await fetchPlans(planTypeId, authData);
1681
- const planFolder = plans.find((p) => {
1682
- const folder2 = planToFolder(p);
1683
- return folder2.providerData?.planId === planId || folder2.id === planId;
1684
- });
1603
+ const planFolder = plans.find((p) => p.id === planId);
1685
1604
  if (!planFolder) return null;
1686
- const folder = planToFolder(planFolder);
1687
- const providerData = folder.providerData;
1688
- const churchId = providerData?.churchId;
1689
- const venueId = providerData?.contentId;
1690
- const planTitle = folder.title || "Plan";
1605
+ const churchId = planFolder.churchId;
1606
+ const venueId = planFolder.contentId;
1607
+ const planTitle = planFolder.name || "Plan";
1691
1608
  if (!churchId) return null;
1692
1609
  const pathFn = this.config.endpoints.planItems;
1693
1610
  const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1611
+ if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1612
+ const externalPlan = await fetchFromProviderProxy(
1613
+ "getPresentations",
1614
+ ministryId,
1615
+ planFolder.providerId,
1616
+ planFolder.providerPlanId,
1617
+ authData
1618
+ );
1619
+ if (externalPlan) {
1620
+ return { id: planId, name: planTitle, sections: externalPlan.sections, allFiles: externalPlan.allFiles };
1621
+ }
1622
+ }
1694
1623
  if (!planItems || !Array.isArray(planItems)) return null;
1695
1624
  const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
1696
1625
  const sections = [];
@@ -1707,10 +1636,25 @@ var B1ChurchProvider = class {
1707
1636
  authData
1708
1637
  );
1709
1638
  if (externalPlan) {
1710
- for (const section of externalPlan.sections) {
1711
- presentations.push(...section.presentations);
1639
+ if (child.providerContentPath) {
1640
+ const externalInstructions = await fetchFromProviderProxy(
1641
+ "getInstructions",
1642
+ ministryId,
1643
+ child.providerId,
1644
+ child.providerPath,
1645
+ authData
1646
+ );
1647
+ const matchingPresentation = this.findPresentationByPath(externalPlan, externalInstructions, child.providerContentPath);
1648
+ if (matchingPresentation) {
1649
+ presentations.push(matchingPresentation);
1650
+ allFiles.push(...matchingPresentation.files);
1651
+ }
1652
+ } else {
1653
+ for (const section of externalPlan.sections) {
1654
+ presentations.push(...section.presentations);
1655
+ }
1656
+ allFiles.push(...externalPlan.allFiles);
1712
1657
  }
1713
- allFiles.push(...externalPlan.allFiles);
1714
1658
  }
1715
1659
  } else {
1716
1660
  const presentation = await planItemToPresentation(child, venueFeed);
@@ -1733,49 +1677,74 @@ var B1ChurchProvider = class {
1733
1677
  const planId = segments[3];
1734
1678
  const planTypeId = segments[2];
1735
1679
  const plans = await fetchPlans(planTypeId, authData);
1736
- const planFolder = plans.find((p) => {
1737
- const folder2 = planToFolder(p);
1738
- return folder2.providerData?.planId === planId || folder2.id === planId;
1739
- });
1680
+ const planFolder = plans.find((p) => p.id === planId);
1740
1681
  if (!planFolder) return null;
1741
- const folder = planToFolder(planFolder);
1742
- const providerData = folder.providerData;
1743
- const churchId = providerData?.churchId;
1744
- const planTitle = folder.title || "Plan";
1682
+ const churchId = planFolder.churchId;
1683
+ const planTitle = planFolder.name || "Plan";
1745
1684
  if (!churchId) return null;
1746
1685
  const pathFn = this.config.endpoints.planItems;
1747
1686
  const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1687
+ if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1688
+ const externalInstructions = await fetchFromProviderProxy(
1689
+ "getInstructions",
1690
+ ministryId,
1691
+ planFolder.providerId,
1692
+ planFolder.providerPlanId,
1693
+ authData
1694
+ );
1695
+ if (externalInstructions) {
1696
+ return { venueName: planTitle, items: externalInstructions.items };
1697
+ }
1698
+ }
1748
1699
  if (!planItems || !Array.isArray(planItems)) return null;
1749
1700
  const processedItems = await this.processInstructionItems(planItems, ministryId, authData);
1750
1701
  return { venueName: planTitle, items: processedItems };
1751
1702
  }
1752
- async getExpandedInstructions(path, authData) {
1753
- return this.getInstructions(path, authData);
1754
- }
1755
1703
  async processInstructionItems(items, ministryId, authData) {
1756
1704
  const result = [];
1757
1705
  for (const item of items) {
1706
+ const instructionItem = planItemToInstruction(item);
1758
1707
  if (isExternalProviderItem(item) && item.providerId && item.providerPath) {
1759
1708
  const externalInstructions = await fetchFromProviderProxy(
1760
- "getExpandedInstructions",
1709
+ "getInstructions",
1761
1710
  ministryId,
1762
1711
  item.providerId,
1763
1712
  item.providerPath,
1764
1713
  authData
1765
1714
  );
1766
1715
  if (externalInstructions) {
1767
- result.push(...externalInstructions.items);
1768
- }
1769
- } else {
1770
- const instructionItem = planItemToInstruction(item);
1771
- if (item.children && item.children.length > 0) {
1772
- instructionItem.children = await this.processInstructionItems(item.children, ministryId, authData);
1716
+ if (item.providerContentPath) {
1717
+ const matchingItem = this.findItemByPath(externalInstructions, item.providerContentPath);
1718
+ if (matchingItem?.children) {
1719
+ instructionItem.children = matchingItem.children;
1720
+ }
1721
+ } else {
1722
+ instructionItem.children = externalInstructions.items;
1723
+ }
1773
1724
  }
1774
- result.push(instructionItem);
1725
+ } else if (item.children && item.children.length > 0) {
1726
+ instructionItem.children = await this.processInstructionItems(item.children, ministryId, authData);
1775
1727
  }
1728
+ result.push(instructionItem);
1776
1729
  }
1777
1730
  return result;
1778
1731
  }
1732
+ findItemByPath(instructions, path) {
1733
+ if (!path || !instructions) return null;
1734
+ return navigateToPath(instructions, path);
1735
+ }
1736
+ findPresentationByPath(plan, instructions, path) {
1737
+ if (!path || !instructions) return null;
1738
+ const item = navigateToPath(instructions, path);
1739
+ if (!item?.relatedId && !item?.id) return null;
1740
+ const presentationId = item.relatedId || item.id;
1741
+ for (const section of plan.sections) {
1742
+ for (const presentation of section.presentations) {
1743
+ if (presentation.id === presentationId) return presentation;
1744
+ }
1745
+ }
1746
+ return null;
1747
+ }
1779
1748
  async getPlaylist(path, authData, resolution) {
1780
1749
  const { segments, depth } = parsePath(path);
1781
1750
  if (depth < 4 || segments[0] !== "ministries") return [];
@@ -1783,34 +1752,63 @@ var B1ChurchProvider = class {
1783
1752
  const planId = segments[3];
1784
1753
  const planTypeId = segments[2];
1785
1754
  const plans = await fetchPlans(planTypeId, authData);
1786
- const planFolder = plans.find((p) => {
1787
- const folder2 = planToFolder(p);
1788
- return folder2.providerData?.planId === planId || folder2.id === planId;
1789
- });
1755
+ const planFolder = plans.find((p) => p.id === planId);
1790
1756
  if (!planFolder) return [];
1791
- const folder = planToFolder(planFolder);
1792
- const providerData = folder.providerData;
1793
- const churchId = providerData?.churchId;
1794
- const venueId = providerData?.contentId;
1757
+ const churchId = planFolder.churchId;
1758
+ const venueId = planFolder.contentId;
1795
1759
  if (!churchId) return [];
1796
1760
  const pathFn = this.config.endpoints.planItems;
1797
1761
  const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
1762
+ if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
1763
+ const externalFiles = await fetchFromProviderProxy(
1764
+ "getPlaylist",
1765
+ ministryId,
1766
+ planFolder.providerId,
1767
+ planFolder.providerPlanId,
1768
+ authData,
1769
+ resolution
1770
+ );
1771
+ return externalFiles || [];
1772
+ }
1798
1773
  if (!planItems || !Array.isArray(planItems)) return [];
1799
1774
  const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
1800
1775
  const files = [];
1801
1776
  for (const sectionItem of planItems) {
1802
1777
  for (const child of sectionItem.children || []) {
1803
1778
  if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
1804
- const externalFiles = await fetchFromProviderProxy(
1805
- "getPlaylist",
1806
- ministryId,
1807
- child.providerId,
1808
- child.providerPath,
1809
- authData,
1810
- resolution
1811
- );
1812
- if (externalFiles) {
1813
- files.push(...externalFiles);
1779
+ if (child.providerContentPath) {
1780
+ const externalPlan = await fetchFromProviderProxy(
1781
+ "getPresentations",
1782
+ ministryId,
1783
+ child.providerId,
1784
+ child.providerPath,
1785
+ authData
1786
+ );
1787
+ const externalInstructions = await fetchFromProviderProxy(
1788
+ "getInstructions",
1789
+ ministryId,
1790
+ child.providerId,
1791
+ child.providerPath,
1792
+ authData
1793
+ );
1794
+ if (externalPlan) {
1795
+ const matchingPresentation = this.findPresentationByPath(externalPlan, externalInstructions, child.providerContentPath);
1796
+ if (matchingPresentation) {
1797
+ files.push(...matchingPresentation.files);
1798
+ }
1799
+ }
1800
+ } else {
1801
+ const externalFiles = await fetchFromProviderProxy(
1802
+ "getPlaylist",
1803
+ ministryId,
1804
+ child.providerId,
1805
+ child.providerPath,
1806
+ authData,
1807
+ resolution
1808
+ );
1809
+ if (externalFiles) {
1810
+ files.push(...externalFiles);
1811
+ }
1814
1812
  }
1815
1813
  } else {
1816
1814
  const itemType = child.itemType;
@@ -1836,7 +1834,7 @@ var PlanningCenterProvider = class {
1836
1834
  this.ONE_WEEK_MS = 6048e5;
1837
1835
  this.requiresAuth = true;
1838
1836
  this.authTypes = ["oauth_pkce"];
1839
- this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
1837
+ this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
1840
1838
  }
1841
1839
  async apiRequest(path, auth) {
1842
1840
  return this.apiHelper.apiRequest(this.config, this.id, path, auth);
@@ -1898,8 +1896,7 @@ var PlanningCenterProvider = class {
1898
1896
  id: plan.id,
1899
1897
  title: plan.attributes.title || this.formatDate(plan.attributes.sort_date),
1900
1898
  isLeaf: true,
1901
- path: `${currentPath}/${plan.id}`,
1902
- providerData: { sortDate: plan.attributes.sort_date }
1899
+ path: `${currentPath}/${plan.id}`
1903
1900
  }));
1904
1901
  }
1905
1902
  async getPlanItems(serviceTypeId, planId, auth) {
@@ -1909,7 +1906,7 @@ var PlanningCenterProvider = class {
1909
1906
  auth
1910
1907
  );
1911
1908
  if (!response?.data) return [];
1912
- return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "", providerData: { itemType: item.attributes.item_type, description: item.attributes.description, length: item.attributes.length, songId: item.relationships?.song?.data?.id, arrangementId: item.relationships?.arrangement?.data?.id } }));
1909
+ return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
1913
1910
  }
1914
1911
  async getPresentations(path, auth) {
1915
1912
  const { segments, depth } = parsePath(path);
@@ -2017,6 +2014,43 @@ var PlanningCenterProvider = class {
2017
2014
  const date = new Date(dateString);
2018
2015
  return date.toISOString().slice(0, 10);
2019
2016
  }
2017
+ async getPlaylist(path, auth, _resolution) {
2018
+ const plan = await this.getPresentations(path, auth);
2019
+ if (!plan) return null;
2020
+ return plan.allFiles.length > 0 ? plan.allFiles : null;
2021
+ }
2022
+ async getInstructions(path, auth) {
2023
+ const plan = await this.getPresentations(path, auth);
2024
+ if (!plan) return null;
2025
+ const sectionItems = plan.sections.map((section) => {
2026
+ const actionItems = section.presentations.map((pres) => {
2027
+ const fileItems = pres.files.map((file) => ({
2028
+ id: file.id,
2029
+ itemType: "file",
2030
+ label: file.title,
2031
+ embedUrl: file.url
2032
+ }));
2033
+ return {
2034
+ id: pres.id,
2035
+ itemType: "action",
2036
+ relatedId: pres.id,
2037
+ label: pres.name,
2038
+ description: pres.actionType,
2039
+ children: fileItems.length > 0 ? fileItems : void 0
2040
+ };
2041
+ });
2042
+ return {
2043
+ id: section.id,
2044
+ itemType: "section",
2045
+ label: section.name,
2046
+ children: actionItems
2047
+ };
2048
+ });
2049
+ return {
2050
+ venueName: plan.name,
2051
+ items: sectionItems
2052
+ };
2053
+ }
2020
2054
  };
2021
2055
 
2022
2056
  // src/providers/bibleProject/data.json
@@ -4154,8 +4188,7 @@ var BibleProjectProvider = class {
4154
4188
  browse: true,
4155
4189
  presentations: true,
4156
4190
  playlist: true,
4157
- instructions: false,
4158
- expandedInstructions: false,
4191
+ instructions: true,
4159
4192
  mediaLicensing: false
4160
4193
  };
4161
4194
  }
@@ -4193,8 +4226,7 @@ var BibleProjectProvider = class {
4193
4226
  title: video.title,
4194
4227
  image: video.thumbnailUrl,
4195
4228
  isLeaf: true,
4196
- path: `${currentPath}/${video.id}`,
4197
- providerData: { videoData: video }
4229
+ path: `${currentPath}/${video.id}`
4198
4230
  }));
4199
4231
  }
4200
4232
  getVideoFile(collectionSlug, videoId) {
@@ -4245,6 +4277,50 @@ var BibleProjectProvider = class {
4245
4277
  }
4246
4278
  return null;
4247
4279
  }
4280
+ async getInstructions(path, _auth) {
4281
+ const { segments, depth } = parsePath(path);
4282
+ if (depth < 1) return null;
4283
+ const collectionSlug = segments[0];
4284
+ const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
4285
+ if (!collection) return null;
4286
+ if (depth === 1) {
4287
+ const fileItems = collection.videos.map((video) => ({
4288
+ id: video.id,
4289
+ itemType: "file",
4290
+ label: video.title,
4291
+ embedUrl: video.videoUrl
4292
+ }));
4293
+ return {
4294
+ venueName: collection.name,
4295
+ items: [{
4296
+ id: this.slugify(collection.name),
4297
+ itemType: "section",
4298
+ label: "Videos",
4299
+ children: fileItems
4300
+ }]
4301
+ };
4302
+ }
4303
+ if (depth === 2) {
4304
+ const videoId = segments[1];
4305
+ const video = collection.videos.find((v) => v.id === videoId);
4306
+ if (!video) return null;
4307
+ return {
4308
+ venueName: video.title,
4309
+ items: [{
4310
+ id: "main",
4311
+ itemType: "section",
4312
+ label: "Content",
4313
+ children: [{
4314
+ id: video.id,
4315
+ itemType: "file",
4316
+ label: video.title,
4317
+ embedUrl: video.videoUrl
4318
+ }]
4319
+ }]
4320
+ };
4321
+ }
4322
+ return null;
4323
+ }
4248
4324
  slugify(text) {
4249
4325
  return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
4250
4326
  }
@@ -11781,8 +11857,7 @@ var HighVoltageKidsProvider = class {
11781
11857
  browse: true,
11782
11858
  presentations: true,
11783
11859
  playlist: true,
11784
- instructions: false,
11785
- expandedInstructions: true,
11860
+ instructions: true,
11786
11861
  mediaLicensing: false
11787
11862
  };
11788
11863
  }
@@ -11824,8 +11899,7 @@ var HighVoltageKidsProvider = class {
11824
11899
  id: study.id,
11825
11900
  title: study.name,
11826
11901
  image: study.image || void 0,
11827
- path: `${currentPath}/${study.id}`,
11828
- providerData: { studyData: study }
11902
+ path: `${currentPath}/${study.id}`
11829
11903
  }));
11830
11904
  }
11831
11905
  getLessonFolders(collectionSlug, studyId, currentPath) {
@@ -11839,8 +11913,7 @@ var HighVoltageKidsProvider = class {
11839
11913
  title: lesson.name,
11840
11914
  image: lesson.image || void 0,
11841
11915
  isLeaf: true,
11842
- path: `${currentPath}/${lesson.id}`,
11843
- providerData: { lessonData: lesson, studyName: study.name }
11916
+ path: `${currentPath}/${lesson.id}`
11844
11917
  }));
11845
11918
  }
11846
11919
  getLessonFiles(collectionSlug, studyId, lessonId) {
@@ -11910,7 +11983,7 @@ var HighVoltageKidsProvider = class {
11910
11983
  }
11911
11984
  return null;
11912
11985
  }
11913
- async getExpandedInstructions(path, _auth) {
11986
+ async getInstructions(path, _auth) {
11914
11987
  const { segments, depth } = parsePath(path);
11915
11988
  if (depth < 2) return null;
11916
11989
  const collectionSlug = segments[0];
@@ -11921,7 +11994,10 @@ var HighVoltageKidsProvider = class {
11921
11994
  if (!study) return null;
11922
11995
  if (depth === 2) {
11923
11996
  const lessonItems = study.lessons.map((lesson) => {
11924
- const fileItems = lesson.files.map((file) => ({ id: file.id, itemType: "file", label: file.title, embedUrl: file.url }));
11997
+ const fileItems = lesson.files.map((file) => {
11998
+ const seconds = estimateDuration(file.mediaType);
11999
+ return { id: file.id, itemType: "file", label: file.title, seconds, embedUrl: file.url };
12000
+ });
11925
12001
  return { id: lesson.id, itemType: "action", label: lesson.name, description: "play", children: fileItems };
11926
12002
  });
11927
12003
  return { venueName: study.name, items: [{ id: study.id, itemType: "header", label: study.name, children: [{ id: "main", itemType: "section", label: "Content", children: lessonItems }] }] };
@@ -11945,12 +12021,16 @@ var HighVoltageKidsProvider = class {
11945
12021
  let currentBaseName = null;
11946
12022
  const flushGroup = () => {
11947
12023
  if (currentGroup.length === 0) return;
11948
- const children = currentGroup.map((file) => ({
11949
- id: file.id,
11950
- itemType: "file",
11951
- label: file.title,
11952
- embedUrl: file.url
11953
- }));
12024
+ const children = currentGroup.map((file) => {
12025
+ const seconds = estimateDuration(file.mediaType);
12026
+ return {
12027
+ id: file.id,
12028
+ itemType: "file",
12029
+ label: file.title,
12030
+ seconds,
12031
+ embedUrl: file.url
12032
+ };
12033
+ });
11954
12034
  const label = currentGroup.length > 1 && currentBaseName ? currentBaseName : currentGroup[0].title;
11955
12035
  actionItems.push({
11956
12036
  id: currentGroup[0].id + "-action",
@@ -12081,7 +12161,7 @@ function getAvailableProviders(ids) {
12081
12161
  implemented: false,
12082
12162
  requiresAuth: false,
12083
12163
  authTypes: [],
12084
- capabilities: { browse: false, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false }
12164
+ capabilities: { browse: false, presentations: false, playlist: false, instructions: false, mediaLicensing: false }
12085
12165
  }));
12086
12166
  const all = [...implemented, ...comingSoon];
12087
12167
  if (ids && ids.length > 0) {
@@ -12092,7 +12172,7 @@ function getAvailableProviders(ids) {
12092
12172
  }
12093
12173
 
12094
12174
  // src/index.ts
12095
- var VERSION = "0.0.1";
12175
+ var VERSION = "0.0.4";
12096
12176
  // Annotate the CommonJS export names for ESM import in node:
12097
12177
  0 && (module.exports = {
12098
12178
  APlayProvider,
@@ -12100,6 +12180,7 @@ var VERSION = "0.0.1";
12100
12180
  B1ChurchProvider,
12101
12181
  BibleProjectProvider,
12102
12182
  ContentProvider,
12183
+ DEFAULT_DURATION_CONFIG,
12103
12184
  DeviceFlowHelper,
12104
12185
  FormatConverters,
12105
12186
  FormatResolver,
@@ -12112,12 +12193,16 @@ var VERSION = "0.0.1";
12112
12193
  VERSION,
12113
12194
  appendToPath,
12114
12195
  buildPath,
12115
- collapseInstructions,
12196
+ countWords,
12116
12197
  createFile,
12117
12198
  createFolder,
12118
12199
  detectMediaType,
12200
+ estimateDuration,
12201
+ estimateImageDuration,
12202
+ estimateTextDuration,
12119
12203
  expandedInstructionsToPlaylist,
12120
12204
  expandedInstructionsToPresentations,
12205
+ generatePath,
12121
12206
  getAllProviders,
12122
12207
  getAvailableProviders,
12123
12208
  getProvider,
@@ -12127,12 +12212,12 @@ var VERSION = "0.0.1";
12127
12212
  instructionsToPresentations,
12128
12213
  isContentFile,
12129
12214
  isContentFolder,
12215
+ navigateToPath,
12130
12216
  parsePath,
12131
12217
  playlistToExpandedInstructions,
12132
12218
  playlistToInstructions,
12133
12219
  playlistToPresentations,
12134
12220
  presentationsToExpandedInstructions,
12135
- presentationsToInstructions,
12136
12221
  presentationsToPlaylist,
12137
12222
  registerProvider
12138
12223
  });