@churchapps/content-provider-helper 0.0.2 → 0.0.3
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 +1239 -1599
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +340 -95
- package/dist/index.d.ts +340 -95
- package/dist/index.js +1228 -1599
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -18,6 +18,36 @@ function detectMediaType(url, explicitType) {
|
|
|
18
18
|
const videoPatterns = [".mp4", ".webm", ".m3u8", ".mov", "stream.mux.com"];
|
|
19
19
|
return videoPatterns.some((p) => url.includes(p)) ? "video" : "image";
|
|
20
20
|
}
|
|
21
|
+
function createFolder(id, title, path, image, providerData, isLeaf) {
|
|
22
|
+
return { type: "folder", id, title, path, image, isLeaf, providerData };
|
|
23
|
+
}
|
|
24
|
+
function createFile(id, title, url, options) {
|
|
25
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// src/pathUtils.ts
|
|
29
|
+
function parsePath(path) {
|
|
30
|
+
if (!path || path === "/" || path === "") {
|
|
31
|
+
return { segments: [], depth: 0 };
|
|
32
|
+
}
|
|
33
|
+
const segments = path.replace(/^\//, "").split("/").filter(Boolean);
|
|
34
|
+
return { segments, depth: segments.length };
|
|
35
|
+
}
|
|
36
|
+
function getSegment(path, index) {
|
|
37
|
+
const { segments } = parsePath(path);
|
|
38
|
+
return segments[index] ?? null;
|
|
39
|
+
}
|
|
40
|
+
function buildPath(segments) {
|
|
41
|
+
if (segments.length === 0) return "";
|
|
42
|
+
return "/" + segments.join("/");
|
|
43
|
+
}
|
|
44
|
+
function appendToPath(basePath, segment) {
|
|
45
|
+
if (!basePath || basePath === "/" || basePath === "") {
|
|
46
|
+
return "/" + segment;
|
|
47
|
+
}
|
|
48
|
+
const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
49
|
+
return cleanBase + "/" + segment;
|
|
50
|
+
}
|
|
21
51
|
|
|
22
52
|
// src/FormatConverters.ts
|
|
23
53
|
var FormatConverters_exports = {};
|
|
@@ -76,69 +106,20 @@ function presentationsToPlaylist(plan) {
|
|
|
76
106
|
return files;
|
|
77
107
|
}
|
|
78
108
|
function presentationsToInstructions(plan) {
|
|
79
|
-
return {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
itemType: "section",
|
|
84
|
-
label: section.name,
|
|
85
|
-
children: section.presentations.map((pres) => {
|
|
86
|
-
const totalSeconds = pres.files.reduce(
|
|
87
|
-
(sum, f) => sum + (f.providerData?.seconds || 0),
|
|
88
|
-
0
|
|
89
|
-
);
|
|
90
|
-
return {
|
|
91
|
-
id: pres.id,
|
|
92
|
-
itemType: mapActionTypeToItemType(pres.actionType),
|
|
93
|
-
label: pres.name,
|
|
94
|
-
seconds: totalSeconds || void 0,
|
|
95
|
-
embedUrl: pres.files[0]?.embedUrl || pres.files[0]?.url
|
|
96
|
-
};
|
|
97
|
-
})
|
|
98
|
-
}))
|
|
99
|
-
};
|
|
109
|
+
return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => {
|
|
110
|
+
const totalSeconds = pres.files.reduce((sum, f) => sum + (f.providerData?.seconds || 0), 0);
|
|
111
|
+
return { id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, seconds: totalSeconds || void 0, embedUrl: pres.files[0]?.embedUrl || pres.files[0]?.url };
|
|
112
|
+
}) })) };
|
|
100
113
|
}
|
|
101
114
|
function presentationsToExpandedInstructions(plan) {
|
|
102
|
-
return {
|
|
103
|
-
venueName: plan.name,
|
|
104
|
-
items: plan.sections.map((section) => ({
|
|
105
|
-
id: section.id,
|
|
106
|
-
itemType: "section",
|
|
107
|
-
label: section.name,
|
|
108
|
-
children: section.presentations.map((pres) => ({
|
|
109
|
-
id: pres.id,
|
|
110
|
-
itemType: mapActionTypeToItemType(pres.actionType),
|
|
111
|
-
label: pres.name,
|
|
112
|
-
description: pres.actionType !== "other" ? pres.actionType : void 0,
|
|
113
|
-
seconds: pres.files.reduce(
|
|
114
|
-
(sum, f) => sum + (f.providerData?.seconds || 0),
|
|
115
|
-
0
|
|
116
|
-
) || void 0,
|
|
117
|
-
children: pres.files.map((f) => ({
|
|
118
|
-
id: f.id,
|
|
119
|
-
itemType: "file",
|
|
120
|
-
label: f.title,
|
|
121
|
-
seconds: f.providerData?.seconds || void 0,
|
|
122
|
-
embedUrl: f.embedUrl || f.url
|
|
123
|
-
}))
|
|
124
|
-
}))
|
|
125
|
-
}))
|
|
126
|
-
};
|
|
115
|
+
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 })) })) })) };
|
|
127
116
|
}
|
|
128
117
|
function instructionsToPlaylist(instructions) {
|
|
129
118
|
const files = [];
|
|
130
119
|
function extractFiles(items) {
|
|
131
120
|
for (const item of items) {
|
|
132
121
|
if (item.embedUrl && (item.itemType === "file" || !item.children?.length)) {
|
|
133
|
-
files.push({
|
|
134
|
-
type: "file",
|
|
135
|
-
id: item.id || item.relatedId || generateId(),
|
|
136
|
-
title: item.label || "Untitled",
|
|
137
|
-
mediaType: detectMediaType(item.embedUrl),
|
|
138
|
-
url: item.embedUrl,
|
|
139
|
-
embedUrl: item.embedUrl,
|
|
140
|
-
providerData: item.seconds ? { seconds: item.seconds } : void 0
|
|
141
|
-
});
|
|
122
|
+
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 });
|
|
142
123
|
}
|
|
143
124
|
if (item.children) {
|
|
144
125
|
extractFiles(item.children);
|
|
@@ -157,52 +138,22 @@ function instructionsToPresentations(instructions, planId) {
|
|
|
157
138
|
if (presItem.children && presItem.children.length > 0) {
|
|
158
139
|
for (const child of presItem.children) {
|
|
159
140
|
if (child.embedUrl) {
|
|
160
|
-
const file = {
|
|
161
|
-
type: "file",
|
|
162
|
-
id: child.id || child.relatedId || generateId(),
|
|
163
|
-
title: child.label || "Untitled",
|
|
164
|
-
mediaType: detectMediaType(child.embedUrl),
|
|
165
|
-
url: child.embedUrl,
|
|
166
|
-
embedUrl: child.embedUrl,
|
|
167
|
-
providerData: child.seconds ? { seconds: child.seconds } : void 0
|
|
168
|
-
};
|
|
141
|
+
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 };
|
|
169
142
|
allFiles.push(file);
|
|
170
143
|
files.push(file);
|
|
171
144
|
}
|
|
172
145
|
}
|
|
173
146
|
}
|
|
174
147
|
if (files.length === 0 && presItem.embedUrl) {
|
|
175
|
-
const file = {
|
|
176
|
-
type: "file",
|
|
177
|
-
id: presItem.id || presItem.relatedId || generateId(),
|
|
178
|
-
title: presItem.label || "Untitled",
|
|
179
|
-
mediaType: detectMediaType(presItem.embedUrl),
|
|
180
|
-
url: presItem.embedUrl,
|
|
181
|
-
embedUrl: presItem.embedUrl,
|
|
182
|
-
providerData: presItem.seconds ? { seconds: presItem.seconds } : void 0
|
|
183
|
-
};
|
|
148
|
+
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 };
|
|
184
149
|
allFiles.push(file);
|
|
185
150
|
files.push(file);
|
|
186
151
|
}
|
|
187
|
-
return {
|
|
188
|
-
id: presItem.id || presItem.relatedId || generateId(),
|
|
189
|
-
name: presItem.label || "Presentation",
|
|
190
|
-
actionType: mapItemTypeToActionType(presItem.itemType),
|
|
191
|
-
files
|
|
192
|
-
};
|
|
152
|
+
return { id: presItem.id || presItem.relatedId || generateId(), name: presItem.label || "Presentation", actionType: mapItemTypeToActionType(presItem.itemType), files };
|
|
193
153
|
});
|
|
194
|
-
return {
|
|
195
|
-
id: sectionItem.id || sectionItem.relatedId || generateId(),
|
|
196
|
-
name: sectionItem.label || "Section",
|
|
197
|
-
presentations
|
|
198
|
-
};
|
|
154
|
+
return { id: sectionItem.id || sectionItem.relatedId || generateId(), name: sectionItem.label || "Section", presentations };
|
|
199
155
|
});
|
|
200
|
-
return {
|
|
201
|
-
id: planId || generateId(),
|
|
202
|
-
name: instructions.venueName || "Plan",
|
|
203
|
-
sections,
|
|
204
|
-
allFiles
|
|
205
|
-
};
|
|
156
|
+
return { id: planId || generateId(), name: instructions.venueName || "Plan", sections, allFiles };
|
|
206
157
|
}
|
|
207
158
|
var expandedInstructionsToPresentations = instructionsToPresentations;
|
|
208
159
|
function collapseInstructions(instructions, maxDepth = 2) {
|
|
@@ -234,39 +185,11 @@ function collapseInstructions(instructions, maxDepth = 2) {
|
|
|
234
185
|
};
|
|
235
186
|
}
|
|
236
187
|
function playlistToPresentations(files, planName = "Playlist", sectionName = "Content") {
|
|
237
|
-
const presentations = files.map((file, index) => ({
|
|
238
|
-
|
|
239
|
-
name: file.title,
|
|
240
|
-
actionType: "play",
|
|
241
|
-
files: [file]
|
|
242
|
-
}));
|
|
243
|
-
return {
|
|
244
|
-
id: "playlist-plan-" + generateId(),
|
|
245
|
-
name: planName,
|
|
246
|
-
sections: [{
|
|
247
|
-
id: "main-section",
|
|
248
|
-
name: sectionName,
|
|
249
|
-
presentations
|
|
250
|
-
}],
|
|
251
|
-
allFiles: [...files]
|
|
252
|
-
};
|
|
188
|
+
const presentations = files.map((file, index) => ({ id: `pres-${index}-${file.id}`, name: file.title, actionType: "play", files: [file] }));
|
|
189
|
+
return { id: "playlist-plan-" + generateId(), name: planName, sections: [{ id: "main-section", name: sectionName, presentations }], allFiles: [...files] };
|
|
253
190
|
}
|
|
254
191
|
function playlistToInstructions(files, venueName = "Playlist") {
|
|
255
|
-
return {
|
|
256
|
-
venueName,
|
|
257
|
-
items: [{
|
|
258
|
-
id: "main-section",
|
|
259
|
-
itemType: "section",
|
|
260
|
-
label: "Content",
|
|
261
|
-
children: files.map((file, index) => ({
|
|
262
|
-
id: file.id || `item-${index}`,
|
|
263
|
-
itemType: "file",
|
|
264
|
-
label: file.title,
|
|
265
|
-
seconds: file.providerData?.seconds || void 0,
|
|
266
|
-
embedUrl: file.embedUrl || file.url
|
|
267
|
-
}))
|
|
268
|
-
}]
|
|
269
|
-
};
|
|
192
|
+
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 })) }] };
|
|
270
193
|
}
|
|
271
194
|
var playlistToExpandedInstructions = playlistToInstructions;
|
|
272
195
|
|
|
@@ -274,310 +197,200 @@ var playlistToExpandedInstructions = playlistToInstructions;
|
|
|
274
197
|
var FormatResolver = class {
|
|
275
198
|
constructor(provider, options = {}) {
|
|
276
199
|
this.provider = provider;
|
|
277
|
-
this.options = {
|
|
278
|
-
allowLossy: options.allowLossy ?? true
|
|
279
|
-
};
|
|
200
|
+
this.options = { allowLossy: options.allowLossy ?? true };
|
|
280
201
|
}
|
|
281
202
|
getProvider() {
|
|
282
203
|
return this.provider;
|
|
283
204
|
}
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
205
|
+
/** Extract the last segment from a path to use as fallback ID/title */
|
|
206
|
+
getIdFromPath(path) {
|
|
207
|
+
const { segments } = parsePath(path);
|
|
208
|
+
return segments[segments.length - 1] || "content";
|
|
209
|
+
}
|
|
210
|
+
async getPlaylist(path, auth) {
|
|
211
|
+
const caps = this.provider.capabilities;
|
|
212
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
213
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
288
214
|
if (result && result.length > 0) return result;
|
|
289
215
|
}
|
|
290
216
|
if (caps.presentations) {
|
|
291
|
-
const plan = await this.provider.getPresentations(
|
|
217
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
292
218
|
if (plan) return presentationsToPlaylist(plan);
|
|
293
219
|
}
|
|
294
|
-
if (caps.expandedInstructions) {
|
|
295
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
220
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
221
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
296
222
|
if (expanded) return instructionsToPlaylist(expanded);
|
|
297
223
|
}
|
|
298
|
-
if (this.options.allowLossy && caps.instructions) {
|
|
299
|
-
const instructions = await this.provider.getInstructions(
|
|
224
|
+
if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
|
|
225
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
300
226
|
if (instructions) return instructionsToPlaylist(instructions);
|
|
301
227
|
}
|
|
302
228
|
return null;
|
|
303
229
|
}
|
|
304
|
-
async getPlaylistWithMeta(
|
|
305
|
-
const caps = this.provider.
|
|
306
|
-
if (caps.playlist) {
|
|
307
|
-
const result = await this.provider.getPlaylist(
|
|
230
|
+
async getPlaylistWithMeta(path, auth) {
|
|
231
|
+
const caps = this.provider.capabilities;
|
|
232
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
233
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
308
234
|
if (result && result.length > 0) {
|
|
309
235
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
310
236
|
}
|
|
311
237
|
}
|
|
312
238
|
if (caps.presentations) {
|
|
313
|
-
const plan = await this.provider.getPresentations(
|
|
314
|
-
if (plan) {
|
|
315
|
-
return {
|
|
316
|
-
data: presentationsToPlaylist(plan),
|
|
317
|
-
meta: { isNative: false, sourceFormat: "presentations", isLossy: false }
|
|
318
|
-
};
|
|
319
|
-
}
|
|
239
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
240
|
+
if (plan) return { data: presentationsToPlaylist(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
320
241
|
}
|
|
321
|
-
if (caps.expandedInstructions) {
|
|
322
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
323
|
-
if (expanded) {
|
|
324
|
-
return {
|
|
325
|
-
data: instructionsToPlaylist(expanded),
|
|
326
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false }
|
|
327
|
-
};
|
|
328
|
-
}
|
|
242
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
243
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
244
|
+
if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
|
|
329
245
|
}
|
|
330
|
-
if (this.options.allowLossy && caps.instructions) {
|
|
331
|
-
const instructions = await this.provider.getInstructions(
|
|
332
|
-
if (instructions) {
|
|
333
|
-
return {
|
|
334
|
-
data: instructionsToPlaylist(instructions),
|
|
335
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
336
|
-
};
|
|
337
|
-
}
|
|
246
|
+
if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
|
|
247
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
248
|
+
if (instructions) return { data: instructionsToPlaylist(instructions), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
338
249
|
}
|
|
339
250
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
340
251
|
}
|
|
341
|
-
async getPresentations(
|
|
342
|
-
const caps = this.provider.
|
|
252
|
+
async getPresentations(path, auth) {
|
|
253
|
+
const caps = this.provider.capabilities;
|
|
254
|
+
const fallbackId = this.getIdFromPath(path);
|
|
343
255
|
if (caps.presentations) {
|
|
344
|
-
const result = await this.provider.getPresentations(
|
|
256
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
345
257
|
if (result) return result;
|
|
346
258
|
}
|
|
347
|
-
if (caps.expandedInstructions) {
|
|
348
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
349
|
-
if (expanded) return instructionsToPresentations(expanded,
|
|
259
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
260
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
261
|
+
if (expanded) return instructionsToPresentations(expanded, fallbackId);
|
|
350
262
|
}
|
|
351
|
-
if (caps.instructions) {
|
|
352
|
-
const instructions = await this.provider.getInstructions(
|
|
353
|
-
if (instructions) return instructionsToPresentations(instructions,
|
|
263
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
264
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
265
|
+
if (instructions) return instructionsToPresentations(instructions, fallbackId);
|
|
354
266
|
}
|
|
355
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
356
|
-
const playlist = await this.provider.getPlaylist(
|
|
267
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
268
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
357
269
|
if (playlist && playlist.length > 0) {
|
|
358
|
-
return playlistToPresentations(playlist,
|
|
270
|
+
return playlistToPresentations(playlist, fallbackId);
|
|
359
271
|
}
|
|
360
272
|
}
|
|
361
273
|
return null;
|
|
362
274
|
}
|
|
363
|
-
async getPresentationsWithMeta(
|
|
364
|
-
const caps = this.provider.
|
|
275
|
+
async getPresentationsWithMeta(path, auth) {
|
|
276
|
+
const caps = this.provider.capabilities;
|
|
277
|
+
const fallbackId = this.getIdFromPath(path);
|
|
365
278
|
if (caps.presentations) {
|
|
366
|
-
const result = await this.provider.getPresentations(
|
|
279
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
367
280
|
if (result) {
|
|
368
281
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
369
282
|
}
|
|
370
283
|
}
|
|
371
|
-
if (caps.expandedInstructions) {
|
|
372
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
373
|
-
if (expanded) {
|
|
374
|
-
return {
|
|
375
|
-
data: instructionsToPresentations(expanded, folder.id),
|
|
376
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false }
|
|
377
|
-
};
|
|
378
|
-
}
|
|
284
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
285
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
286
|
+
if (expanded) return { data: instructionsToPresentations(expanded, fallbackId), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
|
|
379
287
|
}
|
|
380
|
-
if (caps.instructions) {
|
|
381
|
-
const instructions = await this.provider.getInstructions(
|
|
382
|
-
if (instructions) {
|
|
383
|
-
return {
|
|
384
|
-
data: instructionsToPresentations(instructions, folder.id),
|
|
385
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
386
|
-
};
|
|
387
|
-
}
|
|
288
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
289
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
290
|
+
if (instructions) return { data: instructionsToPresentations(instructions, fallbackId), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
388
291
|
}
|
|
389
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
390
|
-
const playlist = await this.provider.getPlaylist(
|
|
391
|
-
if (playlist && playlist.length > 0) {
|
|
392
|
-
return {
|
|
393
|
-
data: playlistToPresentations(playlist, folder.title),
|
|
394
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
395
|
-
};
|
|
396
|
-
}
|
|
292
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
293
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
294
|
+
if (playlist && playlist.length > 0) return { data: playlistToPresentations(playlist, fallbackId), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
397
295
|
}
|
|
398
296
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
399
297
|
}
|
|
400
|
-
async getInstructions(
|
|
401
|
-
const caps = this.provider.
|
|
402
|
-
|
|
403
|
-
|
|
298
|
+
async getInstructions(path, auth) {
|
|
299
|
+
const caps = this.provider.capabilities;
|
|
300
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
301
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
302
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
404
303
|
if (result) return result;
|
|
405
304
|
}
|
|
406
|
-
if (caps.expandedInstructions) {
|
|
407
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
305
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
306
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
408
307
|
if (expanded) return collapseInstructions(expanded);
|
|
409
308
|
}
|
|
410
309
|
if (caps.presentations) {
|
|
411
|
-
const plan = await this.provider.getPresentations(
|
|
310
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
412
311
|
if (plan) return presentationsToInstructions(plan);
|
|
413
312
|
}
|
|
414
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
415
|
-
const playlist = await this.provider.getPlaylist(
|
|
313
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
314
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
416
315
|
if (playlist && playlist.length > 0) {
|
|
417
|
-
return playlistToInstructions(playlist,
|
|
316
|
+
return playlistToInstructions(playlist, fallbackTitle);
|
|
418
317
|
}
|
|
419
318
|
}
|
|
420
319
|
return null;
|
|
421
320
|
}
|
|
422
|
-
async getInstructionsWithMeta(
|
|
423
|
-
const caps = this.provider.
|
|
424
|
-
|
|
425
|
-
|
|
321
|
+
async getInstructionsWithMeta(path, auth) {
|
|
322
|
+
const caps = this.provider.capabilities;
|
|
323
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
324
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
325
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
426
326
|
if (result) {
|
|
427
327
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
428
328
|
}
|
|
429
329
|
}
|
|
430
|
-
if (caps.expandedInstructions) {
|
|
431
|
-
const expanded = await this.provider.getExpandedInstructions(
|
|
432
|
-
if (expanded) {
|
|
433
|
-
return {
|
|
434
|
-
data: collapseInstructions(expanded),
|
|
435
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: true }
|
|
436
|
-
};
|
|
437
|
-
}
|
|
330
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
331
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
332
|
+
if (expanded) return { data: collapseInstructions(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: true } };
|
|
438
333
|
}
|
|
439
334
|
if (caps.presentations) {
|
|
440
|
-
const plan = await this.provider.getPresentations(
|
|
441
|
-
if (plan) {
|
|
442
|
-
return {
|
|
443
|
-
data: presentationsToInstructions(plan),
|
|
444
|
-
meta: { isNative: false, sourceFormat: "presentations", isLossy: false }
|
|
445
|
-
};
|
|
446
|
-
}
|
|
335
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
336
|
+
if (plan) return { data: presentationsToInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
447
337
|
}
|
|
448
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
449
|
-
const playlist = await this.provider.getPlaylist(
|
|
450
|
-
if (playlist && playlist.length > 0) {
|
|
451
|
-
return {
|
|
452
|
-
data: playlistToInstructions(playlist, folder.title),
|
|
453
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
454
|
-
};
|
|
455
|
-
}
|
|
338
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
339
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
340
|
+
if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
456
341
|
}
|
|
457
342
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
458
343
|
}
|
|
459
|
-
async getExpandedInstructions(
|
|
460
|
-
const caps = this.provider.
|
|
461
|
-
|
|
462
|
-
|
|
344
|
+
async getExpandedInstructions(path, auth) {
|
|
345
|
+
const caps = this.provider.capabilities;
|
|
346
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
347
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
348
|
+
const result = await this.provider.getExpandedInstructions(path, auth);
|
|
463
349
|
if (result) return result;
|
|
464
350
|
}
|
|
465
351
|
if (caps.presentations) {
|
|
466
|
-
const plan = await this.provider.getPresentations(
|
|
352
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
467
353
|
if (plan) return presentationsToExpandedInstructions(plan);
|
|
468
354
|
}
|
|
469
|
-
if (caps.instructions) {
|
|
470
|
-
const instructions = await this.provider.getInstructions(
|
|
355
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
356
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
471
357
|
if (instructions) return instructions;
|
|
472
358
|
}
|
|
473
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
474
|
-
const playlist = await this.provider.getPlaylist(
|
|
359
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
360
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
475
361
|
if (playlist && playlist.length > 0) {
|
|
476
|
-
return playlistToInstructions(playlist,
|
|
362
|
+
return playlistToInstructions(playlist, fallbackTitle);
|
|
477
363
|
}
|
|
478
364
|
}
|
|
479
365
|
return null;
|
|
480
366
|
}
|
|
481
|
-
async getExpandedInstructionsWithMeta(
|
|
482
|
-
const caps = this.provider.
|
|
483
|
-
|
|
484
|
-
|
|
367
|
+
async getExpandedInstructionsWithMeta(path, auth) {
|
|
368
|
+
const caps = this.provider.capabilities;
|
|
369
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
370
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
371
|
+
const result = await this.provider.getExpandedInstructions(path, auth);
|
|
485
372
|
if (result) {
|
|
486
373
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
487
374
|
}
|
|
488
375
|
}
|
|
489
376
|
if (caps.presentations) {
|
|
490
|
-
const plan = await this.provider.getPresentations(
|
|
491
|
-
if (plan) {
|
|
492
|
-
return {
|
|
493
|
-
data: presentationsToExpandedInstructions(plan),
|
|
494
|
-
meta: { isNative: false, sourceFormat: "presentations", isLossy: false }
|
|
495
|
-
};
|
|
496
|
-
}
|
|
377
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
378
|
+
if (plan) return { data: presentationsToExpandedInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
497
379
|
}
|
|
498
|
-
if (caps.instructions) {
|
|
499
|
-
const instructions = await this.provider.getInstructions(
|
|
500
|
-
if (instructions) {
|
|
501
|
-
return {
|
|
502
|
-
data: instructions,
|
|
503
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
504
|
-
};
|
|
505
|
-
}
|
|
380
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
381
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
382
|
+
if (instructions) return { data: instructions, meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
506
383
|
}
|
|
507
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
508
|
-
const playlist = await this.provider.getPlaylist(
|
|
509
|
-
if (playlist && playlist.length > 0) {
|
|
510
|
-
return {
|
|
511
|
-
data: playlistToInstructions(playlist, folder.title),
|
|
512
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
513
|
-
};
|
|
514
|
-
}
|
|
384
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
385
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
386
|
+
if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
515
387
|
}
|
|
516
388
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
517
389
|
}
|
|
518
390
|
};
|
|
519
391
|
|
|
520
|
-
// src/
|
|
521
|
-
var
|
|
522
|
-
async getPlaylist(folder, auth, _resolution) {
|
|
523
|
-
const caps = this.getCapabilities();
|
|
524
|
-
if (caps.presentations) {
|
|
525
|
-
const plan = await this.getPresentations(folder, auth);
|
|
526
|
-
if (plan) return presentationsToPlaylist(plan);
|
|
527
|
-
}
|
|
528
|
-
return null;
|
|
529
|
-
}
|
|
530
|
-
async getInstructions(folder, auth) {
|
|
531
|
-
const caps = this.getCapabilities();
|
|
532
|
-
if (caps.expandedInstructions) {
|
|
533
|
-
const expanded = await this.getExpandedInstructions(folder, auth);
|
|
534
|
-
if (expanded) return collapseInstructions(expanded);
|
|
535
|
-
}
|
|
536
|
-
if (caps.presentations) {
|
|
537
|
-
const plan = await this.getPresentations(folder, auth);
|
|
538
|
-
if (plan) return presentationsToInstructions(plan);
|
|
539
|
-
}
|
|
540
|
-
return null;
|
|
541
|
-
}
|
|
542
|
-
async getExpandedInstructions(folder, auth) {
|
|
543
|
-
const caps = this.getCapabilities();
|
|
544
|
-
if (caps.presentations) {
|
|
545
|
-
const plan = await this.getPresentations(folder, auth);
|
|
546
|
-
if (plan) return presentationsToExpandedInstructions(plan);
|
|
547
|
-
}
|
|
548
|
-
return null;
|
|
549
|
-
}
|
|
550
|
-
requiresAuth() {
|
|
551
|
-
return !!this.config.clientId;
|
|
552
|
-
}
|
|
553
|
-
getCapabilities() {
|
|
554
|
-
return {
|
|
555
|
-
browse: true,
|
|
556
|
-
presentations: false,
|
|
557
|
-
playlist: false,
|
|
558
|
-
instructions: false,
|
|
559
|
-
expandedInstructions: false,
|
|
560
|
-
mediaLicensing: false
|
|
561
|
-
};
|
|
562
|
-
}
|
|
563
|
-
checkMediaLicense(_mediaId, _auth) {
|
|
564
|
-
return Promise.resolve(null);
|
|
565
|
-
}
|
|
566
|
-
getAuthTypes() {
|
|
567
|
-
if (!this.requiresAuth()) return ["none"];
|
|
568
|
-
const types = ["oauth_pkce"];
|
|
569
|
-
if (this.supportsDeviceFlow()) types.push("device_flow");
|
|
570
|
-
return types;
|
|
571
|
-
}
|
|
572
|
-
isAuthValid(auth) {
|
|
573
|
-
if (!auth) return false;
|
|
574
|
-
return !this.isTokenExpired(auth);
|
|
575
|
-
}
|
|
576
|
-
isTokenExpired(auth) {
|
|
577
|
-
if (!auth.created_at || !auth.expires_in) return true;
|
|
578
|
-
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
579
|
-
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
580
|
-
}
|
|
392
|
+
// src/helpers/OAuthHelper.ts
|
|
393
|
+
var OAuthHelper = class {
|
|
581
394
|
generateCodeVerifier() {
|
|
582
395
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
583
396
|
const length = 64;
|
|
@@ -601,110 +414,100 @@ var ContentProvider = class {
|
|
|
601
414
|
const base64 = btoa(binary);
|
|
602
415
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
603
416
|
}
|
|
604
|
-
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
417
|
+
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
605
418
|
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
606
419
|
const params = new URLSearchParams({
|
|
607
420
|
response_type: "code",
|
|
608
|
-
client_id:
|
|
421
|
+
client_id: config.clientId,
|
|
609
422
|
redirect_uri: redirectUri,
|
|
610
|
-
scope:
|
|
423
|
+
scope: config.scopes.join(" "),
|
|
611
424
|
code_challenge: codeChallenge,
|
|
612
425
|
code_challenge_method: "S256",
|
|
613
|
-
state: state ||
|
|
426
|
+
state: state || ""
|
|
614
427
|
});
|
|
615
|
-
return { url: `${
|
|
428
|
+
return { url: `${config.oauthBase}/authorize?${params.toString()}`, challengeMethod: "S256" };
|
|
616
429
|
}
|
|
617
|
-
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
430
|
+
async exchangeCodeForTokens(config, providerId, code, codeVerifier, redirectUri) {
|
|
618
431
|
try {
|
|
619
432
|
const params = new URLSearchParams({
|
|
620
433
|
grant_type: "authorization_code",
|
|
621
434
|
code,
|
|
622
435
|
redirect_uri: redirectUri,
|
|
623
|
-
client_id:
|
|
436
|
+
client_id: config.clientId,
|
|
624
437
|
code_verifier: codeVerifier
|
|
625
438
|
});
|
|
626
|
-
const tokenUrl = `${
|
|
627
|
-
console.log(`${
|
|
628
|
-
console.log(` - client_id: ${
|
|
439
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
440
|
+
console.log(`${providerId} token exchange request to: ${tokenUrl}`);
|
|
441
|
+
console.log(` - client_id: ${config.clientId}`);
|
|
629
442
|
console.log(` - redirect_uri: ${redirectUri}`);
|
|
630
443
|
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
631
444
|
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
632
|
-
console.log(`${
|
|
445
|
+
console.log(`${providerId} token response status: ${response.status}`);
|
|
633
446
|
if (!response.ok) {
|
|
634
447
|
const errorText = await response.text();
|
|
635
|
-
console.error(`${
|
|
448
|
+
console.error(`${providerId} token exchange failed: ${response.status} - ${errorText}`);
|
|
636
449
|
return null;
|
|
637
450
|
}
|
|
638
451
|
const data = await response.json();
|
|
639
|
-
console.log(`${
|
|
640
|
-
return {
|
|
641
|
-
access_token: data.access_token,
|
|
642
|
-
refresh_token: data.refresh_token,
|
|
643
|
-
token_type: data.token_type || "Bearer",
|
|
644
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
645
|
-
expires_in: data.expires_in,
|
|
646
|
-
scope: data.scope || this.config.scopes.join(" ")
|
|
647
|
-
};
|
|
452
|
+
console.log(`${providerId} token exchange successful, got access_token: ${!!data.access_token}`);
|
|
453
|
+
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(" ") };
|
|
648
454
|
} catch (error) {
|
|
649
|
-
console.error(`${
|
|
455
|
+
console.error(`${providerId} token exchange error:`, error);
|
|
650
456
|
return null;
|
|
651
457
|
}
|
|
652
458
|
}
|
|
653
|
-
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/helpers/TokenHelper.ts
|
|
462
|
+
var TokenHelper = class {
|
|
463
|
+
isAuthValid(auth) {
|
|
464
|
+
if (!auth) return false;
|
|
465
|
+
return !this.isTokenExpired(auth);
|
|
466
|
+
}
|
|
467
|
+
isTokenExpired(auth) {
|
|
468
|
+
if (!auth.created_at || !auth.expires_in) return true;
|
|
469
|
+
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
470
|
+
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
471
|
+
}
|
|
472
|
+
async refreshToken(config, auth) {
|
|
654
473
|
if (!auth.refresh_token) return null;
|
|
655
474
|
try {
|
|
656
475
|
const params = new URLSearchParams({
|
|
657
476
|
grant_type: "refresh_token",
|
|
658
477
|
refresh_token: auth.refresh_token,
|
|
659
|
-
client_id:
|
|
478
|
+
client_id: config.clientId
|
|
660
479
|
});
|
|
661
|
-
const response = await fetch(`${
|
|
480
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
662
481
|
if (!response.ok) return null;
|
|
663
482
|
const data = await response.json();
|
|
664
|
-
return {
|
|
665
|
-
access_token: data.access_token,
|
|
666
|
-
refresh_token: data.refresh_token || auth.refresh_token,
|
|
667
|
-
token_type: data.token_type || "Bearer",
|
|
668
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
669
|
-
expires_in: data.expires_in,
|
|
670
|
-
scope: data.scope || auth.scope
|
|
671
|
-
};
|
|
483
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
|
|
672
484
|
} catch {
|
|
673
485
|
return null;
|
|
674
486
|
}
|
|
675
487
|
}
|
|
676
|
-
|
|
677
|
-
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/helpers/DeviceFlowHelper.ts
|
|
491
|
+
var DeviceFlowHelper = class {
|
|
492
|
+
supportsDeviceFlow(config) {
|
|
493
|
+
return !!config.supportsDeviceFlow && !!config.deviceAuthEndpoint;
|
|
678
494
|
}
|
|
679
|
-
async initiateDeviceFlow() {
|
|
680
|
-
if (!this.supportsDeviceFlow()) return null;
|
|
495
|
+
async initiateDeviceFlow(config) {
|
|
496
|
+
if (!this.supportsDeviceFlow(config)) return null;
|
|
681
497
|
try {
|
|
682
|
-
const
|
|
683
|
-
const response = await fetch(`${this.config.oauthBase}${this.config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
498
|
+
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(" ") }) });
|
|
684
499
|
if (!response.ok) return null;
|
|
685
500
|
return await response.json();
|
|
686
501
|
} catch {
|
|
687
502
|
return null;
|
|
688
503
|
}
|
|
689
504
|
}
|
|
690
|
-
async pollDeviceFlowToken(deviceCode) {
|
|
505
|
+
async pollDeviceFlowToken(config, deviceCode) {
|
|
691
506
|
try {
|
|
692
|
-
const
|
|
693
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
694
|
-
device_code: deviceCode,
|
|
695
|
-
client_id: this.config.clientId
|
|
696
|
-
});
|
|
697
|
-
const response = await fetch(`${this.config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
507
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
|
|
698
508
|
if (response.ok) {
|
|
699
509
|
const data = await response.json();
|
|
700
|
-
return {
|
|
701
|
-
access_token: data.access_token,
|
|
702
|
-
refresh_token: data.refresh_token,
|
|
703
|
-
token_type: data.token_type || "Bearer",
|
|
704
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
705
|
-
expires_in: data.expires_in,
|
|
706
|
-
scope: data.scope || this.config.scopes.join(" ")
|
|
707
|
-
};
|
|
510
|
+
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(" ") };
|
|
708
511
|
}
|
|
709
512
|
const errorData = await response.json();
|
|
710
513
|
switch (errorData.error) {
|
|
@@ -726,183 +529,292 @@ var ContentProvider = class {
|
|
|
726
529
|
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
727
530
|
return (baseInterval + slowDownCount * 5) * 1e3;
|
|
728
531
|
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/helpers/ApiHelper.ts
|
|
535
|
+
var ApiHelper = class {
|
|
729
536
|
createAuthHeaders(auth) {
|
|
730
537
|
if (!auth) return null;
|
|
731
538
|
return { Authorization: `Bearer ${auth.access_token}`, Accept: "application/json" };
|
|
732
539
|
}
|
|
733
|
-
async apiRequest(path, auth, method = "GET", body) {
|
|
540
|
+
async apiRequest(config, providerId, path, auth, method = "GET", body) {
|
|
734
541
|
try {
|
|
735
|
-
const url = `${
|
|
542
|
+
const url = `${config.apiBase}${path}`;
|
|
736
543
|
const headers = { Accept: "application/json" };
|
|
737
544
|
if (auth) headers["Authorization"] = `Bearer ${auth.access_token}`;
|
|
738
545
|
if (body) headers["Content-Type"] = "application/json";
|
|
739
|
-
console.log(`${
|
|
740
|
-
console.log(`${
|
|
546
|
+
console.log(`${providerId} API request: ${method} ${url}`);
|
|
547
|
+
console.log(`${providerId} API auth present: ${!!auth}`);
|
|
741
548
|
const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
|
|
742
549
|
const response = await fetch(url, options);
|
|
743
|
-
console.log(`${
|
|
550
|
+
console.log(`${providerId} API response status: ${response.status}`);
|
|
744
551
|
if (!response.ok) {
|
|
745
552
|
const errorText = await response.text();
|
|
746
|
-
console.error(`${
|
|
553
|
+
console.error(`${providerId} API request failed: ${response.status} - ${errorText}`);
|
|
747
554
|
return null;
|
|
748
555
|
}
|
|
749
556
|
return await response.json();
|
|
750
557
|
} catch (error) {
|
|
751
|
-
console.error(`${
|
|
558
|
+
console.error(`${providerId} API request error:`, error);
|
|
752
559
|
return null;
|
|
753
560
|
}
|
|
754
561
|
}
|
|
755
|
-
|
|
756
|
-
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/ContentProvider.ts
|
|
565
|
+
var ContentProvider = class {
|
|
566
|
+
constructor() {
|
|
567
|
+
this.oauthHelper = new OAuthHelper();
|
|
568
|
+
this.tokenHelper = new TokenHelper();
|
|
569
|
+
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
570
|
+
this.apiHelper = new ApiHelper();
|
|
571
|
+
}
|
|
572
|
+
async getPlaylist(path, auth, _resolution) {
|
|
573
|
+
const caps = this.getCapabilities();
|
|
574
|
+
if (caps.presentations) {
|
|
575
|
+
const plan = await this.getPresentations(path, auth);
|
|
576
|
+
if (plan) return presentationsToPlaylist(plan);
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
async getInstructions(path, auth) {
|
|
581
|
+
const caps = this.getCapabilities();
|
|
582
|
+
if (caps.expandedInstructions) {
|
|
583
|
+
const expanded = await this.getExpandedInstructions(path, auth);
|
|
584
|
+
if (expanded) return collapseInstructions(expanded);
|
|
585
|
+
}
|
|
586
|
+
if (caps.presentations) {
|
|
587
|
+
const plan = await this.getPresentations(path, auth);
|
|
588
|
+
if (plan) return presentationsToInstructions(plan);
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
async getExpandedInstructions(path, auth) {
|
|
593
|
+
const caps = this.getCapabilities();
|
|
594
|
+
if (caps.presentations) {
|
|
595
|
+
const plan = await this.getPresentations(path, auth);
|
|
596
|
+
if (plan) return presentationsToExpandedInstructions(plan);
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
requiresAuth() {
|
|
601
|
+
return !!this.config.clientId;
|
|
602
|
+
}
|
|
603
|
+
getCapabilities() {
|
|
604
|
+
return { browse: true, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
605
|
+
}
|
|
606
|
+
checkMediaLicense(_mediaId, _auth) {
|
|
607
|
+
return Promise.resolve(null);
|
|
608
|
+
}
|
|
609
|
+
getAuthTypes() {
|
|
610
|
+
if (!this.requiresAuth()) return ["none"];
|
|
611
|
+
const types = ["oauth_pkce"];
|
|
612
|
+
if (this.supportsDeviceFlow()) types.push("device_flow");
|
|
613
|
+
return types;
|
|
614
|
+
}
|
|
615
|
+
// Token management - delegated to TokenHelper
|
|
616
|
+
isAuthValid(auth) {
|
|
617
|
+
return this.tokenHelper.isAuthValid(auth);
|
|
618
|
+
}
|
|
619
|
+
isTokenExpired(auth) {
|
|
620
|
+
return this.tokenHelper.isTokenExpired(auth);
|
|
621
|
+
}
|
|
622
|
+
async refreshToken(auth) {
|
|
623
|
+
return this.tokenHelper.refreshToken(this.config, auth);
|
|
624
|
+
}
|
|
625
|
+
// OAuth PKCE - delegated to OAuthHelper
|
|
626
|
+
generateCodeVerifier() {
|
|
627
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
628
|
+
}
|
|
629
|
+
async generateCodeChallenge(verifier) {
|
|
630
|
+
return this.oauthHelper.generateCodeChallenge(verifier);
|
|
631
|
+
}
|
|
632
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
633
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
634
|
+
}
|
|
635
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
636
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
637
|
+
}
|
|
638
|
+
// Device flow - delegated to DeviceFlowHelper
|
|
639
|
+
supportsDeviceFlow() {
|
|
640
|
+
return this.deviceFlowHelper.supportsDeviceFlow(this.config);
|
|
641
|
+
}
|
|
642
|
+
async initiateDeviceFlow() {
|
|
643
|
+
return this.deviceFlowHelper.initiateDeviceFlow(this.config);
|
|
644
|
+
}
|
|
645
|
+
async pollDeviceFlowToken(deviceCode) {
|
|
646
|
+
return this.deviceFlowHelper.pollDeviceFlowToken(this.config, deviceCode);
|
|
647
|
+
}
|
|
648
|
+
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
649
|
+
return this.deviceFlowHelper.calculatePollDelay(baseInterval, slowDownCount);
|
|
650
|
+
}
|
|
651
|
+
// API requests - delegated to ApiHelper
|
|
652
|
+
createAuthHeaders(auth) {
|
|
653
|
+
return this.apiHelper.createAuthHeaders(auth);
|
|
654
|
+
}
|
|
655
|
+
async apiRequest(path, auth, method = "GET", body) {
|
|
656
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
|
|
657
|
+
}
|
|
658
|
+
// Content factories
|
|
659
|
+
createFolder(id, title, path, image, providerData, isLeaf) {
|
|
660
|
+
return { type: "folder", id, title, path, image, isLeaf, providerData };
|
|
757
661
|
}
|
|
758
662
|
createFile(id, title, url, options) {
|
|
759
|
-
return {
|
|
760
|
-
type: "file",
|
|
761
|
-
id,
|
|
762
|
-
title,
|
|
763
|
-
url,
|
|
764
|
-
mediaType: options?.mediaType ?? detectMediaType(url),
|
|
765
|
-
image: options?.image,
|
|
766
|
-
muxPlaybackId: options?.muxPlaybackId,
|
|
767
|
-
providerData: options?.providerData
|
|
768
|
-
};
|
|
663
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
|
|
769
664
|
}
|
|
770
665
|
};
|
|
771
666
|
|
|
772
|
-
// src/providers/APlayProvider.ts
|
|
773
|
-
var APlayProvider = class
|
|
667
|
+
// src/providers/aPlay/APlayProvider.ts
|
|
668
|
+
var APlayProvider = class {
|
|
774
669
|
constructor() {
|
|
775
|
-
|
|
670
|
+
this.apiHelper = new ApiHelper();
|
|
776
671
|
this.id = "aplay";
|
|
777
672
|
this.name = "APlay";
|
|
778
|
-
this.logos = {
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
this.
|
|
783
|
-
id: "aplay",
|
|
784
|
-
name: "APlay",
|
|
785
|
-
apiBase: "https://api-prod.amazingkids.app",
|
|
786
|
-
oauthBase: "https://api.joinamazing.com/prod/aims/oauth",
|
|
787
|
-
clientId: "xFJFq7yNYuXXXMx0YBiQ",
|
|
788
|
-
scopes: ["openid", "profile", "email"],
|
|
789
|
-
endpoints: {
|
|
790
|
-
modules: "/prod/curriculum/modules",
|
|
791
|
-
productLibraries: (productId) => `/prod/curriculum/modules/products/${productId}/libraries`,
|
|
792
|
-
libraryMedia: (libraryId) => `/prod/creators/libraries/${libraryId}/media`
|
|
793
|
-
}
|
|
794
|
-
};
|
|
673
|
+
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
674
|
+
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` } };
|
|
675
|
+
this.requiresAuth = true;
|
|
676
|
+
this.authTypes = ["oauth_pkce"];
|
|
677
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: true };
|
|
795
678
|
}
|
|
796
|
-
|
|
797
|
-
return
|
|
798
|
-
browse: true,
|
|
799
|
-
presentations: true,
|
|
800
|
-
playlist: false,
|
|
801
|
-
instructions: false,
|
|
802
|
-
expandedInstructions: false,
|
|
803
|
-
mediaLicensing: true
|
|
804
|
-
};
|
|
679
|
+
async apiRequest(path, auth) {
|
|
680
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
805
681
|
}
|
|
806
|
-
async browse(
|
|
807
|
-
|
|
808
|
-
console.log(
|
|
809
|
-
if (
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
if (!Array.isArray(modules)) return [];
|
|
817
|
-
const items = [];
|
|
818
|
-
for (const m of modules) {
|
|
819
|
-
if (m.isLocked) continue;
|
|
820
|
-
const allProducts = m.products || [];
|
|
821
|
-
const products = allProducts.filter((p) => !p.isHidden);
|
|
822
|
-
if (products.length === 0) {
|
|
823
|
-
items.push({
|
|
824
|
-
type: "folder",
|
|
825
|
-
id: m.id || m.moduleId,
|
|
826
|
-
title: m.title || m.name,
|
|
827
|
-
image: m.image,
|
|
828
|
-
providerData: { level: "libraries", productId: m.id || m.moduleId }
|
|
829
|
-
});
|
|
830
|
-
} else if (products.length === 1) {
|
|
831
|
-
const product = products[0];
|
|
832
|
-
items.push({
|
|
833
|
-
type: "folder",
|
|
834
|
-
id: product.productId || product.id,
|
|
835
|
-
title: m.title || m.name,
|
|
836
|
-
image: m.image || product.image,
|
|
837
|
-
providerData: { level: "libraries", productId: product.productId || product.id }
|
|
838
|
-
});
|
|
839
|
-
} else {
|
|
840
|
-
items.push({
|
|
841
|
-
type: "folder",
|
|
842
|
-
id: m.id || m.moduleId,
|
|
843
|
-
title: m.title || m.name,
|
|
844
|
-
image: m.image,
|
|
845
|
-
providerData: {
|
|
846
|
-
level: "products",
|
|
847
|
-
products: products.map((p) => ({ id: p.productId || p.id, title: p.title || p.name, image: p.image }))
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
}
|
|
851
|
-
}
|
|
852
|
-
return items;
|
|
682
|
+
async browse(path, auth) {
|
|
683
|
+
const { segments, depth } = parsePath(path);
|
|
684
|
+
console.log("APlay browse called with path:", path, "depth:", depth);
|
|
685
|
+
if (depth === 0) {
|
|
686
|
+
return [{
|
|
687
|
+
type: "folder",
|
|
688
|
+
id: "modules-root",
|
|
689
|
+
title: "Modules",
|
|
690
|
+
path: "/modules"
|
|
691
|
+
}];
|
|
853
692
|
}
|
|
854
|
-
const
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
693
|
+
const root = segments[0];
|
|
694
|
+
if (root !== "modules") return [];
|
|
695
|
+
if (depth === 1) {
|
|
696
|
+
return this.getModules(auth);
|
|
697
|
+
}
|
|
698
|
+
if (depth === 2) {
|
|
699
|
+
const moduleId = segments[1];
|
|
700
|
+
return this.getModuleContent(moduleId, path, auth);
|
|
701
|
+
}
|
|
702
|
+
if (depth === 4 && segments[2] === "products") {
|
|
703
|
+
const productId = segments[3];
|
|
704
|
+
return this.getLibraryFolders(productId, path, auth);
|
|
705
|
+
}
|
|
706
|
+
if (depth === 5 && segments[2] === "products") {
|
|
707
|
+
const libraryId = segments[4];
|
|
708
|
+
return this.getMediaFiles(libraryId, auth);
|
|
864
709
|
}
|
|
710
|
+
if (depth === 4 && segments[2] === "libraries") {
|
|
711
|
+
const libraryId = segments[3];
|
|
712
|
+
return this.getMediaFiles(libraryId, auth);
|
|
713
|
+
}
|
|
714
|
+
return [];
|
|
865
715
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
716
|
+
async getModules(auth) {
|
|
717
|
+
console.log(`APlay fetching modules from: ${this.config.endpoints.modules}`);
|
|
718
|
+
const response = await this.apiRequest(this.config.endpoints.modules, auth);
|
|
719
|
+
console.log("APlay modules response:", response ? "received" : "null");
|
|
720
|
+
if (!response) return [];
|
|
721
|
+
const modules = response.data || response.modules || response;
|
|
722
|
+
console.log("APlay modules count:", Array.isArray(modules) ? modules.length : "not an array");
|
|
723
|
+
if (!Array.isArray(modules)) return [];
|
|
724
|
+
const items = [];
|
|
725
|
+
for (const m of modules) {
|
|
726
|
+
if (m.isLocked) continue;
|
|
727
|
+
const moduleId = m.id || m.moduleId;
|
|
728
|
+
const moduleTitle = m.title || m.name;
|
|
729
|
+
const moduleImage = m.image;
|
|
730
|
+
const allProducts = m.products || [];
|
|
731
|
+
const products = allProducts.filter((p) => !p.isHidden);
|
|
732
|
+
if (products.length === 0) {
|
|
733
|
+
items.push({
|
|
734
|
+
type: "folder",
|
|
735
|
+
id: moduleId,
|
|
736
|
+
title: moduleTitle,
|
|
737
|
+
image: moduleImage,
|
|
738
|
+
path: `/modules/${moduleId}`,
|
|
739
|
+
providerData: { productCount: 0 }
|
|
740
|
+
});
|
|
741
|
+
} else if (products.length === 1) {
|
|
742
|
+
const product = products[0];
|
|
743
|
+
items.push({
|
|
744
|
+
type: "folder",
|
|
745
|
+
id: product.productId || product.id,
|
|
746
|
+
title: moduleTitle,
|
|
747
|
+
image: moduleImage || product.image,
|
|
748
|
+
path: `/modules/${moduleId}`,
|
|
749
|
+
providerData: { productCount: 1, productId: product.productId || product.id }
|
|
750
|
+
});
|
|
751
|
+
} else {
|
|
752
|
+
items.push({
|
|
753
|
+
type: "folder",
|
|
754
|
+
id: moduleId,
|
|
755
|
+
title: moduleTitle,
|
|
756
|
+
image: moduleImage,
|
|
757
|
+
path: `/modules/${moduleId}`,
|
|
758
|
+
providerData: {
|
|
759
|
+
productCount: products.length,
|
|
760
|
+
products: products.map((p) => ({
|
|
761
|
+
id: p.productId || p.id,
|
|
762
|
+
title: p.title || p.name,
|
|
763
|
+
image: p.image
|
|
764
|
+
}))
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return items;
|
|
875
770
|
}
|
|
876
|
-
async
|
|
877
|
-
const
|
|
878
|
-
|
|
879
|
-
if (!
|
|
771
|
+
async getModuleContent(moduleId, currentPath, auth) {
|
|
772
|
+
const modules = await this.getModules(auth);
|
|
773
|
+
const module = modules.find((m) => m.id === moduleId || m.providerData?.productId === moduleId);
|
|
774
|
+
if (!module) return [];
|
|
775
|
+
const providerData = module.providerData;
|
|
776
|
+
const productCount = providerData?.productCount || 0;
|
|
777
|
+
if (productCount === 0 || productCount === 1) {
|
|
778
|
+
const productId = providerData?.productId || moduleId;
|
|
779
|
+
return this.getLibraryFolders(productId, `${currentPath}/libraries`, auth);
|
|
780
|
+
} else {
|
|
781
|
+
const products = providerData?.products || [];
|
|
782
|
+
return products.map((p) => ({
|
|
783
|
+
type: "folder",
|
|
784
|
+
id: p.id,
|
|
785
|
+
title: p.title,
|
|
786
|
+
image: p.image,
|
|
787
|
+
path: `${currentPath}/products/${p.id}`
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async getLibraryFolders(productId, currentPath, auth) {
|
|
792
|
+
console.log("APlay getLibraryFolders called with productId:", productId);
|
|
880
793
|
const pathFn = this.config.endpoints.productLibraries;
|
|
881
|
-
const
|
|
882
|
-
console.log(`APlay fetching libraries from: ${
|
|
883
|
-
const response = await this.apiRequest(
|
|
884
|
-
console.log(
|
|
794
|
+
const apiPath = pathFn(productId);
|
|
795
|
+
console.log(`APlay fetching libraries from: ${apiPath}`);
|
|
796
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
797
|
+
console.log("APlay libraries response:", response ? "received" : "null");
|
|
885
798
|
if (!response) return [];
|
|
886
799
|
const libraries = response.data || response.libraries || response;
|
|
887
|
-
console.log(
|
|
800
|
+
console.log("APlay libraries count:", Array.isArray(libraries) ? libraries.length : "not an array");
|
|
888
801
|
if (!Array.isArray(libraries)) return [];
|
|
889
802
|
return libraries.map((l) => ({
|
|
890
803
|
type: "folder",
|
|
891
804
|
id: l.libraryId || l.id,
|
|
892
805
|
title: l.title || l.name,
|
|
893
806
|
image: l.image,
|
|
894
|
-
|
|
807
|
+
isLeaf: true,
|
|
808
|
+
path: `${currentPath}/${l.libraryId || l.id}`
|
|
895
809
|
}));
|
|
896
810
|
}
|
|
897
|
-
async getMediaFiles(
|
|
898
|
-
|
|
899
|
-
console.log(`APlay getMediaFiles called with libraryId:`, libraryId);
|
|
900
|
-
if (!libraryId) return [];
|
|
811
|
+
async getMediaFiles(libraryId, auth) {
|
|
812
|
+
console.log("APlay getMediaFiles called with libraryId:", libraryId);
|
|
901
813
|
const pathFn = this.config.endpoints.libraryMedia;
|
|
902
|
-
const
|
|
903
|
-
console.log(`APlay fetching media from: ${
|
|
904
|
-
const response = await this.apiRequest(
|
|
905
|
-
console.log(
|
|
814
|
+
const apiPath = pathFn(libraryId);
|
|
815
|
+
console.log(`APlay fetching media from: ${apiPath}`);
|
|
816
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
817
|
+
console.log("APlay media response:", response ? "received" : "null");
|
|
906
818
|
if (!response) return [];
|
|
907
819
|
const mediaItems = response.data || response.media || response;
|
|
908
820
|
if (!Array.isArray(mediaItems)) return [];
|
|
@@ -932,140 +844,97 @@ var APlayProvider = class extends ContentProvider {
|
|
|
932
844
|
if (!url) continue;
|
|
933
845
|
const detectedMediaType = detectMediaType(url, mediaType);
|
|
934
846
|
const fileId = item.mediaId || item.id;
|
|
935
|
-
files.push({
|
|
936
|
-
type: "file",
|
|
937
|
-
id: fileId,
|
|
938
|
-
title: item.title || item.name || item.fileName || "",
|
|
939
|
-
mediaType: detectedMediaType,
|
|
940
|
-
image: thumbnail,
|
|
941
|
-
url,
|
|
942
|
-
muxPlaybackId,
|
|
943
|
-
mediaId: fileId
|
|
944
|
-
});
|
|
847
|
+
files.push({ type: "file", id: fileId, title: item.title || item.name || item.fileName || "", mediaType: detectedMediaType, image: thumbnail, url, muxPlaybackId, mediaId: fileId });
|
|
945
848
|
}
|
|
946
849
|
return files;
|
|
947
850
|
}
|
|
948
|
-
async getPresentations(
|
|
949
|
-
const
|
|
950
|
-
if (
|
|
951
|
-
|
|
851
|
+
async getPresentations(path, auth) {
|
|
852
|
+
const { segments, depth } = parsePath(path);
|
|
853
|
+
if (depth < 4 || segments[0] !== "modules") return null;
|
|
854
|
+
let libraryId;
|
|
855
|
+
const title = "Library";
|
|
856
|
+
if (segments[2] === "products" && depth === 5) {
|
|
857
|
+
libraryId = segments[4];
|
|
858
|
+
} else if (segments[2] === "libraries" && depth === 4) {
|
|
859
|
+
libraryId = segments[3];
|
|
860
|
+
} else {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
const files = await this.getMediaFiles(libraryId, auth);
|
|
952
864
|
if (files.length === 0) return null;
|
|
953
|
-
const presentations = files.map((f) => ({
|
|
954
|
-
|
|
955
|
-
name: f.title,
|
|
956
|
-
actionType: "play",
|
|
957
|
-
files: [f]
|
|
958
|
-
}));
|
|
959
|
-
return {
|
|
960
|
-
id: libraryId,
|
|
961
|
-
name: folder.title,
|
|
962
|
-
image: folder.image,
|
|
963
|
-
sections: [{
|
|
964
|
-
id: `section-${libraryId}`,
|
|
965
|
-
name: folder.title || "Library",
|
|
966
|
-
presentations
|
|
967
|
-
}],
|
|
968
|
-
allFiles: files
|
|
969
|
-
};
|
|
865
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
866
|
+
return { id: libraryId, name: title, sections: [{ id: `section-${libraryId}`, name: title, presentations }], allFiles: files };
|
|
970
867
|
}
|
|
971
868
|
async checkMediaLicense(mediaId, auth) {
|
|
972
869
|
if (!auth) return null;
|
|
973
870
|
try {
|
|
974
871
|
const url = `${this.config.apiBase}/prod/reports/media/license-check`;
|
|
975
|
-
const response = await fetch(url, {
|
|
976
|
-
method: "POST",
|
|
977
|
-
headers: {
|
|
978
|
-
"Authorization": `Bearer ${auth.access_token}`,
|
|
979
|
-
"Content-Type": "application/json",
|
|
980
|
-
"Accept": "application/json"
|
|
981
|
-
},
|
|
982
|
-
body: JSON.stringify({ mediaIds: [mediaId] })
|
|
983
|
-
});
|
|
872
|
+
const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${auth.access_token}`, "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ mediaIds: [mediaId] }) });
|
|
984
873
|
if (!response.ok) return null;
|
|
985
874
|
const data = await response.json();
|
|
986
875
|
const licenseData = Array.isArray(data) ? data : data.data || [];
|
|
987
876
|
const result = licenseData.find((item) => item.mediaId === mediaId);
|
|
988
877
|
if (result?.isLicensed) {
|
|
989
|
-
|
|
990
|
-
return {
|
|
991
|
-
mediaId,
|
|
992
|
-
status: "valid",
|
|
993
|
-
message: "Media is licensed for playback",
|
|
994
|
-
expiresAt: result.expiresAt
|
|
995
|
-
};
|
|
878
|
+
return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
|
|
996
879
|
}
|
|
997
|
-
return {
|
|
998
|
-
mediaId,
|
|
999
|
-
status: "not_licensed",
|
|
1000
|
-
message: "Media is not licensed"
|
|
1001
|
-
};
|
|
880
|
+
return { mediaId, status: "not_licensed", message: "Media is not licensed" };
|
|
1002
881
|
} catch {
|
|
1003
|
-
return {
|
|
1004
|
-
mediaId,
|
|
1005
|
-
status: "unknown",
|
|
1006
|
-
message: "Unable to verify license status"
|
|
1007
|
-
};
|
|
882
|
+
return { mediaId, status: "unknown", message: "Unable to verify license status" };
|
|
1008
883
|
}
|
|
1009
884
|
}
|
|
1010
885
|
};
|
|
1011
886
|
|
|
1012
|
-
// src/providers/SignPresenterProvider.ts
|
|
1013
|
-
var SignPresenterProvider = class
|
|
887
|
+
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
888
|
+
var SignPresenterProvider = class {
|
|
1014
889
|
constructor() {
|
|
1015
|
-
|
|
890
|
+
this.apiHelper = new ApiHelper();
|
|
1016
891
|
this.id = "signpresenter";
|
|
1017
892
|
this.name = "SignPresenter";
|
|
1018
|
-
this.logos = {
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
this.
|
|
1023
|
-
id: "signpresenter",
|
|
1024
|
-
name: "SignPresenter",
|
|
1025
|
-
apiBase: "https://api.signpresenter.com",
|
|
1026
|
-
oauthBase: "https://api.signpresenter.com/oauth",
|
|
1027
|
-
clientId: "lessonsscreen-tv",
|
|
1028
|
-
scopes: ["openid", "profile", "content"],
|
|
1029
|
-
supportsDeviceFlow: true,
|
|
1030
|
-
deviceAuthEndpoint: "/device/authorize",
|
|
1031
|
-
endpoints: {
|
|
1032
|
-
playlists: "/content/playlists",
|
|
1033
|
-
messages: (playlistId) => `/content/playlists/${playlistId}/messages`
|
|
1034
|
-
}
|
|
1035
|
-
};
|
|
893
|
+
this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
|
|
894
|
+
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` } };
|
|
895
|
+
this.requiresAuth = true;
|
|
896
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
897
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
1036
898
|
}
|
|
1037
|
-
|
|
1038
|
-
return
|
|
1039
|
-
browse: true,
|
|
1040
|
-
presentations: true,
|
|
1041
|
-
playlist: false,
|
|
1042
|
-
instructions: false,
|
|
1043
|
-
expandedInstructions: false,
|
|
1044
|
-
mediaLicensing: false
|
|
1045
|
-
};
|
|
899
|
+
async apiRequest(path, auth) {
|
|
900
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
1046
901
|
}
|
|
1047
|
-
async browse(
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
if (!response) return [];
|
|
1052
|
-
const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
|
|
1053
|
-
if (!Array.isArray(playlists)) return [];
|
|
1054
|
-
return playlists.map((p) => ({
|
|
902
|
+
async browse(path, auth) {
|
|
903
|
+
const { segments, depth } = parsePath(path);
|
|
904
|
+
if (depth === 0) {
|
|
905
|
+
return [{
|
|
1055
906
|
type: "folder",
|
|
1056
|
-
id:
|
|
1057
|
-
title:
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
907
|
+
id: "playlists-root",
|
|
908
|
+
title: "Playlists",
|
|
909
|
+
path: "/playlists"
|
|
910
|
+
}];
|
|
911
|
+
}
|
|
912
|
+
const root = segments[0];
|
|
913
|
+
if (root !== "playlists") return [];
|
|
914
|
+
if (depth === 1) {
|
|
915
|
+
return this.getPlaylists(auth);
|
|
916
|
+
}
|
|
917
|
+
if (depth === 2) {
|
|
918
|
+
const playlistId = segments[1];
|
|
919
|
+
return this.getMessages(playlistId, auth);
|
|
1061
920
|
}
|
|
1062
|
-
const level = folder.providerData?.level;
|
|
1063
|
-
if (level === "messages") return this.getMessages(folder, auth);
|
|
1064
921
|
return [];
|
|
1065
922
|
}
|
|
1066
|
-
async
|
|
1067
|
-
const
|
|
1068
|
-
|
|
923
|
+
async getPlaylists(auth) {
|
|
924
|
+
const apiPath = this.config.endpoints.playlists;
|
|
925
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
926
|
+
if (!response) return [];
|
|
927
|
+
const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
|
|
928
|
+
if (!Array.isArray(playlists)) return [];
|
|
929
|
+
return playlists.map((p) => ({
|
|
930
|
+
type: "folder",
|
|
931
|
+
id: p.id,
|
|
932
|
+
title: p.name,
|
|
933
|
+
image: p.image,
|
|
934
|
+
path: `/playlists/${p.id}`
|
|
935
|
+
}));
|
|
936
|
+
}
|
|
937
|
+
async getMessages(playlistId, auth) {
|
|
1069
938
|
const pathFn = this.config.endpoints.messages;
|
|
1070
939
|
const response = await this.apiRequest(pathFn(playlistId), auth);
|
|
1071
940
|
if (!response) return [];
|
|
@@ -1076,93 +945,42 @@ var SignPresenterProvider = class extends ContentProvider {
|
|
|
1076
945
|
if (!msg.url) continue;
|
|
1077
946
|
const url = msg.url;
|
|
1078
947
|
const seconds = msg.seconds;
|
|
1079
|
-
files.push({
|
|
1080
|
-
type: "file",
|
|
1081
|
-
id: msg.id,
|
|
1082
|
-
title: msg.name,
|
|
1083
|
-
mediaType: detectMediaType(url, msg.mediaType),
|
|
1084
|
-
image: msg.thumbnail || msg.image,
|
|
1085
|
-
url,
|
|
1086
|
-
// For direct media providers, embedUrl is the media URL itself
|
|
1087
|
-
embedUrl: url,
|
|
1088
|
-
providerData: seconds !== void 0 ? { seconds } : void 0
|
|
1089
|
-
});
|
|
948
|
+
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 });
|
|
1090
949
|
}
|
|
1091
950
|
return files;
|
|
1092
951
|
}
|
|
1093
|
-
async getPresentations(
|
|
1094
|
-
const
|
|
1095
|
-
if (
|
|
1096
|
-
const
|
|
952
|
+
async getPresentations(path, auth) {
|
|
953
|
+
const { segments, depth } = parsePath(path);
|
|
954
|
+
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
955
|
+
const playlistId = segments[1];
|
|
956
|
+
const files = await this.getMessages(playlistId, auth);
|
|
1097
957
|
if (files.length === 0) return null;
|
|
1098
|
-
const
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
}
|
|
1104
|
-
return {
|
|
1105
|
-
id: playlistId,
|
|
1106
|
-
name: folder.title,
|
|
1107
|
-
image: folder.image,
|
|
1108
|
-
sections: [{
|
|
1109
|
-
id: `section-${playlistId}`,
|
|
1110
|
-
name: folder.title || "Playlist",
|
|
1111
|
-
presentations
|
|
1112
|
-
}],
|
|
1113
|
-
allFiles: files
|
|
1114
|
-
};
|
|
958
|
+
const playlists = await this.getPlaylists(auth);
|
|
959
|
+
const playlist = playlists.find((p) => p.id === playlistId);
|
|
960
|
+
const title = playlist?.title || "Playlist";
|
|
961
|
+
const image = playlist?.image;
|
|
962
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
963
|
+
return { id: playlistId, name: title, image, sections: [{ id: `section-${playlistId}`, name: title, presentations }], allFiles: files };
|
|
1115
964
|
}
|
|
1116
965
|
};
|
|
1117
966
|
|
|
1118
|
-
// src/providers/LessonsChurchProvider.ts
|
|
1119
|
-
var LessonsChurchProvider = class
|
|
967
|
+
// src/providers/lessonsChurch/LessonsChurchProvider.ts
|
|
968
|
+
var LessonsChurchProvider = class {
|
|
1120
969
|
constructor() {
|
|
1121
|
-
super(...arguments);
|
|
1122
970
|
this.id = "lessonschurch";
|
|
1123
971
|
this.name = "Lessons.church";
|
|
1124
|
-
this.logos = {
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
this.
|
|
1129
|
-
id: "lessonschurch",
|
|
1130
|
-
name: "Lessons.church",
|
|
1131
|
-
apiBase: "https://api.lessons.church",
|
|
1132
|
-
oauthBase: "",
|
|
1133
|
-
clientId: "",
|
|
1134
|
-
scopes: [],
|
|
1135
|
-
endpoints: {
|
|
1136
|
-
programs: "/programs/public",
|
|
1137
|
-
studies: (programId) => `/studies/public/program/${programId}`,
|
|
1138
|
-
lessons: (studyId) => `/lessons/public/study/${studyId}`,
|
|
1139
|
-
venues: (lessonId) => `/venues/public/lesson/${lessonId}`,
|
|
1140
|
-
playlist: (venueId) => `/venues/playlist/${venueId}`,
|
|
1141
|
-
feed: (venueId) => `/venues/public/feed/${venueId}`,
|
|
1142
|
-
addOns: "/addOns/public",
|
|
1143
|
-
addOnDetail: (id) => `/addOns/public/${id}`
|
|
1144
|
-
}
|
|
1145
|
-
};
|
|
1146
|
-
}
|
|
1147
|
-
requiresAuth() {
|
|
1148
|
-
return false;
|
|
972
|
+
this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
|
|
973
|
+
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}` } };
|
|
974
|
+
this.requiresAuth = false;
|
|
975
|
+
this.authTypes = ["none"];
|
|
976
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
|
|
1149
977
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
browse: true,
|
|
1153
|
-
presentations: true,
|
|
1154
|
-
playlist: true,
|
|
1155
|
-
instructions: true,
|
|
1156
|
-
expandedInstructions: true,
|
|
1157
|
-
mediaLicensing: false
|
|
1158
|
-
};
|
|
1159
|
-
}
|
|
1160
|
-
async getPlaylist(folder, _auth, resolution) {
|
|
1161
|
-
const venueId = folder.providerData?.venueId;
|
|
978
|
+
async getPlaylist(path, _auth, resolution) {
|
|
979
|
+
const venueId = getSegment(path, 4);
|
|
1162
980
|
if (!venueId) return null;
|
|
1163
|
-
let
|
|
1164
|
-
if (resolution)
|
|
1165
|
-
const response = await this.apiRequest(
|
|
981
|
+
let apiPath = `/venues/playlist/${venueId}`;
|
|
982
|
+
if (resolution) apiPath += `?resolution=${resolution}`;
|
|
983
|
+
const response = await this.apiRequest(apiPath);
|
|
1166
984
|
if (!response) return null;
|
|
1167
985
|
const files = [];
|
|
1168
986
|
const messages = response.messages || [];
|
|
@@ -1174,15 +992,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1174
992
|
if (!f.url) continue;
|
|
1175
993
|
const url = f.url;
|
|
1176
994
|
const fileId = f.id || `playlist-${fileIndex++}`;
|
|
1177
|
-
files.push({
|
|
1178
|
-
type: "file",
|
|
1179
|
-
id: fileId,
|
|
1180
|
-
title: f.name || msg.name,
|
|
1181
|
-
mediaType: detectMediaType(url, f.fileType),
|
|
1182
|
-
image: response.lessonImage,
|
|
1183
|
-
url,
|
|
1184
|
-
providerData: { seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo }
|
|
1185
|
-
});
|
|
995
|
+
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 } });
|
|
1186
996
|
}
|
|
1187
997
|
}
|
|
1188
998
|
return files;
|
|
@@ -1197,48 +1007,32 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1197
1007
|
return null;
|
|
1198
1008
|
}
|
|
1199
1009
|
}
|
|
1200
|
-
async browse(
|
|
1201
|
-
|
|
1010
|
+
async browse(path, _auth) {
|
|
1011
|
+
const { segments, depth } = parsePath(path);
|
|
1012
|
+
console.log("[LessonsChurchProvider.browse] path:", path, "depth:", depth, "segments:", segments);
|
|
1013
|
+
if (depth === 0) {
|
|
1202
1014
|
return [
|
|
1203
|
-
{
|
|
1204
|
-
|
|
1205
|
-
id: "lessons-root",
|
|
1206
|
-
title: "Lessons",
|
|
1207
|
-
providerData: { level: "programs" }
|
|
1208
|
-
},
|
|
1209
|
-
{
|
|
1210
|
-
type: "folder",
|
|
1211
|
-
id: "addons-root",
|
|
1212
|
-
title: "Add-Ons",
|
|
1213
|
-
providerData: { level: "addOnCategories" }
|
|
1214
|
-
}
|
|
1015
|
+
{ type: "folder", id: "lessons-root", title: "Lessons", path: "/lessons" },
|
|
1016
|
+
{ type: "folder", id: "addons-root", title: "Add-Ons", path: "/addons" }
|
|
1215
1017
|
];
|
|
1216
1018
|
}
|
|
1217
|
-
const
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
// Add-ons hierarchy
|
|
1231
|
-
case "addOnCategories":
|
|
1232
|
-
return this.getAddOnCategories();
|
|
1233
|
-
case "addOns":
|
|
1234
|
-
return this.getAddOnsByCategory(folder);
|
|
1235
|
-
default:
|
|
1236
|
-
return [];
|
|
1237
|
-
}
|
|
1019
|
+
const root = segments[0];
|
|
1020
|
+
if (root === "lessons") return this.browseLessons(path, segments);
|
|
1021
|
+
if (root === "addons") return this.browseAddOns(path, segments);
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
async browseLessons(currentPath, segments) {
|
|
1025
|
+
const depth = segments.length;
|
|
1026
|
+
if (depth === 1) return this.getPrograms();
|
|
1027
|
+
if (depth === 2) return this.getStudies(segments[1], currentPath);
|
|
1028
|
+
if (depth === 3) return this.getLessons(segments[2], currentPath);
|
|
1029
|
+
if (depth === 4) return this.getVenues(segments[3], currentPath);
|
|
1030
|
+
if (depth === 5) return this.getPlaylistFiles(segments[4]);
|
|
1031
|
+
return [];
|
|
1238
1032
|
}
|
|
1239
1033
|
async getPrograms() {
|
|
1240
|
-
const
|
|
1241
|
-
const response = await this.apiRequest(
|
|
1034
|
+
const apiPath = this.config.endpoints.programs;
|
|
1035
|
+
const response = await this.apiRequest(apiPath);
|
|
1242
1036
|
if (!response) return [];
|
|
1243
1037
|
const programs = Array.isArray(response) ? response : [];
|
|
1244
1038
|
return programs.map((p) => ({
|
|
@@ -1246,12 +1040,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1246
1040
|
id: p.id,
|
|
1247
1041
|
title: p.name,
|
|
1248
1042
|
image: p.image,
|
|
1249
|
-
|
|
1043
|
+
path: `/lessons/${p.id}`
|
|
1250
1044
|
}));
|
|
1251
1045
|
}
|
|
1252
|
-
async getStudies(
|
|
1253
|
-
const programId = folder.providerData?.programId;
|
|
1254
|
-
if (!programId) return [];
|
|
1046
|
+
async getStudies(programId, currentPath) {
|
|
1255
1047
|
const pathFn = this.config.endpoints.studies;
|
|
1256
1048
|
const response = await this.apiRequest(pathFn(programId));
|
|
1257
1049
|
if (!response) return [];
|
|
@@ -1261,12 +1053,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1261
1053
|
id: s.id,
|
|
1262
1054
|
title: s.name,
|
|
1263
1055
|
image: s.image,
|
|
1264
|
-
|
|
1056
|
+
path: `${currentPath}/${s.id}`
|
|
1265
1057
|
}));
|
|
1266
1058
|
}
|
|
1267
|
-
async getLessons(
|
|
1268
|
-
const studyId = folder.providerData?.studyId;
|
|
1269
|
-
if (!studyId) return [];
|
|
1059
|
+
async getLessons(studyId, currentPath) {
|
|
1270
1060
|
const pathFn = this.config.endpoints.lessons;
|
|
1271
1061
|
const response = await this.apiRequest(pathFn(studyId));
|
|
1272
1062
|
if (!response) return [];
|
|
@@ -1276,31 +1066,42 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1276
1066
|
id: l.id,
|
|
1277
1067
|
title: l.name || l.title,
|
|
1278
1068
|
image: l.image,
|
|
1279
|
-
|
|
1069
|
+
path: `${currentPath}/${l.id}`,
|
|
1070
|
+
providerData: { lessonImage: l.image }
|
|
1071
|
+
// Keep for display on venues
|
|
1280
1072
|
}));
|
|
1281
1073
|
}
|
|
1282
|
-
async getVenues(
|
|
1283
|
-
const lessonId = folder.providerData?.lessonId;
|
|
1284
|
-
if (!lessonId) return [];
|
|
1074
|
+
async getVenues(lessonId, currentPath) {
|
|
1285
1075
|
const pathFn = this.config.endpoints.venues;
|
|
1286
1076
|
const response = await this.apiRequest(pathFn(lessonId));
|
|
1287
1077
|
if (!response) return [];
|
|
1078
|
+
const lessonResponse = await this.apiRequest(`/lessons/public/${lessonId}`);
|
|
1079
|
+
const lessonImage = lessonResponse?.image;
|
|
1288
1080
|
const venues = Array.isArray(response) ? response : [];
|
|
1289
|
-
|
|
1081
|
+
const result = venues.map((v) => ({
|
|
1290
1082
|
type: "folder",
|
|
1291
1083
|
id: v.id,
|
|
1292
1084
|
title: v.name,
|
|
1293
|
-
image:
|
|
1294
|
-
|
|
1085
|
+
image: lessonImage,
|
|
1086
|
+
isLeaf: true,
|
|
1087
|
+
path: `${currentPath}/${v.id}`
|
|
1295
1088
|
}));
|
|
1089
|
+
console.log("[LessonsChurchProvider.getVenues] returning:", result.map((r) => ({ id: r.id, title: r.title, isLeaf: r.isLeaf })));
|
|
1090
|
+
return result;
|
|
1296
1091
|
}
|
|
1297
|
-
async getPlaylistFiles(
|
|
1298
|
-
const files = await this.getPlaylist(
|
|
1092
|
+
async getPlaylistFiles(venueId) {
|
|
1093
|
+
const files = await this.getPlaylist(`/lessons/_/_/_/${venueId}`, null);
|
|
1299
1094
|
return files || [];
|
|
1300
1095
|
}
|
|
1096
|
+
async browseAddOns(_currentPath, segments) {
|
|
1097
|
+
const depth = segments.length;
|
|
1098
|
+
if (depth === 1) return this.getAddOnCategories();
|
|
1099
|
+
if (depth === 2) return this.getAddOnsByCategory(segments[1]);
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
1301
1102
|
async getAddOnCategories() {
|
|
1302
|
-
const
|
|
1303
|
-
const response = await this.apiRequest(
|
|
1103
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1104
|
+
const response = await this.apiRequest(apiPath);
|
|
1304
1105
|
if (!response) return [];
|
|
1305
1106
|
const addOns = Array.isArray(response) ? response : [];
|
|
1306
1107
|
const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
|
|
@@ -1308,17 +1109,16 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1308
1109
|
type: "folder",
|
|
1309
1110
|
id: `category-${category}`,
|
|
1310
1111
|
title: category,
|
|
1311
|
-
|
|
1312
|
-
level: "addOns",
|
|
1313
|
-
category,
|
|
1314
|
-
allAddOns: addOns
|
|
1315
|
-
}
|
|
1112
|
+
path: `/addons/${encodeURIComponent(category)}`
|
|
1316
1113
|
}));
|
|
1317
1114
|
}
|
|
1318
|
-
async getAddOnsByCategory(
|
|
1319
|
-
const
|
|
1320
|
-
const
|
|
1321
|
-
const
|
|
1115
|
+
async getAddOnsByCategory(category) {
|
|
1116
|
+
const decodedCategory = decodeURIComponent(category);
|
|
1117
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1118
|
+
const response = await this.apiRequest(apiPath);
|
|
1119
|
+
if (!response) return [];
|
|
1120
|
+
const allAddOns = Array.isArray(response) ? response : [];
|
|
1121
|
+
const filtered = allAddOns.filter((a) => a.category === decodedCategory);
|
|
1322
1122
|
const files = [];
|
|
1323
1123
|
for (const addOn of filtered) {
|
|
1324
1124
|
const file = await this.convertAddOnToFile(addOn);
|
|
@@ -1328,8 +1128,8 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1328
1128
|
}
|
|
1329
1129
|
async convertAddOnToFile(addOn) {
|
|
1330
1130
|
const pathFn = this.config.endpoints.addOnDetail;
|
|
1331
|
-
const
|
|
1332
|
-
const detail = await this.apiRequest(
|
|
1131
|
+
const apiPath = pathFn(addOn.id);
|
|
1132
|
+
const detail = await this.apiRequest(apiPath);
|
|
1333
1133
|
if (!detail) return null;
|
|
1334
1134
|
let url = "";
|
|
1335
1135
|
let mediaType = "video";
|
|
@@ -1346,51 +1146,30 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1346
1146
|
} else {
|
|
1347
1147
|
return null;
|
|
1348
1148
|
}
|
|
1349
|
-
return {
|
|
1350
|
-
type: "file",
|
|
1351
|
-
id: addOn.id,
|
|
1352
|
-
title: addOn.name,
|
|
1353
|
-
mediaType,
|
|
1354
|
-
image: addOn.image,
|
|
1355
|
-
url,
|
|
1356
|
-
embedUrl: `https://lessons.church/embed/addon/${addOn.id}`,
|
|
1357
|
-
providerData: {
|
|
1358
|
-
seconds,
|
|
1359
|
-
loopVideo: video?.loopVideo || false
|
|
1360
|
-
}
|
|
1361
|
-
};
|
|
1149
|
+
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 } };
|
|
1362
1150
|
}
|
|
1363
|
-
async getPresentations(
|
|
1364
|
-
const venueId =
|
|
1151
|
+
async getPresentations(path, _auth) {
|
|
1152
|
+
const venueId = getSegment(path, 4);
|
|
1365
1153
|
if (!venueId) return null;
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
const venueData = await this.apiRequest(path);
|
|
1154
|
+
const apiPath = `/venues/public/feed/${venueId}`;
|
|
1155
|
+
const venueData = await this.apiRequest(apiPath);
|
|
1369
1156
|
if (!venueData) return null;
|
|
1370
1157
|
return this.convertVenueToPlan(venueData);
|
|
1371
1158
|
}
|
|
1372
|
-
async getInstructions(
|
|
1373
|
-
const venueId =
|
|
1159
|
+
async getInstructions(path, _auth) {
|
|
1160
|
+
const venueId = getSegment(path, 4);
|
|
1374
1161
|
if (!venueId) return null;
|
|
1375
1162
|
const response = await this.apiRequest(`/venues/public/planItems/${venueId}`);
|
|
1376
1163
|
if (!response) return null;
|
|
1377
|
-
const processItem = (item) =>
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
relatedId: item.relatedId
|
|
1381
|
-
label: item.label,
|
|
1382
|
-
description: item.description,
|
|
1383
|
-
seconds: item.seconds,
|
|
1384
|
-
children: item.children?.map(processItem),
|
|
1385
|
-
embedUrl: this.getEmbedUrl(item.itemType, item.relatedId)
|
|
1386
|
-
});
|
|
1387
|
-
return {
|
|
1388
|
-
venueName: response.venueName,
|
|
1389
|
-
items: (response.items || []).map(processItem)
|
|
1164
|
+
const processItem = (item) => {
|
|
1165
|
+
const itemType = this.normalizeItemType(item.itemType);
|
|
1166
|
+
const relatedId = item.relatedId;
|
|
1167
|
+
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) };
|
|
1390
1168
|
};
|
|
1169
|
+
return { venueName: response.venueName, items: (response.items || []).map(processItem) };
|
|
1391
1170
|
}
|
|
1392
|
-
async getExpandedInstructions(
|
|
1393
|
-
const venueId =
|
|
1171
|
+
async getExpandedInstructions(path, _auth) {
|
|
1172
|
+
const venueId = getSegment(path, 4);
|
|
1394
1173
|
if (!venueId) return null;
|
|
1395
1174
|
const [planItemsResponse, actionsResponse] = await Promise.all([
|
|
1396
1175
|
this.apiRequest(`/venues/public/planItems/${venueId}`),
|
|
@@ -1401,66 +1180,47 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1401
1180
|
if (actionsResponse?.sections) {
|
|
1402
1181
|
for (const section of actionsResponse.sections) {
|
|
1403
1182
|
if (section.id && section.actions) {
|
|
1404
|
-
sectionActionsMap.set(section.id, section.actions.map((action) =>
|
|
1405
|
-
|
|
1406
|
-
itemType: "
|
|
1407
|
-
|
|
1408
|
-
label: action.name,
|
|
1409
|
-
description: action.actionType,
|
|
1410
|
-
seconds: action.seconds,
|
|
1411
|
-
embedUrl: this.getEmbedUrl("lessonAction", action.id)
|
|
1412
|
-
})));
|
|
1183
|
+
sectionActionsMap.set(section.id, section.actions.map((action) => {
|
|
1184
|
+
const embedUrl = this.getEmbedUrl("action", action.id);
|
|
1185
|
+
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 }] };
|
|
1186
|
+
}));
|
|
1413
1187
|
}
|
|
1414
1188
|
}
|
|
1415
1189
|
}
|
|
1416
1190
|
const processItem = (item) => {
|
|
1417
1191
|
const relatedId = item.relatedId;
|
|
1418
|
-
const itemType = item.itemType;
|
|
1192
|
+
const itemType = this.normalizeItemType(item.itemType);
|
|
1419
1193
|
const children = item.children;
|
|
1420
1194
|
let processedChildren;
|
|
1421
1195
|
if (children) {
|
|
1422
1196
|
processedChildren = children.map((child) => {
|
|
1423
1197
|
const childRelatedId = child.relatedId;
|
|
1198
|
+
const childItemType = this.normalizeItemType(child.itemType);
|
|
1424
1199
|
if (childRelatedId && sectionActionsMap.has(childRelatedId)) {
|
|
1425
|
-
return {
|
|
1426
|
-
id: child.id,
|
|
1427
|
-
itemType: child.itemType,
|
|
1428
|
-
relatedId: childRelatedId,
|
|
1429
|
-
label: child.label,
|
|
1430
|
-
description: child.description,
|
|
1431
|
-
seconds: child.seconds,
|
|
1432
|
-
children: sectionActionsMap.get(childRelatedId),
|
|
1433
|
-
embedUrl: this.getEmbedUrl(child.itemType, childRelatedId)
|
|
1434
|
-
};
|
|
1200
|
+
return { id: child.id, itemType: childItemType, relatedId: childRelatedId, label: child.label, description: child.description, seconds: child.seconds, children: sectionActionsMap.get(childRelatedId), embedUrl: this.getEmbedUrl(childItemType, childRelatedId) };
|
|
1435
1201
|
}
|
|
1436
1202
|
return processItem(child);
|
|
1437
1203
|
});
|
|
1438
1204
|
}
|
|
1439
|
-
return {
|
|
1440
|
-
id: item.id,
|
|
1441
|
-
itemType,
|
|
1442
|
-
relatedId,
|
|
1443
|
-
label: item.label,
|
|
1444
|
-
description: item.description,
|
|
1445
|
-
seconds: item.seconds,
|
|
1446
|
-
children: processedChildren,
|
|
1447
|
-
embedUrl: this.getEmbedUrl(itemType, relatedId)
|
|
1448
|
-
};
|
|
1449
|
-
};
|
|
1450
|
-
return {
|
|
1451
|
-
venueName: planItemsResponse.venueName,
|
|
1452
|
-
items: (planItemsResponse.items || []).map(processItem)
|
|
1205
|
+
return { id: item.id, itemType, relatedId, label: item.label, description: item.description, seconds: item.seconds, children: processedChildren, embedUrl: this.getEmbedUrl(itemType, relatedId) };
|
|
1453
1206
|
};
|
|
1207
|
+
return { venueName: planItemsResponse.venueName, items: (planItemsResponse.items || []).map(processItem) };
|
|
1208
|
+
}
|
|
1209
|
+
normalizeItemType(type) {
|
|
1210
|
+
if (type === "lessonSection") return "section";
|
|
1211
|
+
if (type === "lessonAction") return "action";
|
|
1212
|
+
if (type === "lessonAddOn") return "addon";
|
|
1213
|
+
return type;
|
|
1454
1214
|
}
|
|
1455
1215
|
getEmbedUrl(itemType, relatedId) {
|
|
1456
1216
|
if (!relatedId) return void 0;
|
|
1457
1217
|
const baseUrl = "https://lessons.church";
|
|
1458
1218
|
switch (itemType) {
|
|
1459
|
-
case "
|
|
1219
|
+
case "action":
|
|
1460
1220
|
return `${baseUrl}/embed/action/${relatedId}`;
|
|
1461
|
-
case "
|
|
1221
|
+
case "addon":
|
|
1462
1222
|
return `${baseUrl}/embed/addon/${relatedId}`;
|
|
1463
|
-
case "
|
|
1223
|
+
case "section":
|
|
1464
1224
|
return `${baseUrl}/embed/section/${relatedId}`;
|
|
1465
1225
|
default:
|
|
1466
1226
|
return void 0;
|
|
@@ -1478,16 +1238,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1478
1238
|
for (const file of action.files || []) {
|
|
1479
1239
|
if (!file.url) continue;
|
|
1480
1240
|
const embedUrl = action.id ? `https://lessons.church/embed/action/${action.id}` : void 0;
|
|
1481
|
-
const contentFile = {
|
|
1482
|
-
type: "file",
|
|
1483
|
-
id: file.id || "",
|
|
1484
|
-
title: file.name || "",
|
|
1485
|
-
mediaType: detectMediaType(file.url, file.fileType),
|
|
1486
|
-
image: venue.lessonImage,
|
|
1487
|
-
url: file.url,
|
|
1488
|
-
embedUrl,
|
|
1489
|
-
providerData: { seconds: file.seconds, streamUrl: file.streamUrl }
|
|
1490
|
-
};
|
|
1241
|
+
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 } };
|
|
1491
1242
|
files.push(contentFile);
|
|
1492
1243
|
allFiles.push(contentFile);
|
|
1493
1244
|
}
|
|
@@ -1499,51 +1250,69 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1499
1250
|
sections.push({ id: section.id || "", name: section.name || "Untitled Section", presentations });
|
|
1500
1251
|
}
|
|
1501
1252
|
}
|
|
1502
|
-
return {
|
|
1503
|
-
id: venue.id || "",
|
|
1504
|
-
name: venue.lessonName || venue.name || "Plan",
|
|
1505
|
-
description: venue.lessonDescription,
|
|
1506
|
-
image: venue.lessonImage,
|
|
1507
|
-
sections,
|
|
1508
|
-
allFiles
|
|
1509
|
-
};
|
|
1253
|
+
return { id: venue.id || "", name: venue.lessonName || venue.name || "Plan", description: venue.lessonDescription, image: venue.lessonImage, sections, allFiles };
|
|
1510
1254
|
}
|
|
1511
1255
|
};
|
|
1512
1256
|
|
|
1513
|
-
// src/providers/
|
|
1514
|
-
function
|
|
1257
|
+
// src/providers/b1Church/auth.ts
|
|
1258
|
+
async function generateCodeChallenge(verifier) {
|
|
1259
|
+
const encoder = new TextEncoder();
|
|
1260
|
+
const data = encoder.encode(verifier);
|
|
1261
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1262
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
1263
|
+
let binary = "";
|
|
1264
|
+
for (let i = 0; i < hashArray.length; i++) {
|
|
1265
|
+
binary += String.fromCharCode(hashArray[i]);
|
|
1266
|
+
}
|
|
1267
|
+
const base64 = btoa(binary);
|
|
1268
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1269
|
+
}
|
|
1270
|
+
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
1271
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1515
1272
|
const oauthParams = new URLSearchParams({
|
|
1273
|
+
response_type: "code",
|
|
1516
1274
|
client_id: config.clientId,
|
|
1517
1275
|
redirect_uri: redirectUri,
|
|
1518
|
-
|
|
1519
|
-
|
|
1276
|
+
scope: config.scopes.join(" "),
|
|
1277
|
+
code_challenge: codeChallenge,
|
|
1278
|
+
code_challenge_method: "S256",
|
|
1279
|
+
state: state || ""
|
|
1520
1280
|
});
|
|
1521
|
-
|
|
1522
|
-
|
|
1281
|
+
const url = `${appBase}/oauth?${oauthParams.toString()}`;
|
|
1282
|
+
return { url, challengeMethod: "S256" };
|
|
1283
|
+
}
|
|
1284
|
+
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
1285
|
+
try {
|
|
1286
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, code_verifier: codeVerifier, redirect_uri: redirectUri };
|
|
1287
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
1288
|
+
console.log(`B1Church PKCE token exchange request to: ${tokenUrl}`);
|
|
1289
|
+
console.log(` - client_id: ${config.clientId}`);
|
|
1290
|
+
console.log(` - redirect_uri: ${redirectUri}`);
|
|
1291
|
+
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
1292
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1293
|
+
console.log(`B1Church token response status: ${response.status}`);
|
|
1294
|
+
if (!response.ok) {
|
|
1295
|
+
const errorText = await response.text();
|
|
1296
|
+
console.error(`B1Church token exchange failed: ${response.status} - ${errorText}`);
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
const data = await response.json();
|
|
1300
|
+
console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
|
|
1301
|
+
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(" ") };
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
console.error("B1Church token exchange error:", error);
|
|
1304
|
+
return null;
|
|
1523
1305
|
}
|
|
1524
|
-
const returnUrl = `/oauth?${oauthParams.toString()}`;
|
|
1525
|
-
const url = `${appBase}/login?returnUrl=${encodeURIComponent(returnUrl)}`;
|
|
1526
|
-
return { url, challengeMethod: "none" };
|
|
1527
1306
|
}
|
|
1528
1307
|
async function exchangeCodeForTokensWithSecret(config, code, redirectUri, clientSecret) {
|
|
1529
1308
|
try {
|
|
1530
|
-
const params = {
|
|
1531
|
-
grant_type: "authorization_code",
|
|
1532
|
-
code,
|
|
1533
|
-
client_id: config.clientId,
|
|
1534
|
-
client_secret: clientSecret,
|
|
1535
|
-
redirect_uri: redirectUri
|
|
1536
|
-
};
|
|
1309
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, client_secret: clientSecret, redirect_uri: redirectUri };
|
|
1537
1310
|
const tokenUrl = `${config.oauthBase}/token`;
|
|
1538
1311
|
console.log(`B1Church token exchange request to: ${tokenUrl}`);
|
|
1539
1312
|
console.log(` - client_id: ${config.clientId}`);
|
|
1540
1313
|
console.log(` - redirect_uri: ${redirectUri}`);
|
|
1541
1314
|
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
1542
|
-
const response = await fetch(tokenUrl, {
|
|
1543
|
-
method: "POST",
|
|
1544
|
-
headers: { "Content-Type": "application/json" },
|
|
1545
|
-
body: JSON.stringify(params)
|
|
1546
|
-
});
|
|
1315
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1547
1316
|
console.log(`B1Church token response status: ${response.status}`);
|
|
1548
1317
|
if (!response.ok) {
|
|
1549
1318
|
const errorText = await response.text();
|
|
@@ -1552,14 +1321,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
|
|
|
1552
1321
|
}
|
|
1553
1322
|
const data = await response.json();
|
|
1554
1323
|
console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
|
|
1555
|
-
return {
|
|
1556
|
-
access_token: data.access_token,
|
|
1557
|
-
refresh_token: data.refresh_token,
|
|
1558
|
-
token_type: data.token_type || "Bearer",
|
|
1559
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1560
|
-
expires_in: data.expires_in,
|
|
1561
|
-
scope: data.scope || config.scopes.join(" ")
|
|
1562
|
-
};
|
|
1324
|
+
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(" ") };
|
|
1563
1325
|
} catch (error) {
|
|
1564
1326
|
console.error("B1Church token exchange error:", error);
|
|
1565
1327
|
return null;
|
|
@@ -1568,27 +1330,11 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
|
|
|
1568
1330
|
async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
1569
1331
|
if (!auth.refresh_token) return null;
|
|
1570
1332
|
try {
|
|
1571
|
-
const params = {
|
|
1572
|
-
|
|
1573
|
-
refresh_token: auth.refresh_token,
|
|
1574
|
-
client_id: config.clientId,
|
|
1575
|
-
client_secret: clientSecret
|
|
1576
|
-
};
|
|
1577
|
-
const response = await fetch(`${config.oauthBase}/token`, {
|
|
1578
|
-
method: "POST",
|
|
1579
|
-
headers: { "Content-Type": "application/json" },
|
|
1580
|
-
body: JSON.stringify(params)
|
|
1581
|
-
});
|
|
1333
|
+
const params = { grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: config.clientId, client_secret: clientSecret };
|
|
1334
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1582
1335
|
if (!response.ok) return null;
|
|
1583
1336
|
const data = await response.json();
|
|
1584
|
-
return {
|
|
1585
|
-
access_token: data.access_token,
|
|
1586
|
-
refresh_token: data.refresh_token || auth.refresh_token,
|
|
1587
|
-
token_type: data.token_type || "Bearer",
|
|
1588
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1589
|
-
expires_in: data.expires_in,
|
|
1590
|
-
scope: data.scope || auth.scope
|
|
1591
|
-
};
|
|
1337
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
|
|
1592
1338
|
} catch {
|
|
1593
1339
|
return null;
|
|
1594
1340
|
}
|
|
@@ -1596,14 +1342,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
|
1596
1342
|
async function initiateDeviceFlow(config) {
|
|
1597
1343
|
if (!config.supportsDeviceFlow || !config.deviceAuthEndpoint) return null;
|
|
1598
1344
|
try {
|
|
1599
|
-
const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, {
|
|
1600
|
-
method: "POST",
|
|
1601
|
-
headers: { "Content-Type": "application/json" },
|
|
1602
|
-
body: JSON.stringify({
|
|
1603
|
-
client_id: config.clientId,
|
|
1604
|
-
scope: config.scopes.join(" ")
|
|
1605
|
-
})
|
|
1606
|
-
});
|
|
1345
|
+
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(" ") }) });
|
|
1607
1346
|
if (!response.ok) {
|
|
1608
1347
|
const errorText = await response.text();
|
|
1609
1348
|
console.error(`B1Church device authorize failed: ${response.status} - ${errorText}`);
|
|
@@ -1617,25 +1356,10 @@ async function initiateDeviceFlow(config) {
|
|
|
1617
1356
|
}
|
|
1618
1357
|
async function pollDeviceFlowToken(config, deviceCode) {
|
|
1619
1358
|
try {
|
|
1620
|
-
const response = await fetch(`${config.oauthBase}/token`, {
|
|
1621
|
-
method: "POST",
|
|
1622
|
-
headers: { "Content-Type": "application/json" },
|
|
1623
|
-
body: JSON.stringify({
|
|
1624
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1625
|
-
device_code: deviceCode,
|
|
1626
|
-
client_id: config.clientId
|
|
1627
|
-
})
|
|
1628
|
-
});
|
|
1359
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
|
|
1629
1360
|
if (response.ok) {
|
|
1630
1361
|
const data = await response.json();
|
|
1631
|
-
return {
|
|
1632
|
-
access_token: data.access_token,
|
|
1633
|
-
refresh_token: data.refresh_token,
|
|
1634
|
-
token_type: data.token_type || "Bearer",
|
|
1635
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1636
|
-
expires_in: data.expires_in,
|
|
1637
|
-
scope: data.scope || config.scopes.join(" ")
|
|
1638
|
-
};
|
|
1362
|
+
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(" ") };
|
|
1639
1363
|
}
|
|
1640
1364
|
const errorData = await response.json();
|
|
1641
1365
|
switch (errorData.error) {
|
|
@@ -1655,7 +1379,7 @@ async function pollDeviceFlowToken(config, deviceCode) {
|
|
|
1655
1379
|
}
|
|
1656
1380
|
}
|
|
1657
1381
|
|
|
1658
|
-
// src/providers/
|
|
1382
|
+
// src/providers/b1Church/api.ts
|
|
1659
1383
|
var API_BASE = "https://api.churchapps.org";
|
|
1660
1384
|
var LESSONS_API_BASE = "https://api.lessons.church";
|
|
1661
1385
|
var CONTENT_API_BASE = "https://contentapi.churchapps.org";
|
|
@@ -1687,10 +1411,7 @@ async function fetchPlans(planTypeId, auth) {
|
|
|
1687
1411
|
async function fetchVenueFeed(venueId) {
|
|
1688
1412
|
try {
|
|
1689
1413
|
const url = `${LESSONS_API_BASE}/venues/public/feed/${venueId}`;
|
|
1690
|
-
const response = await fetch(url, {
|
|
1691
|
-
method: "GET",
|
|
1692
|
-
headers: { Accept: "application/json" }
|
|
1693
|
-
});
|
|
1414
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1694
1415
|
if (!response.ok) return null;
|
|
1695
1416
|
return await response.json();
|
|
1696
1417
|
} catch {
|
|
@@ -1700,9 +1421,29 @@ async function fetchVenueFeed(venueId) {
|
|
|
1700
1421
|
async function fetchArrangementKey(churchId, arrangementId) {
|
|
1701
1422
|
try {
|
|
1702
1423
|
const url = `${CONTENT_API_BASE}/arrangementKeys/presenter/${churchId}/${arrangementId}`;
|
|
1424
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1425
|
+
if (!response.ok) return null;
|
|
1426
|
+
return await response.json();
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async function fetchFromProviderProxy(method, ministryId, providerId, path, authData, resolution) {
|
|
1432
|
+
try {
|
|
1433
|
+
const url = `${API_BASE}/doing/providerProxy/${method}`;
|
|
1434
|
+
const headers = {
|
|
1435
|
+
"Content-Type": "application/json",
|
|
1436
|
+
Accept: "application/json"
|
|
1437
|
+
};
|
|
1438
|
+
if (authData) {
|
|
1439
|
+
headers["Authorization"] = `Bearer ${authData.access_token}`;
|
|
1440
|
+
}
|
|
1441
|
+
const body = { ministryId, providerId, path };
|
|
1442
|
+
if (resolution !== void 0) body.resolution = resolution;
|
|
1703
1443
|
const response = await fetch(url, {
|
|
1704
|
-
method: "
|
|
1705
|
-
headers
|
|
1444
|
+
method: "POST",
|
|
1445
|
+
headers,
|
|
1446
|
+
body: JSON.stringify(body)
|
|
1706
1447
|
});
|
|
1707
1448
|
if (!response.ok) return null;
|
|
1708
1449
|
return await response.json();
|
|
@@ -1711,107 +1452,39 @@ async function fetchArrangementKey(churchId, arrangementId) {
|
|
|
1711
1452
|
}
|
|
1712
1453
|
}
|
|
1713
1454
|
|
|
1714
|
-
// src/providers/
|
|
1455
|
+
// src/providers/b1Church/converters.ts
|
|
1715
1456
|
function ministryToFolder(ministry) {
|
|
1716
|
-
return {
|
|
1717
|
-
type: "folder",
|
|
1718
|
-
id: ministry.id,
|
|
1719
|
-
title: ministry.name,
|
|
1720
|
-
image: ministry.photoUrl,
|
|
1721
|
-
providerData: {
|
|
1722
|
-
level: "ministry",
|
|
1723
|
-
ministryId: ministry.id,
|
|
1724
|
-
churchId: ministry.churchId
|
|
1725
|
-
}
|
|
1726
|
-
};
|
|
1457
|
+
return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl, providerData: { level: "ministry", ministryId: ministry.id, churchId: ministry.churchId } };
|
|
1727
1458
|
}
|
|
1728
1459
|
function planTypeToFolder(planType, ministryId) {
|
|
1729
|
-
return {
|
|
1730
|
-
type: "folder",
|
|
1731
|
-
id: planType.id,
|
|
1732
|
-
title: planType.name,
|
|
1733
|
-
providerData: {
|
|
1734
|
-
level: "planType",
|
|
1735
|
-
planTypeId: planType.id,
|
|
1736
|
-
ministryId,
|
|
1737
|
-
churchId: planType.churchId
|
|
1738
|
-
}
|
|
1739
|
-
};
|
|
1460
|
+
return { type: "folder", id: planType.id, title: planType.name, path: "", providerData: { level: "planType", planTypeId: planType.id, ministryId, churchId: planType.churchId } };
|
|
1740
1461
|
}
|
|
1741
1462
|
function planToFolder(plan) {
|
|
1742
|
-
return {
|
|
1743
|
-
type: "folder",
|
|
1744
|
-
id: plan.id,
|
|
1745
|
-
title: plan.name,
|
|
1746
|
-
providerData: {
|
|
1747
|
-
isLeaf: true,
|
|
1748
|
-
level: "plan",
|
|
1749
|
-
planId: plan.id,
|
|
1750
|
-
planTypeId: plan.planTypeId,
|
|
1751
|
-
ministryId: plan.ministryId,
|
|
1752
|
-
churchId: plan.churchId,
|
|
1753
|
-
serviceDate: plan.serviceDate,
|
|
1754
|
-
contentType: plan.contentType,
|
|
1755
|
-
contentId: plan.contentId
|
|
1756
|
-
}
|
|
1757
|
-
};
|
|
1463
|
+
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 } };
|
|
1758
1464
|
}
|
|
1759
1465
|
async function planItemToPresentation(item, venueFeed) {
|
|
1760
1466
|
const itemType = item.itemType;
|
|
1761
1467
|
if (itemType === "arrangementKey" && item.churchId && item.relatedId) {
|
|
1762
1468
|
const songData = await fetchArrangementKey(item.churchId, item.relatedId);
|
|
1763
|
-
if (songData)
|
|
1764
|
-
return arrangementToPresentation(item, songData);
|
|
1765
|
-
}
|
|
1469
|
+
if (songData) return arrangementToPresentation(item, songData);
|
|
1766
1470
|
}
|
|
1767
|
-
if ((itemType === "lessonSection" || itemType === "lessonAction" || itemType === "lessonAddOn") && venueFeed) {
|
|
1471
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1768
1472
|
const files = getFilesFromVenueFeed(venueFeed, itemType, item.relatedId);
|
|
1769
|
-
if (files.length > 0) {
|
|
1770
|
-
return {
|
|
1771
|
-
id: item.id,
|
|
1772
|
-
name: item.label || "Lesson Content",
|
|
1773
|
-
actionType: itemType === "lessonAddOn" ? "add-on" : "play",
|
|
1774
|
-
files
|
|
1775
|
-
};
|
|
1776
|
-
}
|
|
1473
|
+
if (files.length > 0) return { id: item.id, name: item.label || "Lesson Content", actionType: itemType === "lessonAddOn" || itemType === "addon" ? "add-on" : "play", files };
|
|
1777
1474
|
}
|
|
1778
1475
|
if (itemType === "item" || itemType === "header") {
|
|
1779
|
-
return {
|
|
1780
|
-
id: item.id,
|
|
1781
|
-
name: item.label || "",
|
|
1782
|
-
actionType: "other",
|
|
1783
|
-
files: [],
|
|
1784
|
-
providerData: {
|
|
1785
|
-
itemType,
|
|
1786
|
-
description: item.description,
|
|
1787
|
-
seconds: item.seconds
|
|
1788
|
-
}
|
|
1789
|
-
};
|
|
1476
|
+
return { id: item.id, name: item.label || "", actionType: "other", files: [], providerData: { itemType, description: item.description, seconds: item.seconds } };
|
|
1790
1477
|
}
|
|
1791
1478
|
return null;
|
|
1792
1479
|
}
|
|
1793
1480
|
function arrangementToPresentation(item, songData) {
|
|
1794
1481
|
const title = songData.songDetail?.title || item.label || "Song";
|
|
1795
|
-
return {
|
|
1796
|
-
id: item.id,
|
|
1797
|
-
name: title,
|
|
1798
|
-
actionType: "other",
|
|
1799
|
-
files: [],
|
|
1800
|
-
providerData: {
|
|
1801
|
-
itemType: "song",
|
|
1802
|
-
title,
|
|
1803
|
-
artist: songData.songDetail?.artist,
|
|
1804
|
-
lyrics: songData.arrangement?.lyrics,
|
|
1805
|
-
keySignature: songData.arrangementKey?.keySignature,
|
|
1806
|
-
arrangementName: songData.arrangement?.name,
|
|
1807
|
-
seconds: songData.songDetail?.seconds || item.seconds
|
|
1808
|
-
}
|
|
1809
|
-
};
|
|
1482
|
+
return { id: item.id, name: title, actionType: "other", files: [], providerData: { itemType: "song", title, artist: songData.songDetail?.artist, lyrics: songData.arrangement?.lyrics, keySignature: songData.arrangementKey?.keySignature, arrangementName: songData.arrangement?.name, seconds: songData.songDetail?.seconds || item.seconds } };
|
|
1810
1483
|
}
|
|
1811
1484
|
function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
1812
1485
|
const files = [];
|
|
1813
1486
|
if (!relatedId) return files;
|
|
1814
|
-
if (itemType === "lessonSection") {
|
|
1487
|
+
if (itemType === "lessonSection" || itemType === "section") {
|
|
1815
1488
|
for (const section of venueFeed.sections || []) {
|
|
1816
1489
|
if (section.id === relatedId) {
|
|
1817
1490
|
for (const action of section.actions || []) {
|
|
@@ -1823,7 +1496,7 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1823
1496
|
break;
|
|
1824
1497
|
}
|
|
1825
1498
|
}
|
|
1826
|
-
} else if (itemType === "lessonAction") {
|
|
1499
|
+
} else if (itemType === "lessonAction" || itemType === "action") {
|
|
1827
1500
|
for (const section of venueFeed.sections || []) {
|
|
1828
1501
|
for (const action of section.actions || []) {
|
|
1829
1502
|
if (action.id === relatedId) {
|
|
@@ -1836,68 +1509,51 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1836
1509
|
return files;
|
|
1837
1510
|
}
|
|
1838
1511
|
function convertFeedFiles(feedFiles, thumbnailImage) {
|
|
1839
|
-
return feedFiles.filter((f) => f.url).map((f) => ({
|
|
1840
|
-
type: "file",
|
|
1841
|
-
id: f.id || "",
|
|
1842
|
-
title: f.name || "",
|
|
1843
|
-
mediaType: detectMediaType(f.url || "", f.fileType),
|
|
1844
|
-
image: thumbnailImage,
|
|
1845
|
-
url: f.url || "",
|
|
1846
|
-
providerData: { seconds: f.seconds, streamUrl: f.streamUrl }
|
|
1847
|
-
}));
|
|
1512
|
+
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 } }));
|
|
1848
1513
|
}
|
|
1849
1514
|
function planItemToInstruction(item) {
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1515
|
+
let itemType = item.itemType;
|
|
1516
|
+
switch (item.itemType) {
|
|
1517
|
+
case "lessonSection":
|
|
1518
|
+
itemType = "section";
|
|
1519
|
+
break;
|
|
1520
|
+
case "lessonAction":
|
|
1521
|
+
itemType = "action";
|
|
1522
|
+
break;
|
|
1523
|
+
case "lessonAddOn":
|
|
1524
|
+
itemType = "addon";
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
return { id: item.id, itemType, relatedId: item.relatedId, label: item.label, description: item.description, seconds: item.seconds, children: item.children?.map(planItemToInstruction) };
|
|
1859
1528
|
}
|
|
1860
1529
|
|
|
1861
|
-
// src/providers/
|
|
1862
|
-
var
|
|
1530
|
+
// src/providers/b1Church/B1ChurchProvider.ts
|
|
1531
|
+
var INTERNAL_PROVIDERS = ["b1church", "lessonschurch"];
|
|
1532
|
+
function isExternalProviderItem(item) {
|
|
1533
|
+
if (!item.providerId || INTERNAL_PROVIDERS.includes(item.providerId)) return false;
|
|
1534
|
+
const itemType = item.itemType || "";
|
|
1535
|
+
return itemType.startsWith("provider");
|
|
1536
|
+
}
|
|
1537
|
+
var B1ChurchProvider = class {
|
|
1863
1538
|
constructor() {
|
|
1864
|
-
|
|
1539
|
+
this.apiHelper = new ApiHelper();
|
|
1865
1540
|
this.id = "b1church";
|
|
1866
1541
|
this.name = "B1.Church";
|
|
1867
|
-
this.logos = {
|
|
1868
|
-
|
|
1869
|
-
dark: "https://b1.church/b1-church-logo.png"
|
|
1870
|
-
};
|
|
1871
|
-
this.config = {
|
|
1872
|
-
id: "b1church",
|
|
1873
|
-
name: "B1.Church",
|
|
1874
|
-
apiBase: `${API_BASE}/doing`,
|
|
1875
|
-
oauthBase: `${API_BASE}/membership/oauth`,
|
|
1876
|
-
clientId: "",
|
|
1877
|
-
scopes: ["plans"],
|
|
1878
|
-
supportsDeviceFlow: true,
|
|
1879
|
-
deviceAuthEndpoint: "/device/authorize",
|
|
1880
|
-
endpoints: {
|
|
1881
|
-
planItems: (churchId, planId) => `/planItems/presenter/${churchId}/${planId}`
|
|
1882
|
-
}
|
|
1883
|
-
};
|
|
1542
|
+
this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
|
|
1543
|
+
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}` } };
|
|
1884
1544
|
this.appBase = "https://admin.b1.church";
|
|
1545
|
+
this.requiresAuth = true;
|
|
1546
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
1547
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
|
|
1885
1548
|
}
|
|
1886
|
-
|
|
1887
|
-
return
|
|
1549
|
+
async apiRequest(path, authData) {
|
|
1550
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, authData);
|
|
1888
1551
|
}
|
|
1889
|
-
|
|
1890
|
-
return
|
|
1891
|
-
browse: true,
|
|
1892
|
-
presentations: true,
|
|
1893
|
-
playlist: true,
|
|
1894
|
-
instructions: true,
|
|
1895
|
-
expandedInstructions: true,
|
|
1896
|
-
mediaLicensing: false
|
|
1897
|
-
};
|
|
1552
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1553
|
+
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1898
1554
|
}
|
|
1899
|
-
async
|
|
1900
|
-
return
|
|
1555
|
+
async exchangeCodeForTokensWithPKCE(code, redirectUri, codeVerifier) {
|
|
1556
|
+
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1901
1557
|
}
|
|
1902
1558
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
1903
1559
|
return exchangeCodeForTokensWithSecret(this.config, code, redirectUri, clientSecret);
|
|
@@ -1911,33 +1567,69 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1911
1567
|
async pollDeviceFlowToken(deviceCode) {
|
|
1912
1568
|
return pollDeviceFlowToken(this.config, deviceCode);
|
|
1913
1569
|
}
|
|
1914
|
-
async browse(
|
|
1915
|
-
|
|
1570
|
+
async browse(path, authData) {
|
|
1571
|
+
const { segments, depth } = parsePath(path);
|
|
1572
|
+
if (depth === 0) {
|
|
1573
|
+
return [{
|
|
1574
|
+
type: "folder",
|
|
1575
|
+
id: "ministries-root",
|
|
1576
|
+
title: "Ministries",
|
|
1577
|
+
path: "/ministries"
|
|
1578
|
+
}];
|
|
1579
|
+
}
|
|
1580
|
+
const root = segments[0];
|
|
1581
|
+
if (root !== "ministries") return [];
|
|
1582
|
+
if (depth === 1) {
|
|
1916
1583
|
const ministries = await fetchMinistries(authData);
|
|
1917
|
-
return ministries.map(
|
|
1584
|
+
return ministries.map((m) => {
|
|
1585
|
+
const folder = ministryToFolder(m);
|
|
1586
|
+
const ministryId = folder.providerData?.ministryId || folder.id;
|
|
1587
|
+
return { ...folder, path: `/ministries/${ministryId}` };
|
|
1588
|
+
});
|
|
1918
1589
|
}
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
const ministryId = folder.providerData?.ministryId;
|
|
1922
|
-
if (!ministryId) return [];
|
|
1590
|
+
if (depth === 2) {
|
|
1591
|
+
const ministryId = segments[1];
|
|
1923
1592
|
const planTypes = await fetchPlanTypes(ministryId, authData);
|
|
1924
|
-
return planTypes.map((pt) =>
|
|
1593
|
+
return planTypes.map((pt) => {
|
|
1594
|
+
const folder = planTypeToFolder(pt, ministryId);
|
|
1595
|
+
const planTypeId = folder.providerData?.planTypeId || folder.id;
|
|
1596
|
+
return { ...folder, path: `/ministries/${ministryId}/${planTypeId}` };
|
|
1597
|
+
});
|
|
1925
1598
|
}
|
|
1926
|
-
if (
|
|
1927
|
-
const
|
|
1928
|
-
|
|
1599
|
+
if (depth === 3) {
|
|
1600
|
+
const ministryId = segments[1];
|
|
1601
|
+
const planTypeId = segments[2];
|
|
1929
1602
|
const plans = await fetchPlans(planTypeId, authData);
|
|
1930
|
-
return plans.map(
|
|
1603
|
+
return plans.map((p) => {
|
|
1604
|
+
const folder = planToFolder(p);
|
|
1605
|
+
const planId = folder.providerData?.planId || folder.id;
|
|
1606
|
+
return {
|
|
1607
|
+
...folder,
|
|
1608
|
+
isLeaf: true,
|
|
1609
|
+
path: `/ministries/${ministryId}/${planTypeId}/${planId}`
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1931
1612
|
}
|
|
1932
1613
|
return [];
|
|
1933
1614
|
}
|
|
1934
|
-
async getPresentations(
|
|
1935
|
-
const
|
|
1936
|
-
if (
|
|
1937
|
-
const
|
|
1938
|
-
const
|
|
1939
|
-
const
|
|
1940
|
-
|
|
1615
|
+
async getPresentations(path, authData) {
|
|
1616
|
+
const { segments, depth } = parsePath(path);
|
|
1617
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1618
|
+
const ministryId = segments[1];
|
|
1619
|
+
const planId = segments[3];
|
|
1620
|
+
const planTypeId = segments[2];
|
|
1621
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1622
|
+
const planFolder = plans.find((p) => {
|
|
1623
|
+
const folder2 = planToFolder(p);
|
|
1624
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1625
|
+
});
|
|
1626
|
+
if (!planFolder) return null;
|
|
1627
|
+
const folder = planToFolder(planFolder);
|
|
1628
|
+
const providerData = folder.providerData;
|
|
1629
|
+
const churchId = providerData?.churchId;
|
|
1630
|
+
const venueId = providerData?.contentId;
|
|
1631
|
+
const planTitle = folder.title || "Plan";
|
|
1632
|
+
if (!churchId) return null;
|
|
1941
1633
|
const pathFn = this.config.endpoints.planItems;
|
|
1942
1634
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1943
1635
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
@@ -1947,43 +1639,101 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1947
1639
|
for (const sectionItem of planItems) {
|
|
1948
1640
|
const presentations = [];
|
|
1949
1641
|
for (const child of sectionItem.children || []) {
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1642
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1643
|
+
const externalPlan = await fetchFromProviderProxy(
|
|
1644
|
+
"getPresentations",
|
|
1645
|
+
ministryId,
|
|
1646
|
+
child.providerId,
|
|
1647
|
+
child.providerPath,
|
|
1648
|
+
authData
|
|
1649
|
+
);
|
|
1650
|
+
if (externalPlan) {
|
|
1651
|
+
for (const section of externalPlan.sections) {
|
|
1652
|
+
presentations.push(...section.presentations);
|
|
1653
|
+
}
|
|
1654
|
+
allFiles.push(...externalPlan.allFiles);
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
const presentation = await planItemToPresentation(child, venueFeed);
|
|
1658
|
+
if (presentation) {
|
|
1659
|
+
presentations.push(presentation);
|
|
1660
|
+
allFiles.push(...presentation.files);
|
|
1661
|
+
}
|
|
1954
1662
|
}
|
|
1955
1663
|
}
|
|
1956
1664
|
if (presentations.length > 0 || sectionItem.label) {
|
|
1957
|
-
sections.push({
|
|
1958
|
-
id: sectionItem.id,
|
|
1959
|
-
name: sectionItem.label || "Section",
|
|
1960
|
-
presentations
|
|
1961
|
-
});
|
|
1665
|
+
sections.push({ id: sectionItem.id, name: sectionItem.label || "Section", presentations });
|
|
1962
1666
|
}
|
|
1963
1667
|
}
|
|
1964
|
-
return { id: planId, name:
|
|
1965
|
-
}
|
|
1966
|
-
async getInstructions(
|
|
1967
|
-
const
|
|
1968
|
-
if (
|
|
1969
|
-
const
|
|
1970
|
-
const
|
|
1971
|
-
|
|
1668
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
1669
|
+
}
|
|
1670
|
+
async getInstructions(path, authData) {
|
|
1671
|
+
const { segments, depth } = parsePath(path);
|
|
1672
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1673
|
+
const ministryId = segments[1];
|
|
1674
|
+
const planId = segments[3];
|
|
1675
|
+
const planTypeId = segments[2];
|
|
1676
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1677
|
+
const planFolder = plans.find((p) => {
|
|
1678
|
+
const folder2 = planToFolder(p);
|
|
1679
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1680
|
+
});
|
|
1681
|
+
if (!planFolder) return null;
|
|
1682
|
+
const folder = planToFolder(planFolder);
|
|
1683
|
+
const providerData = folder.providerData;
|
|
1684
|
+
const churchId = providerData?.churchId;
|
|
1685
|
+
const planTitle = folder.title || "Plan";
|
|
1686
|
+
if (!churchId) return null;
|
|
1972
1687
|
const pathFn = this.config.endpoints.planItems;
|
|
1973
1688
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1974
1689
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1690
|
+
const processedItems = await this.processInstructionItems(planItems, ministryId, authData);
|
|
1691
|
+
return { venueName: planTitle, items: processedItems };
|
|
1692
|
+
}
|
|
1693
|
+
async getExpandedInstructions(path, authData) {
|
|
1694
|
+
return this.getInstructions(path, authData);
|
|
1695
|
+
}
|
|
1696
|
+
async processInstructionItems(items, ministryId, authData) {
|
|
1697
|
+
const result = [];
|
|
1698
|
+
for (const item of items) {
|
|
1699
|
+
if (isExternalProviderItem(item) && item.providerId && item.providerPath) {
|
|
1700
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1701
|
+
"getExpandedInstructions",
|
|
1702
|
+
ministryId,
|
|
1703
|
+
item.providerId,
|
|
1704
|
+
item.providerPath,
|
|
1705
|
+
authData
|
|
1706
|
+
);
|
|
1707
|
+
if (externalInstructions) {
|
|
1708
|
+
result.push(...externalInstructions.items);
|
|
1709
|
+
}
|
|
1710
|
+
} else {
|
|
1711
|
+
const instructionItem = planItemToInstruction(item);
|
|
1712
|
+
if (item.children && item.children.length > 0) {
|
|
1713
|
+
instructionItem.children = await this.processInstructionItems(item.children, ministryId, authData);
|
|
1714
|
+
}
|
|
1715
|
+
result.push(instructionItem);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return result;
|
|
1979
1719
|
}
|
|
1980
|
-
async getPlaylist(
|
|
1981
|
-
const
|
|
1982
|
-
if (
|
|
1983
|
-
const
|
|
1984
|
-
const
|
|
1985
|
-
const
|
|
1986
|
-
|
|
1720
|
+
async getPlaylist(path, authData, resolution) {
|
|
1721
|
+
const { segments, depth } = parsePath(path);
|
|
1722
|
+
if (depth < 4 || segments[0] !== "ministries") return [];
|
|
1723
|
+
const ministryId = segments[1];
|
|
1724
|
+
const planId = segments[3];
|
|
1725
|
+
const planTypeId = segments[2];
|
|
1726
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1727
|
+
const planFolder = plans.find((p) => {
|
|
1728
|
+
const folder2 = planToFolder(p);
|
|
1729
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1730
|
+
});
|
|
1731
|
+
if (!planFolder) return [];
|
|
1732
|
+
const folder = planToFolder(planFolder);
|
|
1733
|
+
const providerData = folder.providerData;
|
|
1734
|
+
const churchId = providerData?.churchId;
|
|
1735
|
+
const venueId = providerData?.contentId;
|
|
1736
|
+
if (!churchId) return [];
|
|
1987
1737
|
const pathFn = this.config.endpoints.planItems;
|
|
1988
1738
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1989
1739
|
if (!planItems || !Array.isArray(planItems)) return [];
|
|
@@ -1991,10 +1741,24 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1991
1741
|
const files = [];
|
|
1992
1742
|
for (const sectionItem of planItems) {
|
|
1993
1743
|
for (const child of sectionItem.children || []) {
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1744
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1745
|
+
const externalFiles = await fetchFromProviderProxy(
|
|
1746
|
+
"getPlaylist",
|
|
1747
|
+
ministryId,
|
|
1748
|
+
child.providerId,
|
|
1749
|
+
child.providerPath,
|
|
1750
|
+
authData,
|
|
1751
|
+
resolution
|
|
1752
|
+
);
|
|
1753
|
+
if (externalFiles) {
|
|
1754
|
+
files.push(...externalFiles);
|
|
1755
|
+
}
|
|
1756
|
+
} else {
|
|
1757
|
+
const itemType = child.itemType;
|
|
1758
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1759
|
+
const itemFiles = getFilesFromVenueFeed(venueFeed, itemType, child.relatedId);
|
|
1760
|
+
files.push(...itemFiles);
|
|
1761
|
+
}
|
|
1998
1762
|
}
|
|
1999
1763
|
}
|
|
2000
1764
|
}
|
|
@@ -2002,81 +1766,62 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
2002
1766
|
}
|
|
2003
1767
|
};
|
|
2004
1768
|
|
|
2005
|
-
// src/providers/PlanningCenterProvider.ts
|
|
2006
|
-
var PlanningCenterProvider = class
|
|
1769
|
+
// src/providers/planningCenter/PlanningCenterProvider.ts
|
|
1770
|
+
var PlanningCenterProvider = class {
|
|
2007
1771
|
constructor() {
|
|
2008
|
-
|
|
1772
|
+
this.apiHelper = new ApiHelper();
|
|
2009
1773
|
this.id = "planningcenter";
|
|
2010
1774
|
this.name = "Planning Center";
|
|
2011
|
-
this.logos = {
|
|
2012
|
-
|
|
2013
|
-
dark: "https://www.planningcenter.com/icons/icon-512x512.png"
|
|
2014
|
-
};
|
|
2015
|
-
// Planning Center uses OAuth 2.0 with PKCE (handled by base ContentProvider class)
|
|
2016
|
-
this.config = {
|
|
2017
|
-
id: "planningcenter",
|
|
2018
|
-
name: "Planning Center",
|
|
2019
|
-
apiBase: "https://api.planningcenteronline.com",
|
|
2020
|
-
oauthBase: "https://api.planningcenteronline.com/oauth",
|
|
2021
|
-
clientId: "",
|
|
2022
|
-
// Consumer must provide client_id
|
|
2023
|
-
scopes: ["services"],
|
|
2024
|
-
endpoints: {
|
|
2025
|
-
serviceTypes: "/services/v2/service_types",
|
|
2026
|
-
plans: (serviceTypeId) => `/services/v2/service_types/${serviceTypeId}/plans`,
|
|
2027
|
-
planItems: (serviceTypeId, planId) => `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items`,
|
|
2028
|
-
song: (itemId) => `/services/v2/songs/${itemId}`,
|
|
2029
|
-
arrangement: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}`,
|
|
2030
|
-
arrangementSections: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}/sections`,
|
|
2031
|
-
media: (mediaId) => `/services/v2/media/${mediaId}`,
|
|
2032
|
-
mediaAttachments: (mediaId) => `/services/v2/media/${mediaId}/attachments`
|
|
2033
|
-
}
|
|
2034
|
-
};
|
|
1775
|
+
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1776
|
+
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"], endpoints: { serviceTypes: "/services/v2/service_types", plans: (serviceTypeId) => `/services/v2/service_types/${serviceTypeId}/plans`, planItems: (serviceTypeId, planId) => `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items`, song: (itemId) => `/services/v2/songs/${itemId}`, arrangement: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}`, arrangementSections: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}/sections`, media: (mediaId) => `/services/v2/media/${mediaId}`, mediaAttachments: (mediaId) => `/services/v2/media/${mediaId}/attachments` } };
|
|
2035
1777
|
this.ONE_WEEK_MS = 6048e5;
|
|
1778
|
+
this.requiresAuth = true;
|
|
1779
|
+
this.authTypes = ["oauth_pkce"];
|
|
1780
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
2036
1781
|
}
|
|
2037
|
-
|
|
2038
|
-
return
|
|
2039
|
-
}
|
|
2040
|
-
getCapabilities() {
|
|
2041
|
-
return {
|
|
2042
|
-
browse: true,
|
|
2043
|
-
presentations: true,
|
|
2044
|
-
playlist: false,
|
|
2045
|
-
instructions: false,
|
|
2046
|
-
expandedInstructions: false,
|
|
2047
|
-
mediaLicensing: false
|
|
2048
|
-
};
|
|
1782
|
+
async apiRequest(path, auth) {
|
|
1783
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
2049
1784
|
}
|
|
2050
|
-
async browse(
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
auth
|
|
2055
|
-
);
|
|
2056
|
-
if (!response?.data) return [];
|
|
2057
|
-
return response.data.map((serviceType) => ({
|
|
1785
|
+
async browse(path, auth) {
|
|
1786
|
+
const { segments, depth } = parsePath(path);
|
|
1787
|
+
if (depth === 0) {
|
|
1788
|
+
return [{
|
|
2058
1789
|
type: "folder",
|
|
2059
|
-
id:
|
|
2060
|
-
title:
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
serviceTypeId: serviceType.id
|
|
2064
|
-
}
|
|
2065
|
-
}));
|
|
1790
|
+
id: "serviceTypes-root",
|
|
1791
|
+
title: "Service Types",
|
|
1792
|
+
path: "/serviceTypes"
|
|
1793
|
+
}];
|
|
2066
1794
|
}
|
|
2067
|
-
const
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
case "plan":
|
|
2072
|
-
return this.getPlanItems(folder, auth);
|
|
2073
|
-
default:
|
|
2074
|
-
return [];
|
|
1795
|
+
const root = segments[0];
|
|
1796
|
+
if (root !== "serviceTypes") return [];
|
|
1797
|
+
if (depth === 1) {
|
|
1798
|
+
return this.getServiceTypes(auth);
|
|
2075
1799
|
}
|
|
1800
|
+
if (depth === 2) {
|
|
1801
|
+
const serviceTypeId = segments[1];
|
|
1802
|
+
return this.getPlans(serviceTypeId, path, auth);
|
|
1803
|
+
}
|
|
1804
|
+
if (depth === 3) {
|
|
1805
|
+
const serviceTypeId = segments[1];
|
|
1806
|
+
const planId = segments[2];
|
|
1807
|
+
return this.getPlanItems(serviceTypeId, planId, auth);
|
|
1808
|
+
}
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
1811
|
+
async getServiceTypes(auth) {
|
|
1812
|
+
const response = await this.apiRequest(
|
|
1813
|
+
this.config.endpoints.serviceTypes,
|
|
1814
|
+
auth
|
|
1815
|
+
);
|
|
1816
|
+
if (!response?.data) return [];
|
|
1817
|
+
return response.data.map((serviceType) => ({
|
|
1818
|
+
type: "folder",
|
|
1819
|
+
id: serviceType.id,
|
|
1820
|
+
title: serviceType.attributes.name,
|
|
1821
|
+
path: `/serviceTypes/${serviceType.id}`
|
|
1822
|
+
}));
|
|
2076
1823
|
}
|
|
2077
|
-
async getPlans(
|
|
2078
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
2079
|
-
if (!serviceTypeId) return [];
|
|
1824
|
+
async getPlans(serviceTypeId, currentPath, auth) {
|
|
2080
1825
|
const pathFn = this.config.endpoints.plans;
|
|
2081
1826
|
const response = await this.apiRequest(
|
|
2082
1827
|
`${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
|
|
@@ -2093,73 +1838,46 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2093
1838
|
type: "folder",
|
|
2094
1839
|
id: plan.id,
|
|
2095
1840
|
title: plan.attributes.title || this.formatDate(plan.attributes.sort_date),
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
planId: plan.id,
|
|
2100
|
-
sortDate: plan.attributes.sort_date
|
|
2101
|
-
}
|
|
1841
|
+
isLeaf: true,
|
|
1842
|
+
path: `${currentPath}/${plan.id}`,
|
|
1843
|
+
providerData: { sortDate: plan.attributes.sort_date }
|
|
2102
1844
|
}));
|
|
2103
1845
|
}
|
|
2104
|
-
async getPlanItems(
|
|
2105
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
2106
|
-
const planId = folder.providerData?.planId;
|
|
2107
|
-
if (!serviceTypeId || !planId) return [];
|
|
1846
|
+
async getPlanItems(serviceTypeId, planId, auth) {
|
|
2108
1847
|
const pathFn = this.config.endpoints.planItems;
|
|
2109
1848
|
const response = await this.apiRequest(
|
|
2110
1849
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
2111
1850
|
auth
|
|
2112
1851
|
);
|
|
2113
1852
|
if (!response?.data) return [];
|
|
2114
|
-
return response.data.map((item) => ({
|
|
2115
|
-
type: "file",
|
|
2116
|
-
id: item.id,
|
|
2117
|
-
title: item.attributes.title || "",
|
|
2118
|
-
mediaType: "image",
|
|
2119
|
-
url: "",
|
|
2120
|
-
providerData: {
|
|
2121
|
-
itemType: item.attributes.item_type,
|
|
2122
|
-
description: item.attributes.description,
|
|
2123
|
-
length: item.attributes.length,
|
|
2124
|
-
songId: item.relationships?.song?.data?.id,
|
|
2125
|
-
arrangementId: item.relationships?.arrangement?.data?.id
|
|
2126
|
-
}
|
|
2127
|
-
}));
|
|
1853
|
+
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 } }));
|
|
2128
1854
|
}
|
|
2129
|
-
async getPresentations(
|
|
2130
|
-
const
|
|
2131
|
-
if (
|
|
2132
|
-
const serviceTypeId =
|
|
2133
|
-
const planId =
|
|
2134
|
-
if (!serviceTypeId || !planId) return null;
|
|
1855
|
+
async getPresentations(path, auth) {
|
|
1856
|
+
const { segments, depth } = parsePath(path);
|
|
1857
|
+
if (depth < 3 || segments[0] !== "serviceTypes") return null;
|
|
1858
|
+
const serviceTypeId = segments[1];
|
|
1859
|
+
const planId = segments[2];
|
|
2135
1860
|
const pathFn = this.config.endpoints.planItems;
|
|
2136
1861
|
const response = await this.apiRequest(
|
|
2137
1862
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
2138
1863
|
auth
|
|
2139
1864
|
);
|
|
2140
1865
|
if (!response?.data) return null;
|
|
1866
|
+
const plans = await this.getPlans(serviceTypeId, `/serviceTypes/${serviceTypeId}`, auth);
|
|
1867
|
+
const plan = plans.find((p) => p.id === planId);
|
|
1868
|
+
const planTitle = plan?.title || "Plan";
|
|
2141
1869
|
const sections = [];
|
|
2142
1870
|
const allFiles = [];
|
|
2143
1871
|
let currentSection = null;
|
|
2144
1872
|
for (const item of response.data) {
|
|
2145
1873
|
const itemType = item.attributes.item_type;
|
|
2146
1874
|
if (itemType === "header") {
|
|
2147
|
-
if (currentSection && currentSection.presentations.length > 0)
|
|
2148
|
-
|
|
2149
|
-
}
|
|
2150
|
-
currentSection = {
|
|
2151
|
-
id: item.id,
|
|
2152
|
-
name: item.attributes.title || "Section",
|
|
2153
|
-
presentations: []
|
|
2154
|
-
};
|
|
1875
|
+
if (currentSection && currentSection.presentations.length > 0) sections.push(currentSection);
|
|
1876
|
+
currentSection = { id: item.id, name: item.attributes.title || "Section", presentations: [] };
|
|
2155
1877
|
continue;
|
|
2156
1878
|
}
|
|
2157
1879
|
if (!currentSection) {
|
|
2158
|
-
currentSection = {
|
|
2159
|
-
id: `default-${planId}`,
|
|
2160
|
-
name: "Service",
|
|
2161
|
-
presentations: []
|
|
2162
|
-
};
|
|
1880
|
+
currentSection = { id: `default-${planId}`, name: "Service", presentations: [] };
|
|
2163
1881
|
}
|
|
2164
1882
|
const presentation = await this.convertToPresentation(item, auth);
|
|
2165
1883
|
if (presentation) {
|
|
@@ -2170,12 +1888,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2170
1888
|
if (currentSection && currentSection.presentations.length > 0) {
|
|
2171
1889
|
sections.push(currentSection);
|
|
2172
1890
|
}
|
|
2173
|
-
return {
|
|
2174
|
-
id: planId,
|
|
2175
|
-
name: folder.title,
|
|
2176
|
-
sections,
|
|
2177
|
-
allFiles
|
|
2178
|
-
};
|
|
1891
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
2179
1892
|
}
|
|
2180
1893
|
async convertToPresentation(item, auth) {
|
|
2181
1894
|
const itemType = item.attributes.item_type;
|
|
@@ -2186,17 +1899,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2186
1899
|
return this.convertMediaToPresentation(item, auth);
|
|
2187
1900
|
}
|
|
2188
1901
|
if (itemType === "item") {
|
|
2189
|
-
return {
|
|
2190
|
-
id: item.id,
|
|
2191
|
-
name: item.attributes.title || "",
|
|
2192
|
-
actionType: "other",
|
|
2193
|
-
files: [],
|
|
2194
|
-
providerData: {
|
|
2195
|
-
itemType: "item",
|
|
2196
|
-
description: item.attributes.description,
|
|
2197
|
-
length: item.attributes.length
|
|
2198
|
-
}
|
|
2199
|
-
};
|
|
1902
|
+
return { id: item.id, name: item.attributes.title || "", actionType: "other", files: [], providerData: { itemType: "item", description: item.attributes.description, length: item.attributes.length } };
|
|
2200
1903
|
}
|
|
2201
1904
|
return null;
|
|
2202
1905
|
}
|
|
@@ -2204,13 +1907,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2204
1907
|
const songId = item.relationships?.song?.data?.id;
|
|
2205
1908
|
const arrangementId = item.relationships?.arrangement?.data?.id;
|
|
2206
1909
|
if (!songId) {
|
|
2207
|
-
return {
|
|
2208
|
-
id: item.id,
|
|
2209
|
-
name: item.attributes.title || "Song",
|
|
2210
|
-
actionType: "other",
|
|
2211
|
-
files: [],
|
|
2212
|
-
providerData: { itemType: "song" }
|
|
2213
|
-
};
|
|
1910
|
+
return { id: item.id, name: item.attributes.title || "Song", actionType: "other", files: [], providerData: { itemType: "song" } };
|
|
2214
1911
|
}
|
|
2215
1912
|
const songFn = this.config.endpoints.song;
|
|
2216
1913
|
const songResponse = await this.apiRequest(songFn(songId), auth);
|
|
@@ -2232,25 +1929,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2232
1929
|
}
|
|
2233
1930
|
const song = songResponse?.data;
|
|
2234
1931
|
const title = song?.attributes?.title || item.attributes.title || "Song";
|
|
2235
|
-
return {
|
|
2236
|
-
id: item.id,
|
|
2237
|
-
name: title,
|
|
2238
|
-
actionType: "other",
|
|
2239
|
-
files: [],
|
|
2240
|
-
providerData: {
|
|
2241
|
-
itemType: "song",
|
|
2242
|
-
title,
|
|
2243
|
-
author: song?.attributes?.author,
|
|
2244
|
-
copyright: song?.attributes?.copyright,
|
|
2245
|
-
ccliNumber: song?.attributes?.ccli_number,
|
|
2246
|
-
arrangementName: arrangement?.attributes?.name,
|
|
2247
|
-
keySignature: arrangement?.attributes?.chord_chart_key,
|
|
2248
|
-
bpm: arrangement?.attributes?.bpm,
|
|
2249
|
-
sequence: arrangement?.attributes?.sequence,
|
|
2250
|
-
sections: sections.map((s) => ({ label: s.label, lyrics: s.lyrics })),
|
|
2251
|
-
length: item.attributes.length
|
|
2252
|
-
}
|
|
2253
|
-
};
|
|
1932
|
+
return { id: item.id, name: title, actionType: "other", files: [], providerData: { itemType: "song", title, author: song?.attributes?.author, copyright: song?.attributes?.copyright, ccliNumber: song?.attributes?.ccli_number, arrangementName: arrangement?.attributes?.name, keySignature: arrangement?.attributes?.chord_chart_key, bpm: arrangement?.attributes?.bpm, sequence: arrangement?.attributes?.sequence, sections: sections.map((s) => ({ label: s.label, lyrics: s.lyrics })), length: item.attributes.length } };
|
|
2254
1933
|
}
|
|
2255
1934
|
async convertMediaToPresentation(item, auth) {
|
|
2256
1935
|
const files = [];
|
|
@@ -2270,25 +1949,10 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2270
1949
|
if (!url) continue;
|
|
2271
1950
|
const contentType = attachment.attributes.content_type;
|
|
2272
1951
|
const explicitType = contentType?.startsWith("video/") ? "video" : void 0;
|
|
2273
|
-
files.push({
|
|
2274
|
-
type: "file",
|
|
2275
|
-
id: attachment.id,
|
|
2276
|
-
title: attachment.attributes.filename,
|
|
2277
|
-
mediaType: detectMediaType(url, explicitType),
|
|
2278
|
-
url
|
|
2279
|
-
});
|
|
1952
|
+
files.push({ type: "file", id: attachment.id, title: attachment.attributes.filename, mediaType: detectMediaType(url, explicitType), url });
|
|
2280
1953
|
}
|
|
2281
1954
|
}
|
|
2282
|
-
return {
|
|
2283
|
-
id: item.id,
|
|
2284
|
-
name: item.attributes.title || "Media",
|
|
2285
|
-
actionType: "play",
|
|
2286
|
-
files,
|
|
2287
|
-
providerData: {
|
|
2288
|
-
itemType: "media",
|
|
2289
|
-
length: item.attributes.length
|
|
2290
|
-
}
|
|
2291
|
-
};
|
|
1955
|
+
return { id: item.id, name: item.attributes.title || "Media", actionType: "play", files, providerData: { itemType: "media", length: item.attributes.length } };
|
|
2292
1956
|
}
|
|
2293
1957
|
formatDate(dateString) {
|
|
2294
1958
|
const date = new Date(dateString);
|
|
@@ -2296,7 +1960,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2296
1960
|
}
|
|
2297
1961
|
};
|
|
2298
1962
|
|
|
2299
|
-
// src/providers/
|
|
1963
|
+
// src/providers/bibleProject/data.json
|
|
2300
1964
|
var data_default = {
|
|
2301
1965
|
collections: [
|
|
2302
1966
|
{
|
|
@@ -4404,10 +4068,9 @@ var data_default = {
|
|
|
4404
4068
|
]
|
|
4405
4069
|
};
|
|
4406
4070
|
|
|
4407
|
-
// src/providers/
|
|
4408
|
-
var BibleProjectProvider = class
|
|
4071
|
+
// src/providers/bibleProject/BibleProjectProvider.ts
|
|
4072
|
+
var BibleProjectProvider = class {
|
|
4409
4073
|
constructor() {
|
|
4410
|
-
super(...arguments);
|
|
4411
4074
|
this.id = "bibleproject";
|
|
4412
4075
|
this.name = "The Bible Project";
|
|
4413
4076
|
this.logos = {
|
|
@@ -4426,174 +4089,109 @@ var BibleProjectProvider = class extends ContentProvider {
|
|
|
4426
4089
|
}
|
|
4427
4090
|
};
|
|
4428
4091
|
this.data = data_default;
|
|
4429
|
-
|
|
4430
|
-
|
|
4431
|
-
|
|
4432
|
-
}
|
|
4433
|
-
getCapabilities() {
|
|
4434
|
-
return {
|
|
4092
|
+
this.requiresAuth = false;
|
|
4093
|
+
this.authTypes = ["none"];
|
|
4094
|
+
this.capabilities = {
|
|
4435
4095
|
browse: true,
|
|
4436
4096
|
presentations: true,
|
|
4437
|
-
// Has collections with videos
|
|
4438
4097
|
playlist: true,
|
|
4439
|
-
// Can return flat list of videos
|
|
4440
4098
|
instructions: false,
|
|
4441
4099
|
expandedInstructions: false,
|
|
4442
4100
|
mediaLicensing: false
|
|
4443
4101
|
};
|
|
4444
4102
|
}
|
|
4445
|
-
async browse(
|
|
4446
|
-
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
collection.name,
|
|
4450
|
-
collection.image || void 0,
|
|
4451
|
-
{ level: "collection", collectionName: collection.name }
|
|
4452
|
-
));
|
|
4103
|
+
async browse(path, _auth) {
|
|
4104
|
+
const { segments, depth } = parsePath(path);
|
|
4105
|
+
if (depth === 0) {
|
|
4106
|
+
return this.getCollections();
|
|
4453
4107
|
}
|
|
4454
|
-
|
|
4455
|
-
|
|
4456
|
-
|
|
4457
|
-
return this.getLessonFolders(collectionName);
|
|
4108
|
+
if (depth === 1) {
|
|
4109
|
+
const collectionSlug = segments[0];
|
|
4110
|
+
return this.getLessonFolders(collectionSlug, path);
|
|
4458
4111
|
}
|
|
4459
|
-
if (
|
|
4460
|
-
const
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
videoData.id,
|
|
4464
|
-
videoData.title,
|
|
4465
|
-
videoData.videoUrl,
|
|
4466
|
-
{
|
|
4467
|
-
mediaType: "video",
|
|
4468
|
-
muxPlaybackId: videoData.muxPlaybackId
|
|
4469
|
-
}
|
|
4470
|
-
)];
|
|
4471
|
-
}
|
|
4472
|
-
return [];
|
|
4112
|
+
if (depth === 2) {
|
|
4113
|
+
const collectionSlug = segments[0];
|
|
4114
|
+
const videoId = segments[1];
|
|
4115
|
+
return this.getVideoFile(collectionSlug, videoId);
|
|
4473
4116
|
}
|
|
4474
4117
|
return [];
|
|
4475
4118
|
}
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
|
|
4481
|
-
|
|
4119
|
+
getCollections() {
|
|
4120
|
+
return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
|
|
4121
|
+
type: "folder",
|
|
4122
|
+
id: this.slugify(collection.name),
|
|
4123
|
+
title: collection.name,
|
|
4124
|
+
image: collection.image || void 0,
|
|
4125
|
+
path: `/${this.slugify(collection.name)}`
|
|
4126
|
+
}));
|
|
4127
|
+
}
|
|
4128
|
+
getLessonFolders(collectionSlug, currentPath) {
|
|
4129
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4130
|
+
if (!collection) return [];
|
|
4131
|
+
return collection.videos.map((video) => ({
|
|
4132
|
+
type: "folder",
|
|
4133
|
+
id: video.id,
|
|
4134
|
+
title: video.title,
|
|
4135
|
+
image: video.thumbnailUrl,
|
|
4136
|
+
isLeaf: true,
|
|
4137
|
+
path: `${currentPath}/${video.id}`,
|
|
4138
|
+
providerData: { videoData: video }
|
|
4139
|
+
}));
|
|
4140
|
+
}
|
|
4141
|
+
getVideoFile(collectionSlug, videoId) {
|
|
4142
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4143
|
+
if (!collection) return [];
|
|
4144
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4145
|
+
if (!video) return [];
|
|
4146
|
+
return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId })];
|
|
4147
|
+
}
|
|
4148
|
+
async getPresentations(path, _auth) {
|
|
4149
|
+
const { segments, depth } = parsePath(path);
|
|
4150
|
+
if (depth < 1) return null;
|
|
4151
|
+
const collectionSlug = segments[0];
|
|
4152
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4153
|
+
if (!collection) return null;
|
|
4154
|
+
if (depth === 1) {
|
|
4482
4155
|
const allFiles = [];
|
|
4483
4156
|
const presentations = collection.videos.map((video) => {
|
|
4484
|
-
const file = {
|
|
4485
|
-
type: "file",
|
|
4486
|
-
id: video.id,
|
|
4487
|
-
title: video.title,
|
|
4488
|
-
mediaType: "video",
|
|
4489
|
-
url: video.videoUrl,
|
|
4490
|
-
image: video.thumbnailUrl,
|
|
4491
|
-
muxPlaybackId: video.muxPlaybackId
|
|
4492
|
-
};
|
|
4157
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4493
4158
|
allFiles.push(file);
|
|
4494
|
-
return {
|
|
4495
|
-
id: video.id,
|
|
4496
|
-
name: video.title,
|
|
4497
|
-
actionType: "play",
|
|
4498
|
-
files: [file]
|
|
4499
|
-
};
|
|
4159
|
+
return { id: video.id, name: video.title, actionType: "play", files: [file] };
|
|
4500
4160
|
});
|
|
4501
|
-
return {
|
|
4502
|
-
id: this.slugify(collection.name),
|
|
4503
|
-
name: collection.name,
|
|
4504
|
-
image: collection.image || void 0,
|
|
4505
|
-
sections: [{
|
|
4506
|
-
id: "videos",
|
|
4507
|
-
name: "Videos",
|
|
4508
|
-
presentations
|
|
4509
|
-
}],
|
|
4510
|
-
allFiles
|
|
4511
|
-
};
|
|
4161
|
+
return { id: this.slugify(collection.name), name: collection.name, image: collection.image || void 0, sections: [{ id: "videos", name: "Videos", presentations }], allFiles };
|
|
4512
4162
|
}
|
|
4513
|
-
if (
|
|
4514
|
-
const
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
4519
|
-
title: videoData.title,
|
|
4520
|
-
mediaType: "video",
|
|
4521
|
-
url: videoData.videoUrl,
|
|
4522
|
-
image: videoData.thumbnailUrl,
|
|
4523
|
-
muxPlaybackId: videoData.muxPlaybackId
|
|
4524
|
-
};
|
|
4525
|
-
return {
|
|
4526
|
-
id: videoData.id,
|
|
4527
|
-
name: videoData.title,
|
|
4528
|
-
image: videoData.thumbnailUrl,
|
|
4529
|
-
sections: [{
|
|
4530
|
-
id: "main",
|
|
4531
|
-
name: "Content",
|
|
4532
|
-
presentations: [{
|
|
4533
|
-
id: videoData.id,
|
|
4534
|
-
name: videoData.title,
|
|
4535
|
-
actionType: "play",
|
|
4536
|
-
files: [file]
|
|
4537
|
-
}]
|
|
4538
|
-
}],
|
|
4539
|
-
allFiles: [file]
|
|
4540
|
-
};
|
|
4163
|
+
if (depth === 2) {
|
|
4164
|
+
const videoId = segments[1];
|
|
4165
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4166
|
+
if (!video) return null;
|
|
4167
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4168
|
+
return { id: video.id, name: video.title, image: video.thumbnailUrl, sections: [{ id: "main", name: "Content", presentations: [{ id: video.id, name: video.title, actionType: "play", files: [file] }] }], allFiles: [file] };
|
|
4541
4169
|
}
|
|
4542
4170
|
return null;
|
|
4543
4171
|
}
|
|
4544
|
-
async getPlaylist(
|
|
4545
|
-
const
|
|
4546
|
-
if (
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
id: video.id,
|
|
4553
|
-
title: video.title,
|
|
4554
|
-
mediaType: "video",
|
|
4555
|
-
url: video.videoUrl,
|
|
4556
|
-
image: video.thumbnailUrl,
|
|
4557
|
-
muxPlaybackId: video.muxPlaybackId
|
|
4558
|
-
}));
|
|
4172
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
4173
|
+
const { segments, depth } = parsePath(path);
|
|
4174
|
+
if (depth < 1) return null;
|
|
4175
|
+
const collectionSlug = segments[0];
|
|
4176
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4177
|
+
if (!collection) return null;
|
|
4178
|
+
if (depth === 1) {
|
|
4179
|
+
return collection.videos.map((video) => ({ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }));
|
|
4559
4180
|
}
|
|
4560
|
-
if (
|
|
4561
|
-
const
|
|
4562
|
-
|
|
4563
|
-
return
|
|
4564
|
-
|
|
4565
|
-
id: videoData.id,
|
|
4566
|
-
title: videoData.title,
|
|
4567
|
-
mediaType: "video",
|
|
4568
|
-
url: videoData.videoUrl,
|
|
4569
|
-
image: videoData.thumbnailUrl,
|
|
4570
|
-
muxPlaybackId: videoData.muxPlaybackId
|
|
4571
|
-
}];
|
|
4181
|
+
if (depth === 2) {
|
|
4182
|
+
const videoId = segments[1];
|
|
4183
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4184
|
+
if (!video) return null;
|
|
4185
|
+
return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }];
|
|
4572
4186
|
}
|
|
4573
4187
|
return null;
|
|
4574
4188
|
}
|
|
4575
|
-
getLessonFolders(collectionName) {
|
|
4576
|
-
const collection = this.data.collections.find((c) => c.name === collectionName);
|
|
4577
|
-
if (!collection) return [];
|
|
4578
|
-
return collection.videos.map((video) => this.createFolder(
|
|
4579
|
-
video.id,
|
|
4580
|
-
video.title,
|
|
4581
|
-
video.thumbnailUrl,
|
|
4582
|
-
{
|
|
4583
|
-
level: "lesson",
|
|
4584
|
-
collectionName,
|
|
4585
|
-
videoData: video,
|
|
4586
|
-
isLeaf: true
|
|
4587
|
-
// Mark as leaf so venue choice modal appears
|
|
4588
|
-
}
|
|
4589
|
-
));
|
|
4590
|
-
}
|
|
4591
4189
|
slugify(text) {
|
|
4592
4190
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4593
4191
|
}
|
|
4594
4192
|
};
|
|
4595
4193
|
|
|
4596
|
-
// src/providers/
|
|
4194
|
+
// src/providers/highVoltage/data.json
|
|
4597
4195
|
var data_default2 = {
|
|
4598
4196
|
collections: [
|
|
4599
4197
|
{
|
|
@@ -12097,10 +11695,9 @@ var data_default2 = {
|
|
|
12097
11695
|
]
|
|
12098
11696
|
};
|
|
12099
11697
|
|
|
12100
|
-
// src/providers/HighVoltageKidsProvider.ts
|
|
12101
|
-
var HighVoltageKidsProvider = class
|
|
11698
|
+
// src/providers/highVoltage/HighVoltageKidsProvider.ts
|
|
11699
|
+
var HighVoltageKidsProvider = class {
|
|
12102
11700
|
constructor() {
|
|
12103
|
-
super(...arguments);
|
|
12104
11701
|
this.id = "highvoltagekids";
|
|
12105
11702
|
this.name = "High Voltage Kids";
|
|
12106
11703
|
this.logos = {
|
|
@@ -12119,195 +11716,211 @@ var HighVoltageKidsProvider = class extends ContentProvider {
|
|
|
12119
11716
|
}
|
|
12120
11717
|
};
|
|
12121
11718
|
this.data = data_default2;
|
|
12122
|
-
|
|
12123
|
-
|
|
12124
|
-
|
|
12125
|
-
}
|
|
12126
|
-
getCapabilities() {
|
|
12127
|
-
return {
|
|
11719
|
+
this.requiresAuth = false;
|
|
11720
|
+
this.authTypes = ["none"];
|
|
11721
|
+
this.capabilities = {
|
|
12128
11722
|
browse: true,
|
|
12129
11723
|
presentations: true,
|
|
12130
|
-
// Has hierarchical structure: study -> lessons -> files
|
|
12131
11724
|
playlist: true,
|
|
12132
|
-
// Can return flat list of files for a lesson
|
|
12133
11725
|
instructions: false,
|
|
12134
|
-
expandedInstructions:
|
|
11726
|
+
expandedInstructions: true,
|
|
12135
11727
|
mediaLicensing: false
|
|
12136
11728
|
};
|
|
12137
11729
|
}
|
|
12138
|
-
async browse(
|
|
12139
|
-
|
|
12140
|
-
|
|
12141
|
-
|
|
12142
|
-
collection.name,
|
|
12143
|
-
void 0,
|
|
12144
|
-
{ level: "collection", collectionName: collection.name }
|
|
12145
|
-
));
|
|
11730
|
+
async browse(path, _auth) {
|
|
11731
|
+
const { segments, depth } = parsePath(path);
|
|
11732
|
+
if (depth === 0) {
|
|
11733
|
+
return this.getCollections();
|
|
12146
11734
|
}
|
|
12147
|
-
|
|
12148
|
-
|
|
12149
|
-
|
|
12150
|
-
return this.getStudyFolders(collectionName);
|
|
11735
|
+
if (depth === 1) {
|
|
11736
|
+
const collectionSlug = segments[0];
|
|
11737
|
+
return this.getStudyFolders(collectionSlug, path);
|
|
12151
11738
|
}
|
|
12152
|
-
if (
|
|
12153
|
-
const
|
|
12154
|
-
|
|
12155
|
-
|
|
12156
|
-
}
|
|
12157
|
-
return [];
|
|
11739
|
+
if (depth === 2) {
|
|
11740
|
+
const collectionSlug = segments[0];
|
|
11741
|
+
const studyId = segments[1];
|
|
11742
|
+
return this.getLessonFolders(collectionSlug, studyId, path);
|
|
12158
11743
|
}
|
|
12159
|
-
if (
|
|
12160
|
-
const
|
|
12161
|
-
|
|
12162
|
-
|
|
12163
|
-
|
|
12164
|
-
file.title,
|
|
12165
|
-
file.url,
|
|
12166
|
-
{ mediaType: file.mediaType }
|
|
12167
|
-
));
|
|
12168
|
-
}
|
|
12169
|
-
return [];
|
|
11744
|
+
if (depth === 3) {
|
|
11745
|
+
const collectionSlug = segments[0];
|
|
11746
|
+
const studyId = segments[1];
|
|
11747
|
+
const lessonId = segments[2];
|
|
11748
|
+
return this.getLessonFiles(collectionSlug, studyId, lessonId);
|
|
12170
11749
|
}
|
|
12171
11750
|
return [];
|
|
12172
11751
|
}
|
|
12173
|
-
|
|
12174
|
-
|
|
12175
|
-
|
|
12176
|
-
|
|
12177
|
-
|
|
11752
|
+
getCollections() {
|
|
11753
|
+
return this.data.collections.filter((collection) => collection.folders.length > 0).map((collection) => ({
|
|
11754
|
+
type: "folder",
|
|
11755
|
+
id: this.slugify(collection.name),
|
|
11756
|
+
title: collection.name,
|
|
11757
|
+
path: `/${this.slugify(collection.name)}`
|
|
11758
|
+
}));
|
|
11759
|
+
}
|
|
11760
|
+
getStudyFolders(collectionSlug, currentPath) {
|
|
11761
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11762
|
+
if (!collection) return [];
|
|
11763
|
+
return collection.folders.map((study) => ({
|
|
11764
|
+
type: "folder",
|
|
11765
|
+
id: study.id,
|
|
11766
|
+
title: study.name,
|
|
11767
|
+
image: study.image || void 0,
|
|
11768
|
+
path: `${currentPath}/${study.id}`,
|
|
11769
|
+
providerData: { studyData: study }
|
|
11770
|
+
}));
|
|
11771
|
+
}
|
|
11772
|
+
getLessonFolders(collectionSlug, studyId, currentPath) {
|
|
11773
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11774
|
+
if (!collection) return [];
|
|
11775
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11776
|
+
if (!study) return [];
|
|
11777
|
+
return study.lessons.map((lesson) => ({
|
|
11778
|
+
type: "folder",
|
|
11779
|
+
id: lesson.id,
|
|
11780
|
+
title: lesson.name,
|
|
11781
|
+
image: lesson.image || void 0,
|
|
11782
|
+
isLeaf: true,
|
|
11783
|
+
path: `${currentPath}/${lesson.id}`,
|
|
11784
|
+
providerData: { lessonData: lesson, studyName: study.name }
|
|
11785
|
+
}));
|
|
11786
|
+
}
|
|
11787
|
+
getLessonFiles(collectionSlug, studyId, lessonId) {
|
|
11788
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11789
|
+
if (!collection) return [];
|
|
11790
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11791
|
+
if (!study) return [];
|
|
11792
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11793
|
+
if (!lesson?.files) return [];
|
|
11794
|
+
return lesson.files.map((file) => createFile(file.id, file.title, file.url, { mediaType: file.mediaType }));
|
|
11795
|
+
}
|
|
11796
|
+
async getPresentations(path, _auth) {
|
|
11797
|
+
const { segments, depth } = parsePath(path);
|
|
11798
|
+
if (depth < 2) return null;
|
|
11799
|
+
const collectionSlug = segments[0];
|
|
11800
|
+
const studyId = segments[1];
|
|
11801
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11802
|
+
if (!collection) return null;
|
|
11803
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11804
|
+
if (!study) return null;
|
|
11805
|
+
if (depth === 2) {
|
|
12178
11806
|
const allFiles = [];
|
|
12179
|
-
const sections =
|
|
11807
|
+
const sections = study.lessons.map((lesson) => {
|
|
12180
11808
|
const files = lesson.files.map((file) => {
|
|
12181
|
-
const contentFile = {
|
|
12182
|
-
type: "file",
|
|
12183
|
-
id: file.id,
|
|
12184
|
-
title: file.title,
|
|
12185
|
-
mediaType: file.mediaType,
|
|
12186
|
-
url: file.url,
|
|
12187
|
-
image: lesson.image
|
|
12188
|
-
};
|
|
11809
|
+
const contentFile = { type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image };
|
|
12189
11810
|
allFiles.push(contentFile);
|
|
12190
11811
|
return contentFile;
|
|
12191
11812
|
});
|
|
12192
|
-
const presentation = {
|
|
12193
|
-
|
|
12194
|
-
name: lesson.name,
|
|
12195
|
-
actionType: "play",
|
|
12196
|
-
files
|
|
12197
|
-
};
|
|
12198
|
-
return {
|
|
12199
|
-
id: lesson.id,
|
|
12200
|
-
name: lesson.name,
|
|
12201
|
-
presentations: [presentation]
|
|
12202
|
-
};
|
|
11813
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11814
|
+
return { id: lesson.id, name: lesson.name, presentations: [presentation] };
|
|
12203
11815
|
});
|
|
12204
|
-
return {
|
|
12205
|
-
id: studyData.id,
|
|
12206
|
-
name: studyData.name,
|
|
12207
|
-
description: studyData.description,
|
|
12208
|
-
image: studyData.image,
|
|
12209
|
-
sections,
|
|
12210
|
-
allFiles
|
|
12211
|
-
};
|
|
11816
|
+
return { id: study.id, name: study.name, description: study.description, image: study.image, sections, allFiles };
|
|
12212
11817
|
}
|
|
12213
|
-
if (
|
|
12214
|
-
const
|
|
12215
|
-
|
|
12216
|
-
|
|
12217
|
-
|
|
12218
|
-
|
|
12219
|
-
|
|
12220
|
-
mediaType: file.mediaType,
|
|
12221
|
-
url: file.url,
|
|
12222
|
-
image: lessonData.image
|
|
12223
|
-
}));
|
|
12224
|
-
const presentation = {
|
|
12225
|
-
id: lessonData.id,
|
|
12226
|
-
name: lessonData.name,
|
|
12227
|
-
actionType: "play",
|
|
12228
|
-
files
|
|
12229
|
-
};
|
|
12230
|
-
return {
|
|
12231
|
-
id: lessonData.id,
|
|
12232
|
-
name: lessonData.name,
|
|
12233
|
-
image: lessonData.image,
|
|
12234
|
-
sections: [{
|
|
12235
|
-
id: "main",
|
|
12236
|
-
name: "Content",
|
|
12237
|
-
presentations: [presentation]
|
|
12238
|
-
}],
|
|
12239
|
-
allFiles: files
|
|
12240
|
-
};
|
|
11818
|
+
if (depth === 3) {
|
|
11819
|
+
const lessonId = segments[2];
|
|
11820
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11821
|
+
if (!lesson?.files) return null;
|
|
11822
|
+
const files = lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11823
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11824
|
+
return { id: lesson.id, name: lesson.name, image: lesson.image, sections: [{ id: "main", name: "Content", presentations: [presentation] }], allFiles: files };
|
|
12241
11825
|
}
|
|
12242
11826
|
return null;
|
|
12243
11827
|
}
|
|
12244
|
-
async getPlaylist(
|
|
12245
|
-
const
|
|
12246
|
-
if (
|
|
12247
|
-
|
|
12248
|
-
|
|
12249
|
-
|
|
12250
|
-
|
|
12251
|
-
|
|
12252
|
-
|
|
12253
|
-
|
|
12254
|
-
url: file.url,
|
|
12255
|
-
image: lessonData.image
|
|
12256
|
-
}));
|
|
12257
|
-
}
|
|
12258
|
-
if (level === "study") {
|
|
12259
|
-
const studyData = folder.providerData?.studyData;
|
|
12260
|
-
if (!studyData) return null;
|
|
11828
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
11829
|
+
const { segments, depth } = parsePath(path);
|
|
11830
|
+
if (depth < 2) return null;
|
|
11831
|
+
const collectionSlug = segments[0];
|
|
11832
|
+
const studyId = segments[1];
|
|
11833
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11834
|
+
if (!collection) return null;
|
|
11835
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11836
|
+
if (!study) return null;
|
|
11837
|
+
if (depth === 2) {
|
|
12261
11838
|
const allFiles = [];
|
|
12262
|
-
for (const lesson of
|
|
11839
|
+
for (const lesson of study.lessons) {
|
|
12263
11840
|
for (const file of lesson.files) {
|
|
12264
|
-
allFiles.push({
|
|
12265
|
-
type: "file",
|
|
12266
|
-
id: file.id,
|
|
12267
|
-
title: file.title,
|
|
12268
|
-
mediaType: file.mediaType,
|
|
12269
|
-
url: file.url,
|
|
12270
|
-
image: lesson.image
|
|
12271
|
-
});
|
|
11841
|
+
allFiles.push({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image });
|
|
12272
11842
|
}
|
|
12273
11843
|
}
|
|
12274
11844
|
return allFiles;
|
|
12275
11845
|
}
|
|
11846
|
+
if (depth === 3) {
|
|
11847
|
+
const lessonId = segments[2];
|
|
11848
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11849
|
+
if (!lesson?.files) return null;
|
|
11850
|
+
return lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11851
|
+
}
|
|
12276
11852
|
return null;
|
|
12277
11853
|
}
|
|
12278
|
-
|
|
12279
|
-
const
|
|
12280
|
-
if (
|
|
12281
|
-
|
|
12282
|
-
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
|
|
12286
|
-
|
|
12287
|
-
|
|
12288
|
-
|
|
12289
|
-
|
|
12290
|
-
|
|
12291
|
-
}
|
|
12292
|
-
|
|
12293
|
-
|
|
12294
|
-
|
|
12295
|
-
|
|
12296
|
-
lesson.id
|
|
12297
|
-
lesson
|
|
12298
|
-
|
|
12299
|
-
|
|
12300
|
-
|
|
12301
|
-
|
|
12302
|
-
|
|
12303
|
-
isLeaf: true
|
|
12304
|
-
// Mark as leaf so venue choice modal appears
|
|
12305
|
-
}
|
|
12306
|
-
));
|
|
11854
|
+
async getExpandedInstructions(path, _auth) {
|
|
11855
|
+
const { segments, depth } = parsePath(path);
|
|
11856
|
+
if (depth < 2) return null;
|
|
11857
|
+
const collectionSlug = segments[0];
|
|
11858
|
+
const studyId = segments[1];
|
|
11859
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11860
|
+
if (!collection) return null;
|
|
11861
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11862
|
+
if (!study) return null;
|
|
11863
|
+
if (depth === 2) {
|
|
11864
|
+
const lessonItems = study.lessons.map((lesson) => {
|
|
11865
|
+
const fileItems = lesson.files.map((file) => ({ id: file.id, itemType: "file", label: file.title, embedUrl: file.url }));
|
|
11866
|
+
return { id: lesson.id, itemType: "action", label: lesson.name, description: "play", children: fileItems };
|
|
11867
|
+
});
|
|
11868
|
+
return { venueName: study.name, items: [{ id: study.id, itemType: "header", label: study.name, children: [{ id: "main", itemType: "section", label: "Content", children: lessonItems }] }] };
|
|
11869
|
+
}
|
|
11870
|
+
if (depth === 3) {
|
|
11871
|
+
const lessonId = segments[2];
|
|
11872
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11873
|
+
if (!lesson?.files) return null;
|
|
11874
|
+
const headerLabel = `${study.name} - ${lesson.name}`;
|
|
11875
|
+
const actionItems = this.groupFilesIntoActions(lesson.files);
|
|
11876
|
+
return { venueName: lesson.name, items: [{ id: lesson.id, itemType: "header", label: headerLabel, children: [{ id: "main", itemType: "section", label: lesson.name, children: actionItems }] }] };
|
|
11877
|
+
}
|
|
11878
|
+
return null;
|
|
12307
11879
|
}
|
|
12308
11880
|
slugify(text) {
|
|
12309
11881
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
12310
11882
|
}
|
|
11883
|
+
groupFilesIntoActions(files) {
|
|
11884
|
+
const actionItems = [];
|
|
11885
|
+
let currentGroup = [];
|
|
11886
|
+
let currentBaseName = null;
|
|
11887
|
+
const flushGroup = () => {
|
|
11888
|
+
if (currentGroup.length === 0) return;
|
|
11889
|
+
const children = currentGroup.map((file) => ({
|
|
11890
|
+
id: file.id,
|
|
11891
|
+
itemType: "file",
|
|
11892
|
+
label: file.title,
|
|
11893
|
+
embedUrl: file.url
|
|
11894
|
+
}));
|
|
11895
|
+
const label = currentGroup.length > 1 && currentBaseName ? currentBaseName : currentGroup[0].title;
|
|
11896
|
+
actionItems.push({
|
|
11897
|
+
id: currentGroup[0].id + "-action",
|
|
11898
|
+
itemType: "action",
|
|
11899
|
+
label,
|
|
11900
|
+
description: "play",
|
|
11901
|
+
children
|
|
11902
|
+
});
|
|
11903
|
+
currentGroup = [];
|
|
11904
|
+
currentBaseName = null;
|
|
11905
|
+
};
|
|
11906
|
+
for (const file of files) {
|
|
11907
|
+
const baseName = this.getBaseName(file.title);
|
|
11908
|
+
const isNumbered = baseName !== file.title;
|
|
11909
|
+
if (isNumbered && baseName === currentBaseName) {
|
|
11910
|
+
currentGroup.push(file);
|
|
11911
|
+
} else {
|
|
11912
|
+
flushGroup();
|
|
11913
|
+
currentGroup = [file];
|
|
11914
|
+
currentBaseName = isNumbered ? baseName : null;
|
|
11915
|
+
}
|
|
11916
|
+
}
|
|
11917
|
+
flushGroup();
|
|
11918
|
+
return actionItems;
|
|
11919
|
+
}
|
|
11920
|
+
getBaseName(title) {
|
|
11921
|
+
const match = title.match(/^(.+?)\s*\d+$/);
|
|
11922
|
+
return match ? match[1].trim() : title;
|
|
11923
|
+
}
|
|
12311
11924
|
};
|
|
12312
11925
|
|
|
12313
11926
|
// src/providers/index.ts
|
|
@@ -12392,15 +12005,15 @@ function getProviderConfig(providerId) {
|
|
|
12392
12005
|
const provider = getProvider(providerId);
|
|
12393
12006
|
return provider?.config || null;
|
|
12394
12007
|
}
|
|
12395
|
-
function getAvailableProviders() {
|
|
12008
|
+
function getAvailableProviders(ids) {
|
|
12396
12009
|
const implemented = getAllProviders().map((provider) => ({
|
|
12397
12010
|
id: provider.id,
|
|
12398
12011
|
name: provider.name,
|
|
12399
12012
|
logos: provider.logos,
|
|
12400
12013
|
implemented: true,
|
|
12401
|
-
requiresAuth: provider.requiresAuth
|
|
12402
|
-
authTypes: provider.
|
|
12403
|
-
capabilities: provider.
|
|
12014
|
+
requiresAuth: provider.requiresAuth,
|
|
12015
|
+
authTypes: provider.authTypes,
|
|
12016
|
+
capabilities: provider.capabilities
|
|
12404
12017
|
}));
|
|
12405
12018
|
const comingSoon = unimplementedProviders.map((p) => ({
|
|
12406
12019
|
id: p.id,
|
|
@@ -12411,23 +12024,37 @@ function getAvailableProviders() {
|
|
|
12411
12024
|
authTypes: [],
|
|
12412
12025
|
capabilities: { browse: false, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false }
|
|
12413
12026
|
}));
|
|
12414
|
-
|
|
12027
|
+
const all = [...implemented, ...comingSoon];
|
|
12028
|
+
if (ids && ids.length > 0) {
|
|
12029
|
+
const idSet = new Set(ids);
|
|
12030
|
+
return all.filter((provider) => idSet.has(provider.id));
|
|
12031
|
+
}
|
|
12032
|
+
return all;
|
|
12415
12033
|
}
|
|
12416
12034
|
|
|
12417
12035
|
// src/index.ts
|
|
12418
12036
|
var VERSION = "0.0.1";
|
|
12419
12037
|
export {
|
|
12420
12038
|
APlayProvider,
|
|
12039
|
+
ApiHelper,
|
|
12421
12040
|
B1ChurchProvider,
|
|
12422
12041
|
BibleProjectProvider,
|
|
12423
12042
|
ContentProvider,
|
|
12043
|
+
DeviceFlowHelper,
|
|
12424
12044
|
FormatConverters_exports as FormatConverters,
|
|
12425
12045
|
FormatResolver,
|
|
12046
|
+
HighVoltageKidsProvider,
|
|
12426
12047
|
LessonsChurchProvider,
|
|
12048
|
+
OAuthHelper,
|
|
12427
12049
|
PlanningCenterProvider,
|
|
12428
12050
|
SignPresenterProvider,
|
|
12051
|
+
TokenHelper,
|
|
12429
12052
|
VERSION,
|
|
12053
|
+
appendToPath,
|
|
12054
|
+
buildPath,
|
|
12430
12055
|
collapseInstructions,
|
|
12056
|
+
createFile,
|
|
12057
|
+
createFolder,
|
|
12431
12058
|
detectMediaType,
|
|
12432
12059
|
expandedInstructionsToPlaylist,
|
|
12433
12060
|
expandedInstructionsToPresentations,
|
|
@@ -12435,10 +12062,12 @@ export {
|
|
|
12435
12062
|
getAvailableProviders,
|
|
12436
12063
|
getProvider,
|
|
12437
12064
|
getProviderConfig,
|
|
12065
|
+
getSegment,
|
|
12438
12066
|
instructionsToPlaylist,
|
|
12439
12067
|
instructionsToPresentations,
|
|
12440
12068
|
isContentFile,
|
|
12441
12069
|
isContentFolder,
|
|
12070
|
+
parsePath,
|
|
12442
12071
|
playlistToExpandedInstructions,
|
|
12443
12072
|
playlistToInstructions,
|
|
12444
12073
|
playlistToPresentations,
|