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