@churchapps/content-provider-helper 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1411 -1686
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +396 -105
- package/dist/index.d.ts +396 -105
- package/dist/index.js +1393 -1684
- package/dist/index.js.map +1 -1
- package/package.json +55 -48
package/dist/index.cjs
CHANGED
|
@@ -21,32 +21,48 @@ 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
|
+
DEFAULT_DURATION_CONFIG: () => DEFAULT_DURATION_CONFIG,
|
|
29
|
+
DeviceFlowHelper: () => DeviceFlowHelper,
|
|
27
30
|
FormatConverters: () => FormatConverters_exports,
|
|
28
31
|
FormatResolver: () => FormatResolver,
|
|
32
|
+
HighVoltageKidsProvider: () => HighVoltageKidsProvider,
|
|
29
33
|
LessonsChurchProvider: () => LessonsChurchProvider,
|
|
34
|
+
OAuthHelper: () => OAuthHelper,
|
|
30
35
|
PlanningCenterProvider: () => PlanningCenterProvider,
|
|
31
36
|
SignPresenterProvider: () => SignPresenterProvider,
|
|
37
|
+
TokenHelper: () => TokenHelper,
|
|
32
38
|
VERSION: () => VERSION,
|
|
33
|
-
|
|
39
|
+
appendToPath: () => appendToPath,
|
|
40
|
+
buildPath: () => buildPath,
|
|
41
|
+
countWords: () => countWords,
|
|
42
|
+
createFile: () => createFile,
|
|
43
|
+
createFolder: () => createFolder,
|
|
34
44
|
detectMediaType: () => detectMediaType,
|
|
45
|
+
estimateDuration: () => estimateDuration,
|
|
46
|
+
estimateImageDuration: () => estimateImageDuration,
|
|
47
|
+
estimateTextDuration: () => estimateTextDuration,
|
|
35
48
|
expandedInstructionsToPlaylist: () => expandedInstructionsToPlaylist,
|
|
36
49
|
expandedInstructionsToPresentations: () => expandedInstructionsToPresentations,
|
|
50
|
+
generatePath: () => generatePath,
|
|
37
51
|
getAllProviders: () => getAllProviders,
|
|
38
52
|
getAvailableProviders: () => getAvailableProviders,
|
|
39
53
|
getProvider: () => getProvider,
|
|
40
54
|
getProviderConfig: () => getProviderConfig,
|
|
55
|
+
getSegment: () => getSegment,
|
|
41
56
|
instructionsToPlaylist: () => instructionsToPlaylist,
|
|
42
57
|
instructionsToPresentations: () => instructionsToPresentations,
|
|
43
58
|
isContentFile: () => isContentFile,
|
|
44
59
|
isContentFolder: () => isContentFolder,
|
|
60
|
+
navigateToPath: () => navigateToPath,
|
|
61
|
+
parsePath: () => parsePath,
|
|
45
62
|
playlistToExpandedInstructions: () => playlistToExpandedInstructions,
|
|
46
63
|
playlistToInstructions: () => playlistToInstructions,
|
|
47
64
|
playlistToPresentations: () => playlistToPresentations,
|
|
48
65
|
presentationsToExpandedInstructions: () => presentationsToExpandedInstructions,
|
|
49
|
-
presentationsToInstructions: () => presentationsToInstructions,
|
|
50
66
|
presentationsToPlaylist: () => presentationsToPlaylist,
|
|
51
67
|
registerProvider: () => registerProvider
|
|
52
68
|
});
|
|
@@ -66,11 +82,92 @@ function detectMediaType(url, explicitType) {
|
|
|
66
82
|
const videoPatterns = [".mp4", ".webm", ".m3u8", ".mov", "stream.mux.com"];
|
|
67
83
|
return videoPatterns.some((p) => url.includes(p)) ? "video" : "image";
|
|
68
84
|
}
|
|
85
|
+
function createFolder(id, title, path, image, isLeaf) {
|
|
86
|
+
return { type: "folder", id, title, path, image, isLeaf };
|
|
87
|
+
}
|
|
88
|
+
function createFile(id, title, url, options) {
|
|
89
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, seconds: options?.seconds, loop: options?.loop, loopVideo: options?.loopVideo, streamUrl: options?.streamUrl };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/pathUtils.ts
|
|
93
|
+
function parsePath(path) {
|
|
94
|
+
if (!path || path === "/" || path === "") {
|
|
95
|
+
return { segments: [], depth: 0 };
|
|
96
|
+
}
|
|
97
|
+
const segments = path.replace(/^\//, "").split("/").filter(Boolean);
|
|
98
|
+
return { segments, depth: segments.length };
|
|
99
|
+
}
|
|
100
|
+
function getSegment(path, index) {
|
|
101
|
+
const { segments } = parsePath(path);
|
|
102
|
+
return segments[index] ?? null;
|
|
103
|
+
}
|
|
104
|
+
function buildPath(segments) {
|
|
105
|
+
if (segments.length === 0) return "";
|
|
106
|
+
return "/" + segments.join("/");
|
|
107
|
+
}
|
|
108
|
+
function appendToPath(basePath, segment) {
|
|
109
|
+
if (!basePath || basePath === "/" || basePath === "") {
|
|
110
|
+
return "/" + segment;
|
|
111
|
+
}
|
|
112
|
+
const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
113
|
+
return cleanBase + "/" + segment;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/instructionPathUtils.ts
|
|
117
|
+
function navigateToPath(instructions, path) {
|
|
118
|
+
if (!path || !instructions?.items) return null;
|
|
119
|
+
const indices = path.split(".").map(Number);
|
|
120
|
+
if (indices.some(isNaN)) return null;
|
|
121
|
+
let current = instructions.items[indices[0]] || null;
|
|
122
|
+
for (let i = 1; i < indices.length && current; i++) {
|
|
123
|
+
current = current.children?.[indices[i]] || null;
|
|
124
|
+
}
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
function generatePath(indices) {
|
|
128
|
+
return indices.join(".");
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// src/durationUtils.ts
|
|
132
|
+
var DEFAULT_DURATION_CONFIG = {
|
|
133
|
+
secondsPerImage: 15,
|
|
134
|
+
wordsPerMinute: 150
|
|
135
|
+
};
|
|
136
|
+
function countWords(text) {
|
|
137
|
+
if (!text || !text.trim()) return 0;
|
|
138
|
+
return text.trim().split(/\s+/).length;
|
|
139
|
+
}
|
|
140
|
+
function estimateImageDuration(config = {}) {
|
|
141
|
+
return config.secondsPerImage ?? DEFAULT_DURATION_CONFIG.secondsPerImage;
|
|
142
|
+
}
|
|
143
|
+
function estimateTextDuration(text, config = {}) {
|
|
144
|
+
const words = countWords(text);
|
|
145
|
+
const wpm = config.wordsPerMinute ?? DEFAULT_DURATION_CONFIG.wordsPerMinute;
|
|
146
|
+
return Math.ceil(words / wpm * 60);
|
|
147
|
+
}
|
|
148
|
+
function estimateDuration(mediaType, options) {
|
|
149
|
+
const config = options?.config ?? {};
|
|
150
|
+
switch (mediaType) {
|
|
151
|
+
case "image":
|
|
152
|
+
return estimateImageDuration(config);
|
|
153
|
+
case "text":
|
|
154
|
+
if (options?.wordCount) {
|
|
155
|
+
const wpm = config.wordsPerMinute ?? DEFAULT_DURATION_CONFIG.wordsPerMinute;
|
|
156
|
+
return Math.ceil(options.wordCount / wpm * 60);
|
|
157
|
+
}
|
|
158
|
+
if (options?.text) {
|
|
159
|
+
return estimateTextDuration(options.text, config);
|
|
160
|
+
}
|
|
161
|
+
return 0;
|
|
162
|
+
case "video":
|
|
163
|
+
default:
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
69
167
|
|
|
70
168
|
// src/FormatConverters.ts
|
|
71
169
|
var FormatConverters_exports = {};
|
|
72
170
|
__export(FormatConverters_exports, {
|
|
73
|
-
collapseInstructions: () => collapseInstructions,
|
|
74
171
|
expandedInstructionsToPlaylist: () => expandedInstructionsToPlaylist,
|
|
75
172
|
expandedInstructionsToPresentations: () => expandedInstructionsToPresentations,
|
|
76
173
|
instructionsToPlaylist: () => instructionsToPlaylist,
|
|
@@ -79,7 +176,6 @@ __export(FormatConverters_exports, {
|
|
|
79
176
|
playlistToInstructions: () => playlistToInstructions,
|
|
80
177
|
playlistToPresentations: () => playlistToPresentations,
|
|
81
178
|
presentationsToExpandedInstructions: () => presentationsToExpandedInstructions,
|
|
82
|
-
presentationsToInstructions: () => presentationsToInstructions,
|
|
83
179
|
presentationsToPlaylist: () => presentationsToPlaylist
|
|
84
180
|
});
|
|
85
181
|
function generateId() {
|
|
@@ -123,70 +219,15 @@ function presentationsToPlaylist(plan) {
|
|
|
123
219
|
}
|
|
124
220
|
return files;
|
|
125
221
|
}
|
|
126
|
-
function presentationsToInstructions(plan) {
|
|
127
|
-
return {
|
|
128
|
-
venueName: plan.name,
|
|
129
|
-
items: plan.sections.map((section) => ({
|
|
130
|
-
id: section.id,
|
|
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
|
-
};
|
|
148
|
-
}
|
|
149
222
|
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
|
-
};
|
|
223
|
+
return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => ({ id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, description: pres.actionType !== "other" ? pres.actionType : void 0, seconds: pres.files.reduce((sum, f) => sum + (f.seconds || 0), 0) || void 0, children: pres.files.map((f) => ({ id: f.id, itemType: "file", label: f.title, seconds: f.seconds, embedUrl: f.embedUrl || f.url })) })) })) };
|
|
175
224
|
}
|
|
176
225
|
function instructionsToPlaylist(instructions) {
|
|
177
226
|
const files = [];
|
|
178
227
|
function extractFiles(items) {
|
|
179
228
|
for (const item of items) {
|
|
180
229
|
if (item.embedUrl && (item.itemType === "file" || !item.children?.length)) {
|
|
181
|
-
files.push({
|
|
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
|
-
});
|
|
230
|
+
files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: detectMediaType(item.embedUrl), url: item.embedUrl, embedUrl: item.embedUrl, seconds: item.seconds });
|
|
190
231
|
}
|
|
191
232
|
if (item.children) {
|
|
192
233
|
extractFiles(item.children);
|
|
@@ -205,116 +246,30 @@ function instructionsToPresentations(instructions, planId) {
|
|
|
205
246
|
if (presItem.children && presItem.children.length > 0) {
|
|
206
247
|
for (const child of presItem.children) {
|
|
207
248
|
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
|
-
};
|
|
249
|
+
const file = { type: "file", id: child.id || child.relatedId || generateId(), title: child.label || "Untitled", mediaType: detectMediaType(child.embedUrl), url: child.embedUrl, embedUrl: child.embedUrl, seconds: child.seconds };
|
|
217
250
|
allFiles.push(file);
|
|
218
251
|
files.push(file);
|
|
219
252
|
}
|
|
220
253
|
}
|
|
221
254
|
}
|
|
222
255
|
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
|
-
};
|
|
256
|
+
const file = { type: "file", id: presItem.id || presItem.relatedId || generateId(), title: presItem.label || "Untitled", mediaType: detectMediaType(presItem.embedUrl), url: presItem.embedUrl, embedUrl: presItem.embedUrl, seconds: presItem.seconds };
|
|
232
257
|
allFiles.push(file);
|
|
233
258
|
files.push(file);
|
|
234
259
|
}
|
|
235
|
-
return {
|
|
236
|
-
id: presItem.id || presItem.relatedId || generateId(),
|
|
237
|
-
name: presItem.label || "Presentation",
|
|
238
|
-
actionType: mapItemTypeToActionType(presItem.itemType),
|
|
239
|
-
files
|
|
240
|
-
};
|
|
260
|
+
return { id: presItem.id || presItem.relatedId || generateId(), name: presItem.label || "Presentation", actionType: mapItemTypeToActionType(presItem.itemType), files };
|
|
241
261
|
});
|
|
242
|
-
return {
|
|
243
|
-
id: sectionItem.id || sectionItem.relatedId || generateId(),
|
|
244
|
-
name: sectionItem.label || "Section",
|
|
245
|
-
presentations
|
|
246
|
-
};
|
|
262
|
+
return { id: sectionItem.id || sectionItem.relatedId || generateId(), name: sectionItem.label || "Section", presentations };
|
|
247
263
|
});
|
|
248
|
-
return {
|
|
249
|
-
id: planId || generateId(),
|
|
250
|
-
name: instructions.venueName || "Plan",
|
|
251
|
-
sections,
|
|
252
|
-
allFiles
|
|
253
|
-
};
|
|
264
|
+
return { id: planId || generateId(), name: instructions.venueName || "Plan", sections, allFiles };
|
|
254
265
|
}
|
|
255
266
|
var expandedInstructionsToPresentations = instructionsToPresentations;
|
|
256
|
-
function collapseInstructions(instructions, maxDepth = 2) {
|
|
257
|
-
function collapseItem(item, currentDepth) {
|
|
258
|
-
if (currentDepth >= maxDepth || !item.children || item.children.length === 0) {
|
|
259
|
-
const { children, ...rest } = item;
|
|
260
|
-
if (children && children.length > 0) {
|
|
261
|
-
const totalSeconds = children.reduce((sum, c) => sum + (c.seconds || 0), 0);
|
|
262
|
-
if (totalSeconds > 0) {
|
|
263
|
-
rest.seconds = totalSeconds;
|
|
264
|
-
}
|
|
265
|
-
if (!rest.embedUrl) {
|
|
266
|
-
const firstWithUrl = children.find((c) => c.embedUrl);
|
|
267
|
-
if (firstWithUrl) {
|
|
268
|
-
rest.embedUrl = firstWithUrl.embedUrl;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
return rest;
|
|
273
|
-
}
|
|
274
|
-
return {
|
|
275
|
-
...item,
|
|
276
|
-
children: item.children.map((child) => collapseItem(child, currentDepth + 1))
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
return {
|
|
280
|
-
venueName: instructions.venueName,
|
|
281
|
-
items: instructions.items.map((item) => collapseItem(item, 0))
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
267
|
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
|
-
};
|
|
268
|
+
const presentations = files.map((file, index) => ({ id: `pres-${index}-${file.id}`, name: file.title, actionType: "play", files: [file] }));
|
|
269
|
+
return { id: "playlist-plan-" + generateId(), name: planName, sections: [{ id: "main-section", name: sectionName, presentations }], allFiles: [...files] };
|
|
301
270
|
}
|
|
302
271
|
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
|
-
};
|
|
272
|
+
return { venueName, items: [{ id: "main-section", itemType: "section", label: "Content", children: files.map((file, index) => ({ id: file.id || `item-${index}`, itemType: "file", label: file.title, seconds: file.seconds, embedUrl: file.embedUrl || file.url })) }] };
|
|
318
273
|
}
|
|
319
274
|
var playlistToExpandedInstructions = playlistToInstructions;
|
|
320
275
|
|
|
@@ -322,310 +277,130 @@ var playlistToExpandedInstructions = playlistToInstructions;
|
|
|
322
277
|
var FormatResolver = class {
|
|
323
278
|
constructor(provider, options = {}) {
|
|
324
279
|
this.provider = provider;
|
|
325
|
-
this.options = {
|
|
326
|
-
allowLossy: options.allowLossy ?? true
|
|
327
|
-
};
|
|
280
|
+
this.options = { allowLossy: options.allowLossy ?? true };
|
|
328
281
|
}
|
|
329
282
|
getProvider() {
|
|
330
283
|
return this.provider;
|
|
331
284
|
}
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
285
|
+
/** Extract the last segment from a path to use as fallback ID/title */
|
|
286
|
+
getIdFromPath(path) {
|
|
287
|
+
const { segments } = parsePath(path);
|
|
288
|
+
return segments[segments.length - 1] || "content";
|
|
289
|
+
}
|
|
290
|
+
async getPlaylist(path, auth) {
|
|
291
|
+
const caps = this.provider.capabilities;
|
|
292
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
293
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
336
294
|
if (result && result.length > 0) return result;
|
|
337
295
|
}
|
|
338
296
|
if (caps.presentations) {
|
|
339
|
-
const plan = await this.provider.getPresentations(
|
|
297
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
340
298
|
if (plan) return presentationsToPlaylist(plan);
|
|
341
299
|
}
|
|
342
|
-
if (caps.
|
|
343
|
-
const expanded = await this.provider.
|
|
300
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
301
|
+
const expanded = await this.provider.getInstructions(path, auth);
|
|
344
302
|
if (expanded) return instructionsToPlaylist(expanded);
|
|
345
303
|
}
|
|
346
|
-
if (this.options.allowLossy && caps.instructions) {
|
|
347
|
-
const instructions = await this.provider.getInstructions(folder, auth);
|
|
348
|
-
if (instructions) return instructionsToPlaylist(instructions);
|
|
349
|
-
}
|
|
350
304
|
return null;
|
|
351
305
|
}
|
|
352
|
-
async getPlaylistWithMeta(
|
|
353
|
-
const caps = this.provider.
|
|
354
|
-
if (caps.playlist) {
|
|
355
|
-
const result = await this.provider.getPlaylist(
|
|
306
|
+
async getPlaylistWithMeta(path, auth) {
|
|
307
|
+
const caps = this.provider.capabilities;
|
|
308
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
309
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
356
310
|
if (result && result.length > 0) {
|
|
357
311
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
358
312
|
}
|
|
359
313
|
}
|
|
360
314
|
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
|
-
}
|
|
368
|
-
}
|
|
369
|
-
if (caps.expandedInstructions) {
|
|
370
|
-
const expanded = await this.provider.getExpandedInstructions(folder, auth);
|
|
371
|
-
if (expanded) {
|
|
372
|
-
return {
|
|
373
|
-
data: instructionsToPlaylist(expanded),
|
|
374
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false }
|
|
375
|
-
};
|
|
376
|
-
}
|
|
315
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
316
|
+
if (plan) return { data: presentationsToPlaylist(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
377
317
|
}
|
|
378
|
-
if (
|
|
379
|
-
const
|
|
380
|
-
if (
|
|
381
|
-
return {
|
|
382
|
-
data: instructionsToPlaylist(instructions),
|
|
383
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
384
|
-
};
|
|
385
|
-
}
|
|
318
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
319
|
+
const expanded = await this.provider.getInstructions(path, auth);
|
|
320
|
+
if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "instructions", isLossy: false } };
|
|
386
321
|
}
|
|
387
322
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
388
323
|
}
|
|
389
|
-
async getPresentations(
|
|
390
|
-
const caps = this.provider.
|
|
324
|
+
async getPresentations(path, auth) {
|
|
325
|
+
const caps = this.provider.capabilities;
|
|
326
|
+
const fallbackId = this.getIdFromPath(path);
|
|
391
327
|
if (caps.presentations) {
|
|
392
|
-
const result = await this.provider.getPresentations(
|
|
328
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
393
329
|
if (result) return result;
|
|
394
330
|
}
|
|
395
|
-
if (caps.
|
|
396
|
-
const expanded = await this.provider.
|
|
397
|
-
if (expanded) return instructionsToPresentations(expanded,
|
|
398
|
-
}
|
|
399
|
-
if (caps.instructions) {
|
|
400
|
-
const instructions = await this.provider.getInstructions(folder, auth);
|
|
401
|
-
if (instructions) return instructionsToPresentations(instructions, folder.id);
|
|
331
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
332
|
+
const expanded = await this.provider.getInstructions(path, auth);
|
|
333
|
+
if (expanded) return instructionsToPresentations(expanded, fallbackId);
|
|
402
334
|
}
|
|
403
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
404
|
-
const playlist = await this.provider.getPlaylist(
|
|
335
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
336
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
405
337
|
if (playlist && playlist.length > 0) {
|
|
406
|
-
return playlistToPresentations(playlist,
|
|
338
|
+
return playlistToPresentations(playlist, fallbackId);
|
|
407
339
|
}
|
|
408
340
|
}
|
|
409
341
|
return null;
|
|
410
342
|
}
|
|
411
|
-
async getPresentationsWithMeta(
|
|
412
|
-
const caps = this.provider.
|
|
413
|
-
|
|
414
|
-
const result = await this.provider.getPresentations(folder, auth);
|
|
415
|
-
if (result) {
|
|
416
|
-
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
if (caps.expandedInstructions) {
|
|
420
|
-
const expanded = await this.provider.getExpandedInstructions(folder, auth);
|
|
421
|
-
if (expanded) {
|
|
422
|
-
return {
|
|
423
|
-
data: instructionsToPresentations(expanded, folder.id),
|
|
424
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false }
|
|
425
|
-
};
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (caps.instructions) {
|
|
429
|
-
const instructions = await this.provider.getInstructions(folder, auth);
|
|
430
|
-
if (instructions) {
|
|
431
|
-
return {
|
|
432
|
-
data: instructionsToPresentations(instructions, folder.id),
|
|
433
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
434
|
-
};
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
438
|
-
const playlist = await this.provider.getPlaylist(folder, auth);
|
|
439
|
-
if (playlist && playlist.length > 0) {
|
|
440
|
-
return {
|
|
441
|
-
data: playlistToPresentations(playlist, folder.title),
|
|
442
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
443
|
-
};
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
447
|
-
}
|
|
448
|
-
async getInstructions(folder, auth) {
|
|
449
|
-
const caps = this.provider.getCapabilities();
|
|
450
|
-
if (caps.instructions) {
|
|
451
|
-
const result = await this.provider.getInstructions(folder, auth);
|
|
452
|
-
if (result) return result;
|
|
453
|
-
}
|
|
454
|
-
if (caps.expandedInstructions) {
|
|
455
|
-
const expanded = await this.provider.getExpandedInstructions(folder, auth);
|
|
456
|
-
if (expanded) return collapseInstructions(expanded);
|
|
457
|
-
}
|
|
343
|
+
async getPresentationsWithMeta(path, auth) {
|
|
344
|
+
const caps = this.provider.capabilities;
|
|
345
|
+
const fallbackId = this.getIdFromPath(path);
|
|
458
346
|
if (caps.presentations) {
|
|
459
|
-
const
|
|
460
|
-
if (plan) return presentationsToInstructions(plan);
|
|
461
|
-
}
|
|
462
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
463
|
-
const playlist = await this.provider.getPlaylist(folder, auth);
|
|
464
|
-
if (playlist && playlist.length > 0) {
|
|
465
|
-
return playlistToInstructions(playlist, folder.title);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
return null;
|
|
469
|
-
}
|
|
470
|
-
async getInstructionsWithMeta(folder, auth) {
|
|
471
|
-
const caps = this.provider.getCapabilities();
|
|
472
|
-
if (caps.instructions) {
|
|
473
|
-
const result = await this.provider.getInstructions(folder, auth);
|
|
347
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
474
348
|
if (result) {
|
|
475
349
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
476
350
|
}
|
|
477
351
|
}
|
|
478
|
-
if (caps.
|
|
479
|
-
const expanded = await this.provider.
|
|
480
|
-
if (expanded) {
|
|
481
|
-
return {
|
|
482
|
-
data: collapseInstructions(expanded),
|
|
483
|
-
meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: true }
|
|
484
|
-
};
|
|
485
|
-
}
|
|
352
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
353
|
+
const expanded = await this.provider.getInstructions(path, auth);
|
|
354
|
+
if (expanded) return { data: instructionsToPresentations(expanded, fallbackId), meta: { isNative: false, sourceFormat: "instructions", isLossy: false } };
|
|
486
355
|
}
|
|
487
|
-
if (caps.
|
|
488
|
-
const
|
|
489
|
-
if (
|
|
490
|
-
return {
|
|
491
|
-
data: presentationsToInstructions(plan),
|
|
492
|
-
meta: { isNative: false, sourceFormat: "presentations", isLossy: false }
|
|
493
|
-
};
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
497
|
-
const playlist = await this.provider.getPlaylist(folder, auth);
|
|
498
|
-
if (playlist && playlist.length > 0) {
|
|
499
|
-
return {
|
|
500
|
-
data: playlistToInstructions(playlist, folder.title),
|
|
501
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
502
|
-
};
|
|
503
|
-
}
|
|
356
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
357
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
358
|
+
if (playlist && playlist.length > 0) return { data: playlistToPresentations(playlist, fallbackId), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
504
359
|
}
|
|
505
360
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
506
361
|
}
|
|
507
|
-
async
|
|
508
|
-
const caps = this.provider.
|
|
509
|
-
|
|
510
|
-
|
|
362
|
+
async getInstructions(path, auth) {
|
|
363
|
+
const caps = this.provider.capabilities;
|
|
364
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
365
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
366
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
511
367
|
if (result) return result;
|
|
512
368
|
}
|
|
513
369
|
if (caps.presentations) {
|
|
514
|
-
const plan = await this.provider.getPresentations(
|
|
370
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
515
371
|
if (plan) return presentationsToExpandedInstructions(plan);
|
|
516
372
|
}
|
|
517
|
-
if (caps.
|
|
518
|
-
const
|
|
519
|
-
if (instructions) return instructions;
|
|
520
|
-
}
|
|
521
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
522
|
-
const playlist = await this.provider.getPlaylist(folder, auth);
|
|
373
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
374
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
523
375
|
if (playlist && playlist.length > 0) {
|
|
524
|
-
return playlistToInstructions(playlist,
|
|
376
|
+
return playlistToInstructions(playlist, fallbackTitle);
|
|
525
377
|
}
|
|
526
378
|
}
|
|
527
379
|
return null;
|
|
528
380
|
}
|
|
529
|
-
async
|
|
530
|
-
const caps = this.provider.
|
|
531
|
-
|
|
532
|
-
|
|
381
|
+
async getInstructionsWithMeta(path, auth) {
|
|
382
|
+
const caps = this.provider.capabilities;
|
|
383
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
384
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
385
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
533
386
|
if (result) {
|
|
534
387
|
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
535
388
|
}
|
|
536
389
|
}
|
|
537
390
|
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
|
-
}
|
|
391
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
392
|
+
if (plan) return { data: presentationsToExpandedInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
545
393
|
}
|
|
546
|
-
if (caps.
|
|
547
|
-
const
|
|
548
|
-
if (
|
|
549
|
-
return {
|
|
550
|
-
data: instructions,
|
|
551
|
-
meta: { isNative: false, sourceFormat: "instructions", isLossy: true }
|
|
552
|
-
};
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (this.options.allowLossy && caps.playlist) {
|
|
556
|
-
const playlist = await this.provider.getPlaylist(folder, auth);
|
|
557
|
-
if (playlist && playlist.length > 0) {
|
|
558
|
-
return {
|
|
559
|
-
data: playlistToInstructions(playlist, folder.title),
|
|
560
|
-
meta: { isNative: false, sourceFormat: "playlist", isLossy: true }
|
|
561
|
-
};
|
|
562
|
-
}
|
|
394
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
395
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
396
|
+
if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
563
397
|
}
|
|
564
398
|
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
565
399
|
}
|
|
566
400
|
};
|
|
567
401
|
|
|
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
|
-
}
|
|
402
|
+
// src/helpers/OAuthHelper.ts
|
|
403
|
+
var OAuthHelper = class {
|
|
629
404
|
generateCodeVerifier() {
|
|
630
405
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
631
406
|
const length = 64;
|
|
@@ -649,110 +424,91 @@ var ContentProvider = class {
|
|
|
649
424
|
const base64 = btoa(binary);
|
|
650
425
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
651
426
|
}
|
|
652
|
-
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
427
|
+
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
653
428
|
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
654
429
|
const params = new URLSearchParams({
|
|
655
430
|
response_type: "code",
|
|
656
|
-
client_id:
|
|
431
|
+
client_id: config.clientId,
|
|
657
432
|
redirect_uri: redirectUri,
|
|
658
|
-
scope:
|
|
433
|
+
scope: config.scopes.join(" "),
|
|
659
434
|
code_challenge: codeChallenge,
|
|
660
435
|
code_challenge_method: "S256",
|
|
661
|
-
state: state ||
|
|
436
|
+
state: state || ""
|
|
662
437
|
});
|
|
663
|
-
return { url: `${
|
|
438
|
+
return { url: `${config.oauthBase}/authorize?${params.toString()}`, challengeMethod: "S256" };
|
|
664
439
|
}
|
|
665
|
-
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
440
|
+
async exchangeCodeForTokens(config, providerId, code, codeVerifier, redirectUri) {
|
|
666
441
|
try {
|
|
667
442
|
const params = new URLSearchParams({
|
|
668
443
|
grant_type: "authorization_code",
|
|
669
444
|
code,
|
|
670
445
|
redirect_uri: redirectUri,
|
|
671
|
-
client_id:
|
|
446
|
+
client_id: config.clientId,
|
|
672
447
|
code_verifier: codeVerifier
|
|
673
448
|
});
|
|
674
|
-
const tokenUrl = `${
|
|
675
|
-
console.log(`${this.id} token exchange request to: ${tokenUrl}`);
|
|
676
|
-
console.log(` - client_id: ${this.config.clientId}`);
|
|
677
|
-
console.log(` - redirect_uri: ${redirectUri}`);
|
|
678
|
-
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
449
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
679
450
|
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
680
|
-
console.log(`${this.id} token response status: ${response.status}`);
|
|
681
451
|
if (!response.ok) {
|
|
682
|
-
const errorText = await response.text();
|
|
683
|
-
console.error(`${this.id} token exchange failed: ${response.status} - ${errorText}`);
|
|
684
452
|
return null;
|
|
685
453
|
}
|
|
686
454
|
const data = await response.json();
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
};
|
|
696
|
-
} catch (error) {
|
|
697
|
-
console.error(`${this.id} token exchange error:`, error);
|
|
455
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
456
|
+
} catch {
|
|
698
457
|
return null;
|
|
699
458
|
}
|
|
700
459
|
}
|
|
701
|
-
|
|
460
|
+
};
|
|
461
|
+
|
|
462
|
+
// src/helpers/TokenHelper.ts
|
|
463
|
+
var TokenHelper = class {
|
|
464
|
+
isAuthValid(auth) {
|
|
465
|
+
if (!auth) return false;
|
|
466
|
+
return !this.isTokenExpired(auth);
|
|
467
|
+
}
|
|
468
|
+
isTokenExpired(auth) {
|
|
469
|
+
if (!auth.created_at || !auth.expires_in) return true;
|
|
470
|
+
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
471
|
+
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
472
|
+
}
|
|
473
|
+
async refreshToken(config, auth) {
|
|
702
474
|
if (!auth.refresh_token) return null;
|
|
703
475
|
try {
|
|
704
476
|
const params = new URLSearchParams({
|
|
705
477
|
grant_type: "refresh_token",
|
|
706
478
|
refresh_token: auth.refresh_token,
|
|
707
|
-
client_id:
|
|
479
|
+
client_id: config.clientId
|
|
708
480
|
});
|
|
709
|
-
const response = await fetch(`${
|
|
481
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
710
482
|
if (!response.ok) return null;
|
|
711
483
|
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
|
-
};
|
|
484
|
+
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
485
|
} catch {
|
|
721
486
|
return null;
|
|
722
487
|
}
|
|
723
488
|
}
|
|
724
|
-
|
|
725
|
-
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
// src/helpers/DeviceFlowHelper.ts
|
|
492
|
+
var DeviceFlowHelper = class {
|
|
493
|
+
supportsDeviceFlow(config) {
|
|
494
|
+
return !!config.supportsDeviceFlow && !!config.deviceAuthEndpoint;
|
|
726
495
|
}
|
|
727
|
-
async initiateDeviceFlow() {
|
|
728
|
-
if (!this.supportsDeviceFlow()) return null;
|
|
496
|
+
async initiateDeviceFlow(config) {
|
|
497
|
+
if (!this.supportsDeviceFlow(config)) return null;
|
|
729
498
|
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() });
|
|
499
|
+
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
500
|
if (!response.ok) return null;
|
|
733
501
|
return await response.json();
|
|
734
502
|
} catch {
|
|
735
503
|
return null;
|
|
736
504
|
}
|
|
737
505
|
}
|
|
738
|
-
async pollDeviceFlowToken(deviceCode) {
|
|
506
|
+
async pollDeviceFlowToken(config, deviceCode) {
|
|
739
507
|
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() });
|
|
508
|
+
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
509
|
if (response.ok) {
|
|
747
510
|
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
|
-
};
|
|
511
|
+
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
512
|
}
|
|
757
513
|
const errorData = await response.json();
|
|
758
514
|
switch (errorData.error) {
|
|
@@ -774,183 +530,236 @@ var ContentProvider = class {
|
|
|
774
530
|
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
775
531
|
return (baseInterval + slowDownCount * 5) * 1e3;
|
|
776
532
|
}
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
// src/helpers/ApiHelper.ts
|
|
536
|
+
var ApiHelper = class {
|
|
777
537
|
createAuthHeaders(auth) {
|
|
778
538
|
if (!auth) return null;
|
|
779
539
|
return { Authorization: `Bearer ${auth.access_token}`, Accept: "application/json" };
|
|
780
540
|
}
|
|
781
|
-
async apiRequest(path, auth, method = "GET", body) {
|
|
541
|
+
async apiRequest(config, _providerId, path, auth, method = "GET", body) {
|
|
782
542
|
try {
|
|
783
|
-
const url = `${
|
|
543
|
+
const url = `${config.apiBase}${path}`;
|
|
784
544
|
const headers = { Accept: "application/json" };
|
|
785
545
|
if (auth) headers["Authorization"] = `Bearer ${auth.access_token}`;
|
|
786
546
|
if (body) headers["Content-Type"] = "application/json";
|
|
787
|
-
console.log(`${this.id} API request: ${method} ${url}`);
|
|
788
|
-
console.log(`${this.id} API auth present: ${!!auth}`);
|
|
789
547
|
const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
|
|
790
548
|
const response = await fetch(url, options);
|
|
791
|
-
console.log(`${this.id} API response status: ${response.status}`);
|
|
792
549
|
if (!response.ok) {
|
|
793
|
-
const errorText = await response.text();
|
|
794
|
-
console.error(`${this.id} API request failed: ${response.status} - ${errorText}`);
|
|
795
550
|
return null;
|
|
796
551
|
}
|
|
797
552
|
return await response.json();
|
|
798
|
-
} catch
|
|
799
|
-
console.error(`${this.id} API request error:`, error);
|
|
553
|
+
} catch {
|
|
800
554
|
return null;
|
|
801
555
|
}
|
|
802
556
|
}
|
|
803
|
-
|
|
804
|
-
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
// src/ContentProvider.ts
|
|
560
|
+
var ContentProvider = class {
|
|
561
|
+
constructor() {
|
|
562
|
+
this.oauthHelper = new OAuthHelper();
|
|
563
|
+
this.tokenHelper = new TokenHelper();
|
|
564
|
+
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
565
|
+
this.apiHelper = new ApiHelper();
|
|
566
|
+
}
|
|
567
|
+
async getPlaylist(path, auth, _resolution) {
|
|
568
|
+
const caps = this.getCapabilities();
|
|
569
|
+
if (caps.presentations) {
|
|
570
|
+
const plan = await this.getPresentations(path, auth);
|
|
571
|
+
if (plan) return presentationsToPlaylist(plan);
|
|
572
|
+
}
|
|
573
|
+
return null;
|
|
574
|
+
}
|
|
575
|
+
async getInstructions(path, auth) {
|
|
576
|
+
const caps = this.getCapabilities();
|
|
577
|
+
if (caps.presentations) {
|
|
578
|
+
const plan = await this.getPresentations(path, auth);
|
|
579
|
+
if (plan) return presentationsToExpandedInstructions(plan);
|
|
580
|
+
}
|
|
581
|
+
return null;
|
|
582
|
+
}
|
|
583
|
+
requiresAuth() {
|
|
584
|
+
return !!this.config.clientId;
|
|
585
|
+
}
|
|
586
|
+
getCapabilities() {
|
|
587
|
+
return { browse: true, presentations: false, playlist: false, instructions: false, mediaLicensing: false };
|
|
588
|
+
}
|
|
589
|
+
checkMediaLicense(_mediaId, _auth) {
|
|
590
|
+
return Promise.resolve(null);
|
|
591
|
+
}
|
|
592
|
+
getAuthTypes() {
|
|
593
|
+
if (!this.requiresAuth()) return ["none"];
|
|
594
|
+
const types = ["oauth_pkce"];
|
|
595
|
+
if (this.supportsDeviceFlow()) types.push("device_flow");
|
|
596
|
+
return types;
|
|
597
|
+
}
|
|
598
|
+
// Token management - delegated to TokenHelper
|
|
599
|
+
isAuthValid(auth) {
|
|
600
|
+
return this.tokenHelper.isAuthValid(auth);
|
|
601
|
+
}
|
|
602
|
+
isTokenExpired(auth) {
|
|
603
|
+
return this.tokenHelper.isTokenExpired(auth);
|
|
604
|
+
}
|
|
605
|
+
async refreshToken(auth) {
|
|
606
|
+
return this.tokenHelper.refreshToken(this.config, auth);
|
|
607
|
+
}
|
|
608
|
+
// OAuth PKCE - delegated to OAuthHelper
|
|
609
|
+
generateCodeVerifier() {
|
|
610
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
611
|
+
}
|
|
612
|
+
async generateCodeChallenge(verifier) {
|
|
613
|
+
return this.oauthHelper.generateCodeChallenge(verifier);
|
|
614
|
+
}
|
|
615
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
616
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
617
|
+
}
|
|
618
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
619
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
620
|
+
}
|
|
621
|
+
// Device flow - delegated to DeviceFlowHelper
|
|
622
|
+
supportsDeviceFlow() {
|
|
623
|
+
return this.deviceFlowHelper.supportsDeviceFlow(this.config);
|
|
624
|
+
}
|
|
625
|
+
async initiateDeviceFlow() {
|
|
626
|
+
return this.deviceFlowHelper.initiateDeviceFlow(this.config);
|
|
627
|
+
}
|
|
628
|
+
async pollDeviceFlowToken(deviceCode) {
|
|
629
|
+
return this.deviceFlowHelper.pollDeviceFlowToken(this.config, deviceCode);
|
|
630
|
+
}
|
|
631
|
+
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
632
|
+
return this.deviceFlowHelper.calculatePollDelay(baseInterval, slowDownCount);
|
|
633
|
+
}
|
|
634
|
+
// API requests - delegated to ApiHelper
|
|
635
|
+
createAuthHeaders(auth) {
|
|
636
|
+
return this.apiHelper.createAuthHeaders(auth);
|
|
637
|
+
}
|
|
638
|
+
async apiRequest(path, auth, method = "GET", body) {
|
|
639
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
|
|
640
|
+
}
|
|
641
|
+
// Content factories
|
|
642
|
+
createFolder(id, title, path, image, isLeaf) {
|
|
643
|
+
return { type: "folder", id, title, path, image, isLeaf };
|
|
805
644
|
}
|
|
806
645
|
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
|
-
};
|
|
646
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, seconds: options?.seconds, loop: options?.loop, loopVideo: options?.loopVideo, streamUrl: options?.streamUrl };
|
|
817
647
|
}
|
|
818
648
|
};
|
|
819
649
|
|
|
820
|
-
// src/providers/APlayProvider.ts
|
|
821
|
-
var APlayProvider = class
|
|
650
|
+
// src/providers/aPlay/APlayProvider.ts
|
|
651
|
+
var APlayProvider = class {
|
|
822
652
|
constructor() {
|
|
823
|
-
|
|
653
|
+
this.apiHelper = new ApiHelper();
|
|
824
654
|
this.id = "aplay";
|
|
825
655
|
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
|
-
};
|
|
656
|
+
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
657
|
+
this.config = { id: "aplay", name: "APlay", apiBase: "https://api-prod.amazingkids.app", oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"], endpoints: { modules: "/prod/curriculum/modules", productLibraries: (productId) => `/prod/curriculum/modules/products/${productId}/libraries`, libraryMedia: (libraryId) => `/prod/creators/libraries/${libraryId}/media` } };
|
|
658
|
+
this.requiresAuth = true;
|
|
659
|
+
this.authTypes = ["oauth_pkce"];
|
|
660
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: true };
|
|
843
661
|
}
|
|
844
|
-
|
|
845
|
-
return
|
|
846
|
-
browse: true,
|
|
847
|
-
presentations: true,
|
|
848
|
-
playlist: false,
|
|
849
|
-
instructions: false,
|
|
850
|
-
expandedInstructions: false,
|
|
851
|
-
mediaLicensing: true
|
|
852
|
-
};
|
|
662
|
+
async apiRequest(path, auth) {
|
|
663
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
853
664
|
}
|
|
854
|
-
async browse(
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
console.log(`APlay modules count:`, Array.isArray(modules) ? modules.length : "not an array");
|
|
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;
|
|
665
|
+
async browse(path, auth) {
|
|
666
|
+
const { segments, depth } = parsePath(path);
|
|
667
|
+
if (depth === 0) {
|
|
668
|
+
return [{
|
|
669
|
+
type: "folder",
|
|
670
|
+
id: "modules-root",
|
|
671
|
+
title: "Modules",
|
|
672
|
+
path: "/modules"
|
|
673
|
+
}];
|
|
901
674
|
}
|
|
902
|
-
const
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
default:
|
|
911
|
-
return [];
|
|
675
|
+
const root = segments[0];
|
|
676
|
+
if (root !== "modules") return [];
|
|
677
|
+
if (depth === 1) {
|
|
678
|
+
return this.getModules(auth);
|
|
679
|
+
}
|
|
680
|
+
if (depth === 2) {
|
|
681
|
+
const moduleId = segments[1];
|
|
682
|
+
return this.getModuleContent(moduleId, path, auth);
|
|
912
683
|
}
|
|
684
|
+
if (depth === 4 && segments[2] === "products") {
|
|
685
|
+
const productId = segments[3];
|
|
686
|
+
return this.getLibraryFolders(productId, path, auth);
|
|
687
|
+
}
|
|
688
|
+
if (depth === 5 && segments[2] === "products") {
|
|
689
|
+
const libraryId = segments[4];
|
|
690
|
+
return this.getMediaFiles(libraryId, auth);
|
|
691
|
+
}
|
|
692
|
+
if (depth === 4 && segments[2] === "libraries") {
|
|
693
|
+
const libraryId = segments[3];
|
|
694
|
+
return this.getMediaFiles(libraryId, auth);
|
|
695
|
+
}
|
|
696
|
+
return [];
|
|
913
697
|
}
|
|
914
|
-
|
|
915
|
-
const
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
698
|
+
async getModules(auth) {
|
|
699
|
+
const response = await this.apiRequest(this.config.endpoints.modules, auth);
|
|
700
|
+
if (!response) return [];
|
|
701
|
+
const modules = response.data || response.modules || response;
|
|
702
|
+
if (!Array.isArray(modules)) return [];
|
|
703
|
+
const items = [];
|
|
704
|
+
for (const m of modules) {
|
|
705
|
+
if (m.isLocked) continue;
|
|
706
|
+
const moduleId = m.id || m.moduleId;
|
|
707
|
+
const moduleTitle = m.title || m.name;
|
|
708
|
+
const moduleImage = m.image;
|
|
709
|
+
items.push({
|
|
710
|
+
type: "folder",
|
|
711
|
+
id: moduleId,
|
|
712
|
+
title: moduleTitle,
|
|
713
|
+
image: moduleImage,
|
|
714
|
+
path: `/modules/${moduleId}`
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
return items;
|
|
923
718
|
}
|
|
924
|
-
async
|
|
925
|
-
const
|
|
926
|
-
|
|
927
|
-
|
|
719
|
+
async getModuleContent(moduleId, currentPath, auth) {
|
|
720
|
+
const response = await this.apiRequest(this.config.endpoints.modules, auth);
|
|
721
|
+
if (!response) return [];
|
|
722
|
+
const modules = response.data || response.modules || response;
|
|
723
|
+
if (!Array.isArray(modules)) return [];
|
|
724
|
+
const module2 = modules.find((m) => (m.id || m.moduleId) === moduleId);
|
|
725
|
+
if (!module2) return [];
|
|
726
|
+
const allProducts = module2.products || [];
|
|
727
|
+
const products = allProducts.filter((p) => !p.isHidden);
|
|
728
|
+
if (products.length === 0) {
|
|
729
|
+
return this.getLibraryFolders(moduleId, `${currentPath}/libraries`, auth);
|
|
730
|
+
} else if (products.length === 1) {
|
|
731
|
+
const productId = products[0].productId || products[0].id;
|
|
732
|
+
return this.getLibraryFolders(productId, `${currentPath}/libraries`, auth);
|
|
733
|
+
} else {
|
|
734
|
+
return products.map((p) => ({
|
|
735
|
+
type: "folder",
|
|
736
|
+
id: p.productId || p.id,
|
|
737
|
+
title: p.title || p.name,
|
|
738
|
+
image: p.image,
|
|
739
|
+
path: `${currentPath}/products/${p.productId || p.id}`
|
|
740
|
+
}));
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
async getLibraryFolders(productId, currentPath, auth) {
|
|
928
744
|
const pathFn = this.config.endpoints.productLibraries;
|
|
929
|
-
const
|
|
930
|
-
|
|
931
|
-
const response = await this.apiRequest(path, auth);
|
|
932
|
-
console.log(`APlay libraries response:`, response ? "received" : "null");
|
|
745
|
+
const apiPath = pathFn(productId);
|
|
746
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
933
747
|
if (!response) return [];
|
|
934
748
|
const libraries = response.data || response.libraries || response;
|
|
935
|
-
console.log(`APlay libraries count:`, Array.isArray(libraries) ? libraries.length : "not an array");
|
|
936
749
|
if (!Array.isArray(libraries)) return [];
|
|
937
750
|
return libraries.map((l) => ({
|
|
938
751
|
type: "folder",
|
|
939
752
|
id: l.libraryId || l.id,
|
|
940
753
|
title: l.title || l.name,
|
|
941
754
|
image: l.image,
|
|
942
|
-
|
|
755
|
+
isLeaf: true,
|
|
756
|
+
path: `${currentPath}/${l.libraryId || l.id}`
|
|
943
757
|
}));
|
|
944
758
|
}
|
|
945
|
-
async getMediaFiles(
|
|
946
|
-
const libraryId = folder.providerData?.libraryId;
|
|
947
|
-
console.log(`APlay getMediaFiles called with libraryId:`, libraryId);
|
|
948
|
-
if (!libraryId) return [];
|
|
759
|
+
async getMediaFiles(libraryId, auth) {
|
|
949
760
|
const pathFn = this.config.endpoints.libraryMedia;
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
const response = await this.apiRequest(path, auth);
|
|
953
|
-
console.log(`APlay media response:`, response ? "received" : "null");
|
|
761
|
+
const apiPath = pathFn(libraryId);
|
|
762
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
954
763
|
if (!response) return [];
|
|
955
764
|
const mediaItems = response.data || response.media || response;
|
|
956
765
|
if (!Array.isArray(mediaItems)) return [];
|
|
@@ -980,140 +789,140 @@ var APlayProvider = class extends ContentProvider {
|
|
|
980
789
|
if (!url) continue;
|
|
981
790
|
const detectedMediaType = detectMediaType(url, mediaType);
|
|
982
791
|
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
|
-
});
|
|
792
|
+
files.push({ type: "file", id: fileId, title: item.title || item.name || item.fileName || "", mediaType: detectedMediaType, image: thumbnail, url, muxPlaybackId, mediaId: fileId });
|
|
993
793
|
}
|
|
994
794
|
return files;
|
|
995
795
|
}
|
|
996
|
-
async getPresentations(
|
|
997
|
-
const
|
|
998
|
-
if (
|
|
999
|
-
|
|
796
|
+
async getPresentations(path, auth) {
|
|
797
|
+
const { segments, depth } = parsePath(path);
|
|
798
|
+
if (depth < 4 || segments[0] !== "modules") return null;
|
|
799
|
+
let libraryId;
|
|
800
|
+
const title = "Library";
|
|
801
|
+
if (segments[2] === "products" && depth === 5) {
|
|
802
|
+
libraryId = segments[4];
|
|
803
|
+
} else if (segments[2] === "libraries" && depth === 4) {
|
|
804
|
+
libraryId = segments[3];
|
|
805
|
+
} else {
|
|
806
|
+
return null;
|
|
807
|
+
}
|
|
808
|
+
const files = await this.getMediaFiles(libraryId, auth);
|
|
809
|
+
if (files.length === 0) return null;
|
|
810
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
811
|
+
return { id: libraryId, name: title, sections: [{ id: `section-${libraryId}`, name: title, presentations }], allFiles: files };
|
|
812
|
+
}
|
|
813
|
+
async getPlaylist(path, auth, _resolution) {
|
|
814
|
+
const { segments, depth } = parsePath(path);
|
|
815
|
+
if (depth < 4 || segments[0] !== "modules") return null;
|
|
816
|
+
let libraryId;
|
|
817
|
+
if (segments[2] === "products" && depth === 5) {
|
|
818
|
+
libraryId = segments[4];
|
|
819
|
+
} else if (segments[2] === "libraries" && depth === 4) {
|
|
820
|
+
libraryId = segments[3];
|
|
821
|
+
} else {
|
|
822
|
+
return null;
|
|
823
|
+
}
|
|
824
|
+
const files = await this.getMediaFiles(libraryId, auth);
|
|
825
|
+
return files.length > 0 ? files : null;
|
|
826
|
+
}
|
|
827
|
+
async getInstructions(path, auth) {
|
|
828
|
+
const { segments, depth } = parsePath(path);
|
|
829
|
+
if (depth < 4 || segments[0] !== "modules") return null;
|
|
830
|
+
let libraryId;
|
|
831
|
+
if (segments[2] === "products" && depth === 5) {
|
|
832
|
+
libraryId = segments[4];
|
|
833
|
+
} else if (segments[2] === "libraries" && depth === 4) {
|
|
834
|
+
libraryId = segments[3];
|
|
835
|
+
} else {
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
const files = await this.getMediaFiles(libraryId, auth);
|
|
1000
839
|
if (files.length === 0) return null;
|
|
1001
|
-
const
|
|
1002
|
-
id:
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
840
|
+
const fileItems = files.map((file) => ({
|
|
841
|
+
id: file.id,
|
|
842
|
+
itemType: "file",
|
|
843
|
+
label: file.title,
|
|
844
|
+
embedUrl: file.url
|
|
1006
845
|
}));
|
|
1007
846
|
return {
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
image: folder.image,
|
|
1011
|
-
sections: [{
|
|
847
|
+
venueName: "Library",
|
|
848
|
+
items: [{
|
|
1012
849
|
id: `section-${libraryId}`,
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
850
|
+
itemType: "section",
|
|
851
|
+
label: "Content",
|
|
852
|
+
children: fileItems
|
|
853
|
+
}]
|
|
1017
854
|
};
|
|
1018
855
|
}
|
|
1019
856
|
async checkMediaLicense(mediaId, auth) {
|
|
1020
857
|
if (!auth) return null;
|
|
1021
858
|
try {
|
|
1022
859
|
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
|
-
});
|
|
860
|
+
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
861
|
if (!response.ok) return null;
|
|
1033
862
|
const data = await response.json();
|
|
1034
863
|
const licenseData = Array.isArray(data) ? data : data.data || [];
|
|
1035
864
|
const result = licenseData.find((item) => item.mediaId === mediaId);
|
|
1036
865
|
if (result?.isLicensed) {
|
|
1037
|
-
|
|
1038
|
-
return {
|
|
1039
|
-
mediaId,
|
|
1040
|
-
status: "valid",
|
|
1041
|
-
message: "Media is licensed for playback",
|
|
1042
|
-
expiresAt: result.expiresAt
|
|
1043
|
-
};
|
|
866
|
+
return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
|
|
1044
867
|
}
|
|
1045
|
-
return {
|
|
1046
|
-
mediaId,
|
|
1047
|
-
status: "not_licensed",
|
|
1048
|
-
message: "Media is not licensed"
|
|
1049
|
-
};
|
|
868
|
+
return { mediaId, status: "not_licensed", message: "Media is not licensed" };
|
|
1050
869
|
} catch {
|
|
1051
|
-
return {
|
|
1052
|
-
mediaId,
|
|
1053
|
-
status: "unknown",
|
|
1054
|
-
message: "Unable to verify license status"
|
|
1055
|
-
};
|
|
870
|
+
return { mediaId, status: "unknown", message: "Unable to verify license status" };
|
|
1056
871
|
}
|
|
1057
872
|
}
|
|
1058
873
|
};
|
|
1059
874
|
|
|
1060
|
-
// src/providers/SignPresenterProvider.ts
|
|
1061
|
-
var SignPresenterProvider = class
|
|
875
|
+
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
876
|
+
var SignPresenterProvider = class {
|
|
1062
877
|
constructor() {
|
|
1063
|
-
|
|
878
|
+
this.apiHelper = new ApiHelper();
|
|
1064
879
|
this.id = "signpresenter";
|
|
1065
880
|
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
|
-
};
|
|
881
|
+
this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
|
|
882
|
+
this.config = { id: "signpresenter", name: "SignPresenter", apiBase: "https://api.signpresenter.com", oauthBase: "https://api.signpresenter.com/oauth", clientId: "lessonsscreen-tv", scopes: ["openid", "profile", "content"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { playlists: "/content/playlists", messages: (playlistId) => `/content/playlists/${playlistId}/messages` } };
|
|
883
|
+
this.requiresAuth = true;
|
|
884
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
885
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1084
886
|
}
|
|
1085
|
-
|
|
1086
|
-
return
|
|
1087
|
-
browse: true,
|
|
1088
|
-
presentations: true,
|
|
1089
|
-
playlist: false,
|
|
1090
|
-
instructions: false,
|
|
1091
|
-
expandedInstructions: false,
|
|
1092
|
-
mediaLicensing: false
|
|
1093
|
-
};
|
|
887
|
+
async apiRequest(path, auth) {
|
|
888
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
1094
889
|
}
|
|
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) => ({
|
|
890
|
+
async browse(path, auth) {
|
|
891
|
+
const { segments, depth } = parsePath(path);
|
|
892
|
+
if (depth === 0) {
|
|
893
|
+
return [{
|
|
1103
894
|
type: "folder",
|
|
1104
|
-
id:
|
|
1105
|
-
title:
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
895
|
+
id: "playlists-root",
|
|
896
|
+
title: "Playlists",
|
|
897
|
+
path: "/playlists"
|
|
898
|
+
}];
|
|
899
|
+
}
|
|
900
|
+
const root = segments[0];
|
|
901
|
+
if (root !== "playlists") return [];
|
|
902
|
+
if (depth === 1) {
|
|
903
|
+
return this.getPlaylists(auth);
|
|
904
|
+
}
|
|
905
|
+
if (depth === 2) {
|
|
906
|
+
const playlistId = segments[1];
|
|
907
|
+
return this.getMessages(playlistId, auth);
|
|
1109
908
|
}
|
|
1110
|
-
const level = folder.providerData?.level;
|
|
1111
|
-
if (level === "messages") return this.getMessages(folder, auth);
|
|
1112
909
|
return [];
|
|
1113
910
|
}
|
|
1114
|
-
async
|
|
1115
|
-
const
|
|
1116
|
-
|
|
911
|
+
async getPlaylists(auth) {
|
|
912
|
+
const apiPath = this.config.endpoints.playlists;
|
|
913
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
914
|
+
if (!response) return [];
|
|
915
|
+
const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
|
|
916
|
+
if (!Array.isArray(playlists)) return [];
|
|
917
|
+
return playlists.map((p) => ({
|
|
918
|
+
type: "folder",
|
|
919
|
+
id: p.id,
|
|
920
|
+
title: p.name,
|
|
921
|
+
image: p.image,
|
|
922
|
+
path: `/playlists/${p.id}`
|
|
923
|
+
}));
|
|
924
|
+
}
|
|
925
|
+
async getMessages(playlistId, auth) {
|
|
1117
926
|
const pathFn = this.config.endpoints.messages;
|
|
1118
927
|
const response = await this.apiRequest(pathFn(playlistId), auth);
|
|
1119
928
|
if (!response) return [];
|
|
@@ -1124,93 +933,75 @@ var SignPresenterProvider = class extends ContentProvider {
|
|
|
1124
933
|
if (!msg.url) continue;
|
|
1125
934
|
const url = msg.url;
|
|
1126
935
|
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
|
-
});
|
|
936
|
+
files.push({ type: "file", id: msg.id, title: msg.name, mediaType: detectMediaType(url, msg.mediaType), image: msg.thumbnail || msg.image, url, embedUrl: url, seconds });
|
|
1138
937
|
}
|
|
1139
938
|
return files;
|
|
1140
939
|
}
|
|
1141
|
-
async getPresentations(
|
|
1142
|
-
const
|
|
1143
|
-
if (
|
|
1144
|
-
const
|
|
940
|
+
async getPresentations(path, auth) {
|
|
941
|
+
const { segments, depth } = parsePath(path);
|
|
942
|
+
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
943
|
+
const playlistId = segments[1];
|
|
944
|
+
const files = await this.getMessages(playlistId, auth);
|
|
1145
945
|
if (files.length === 0) return null;
|
|
1146
|
-
const
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
946
|
+
const playlists = await this.getPlaylists(auth);
|
|
947
|
+
const playlist = playlists.find((p) => p.id === playlistId);
|
|
948
|
+
const title = playlist?.title || "Playlist";
|
|
949
|
+
const image = playlist?.image;
|
|
950
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
951
|
+
return { id: playlistId, name: title, image, sections: [{ id: `section-${playlistId}`, name: title, presentations }], allFiles: files };
|
|
952
|
+
}
|
|
953
|
+
async getPlaylist(path, auth, _resolution) {
|
|
954
|
+
const { segments, depth } = parsePath(path);
|
|
955
|
+
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
956
|
+
const playlistId = segments[1];
|
|
957
|
+
const files = await this.getMessages(playlistId, auth);
|
|
958
|
+
return files.length > 0 ? files : null;
|
|
959
|
+
}
|
|
960
|
+
async getInstructions(path, auth) {
|
|
961
|
+
const { segments, depth } = parsePath(path);
|
|
962
|
+
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
963
|
+
const playlistId = segments[1];
|
|
964
|
+
const files = await this.getMessages(playlistId, auth);
|
|
965
|
+
if (files.length === 0) return null;
|
|
966
|
+
const playlists = await this.getPlaylists(auth);
|
|
967
|
+
const playlist = playlists.find((p) => p.id === playlistId);
|
|
968
|
+
const title = playlist?.title || "Playlist";
|
|
969
|
+
const fileItems = files.map((file) => ({
|
|
970
|
+
id: file.id,
|
|
971
|
+
itemType: "file",
|
|
972
|
+
label: file.title,
|
|
973
|
+
seconds: file.seconds,
|
|
974
|
+
embedUrl: file.embedUrl || file.url
|
|
1151
975
|
}));
|
|
1152
976
|
return {
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
image: folder.image,
|
|
1156
|
-
sections: [{
|
|
977
|
+
venueName: title,
|
|
978
|
+
items: [{
|
|
1157
979
|
id: `section-${playlistId}`,
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
980
|
+
itemType: "section",
|
|
981
|
+
label: title,
|
|
982
|
+
children: fileItems
|
|
983
|
+
}]
|
|
1162
984
|
};
|
|
1163
985
|
}
|
|
1164
986
|
};
|
|
1165
987
|
|
|
1166
|
-
// src/providers/LessonsChurchProvider.ts
|
|
1167
|
-
var LessonsChurchProvider = class
|
|
988
|
+
// src/providers/lessonsChurch/LessonsChurchProvider.ts
|
|
989
|
+
var LessonsChurchProvider = class {
|
|
1168
990
|
constructor() {
|
|
1169
|
-
super(...arguments);
|
|
1170
991
|
this.id = "lessonschurch";
|
|
1171
992
|
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
|
-
};
|
|
993
|
+
this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
|
|
994
|
+
this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: "https://api.lessons.church", oauthBase: "", clientId: "", scopes: [], endpoints: { programs: "/programs/public", studies: (programId) => `/studies/public/program/${programId}`, lessons: (studyId) => `/lessons/public/study/${studyId}`, venues: (lessonId) => `/venues/public/lesson/${lessonId}`, playlist: (venueId) => `/venues/playlist/${venueId}`, feed: (venueId) => `/venues/public/feed/${venueId}`, addOns: "/addOns/public", addOnDetail: (id) => `/addOns/public/${id}` } };
|
|
995
|
+
this.requiresAuth = false;
|
|
996
|
+
this.authTypes = ["none"];
|
|
997
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1194
998
|
}
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
}
|
|
1198
|
-
getCapabilities() {
|
|
1199
|
-
return {
|
|
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;
|
|
999
|
+
async getPlaylist(path, _auth, resolution) {
|
|
1000
|
+
const venueId = getSegment(path, 4);
|
|
1210
1001
|
if (!venueId) return null;
|
|
1211
|
-
let
|
|
1212
|
-
if (resolution)
|
|
1213
|
-
const response = await this.apiRequest(
|
|
1002
|
+
let apiPath = `/venues/playlist/${venueId}`;
|
|
1003
|
+
if (resolution) apiPath += `?resolution=${resolution}`;
|
|
1004
|
+
const response = await this.apiRequest(apiPath);
|
|
1214
1005
|
if (!response) return null;
|
|
1215
1006
|
const files = [];
|
|
1216
1007
|
const messages = response.messages || [];
|
|
@@ -1222,15 +1013,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1222
1013
|
if (!f.url) continue;
|
|
1223
1014
|
const url = f.url;
|
|
1224
1015
|
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
|
-
});
|
|
1016
|
+
files.push({ type: "file", id: fileId, title: f.name || msg.name, mediaType: detectMediaType(url, f.fileType), image: response.lessonImage, url, seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo });
|
|
1234
1017
|
}
|
|
1235
1018
|
}
|
|
1236
1019
|
return files;
|
|
@@ -1245,48 +1028,31 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1245
1028
|
return null;
|
|
1246
1029
|
}
|
|
1247
1030
|
}
|
|
1248
|
-
async browse(
|
|
1249
|
-
|
|
1031
|
+
async browse(path, _auth) {
|
|
1032
|
+
const { segments, depth } = parsePath(path);
|
|
1033
|
+
if (depth === 0) {
|
|
1250
1034
|
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
|
-
}
|
|
1035
|
+
{ type: "folder", id: "lessons-root", title: "Lessons", path: "/lessons" },
|
|
1036
|
+
{ type: "folder", id: "addons-root", title: "Add-Ons", path: "/addons" }
|
|
1263
1037
|
];
|
|
1264
1038
|
}
|
|
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
|
-
}
|
|
1039
|
+
const root = segments[0];
|
|
1040
|
+
if (root === "lessons") return this.browseLessons(path, segments);
|
|
1041
|
+
if (root === "addons") return this.browseAddOns(path, segments);
|
|
1042
|
+
return [];
|
|
1043
|
+
}
|
|
1044
|
+
async browseLessons(currentPath, segments) {
|
|
1045
|
+
const depth = segments.length;
|
|
1046
|
+
if (depth === 1) return this.getPrograms();
|
|
1047
|
+
if (depth === 2) return this.getStudies(segments[1], currentPath);
|
|
1048
|
+
if (depth === 3) return this.getLessons(segments[2], currentPath);
|
|
1049
|
+
if (depth === 4) return this.getVenues(segments[3], currentPath);
|
|
1050
|
+
if (depth === 5) return this.getPlaylistFiles(segments[4]);
|
|
1051
|
+
return [];
|
|
1286
1052
|
}
|
|
1287
1053
|
async getPrograms() {
|
|
1288
|
-
const
|
|
1289
|
-
const response = await this.apiRequest(
|
|
1054
|
+
const apiPath = this.config.endpoints.programs;
|
|
1055
|
+
const response = await this.apiRequest(apiPath);
|
|
1290
1056
|
if (!response) return [];
|
|
1291
1057
|
const programs = Array.isArray(response) ? response : [];
|
|
1292
1058
|
return programs.map((p) => ({
|
|
@@ -1294,12 +1060,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1294
1060
|
id: p.id,
|
|
1295
1061
|
title: p.name,
|
|
1296
1062
|
image: p.image,
|
|
1297
|
-
|
|
1063
|
+
path: `/lessons/${p.id}`
|
|
1298
1064
|
}));
|
|
1299
1065
|
}
|
|
1300
|
-
async getStudies(
|
|
1301
|
-
const programId = folder.providerData?.programId;
|
|
1302
|
-
if (!programId) return [];
|
|
1066
|
+
async getStudies(programId, currentPath) {
|
|
1303
1067
|
const pathFn = this.config.endpoints.studies;
|
|
1304
1068
|
const response = await this.apiRequest(pathFn(programId));
|
|
1305
1069
|
if (!response) return [];
|
|
@@ -1309,12 +1073,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1309
1073
|
id: s.id,
|
|
1310
1074
|
title: s.name,
|
|
1311
1075
|
image: s.image,
|
|
1312
|
-
|
|
1076
|
+
path: `${currentPath}/${s.id}`
|
|
1313
1077
|
}));
|
|
1314
1078
|
}
|
|
1315
|
-
async getLessons(
|
|
1316
|
-
const studyId = folder.providerData?.studyId;
|
|
1317
|
-
if (!studyId) return [];
|
|
1079
|
+
async getLessons(studyId, currentPath) {
|
|
1318
1080
|
const pathFn = this.config.endpoints.lessons;
|
|
1319
1081
|
const response = await this.apiRequest(pathFn(studyId));
|
|
1320
1082
|
if (!response) return [];
|
|
@@ -1324,31 +1086,38 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1324
1086
|
id: l.id,
|
|
1325
1087
|
title: l.name || l.title,
|
|
1326
1088
|
image: l.image,
|
|
1327
|
-
|
|
1089
|
+
path: `${currentPath}/${l.id}`
|
|
1328
1090
|
}));
|
|
1329
1091
|
}
|
|
1330
|
-
async getVenues(
|
|
1331
|
-
const lessonId = folder.providerData?.lessonId;
|
|
1332
|
-
if (!lessonId) return [];
|
|
1092
|
+
async getVenues(lessonId, currentPath) {
|
|
1333
1093
|
const pathFn = this.config.endpoints.venues;
|
|
1334
1094
|
const response = await this.apiRequest(pathFn(lessonId));
|
|
1335
1095
|
if (!response) return [];
|
|
1096
|
+
const lessonResponse = await this.apiRequest(`/lessons/public/${lessonId}`);
|
|
1097
|
+
const lessonImage = lessonResponse?.image;
|
|
1336
1098
|
const venues = Array.isArray(response) ? response : [];
|
|
1337
1099
|
return venues.map((v) => ({
|
|
1338
1100
|
type: "folder",
|
|
1339
1101
|
id: v.id,
|
|
1340
1102
|
title: v.name,
|
|
1341
|
-
image:
|
|
1342
|
-
|
|
1103
|
+
image: lessonImage,
|
|
1104
|
+
isLeaf: true,
|
|
1105
|
+
path: `${currentPath}/${v.id}`
|
|
1343
1106
|
}));
|
|
1344
1107
|
}
|
|
1345
|
-
async getPlaylistFiles(
|
|
1346
|
-
const files = await this.getPlaylist(
|
|
1108
|
+
async getPlaylistFiles(venueId) {
|
|
1109
|
+
const files = await this.getPlaylist(`/lessons/_/_/_/${venueId}`, null);
|
|
1347
1110
|
return files || [];
|
|
1348
1111
|
}
|
|
1112
|
+
async browseAddOns(_currentPath, segments) {
|
|
1113
|
+
const depth = segments.length;
|
|
1114
|
+
if (depth === 1) return this.getAddOnCategories();
|
|
1115
|
+
if (depth === 2) return this.getAddOnsByCategory(segments[1]);
|
|
1116
|
+
return [];
|
|
1117
|
+
}
|
|
1349
1118
|
async getAddOnCategories() {
|
|
1350
|
-
const
|
|
1351
|
-
const response = await this.apiRequest(
|
|
1119
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1120
|
+
const response = await this.apiRequest(apiPath);
|
|
1352
1121
|
if (!response) return [];
|
|
1353
1122
|
const addOns = Array.isArray(response) ? response : [];
|
|
1354
1123
|
const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
|
|
@@ -1356,17 +1125,16 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1356
1125
|
type: "folder",
|
|
1357
1126
|
id: `category-${category}`,
|
|
1358
1127
|
title: category,
|
|
1359
|
-
|
|
1360
|
-
level: "addOns",
|
|
1361
|
-
category,
|
|
1362
|
-
allAddOns: addOns
|
|
1363
|
-
}
|
|
1128
|
+
path: `/addons/${encodeURIComponent(category)}`
|
|
1364
1129
|
}));
|
|
1365
1130
|
}
|
|
1366
|
-
async getAddOnsByCategory(
|
|
1367
|
-
const
|
|
1368
|
-
const
|
|
1369
|
-
const
|
|
1131
|
+
async getAddOnsByCategory(category) {
|
|
1132
|
+
const decodedCategory = decodeURIComponent(category);
|
|
1133
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1134
|
+
const response = await this.apiRequest(apiPath);
|
|
1135
|
+
if (!response) return [];
|
|
1136
|
+
const allAddOns = Array.isArray(response) ? response : [];
|
|
1137
|
+
const filtered = allAddOns.filter((a) => a.category === decodedCategory);
|
|
1370
1138
|
const files = [];
|
|
1371
1139
|
for (const addOn of filtered) {
|
|
1372
1140
|
const file = await this.convertAddOnToFile(addOn);
|
|
@@ -1376,8 +1144,8 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1376
1144
|
}
|
|
1377
1145
|
async convertAddOnToFile(addOn) {
|
|
1378
1146
|
const pathFn = this.config.endpoints.addOnDetail;
|
|
1379
|
-
const
|
|
1380
|
-
const detail = await this.apiRequest(
|
|
1147
|
+
const apiPath = pathFn(addOn.id);
|
|
1148
|
+
const detail = await this.apiRequest(apiPath);
|
|
1381
1149
|
if (!detail) return null;
|
|
1382
1150
|
let url = "";
|
|
1383
1151
|
let mediaType = "video";
|
|
@@ -1394,51 +1162,18 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1394
1162
|
} else {
|
|
1395
1163
|
return null;
|
|
1396
1164
|
}
|
|
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
|
-
};
|
|
1165
|
+
return { type: "file", id: addOn.id, title: addOn.name, mediaType, image: addOn.image, url, embedUrl: `https://lessons.church/embed/addon/${addOn.id}`, seconds, loopVideo: video?.loopVideo || false };
|
|
1410
1166
|
}
|
|
1411
|
-
async getPresentations(
|
|
1412
|
-
const venueId =
|
|
1167
|
+
async getPresentations(path, _auth) {
|
|
1168
|
+
const venueId = getSegment(path, 4);
|
|
1413
1169
|
if (!venueId) return null;
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
const venueData = await this.apiRequest(path);
|
|
1170
|
+
const apiPath = `/venues/public/feed/${venueId}`;
|
|
1171
|
+
const venueData = await this.apiRequest(apiPath);
|
|
1417
1172
|
if (!venueData) return null;
|
|
1418
1173
|
return this.convertVenueToPlan(venueData);
|
|
1419
1174
|
}
|
|
1420
|
-
async getInstructions(
|
|
1421
|
-
const venueId =
|
|
1422
|
-
if (!venueId) return null;
|
|
1423
|
-
const response = await this.apiRequest(`/venues/public/planItems/${venueId}`);
|
|
1424
|
-
if (!response) return null;
|
|
1425
|
-
const processItem = (item) => ({
|
|
1426
|
-
id: item.id,
|
|
1427
|
-
itemType: item.itemType,
|
|
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)
|
|
1438
|
-
};
|
|
1439
|
-
}
|
|
1440
|
-
async getExpandedInstructions(folder, _auth) {
|
|
1441
|
-
const venueId = folder.providerData?.venueId;
|
|
1175
|
+
async getInstructions(path, _auth) {
|
|
1176
|
+
const venueId = getSegment(path, 4);
|
|
1442
1177
|
if (!venueId) return null;
|
|
1443
1178
|
const [planItemsResponse, actionsResponse] = await Promise.all([
|
|
1444
1179
|
this.apiRequest(`/venues/public/planItems/${venueId}`),
|
|
@@ -1449,66 +1184,48 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1449
1184
|
if (actionsResponse?.sections) {
|
|
1450
1185
|
for (const section of actionsResponse.sections) {
|
|
1451
1186
|
if (section.id && section.actions) {
|
|
1452
|
-
sectionActionsMap.set(section.id, section.actions.map((action) =>
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
relatedId: action.id,
|
|
1456
|
-
|
|
1457
|
-
description: action.actionType,
|
|
1458
|
-
seconds: action.seconds,
|
|
1459
|
-
embedUrl: this.getEmbedUrl("lessonAction", action.id)
|
|
1460
|
-
})));
|
|
1187
|
+
sectionActionsMap.set(section.id, section.actions.map((action) => {
|
|
1188
|
+
const embedUrl = this.getEmbedUrl("action", action.id);
|
|
1189
|
+
const seconds = action.seconds ?? estimateImageDuration();
|
|
1190
|
+
return { id: action.id, itemType: "action", relatedId: action.id, label: action.name, description: action.actionType, seconds, children: [{ id: action.id + "-file", itemType: "file", label: action.name, seconds, embedUrl }] };
|
|
1191
|
+
}));
|
|
1461
1192
|
}
|
|
1462
1193
|
}
|
|
1463
1194
|
}
|
|
1464
1195
|
const processItem = (item) => {
|
|
1465
1196
|
const relatedId = item.relatedId;
|
|
1466
|
-
const itemType = item.itemType;
|
|
1197
|
+
const itemType = this.normalizeItemType(item.itemType);
|
|
1467
1198
|
const children = item.children;
|
|
1468
1199
|
let processedChildren;
|
|
1469
1200
|
if (children) {
|
|
1470
1201
|
processedChildren = children.map((child) => {
|
|
1471
1202
|
const childRelatedId = child.relatedId;
|
|
1203
|
+
const childItemType = this.normalizeItemType(child.itemType);
|
|
1472
1204
|
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
|
-
};
|
|
1205
|
+
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
1206
|
}
|
|
1484
1207
|
return processItem(child);
|
|
1485
1208
|
});
|
|
1486
1209
|
}
|
|
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)
|
|
1210
|
+
return { id: item.id, itemType, relatedId, label: item.label, description: item.description, seconds: item.seconds, children: processedChildren, embedUrl: this.getEmbedUrl(itemType, relatedId) };
|
|
1501
1211
|
};
|
|
1212
|
+
return { venueName: planItemsResponse.venueName, items: (planItemsResponse.items || []).map(processItem) };
|
|
1213
|
+
}
|
|
1214
|
+
normalizeItemType(type) {
|
|
1215
|
+
if (type === "lessonSection") return "section";
|
|
1216
|
+
if (type === "lessonAction") return "action";
|
|
1217
|
+
if (type === "lessonAddOn") return "addon";
|
|
1218
|
+
return type;
|
|
1502
1219
|
}
|
|
1503
1220
|
getEmbedUrl(itemType, relatedId) {
|
|
1504
1221
|
if (!relatedId) return void 0;
|
|
1505
1222
|
const baseUrl = "https://lessons.church";
|
|
1506
1223
|
switch (itemType) {
|
|
1507
|
-
case "
|
|
1224
|
+
case "action":
|
|
1508
1225
|
return `${baseUrl}/embed/action/${relatedId}`;
|
|
1509
|
-
case "
|
|
1226
|
+
case "addon":
|
|
1510
1227
|
return `${baseUrl}/embed/addon/${relatedId}`;
|
|
1511
|
-
case "
|
|
1228
|
+
case "section":
|
|
1512
1229
|
return `${baseUrl}/embed/section/${relatedId}`;
|
|
1513
1230
|
default:
|
|
1514
1231
|
return void 0;
|
|
@@ -1526,16 +1243,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1526
1243
|
for (const file of action.files || []) {
|
|
1527
1244
|
if (!file.url) continue;
|
|
1528
1245
|
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
|
-
};
|
|
1246
|
+
const contentFile = { type: "file", id: file.id || "", title: file.name || "", mediaType: detectMediaType(file.url, file.fileType), image: venue.lessonImage, url: file.url, embedUrl, seconds: file.seconds, streamUrl: file.streamUrl };
|
|
1539
1247
|
files.push(contentFile);
|
|
1540
1248
|
allFiles.push(contentFile);
|
|
1541
1249
|
}
|
|
@@ -1547,96 +1255,73 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1547
1255
|
sections.push({ id: section.id || "", name: section.name || "Untitled Section", presentations });
|
|
1548
1256
|
}
|
|
1549
1257
|
}
|
|
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
|
-
};
|
|
1258
|
+
return { id: venue.id || "", name: venue.lessonName || venue.name || "Plan", description: venue.lessonDescription, image: venue.lessonImage, sections, allFiles };
|
|
1558
1259
|
}
|
|
1559
1260
|
};
|
|
1560
1261
|
|
|
1561
|
-
// src/providers/
|
|
1562
|
-
function
|
|
1262
|
+
// src/providers/b1Church/auth.ts
|
|
1263
|
+
async function generateCodeChallenge(verifier) {
|
|
1264
|
+
const encoder = new TextEncoder();
|
|
1265
|
+
const data = encoder.encode(verifier);
|
|
1266
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1267
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
1268
|
+
let binary = "";
|
|
1269
|
+
for (let i = 0; i < hashArray.length; i++) {
|
|
1270
|
+
binary += String.fromCharCode(hashArray[i]);
|
|
1271
|
+
}
|
|
1272
|
+
const base64 = btoa(binary);
|
|
1273
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1274
|
+
}
|
|
1275
|
+
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
1276
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1563
1277
|
const oauthParams = new URLSearchParams({
|
|
1278
|
+
response_type: "code",
|
|
1564
1279
|
client_id: config.clientId,
|
|
1565
1280
|
redirect_uri: redirectUri,
|
|
1566
|
-
|
|
1567
|
-
|
|
1281
|
+
scope: config.scopes.join(" "),
|
|
1282
|
+
code_challenge: codeChallenge,
|
|
1283
|
+
code_challenge_method: "S256",
|
|
1284
|
+
state: state || ""
|
|
1568
1285
|
});
|
|
1569
|
-
|
|
1570
|
-
|
|
1286
|
+
const url = `${appBase}/oauth?${oauthParams.toString()}`;
|
|
1287
|
+
return { url, challengeMethod: "S256" };
|
|
1288
|
+
}
|
|
1289
|
+
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
1290
|
+
try {
|
|
1291
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, code_verifier: codeVerifier, redirect_uri: redirectUri };
|
|
1292
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
1293
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1294
|
+
if (!response.ok) {
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
const data = await response.json();
|
|
1298
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
1299
|
+
} catch {
|
|
1300
|
+
return null;
|
|
1571
1301
|
}
|
|
1572
|
-
const returnUrl = `/oauth?${oauthParams.toString()}`;
|
|
1573
|
-
const url = `${appBase}/login?returnUrl=${encodeURIComponent(returnUrl)}`;
|
|
1574
|
-
return { url, challengeMethod: "none" };
|
|
1575
1302
|
}
|
|
1576
1303
|
async function exchangeCodeForTokensWithSecret(config, code, redirectUri, clientSecret) {
|
|
1577
1304
|
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
|
-
};
|
|
1305
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, client_secret: clientSecret, redirect_uri: redirectUri };
|
|
1585
1306
|
const tokenUrl = `${config.oauthBase}/token`;
|
|
1586
|
-
|
|
1587
|
-
console.log(` - client_id: ${config.clientId}`);
|
|
1588
|
-
console.log(` - redirect_uri: ${redirectUri}`);
|
|
1589
|
-
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
|
-
});
|
|
1595
|
-
console.log(`B1Church token response status: ${response.status}`);
|
|
1307
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1596
1308
|
if (!response.ok) {
|
|
1597
|
-
const errorText = await response.text();
|
|
1598
|
-
console.error(`B1Church token exchange failed: ${response.status} - ${errorText}`);
|
|
1599
1309
|
return null;
|
|
1600
1310
|
}
|
|
1601
1311
|
const data = await response.json();
|
|
1602
|
-
|
|
1603
|
-
|
|
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
|
-
};
|
|
1611
|
-
} catch (error) {
|
|
1612
|
-
console.error("B1Church token exchange error:", error);
|
|
1312
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
1313
|
+
} catch {
|
|
1613
1314
|
return null;
|
|
1614
1315
|
}
|
|
1615
1316
|
}
|
|
1616
1317
|
async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
1617
1318
|
if (!auth.refresh_token) return null;
|
|
1618
1319
|
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
|
-
});
|
|
1320
|
+
const params = { grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: config.clientId, client_secret: clientSecret };
|
|
1321
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1630
1322
|
if (!response.ok) return null;
|
|
1631
1323
|
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
|
-
};
|
|
1324
|
+
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
1325
|
} catch {
|
|
1641
1326
|
return null;
|
|
1642
1327
|
}
|
|
@@ -1644,46 +1329,21 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
|
1644
1329
|
async function initiateDeviceFlow(config) {
|
|
1645
1330
|
if (!config.supportsDeviceFlow || !config.deviceAuthEndpoint) return null;
|
|
1646
1331
|
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
|
-
});
|
|
1332
|
+
const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: config.clientId, scope: config.scopes.join(" ") }) });
|
|
1655
1333
|
if (!response.ok) {
|
|
1656
|
-
const errorText = await response.text();
|
|
1657
|
-
console.error(`B1Church device authorize failed: ${response.status} - ${errorText}`);
|
|
1658
1334
|
return null;
|
|
1659
1335
|
}
|
|
1660
1336
|
return await response.json();
|
|
1661
|
-
} catch
|
|
1662
|
-
console.error("B1Church device flow initiation error:", error);
|
|
1337
|
+
} catch {
|
|
1663
1338
|
return null;
|
|
1664
1339
|
}
|
|
1665
1340
|
}
|
|
1666
1341
|
async function pollDeviceFlowToken(config, deviceCode) {
|
|
1667
|
-
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
|
-
});
|
|
1342
|
+
try {
|
|
1343
|
+
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
1344
|
if (response.ok) {
|
|
1678
1345
|
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
|
-
};
|
|
1346
|
+
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
1347
|
}
|
|
1688
1348
|
const errorData = await response.json();
|
|
1689
1349
|
switch (errorData.error) {
|
|
@@ -1703,7 +1363,7 @@ async function pollDeviceFlowToken(config, deviceCode) {
|
|
|
1703
1363
|
}
|
|
1704
1364
|
}
|
|
1705
1365
|
|
|
1706
|
-
// src/providers/
|
|
1366
|
+
// src/providers/b1Church/api.ts
|
|
1707
1367
|
var API_BASE = "https://api.churchapps.org";
|
|
1708
1368
|
var LESSONS_API_BASE = "https://api.lessons.church";
|
|
1709
1369
|
var CONTENT_API_BASE = "https://contentapi.churchapps.org";
|
|
@@ -1735,10 +1395,7 @@ async function fetchPlans(planTypeId, auth) {
|
|
|
1735
1395
|
async function fetchVenueFeed(venueId) {
|
|
1736
1396
|
try {
|
|
1737
1397
|
const url = `${LESSONS_API_BASE}/venues/public/feed/${venueId}`;
|
|
1738
|
-
const response = await fetch(url, {
|
|
1739
|
-
method: "GET",
|
|
1740
|
-
headers: { Accept: "application/json" }
|
|
1741
|
-
});
|
|
1398
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1742
1399
|
if (!response.ok) return null;
|
|
1743
1400
|
return await response.json();
|
|
1744
1401
|
} catch {
|
|
@@ -1748,9 +1405,29 @@ async function fetchVenueFeed(venueId) {
|
|
|
1748
1405
|
async function fetchArrangementKey(churchId, arrangementId) {
|
|
1749
1406
|
try {
|
|
1750
1407
|
const url = `${CONTENT_API_BASE}/arrangementKeys/presenter/${churchId}/${arrangementId}`;
|
|
1408
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1409
|
+
if (!response.ok) return null;
|
|
1410
|
+
return await response.json();
|
|
1411
|
+
} catch {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
async function fetchFromProviderProxy(method, ministryId, providerId, path, authData, resolution) {
|
|
1416
|
+
try {
|
|
1417
|
+
const url = `${API_BASE}/doing/providerProxy/${method}`;
|
|
1418
|
+
const headers = {
|
|
1419
|
+
"Content-Type": "application/json",
|
|
1420
|
+
Accept: "application/json"
|
|
1421
|
+
};
|
|
1422
|
+
if (authData) {
|
|
1423
|
+
headers["Authorization"] = `Bearer ${authData.access_token}`;
|
|
1424
|
+
}
|
|
1425
|
+
const body = { ministryId, providerId, path };
|
|
1426
|
+
if (resolution !== void 0) body.resolution = resolution;
|
|
1751
1427
|
const response = await fetch(url, {
|
|
1752
|
-
method: "
|
|
1753
|
-
headers
|
|
1428
|
+
method: "POST",
|
|
1429
|
+
headers,
|
|
1430
|
+
body: JSON.stringify(body)
|
|
1754
1431
|
});
|
|
1755
1432
|
if (!response.ok) return null;
|
|
1756
1433
|
return await response.json();
|
|
@@ -1759,107 +1436,39 @@ async function fetchArrangementKey(churchId, arrangementId) {
|
|
|
1759
1436
|
}
|
|
1760
1437
|
}
|
|
1761
1438
|
|
|
1762
|
-
// src/providers/
|
|
1439
|
+
// src/providers/b1Church/converters.ts
|
|
1763
1440
|
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
|
-
};
|
|
1441
|
+
return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl };
|
|
1775
1442
|
}
|
|
1776
|
-
function planTypeToFolder(planType
|
|
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
|
-
};
|
|
1443
|
+
function planTypeToFolder(planType) {
|
|
1444
|
+
return { type: "folder", id: planType.id, title: planType.name, path: "" };
|
|
1788
1445
|
}
|
|
1789
1446
|
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
|
-
};
|
|
1447
|
+
return { type: "folder", id: plan.id, title: plan.name, path: "", isLeaf: true };
|
|
1806
1448
|
}
|
|
1807
1449
|
async function planItemToPresentation(item, venueFeed) {
|
|
1808
1450
|
const itemType = item.itemType;
|
|
1809
1451
|
if (itemType === "arrangementKey" && item.churchId && item.relatedId) {
|
|
1810
1452
|
const songData = await fetchArrangementKey(item.churchId, item.relatedId);
|
|
1811
|
-
if (songData)
|
|
1812
|
-
return arrangementToPresentation(item, songData);
|
|
1813
|
-
}
|
|
1453
|
+
if (songData) return arrangementToPresentation(item, songData);
|
|
1814
1454
|
}
|
|
1815
|
-
if ((itemType === "lessonSection" || itemType === "lessonAction" || itemType === "lessonAddOn") && venueFeed) {
|
|
1455
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1816
1456
|
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
|
-
}
|
|
1457
|
+
if (files.length > 0) return { id: item.id, name: item.label || "Lesson Content", actionType: itemType === "lessonAddOn" || itemType === "addon" ? "add-on" : "play", files };
|
|
1825
1458
|
}
|
|
1826
1459
|
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
|
-
};
|
|
1460
|
+
return { id: item.id, name: item.label || "", actionType: "other", files: [], providerData: { itemType, description: item.description, seconds: item.seconds } };
|
|
1838
1461
|
}
|
|
1839
1462
|
return null;
|
|
1840
1463
|
}
|
|
1841
1464
|
function arrangementToPresentation(item, songData) {
|
|
1842
1465
|
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
|
-
};
|
|
1466
|
+
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
1467
|
}
|
|
1859
1468
|
function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
1860
1469
|
const files = [];
|
|
1861
1470
|
if (!relatedId) return files;
|
|
1862
|
-
if (itemType === "lessonSection") {
|
|
1471
|
+
if (itemType === "lessonSection" || itemType === "section") {
|
|
1863
1472
|
for (const section of venueFeed.sections || []) {
|
|
1864
1473
|
if (section.id === relatedId) {
|
|
1865
1474
|
for (const action of section.actions || []) {
|
|
@@ -1871,7 +1480,7 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1871
1480
|
break;
|
|
1872
1481
|
}
|
|
1873
1482
|
}
|
|
1874
|
-
} else if (itemType === "lessonAction") {
|
|
1483
|
+
} else if (itemType === "lessonAction" || itemType === "action") {
|
|
1875
1484
|
for (const section of venueFeed.sections || []) {
|
|
1876
1485
|
for (const action of section.actions || []) {
|
|
1877
1486
|
if (action.id === relatedId) {
|
|
@@ -1884,68 +1493,51 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1884
1493
|
return files;
|
|
1885
1494
|
}
|
|
1886
1495
|
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
|
-
}));
|
|
1496
|
+
return feedFiles.filter((f) => f.url).map((f) => ({ type: "file", id: f.id || "", title: f.name || "", mediaType: detectMediaType(f.url || "", f.fileType), image: thumbnailImage, url: f.url || "", seconds: f.seconds, streamUrl: f.streamUrl }));
|
|
1896
1497
|
}
|
|
1897
1498
|
function planItemToInstruction(item) {
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1499
|
+
let itemType = item.itemType;
|
|
1500
|
+
switch (item.itemType) {
|
|
1501
|
+
case "lessonSection":
|
|
1502
|
+
itemType = "section";
|
|
1503
|
+
break;
|
|
1504
|
+
case "lessonAction":
|
|
1505
|
+
itemType = "action";
|
|
1506
|
+
break;
|
|
1507
|
+
case "lessonAddOn":
|
|
1508
|
+
itemType = "addon";
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
return { id: item.id, itemType, relatedId: item.relatedId, label: item.label, description: item.description, seconds: item.seconds, children: item.children?.map(planItemToInstruction) };
|
|
1907
1512
|
}
|
|
1908
1513
|
|
|
1909
|
-
// src/providers/
|
|
1910
|
-
|
|
1514
|
+
// src/providers/b1Church/B1ChurchProvider.ts
|
|
1515
|
+
function isExternalProviderItem(item) {
|
|
1516
|
+
if (!item.providerId || item.providerId === "b1church") return false;
|
|
1517
|
+
if (item.providerPath) return true;
|
|
1518
|
+
const itemType = item.itemType || "";
|
|
1519
|
+
return itemType.startsWith("provider");
|
|
1520
|
+
}
|
|
1521
|
+
var B1ChurchProvider = class {
|
|
1911
1522
|
constructor() {
|
|
1912
|
-
|
|
1523
|
+
this.apiHelper = new ApiHelper();
|
|
1913
1524
|
this.id = "b1church";
|
|
1914
1525
|
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
|
-
};
|
|
1526
|
+
this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
|
|
1527
|
+
this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE}/doing`, oauthBase: `${API_BASE}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { planItems: (churchId, planId) => `/planFeed/presenter/${churchId}/${planId}` } };
|
|
1932
1528
|
this.appBase = "https://admin.b1.church";
|
|
1529
|
+
this.requiresAuth = true;
|
|
1530
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
1531
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1933
1532
|
}
|
|
1934
|
-
|
|
1935
|
-
return
|
|
1533
|
+
async apiRequest(path, authData) {
|
|
1534
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, authData);
|
|
1936
1535
|
}
|
|
1937
|
-
|
|
1938
|
-
return
|
|
1939
|
-
browse: true,
|
|
1940
|
-
presentations: true,
|
|
1941
|
-
playlist: true,
|
|
1942
|
-
instructions: true,
|
|
1943
|
-
expandedInstructions: true,
|
|
1944
|
-
mediaLicensing: false
|
|
1945
|
-
};
|
|
1536
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1537
|
+
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1946
1538
|
}
|
|
1947
|
-
async
|
|
1948
|
-
return
|
|
1539
|
+
async exchangeCodeForTokensWithPKCE(code, redirectUri, codeVerifier) {
|
|
1540
|
+
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1949
1541
|
}
|
|
1950
1542
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
1951
1543
|
return exchangeCodeForTokensWithSecret(this.config, code, redirectUri, clientSecret);
|
|
@@ -1959,35 +1551,75 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1959
1551
|
async pollDeviceFlowToken(deviceCode) {
|
|
1960
1552
|
return pollDeviceFlowToken(this.config, deviceCode);
|
|
1961
1553
|
}
|
|
1962
|
-
async browse(
|
|
1963
|
-
|
|
1554
|
+
async browse(path, authData) {
|
|
1555
|
+
const { segments, depth } = parsePath(path);
|
|
1556
|
+
if (depth === 0) {
|
|
1557
|
+
return [{
|
|
1558
|
+
type: "folder",
|
|
1559
|
+
id: "ministries-root",
|
|
1560
|
+
title: "Ministries",
|
|
1561
|
+
path: "/ministries"
|
|
1562
|
+
}];
|
|
1563
|
+
}
|
|
1564
|
+
const root = segments[0];
|
|
1565
|
+
if (root !== "ministries") return [];
|
|
1566
|
+
if (depth === 1) {
|
|
1964
1567
|
const ministries = await fetchMinistries(authData);
|
|
1965
|
-
return ministries.map(
|
|
1568
|
+
return ministries.map((m) => {
|
|
1569
|
+
const folder = ministryToFolder(m);
|
|
1570
|
+
return { ...folder, path: `/ministries/${m.id}` };
|
|
1571
|
+
});
|
|
1966
1572
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
const ministryId = folder.providerData?.ministryId;
|
|
1970
|
-
if (!ministryId) return [];
|
|
1573
|
+
if (depth === 2) {
|
|
1574
|
+
const ministryId = segments[1];
|
|
1971
1575
|
const planTypes = await fetchPlanTypes(ministryId, authData);
|
|
1972
|
-
return planTypes.map((pt) =>
|
|
1576
|
+
return planTypes.map((pt) => {
|
|
1577
|
+
const folder = planTypeToFolder(pt);
|
|
1578
|
+
return { ...folder, path: `/ministries/${ministryId}/${pt.id}` };
|
|
1579
|
+
});
|
|
1973
1580
|
}
|
|
1974
|
-
if (
|
|
1975
|
-
const
|
|
1976
|
-
|
|
1581
|
+
if (depth === 3) {
|
|
1582
|
+
const ministryId = segments[1];
|
|
1583
|
+
const planTypeId = segments[2];
|
|
1977
1584
|
const plans = await fetchPlans(planTypeId, authData);
|
|
1978
|
-
return plans.map(
|
|
1585
|
+
return plans.map((p) => {
|
|
1586
|
+
const folder = planToFolder(p);
|
|
1587
|
+
return {
|
|
1588
|
+
...folder,
|
|
1589
|
+
isLeaf: true,
|
|
1590
|
+
path: `/ministries/${ministryId}/${planTypeId}/${p.id}`
|
|
1591
|
+
};
|
|
1592
|
+
});
|
|
1979
1593
|
}
|
|
1980
1594
|
return [];
|
|
1981
1595
|
}
|
|
1982
|
-
async getPresentations(
|
|
1983
|
-
const
|
|
1984
|
-
if (
|
|
1985
|
-
const
|
|
1986
|
-
const
|
|
1987
|
-
const
|
|
1988
|
-
|
|
1596
|
+
async getPresentations(path, authData) {
|
|
1597
|
+
const { segments, depth } = parsePath(path);
|
|
1598
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1599
|
+
const ministryId = segments[1];
|
|
1600
|
+
const planId = segments[3];
|
|
1601
|
+
const planTypeId = segments[2];
|
|
1602
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1603
|
+
const planFolder = plans.find((p) => p.id === planId);
|
|
1604
|
+
if (!planFolder) return null;
|
|
1605
|
+
const churchId = planFolder.churchId;
|
|
1606
|
+
const venueId = planFolder.contentId;
|
|
1607
|
+
const planTitle = planFolder.name || "Plan";
|
|
1608
|
+
if (!churchId) return null;
|
|
1989
1609
|
const pathFn = this.config.endpoints.planItems;
|
|
1990
1610
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1611
|
+
if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
|
|
1612
|
+
const externalPlan = await fetchFromProviderProxy(
|
|
1613
|
+
"getPresentations",
|
|
1614
|
+
ministryId,
|
|
1615
|
+
planFolder.providerId,
|
|
1616
|
+
planFolder.providerPlanId,
|
|
1617
|
+
authData
|
|
1618
|
+
);
|
|
1619
|
+
if (externalPlan) {
|
|
1620
|
+
return { id: planId, name: planTitle, sections: externalPlan.sections, allFiles: externalPlan.allFiles };
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1991
1623
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
1992
1624
|
const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
|
|
1993
1625
|
const sections = [];
|
|
@@ -1995,54 +1627,195 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1995
1627
|
for (const sectionItem of planItems) {
|
|
1996
1628
|
const presentations = [];
|
|
1997
1629
|
for (const child of sectionItem.children || []) {
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
1630
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1631
|
+
const externalPlan = await fetchFromProviderProxy(
|
|
1632
|
+
"getPresentations",
|
|
1633
|
+
ministryId,
|
|
1634
|
+
child.providerId,
|
|
1635
|
+
child.providerPath,
|
|
1636
|
+
authData
|
|
1637
|
+
);
|
|
1638
|
+
if (externalPlan) {
|
|
1639
|
+
if (child.providerContentPath) {
|
|
1640
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1641
|
+
"getInstructions",
|
|
1642
|
+
ministryId,
|
|
1643
|
+
child.providerId,
|
|
1644
|
+
child.providerPath,
|
|
1645
|
+
authData
|
|
1646
|
+
);
|
|
1647
|
+
const matchingPresentation = this.findPresentationByPath(externalPlan, externalInstructions, child.providerContentPath);
|
|
1648
|
+
if (matchingPresentation) {
|
|
1649
|
+
presentations.push(matchingPresentation);
|
|
1650
|
+
allFiles.push(...matchingPresentation.files);
|
|
1651
|
+
}
|
|
1652
|
+
} else {
|
|
1653
|
+
for (const section of externalPlan.sections) {
|
|
1654
|
+
presentations.push(...section.presentations);
|
|
1655
|
+
}
|
|
1656
|
+
allFiles.push(...externalPlan.allFiles);
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
} else {
|
|
1660
|
+
const presentation = await planItemToPresentation(child, venueFeed);
|
|
1661
|
+
if (presentation) {
|
|
1662
|
+
presentations.push(presentation);
|
|
1663
|
+
allFiles.push(...presentation.files);
|
|
1664
|
+
}
|
|
2002
1665
|
}
|
|
2003
1666
|
}
|
|
2004
1667
|
if (presentations.length > 0 || sectionItem.label) {
|
|
2005
|
-
sections.push({
|
|
2006
|
-
id: sectionItem.id,
|
|
2007
|
-
name: sectionItem.label || "Section",
|
|
2008
|
-
presentations
|
|
2009
|
-
});
|
|
1668
|
+
sections.push({ id: sectionItem.id, name: sectionItem.label || "Section", presentations });
|
|
2010
1669
|
}
|
|
2011
1670
|
}
|
|
2012
|
-
return { id: planId, name:
|
|
2013
|
-
}
|
|
2014
|
-
async getInstructions(
|
|
2015
|
-
const
|
|
2016
|
-
if (
|
|
2017
|
-
const
|
|
2018
|
-
const
|
|
2019
|
-
|
|
1671
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
1672
|
+
}
|
|
1673
|
+
async getInstructions(path, authData) {
|
|
1674
|
+
const { segments, depth } = parsePath(path);
|
|
1675
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1676
|
+
const ministryId = segments[1];
|
|
1677
|
+
const planId = segments[3];
|
|
1678
|
+
const planTypeId = segments[2];
|
|
1679
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1680
|
+
const planFolder = plans.find((p) => p.id === planId);
|
|
1681
|
+
if (!planFolder) return null;
|
|
1682
|
+
const churchId = planFolder.churchId;
|
|
1683
|
+
const planTitle = planFolder.name || "Plan";
|
|
1684
|
+
if (!churchId) return null;
|
|
2020
1685
|
const pathFn = this.config.endpoints.planItems;
|
|
2021
1686
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1687
|
+
if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
|
|
1688
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1689
|
+
"getInstructions",
|
|
1690
|
+
ministryId,
|
|
1691
|
+
planFolder.providerId,
|
|
1692
|
+
planFolder.providerPlanId,
|
|
1693
|
+
authData
|
|
1694
|
+
);
|
|
1695
|
+
if (externalInstructions) {
|
|
1696
|
+
return { venueName: planTitle, items: externalInstructions.items };
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
2022
1699
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
1700
|
+
const processedItems = await this.processInstructionItems(planItems, ministryId, authData);
|
|
1701
|
+
return { venueName: planTitle, items: processedItems };
|
|
1702
|
+
}
|
|
1703
|
+
async processInstructionItems(items, ministryId, authData) {
|
|
1704
|
+
const result = [];
|
|
1705
|
+
for (const item of items) {
|
|
1706
|
+
const instructionItem = planItemToInstruction(item);
|
|
1707
|
+
if (isExternalProviderItem(item) && item.providerId && item.providerPath) {
|
|
1708
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1709
|
+
"getInstructions",
|
|
1710
|
+
ministryId,
|
|
1711
|
+
item.providerId,
|
|
1712
|
+
item.providerPath,
|
|
1713
|
+
authData
|
|
1714
|
+
);
|
|
1715
|
+
if (externalInstructions) {
|
|
1716
|
+
if (item.providerContentPath) {
|
|
1717
|
+
const matchingItem = this.findItemByPath(externalInstructions, item.providerContentPath);
|
|
1718
|
+
if (matchingItem?.children) {
|
|
1719
|
+
instructionItem.children = matchingItem.children;
|
|
1720
|
+
}
|
|
1721
|
+
} else {
|
|
1722
|
+
instructionItem.children = externalInstructions.items;
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1725
|
+
} else if (item.children && item.children.length > 0) {
|
|
1726
|
+
instructionItem.children = await this.processInstructionItems(item.children, ministryId, authData);
|
|
1727
|
+
}
|
|
1728
|
+
result.push(instructionItem);
|
|
1729
|
+
}
|
|
1730
|
+
return result;
|
|
2027
1731
|
}
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
1732
|
+
findItemByPath(instructions, path) {
|
|
1733
|
+
if (!path || !instructions) return null;
|
|
1734
|
+
return navigateToPath(instructions, path);
|
|
1735
|
+
}
|
|
1736
|
+
findPresentationByPath(plan, instructions, path) {
|
|
1737
|
+
if (!path || !instructions) return null;
|
|
1738
|
+
const item = navigateToPath(instructions, path);
|
|
1739
|
+
if (!item?.relatedId && !item?.id) return null;
|
|
1740
|
+
const presentationId = item.relatedId || item.id;
|
|
1741
|
+
for (const section of plan.sections) {
|
|
1742
|
+
for (const presentation of section.presentations) {
|
|
1743
|
+
if (presentation.id === presentationId) return presentation;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
return null;
|
|
1747
|
+
}
|
|
1748
|
+
async getPlaylist(path, authData, resolution) {
|
|
1749
|
+
const { segments, depth } = parsePath(path);
|
|
1750
|
+
if (depth < 4 || segments[0] !== "ministries") return [];
|
|
1751
|
+
const ministryId = segments[1];
|
|
1752
|
+
const planId = segments[3];
|
|
1753
|
+
const planTypeId = segments[2];
|
|
1754
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1755
|
+
const planFolder = plans.find((p) => p.id === planId);
|
|
1756
|
+
if (!planFolder) return [];
|
|
1757
|
+
const churchId = planFolder.churchId;
|
|
1758
|
+
const venueId = planFolder.contentId;
|
|
1759
|
+
if (!churchId) return [];
|
|
2035
1760
|
const pathFn = this.config.endpoints.planItems;
|
|
2036
1761
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1762
|
+
if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
|
|
1763
|
+
const externalFiles = await fetchFromProviderProxy(
|
|
1764
|
+
"getPlaylist",
|
|
1765
|
+
ministryId,
|
|
1766
|
+
planFolder.providerId,
|
|
1767
|
+
planFolder.providerPlanId,
|
|
1768
|
+
authData,
|
|
1769
|
+
resolution
|
|
1770
|
+
);
|
|
1771
|
+
return externalFiles || [];
|
|
1772
|
+
}
|
|
2037
1773
|
if (!planItems || !Array.isArray(planItems)) return [];
|
|
2038
1774
|
const venueFeed = venueId ? await fetchVenueFeed(venueId) : null;
|
|
2039
1775
|
const files = [];
|
|
2040
1776
|
for (const sectionItem of planItems) {
|
|
2041
1777
|
for (const child of sectionItem.children || []) {
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
1778
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1779
|
+
if (child.providerContentPath) {
|
|
1780
|
+
const externalPlan = await fetchFromProviderProxy(
|
|
1781
|
+
"getPresentations",
|
|
1782
|
+
ministryId,
|
|
1783
|
+
child.providerId,
|
|
1784
|
+
child.providerPath,
|
|
1785
|
+
authData
|
|
1786
|
+
);
|
|
1787
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1788
|
+
"getInstructions",
|
|
1789
|
+
ministryId,
|
|
1790
|
+
child.providerId,
|
|
1791
|
+
child.providerPath,
|
|
1792
|
+
authData
|
|
1793
|
+
);
|
|
1794
|
+
if (externalPlan) {
|
|
1795
|
+
const matchingPresentation = this.findPresentationByPath(externalPlan, externalInstructions, child.providerContentPath);
|
|
1796
|
+
if (matchingPresentation) {
|
|
1797
|
+
files.push(...matchingPresentation.files);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
} else {
|
|
1801
|
+
const externalFiles = await fetchFromProviderProxy(
|
|
1802
|
+
"getPlaylist",
|
|
1803
|
+
ministryId,
|
|
1804
|
+
child.providerId,
|
|
1805
|
+
child.providerPath,
|
|
1806
|
+
authData,
|
|
1807
|
+
resolution
|
|
1808
|
+
);
|
|
1809
|
+
if (externalFiles) {
|
|
1810
|
+
files.push(...externalFiles);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
} else {
|
|
1814
|
+
const itemType = child.itemType;
|
|
1815
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1816
|
+
const itemFiles = getFilesFromVenueFeed(venueFeed, itemType, child.relatedId);
|
|
1817
|
+
files.push(...itemFiles);
|
|
1818
|
+
}
|
|
2046
1819
|
}
|
|
2047
1820
|
}
|
|
2048
1821
|
}
|
|
@@ -2050,81 +1823,62 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
2050
1823
|
}
|
|
2051
1824
|
};
|
|
2052
1825
|
|
|
2053
|
-
// src/providers/PlanningCenterProvider.ts
|
|
2054
|
-
var PlanningCenterProvider = class
|
|
1826
|
+
// src/providers/planningCenter/PlanningCenterProvider.ts
|
|
1827
|
+
var PlanningCenterProvider = class {
|
|
2055
1828
|
constructor() {
|
|
2056
|
-
|
|
1829
|
+
this.apiHelper = new ApiHelper();
|
|
2057
1830
|
this.id = "planningcenter";
|
|
2058
1831
|
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
|
-
};
|
|
1832
|
+
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1833
|
+
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
1834
|
this.ONE_WEEK_MS = 6048e5;
|
|
1835
|
+
this.requiresAuth = true;
|
|
1836
|
+
this.authTypes = ["oauth_pkce"];
|
|
1837
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
2084
1838
|
}
|
|
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
|
-
};
|
|
1839
|
+
async apiRequest(path, auth) {
|
|
1840
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
2097
1841
|
}
|
|
2098
|
-
async browse(
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
auth
|
|
2103
|
-
);
|
|
2104
|
-
if (!response?.data) return [];
|
|
2105
|
-
return response.data.map((serviceType) => ({
|
|
1842
|
+
async browse(path, auth) {
|
|
1843
|
+
const { segments, depth } = parsePath(path);
|
|
1844
|
+
if (depth === 0) {
|
|
1845
|
+
return [{
|
|
2106
1846
|
type: "folder",
|
|
2107
|
-
id:
|
|
2108
|
-
title:
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
serviceTypeId: serviceType.id
|
|
2112
|
-
}
|
|
2113
|
-
}));
|
|
1847
|
+
id: "serviceTypes-root",
|
|
1848
|
+
title: "Service Types",
|
|
1849
|
+
path: "/serviceTypes"
|
|
1850
|
+
}];
|
|
2114
1851
|
}
|
|
2115
|
-
const
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
1852
|
+
const root = segments[0];
|
|
1853
|
+
if (root !== "serviceTypes") return [];
|
|
1854
|
+
if (depth === 1) {
|
|
1855
|
+
return this.getServiceTypes(auth);
|
|
1856
|
+
}
|
|
1857
|
+
if (depth === 2) {
|
|
1858
|
+
const serviceTypeId = segments[1];
|
|
1859
|
+
return this.getPlans(serviceTypeId, path, auth);
|
|
2123
1860
|
}
|
|
1861
|
+
if (depth === 3) {
|
|
1862
|
+
const serviceTypeId = segments[1];
|
|
1863
|
+
const planId = segments[2];
|
|
1864
|
+
return this.getPlanItems(serviceTypeId, planId, auth);
|
|
1865
|
+
}
|
|
1866
|
+
return [];
|
|
1867
|
+
}
|
|
1868
|
+
async getServiceTypes(auth) {
|
|
1869
|
+
const response = await this.apiRequest(
|
|
1870
|
+
this.config.endpoints.serviceTypes,
|
|
1871
|
+
auth
|
|
1872
|
+
);
|
|
1873
|
+
if (!response?.data) return [];
|
|
1874
|
+
return response.data.map((serviceType) => ({
|
|
1875
|
+
type: "folder",
|
|
1876
|
+
id: serviceType.id,
|
|
1877
|
+
title: serviceType.attributes.name,
|
|
1878
|
+
path: `/serviceTypes/${serviceType.id}`
|
|
1879
|
+
}));
|
|
2124
1880
|
}
|
|
2125
|
-
async getPlans(
|
|
2126
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
2127
|
-
if (!serviceTypeId) return [];
|
|
1881
|
+
async getPlans(serviceTypeId, currentPath, auth) {
|
|
2128
1882
|
const pathFn = this.config.endpoints.plans;
|
|
2129
1883
|
const response = await this.apiRequest(
|
|
2130
1884
|
`${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
|
|
@@ -2141,73 +1895,45 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2141
1895
|
type: "folder",
|
|
2142
1896
|
id: plan.id,
|
|
2143
1897
|
title: plan.attributes.title || this.formatDate(plan.attributes.sort_date),
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
serviceTypeId,
|
|
2147
|
-
planId: plan.id,
|
|
2148
|
-
sortDate: plan.attributes.sort_date
|
|
2149
|
-
}
|
|
1898
|
+
isLeaf: true,
|
|
1899
|
+
path: `${currentPath}/${plan.id}`
|
|
2150
1900
|
}));
|
|
2151
1901
|
}
|
|
2152
|
-
async getPlanItems(
|
|
2153
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
2154
|
-
const planId = folder.providerData?.planId;
|
|
2155
|
-
if (!serviceTypeId || !planId) return [];
|
|
1902
|
+
async getPlanItems(serviceTypeId, planId, auth) {
|
|
2156
1903
|
const pathFn = this.config.endpoints.planItems;
|
|
2157
1904
|
const response = await this.apiRequest(
|
|
2158
1905
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
2159
1906
|
auth
|
|
2160
1907
|
);
|
|
2161
1908
|
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
|
-
}));
|
|
1909
|
+
return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
|
|
2176
1910
|
}
|
|
2177
|
-
async getPresentations(
|
|
2178
|
-
const
|
|
2179
|
-
if (
|
|
2180
|
-
const serviceTypeId =
|
|
2181
|
-
const planId =
|
|
2182
|
-
if (!serviceTypeId || !planId) return null;
|
|
1911
|
+
async getPresentations(path, auth) {
|
|
1912
|
+
const { segments, depth } = parsePath(path);
|
|
1913
|
+
if (depth < 3 || segments[0] !== "serviceTypes") return null;
|
|
1914
|
+
const serviceTypeId = segments[1];
|
|
1915
|
+
const planId = segments[2];
|
|
2183
1916
|
const pathFn = this.config.endpoints.planItems;
|
|
2184
1917
|
const response = await this.apiRequest(
|
|
2185
1918
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
2186
1919
|
auth
|
|
2187
1920
|
);
|
|
2188
1921
|
if (!response?.data) return null;
|
|
1922
|
+
const plans = await this.getPlans(serviceTypeId, `/serviceTypes/${serviceTypeId}`, auth);
|
|
1923
|
+
const plan = plans.find((p) => p.id === planId);
|
|
1924
|
+
const planTitle = plan?.title || "Plan";
|
|
2189
1925
|
const sections = [];
|
|
2190
1926
|
const allFiles = [];
|
|
2191
1927
|
let currentSection = null;
|
|
2192
1928
|
for (const item of response.data) {
|
|
2193
1929
|
const itemType = item.attributes.item_type;
|
|
2194
1930
|
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
|
-
};
|
|
1931
|
+
if (currentSection && currentSection.presentations.length > 0) sections.push(currentSection);
|
|
1932
|
+
currentSection = { id: item.id, name: item.attributes.title || "Section", presentations: [] };
|
|
2203
1933
|
continue;
|
|
2204
1934
|
}
|
|
2205
1935
|
if (!currentSection) {
|
|
2206
|
-
currentSection = {
|
|
2207
|
-
id: `default-${planId}`,
|
|
2208
|
-
name: "Service",
|
|
2209
|
-
presentations: []
|
|
2210
|
-
};
|
|
1936
|
+
currentSection = { id: `default-${planId}`, name: "Service", presentations: [] };
|
|
2211
1937
|
}
|
|
2212
1938
|
const presentation = await this.convertToPresentation(item, auth);
|
|
2213
1939
|
if (presentation) {
|
|
@@ -2218,12 +1944,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2218
1944
|
if (currentSection && currentSection.presentations.length > 0) {
|
|
2219
1945
|
sections.push(currentSection);
|
|
2220
1946
|
}
|
|
2221
|
-
return {
|
|
2222
|
-
id: planId,
|
|
2223
|
-
name: folder.title,
|
|
2224
|
-
sections,
|
|
2225
|
-
allFiles
|
|
2226
|
-
};
|
|
1947
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
2227
1948
|
}
|
|
2228
1949
|
async convertToPresentation(item, auth) {
|
|
2229
1950
|
const itemType = item.attributes.item_type;
|
|
@@ -2234,17 +1955,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2234
1955
|
return this.convertMediaToPresentation(item, auth);
|
|
2235
1956
|
}
|
|
2236
1957
|
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
|
-
};
|
|
1958
|
+
return { id: item.id, name: item.attributes.title || "", actionType: "other", files: [], providerData: { itemType: "item", description: item.attributes.description, length: item.attributes.length } };
|
|
2248
1959
|
}
|
|
2249
1960
|
return null;
|
|
2250
1961
|
}
|
|
@@ -2252,13 +1963,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2252
1963
|
const songId = item.relationships?.song?.data?.id;
|
|
2253
1964
|
const arrangementId = item.relationships?.arrangement?.data?.id;
|
|
2254
1965
|
if (!songId) {
|
|
2255
|
-
return {
|
|
2256
|
-
id: item.id,
|
|
2257
|
-
name: item.attributes.title || "Song",
|
|
2258
|
-
actionType: "other",
|
|
2259
|
-
files: [],
|
|
2260
|
-
providerData: { itemType: "song" }
|
|
2261
|
-
};
|
|
1966
|
+
return { id: item.id, name: item.attributes.title || "Song", actionType: "other", files: [], providerData: { itemType: "song" } };
|
|
2262
1967
|
}
|
|
2263
1968
|
const songFn = this.config.endpoints.song;
|
|
2264
1969
|
const songResponse = await this.apiRequest(songFn(songId), auth);
|
|
@@ -2280,25 +1985,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2280
1985
|
}
|
|
2281
1986
|
const song = songResponse?.data;
|
|
2282
1987
|
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
|
-
};
|
|
1988
|
+
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
1989
|
}
|
|
2303
1990
|
async convertMediaToPresentation(item, auth) {
|
|
2304
1991
|
const files = [];
|
|
@@ -2318,33 +2005,55 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
2318
2005
|
if (!url) continue;
|
|
2319
2006
|
const contentType = attachment.attributes.content_type;
|
|
2320
2007
|
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
|
-
});
|
|
2008
|
+
files.push({ type: "file", id: attachment.id, title: attachment.attributes.filename, mediaType: detectMediaType(url, explicitType), url });
|
|
2328
2009
|
}
|
|
2329
2010
|
}
|
|
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
|
-
};
|
|
2011
|
+
return { id: item.id, name: item.attributes.title || "Media", actionType: "play", files, providerData: { itemType: "media", length: item.attributes.length } };
|
|
2340
2012
|
}
|
|
2341
2013
|
formatDate(dateString) {
|
|
2342
2014
|
const date = new Date(dateString);
|
|
2343
2015
|
return date.toISOString().slice(0, 10);
|
|
2344
2016
|
}
|
|
2017
|
+
async getPlaylist(path, auth, _resolution) {
|
|
2018
|
+
const plan = await this.getPresentations(path, auth);
|
|
2019
|
+
if (!plan) return null;
|
|
2020
|
+
return plan.allFiles.length > 0 ? plan.allFiles : null;
|
|
2021
|
+
}
|
|
2022
|
+
async getInstructions(path, auth) {
|
|
2023
|
+
const plan = await this.getPresentations(path, auth);
|
|
2024
|
+
if (!plan) return null;
|
|
2025
|
+
const sectionItems = plan.sections.map((section) => {
|
|
2026
|
+
const actionItems = section.presentations.map((pres) => {
|
|
2027
|
+
const fileItems = pres.files.map((file) => ({
|
|
2028
|
+
id: file.id,
|
|
2029
|
+
itemType: "file",
|
|
2030
|
+
label: file.title,
|
|
2031
|
+
embedUrl: file.url
|
|
2032
|
+
}));
|
|
2033
|
+
return {
|
|
2034
|
+
id: pres.id,
|
|
2035
|
+
itemType: "action",
|
|
2036
|
+
relatedId: pres.id,
|
|
2037
|
+
label: pres.name,
|
|
2038
|
+
description: pres.actionType,
|
|
2039
|
+
children: fileItems.length > 0 ? fileItems : void 0
|
|
2040
|
+
};
|
|
2041
|
+
});
|
|
2042
|
+
return {
|
|
2043
|
+
id: section.id,
|
|
2044
|
+
itemType: "section",
|
|
2045
|
+
label: section.name,
|
|
2046
|
+
children: actionItems
|
|
2047
|
+
};
|
|
2048
|
+
});
|
|
2049
|
+
return {
|
|
2050
|
+
venueName: plan.name,
|
|
2051
|
+
items: sectionItems
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2345
2054
|
};
|
|
2346
2055
|
|
|
2347
|
-
// src/providers/
|
|
2056
|
+
// src/providers/bibleProject/data.json
|
|
2348
2057
|
var data_default = {
|
|
2349
2058
|
collections: [
|
|
2350
2059
|
{
|
|
@@ -4452,10 +4161,9 @@ var data_default = {
|
|
|
4452
4161
|
]
|
|
4453
4162
|
};
|
|
4454
4163
|
|
|
4455
|
-
// src/providers/
|
|
4456
|
-
var BibleProjectProvider = class
|
|
4164
|
+
// src/providers/bibleProject/BibleProjectProvider.ts
|
|
4165
|
+
var BibleProjectProvider = class {
|
|
4457
4166
|
constructor() {
|
|
4458
|
-
super(...arguments);
|
|
4459
4167
|
this.id = "bibleproject";
|
|
4460
4168
|
this.name = "The Bible Project";
|
|
4461
4169
|
this.logos = {
|
|
@@ -4474,174 +4182,151 @@ var BibleProjectProvider = class extends ContentProvider {
|
|
|
4474
4182
|
}
|
|
4475
4183
|
};
|
|
4476
4184
|
this.data = data_default;
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4480
|
-
}
|
|
4481
|
-
getCapabilities() {
|
|
4482
|
-
return {
|
|
4185
|
+
this.requiresAuth = false;
|
|
4186
|
+
this.authTypes = ["none"];
|
|
4187
|
+
this.capabilities = {
|
|
4483
4188
|
browse: true,
|
|
4484
4189
|
presentations: true,
|
|
4485
|
-
// Has collections with videos
|
|
4486
4190
|
playlist: true,
|
|
4487
|
-
|
|
4488
|
-
instructions: false,
|
|
4489
|
-
expandedInstructions: false,
|
|
4191
|
+
instructions: true,
|
|
4490
4192
|
mediaLicensing: false
|
|
4491
4193
|
};
|
|
4492
4194
|
}
|
|
4493
|
-
async browse(
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
collection.name,
|
|
4498
|
-
collection.image || void 0,
|
|
4499
|
-
{ level: "collection", collectionName: collection.name }
|
|
4500
|
-
));
|
|
4195
|
+
async browse(path, _auth) {
|
|
4196
|
+
const { segments, depth } = parsePath(path);
|
|
4197
|
+
if (depth === 0) {
|
|
4198
|
+
return this.getCollections();
|
|
4501
4199
|
}
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
return this.getLessonFolders(collectionName);
|
|
4200
|
+
if (depth === 1) {
|
|
4201
|
+
const collectionSlug = segments[0];
|
|
4202
|
+
return this.getLessonFolders(collectionSlug, path);
|
|
4506
4203
|
}
|
|
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 [];
|
|
4204
|
+
if (depth === 2) {
|
|
4205
|
+
const collectionSlug = segments[0];
|
|
4206
|
+
const videoId = segments[1];
|
|
4207
|
+
return this.getVideoFile(collectionSlug, videoId);
|
|
4521
4208
|
}
|
|
4522
4209
|
return [];
|
|
4523
4210
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4211
|
+
getCollections() {
|
|
4212
|
+
return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
|
|
4213
|
+
type: "folder",
|
|
4214
|
+
id: this.slugify(collection.name),
|
|
4215
|
+
title: collection.name,
|
|
4216
|
+
image: collection.image || void 0,
|
|
4217
|
+
path: `/${this.slugify(collection.name)}`
|
|
4218
|
+
}));
|
|
4219
|
+
}
|
|
4220
|
+
getLessonFolders(collectionSlug, currentPath) {
|
|
4221
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4222
|
+
if (!collection) return [];
|
|
4223
|
+
return collection.videos.map((video) => ({
|
|
4224
|
+
type: "folder",
|
|
4225
|
+
id: video.id,
|
|
4226
|
+
title: video.title,
|
|
4227
|
+
image: video.thumbnailUrl,
|
|
4228
|
+
isLeaf: true,
|
|
4229
|
+
path: `${currentPath}/${video.id}`
|
|
4230
|
+
}));
|
|
4231
|
+
}
|
|
4232
|
+
getVideoFile(collectionSlug, videoId) {
|
|
4233
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4234
|
+
if (!collection) return [];
|
|
4235
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4236
|
+
if (!video) return [];
|
|
4237
|
+
return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId })];
|
|
4238
|
+
}
|
|
4239
|
+
async getPresentations(path, _auth) {
|
|
4240
|
+
const { segments, depth } = parsePath(path);
|
|
4241
|
+
if (depth < 1) return null;
|
|
4242
|
+
const collectionSlug = segments[0];
|
|
4243
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4244
|
+
if (!collection) return null;
|
|
4245
|
+
if (depth === 1) {
|
|
4530
4246
|
const allFiles = [];
|
|
4531
4247
|
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
|
-
};
|
|
4248
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4541
4249
|
allFiles.push(file);
|
|
4542
|
-
return {
|
|
4543
|
-
id: video.id,
|
|
4544
|
-
name: video.title,
|
|
4545
|
-
actionType: "play",
|
|
4546
|
-
files: [file]
|
|
4547
|
-
};
|
|
4250
|
+
return { id: video.id, name: video.title, actionType: "play", files: [file] };
|
|
4548
4251
|
});
|
|
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
|
-
};
|
|
4252
|
+
return { id: this.slugify(collection.name), name: collection.name, image: collection.image || void 0, sections: [{ id: "videos", name: "Videos", presentations }], allFiles };
|
|
4560
4253
|
}
|
|
4561
|
-
if (
|
|
4562
|
-
const
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
files: [file]
|
|
4585
|
-
}]
|
|
4586
|
-
}],
|
|
4587
|
-
allFiles: [file]
|
|
4588
|
-
};
|
|
4254
|
+
if (depth === 2) {
|
|
4255
|
+
const videoId = segments[1];
|
|
4256
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4257
|
+
if (!video) return null;
|
|
4258
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4259
|
+
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] };
|
|
4260
|
+
}
|
|
4261
|
+
return null;
|
|
4262
|
+
}
|
|
4263
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
4264
|
+
const { segments, depth } = parsePath(path);
|
|
4265
|
+
if (depth < 1) return null;
|
|
4266
|
+
const collectionSlug = segments[0];
|
|
4267
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4268
|
+
if (!collection) return null;
|
|
4269
|
+
if (depth === 1) {
|
|
4270
|
+
return collection.videos.map((video) => ({ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }));
|
|
4271
|
+
}
|
|
4272
|
+
if (depth === 2) {
|
|
4273
|
+
const videoId = segments[1];
|
|
4274
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4275
|
+
if (!video) return null;
|
|
4276
|
+
return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }];
|
|
4589
4277
|
}
|
|
4590
4278
|
return null;
|
|
4591
4279
|
}
|
|
4592
|
-
async
|
|
4593
|
-
const
|
|
4594
|
-
if (
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4280
|
+
async getInstructions(path, _auth) {
|
|
4281
|
+
const { segments, depth } = parsePath(path);
|
|
4282
|
+
if (depth < 1) return null;
|
|
4283
|
+
const collectionSlug = segments[0];
|
|
4284
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4285
|
+
if (!collection) return null;
|
|
4286
|
+
if (depth === 1) {
|
|
4287
|
+
const fileItems = collection.videos.map((video) => ({
|
|
4600
4288
|
id: video.id,
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
image: video.thumbnailUrl,
|
|
4605
|
-
muxPlaybackId: video.muxPlaybackId
|
|
4289
|
+
itemType: "file",
|
|
4290
|
+
label: video.title,
|
|
4291
|
+
embedUrl: video.videoUrl
|
|
4606
4292
|
}));
|
|
4293
|
+
return {
|
|
4294
|
+
venueName: collection.name,
|
|
4295
|
+
items: [{
|
|
4296
|
+
id: this.slugify(collection.name),
|
|
4297
|
+
itemType: "section",
|
|
4298
|
+
label: "Videos",
|
|
4299
|
+
children: fileItems
|
|
4300
|
+
}]
|
|
4301
|
+
};
|
|
4607
4302
|
}
|
|
4608
|
-
if (
|
|
4609
|
-
const
|
|
4610
|
-
|
|
4611
|
-
return
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
4619
|
-
|
|
4303
|
+
if (depth === 2) {
|
|
4304
|
+
const videoId = segments[1];
|
|
4305
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4306
|
+
if (!video) return null;
|
|
4307
|
+
return {
|
|
4308
|
+
venueName: video.title,
|
|
4309
|
+
items: [{
|
|
4310
|
+
id: "main",
|
|
4311
|
+
itemType: "section",
|
|
4312
|
+
label: "Content",
|
|
4313
|
+
children: [{
|
|
4314
|
+
id: video.id,
|
|
4315
|
+
itemType: "file",
|
|
4316
|
+
label: video.title,
|
|
4317
|
+
embedUrl: video.videoUrl
|
|
4318
|
+
}]
|
|
4319
|
+
}]
|
|
4320
|
+
};
|
|
4620
4321
|
}
|
|
4621
4322
|
return null;
|
|
4622
4323
|
}
|
|
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
4324
|
slugify(text) {
|
|
4640
4325
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4641
4326
|
}
|
|
4642
4327
|
};
|
|
4643
4328
|
|
|
4644
|
-
// src/providers/
|
|
4329
|
+
// src/providers/highVoltage/data.json
|
|
4645
4330
|
var data_default2 = {
|
|
4646
4331
|
collections: [
|
|
4647
4332
|
{
|
|
@@ -12145,10 +11830,9 @@ var data_default2 = {
|
|
|
12145
11830
|
]
|
|
12146
11831
|
};
|
|
12147
11832
|
|
|
12148
|
-
// src/providers/HighVoltageKidsProvider.ts
|
|
12149
|
-
var HighVoltageKidsProvider = class
|
|
11833
|
+
// src/providers/highVoltage/HighVoltageKidsProvider.ts
|
|
11834
|
+
var HighVoltageKidsProvider = class {
|
|
12150
11835
|
constructor() {
|
|
12151
|
-
super(...arguments);
|
|
12152
11836
|
this.id = "highvoltagekids";
|
|
12153
11837
|
this.name = "High Voltage Kids";
|
|
12154
11838
|
this.logos = {
|
|
@@ -12167,195 +11851,215 @@ var HighVoltageKidsProvider = class extends ContentProvider {
|
|
|
12167
11851
|
}
|
|
12168
11852
|
};
|
|
12169
11853
|
this.data = data_default2;
|
|
12170
|
-
|
|
12171
|
-
|
|
12172
|
-
|
|
12173
|
-
}
|
|
12174
|
-
getCapabilities() {
|
|
12175
|
-
return {
|
|
11854
|
+
this.requiresAuth = false;
|
|
11855
|
+
this.authTypes = ["none"];
|
|
11856
|
+
this.capabilities = {
|
|
12176
11857
|
browse: true,
|
|
12177
11858
|
presentations: true,
|
|
12178
|
-
// Has hierarchical structure: study -> lessons -> files
|
|
12179
11859
|
playlist: true,
|
|
12180
|
-
|
|
12181
|
-
instructions: false,
|
|
12182
|
-
expandedInstructions: false,
|
|
11860
|
+
instructions: true,
|
|
12183
11861
|
mediaLicensing: false
|
|
12184
11862
|
};
|
|
12185
11863
|
}
|
|
12186
|
-
async browse(
|
|
12187
|
-
|
|
12188
|
-
|
|
12189
|
-
|
|
12190
|
-
collection.name,
|
|
12191
|
-
void 0,
|
|
12192
|
-
{ level: "collection", collectionName: collection.name }
|
|
12193
|
-
));
|
|
11864
|
+
async browse(path, _auth) {
|
|
11865
|
+
const { segments, depth } = parsePath(path);
|
|
11866
|
+
if (depth === 0) {
|
|
11867
|
+
return this.getCollections();
|
|
12194
11868
|
}
|
|
12195
|
-
|
|
12196
|
-
|
|
12197
|
-
|
|
12198
|
-
return this.getStudyFolders(collectionName);
|
|
11869
|
+
if (depth === 1) {
|
|
11870
|
+
const collectionSlug = segments[0];
|
|
11871
|
+
return this.getStudyFolders(collectionSlug, path);
|
|
12199
11872
|
}
|
|
12200
|
-
if (
|
|
12201
|
-
const
|
|
12202
|
-
|
|
12203
|
-
|
|
12204
|
-
}
|
|
12205
|
-
return [];
|
|
11873
|
+
if (depth === 2) {
|
|
11874
|
+
const collectionSlug = segments[0];
|
|
11875
|
+
const studyId = segments[1];
|
|
11876
|
+
return this.getLessonFolders(collectionSlug, studyId, path);
|
|
12206
11877
|
}
|
|
12207
|
-
if (
|
|
12208
|
-
const
|
|
12209
|
-
|
|
12210
|
-
|
|
12211
|
-
|
|
12212
|
-
file.title,
|
|
12213
|
-
file.url,
|
|
12214
|
-
{ mediaType: file.mediaType }
|
|
12215
|
-
));
|
|
12216
|
-
}
|
|
12217
|
-
return [];
|
|
11878
|
+
if (depth === 3) {
|
|
11879
|
+
const collectionSlug = segments[0];
|
|
11880
|
+
const studyId = segments[1];
|
|
11881
|
+
const lessonId = segments[2];
|
|
11882
|
+
return this.getLessonFiles(collectionSlug, studyId, lessonId);
|
|
12218
11883
|
}
|
|
12219
11884
|
return [];
|
|
12220
11885
|
}
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12225
|
-
|
|
11886
|
+
getCollections() {
|
|
11887
|
+
return this.data.collections.filter((collection) => collection.folders.length > 0).map((collection) => ({
|
|
11888
|
+
type: "folder",
|
|
11889
|
+
id: this.slugify(collection.name),
|
|
11890
|
+
title: collection.name,
|
|
11891
|
+
path: `/${this.slugify(collection.name)}`
|
|
11892
|
+
}));
|
|
11893
|
+
}
|
|
11894
|
+
getStudyFolders(collectionSlug, currentPath) {
|
|
11895
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11896
|
+
if (!collection) return [];
|
|
11897
|
+
return collection.folders.map((study) => ({
|
|
11898
|
+
type: "folder",
|
|
11899
|
+
id: study.id,
|
|
11900
|
+
title: study.name,
|
|
11901
|
+
image: study.image || void 0,
|
|
11902
|
+
path: `${currentPath}/${study.id}`
|
|
11903
|
+
}));
|
|
11904
|
+
}
|
|
11905
|
+
getLessonFolders(collectionSlug, studyId, currentPath) {
|
|
11906
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11907
|
+
if (!collection) return [];
|
|
11908
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11909
|
+
if (!study) return [];
|
|
11910
|
+
return study.lessons.map((lesson) => ({
|
|
11911
|
+
type: "folder",
|
|
11912
|
+
id: lesson.id,
|
|
11913
|
+
title: lesson.name,
|
|
11914
|
+
image: lesson.image || void 0,
|
|
11915
|
+
isLeaf: true,
|
|
11916
|
+
path: `${currentPath}/${lesson.id}`
|
|
11917
|
+
}));
|
|
11918
|
+
}
|
|
11919
|
+
getLessonFiles(collectionSlug, studyId, lessonId) {
|
|
11920
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11921
|
+
if (!collection) return [];
|
|
11922
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11923
|
+
if (!study) return [];
|
|
11924
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11925
|
+
if (!lesson?.files) return [];
|
|
11926
|
+
return lesson.files.map((file) => createFile(file.id, file.title, file.url, { mediaType: file.mediaType }));
|
|
11927
|
+
}
|
|
11928
|
+
async getPresentations(path, _auth) {
|
|
11929
|
+
const { segments, depth } = parsePath(path);
|
|
11930
|
+
if (depth < 2) return null;
|
|
11931
|
+
const collectionSlug = segments[0];
|
|
11932
|
+
const studyId = segments[1];
|
|
11933
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11934
|
+
if (!collection) return null;
|
|
11935
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11936
|
+
if (!study) return null;
|
|
11937
|
+
if (depth === 2) {
|
|
12226
11938
|
const allFiles = [];
|
|
12227
|
-
const sections =
|
|
11939
|
+
const sections = study.lessons.map((lesson) => {
|
|
12228
11940
|
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
|
-
};
|
|
11941
|
+
const contentFile = { type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image };
|
|
12237
11942
|
allFiles.push(contentFile);
|
|
12238
11943
|
return contentFile;
|
|
12239
11944
|
});
|
|
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
|
-
};
|
|
11945
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11946
|
+
return { id: lesson.id, name: lesson.name, presentations: [presentation] };
|
|
12251
11947
|
});
|
|
12252
|
-
return {
|
|
12253
|
-
id: studyData.id,
|
|
12254
|
-
name: studyData.name,
|
|
12255
|
-
description: studyData.description,
|
|
12256
|
-
image: studyData.image,
|
|
12257
|
-
sections,
|
|
12258
|
-
allFiles
|
|
12259
|
-
};
|
|
11948
|
+
return { id: study.id, name: study.name, description: study.description, image: study.image, sections, allFiles };
|
|
12260
11949
|
}
|
|
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
|
-
};
|
|
11950
|
+
if (depth === 3) {
|
|
11951
|
+
const lessonId = segments[2];
|
|
11952
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11953
|
+
if (!lesson?.files) return null;
|
|
11954
|
+
const files = lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11955
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11956
|
+
return { id: lesson.id, name: lesson.name, image: lesson.image, sections: [{ id: "main", name: "Content", presentations: [presentation] }], allFiles: files };
|
|
12289
11957
|
}
|
|
12290
11958
|
return null;
|
|
12291
11959
|
}
|
|
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;
|
|
11960
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
11961
|
+
const { segments, depth } = parsePath(path);
|
|
11962
|
+
if (depth < 2) return null;
|
|
11963
|
+
const collectionSlug = segments[0];
|
|
11964
|
+
const studyId = segments[1];
|
|
11965
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11966
|
+
if (!collection) return null;
|
|
11967
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11968
|
+
if (!study) return null;
|
|
11969
|
+
if (depth === 2) {
|
|
12309
11970
|
const allFiles = [];
|
|
12310
|
-
for (const lesson of
|
|
11971
|
+
for (const lesson of study.lessons) {
|
|
12311
11972
|
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
|
-
});
|
|
11973
|
+
allFiles.push({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image });
|
|
12320
11974
|
}
|
|
12321
11975
|
}
|
|
12322
11976
|
return allFiles;
|
|
12323
11977
|
}
|
|
11978
|
+
if (depth === 3) {
|
|
11979
|
+
const lessonId = segments[2];
|
|
11980
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11981
|
+
if (!lesson?.files) return null;
|
|
11982
|
+
return lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11983
|
+
}
|
|
12324
11984
|
return null;
|
|
12325
11985
|
}
|
|
12326
|
-
|
|
12327
|
-
const
|
|
12328
|
-
if (
|
|
12329
|
-
|
|
12330
|
-
|
|
12331
|
-
|
|
12332
|
-
|
|
12333
|
-
|
|
12334
|
-
|
|
12335
|
-
|
|
12336
|
-
|
|
12337
|
-
|
|
12338
|
-
|
|
12339
|
-
|
|
12340
|
-
|
|
12341
|
-
|
|
12342
|
-
|
|
12343
|
-
|
|
12344
|
-
|
|
12345
|
-
|
|
12346
|
-
|
|
12347
|
-
|
|
12348
|
-
|
|
12349
|
-
|
|
12350
|
-
|
|
12351
|
-
|
|
12352
|
-
|
|
12353
|
-
|
|
12354
|
-
));
|
|
11986
|
+
async getInstructions(path, _auth) {
|
|
11987
|
+
const { segments, depth } = parsePath(path);
|
|
11988
|
+
if (depth < 2) return null;
|
|
11989
|
+
const collectionSlug = segments[0];
|
|
11990
|
+
const studyId = segments[1];
|
|
11991
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11992
|
+
if (!collection) return null;
|
|
11993
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11994
|
+
if (!study) return null;
|
|
11995
|
+
if (depth === 2) {
|
|
11996
|
+
const lessonItems = study.lessons.map((lesson) => {
|
|
11997
|
+
const fileItems = lesson.files.map((file) => {
|
|
11998
|
+
const seconds = estimateDuration(file.mediaType);
|
|
11999
|
+
return { id: file.id, itemType: "file", label: file.title, seconds, embedUrl: file.url };
|
|
12000
|
+
});
|
|
12001
|
+
return { id: lesson.id, itemType: "action", label: lesson.name, description: "play", children: fileItems };
|
|
12002
|
+
});
|
|
12003
|
+
return { venueName: study.name, items: [{ id: study.id, itemType: "header", label: study.name, children: [{ id: "main", itemType: "section", label: "Content", children: lessonItems }] }] };
|
|
12004
|
+
}
|
|
12005
|
+
if (depth === 3) {
|
|
12006
|
+
const lessonId = segments[2];
|
|
12007
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
12008
|
+
if (!lesson?.files) return null;
|
|
12009
|
+
const headerLabel = `${study.name} - ${lesson.name}`;
|
|
12010
|
+
const actionItems = this.groupFilesIntoActions(lesson.files);
|
|
12011
|
+
return { venueName: lesson.name, items: [{ id: lesson.id, itemType: "header", label: headerLabel, children: [{ id: "main", itemType: "section", label: lesson.name, children: actionItems }] }] };
|
|
12012
|
+
}
|
|
12013
|
+
return null;
|
|
12355
12014
|
}
|
|
12356
12015
|
slugify(text) {
|
|
12357
12016
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
12358
12017
|
}
|
|
12018
|
+
groupFilesIntoActions(files) {
|
|
12019
|
+
const actionItems = [];
|
|
12020
|
+
let currentGroup = [];
|
|
12021
|
+
let currentBaseName = null;
|
|
12022
|
+
const flushGroup = () => {
|
|
12023
|
+
if (currentGroup.length === 0) return;
|
|
12024
|
+
const children = currentGroup.map((file) => {
|
|
12025
|
+
const seconds = estimateDuration(file.mediaType);
|
|
12026
|
+
return {
|
|
12027
|
+
id: file.id,
|
|
12028
|
+
itemType: "file",
|
|
12029
|
+
label: file.title,
|
|
12030
|
+
seconds,
|
|
12031
|
+
embedUrl: file.url
|
|
12032
|
+
};
|
|
12033
|
+
});
|
|
12034
|
+
const label = currentGroup.length > 1 && currentBaseName ? currentBaseName : currentGroup[0].title;
|
|
12035
|
+
actionItems.push({
|
|
12036
|
+
id: currentGroup[0].id + "-action",
|
|
12037
|
+
itemType: "action",
|
|
12038
|
+
label,
|
|
12039
|
+
description: "play",
|
|
12040
|
+
children
|
|
12041
|
+
});
|
|
12042
|
+
currentGroup = [];
|
|
12043
|
+
currentBaseName = null;
|
|
12044
|
+
};
|
|
12045
|
+
for (const file of files) {
|
|
12046
|
+
const baseName = this.getBaseName(file.title);
|
|
12047
|
+
const isNumbered = baseName !== file.title;
|
|
12048
|
+
if (isNumbered && baseName === currentBaseName) {
|
|
12049
|
+
currentGroup.push(file);
|
|
12050
|
+
} else {
|
|
12051
|
+
flushGroup();
|
|
12052
|
+
currentGroup = [file];
|
|
12053
|
+
currentBaseName = isNumbered ? baseName : null;
|
|
12054
|
+
}
|
|
12055
|
+
}
|
|
12056
|
+
flushGroup();
|
|
12057
|
+
return actionItems;
|
|
12058
|
+
}
|
|
12059
|
+
getBaseName(title) {
|
|
12060
|
+
const match = title.match(/^(.+?)\s*\d+$/);
|
|
12061
|
+
return match ? match[1].trim() : title;
|
|
12062
|
+
}
|
|
12359
12063
|
};
|
|
12360
12064
|
|
|
12361
12065
|
// src/providers/index.ts
|
|
@@ -12440,15 +12144,15 @@ function getProviderConfig(providerId) {
|
|
|
12440
12144
|
const provider = getProvider(providerId);
|
|
12441
12145
|
return provider?.config || null;
|
|
12442
12146
|
}
|
|
12443
|
-
function getAvailableProviders() {
|
|
12147
|
+
function getAvailableProviders(ids) {
|
|
12444
12148
|
const implemented = getAllProviders().map((provider) => ({
|
|
12445
12149
|
id: provider.id,
|
|
12446
12150
|
name: provider.name,
|
|
12447
12151
|
logos: provider.logos,
|
|
12448
12152
|
implemented: true,
|
|
12449
|
-
requiresAuth: provider.requiresAuth
|
|
12450
|
-
authTypes: provider.
|
|
12451
|
-
capabilities: provider.
|
|
12153
|
+
requiresAuth: provider.requiresAuth,
|
|
12154
|
+
authTypes: provider.authTypes,
|
|
12155
|
+
capabilities: provider.capabilities
|
|
12452
12156
|
}));
|
|
12453
12157
|
const comingSoon = unimplementedProviders.map((p) => ({
|
|
12454
12158
|
id: p.id,
|
|
@@ -12457,42 +12161,63 @@ function getAvailableProviders() {
|
|
|
12457
12161
|
implemented: false,
|
|
12458
12162
|
requiresAuth: false,
|
|
12459
12163
|
authTypes: [],
|
|
12460
|
-
capabilities: { browse: false, presentations: false, playlist: false, instructions: false,
|
|
12164
|
+
capabilities: { browse: false, presentations: false, playlist: false, instructions: false, mediaLicensing: false }
|
|
12461
12165
|
}));
|
|
12462
|
-
|
|
12166
|
+
const all = [...implemented, ...comingSoon];
|
|
12167
|
+
if (ids && ids.length > 0) {
|
|
12168
|
+
const idSet = new Set(ids);
|
|
12169
|
+
return all.filter((provider) => idSet.has(provider.id));
|
|
12170
|
+
}
|
|
12171
|
+
return all;
|
|
12463
12172
|
}
|
|
12464
12173
|
|
|
12465
12174
|
// src/index.ts
|
|
12466
|
-
var VERSION = "0.0.
|
|
12175
|
+
var VERSION = "0.0.4";
|
|
12467
12176
|
// Annotate the CommonJS export names for ESM import in node:
|
|
12468
12177
|
0 && (module.exports = {
|
|
12469
12178
|
APlayProvider,
|
|
12179
|
+
ApiHelper,
|
|
12470
12180
|
B1ChurchProvider,
|
|
12471
12181
|
BibleProjectProvider,
|
|
12472
12182
|
ContentProvider,
|
|
12183
|
+
DEFAULT_DURATION_CONFIG,
|
|
12184
|
+
DeviceFlowHelper,
|
|
12473
12185
|
FormatConverters,
|
|
12474
12186
|
FormatResolver,
|
|
12187
|
+
HighVoltageKidsProvider,
|
|
12475
12188
|
LessonsChurchProvider,
|
|
12189
|
+
OAuthHelper,
|
|
12476
12190
|
PlanningCenterProvider,
|
|
12477
12191
|
SignPresenterProvider,
|
|
12192
|
+
TokenHelper,
|
|
12478
12193
|
VERSION,
|
|
12479
|
-
|
|
12194
|
+
appendToPath,
|
|
12195
|
+
buildPath,
|
|
12196
|
+
countWords,
|
|
12197
|
+
createFile,
|
|
12198
|
+
createFolder,
|
|
12480
12199
|
detectMediaType,
|
|
12200
|
+
estimateDuration,
|
|
12201
|
+
estimateImageDuration,
|
|
12202
|
+
estimateTextDuration,
|
|
12481
12203
|
expandedInstructionsToPlaylist,
|
|
12482
12204
|
expandedInstructionsToPresentations,
|
|
12205
|
+
generatePath,
|
|
12483
12206
|
getAllProviders,
|
|
12484
12207
|
getAvailableProviders,
|
|
12485
12208
|
getProvider,
|
|
12486
12209
|
getProviderConfig,
|
|
12210
|
+
getSegment,
|
|
12487
12211
|
instructionsToPlaylist,
|
|
12488
12212
|
instructionsToPresentations,
|
|
12489
12213
|
isContentFile,
|
|
12490
12214
|
isContentFolder,
|
|
12215
|
+
navigateToPath,
|
|
12216
|
+
parsePath,
|
|
12491
12217
|
playlistToExpandedInstructions,
|
|
12492
12218
|
playlistToInstructions,
|
|
12493
12219
|
playlistToPresentations,
|
|
12494
12220
|
presentationsToExpandedInstructions,
|
|
12495
|
-
presentationsToInstructions,
|
|
12496
12221
|
presentationsToPlaylist,
|
|
12497
12222
|
registerProvider
|
|
12498
12223
|
});
|