@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 +446 -361
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +73 -27
- package/dist/index.d.ts +73 -27
- package/dist/index.js +439 -359
- package/dist/index.js.map +1 -1
- package/package.json +55 -55
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
|
-
|
|
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,
|
|
81
|
-
return { type: "folder", id, title, path, image, isLeaf
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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.
|
|
280
|
-
const expanded = await this.provider.
|
|
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.
|
|
302
|
-
const expanded = await this.provider.
|
|
303
|
-
if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "
|
|
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
|
|
324
|
-
if (
|
|
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
|
|
349
|
-
if (
|
|
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
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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,
|
|
719
|
-
return { type: "folder", id, title, path, image, isLeaf
|
|
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,
|
|
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:
|
|
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
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
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
|
|
832
|
-
|
|
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
|
|
835
|
-
const
|
|
836
|
-
if (
|
|
837
|
-
|
|
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:
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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}`,
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1441
|
+
return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl };
|
|
1517
1442
|
}
|
|
1518
|
-
function planTypeToFolder(planType
|
|
1519
|
-
return { type: "folder", id: planType.id, title: planType.name, path: ""
|
|
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
|
|
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 || "",
|
|
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 ||
|
|
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) => `/
|
|
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,
|
|
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
|
-
|
|
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
|
|
1654
|
-
|
|
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}/${
|
|
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
|
|
1687
|
-
const
|
|
1688
|
-
const
|
|
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
|
-
|
|
1711
|
-
|
|
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
|
|
1742
|
-
const
|
|
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
|
-
"
|
|
1709
|
+
"getInstructions",
|
|
1761
1710
|
ministryId,
|
|
1762
1711
|
item.providerId,
|
|
1763
1712
|
item.providerPath,
|
|
1764
1713
|
authData
|
|
1765
1714
|
);
|
|
1766
1715
|
if (externalInstructions) {
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
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
|
-
|
|
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
|
|
1792
|
-
const
|
|
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
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
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:
|
|
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: ""
|
|
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:
|
|
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:
|
|
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
|
|
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) =>
|
|
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
|
-
|
|
11950
|
-
|
|
11951
|
-
|
|
11952
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
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
|
});
|