@churchapps/content-provider-helper 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +1511 -1280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +363 -447
- package/dist/index.d.ts +363 -447
- package/dist/index.js +1493 -1280
- package/dist/index.js.map +1 -1
- package/package.json +9 -2
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
1
7
|
// src/interfaces.ts
|
|
2
8
|
function isContentFolder(item) {
|
|
3
9
|
return item.type === "folder";
|
|
@@ -12,105 +18,379 @@ function detectMediaType(url, explicitType) {
|
|
|
12
18
|
const videoPatterns = [".mp4", ".webm", ".m3u8", ".mov", "stream.mux.com"];
|
|
13
19
|
return videoPatterns.some((p) => url.includes(p)) ? "video" : "image";
|
|
14
20
|
}
|
|
21
|
+
function createFolder(id, title, path, image, providerData, isLeaf) {
|
|
22
|
+
return { type: "folder", id, title, path, image, isLeaf, providerData };
|
|
23
|
+
}
|
|
24
|
+
function createFile(id, title, url, options) {
|
|
25
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
|
|
26
|
+
}
|
|
15
27
|
|
|
16
|
-
// src/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* Override in subclass if the provider supports playlists.
|
|
21
|
-
* @param _folder - The folder to get playlist for (typically a venue or playlist folder)
|
|
22
|
-
* @param _auth - Optional authentication data
|
|
23
|
-
* @param _resolution - Optional resolution hint for video quality
|
|
24
|
-
* @returns Array of ContentFile objects, or null if not supported
|
|
25
|
-
*/
|
|
26
|
-
getPlaylist(_folder, _auth, _resolution) {
|
|
27
|
-
return Promise.resolve(null);
|
|
28
|
+
// src/pathUtils.ts
|
|
29
|
+
function parsePath(path) {
|
|
30
|
+
if (!path || path === "/" || path === "") {
|
|
31
|
+
return { segments: [], depth: 0 };
|
|
28
32
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const segments = path.replace(/^\//, "").split("/").filter(Boolean);
|
|
34
|
+
return { segments, depth: segments.length };
|
|
35
|
+
}
|
|
36
|
+
function getSegment(path, index) {
|
|
37
|
+
const { segments } = parsePath(path);
|
|
38
|
+
return segments[index] ?? null;
|
|
39
|
+
}
|
|
40
|
+
function buildPath(segments) {
|
|
41
|
+
if (segments.length === 0) return "";
|
|
42
|
+
return "/" + segments.join("/");
|
|
43
|
+
}
|
|
44
|
+
function appendToPath(basePath, segment) {
|
|
45
|
+
if (!basePath || basePath === "/" || basePath === "") {
|
|
46
|
+
return "/" + segment;
|
|
38
47
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
const cleanBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
|
|
49
|
+
return cleanBase + "/" + segment;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// src/FormatConverters.ts
|
|
53
|
+
var FormatConverters_exports = {};
|
|
54
|
+
__export(FormatConverters_exports, {
|
|
55
|
+
collapseInstructions: () => collapseInstructions,
|
|
56
|
+
expandedInstructionsToPlaylist: () => expandedInstructionsToPlaylist,
|
|
57
|
+
expandedInstructionsToPresentations: () => expandedInstructionsToPresentations,
|
|
58
|
+
instructionsToPlaylist: () => instructionsToPlaylist,
|
|
59
|
+
instructionsToPresentations: () => instructionsToPresentations,
|
|
60
|
+
playlistToExpandedInstructions: () => playlistToExpandedInstructions,
|
|
61
|
+
playlistToInstructions: () => playlistToInstructions,
|
|
62
|
+
playlistToPresentations: () => playlistToPresentations,
|
|
63
|
+
presentationsToExpandedInstructions: () => presentationsToExpandedInstructions,
|
|
64
|
+
presentationsToInstructions: () => presentationsToInstructions,
|
|
65
|
+
presentationsToPlaylist: () => presentationsToPlaylist
|
|
66
|
+
});
|
|
67
|
+
function generateId() {
|
|
68
|
+
return "gen-" + Math.random().toString(36).substring(2, 11);
|
|
69
|
+
}
|
|
70
|
+
function mapItemTypeToActionType(itemType) {
|
|
71
|
+
switch (itemType) {
|
|
72
|
+
case "action":
|
|
73
|
+
case "lessonAction":
|
|
74
|
+
case "providerPresentation":
|
|
75
|
+
case "play":
|
|
76
|
+
return "play";
|
|
77
|
+
case "addon":
|
|
78
|
+
case "add-on":
|
|
79
|
+
case "lessonAddOn":
|
|
80
|
+
case "providerFile":
|
|
81
|
+
return "add-on";
|
|
82
|
+
default:
|
|
83
|
+
return "other";
|
|
48
84
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
85
|
+
}
|
|
86
|
+
function mapActionTypeToItemType(actionType) {
|
|
87
|
+
switch (actionType) {
|
|
88
|
+
case "play":
|
|
89
|
+
return "action";
|
|
90
|
+
case "add-on":
|
|
91
|
+
return "addon";
|
|
92
|
+
default:
|
|
93
|
+
return "item";
|
|
55
94
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
95
|
+
}
|
|
96
|
+
function presentationsToPlaylist(plan) {
|
|
97
|
+
if (plan.allFiles && plan.allFiles.length > 0) {
|
|
98
|
+
return [...plan.allFiles];
|
|
99
|
+
}
|
|
100
|
+
const files = [];
|
|
101
|
+
for (const section of plan.sections) {
|
|
102
|
+
for (const presentation of section.presentations) {
|
|
103
|
+
files.push(...presentation.files);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return files;
|
|
107
|
+
}
|
|
108
|
+
function presentationsToInstructions(plan) {
|
|
109
|
+
return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => {
|
|
110
|
+
const totalSeconds = pres.files.reduce((sum, f) => sum + (f.providerData?.seconds || 0), 0);
|
|
111
|
+
return { id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, seconds: totalSeconds || void 0, embedUrl: pres.files[0]?.embedUrl || pres.files[0]?.url };
|
|
112
|
+
}) })) };
|
|
113
|
+
}
|
|
114
|
+
function presentationsToExpandedInstructions(plan) {
|
|
115
|
+
return { venueName: plan.name, items: plan.sections.map((section) => ({ id: section.id, itemType: "section", label: section.name, children: section.presentations.map((pres) => ({ id: pres.id, itemType: mapActionTypeToItemType(pres.actionType), label: pres.name, description: pres.actionType !== "other" ? pres.actionType : void 0, seconds: pres.files.reduce((sum, f) => sum + (f.providerData?.seconds || 0), 0) || void 0, children: pres.files.map((f) => ({ id: f.id, itemType: "file", label: f.title, seconds: f.providerData?.seconds || void 0, embedUrl: f.embedUrl || f.url })) })) })) };
|
|
116
|
+
}
|
|
117
|
+
function instructionsToPlaylist(instructions) {
|
|
118
|
+
const files = [];
|
|
119
|
+
function extractFiles(items) {
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
if (item.embedUrl && (item.itemType === "file" || !item.children?.length)) {
|
|
122
|
+
files.push({ type: "file", id: item.id || item.relatedId || generateId(), title: item.label || "Untitled", mediaType: detectMediaType(item.embedUrl), url: item.embedUrl, embedUrl: item.embedUrl, providerData: item.seconds ? { seconds: item.seconds } : void 0 });
|
|
123
|
+
}
|
|
124
|
+
if (item.children) {
|
|
125
|
+
extractFiles(item.children);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
extractFiles(instructions.items);
|
|
130
|
+
return files;
|
|
131
|
+
}
|
|
132
|
+
var expandedInstructionsToPlaylist = instructionsToPlaylist;
|
|
133
|
+
function instructionsToPresentations(instructions, planId) {
|
|
134
|
+
const allFiles = [];
|
|
135
|
+
const sections = instructions.items.filter((item) => item.children && item.children.length > 0).map((sectionItem) => {
|
|
136
|
+
const presentations = (sectionItem.children || []).map((presItem) => {
|
|
137
|
+
const files = [];
|
|
138
|
+
if (presItem.children && presItem.children.length > 0) {
|
|
139
|
+
for (const child of presItem.children) {
|
|
140
|
+
if (child.embedUrl) {
|
|
141
|
+
const file = { type: "file", id: child.id || child.relatedId || generateId(), title: child.label || "Untitled", mediaType: detectMediaType(child.embedUrl), url: child.embedUrl, embedUrl: child.embedUrl, providerData: child.seconds ? { seconds: child.seconds } : void 0 };
|
|
142
|
+
allFiles.push(file);
|
|
143
|
+
files.push(file);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (files.length === 0 && presItem.embedUrl) {
|
|
148
|
+
const file = { type: "file", id: presItem.id || presItem.relatedId || generateId(), title: presItem.label || "Untitled", mediaType: detectMediaType(presItem.embedUrl), url: presItem.embedUrl, embedUrl: presItem.embedUrl, providerData: presItem.seconds ? { seconds: presItem.seconds } : void 0 };
|
|
149
|
+
allFiles.push(file);
|
|
150
|
+
files.push(file);
|
|
151
|
+
}
|
|
152
|
+
return { id: presItem.id || presItem.relatedId || generateId(), name: presItem.label || "Presentation", actionType: mapItemTypeToActionType(presItem.itemType), files };
|
|
153
|
+
});
|
|
154
|
+
return { id: sectionItem.id || sectionItem.relatedId || generateId(), name: sectionItem.label || "Section", presentations };
|
|
155
|
+
});
|
|
156
|
+
return { id: planId || generateId(), name: instructions.venueName || "Plan", sections, allFiles };
|
|
157
|
+
}
|
|
158
|
+
var expandedInstructionsToPresentations = instructionsToPresentations;
|
|
159
|
+
function collapseInstructions(instructions, maxDepth = 2) {
|
|
160
|
+
function collapseItem(item, currentDepth) {
|
|
161
|
+
if (currentDepth >= maxDepth || !item.children || item.children.length === 0) {
|
|
162
|
+
const { children, ...rest } = item;
|
|
163
|
+
if (children && children.length > 0) {
|
|
164
|
+
const totalSeconds = children.reduce((sum, c) => sum + (c.seconds || 0), 0);
|
|
165
|
+
if (totalSeconds > 0) {
|
|
166
|
+
rest.seconds = totalSeconds;
|
|
167
|
+
}
|
|
168
|
+
if (!rest.embedUrl) {
|
|
169
|
+
const firstWithUrl = children.find((c) => c.embedUrl);
|
|
170
|
+
if (firstWithUrl) {
|
|
171
|
+
rest.embedUrl = firstWithUrl.embedUrl;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return rest;
|
|
176
|
+
}
|
|
62
177
|
return {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
playlist: false,
|
|
66
|
-
instructions: false,
|
|
67
|
-
expandedInstructions: false,
|
|
68
|
-
mediaLicensing: false
|
|
178
|
+
...item,
|
|
179
|
+
children: item.children.map((child) => collapseItem(child, currentDepth + 1))
|
|
69
180
|
};
|
|
70
181
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
182
|
+
return {
|
|
183
|
+
venueName: instructions.venueName,
|
|
184
|
+
items: instructions.items.map((item) => collapseItem(item, 0))
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function playlistToPresentations(files, planName = "Playlist", sectionName = "Content") {
|
|
188
|
+
const presentations = files.map((file, index) => ({ id: `pres-${index}-${file.id}`, name: file.title, actionType: "play", files: [file] }));
|
|
189
|
+
return { id: "playlist-plan-" + generateId(), name: planName, sections: [{ id: "main-section", name: sectionName, presentations }], allFiles: [...files] };
|
|
190
|
+
}
|
|
191
|
+
function playlistToInstructions(files, venueName = "Playlist") {
|
|
192
|
+
return { venueName, items: [{ id: "main-section", itemType: "section", label: "Content", children: files.map((file, index) => ({ id: file.id || `item-${index}`, itemType: "file", label: file.title, seconds: file.providerData?.seconds || void 0, embedUrl: file.embedUrl || file.url })) }] };
|
|
193
|
+
}
|
|
194
|
+
var playlistToExpandedInstructions = playlistToInstructions;
|
|
195
|
+
|
|
196
|
+
// src/FormatResolver.ts
|
|
197
|
+
var FormatResolver = class {
|
|
198
|
+
constructor(provider, options = {}) {
|
|
199
|
+
this.provider = provider;
|
|
200
|
+
this.options = { allowLossy: options.allowLossy ?? true };
|
|
80
201
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
* @returns Array of supported AuthType values ('none', 'oauth_pkce', 'device_flow')
|
|
84
|
-
*/
|
|
85
|
-
getAuthTypes() {
|
|
86
|
-
if (!this.requiresAuth()) return ["none"];
|
|
87
|
-
const types = ["oauth_pkce"];
|
|
88
|
-
if (this.supportsDeviceFlow()) types.push("device_flow");
|
|
89
|
-
return types;
|
|
202
|
+
getProvider() {
|
|
203
|
+
return this.provider;
|
|
90
204
|
}
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
*/
|
|
96
|
-
isAuthValid(auth) {
|
|
97
|
-
if (!auth) return false;
|
|
98
|
-
return !this.isTokenExpired(auth);
|
|
205
|
+
/** Extract the last segment from a path to use as fallback ID/title */
|
|
206
|
+
getIdFromPath(path) {
|
|
207
|
+
const { segments } = parsePath(path);
|
|
208
|
+
return segments[segments.length - 1] || "content";
|
|
99
209
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
210
|
+
async getPlaylist(path, auth) {
|
|
211
|
+
const caps = this.provider.capabilities;
|
|
212
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
213
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
214
|
+
if (result && result.length > 0) return result;
|
|
215
|
+
}
|
|
216
|
+
if (caps.presentations) {
|
|
217
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
218
|
+
if (plan) return presentationsToPlaylist(plan);
|
|
219
|
+
}
|
|
220
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
221
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
222
|
+
if (expanded) return instructionsToPlaylist(expanded);
|
|
223
|
+
}
|
|
224
|
+
if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
|
|
225
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
226
|
+
if (instructions) return instructionsToPlaylist(instructions);
|
|
227
|
+
}
|
|
228
|
+
return null;
|
|
109
229
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
230
|
+
async getPlaylistWithMeta(path, auth) {
|
|
231
|
+
const caps = this.provider.capabilities;
|
|
232
|
+
if (caps.playlist && this.provider.getPlaylist) {
|
|
233
|
+
const result = await this.provider.getPlaylist(path, auth);
|
|
234
|
+
if (result && result.length > 0) {
|
|
235
|
+
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (caps.presentations) {
|
|
239
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
240
|
+
if (plan) return { data: presentationsToPlaylist(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
241
|
+
}
|
|
242
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
243
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
244
|
+
if (expanded) return { data: instructionsToPlaylist(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
|
|
245
|
+
}
|
|
246
|
+
if (this.options.allowLossy && caps.instructions && this.provider.getInstructions) {
|
|
247
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
248
|
+
if (instructions) return { data: instructionsToPlaylist(instructions), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
249
|
+
}
|
|
250
|
+
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
251
|
+
}
|
|
252
|
+
async getPresentations(path, auth) {
|
|
253
|
+
const caps = this.provider.capabilities;
|
|
254
|
+
const fallbackId = this.getIdFromPath(path);
|
|
255
|
+
if (caps.presentations) {
|
|
256
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
257
|
+
if (result) return result;
|
|
258
|
+
}
|
|
259
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
260
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
261
|
+
if (expanded) return instructionsToPresentations(expanded, fallbackId);
|
|
262
|
+
}
|
|
263
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
264
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
265
|
+
if (instructions) return instructionsToPresentations(instructions, fallbackId);
|
|
266
|
+
}
|
|
267
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
268
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
269
|
+
if (playlist && playlist.length > 0) {
|
|
270
|
+
return playlistToPresentations(playlist, fallbackId);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
async getPresentationsWithMeta(path, auth) {
|
|
276
|
+
const caps = this.provider.capabilities;
|
|
277
|
+
const fallbackId = this.getIdFromPath(path);
|
|
278
|
+
if (caps.presentations) {
|
|
279
|
+
const result = await this.provider.getPresentations(path, auth);
|
|
280
|
+
if (result) {
|
|
281
|
+
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
285
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
286
|
+
if (expanded) return { data: instructionsToPresentations(expanded, fallbackId), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: false } };
|
|
287
|
+
}
|
|
288
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
289
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
290
|
+
if (instructions) return { data: instructionsToPresentations(instructions, fallbackId), meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
291
|
+
}
|
|
292
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
293
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
294
|
+
if (playlist && playlist.length > 0) return { data: playlistToPresentations(playlist, fallbackId), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
295
|
+
}
|
|
296
|
+
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
297
|
+
}
|
|
298
|
+
async getInstructions(path, auth) {
|
|
299
|
+
const caps = this.provider.capabilities;
|
|
300
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
301
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
302
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
303
|
+
if (result) return result;
|
|
304
|
+
}
|
|
305
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
306
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
307
|
+
if (expanded) return collapseInstructions(expanded);
|
|
308
|
+
}
|
|
309
|
+
if (caps.presentations) {
|
|
310
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
311
|
+
if (plan) return presentationsToInstructions(plan);
|
|
312
|
+
}
|
|
313
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
314
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
315
|
+
if (playlist && playlist.length > 0) {
|
|
316
|
+
return playlistToInstructions(playlist, fallbackTitle);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
async getInstructionsWithMeta(path, auth) {
|
|
322
|
+
const caps = this.provider.capabilities;
|
|
323
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
324
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
325
|
+
const result = await this.provider.getInstructions(path, auth);
|
|
326
|
+
if (result) {
|
|
327
|
+
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
331
|
+
const expanded = await this.provider.getExpandedInstructions(path, auth);
|
|
332
|
+
if (expanded) return { data: collapseInstructions(expanded), meta: { isNative: false, sourceFormat: "expandedInstructions", isLossy: true } };
|
|
333
|
+
}
|
|
334
|
+
if (caps.presentations) {
|
|
335
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
336
|
+
if (plan) return { data: presentationsToInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
337
|
+
}
|
|
338
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
339
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
340
|
+
if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
341
|
+
}
|
|
342
|
+
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
343
|
+
}
|
|
344
|
+
async getExpandedInstructions(path, auth) {
|
|
345
|
+
const caps = this.provider.capabilities;
|
|
346
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
347
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
348
|
+
const result = await this.provider.getExpandedInstructions(path, auth);
|
|
349
|
+
if (result) return result;
|
|
350
|
+
}
|
|
351
|
+
if (caps.presentations) {
|
|
352
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
353
|
+
if (plan) return presentationsToExpandedInstructions(plan);
|
|
354
|
+
}
|
|
355
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
356
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
357
|
+
if (instructions) return instructions;
|
|
358
|
+
}
|
|
359
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
360
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
361
|
+
if (playlist && playlist.length > 0) {
|
|
362
|
+
return playlistToInstructions(playlist, fallbackTitle);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
}
|
|
367
|
+
async getExpandedInstructionsWithMeta(path, auth) {
|
|
368
|
+
const caps = this.provider.capabilities;
|
|
369
|
+
const fallbackTitle = this.getIdFromPath(path);
|
|
370
|
+
if (caps.expandedInstructions && this.provider.getExpandedInstructions) {
|
|
371
|
+
const result = await this.provider.getExpandedInstructions(path, auth);
|
|
372
|
+
if (result) {
|
|
373
|
+
return { data: result, meta: { isNative: true, isLossy: false } };
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (caps.presentations) {
|
|
377
|
+
const plan = await this.provider.getPresentations(path, auth);
|
|
378
|
+
if (plan) return { data: presentationsToExpandedInstructions(plan), meta: { isNative: false, sourceFormat: "presentations", isLossy: false } };
|
|
379
|
+
}
|
|
380
|
+
if (caps.instructions && this.provider.getInstructions) {
|
|
381
|
+
const instructions = await this.provider.getInstructions(path, auth);
|
|
382
|
+
if (instructions) return { data: instructions, meta: { isNative: false, sourceFormat: "instructions", isLossy: true } };
|
|
383
|
+
}
|
|
384
|
+
if (this.options.allowLossy && caps.playlist && this.provider.getPlaylist) {
|
|
385
|
+
const playlist = await this.provider.getPlaylist(path, auth);
|
|
386
|
+
if (playlist && playlist.length > 0) return { data: playlistToInstructions(playlist, fallbackTitle), meta: { isNative: false, sourceFormat: "playlist", isLossy: true } };
|
|
387
|
+
}
|
|
388
|
+
return { data: null, meta: { isNative: false, isLossy: false } };
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/helpers/OAuthHelper.ts
|
|
393
|
+
var OAuthHelper = class {
|
|
114
394
|
generateCodeVerifier() {
|
|
115
395
|
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
116
396
|
const length = 64;
|
|
@@ -122,11 +402,6 @@ var ContentProvider = class {
|
|
|
122
402
|
}
|
|
123
403
|
return result;
|
|
124
404
|
}
|
|
125
|
-
/**
|
|
126
|
-
* Generate a code challenge from a code verifier using SHA-256.
|
|
127
|
-
* @param verifier - The code verifier string
|
|
128
|
-
* @returns Base64url-encoded SHA-256 hash of the verifier
|
|
129
|
-
*/
|
|
130
405
|
async generateCodeChallenge(verifier) {
|
|
131
406
|
const encoder = new TextEncoder();
|
|
132
407
|
const data = encoder.encode(verifier);
|
|
@@ -139,142 +414,100 @@ var ContentProvider = class {
|
|
|
139
414
|
const base64 = btoa(binary);
|
|
140
415
|
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
141
416
|
}
|
|
142
|
-
|
|
143
|
-
* Build the OAuth authorization URL for PKCE flow.
|
|
144
|
-
* @param codeVerifier - The code verifier (store this for token exchange)
|
|
145
|
-
* @param redirectUri - The redirect URI to return to after authorization
|
|
146
|
-
* @param state - Optional state parameter for CSRF protection (defaults to provider ID)
|
|
147
|
-
* @returns Object with authorization URL and challenge method
|
|
148
|
-
*/
|
|
149
|
-
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
417
|
+
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
150
418
|
const codeChallenge = await this.generateCodeChallenge(codeVerifier);
|
|
151
419
|
const params = new URLSearchParams({
|
|
152
420
|
response_type: "code",
|
|
153
|
-
client_id:
|
|
421
|
+
client_id: config.clientId,
|
|
154
422
|
redirect_uri: redirectUri,
|
|
155
|
-
scope:
|
|
423
|
+
scope: config.scopes.join(" "),
|
|
156
424
|
code_challenge: codeChallenge,
|
|
157
425
|
code_challenge_method: "S256",
|
|
158
|
-
state: state ||
|
|
426
|
+
state: state || ""
|
|
159
427
|
});
|
|
160
|
-
return { url: `${
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
* Exchange an authorization code for access and refresh tokens.
|
|
164
|
-
* @param code - The authorization code from the callback
|
|
165
|
-
* @param codeVerifier - The original code verifier used to generate the challenge
|
|
166
|
-
* @param redirectUri - The redirect URI (must match the one used in buildAuthUrl)
|
|
167
|
-
* @returns Authentication data, or null if exchange failed
|
|
168
|
-
*/
|
|
169
|
-
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
428
|
+
return { url: `${config.oauthBase}/authorize?${params.toString()}`, challengeMethod: "S256" };
|
|
429
|
+
}
|
|
430
|
+
async exchangeCodeForTokens(config, providerId, code, codeVerifier, redirectUri) {
|
|
170
431
|
try {
|
|
171
432
|
const params = new URLSearchParams({
|
|
172
433
|
grant_type: "authorization_code",
|
|
173
434
|
code,
|
|
174
435
|
redirect_uri: redirectUri,
|
|
175
|
-
client_id:
|
|
436
|
+
client_id: config.clientId,
|
|
176
437
|
code_verifier: codeVerifier
|
|
177
438
|
});
|
|
178
|
-
const tokenUrl = `${
|
|
179
|
-
console.log(`${
|
|
180
|
-
console.log(` - client_id: ${
|
|
439
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
440
|
+
console.log(`${providerId} token exchange request to: ${tokenUrl}`);
|
|
441
|
+
console.log(` - client_id: ${config.clientId}`);
|
|
181
442
|
console.log(` - redirect_uri: ${redirectUri}`);
|
|
182
443
|
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
183
444
|
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
184
|
-
console.log(`${
|
|
445
|
+
console.log(`${providerId} token response status: ${response.status}`);
|
|
185
446
|
if (!response.ok) {
|
|
186
447
|
const errorText = await response.text();
|
|
187
|
-
console.error(`${
|
|
448
|
+
console.error(`${providerId} token exchange failed: ${response.status} - ${errorText}`);
|
|
188
449
|
return null;
|
|
189
450
|
}
|
|
190
451
|
const data = await response.json();
|
|
191
|
-
console.log(`${
|
|
192
|
-
return {
|
|
193
|
-
access_token: data.access_token,
|
|
194
|
-
refresh_token: data.refresh_token,
|
|
195
|
-
token_type: data.token_type || "Bearer",
|
|
196
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
197
|
-
expires_in: data.expires_in,
|
|
198
|
-
scope: data.scope || this.config.scopes.join(" ")
|
|
199
|
-
};
|
|
452
|
+
console.log(`${providerId} token exchange successful, got access_token: ${!!data.access_token}`);
|
|
453
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
200
454
|
} catch (error) {
|
|
201
|
-
console.error(`${
|
|
455
|
+
console.error(`${providerId} token exchange error:`, error);
|
|
202
456
|
return null;
|
|
203
457
|
}
|
|
204
458
|
}
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
// src/helpers/TokenHelper.ts
|
|
462
|
+
var TokenHelper = class {
|
|
463
|
+
isAuthValid(auth) {
|
|
464
|
+
if (!auth) return false;
|
|
465
|
+
return !this.isTokenExpired(auth);
|
|
466
|
+
}
|
|
467
|
+
isTokenExpired(auth) {
|
|
468
|
+
if (!auth.created_at || !auth.expires_in) return true;
|
|
469
|
+
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
470
|
+
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
471
|
+
}
|
|
472
|
+
async refreshToken(config, auth) {
|
|
211
473
|
if (!auth.refresh_token) return null;
|
|
212
474
|
try {
|
|
213
475
|
const params = new URLSearchParams({
|
|
214
476
|
grant_type: "refresh_token",
|
|
215
477
|
refresh_token: auth.refresh_token,
|
|
216
|
-
client_id:
|
|
478
|
+
client_id: config.clientId
|
|
217
479
|
});
|
|
218
|
-
const response = await fetch(`${
|
|
480
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
219
481
|
if (!response.ok) return null;
|
|
220
482
|
const data = await response.json();
|
|
221
|
-
return {
|
|
222
|
-
access_token: data.access_token,
|
|
223
|
-
refresh_token: data.refresh_token || auth.refresh_token,
|
|
224
|
-
token_type: data.token_type || "Bearer",
|
|
225
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
226
|
-
expires_in: data.expires_in,
|
|
227
|
-
scope: data.scope || auth.scope
|
|
228
|
-
};
|
|
483
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
|
|
229
484
|
} catch {
|
|
230
485
|
return null;
|
|
231
486
|
}
|
|
232
487
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
supportsDeviceFlow() {
|
|
238
|
-
return !!
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
// src/helpers/DeviceFlowHelper.ts
|
|
491
|
+
var DeviceFlowHelper = class {
|
|
492
|
+
supportsDeviceFlow(config) {
|
|
493
|
+
return !!config.supportsDeviceFlow && !!config.deviceAuthEndpoint;
|
|
239
494
|
}
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
* @returns Device authorization response with user_code and verification_uri, or null if not supported
|
|
243
|
-
*/
|
|
244
|
-
async initiateDeviceFlow() {
|
|
245
|
-
if (!this.supportsDeviceFlow()) return null;
|
|
495
|
+
async initiateDeviceFlow(config) {
|
|
496
|
+
if (!this.supportsDeviceFlow(config)) return null;
|
|
246
497
|
try {
|
|
247
|
-
const
|
|
248
|
-
const response = await fetch(`${this.config.oauthBase}${this.config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
498
|
+
const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: config.clientId, scope: config.scopes.join(" ") }) });
|
|
249
499
|
if (!response.ok) return null;
|
|
250
500
|
return await response.json();
|
|
251
501
|
} catch {
|
|
252
502
|
return null;
|
|
253
503
|
}
|
|
254
504
|
}
|
|
255
|
-
|
|
256
|
-
* Poll for a token after user has authorized the device.
|
|
257
|
-
* @param deviceCode - The device_code from initiateDeviceFlow response
|
|
258
|
-
* @returns Auth data if successful, error object if pending/slow_down, or null if failed/expired
|
|
259
|
-
*/
|
|
260
|
-
async pollDeviceFlowToken(deviceCode) {
|
|
505
|
+
async pollDeviceFlowToken(config, deviceCode) {
|
|
261
506
|
try {
|
|
262
|
-
const
|
|
263
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
264
|
-
device_code: deviceCode,
|
|
265
|
-
client_id: this.config.clientId
|
|
266
|
-
});
|
|
267
|
-
const response = await fetch(`${this.config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
507
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
|
|
268
508
|
if (response.ok) {
|
|
269
509
|
const data = await response.json();
|
|
270
|
-
return {
|
|
271
|
-
access_token: data.access_token,
|
|
272
|
-
refresh_token: data.refresh_token,
|
|
273
|
-
token_type: data.token_type || "Bearer",
|
|
274
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
275
|
-
expires_in: data.expires_in,
|
|
276
|
-
scope: data.scope || this.config.scopes.join(" ")
|
|
277
|
-
};
|
|
510
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
278
511
|
}
|
|
279
512
|
const errorData = await response.json();
|
|
280
513
|
switch (errorData.error) {
|
|
@@ -293,221 +526,295 @@ var ContentProvider = class {
|
|
|
293
526
|
return { error: "network_error" };
|
|
294
527
|
}
|
|
295
528
|
}
|
|
296
|
-
/**
|
|
297
|
-
* Calculate the delay between device flow poll attempts.
|
|
298
|
-
* @param baseInterval - Base interval in seconds (default: 5)
|
|
299
|
-
* @param slowDownCount - Number of slow_down responses received
|
|
300
|
-
* @returns Delay in milliseconds
|
|
301
|
-
*/
|
|
302
529
|
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
303
530
|
return (baseInterval + slowDownCount * 5) * 1e3;
|
|
304
531
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
*/
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
// src/helpers/ApiHelper.ts
|
|
535
|
+
var ApiHelper = class {
|
|
310
536
|
createAuthHeaders(auth) {
|
|
311
537
|
if (!auth) return null;
|
|
312
538
|
return { Authorization: `Bearer ${auth.access_token}`, Accept: "application/json" };
|
|
313
539
|
}
|
|
314
|
-
|
|
315
|
-
* Make an authenticated API request.
|
|
316
|
-
* @param path - API endpoint path (appended to config.apiBase)
|
|
317
|
-
* @param auth - Optional authentication data
|
|
318
|
-
* @param method - HTTP method (default: 'GET')
|
|
319
|
-
* @param body - Optional request body (for POST requests)
|
|
320
|
-
* @returns Parsed JSON response, or null if request failed
|
|
321
|
-
*/
|
|
322
|
-
async apiRequest(path, auth, method = "GET", body) {
|
|
540
|
+
async apiRequest(config, providerId, path, auth, method = "GET", body) {
|
|
323
541
|
try {
|
|
324
|
-
const url = `${
|
|
542
|
+
const url = `${config.apiBase}${path}`;
|
|
325
543
|
const headers = { Accept: "application/json" };
|
|
326
544
|
if (auth) headers["Authorization"] = `Bearer ${auth.access_token}`;
|
|
327
545
|
if (body) headers["Content-Type"] = "application/json";
|
|
328
|
-
console.log(`${
|
|
329
|
-
console.log(`${
|
|
546
|
+
console.log(`${providerId} API request: ${method} ${url}`);
|
|
547
|
+
console.log(`${providerId} API auth present: ${!!auth}`);
|
|
330
548
|
const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
|
|
331
549
|
const response = await fetch(url, options);
|
|
332
|
-
console.log(`${
|
|
550
|
+
console.log(`${providerId} API response status: ${response.status}`);
|
|
333
551
|
if (!response.ok) {
|
|
334
552
|
const errorText = await response.text();
|
|
335
|
-
console.error(`${
|
|
553
|
+
console.error(`${providerId} API request failed: ${response.status} - ${errorText}`);
|
|
336
554
|
return null;
|
|
337
555
|
}
|
|
338
556
|
return await response.json();
|
|
339
557
|
} catch (error) {
|
|
340
|
-
console.error(`${
|
|
558
|
+
console.error(`${providerId} API request error:`, error);
|
|
341
559
|
return null;
|
|
342
560
|
}
|
|
343
561
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/ContentProvider.ts
|
|
565
|
+
var ContentProvider = class {
|
|
566
|
+
constructor() {
|
|
567
|
+
this.oauthHelper = new OAuthHelper();
|
|
568
|
+
this.tokenHelper = new TokenHelper();
|
|
569
|
+
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
570
|
+
this.apiHelper = new ApiHelper();
|
|
571
|
+
}
|
|
572
|
+
async getPlaylist(path, auth, _resolution) {
|
|
573
|
+
const caps = this.getCapabilities();
|
|
574
|
+
if (caps.presentations) {
|
|
575
|
+
const plan = await this.getPresentations(path, auth);
|
|
576
|
+
if (plan) return presentationsToPlaylist(plan);
|
|
577
|
+
}
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
async getInstructions(path, auth) {
|
|
581
|
+
const caps = this.getCapabilities();
|
|
582
|
+
if (caps.expandedInstructions) {
|
|
583
|
+
const expanded = await this.getExpandedInstructions(path, auth);
|
|
584
|
+
if (expanded) return collapseInstructions(expanded);
|
|
585
|
+
}
|
|
586
|
+
if (caps.presentations) {
|
|
587
|
+
const plan = await this.getPresentations(path, auth);
|
|
588
|
+
if (plan) return presentationsToInstructions(plan);
|
|
589
|
+
}
|
|
590
|
+
return null;
|
|
591
|
+
}
|
|
592
|
+
async getExpandedInstructions(path, auth) {
|
|
593
|
+
const caps = this.getCapabilities();
|
|
594
|
+
if (caps.presentations) {
|
|
595
|
+
const plan = await this.getPresentations(path, auth);
|
|
596
|
+
if (plan) return presentationsToExpandedInstructions(plan);
|
|
597
|
+
}
|
|
598
|
+
return null;
|
|
599
|
+
}
|
|
600
|
+
requiresAuth() {
|
|
601
|
+
return !!this.config.clientId;
|
|
602
|
+
}
|
|
603
|
+
getCapabilities() {
|
|
604
|
+
return { browse: true, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
605
|
+
}
|
|
606
|
+
checkMediaLicense(_mediaId, _auth) {
|
|
607
|
+
return Promise.resolve(null);
|
|
608
|
+
}
|
|
609
|
+
getAuthTypes() {
|
|
610
|
+
if (!this.requiresAuth()) return ["none"];
|
|
611
|
+
const types = ["oauth_pkce"];
|
|
612
|
+
if (this.supportsDeviceFlow()) types.push("device_flow");
|
|
613
|
+
return types;
|
|
614
|
+
}
|
|
615
|
+
// Token management - delegated to TokenHelper
|
|
616
|
+
isAuthValid(auth) {
|
|
617
|
+
return this.tokenHelper.isAuthValid(auth);
|
|
618
|
+
}
|
|
619
|
+
isTokenExpired(auth) {
|
|
620
|
+
return this.tokenHelper.isTokenExpired(auth);
|
|
621
|
+
}
|
|
622
|
+
async refreshToken(auth) {
|
|
623
|
+
return this.tokenHelper.refreshToken(this.config, auth);
|
|
624
|
+
}
|
|
625
|
+
// OAuth PKCE - delegated to OAuthHelper
|
|
626
|
+
generateCodeVerifier() {
|
|
627
|
+
return this.oauthHelper.generateCodeVerifier();
|
|
628
|
+
}
|
|
629
|
+
async generateCodeChallenge(verifier) {
|
|
630
|
+
return this.oauthHelper.generateCodeChallenge(verifier);
|
|
631
|
+
}
|
|
632
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
633
|
+
return this.oauthHelper.buildAuthUrl(this.config, codeVerifier, redirectUri, state || this.id);
|
|
634
|
+
}
|
|
635
|
+
async exchangeCodeForTokens(code, codeVerifier, redirectUri) {
|
|
636
|
+
return this.oauthHelper.exchangeCodeForTokens(this.config, this.id, code, codeVerifier, redirectUri);
|
|
637
|
+
}
|
|
638
|
+
// Device flow - delegated to DeviceFlowHelper
|
|
639
|
+
supportsDeviceFlow() {
|
|
640
|
+
return this.deviceFlowHelper.supportsDeviceFlow(this.config);
|
|
641
|
+
}
|
|
642
|
+
async initiateDeviceFlow() {
|
|
643
|
+
return this.deviceFlowHelper.initiateDeviceFlow(this.config);
|
|
644
|
+
}
|
|
645
|
+
async pollDeviceFlowToken(deviceCode) {
|
|
646
|
+
return this.deviceFlowHelper.pollDeviceFlowToken(this.config, deviceCode);
|
|
647
|
+
}
|
|
648
|
+
calculatePollDelay(baseInterval = 5, slowDownCount = 0) {
|
|
649
|
+
return this.deviceFlowHelper.calculatePollDelay(baseInterval, slowDownCount);
|
|
650
|
+
}
|
|
651
|
+
// API requests - delegated to ApiHelper
|
|
652
|
+
createAuthHeaders(auth) {
|
|
653
|
+
return this.apiHelper.createAuthHeaders(auth);
|
|
654
|
+
}
|
|
655
|
+
async apiRequest(path, auth, method = "GET", body) {
|
|
656
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
|
|
657
|
+
}
|
|
658
|
+
// Content factories
|
|
659
|
+
createFolder(id, title, path, image, providerData, isLeaf) {
|
|
660
|
+
return { type: "folder", id, title, path, image, isLeaf, providerData };
|
|
661
|
+
}
|
|
363
662
|
createFile(id, title, url, options) {
|
|
364
|
-
return {
|
|
365
|
-
type: "file",
|
|
366
|
-
id,
|
|
367
|
-
title,
|
|
368
|
-
url,
|
|
369
|
-
mediaType: options?.mediaType ?? detectMediaType(url),
|
|
370
|
-
image: options?.image,
|
|
371
|
-
muxPlaybackId: options?.muxPlaybackId,
|
|
372
|
-
providerData: options?.providerData
|
|
373
|
-
};
|
|
663
|
+
return { type: "file", id, title, url, mediaType: options?.mediaType ?? detectMediaType(url), image: options?.image, muxPlaybackId: options?.muxPlaybackId, providerData: options?.providerData };
|
|
374
664
|
}
|
|
375
665
|
};
|
|
376
666
|
|
|
377
|
-
// src/providers/APlayProvider.ts
|
|
378
|
-
var APlayProvider = class
|
|
667
|
+
// src/providers/aPlay/APlayProvider.ts
|
|
668
|
+
var APlayProvider = class {
|
|
379
669
|
constructor() {
|
|
380
|
-
|
|
670
|
+
this.apiHelper = new ApiHelper();
|
|
381
671
|
this.id = "aplay";
|
|
382
672
|
this.name = "APlay";
|
|
383
|
-
this.logos = {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
this.
|
|
388
|
-
id: "aplay",
|
|
389
|
-
name: "APlay",
|
|
390
|
-
apiBase: "https://api-prod.amazingkids.app",
|
|
391
|
-
oauthBase: "https://api.joinamazing.com/prod/aims/oauth",
|
|
392
|
-
clientId: "xFJFq7yNYuXXXMx0YBiQ",
|
|
393
|
-
scopes: ["openid", "profile", "email"],
|
|
394
|
-
endpoints: {
|
|
395
|
-
modules: "/prod/curriculum/modules",
|
|
396
|
-
productLibraries: (productId) => `/prod/curriculum/modules/products/${productId}/libraries`,
|
|
397
|
-
libraryMedia: (libraryId) => `/prod/creators/libraries/${libraryId}/media`
|
|
398
|
-
}
|
|
399
|
-
};
|
|
673
|
+
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
674
|
+
this.config = { id: "aplay", name: "APlay", apiBase: "https://api-prod.amazingkids.app", oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"], endpoints: { modules: "/prod/curriculum/modules", productLibraries: (productId) => `/prod/curriculum/modules/products/${productId}/libraries`, libraryMedia: (libraryId) => `/prod/creators/libraries/${libraryId}/media` } };
|
|
675
|
+
this.requiresAuth = true;
|
|
676
|
+
this.authTypes = ["oauth_pkce"];
|
|
677
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: true };
|
|
400
678
|
}
|
|
401
|
-
|
|
402
|
-
return
|
|
403
|
-
browse: true,
|
|
404
|
-
presentations: true,
|
|
405
|
-
playlist: false,
|
|
406
|
-
instructions: false,
|
|
407
|
-
expandedInstructions: false,
|
|
408
|
-
mediaLicensing: true
|
|
409
|
-
};
|
|
679
|
+
async apiRequest(path, auth) {
|
|
680
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
410
681
|
}
|
|
411
|
-
async browse(
|
|
412
|
-
|
|
413
|
-
console.log(
|
|
414
|
-
if (
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
if (!Array.isArray(modules)) return [];
|
|
422
|
-
const items = [];
|
|
423
|
-
for (const m of modules) {
|
|
424
|
-
if (m.isLocked) continue;
|
|
425
|
-
const allProducts = m.products || [];
|
|
426
|
-
const products = allProducts.filter((p) => !p.isHidden);
|
|
427
|
-
if (products.length === 0) {
|
|
428
|
-
items.push({
|
|
429
|
-
type: "folder",
|
|
430
|
-
id: m.id || m.moduleId,
|
|
431
|
-
title: m.title || m.name,
|
|
432
|
-
image: m.image,
|
|
433
|
-
providerData: { level: "libraries", productId: m.id || m.moduleId }
|
|
434
|
-
});
|
|
435
|
-
} else if (products.length === 1) {
|
|
436
|
-
const product = products[0];
|
|
437
|
-
items.push({
|
|
438
|
-
type: "folder",
|
|
439
|
-
id: product.productId || product.id,
|
|
440
|
-
title: m.title || m.name,
|
|
441
|
-
image: m.image || product.image,
|
|
442
|
-
providerData: { level: "libraries", productId: product.productId || product.id }
|
|
443
|
-
});
|
|
444
|
-
} else {
|
|
445
|
-
items.push({
|
|
446
|
-
type: "folder",
|
|
447
|
-
id: m.id || m.moduleId,
|
|
448
|
-
title: m.title || m.name,
|
|
449
|
-
image: m.image,
|
|
450
|
-
providerData: {
|
|
451
|
-
level: "products",
|
|
452
|
-
products: products.map((p) => ({ id: p.productId || p.id, title: p.title || p.name, image: p.image }))
|
|
453
|
-
}
|
|
454
|
-
});
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
return items;
|
|
682
|
+
async browse(path, auth) {
|
|
683
|
+
const { segments, depth } = parsePath(path);
|
|
684
|
+
console.log("APlay browse called with path:", path, "depth:", depth);
|
|
685
|
+
if (depth === 0) {
|
|
686
|
+
return [{
|
|
687
|
+
type: "folder",
|
|
688
|
+
id: "modules-root",
|
|
689
|
+
title: "Modules",
|
|
690
|
+
path: "/modules"
|
|
691
|
+
}];
|
|
458
692
|
}
|
|
459
|
-
const
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
case "libraries":
|
|
464
|
-
return this.getLibraryFolders(folder, auth);
|
|
465
|
-
case "media":
|
|
466
|
-
return this.getMediaFiles(folder, auth);
|
|
467
|
-
default:
|
|
468
|
-
return [];
|
|
693
|
+
const root = segments[0];
|
|
694
|
+
if (root !== "modules") return [];
|
|
695
|
+
if (depth === 1) {
|
|
696
|
+
return this.getModules(auth);
|
|
469
697
|
}
|
|
698
|
+
if (depth === 2) {
|
|
699
|
+
const moduleId = segments[1];
|
|
700
|
+
return this.getModuleContent(moduleId, path, auth);
|
|
701
|
+
}
|
|
702
|
+
if (depth === 4 && segments[2] === "products") {
|
|
703
|
+
const productId = segments[3];
|
|
704
|
+
return this.getLibraryFolders(productId, path, auth);
|
|
705
|
+
}
|
|
706
|
+
if (depth === 5 && segments[2] === "products") {
|
|
707
|
+
const libraryId = segments[4];
|
|
708
|
+
return this.getMediaFiles(libraryId, auth);
|
|
709
|
+
}
|
|
710
|
+
if (depth === 4 && segments[2] === "libraries") {
|
|
711
|
+
const libraryId = segments[3];
|
|
712
|
+
return this.getMediaFiles(libraryId, auth);
|
|
713
|
+
}
|
|
714
|
+
return [];
|
|
470
715
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
716
|
+
async getModules(auth) {
|
|
717
|
+
console.log(`APlay fetching modules from: ${this.config.endpoints.modules}`);
|
|
718
|
+
const response = await this.apiRequest(this.config.endpoints.modules, auth);
|
|
719
|
+
console.log("APlay modules response:", response ? "received" : "null");
|
|
720
|
+
if (!response) return [];
|
|
721
|
+
const modules = response.data || response.modules || response;
|
|
722
|
+
console.log("APlay modules count:", Array.isArray(modules) ? modules.length : "not an array");
|
|
723
|
+
if (!Array.isArray(modules)) return [];
|
|
724
|
+
const items = [];
|
|
725
|
+
for (const m of modules) {
|
|
726
|
+
if (m.isLocked) continue;
|
|
727
|
+
const moduleId = m.id || m.moduleId;
|
|
728
|
+
const moduleTitle = m.title || m.name;
|
|
729
|
+
const moduleImage = m.image;
|
|
730
|
+
const allProducts = m.products || [];
|
|
731
|
+
const products = allProducts.filter((p) => !p.isHidden);
|
|
732
|
+
if (products.length === 0) {
|
|
733
|
+
items.push({
|
|
734
|
+
type: "folder",
|
|
735
|
+
id: moduleId,
|
|
736
|
+
title: moduleTitle,
|
|
737
|
+
image: moduleImage,
|
|
738
|
+
path: `/modules/${moduleId}`,
|
|
739
|
+
providerData: { productCount: 0 }
|
|
740
|
+
});
|
|
741
|
+
} else if (products.length === 1) {
|
|
742
|
+
const product = products[0];
|
|
743
|
+
items.push({
|
|
744
|
+
type: "folder",
|
|
745
|
+
id: product.productId || product.id,
|
|
746
|
+
title: moduleTitle,
|
|
747
|
+
image: moduleImage || product.image,
|
|
748
|
+
path: `/modules/${moduleId}`,
|
|
749
|
+
providerData: { productCount: 1, productId: product.productId || product.id }
|
|
750
|
+
});
|
|
751
|
+
} else {
|
|
752
|
+
items.push({
|
|
753
|
+
type: "folder",
|
|
754
|
+
id: moduleId,
|
|
755
|
+
title: moduleTitle,
|
|
756
|
+
image: moduleImage,
|
|
757
|
+
path: `/modules/${moduleId}`,
|
|
758
|
+
providerData: {
|
|
759
|
+
productCount: products.length,
|
|
760
|
+
products: products.map((p) => ({
|
|
761
|
+
id: p.productId || p.id,
|
|
762
|
+
title: p.title || p.name,
|
|
763
|
+
image: p.image
|
|
764
|
+
}))
|
|
765
|
+
}
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return items;
|
|
480
770
|
}
|
|
481
|
-
async
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
if (!
|
|
771
|
+
async getModuleContent(moduleId, currentPath, auth) {
|
|
772
|
+
const modules = await this.getModules(auth);
|
|
773
|
+
const module = modules.find((m) => m.id === moduleId || m.providerData?.productId === moduleId);
|
|
774
|
+
if (!module) return [];
|
|
775
|
+
const providerData = module.providerData;
|
|
776
|
+
const productCount = providerData?.productCount || 0;
|
|
777
|
+
if (productCount === 0 || productCount === 1) {
|
|
778
|
+
const productId = providerData?.productId || moduleId;
|
|
779
|
+
return this.getLibraryFolders(productId, `${currentPath}/libraries`, auth);
|
|
780
|
+
} else {
|
|
781
|
+
const products = providerData?.products || [];
|
|
782
|
+
return products.map((p) => ({
|
|
783
|
+
type: "folder",
|
|
784
|
+
id: p.id,
|
|
785
|
+
title: p.title,
|
|
786
|
+
image: p.image,
|
|
787
|
+
path: `${currentPath}/products/${p.id}`
|
|
788
|
+
}));
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
async getLibraryFolders(productId, currentPath, auth) {
|
|
792
|
+
console.log("APlay getLibraryFolders called with productId:", productId);
|
|
485
793
|
const pathFn = this.config.endpoints.productLibraries;
|
|
486
|
-
const
|
|
487
|
-
console.log(`APlay fetching libraries from: ${
|
|
488
|
-
const response = await this.apiRequest(
|
|
489
|
-
console.log(
|
|
794
|
+
const apiPath = pathFn(productId);
|
|
795
|
+
console.log(`APlay fetching libraries from: ${apiPath}`);
|
|
796
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
797
|
+
console.log("APlay libraries response:", response ? "received" : "null");
|
|
490
798
|
if (!response) return [];
|
|
491
799
|
const libraries = response.data || response.libraries || response;
|
|
492
|
-
console.log(
|
|
800
|
+
console.log("APlay libraries count:", Array.isArray(libraries) ? libraries.length : "not an array");
|
|
493
801
|
if (!Array.isArray(libraries)) return [];
|
|
494
802
|
return libraries.map((l) => ({
|
|
495
803
|
type: "folder",
|
|
496
804
|
id: l.libraryId || l.id,
|
|
497
805
|
title: l.title || l.name,
|
|
498
806
|
image: l.image,
|
|
499
|
-
|
|
807
|
+
isLeaf: true,
|
|
808
|
+
path: `${currentPath}/${l.libraryId || l.id}`
|
|
500
809
|
}));
|
|
501
810
|
}
|
|
502
|
-
async getMediaFiles(
|
|
503
|
-
|
|
504
|
-
console.log(`APlay getMediaFiles called with libraryId:`, libraryId);
|
|
505
|
-
if (!libraryId) return [];
|
|
811
|
+
async getMediaFiles(libraryId, auth) {
|
|
812
|
+
console.log("APlay getMediaFiles called with libraryId:", libraryId);
|
|
506
813
|
const pathFn = this.config.endpoints.libraryMedia;
|
|
507
|
-
const
|
|
508
|
-
console.log(`APlay fetching media from: ${
|
|
509
|
-
const response = await this.apiRequest(
|
|
510
|
-
console.log(
|
|
814
|
+
const apiPath = pathFn(libraryId);
|
|
815
|
+
console.log(`APlay fetching media from: ${apiPath}`);
|
|
816
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
817
|
+
console.log("APlay media response:", response ? "received" : "null");
|
|
511
818
|
if (!response) return [];
|
|
512
819
|
const mediaItems = response.data || response.media || response;
|
|
513
820
|
if (!Array.isArray(mediaItems)) return [];
|
|
@@ -537,144 +844,97 @@ var APlayProvider = class extends ContentProvider {
|
|
|
537
844
|
if (!url) continue;
|
|
538
845
|
const detectedMediaType = detectMediaType(url, mediaType);
|
|
539
846
|
const fileId = item.mediaId || item.id;
|
|
540
|
-
files.push({
|
|
541
|
-
type: "file",
|
|
542
|
-
id: fileId,
|
|
543
|
-
title: item.title || item.name || item.fileName || "",
|
|
544
|
-
mediaType: detectedMediaType,
|
|
545
|
-
image: thumbnail,
|
|
546
|
-
url,
|
|
547
|
-
muxPlaybackId,
|
|
548
|
-
mediaId: fileId
|
|
549
|
-
});
|
|
847
|
+
files.push({ type: "file", id: fileId, title: item.title || item.name || item.fileName || "", mediaType: detectedMediaType, image: thumbnail, url, muxPlaybackId, mediaId: fileId });
|
|
550
848
|
}
|
|
551
849
|
return files;
|
|
552
850
|
}
|
|
553
|
-
async getPresentations(
|
|
554
|
-
const
|
|
555
|
-
if (
|
|
556
|
-
|
|
851
|
+
async getPresentations(path, auth) {
|
|
852
|
+
const { segments, depth } = parsePath(path);
|
|
853
|
+
if (depth < 4 || segments[0] !== "modules") return null;
|
|
854
|
+
let libraryId;
|
|
855
|
+
const title = "Library";
|
|
856
|
+
if (segments[2] === "products" && depth === 5) {
|
|
857
|
+
libraryId = segments[4];
|
|
858
|
+
} else if (segments[2] === "libraries" && depth === 4) {
|
|
859
|
+
libraryId = segments[3];
|
|
860
|
+
} else {
|
|
861
|
+
return null;
|
|
862
|
+
}
|
|
863
|
+
const files = await this.getMediaFiles(libraryId, auth);
|
|
557
864
|
if (files.length === 0) return null;
|
|
558
|
-
const presentations = files.map((f) => ({
|
|
559
|
-
|
|
560
|
-
name: f.title,
|
|
561
|
-
actionType: "play",
|
|
562
|
-
files: [f]
|
|
563
|
-
}));
|
|
564
|
-
return {
|
|
565
|
-
id: libraryId,
|
|
566
|
-
name: folder.title,
|
|
567
|
-
image: folder.image,
|
|
568
|
-
sections: [{
|
|
569
|
-
id: `section-${libraryId}`,
|
|
570
|
-
name: folder.title || "Library",
|
|
571
|
-
presentations
|
|
572
|
-
}],
|
|
573
|
-
allFiles: files
|
|
574
|
-
};
|
|
865
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
866
|
+
return { id: libraryId, name: title, sections: [{ id: `section-${libraryId}`, name: title, presentations }], allFiles: files };
|
|
575
867
|
}
|
|
576
|
-
/**
|
|
577
|
-
* Check the license status for a specific media item.
|
|
578
|
-
* Returns license information including pingback URL if licensed.
|
|
579
|
-
*/
|
|
580
868
|
async checkMediaLicense(mediaId, auth) {
|
|
581
869
|
if (!auth) return null;
|
|
582
870
|
try {
|
|
583
871
|
const url = `${this.config.apiBase}/prod/reports/media/license-check`;
|
|
584
|
-
const response = await fetch(url, {
|
|
585
|
-
method: "POST",
|
|
586
|
-
headers: {
|
|
587
|
-
"Authorization": `Bearer ${auth.access_token}`,
|
|
588
|
-
"Content-Type": "application/json",
|
|
589
|
-
"Accept": "application/json"
|
|
590
|
-
},
|
|
591
|
-
body: JSON.stringify({ mediaIds: [mediaId] })
|
|
592
|
-
});
|
|
872
|
+
const response = await fetch(url, { method: "POST", headers: { "Authorization": `Bearer ${auth.access_token}`, "Content-Type": "application/json", "Accept": "application/json" }, body: JSON.stringify({ mediaIds: [mediaId] }) });
|
|
593
873
|
if (!response.ok) return null;
|
|
594
874
|
const data = await response.json();
|
|
595
875
|
const licenseData = Array.isArray(data) ? data : data.data || [];
|
|
596
876
|
const result = licenseData.find((item) => item.mediaId === mediaId);
|
|
597
877
|
if (result?.isLicensed) {
|
|
598
|
-
|
|
599
|
-
return {
|
|
600
|
-
mediaId,
|
|
601
|
-
status: "valid",
|
|
602
|
-
message: "Media is licensed for playback",
|
|
603
|
-
expiresAt: result.expiresAt
|
|
604
|
-
};
|
|
878
|
+
return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
|
|
605
879
|
}
|
|
606
|
-
return {
|
|
607
|
-
mediaId,
|
|
608
|
-
status: "not_licensed",
|
|
609
|
-
message: "Media is not licensed"
|
|
610
|
-
};
|
|
880
|
+
return { mediaId, status: "not_licensed", message: "Media is not licensed" };
|
|
611
881
|
} catch {
|
|
612
|
-
return {
|
|
613
|
-
mediaId,
|
|
614
|
-
status: "unknown",
|
|
615
|
-
message: "Unable to verify license status"
|
|
616
|
-
};
|
|
882
|
+
return { mediaId, status: "unknown", message: "Unable to verify license status" };
|
|
617
883
|
}
|
|
618
884
|
}
|
|
619
885
|
};
|
|
620
886
|
|
|
621
|
-
// src/providers/SignPresenterProvider.ts
|
|
622
|
-
var SignPresenterProvider = class
|
|
887
|
+
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
888
|
+
var SignPresenterProvider = class {
|
|
623
889
|
constructor() {
|
|
624
|
-
|
|
890
|
+
this.apiHelper = new ApiHelper();
|
|
625
891
|
this.id = "signpresenter";
|
|
626
892
|
this.name = "SignPresenter";
|
|
627
|
-
this.logos = {
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
this.
|
|
632
|
-
id: "signpresenter",
|
|
633
|
-
name: "SignPresenter",
|
|
634
|
-
apiBase: "https://api.signpresenter.com",
|
|
635
|
-
oauthBase: "https://api.signpresenter.com/oauth",
|
|
636
|
-
clientId: "lessonsscreen-tv",
|
|
637
|
-
scopes: ["openid", "profile", "content"],
|
|
638
|
-
supportsDeviceFlow: true,
|
|
639
|
-
deviceAuthEndpoint: "/device/authorize",
|
|
640
|
-
endpoints: {
|
|
641
|
-
playlists: "/content/playlists",
|
|
642
|
-
messages: (playlistId) => `/content/playlists/${playlistId}/messages`
|
|
643
|
-
}
|
|
644
|
-
};
|
|
893
|
+
this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
|
|
894
|
+
this.config = { id: "signpresenter", name: "SignPresenter", apiBase: "https://api.signpresenter.com", oauthBase: "https://api.signpresenter.com/oauth", clientId: "lessonsscreen-tv", scopes: ["openid", "profile", "content"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { playlists: "/content/playlists", messages: (playlistId) => `/content/playlists/${playlistId}/messages` } };
|
|
895
|
+
this.requiresAuth = true;
|
|
896
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
897
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
645
898
|
}
|
|
646
|
-
|
|
647
|
-
return
|
|
648
|
-
browse: true,
|
|
649
|
-
presentations: true,
|
|
650
|
-
playlist: false,
|
|
651
|
-
instructions: false,
|
|
652
|
-
expandedInstructions: false,
|
|
653
|
-
mediaLicensing: false
|
|
654
|
-
};
|
|
899
|
+
async apiRequest(path, auth) {
|
|
900
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
655
901
|
}
|
|
656
|
-
async browse(
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
if (!response) return [];
|
|
661
|
-
const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
|
|
662
|
-
if (!Array.isArray(playlists)) return [];
|
|
663
|
-
return playlists.map((p) => ({
|
|
902
|
+
async browse(path, auth) {
|
|
903
|
+
const { segments, depth } = parsePath(path);
|
|
904
|
+
if (depth === 0) {
|
|
905
|
+
return [{
|
|
664
906
|
type: "folder",
|
|
665
|
-
id:
|
|
666
|
-
title:
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
907
|
+
id: "playlists-root",
|
|
908
|
+
title: "Playlists",
|
|
909
|
+
path: "/playlists"
|
|
910
|
+
}];
|
|
911
|
+
}
|
|
912
|
+
const root = segments[0];
|
|
913
|
+
if (root !== "playlists") return [];
|
|
914
|
+
if (depth === 1) {
|
|
915
|
+
return this.getPlaylists(auth);
|
|
916
|
+
}
|
|
917
|
+
if (depth === 2) {
|
|
918
|
+
const playlistId = segments[1];
|
|
919
|
+
return this.getMessages(playlistId, auth);
|
|
670
920
|
}
|
|
671
|
-
const level = folder.providerData?.level;
|
|
672
|
-
if (level === "messages") return this.getMessages(folder, auth);
|
|
673
921
|
return [];
|
|
674
922
|
}
|
|
675
|
-
async
|
|
676
|
-
const
|
|
677
|
-
|
|
923
|
+
async getPlaylists(auth) {
|
|
924
|
+
const apiPath = this.config.endpoints.playlists;
|
|
925
|
+
const response = await this.apiRequest(apiPath, auth);
|
|
926
|
+
if (!response) return [];
|
|
927
|
+
const playlists = Array.isArray(response) ? response : response.data || response.playlists || [];
|
|
928
|
+
if (!Array.isArray(playlists)) return [];
|
|
929
|
+
return playlists.map((p) => ({
|
|
930
|
+
type: "folder",
|
|
931
|
+
id: p.id,
|
|
932
|
+
title: p.name,
|
|
933
|
+
image: p.image,
|
|
934
|
+
path: `/playlists/${p.id}`
|
|
935
|
+
}));
|
|
936
|
+
}
|
|
937
|
+
async getMessages(playlistId, auth) {
|
|
678
938
|
const pathFn = this.config.endpoints.messages;
|
|
679
939
|
const response = await this.apiRequest(pathFn(playlistId), auth);
|
|
680
940
|
if (!response) return [];
|
|
@@ -685,93 +945,42 @@ var SignPresenterProvider = class extends ContentProvider {
|
|
|
685
945
|
if (!msg.url) continue;
|
|
686
946
|
const url = msg.url;
|
|
687
947
|
const seconds = msg.seconds;
|
|
688
|
-
files.push({
|
|
689
|
-
type: "file",
|
|
690
|
-
id: msg.id,
|
|
691
|
-
title: msg.name,
|
|
692
|
-
mediaType: detectMediaType(url, msg.mediaType),
|
|
693
|
-
image: msg.thumbnail || msg.image,
|
|
694
|
-
url,
|
|
695
|
-
// For direct media providers, embedUrl is the media URL itself
|
|
696
|
-
embedUrl: url,
|
|
697
|
-
providerData: seconds !== void 0 ? { seconds } : void 0
|
|
698
|
-
});
|
|
948
|
+
files.push({ type: "file", id: msg.id, title: msg.name, mediaType: detectMediaType(url, msg.mediaType), image: msg.thumbnail || msg.image, url, embedUrl: url, providerData: seconds !== void 0 ? { seconds } : void 0 });
|
|
699
949
|
}
|
|
700
950
|
return files;
|
|
701
951
|
}
|
|
702
|
-
async getPresentations(
|
|
703
|
-
const
|
|
704
|
-
if (
|
|
705
|
-
const
|
|
952
|
+
async getPresentations(path, auth) {
|
|
953
|
+
const { segments, depth } = parsePath(path);
|
|
954
|
+
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
955
|
+
const playlistId = segments[1];
|
|
956
|
+
const files = await this.getMessages(playlistId, auth);
|
|
706
957
|
if (files.length === 0) return null;
|
|
707
|
-
const
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
}
|
|
713
|
-
return {
|
|
714
|
-
id: playlistId,
|
|
715
|
-
name: folder.title,
|
|
716
|
-
image: folder.image,
|
|
717
|
-
sections: [{
|
|
718
|
-
id: `section-${playlistId}`,
|
|
719
|
-
name: folder.title || "Playlist",
|
|
720
|
-
presentations
|
|
721
|
-
}],
|
|
722
|
-
allFiles: files
|
|
723
|
-
};
|
|
958
|
+
const playlists = await this.getPlaylists(auth);
|
|
959
|
+
const playlist = playlists.find((p) => p.id === playlistId);
|
|
960
|
+
const title = playlist?.title || "Playlist";
|
|
961
|
+
const image = playlist?.image;
|
|
962
|
+
const presentations = files.map((f) => ({ id: f.id, name: f.title, actionType: "play", files: [f] }));
|
|
963
|
+
return { id: playlistId, name: title, image, sections: [{ id: `section-${playlistId}`, name: title, presentations }], allFiles: files };
|
|
724
964
|
}
|
|
725
965
|
};
|
|
726
966
|
|
|
727
|
-
// src/providers/LessonsChurchProvider.ts
|
|
728
|
-
var LessonsChurchProvider = class
|
|
967
|
+
// src/providers/lessonsChurch/LessonsChurchProvider.ts
|
|
968
|
+
var LessonsChurchProvider = class {
|
|
729
969
|
constructor() {
|
|
730
|
-
super(...arguments);
|
|
731
970
|
this.id = "lessonschurch";
|
|
732
971
|
this.name = "Lessons.church";
|
|
733
|
-
this.logos = {
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
this.
|
|
738
|
-
id: "lessonschurch",
|
|
739
|
-
name: "Lessons.church",
|
|
740
|
-
apiBase: "https://api.lessons.church",
|
|
741
|
-
oauthBase: "",
|
|
742
|
-
clientId: "",
|
|
743
|
-
scopes: [],
|
|
744
|
-
endpoints: {
|
|
745
|
-
programs: "/programs/public",
|
|
746
|
-
studies: (programId) => `/studies/public/program/${programId}`,
|
|
747
|
-
lessons: (studyId) => `/lessons/public/study/${studyId}`,
|
|
748
|
-
venues: (lessonId) => `/venues/public/lesson/${lessonId}`,
|
|
749
|
-
playlist: (venueId) => `/venues/playlist/${venueId}`,
|
|
750
|
-
feed: (venueId) => `/venues/public/feed/${venueId}`,
|
|
751
|
-
addOns: "/addOns/public",
|
|
752
|
-
addOnDetail: (id) => `/addOns/public/${id}`
|
|
753
|
-
}
|
|
754
|
-
};
|
|
972
|
+
this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
|
|
973
|
+
this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: "https://api.lessons.church", oauthBase: "", clientId: "", scopes: [], endpoints: { programs: "/programs/public", studies: (programId) => `/studies/public/program/${programId}`, lessons: (studyId) => `/lessons/public/study/${studyId}`, venues: (lessonId) => `/venues/public/lesson/${lessonId}`, playlist: (venueId) => `/venues/playlist/${venueId}`, feed: (venueId) => `/venues/public/feed/${venueId}`, addOns: "/addOns/public", addOnDetail: (id) => `/addOns/public/${id}` } };
|
|
974
|
+
this.requiresAuth = false;
|
|
975
|
+
this.authTypes = ["none"];
|
|
976
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
|
|
755
977
|
}
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
}
|
|
759
|
-
getCapabilities() {
|
|
760
|
-
return {
|
|
761
|
-
browse: true,
|
|
762
|
-
presentations: true,
|
|
763
|
-
playlist: true,
|
|
764
|
-
instructions: true,
|
|
765
|
-
expandedInstructions: true,
|
|
766
|
-
mediaLicensing: false
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
async getPlaylist(folder, _auth, resolution) {
|
|
770
|
-
const venueId = folder.providerData?.venueId;
|
|
978
|
+
async getPlaylist(path, _auth, resolution) {
|
|
979
|
+
const venueId = getSegment(path, 4);
|
|
771
980
|
if (!venueId) return null;
|
|
772
|
-
let
|
|
773
|
-
if (resolution)
|
|
774
|
-
const response = await this.apiRequest(
|
|
981
|
+
let apiPath = `/venues/playlist/${venueId}`;
|
|
982
|
+
if (resolution) apiPath += `?resolution=${resolution}`;
|
|
983
|
+
const response = await this.apiRequest(apiPath);
|
|
775
984
|
if (!response) return null;
|
|
776
985
|
const files = [];
|
|
777
986
|
const messages = response.messages || [];
|
|
@@ -783,15 +992,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
783
992
|
if (!f.url) continue;
|
|
784
993
|
const url = f.url;
|
|
785
994
|
const fileId = f.id || `playlist-${fileIndex++}`;
|
|
786
|
-
files.push({
|
|
787
|
-
type: "file",
|
|
788
|
-
id: fileId,
|
|
789
|
-
title: f.name || msg.name,
|
|
790
|
-
mediaType: detectMediaType(url, f.fileType),
|
|
791
|
-
image: response.lessonImage,
|
|
792
|
-
url,
|
|
793
|
-
providerData: { seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo }
|
|
794
|
-
});
|
|
995
|
+
files.push({ type: "file", id: fileId, title: f.name || msg.name, mediaType: detectMediaType(url, f.fileType), image: response.lessonImage, url, providerData: { seconds: f.seconds, loop: f.loop, loopVideo: f.loopVideo } });
|
|
795
996
|
}
|
|
796
997
|
}
|
|
797
998
|
return files;
|
|
@@ -806,48 +1007,32 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
806
1007
|
return null;
|
|
807
1008
|
}
|
|
808
1009
|
}
|
|
809
|
-
async browse(
|
|
810
|
-
|
|
1010
|
+
async browse(path, _auth) {
|
|
1011
|
+
const { segments, depth } = parsePath(path);
|
|
1012
|
+
console.log("[LessonsChurchProvider.browse] path:", path, "depth:", depth, "segments:", segments);
|
|
1013
|
+
if (depth === 0) {
|
|
811
1014
|
return [
|
|
812
|
-
{
|
|
813
|
-
|
|
814
|
-
id: "lessons-root",
|
|
815
|
-
title: "Lessons",
|
|
816
|
-
providerData: { level: "programs" }
|
|
817
|
-
},
|
|
818
|
-
{
|
|
819
|
-
type: "folder",
|
|
820
|
-
id: "addons-root",
|
|
821
|
-
title: "Add-Ons",
|
|
822
|
-
providerData: { level: "addOnCategories" }
|
|
823
|
-
}
|
|
1015
|
+
{ type: "folder", id: "lessons-root", title: "Lessons", path: "/lessons" },
|
|
1016
|
+
{ type: "folder", id: "addons-root", title: "Add-Ons", path: "/addons" }
|
|
824
1017
|
];
|
|
825
1018
|
}
|
|
826
|
-
const
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
// Add-ons hierarchy
|
|
840
|
-
case "addOnCategories":
|
|
841
|
-
return this.getAddOnCategories();
|
|
842
|
-
case "addOns":
|
|
843
|
-
return this.getAddOnsByCategory(folder);
|
|
844
|
-
default:
|
|
845
|
-
return [];
|
|
846
|
-
}
|
|
1019
|
+
const root = segments[0];
|
|
1020
|
+
if (root === "lessons") return this.browseLessons(path, segments);
|
|
1021
|
+
if (root === "addons") return this.browseAddOns(path, segments);
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
async browseLessons(currentPath, segments) {
|
|
1025
|
+
const depth = segments.length;
|
|
1026
|
+
if (depth === 1) return this.getPrograms();
|
|
1027
|
+
if (depth === 2) return this.getStudies(segments[1], currentPath);
|
|
1028
|
+
if (depth === 3) return this.getLessons(segments[2], currentPath);
|
|
1029
|
+
if (depth === 4) return this.getVenues(segments[3], currentPath);
|
|
1030
|
+
if (depth === 5) return this.getPlaylistFiles(segments[4]);
|
|
1031
|
+
return [];
|
|
847
1032
|
}
|
|
848
1033
|
async getPrograms() {
|
|
849
|
-
const
|
|
850
|
-
const response = await this.apiRequest(
|
|
1034
|
+
const apiPath = this.config.endpoints.programs;
|
|
1035
|
+
const response = await this.apiRequest(apiPath);
|
|
851
1036
|
if (!response) return [];
|
|
852
1037
|
const programs = Array.isArray(response) ? response : [];
|
|
853
1038
|
return programs.map((p) => ({
|
|
@@ -855,12 +1040,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
855
1040
|
id: p.id,
|
|
856
1041
|
title: p.name,
|
|
857
1042
|
image: p.image,
|
|
858
|
-
|
|
1043
|
+
path: `/lessons/${p.id}`
|
|
859
1044
|
}));
|
|
860
1045
|
}
|
|
861
|
-
async getStudies(
|
|
862
|
-
const programId = folder.providerData?.programId;
|
|
863
|
-
if (!programId) return [];
|
|
1046
|
+
async getStudies(programId, currentPath) {
|
|
864
1047
|
const pathFn = this.config.endpoints.studies;
|
|
865
1048
|
const response = await this.apiRequest(pathFn(programId));
|
|
866
1049
|
if (!response) return [];
|
|
@@ -870,12 +1053,10 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
870
1053
|
id: s.id,
|
|
871
1054
|
title: s.name,
|
|
872
1055
|
image: s.image,
|
|
873
|
-
|
|
1056
|
+
path: `${currentPath}/${s.id}`
|
|
874
1057
|
}));
|
|
875
1058
|
}
|
|
876
|
-
async getLessons(
|
|
877
|
-
const studyId = folder.providerData?.studyId;
|
|
878
|
-
if (!studyId) return [];
|
|
1059
|
+
async getLessons(studyId, currentPath) {
|
|
879
1060
|
const pathFn = this.config.endpoints.lessons;
|
|
880
1061
|
const response = await this.apiRequest(pathFn(studyId));
|
|
881
1062
|
if (!response) return [];
|
|
@@ -885,31 +1066,42 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
885
1066
|
id: l.id,
|
|
886
1067
|
title: l.name || l.title,
|
|
887
1068
|
image: l.image,
|
|
888
|
-
|
|
1069
|
+
path: `${currentPath}/${l.id}`,
|
|
1070
|
+
providerData: { lessonImage: l.image }
|
|
1071
|
+
// Keep for display on venues
|
|
889
1072
|
}));
|
|
890
1073
|
}
|
|
891
|
-
async getVenues(
|
|
892
|
-
const lessonId = folder.providerData?.lessonId;
|
|
893
|
-
if (!lessonId) return [];
|
|
1074
|
+
async getVenues(lessonId, currentPath) {
|
|
894
1075
|
const pathFn = this.config.endpoints.venues;
|
|
895
1076
|
const response = await this.apiRequest(pathFn(lessonId));
|
|
896
1077
|
if (!response) return [];
|
|
1078
|
+
const lessonResponse = await this.apiRequest(`/lessons/public/${lessonId}`);
|
|
1079
|
+
const lessonImage = lessonResponse?.image;
|
|
897
1080
|
const venues = Array.isArray(response) ? response : [];
|
|
898
|
-
|
|
1081
|
+
const result = venues.map((v) => ({
|
|
899
1082
|
type: "folder",
|
|
900
1083
|
id: v.id,
|
|
901
1084
|
title: v.name,
|
|
902
|
-
image:
|
|
903
|
-
|
|
1085
|
+
image: lessonImage,
|
|
1086
|
+
isLeaf: true,
|
|
1087
|
+
path: `${currentPath}/${v.id}`
|
|
904
1088
|
}));
|
|
1089
|
+
console.log("[LessonsChurchProvider.getVenues] returning:", result.map((r) => ({ id: r.id, title: r.title, isLeaf: r.isLeaf })));
|
|
1090
|
+
return result;
|
|
905
1091
|
}
|
|
906
|
-
async getPlaylistFiles(
|
|
907
|
-
const files = await this.getPlaylist(
|
|
1092
|
+
async getPlaylistFiles(venueId) {
|
|
1093
|
+
const files = await this.getPlaylist(`/lessons/_/_/_/${venueId}`, null);
|
|
908
1094
|
return files || [];
|
|
909
1095
|
}
|
|
1096
|
+
async browseAddOns(_currentPath, segments) {
|
|
1097
|
+
const depth = segments.length;
|
|
1098
|
+
if (depth === 1) return this.getAddOnCategories();
|
|
1099
|
+
if (depth === 2) return this.getAddOnsByCategory(segments[1]);
|
|
1100
|
+
return [];
|
|
1101
|
+
}
|
|
910
1102
|
async getAddOnCategories() {
|
|
911
|
-
const
|
|
912
|
-
const response = await this.apiRequest(
|
|
1103
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1104
|
+
const response = await this.apiRequest(apiPath);
|
|
913
1105
|
if (!response) return [];
|
|
914
1106
|
const addOns = Array.isArray(response) ? response : [];
|
|
915
1107
|
const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
|
|
@@ -917,17 +1109,16 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
917
1109
|
type: "folder",
|
|
918
1110
|
id: `category-${category}`,
|
|
919
1111
|
title: category,
|
|
920
|
-
|
|
921
|
-
level: "addOns",
|
|
922
|
-
category,
|
|
923
|
-
allAddOns: addOns
|
|
924
|
-
}
|
|
1112
|
+
path: `/addons/${encodeURIComponent(category)}`
|
|
925
1113
|
}));
|
|
926
1114
|
}
|
|
927
|
-
async getAddOnsByCategory(
|
|
928
|
-
const
|
|
929
|
-
const
|
|
930
|
-
const
|
|
1115
|
+
async getAddOnsByCategory(category) {
|
|
1116
|
+
const decodedCategory = decodeURIComponent(category);
|
|
1117
|
+
const apiPath = this.config.endpoints.addOns;
|
|
1118
|
+
const response = await this.apiRequest(apiPath);
|
|
1119
|
+
if (!response) return [];
|
|
1120
|
+
const allAddOns = Array.isArray(response) ? response : [];
|
|
1121
|
+
const filtered = allAddOns.filter((a) => a.category === decodedCategory);
|
|
931
1122
|
const files = [];
|
|
932
1123
|
for (const addOn of filtered) {
|
|
933
1124
|
const file = await this.convertAddOnToFile(addOn);
|
|
@@ -937,8 +1128,8 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
937
1128
|
}
|
|
938
1129
|
async convertAddOnToFile(addOn) {
|
|
939
1130
|
const pathFn = this.config.endpoints.addOnDetail;
|
|
940
|
-
const
|
|
941
|
-
const detail = await this.apiRequest(
|
|
1131
|
+
const apiPath = pathFn(addOn.id);
|
|
1132
|
+
const detail = await this.apiRequest(apiPath);
|
|
942
1133
|
if (!detail) return null;
|
|
943
1134
|
let url = "";
|
|
944
1135
|
let mediaType = "video";
|
|
@@ -953,53 +1144,32 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
953
1144
|
const fileType = file.fileType;
|
|
954
1145
|
mediaType = fileType?.startsWith("video/") ? "video" : "image";
|
|
955
1146
|
} else {
|
|
956
|
-
return null;
|
|
957
|
-
}
|
|
958
|
-
return {
|
|
959
|
-
type: "file",
|
|
960
|
-
id: addOn.id,
|
|
961
|
-
title: addOn.name,
|
|
962
|
-
mediaType,
|
|
963
|
-
image: addOn.image,
|
|
964
|
-
url,
|
|
965
|
-
embedUrl: `https://lessons.church/embed/addon/${addOn.id}`,
|
|
966
|
-
providerData: {
|
|
967
|
-
seconds,
|
|
968
|
-
loopVideo: video?.loopVideo || false
|
|
969
|
-
}
|
|
970
|
-
};
|
|
1147
|
+
return null;
|
|
1148
|
+
}
|
|
1149
|
+
return { type: "file", id: addOn.id, title: addOn.name, mediaType, image: addOn.image, url, embedUrl: `https://lessons.church/embed/addon/${addOn.id}`, providerData: { seconds, loopVideo: video?.loopVideo || false } };
|
|
971
1150
|
}
|
|
972
|
-
async getPresentations(
|
|
973
|
-
const venueId =
|
|
1151
|
+
async getPresentations(path, _auth) {
|
|
1152
|
+
const venueId = getSegment(path, 4);
|
|
974
1153
|
if (!venueId) return null;
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
const venueData = await this.apiRequest(path);
|
|
1154
|
+
const apiPath = `/venues/public/feed/${venueId}`;
|
|
1155
|
+
const venueData = await this.apiRequest(apiPath);
|
|
978
1156
|
if (!venueData) return null;
|
|
979
1157
|
return this.convertVenueToPlan(venueData);
|
|
980
1158
|
}
|
|
981
|
-
async getInstructions(
|
|
982
|
-
const venueId =
|
|
1159
|
+
async getInstructions(path, _auth) {
|
|
1160
|
+
const venueId = getSegment(path, 4);
|
|
983
1161
|
if (!venueId) return null;
|
|
984
1162
|
const response = await this.apiRequest(`/venues/public/planItems/${venueId}`);
|
|
985
1163
|
if (!response) return null;
|
|
986
|
-
const processItem = (item) =>
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
relatedId: item.relatedId
|
|
990
|
-
label: item.label,
|
|
991
|
-
description: item.description,
|
|
992
|
-
seconds: item.seconds,
|
|
993
|
-
children: item.children?.map(processItem),
|
|
994
|
-
embedUrl: this.getEmbedUrl(item.itemType, item.relatedId)
|
|
995
|
-
});
|
|
996
|
-
return {
|
|
997
|
-
venueName: response.venueName,
|
|
998
|
-
items: (response.items || []).map(processItem)
|
|
1164
|
+
const processItem = (item) => {
|
|
1165
|
+
const itemType = this.normalizeItemType(item.itemType);
|
|
1166
|
+
const relatedId = item.relatedId;
|
|
1167
|
+
return { id: item.id, itemType, relatedId, label: item.label, description: item.description, seconds: item.seconds, children: item.children?.map(processItem), embedUrl: this.getEmbedUrl(itemType, relatedId) };
|
|
999
1168
|
};
|
|
1169
|
+
return { venueName: response.venueName, items: (response.items || []).map(processItem) };
|
|
1000
1170
|
}
|
|
1001
|
-
async getExpandedInstructions(
|
|
1002
|
-
const venueId =
|
|
1171
|
+
async getExpandedInstructions(path, _auth) {
|
|
1172
|
+
const venueId = getSegment(path, 4);
|
|
1003
1173
|
if (!venueId) return null;
|
|
1004
1174
|
const [planItemsResponse, actionsResponse] = await Promise.all([
|
|
1005
1175
|
this.apiRequest(`/venues/public/planItems/${venueId}`),
|
|
@@ -1010,66 +1180,47 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1010
1180
|
if (actionsResponse?.sections) {
|
|
1011
1181
|
for (const section of actionsResponse.sections) {
|
|
1012
1182
|
if (section.id && section.actions) {
|
|
1013
|
-
sectionActionsMap.set(section.id, section.actions.map((action) =>
|
|
1014
|
-
|
|
1015
|
-
itemType: "
|
|
1016
|
-
|
|
1017
|
-
label: action.name,
|
|
1018
|
-
description: action.actionType,
|
|
1019
|
-
seconds: action.seconds,
|
|
1020
|
-
embedUrl: this.getEmbedUrl("lessonAction", action.id)
|
|
1021
|
-
})));
|
|
1183
|
+
sectionActionsMap.set(section.id, section.actions.map((action) => {
|
|
1184
|
+
const embedUrl = this.getEmbedUrl("action", action.id);
|
|
1185
|
+
return { id: action.id, itemType: "action", relatedId: action.id, label: action.name, description: action.actionType, seconds: action.seconds, children: [{ id: action.id + "-file", itemType: "file", label: action.name, seconds: action.seconds, embedUrl }] };
|
|
1186
|
+
}));
|
|
1022
1187
|
}
|
|
1023
1188
|
}
|
|
1024
1189
|
}
|
|
1025
1190
|
const processItem = (item) => {
|
|
1026
1191
|
const relatedId = item.relatedId;
|
|
1027
|
-
const itemType = item.itemType;
|
|
1192
|
+
const itemType = this.normalizeItemType(item.itemType);
|
|
1028
1193
|
const children = item.children;
|
|
1029
1194
|
let processedChildren;
|
|
1030
1195
|
if (children) {
|
|
1031
1196
|
processedChildren = children.map((child) => {
|
|
1032
1197
|
const childRelatedId = child.relatedId;
|
|
1198
|
+
const childItemType = this.normalizeItemType(child.itemType);
|
|
1033
1199
|
if (childRelatedId && sectionActionsMap.has(childRelatedId)) {
|
|
1034
|
-
return {
|
|
1035
|
-
id: child.id,
|
|
1036
|
-
itemType: child.itemType,
|
|
1037
|
-
relatedId: childRelatedId,
|
|
1038
|
-
label: child.label,
|
|
1039
|
-
description: child.description,
|
|
1040
|
-
seconds: child.seconds,
|
|
1041
|
-
children: sectionActionsMap.get(childRelatedId),
|
|
1042
|
-
embedUrl: this.getEmbedUrl(child.itemType, childRelatedId)
|
|
1043
|
-
};
|
|
1200
|
+
return { id: child.id, itemType: childItemType, relatedId: childRelatedId, label: child.label, description: child.description, seconds: child.seconds, children: sectionActionsMap.get(childRelatedId), embedUrl: this.getEmbedUrl(childItemType, childRelatedId) };
|
|
1044
1201
|
}
|
|
1045
1202
|
return processItem(child);
|
|
1046
1203
|
});
|
|
1047
1204
|
}
|
|
1048
|
-
return {
|
|
1049
|
-
id: item.id,
|
|
1050
|
-
itemType,
|
|
1051
|
-
relatedId,
|
|
1052
|
-
label: item.label,
|
|
1053
|
-
description: item.description,
|
|
1054
|
-
seconds: item.seconds,
|
|
1055
|
-
children: processedChildren,
|
|
1056
|
-
embedUrl: this.getEmbedUrl(itemType, relatedId)
|
|
1057
|
-
};
|
|
1058
|
-
};
|
|
1059
|
-
return {
|
|
1060
|
-
venueName: planItemsResponse.venueName,
|
|
1061
|
-
items: (planItemsResponse.items || []).map(processItem)
|
|
1205
|
+
return { id: item.id, itemType, relatedId, label: item.label, description: item.description, seconds: item.seconds, children: processedChildren, embedUrl: this.getEmbedUrl(itemType, relatedId) };
|
|
1062
1206
|
};
|
|
1207
|
+
return { venueName: planItemsResponse.venueName, items: (planItemsResponse.items || []).map(processItem) };
|
|
1208
|
+
}
|
|
1209
|
+
normalizeItemType(type) {
|
|
1210
|
+
if (type === "lessonSection") return "section";
|
|
1211
|
+
if (type === "lessonAction") return "action";
|
|
1212
|
+
if (type === "lessonAddOn") return "addon";
|
|
1213
|
+
return type;
|
|
1063
1214
|
}
|
|
1064
1215
|
getEmbedUrl(itemType, relatedId) {
|
|
1065
1216
|
if (!relatedId) return void 0;
|
|
1066
1217
|
const baseUrl = "https://lessons.church";
|
|
1067
1218
|
switch (itemType) {
|
|
1068
|
-
case "
|
|
1219
|
+
case "action":
|
|
1069
1220
|
return `${baseUrl}/embed/action/${relatedId}`;
|
|
1070
|
-
case "
|
|
1221
|
+
case "addon":
|
|
1071
1222
|
return `${baseUrl}/embed/addon/${relatedId}`;
|
|
1072
|
-
case "
|
|
1223
|
+
case "section":
|
|
1073
1224
|
return `${baseUrl}/embed/section/${relatedId}`;
|
|
1074
1225
|
default:
|
|
1075
1226
|
return void 0;
|
|
@@ -1087,16 +1238,7 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1087
1238
|
for (const file of action.files || []) {
|
|
1088
1239
|
if (!file.url) continue;
|
|
1089
1240
|
const embedUrl = action.id ? `https://lessons.church/embed/action/${action.id}` : void 0;
|
|
1090
|
-
const contentFile = {
|
|
1091
|
-
type: "file",
|
|
1092
|
-
id: file.id || "",
|
|
1093
|
-
title: file.name || "",
|
|
1094
|
-
mediaType: detectMediaType(file.url, file.fileType),
|
|
1095
|
-
image: venue.lessonImage,
|
|
1096
|
-
url: file.url,
|
|
1097
|
-
embedUrl,
|
|
1098
|
-
providerData: { seconds: file.seconds, streamUrl: file.streamUrl }
|
|
1099
|
-
};
|
|
1241
|
+
const contentFile = { type: "file", id: file.id || "", title: file.name || "", mediaType: detectMediaType(file.url, file.fileType), image: venue.lessonImage, url: file.url, embedUrl, providerData: { seconds: file.seconds, streamUrl: file.streamUrl } };
|
|
1100
1242
|
files.push(contentFile);
|
|
1101
1243
|
allFiles.push(contentFile);
|
|
1102
1244
|
}
|
|
@@ -1108,51 +1250,69 @@ var LessonsChurchProvider = class extends ContentProvider {
|
|
|
1108
1250
|
sections.push({ id: section.id || "", name: section.name || "Untitled Section", presentations });
|
|
1109
1251
|
}
|
|
1110
1252
|
}
|
|
1111
|
-
return {
|
|
1112
|
-
id: venue.id || "",
|
|
1113
|
-
name: venue.lessonName || venue.name || "Plan",
|
|
1114
|
-
description: venue.lessonDescription,
|
|
1115
|
-
image: venue.lessonImage,
|
|
1116
|
-
sections,
|
|
1117
|
-
allFiles
|
|
1118
|
-
};
|
|
1253
|
+
return { id: venue.id || "", name: venue.lessonName || venue.name || "Plan", description: venue.lessonDescription, image: venue.lessonImage, sections, allFiles };
|
|
1119
1254
|
}
|
|
1120
1255
|
};
|
|
1121
1256
|
|
|
1122
|
-
// src/providers/
|
|
1123
|
-
function
|
|
1257
|
+
// src/providers/b1Church/auth.ts
|
|
1258
|
+
async function generateCodeChallenge(verifier) {
|
|
1259
|
+
const encoder = new TextEncoder();
|
|
1260
|
+
const data = encoder.encode(verifier);
|
|
1261
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
1262
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
1263
|
+
let binary = "";
|
|
1264
|
+
for (let i = 0; i < hashArray.length; i++) {
|
|
1265
|
+
binary += String.fromCharCode(hashArray[i]);
|
|
1266
|
+
}
|
|
1267
|
+
const base64 = btoa(binary);
|
|
1268
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
1269
|
+
}
|
|
1270
|
+
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
1271
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
1124
1272
|
const oauthParams = new URLSearchParams({
|
|
1273
|
+
response_type: "code",
|
|
1125
1274
|
client_id: config.clientId,
|
|
1126
1275
|
redirect_uri: redirectUri,
|
|
1127
|
-
|
|
1128
|
-
|
|
1276
|
+
scope: config.scopes.join(" "),
|
|
1277
|
+
code_challenge: codeChallenge,
|
|
1278
|
+
code_challenge_method: "S256",
|
|
1279
|
+
state: state || ""
|
|
1129
1280
|
});
|
|
1130
|
-
|
|
1131
|
-
|
|
1281
|
+
const url = `${appBase}/oauth?${oauthParams.toString()}`;
|
|
1282
|
+
return { url, challengeMethod: "S256" };
|
|
1283
|
+
}
|
|
1284
|
+
async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVerifier) {
|
|
1285
|
+
try {
|
|
1286
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, code_verifier: codeVerifier, redirect_uri: redirectUri };
|
|
1287
|
+
const tokenUrl = `${config.oauthBase}/token`;
|
|
1288
|
+
console.log(`B1Church PKCE token exchange request to: ${tokenUrl}`);
|
|
1289
|
+
console.log(` - client_id: ${config.clientId}`);
|
|
1290
|
+
console.log(` - redirect_uri: ${redirectUri}`);
|
|
1291
|
+
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
1292
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1293
|
+
console.log(`B1Church token response status: ${response.status}`);
|
|
1294
|
+
if (!response.ok) {
|
|
1295
|
+
const errorText = await response.text();
|
|
1296
|
+
console.error(`B1Church token exchange failed: ${response.status} - ${errorText}`);
|
|
1297
|
+
return null;
|
|
1298
|
+
}
|
|
1299
|
+
const data = await response.json();
|
|
1300
|
+
console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
|
|
1301
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
1302
|
+
} catch (error) {
|
|
1303
|
+
console.error("B1Church token exchange error:", error);
|
|
1304
|
+
return null;
|
|
1132
1305
|
}
|
|
1133
|
-
const returnUrl = `/oauth?${oauthParams.toString()}`;
|
|
1134
|
-
const url = `${appBase}/login?returnUrl=${encodeURIComponent(returnUrl)}`;
|
|
1135
|
-
return { url, challengeMethod: "none" };
|
|
1136
1306
|
}
|
|
1137
1307
|
async function exchangeCodeForTokensWithSecret(config, code, redirectUri, clientSecret) {
|
|
1138
1308
|
try {
|
|
1139
|
-
const params = {
|
|
1140
|
-
grant_type: "authorization_code",
|
|
1141
|
-
code,
|
|
1142
|
-
client_id: config.clientId,
|
|
1143
|
-
client_secret: clientSecret,
|
|
1144
|
-
redirect_uri: redirectUri
|
|
1145
|
-
};
|
|
1309
|
+
const params = { grant_type: "authorization_code", code, client_id: config.clientId, client_secret: clientSecret, redirect_uri: redirectUri };
|
|
1146
1310
|
const tokenUrl = `${config.oauthBase}/token`;
|
|
1147
1311
|
console.log(`B1Church token exchange request to: ${tokenUrl}`);
|
|
1148
1312
|
console.log(` - client_id: ${config.clientId}`);
|
|
1149
1313
|
console.log(` - redirect_uri: ${redirectUri}`);
|
|
1150
1314
|
console.log(` - code: ${code.substring(0, 10)}...`);
|
|
1151
|
-
const response = await fetch(tokenUrl, {
|
|
1152
|
-
method: "POST",
|
|
1153
|
-
headers: { "Content-Type": "application/json" },
|
|
1154
|
-
body: JSON.stringify(params)
|
|
1155
|
-
});
|
|
1315
|
+
const response = await fetch(tokenUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1156
1316
|
console.log(`B1Church token response status: ${response.status}`);
|
|
1157
1317
|
if (!response.ok) {
|
|
1158
1318
|
const errorText = await response.text();
|
|
@@ -1161,14 +1321,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
|
|
|
1161
1321
|
}
|
|
1162
1322
|
const data = await response.json();
|
|
1163
1323
|
console.log(`B1Church token exchange successful, got access_token: ${!!data.access_token}`);
|
|
1164
|
-
return {
|
|
1165
|
-
access_token: data.access_token,
|
|
1166
|
-
refresh_token: data.refresh_token,
|
|
1167
|
-
token_type: data.token_type || "Bearer",
|
|
1168
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1169
|
-
expires_in: data.expires_in,
|
|
1170
|
-
scope: data.scope || config.scopes.join(" ")
|
|
1171
|
-
};
|
|
1324
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
1172
1325
|
} catch (error) {
|
|
1173
1326
|
console.error("B1Church token exchange error:", error);
|
|
1174
1327
|
return null;
|
|
@@ -1177,27 +1330,11 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
|
|
|
1177
1330
|
async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
1178
1331
|
if (!auth.refresh_token) return null;
|
|
1179
1332
|
try {
|
|
1180
|
-
const params = {
|
|
1181
|
-
|
|
1182
|
-
refresh_token: auth.refresh_token,
|
|
1183
|
-
client_id: config.clientId,
|
|
1184
|
-
client_secret: clientSecret
|
|
1185
|
-
};
|
|
1186
|
-
const response = await fetch(`${config.oauthBase}/token`, {
|
|
1187
|
-
method: "POST",
|
|
1188
|
-
headers: { "Content-Type": "application/json" },
|
|
1189
|
-
body: JSON.stringify(params)
|
|
1190
|
-
});
|
|
1333
|
+
const params = { grant_type: "refresh_token", refresh_token: auth.refresh_token, client_id: config.clientId, client_secret: clientSecret };
|
|
1334
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1191
1335
|
if (!response.ok) return null;
|
|
1192
1336
|
const data = await response.json();
|
|
1193
|
-
return {
|
|
1194
|
-
access_token: data.access_token,
|
|
1195
|
-
refresh_token: data.refresh_token || auth.refresh_token,
|
|
1196
|
-
token_type: data.token_type || "Bearer",
|
|
1197
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1198
|
-
expires_in: data.expires_in,
|
|
1199
|
-
scope: data.scope || auth.scope
|
|
1200
|
-
};
|
|
1337
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token || auth.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || auth.scope };
|
|
1201
1338
|
} catch {
|
|
1202
1339
|
return null;
|
|
1203
1340
|
}
|
|
@@ -1205,14 +1342,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
|
1205
1342
|
async function initiateDeviceFlow(config) {
|
|
1206
1343
|
if (!config.supportsDeviceFlow || !config.deviceAuthEndpoint) return null;
|
|
1207
1344
|
try {
|
|
1208
|
-
const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, {
|
|
1209
|
-
method: "POST",
|
|
1210
|
-
headers: { "Content-Type": "application/json" },
|
|
1211
|
-
body: JSON.stringify({
|
|
1212
|
-
client_id: config.clientId,
|
|
1213
|
-
scope: config.scopes.join(" ")
|
|
1214
|
-
})
|
|
1215
|
-
});
|
|
1345
|
+
const response = await fetch(`${config.oauthBase}${config.deviceAuthEndpoint}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ client_id: config.clientId, scope: config.scopes.join(" ") }) });
|
|
1216
1346
|
if (!response.ok) {
|
|
1217
1347
|
const errorText = await response.text();
|
|
1218
1348
|
console.error(`B1Church device authorize failed: ${response.status} - ${errorText}`);
|
|
@@ -1226,25 +1356,10 @@ async function initiateDeviceFlow(config) {
|
|
|
1226
1356
|
}
|
|
1227
1357
|
async function pollDeviceFlowToken(config, deviceCode) {
|
|
1228
1358
|
try {
|
|
1229
|
-
const response = await fetch(`${config.oauthBase}/token`, {
|
|
1230
|
-
method: "POST",
|
|
1231
|
-
headers: { "Content-Type": "application/json" },
|
|
1232
|
-
body: JSON.stringify({
|
|
1233
|
-
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
1234
|
-
device_code: deviceCode,
|
|
1235
|
-
client_id: config.clientId
|
|
1236
|
-
})
|
|
1237
|
-
});
|
|
1359
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ grant_type: "urn:ietf:params:oauth:grant-type:device_code", device_code: deviceCode, client_id: config.clientId }) });
|
|
1238
1360
|
if (response.ok) {
|
|
1239
1361
|
const data = await response.json();
|
|
1240
|
-
return {
|
|
1241
|
-
access_token: data.access_token,
|
|
1242
|
-
refresh_token: data.refresh_token,
|
|
1243
|
-
token_type: data.token_type || "Bearer",
|
|
1244
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1245
|
-
expires_in: data.expires_in,
|
|
1246
|
-
scope: data.scope || config.scopes.join(" ")
|
|
1247
|
-
};
|
|
1362
|
+
return { access_token: data.access_token, refresh_token: data.refresh_token, token_type: data.token_type || "Bearer", created_at: Math.floor(Date.now() / 1e3), expires_in: data.expires_in, scope: data.scope || config.scopes.join(" ") };
|
|
1248
1363
|
}
|
|
1249
1364
|
const errorData = await response.json();
|
|
1250
1365
|
switch (errorData.error) {
|
|
@@ -1264,7 +1379,7 @@ async function pollDeviceFlowToken(config, deviceCode) {
|
|
|
1264
1379
|
}
|
|
1265
1380
|
}
|
|
1266
1381
|
|
|
1267
|
-
// src/providers/
|
|
1382
|
+
// src/providers/b1Church/api.ts
|
|
1268
1383
|
var API_BASE = "https://api.churchapps.org";
|
|
1269
1384
|
var LESSONS_API_BASE = "https://api.lessons.church";
|
|
1270
1385
|
var CONTENT_API_BASE = "https://contentapi.churchapps.org";
|
|
@@ -1296,10 +1411,7 @@ async function fetchPlans(planTypeId, auth) {
|
|
|
1296
1411
|
async function fetchVenueFeed(venueId) {
|
|
1297
1412
|
try {
|
|
1298
1413
|
const url = `${LESSONS_API_BASE}/venues/public/feed/${venueId}`;
|
|
1299
|
-
const response = await fetch(url, {
|
|
1300
|
-
method: "GET",
|
|
1301
|
-
headers: { Accept: "application/json" }
|
|
1302
|
-
});
|
|
1414
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1303
1415
|
if (!response.ok) return null;
|
|
1304
1416
|
return await response.json();
|
|
1305
1417
|
} catch {
|
|
@@ -1309,9 +1421,29 @@ async function fetchVenueFeed(venueId) {
|
|
|
1309
1421
|
async function fetchArrangementKey(churchId, arrangementId) {
|
|
1310
1422
|
try {
|
|
1311
1423
|
const url = `${CONTENT_API_BASE}/arrangementKeys/presenter/${churchId}/${arrangementId}`;
|
|
1424
|
+
const response = await fetch(url, { method: "GET", headers: { Accept: "application/json" } });
|
|
1425
|
+
if (!response.ok) return null;
|
|
1426
|
+
return await response.json();
|
|
1427
|
+
} catch {
|
|
1428
|
+
return null;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
async function fetchFromProviderProxy(method, ministryId, providerId, path, authData, resolution) {
|
|
1432
|
+
try {
|
|
1433
|
+
const url = `${API_BASE}/doing/providerProxy/${method}`;
|
|
1434
|
+
const headers = {
|
|
1435
|
+
"Content-Type": "application/json",
|
|
1436
|
+
Accept: "application/json"
|
|
1437
|
+
};
|
|
1438
|
+
if (authData) {
|
|
1439
|
+
headers["Authorization"] = `Bearer ${authData.access_token}`;
|
|
1440
|
+
}
|
|
1441
|
+
const body = { ministryId, providerId, path };
|
|
1442
|
+
if (resolution !== void 0) body.resolution = resolution;
|
|
1312
1443
|
const response = await fetch(url, {
|
|
1313
|
-
method: "
|
|
1314
|
-
headers
|
|
1444
|
+
method: "POST",
|
|
1445
|
+
headers,
|
|
1446
|
+
body: JSON.stringify(body)
|
|
1315
1447
|
});
|
|
1316
1448
|
if (!response.ok) return null;
|
|
1317
1449
|
return await response.json();
|
|
@@ -1320,107 +1452,39 @@ async function fetchArrangementKey(churchId, arrangementId) {
|
|
|
1320
1452
|
}
|
|
1321
1453
|
}
|
|
1322
1454
|
|
|
1323
|
-
// src/providers/
|
|
1455
|
+
// src/providers/b1Church/converters.ts
|
|
1324
1456
|
function ministryToFolder(ministry) {
|
|
1325
|
-
return {
|
|
1326
|
-
type: "folder",
|
|
1327
|
-
id: ministry.id,
|
|
1328
|
-
title: ministry.name,
|
|
1329
|
-
image: ministry.photoUrl,
|
|
1330
|
-
providerData: {
|
|
1331
|
-
level: "ministry",
|
|
1332
|
-
ministryId: ministry.id,
|
|
1333
|
-
churchId: ministry.churchId
|
|
1334
|
-
}
|
|
1335
|
-
};
|
|
1457
|
+
return { type: "folder", id: ministry.id, title: ministry.name, path: "", image: ministry.photoUrl, providerData: { level: "ministry", ministryId: ministry.id, churchId: ministry.churchId } };
|
|
1336
1458
|
}
|
|
1337
1459
|
function planTypeToFolder(planType, ministryId) {
|
|
1338
|
-
return {
|
|
1339
|
-
type: "folder",
|
|
1340
|
-
id: planType.id,
|
|
1341
|
-
title: planType.name,
|
|
1342
|
-
providerData: {
|
|
1343
|
-
level: "planType",
|
|
1344
|
-
planTypeId: planType.id,
|
|
1345
|
-
ministryId,
|
|
1346
|
-
churchId: planType.churchId
|
|
1347
|
-
}
|
|
1348
|
-
};
|
|
1460
|
+
return { type: "folder", id: planType.id, title: planType.name, path: "", providerData: { level: "planType", planTypeId: planType.id, ministryId, churchId: planType.churchId } };
|
|
1349
1461
|
}
|
|
1350
1462
|
function planToFolder(plan) {
|
|
1351
|
-
return {
|
|
1352
|
-
type: "folder",
|
|
1353
|
-
id: plan.id,
|
|
1354
|
-
title: plan.name,
|
|
1355
|
-
providerData: {
|
|
1356
|
-
isLeaf: true,
|
|
1357
|
-
level: "plan",
|
|
1358
|
-
planId: plan.id,
|
|
1359
|
-
planTypeId: plan.planTypeId,
|
|
1360
|
-
ministryId: plan.ministryId,
|
|
1361
|
-
churchId: plan.churchId,
|
|
1362
|
-
serviceDate: plan.serviceDate,
|
|
1363
|
-
contentType: plan.contentType,
|
|
1364
|
-
contentId: plan.contentId
|
|
1365
|
-
}
|
|
1366
|
-
};
|
|
1463
|
+
return { type: "folder", id: plan.id, title: plan.name, path: "", isLeaf: true, providerData: { level: "plan", planId: plan.id, planTypeId: plan.planTypeId, ministryId: plan.ministryId, churchId: plan.churchId, serviceDate: plan.serviceDate, contentType: plan.contentType, contentId: plan.contentId } };
|
|
1367
1464
|
}
|
|
1368
1465
|
async function planItemToPresentation(item, venueFeed) {
|
|
1369
1466
|
const itemType = item.itemType;
|
|
1370
1467
|
if (itemType === "arrangementKey" && item.churchId && item.relatedId) {
|
|
1371
1468
|
const songData = await fetchArrangementKey(item.churchId, item.relatedId);
|
|
1372
|
-
if (songData)
|
|
1373
|
-
return arrangementToPresentation(item, songData);
|
|
1374
|
-
}
|
|
1469
|
+
if (songData) return arrangementToPresentation(item, songData);
|
|
1375
1470
|
}
|
|
1376
|
-
if ((itemType === "lessonSection" || itemType === "lessonAction" || itemType === "lessonAddOn") && venueFeed) {
|
|
1471
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1377
1472
|
const files = getFilesFromVenueFeed(venueFeed, itemType, item.relatedId);
|
|
1378
|
-
if (files.length > 0) {
|
|
1379
|
-
return {
|
|
1380
|
-
id: item.id,
|
|
1381
|
-
name: item.label || "Lesson Content",
|
|
1382
|
-
actionType: itemType === "lessonAddOn" ? "add-on" : "play",
|
|
1383
|
-
files
|
|
1384
|
-
};
|
|
1385
|
-
}
|
|
1473
|
+
if (files.length > 0) return { id: item.id, name: item.label || "Lesson Content", actionType: itemType === "lessonAddOn" || itemType === "addon" ? "add-on" : "play", files };
|
|
1386
1474
|
}
|
|
1387
1475
|
if (itemType === "item" || itemType === "header") {
|
|
1388
|
-
return {
|
|
1389
|
-
id: item.id,
|
|
1390
|
-
name: item.label || "",
|
|
1391
|
-
actionType: "other",
|
|
1392
|
-
files: [],
|
|
1393
|
-
providerData: {
|
|
1394
|
-
itemType,
|
|
1395
|
-
description: item.description,
|
|
1396
|
-
seconds: item.seconds
|
|
1397
|
-
}
|
|
1398
|
-
};
|
|
1476
|
+
return { id: item.id, name: item.label || "", actionType: "other", files: [], providerData: { itemType, description: item.description, seconds: item.seconds } };
|
|
1399
1477
|
}
|
|
1400
1478
|
return null;
|
|
1401
1479
|
}
|
|
1402
1480
|
function arrangementToPresentation(item, songData) {
|
|
1403
1481
|
const title = songData.songDetail?.title || item.label || "Song";
|
|
1404
|
-
return {
|
|
1405
|
-
id: item.id,
|
|
1406
|
-
name: title,
|
|
1407
|
-
actionType: "other",
|
|
1408
|
-
files: [],
|
|
1409
|
-
providerData: {
|
|
1410
|
-
itemType: "song",
|
|
1411
|
-
title,
|
|
1412
|
-
artist: songData.songDetail?.artist,
|
|
1413
|
-
lyrics: songData.arrangement?.lyrics,
|
|
1414
|
-
keySignature: songData.arrangementKey?.keySignature,
|
|
1415
|
-
arrangementName: songData.arrangement?.name,
|
|
1416
|
-
seconds: songData.songDetail?.seconds || item.seconds
|
|
1417
|
-
}
|
|
1418
|
-
};
|
|
1482
|
+
return { id: item.id, name: title, actionType: "other", files: [], providerData: { itemType: "song", title, artist: songData.songDetail?.artist, lyrics: songData.arrangement?.lyrics, keySignature: songData.arrangementKey?.keySignature, arrangementName: songData.arrangement?.name, seconds: songData.songDetail?.seconds || item.seconds } };
|
|
1419
1483
|
}
|
|
1420
1484
|
function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
1421
1485
|
const files = [];
|
|
1422
1486
|
if (!relatedId) return files;
|
|
1423
|
-
if (itemType === "lessonSection") {
|
|
1487
|
+
if (itemType === "lessonSection" || itemType === "section") {
|
|
1424
1488
|
for (const section of venueFeed.sections || []) {
|
|
1425
1489
|
if (section.id === relatedId) {
|
|
1426
1490
|
for (const action of section.actions || []) {
|
|
@@ -1432,7 +1496,7 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1432
1496
|
break;
|
|
1433
1497
|
}
|
|
1434
1498
|
}
|
|
1435
|
-
} else if (itemType === "lessonAction") {
|
|
1499
|
+
} else if (itemType === "lessonAction" || itemType === "action") {
|
|
1436
1500
|
for (const section of venueFeed.sections || []) {
|
|
1437
1501
|
for (const action of section.actions || []) {
|
|
1438
1502
|
if (action.id === relatedId) {
|
|
@@ -1445,75 +1509,51 @@ function getFilesFromVenueFeed(venueFeed, itemType, relatedId) {
|
|
|
1445
1509
|
return files;
|
|
1446
1510
|
}
|
|
1447
1511
|
function convertFeedFiles(feedFiles, thumbnailImage) {
|
|
1448
|
-
return feedFiles.filter((f) => f.url).map((f) => ({
|
|
1449
|
-
type: "file",
|
|
1450
|
-
id: f.id || "",
|
|
1451
|
-
title: f.name || "",
|
|
1452
|
-
mediaType: detectMediaType(f.url || "", f.fileType),
|
|
1453
|
-
image: thumbnailImage,
|
|
1454
|
-
url: f.url || "",
|
|
1455
|
-
providerData: { seconds: f.seconds, streamUrl: f.streamUrl }
|
|
1456
|
-
}));
|
|
1512
|
+
return feedFiles.filter((f) => f.url).map((f) => ({ type: "file", id: f.id || "", title: f.name || "", mediaType: detectMediaType(f.url || "", f.fileType), image: thumbnailImage, url: f.url || "", providerData: { seconds: f.seconds, streamUrl: f.streamUrl } }));
|
|
1457
1513
|
}
|
|
1458
1514
|
function planItemToInstruction(item) {
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1515
|
+
let itemType = item.itemType;
|
|
1516
|
+
switch (item.itemType) {
|
|
1517
|
+
case "lessonSection":
|
|
1518
|
+
itemType = "section";
|
|
1519
|
+
break;
|
|
1520
|
+
case "lessonAction":
|
|
1521
|
+
itemType = "action";
|
|
1522
|
+
break;
|
|
1523
|
+
case "lessonAddOn":
|
|
1524
|
+
itemType = "addon";
|
|
1525
|
+
break;
|
|
1526
|
+
}
|
|
1527
|
+
return { id: item.id, itemType, relatedId: item.relatedId, label: item.label, description: item.description, seconds: item.seconds, children: item.children?.map(planItemToInstruction) };
|
|
1468
1528
|
}
|
|
1469
1529
|
|
|
1470
|
-
// src/providers/
|
|
1471
|
-
var
|
|
1530
|
+
// src/providers/b1Church/B1ChurchProvider.ts
|
|
1531
|
+
var INTERNAL_PROVIDERS = ["b1church", "lessonschurch"];
|
|
1532
|
+
function isExternalProviderItem(item) {
|
|
1533
|
+
if (!item.providerId || INTERNAL_PROVIDERS.includes(item.providerId)) return false;
|
|
1534
|
+
const itemType = item.itemType || "";
|
|
1535
|
+
return itemType.startsWith("provider");
|
|
1536
|
+
}
|
|
1537
|
+
var B1ChurchProvider = class {
|
|
1472
1538
|
constructor() {
|
|
1473
|
-
|
|
1539
|
+
this.apiHelper = new ApiHelper();
|
|
1474
1540
|
this.id = "b1church";
|
|
1475
1541
|
this.name = "B1.Church";
|
|
1476
|
-
this.logos = {
|
|
1477
|
-
|
|
1478
|
-
dark: "https://b1.church/b1-church-logo.png"
|
|
1479
|
-
};
|
|
1480
|
-
this.config = {
|
|
1481
|
-
id: "b1church",
|
|
1482
|
-
name: "B1.Church",
|
|
1483
|
-
apiBase: `${API_BASE}/doing`,
|
|
1484
|
-
oauthBase: `${API_BASE}/membership/oauth`,
|
|
1485
|
-
clientId: "",
|
|
1486
|
-
// Consumer must provide client_id
|
|
1487
|
-
scopes: ["plans"],
|
|
1488
|
-
supportsDeviceFlow: true,
|
|
1489
|
-
deviceAuthEndpoint: "/device/authorize",
|
|
1490
|
-
endpoints: {
|
|
1491
|
-
planItems: (churchId, planId) => `/planItems/presenter/${churchId}/${planId}`
|
|
1492
|
-
}
|
|
1493
|
-
};
|
|
1542
|
+
this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
|
|
1543
|
+
this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE}/doing`, oauthBase: `${API_BASE}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize", endpoints: { planItems: (churchId, planId) => `/planItems/presenter/${churchId}/${planId}` } };
|
|
1494
1544
|
this.appBase = "https://admin.b1.church";
|
|
1545
|
+
this.requiresAuth = true;
|
|
1546
|
+
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
1547
|
+
this.capabilities = { browse: true, presentations: true, playlist: true, instructions: true, expandedInstructions: true, mediaLicensing: false };
|
|
1495
1548
|
}
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
// ============================================================
|
|
1499
|
-
requiresAuth() {
|
|
1500
|
-
return true;
|
|
1549
|
+
async apiRequest(path, authData) {
|
|
1550
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, authData);
|
|
1501
1551
|
}
|
|
1502
|
-
|
|
1503
|
-
return
|
|
1504
|
-
browse: true,
|
|
1505
|
-
presentations: true,
|
|
1506
|
-
playlist: true,
|
|
1507
|
-
instructions: true,
|
|
1508
|
-
expandedInstructions: true,
|
|
1509
|
-
mediaLicensing: false
|
|
1510
|
-
};
|
|
1552
|
+
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1553
|
+
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1511
1554
|
}
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
// ============================================================
|
|
1515
|
-
async buildAuthUrl(_codeVerifier, redirectUri, state) {
|
|
1516
|
-
return buildB1AuthUrl(this.config, this.appBase, redirectUri, state);
|
|
1555
|
+
async exchangeCodeForTokensWithPKCE(code, redirectUri, codeVerifier) {
|
|
1556
|
+
return exchangeCodeForTokensWithPKCE(this.config, code, redirectUri, codeVerifier);
|
|
1517
1557
|
}
|
|
1518
1558
|
async exchangeCodeForTokensWithSecret(code, redirectUri, clientSecret) {
|
|
1519
1559
|
return exchangeCodeForTokensWithSecret(this.config, code, redirectUri, clientSecret);
|
|
@@ -1527,48 +1567,69 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1527
1567
|
async pollDeviceFlowToken(deviceCode) {
|
|
1528
1568
|
return pollDeviceFlowToken(this.config, deviceCode);
|
|
1529
1569
|
}
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
if (!folder) {
|
|
1570
|
+
async browse(path, authData) {
|
|
1571
|
+
const { segments, depth } = parsePath(path);
|
|
1572
|
+
if (depth === 0) {
|
|
1573
|
+
return [{
|
|
1574
|
+
type: "folder",
|
|
1575
|
+
id: "ministries-root",
|
|
1576
|
+
title: "Ministries",
|
|
1577
|
+
path: "/ministries"
|
|
1578
|
+
}];
|
|
1579
|
+
}
|
|
1580
|
+
const root = segments[0];
|
|
1581
|
+
if (root !== "ministries") return [];
|
|
1582
|
+
if (depth === 1) {
|
|
1544
1583
|
const ministries = await fetchMinistries(authData);
|
|
1545
|
-
return ministries.map(
|
|
1584
|
+
return ministries.map((m) => {
|
|
1585
|
+
const folder = ministryToFolder(m);
|
|
1586
|
+
const ministryId = folder.providerData?.ministryId || folder.id;
|
|
1587
|
+
return { ...folder, path: `/ministries/${ministryId}` };
|
|
1588
|
+
});
|
|
1546
1589
|
}
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
const ministryId = folder.providerData?.ministryId;
|
|
1550
|
-
if (!ministryId) return [];
|
|
1590
|
+
if (depth === 2) {
|
|
1591
|
+
const ministryId = segments[1];
|
|
1551
1592
|
const planTypes = await fetchPlanTypes(ministryId, authData);
|
|
1552
|
-
return planTypes.map((pt) =>
|
|
1593
|
+
return planTypes.map((pt) => {
|
|
1594
|
+
const folder = planTypeToFolder(pt, ministryId);
|
|
1595
|
+
const planTypeId = folder.providerData?.planTypeId || folder.id;
|
|
1596
|
+
return { ...folder, path: `/ministries/${ministryId}/${planTypeId}` };
|
|
1597
|
+
});
|
|
1553
1598
|
}
|
|
1554
|
-
if (
|
|
1555
|
-
const
|
|
1556
|
-
|
|
1599
|
+
if (depth === 3) {
|
|
1600
|
+
const ministryId = segments[1];
|
|
1601
|
+
const planTypeId = segments[2];
|
|
1557
1602
|
const plans = await fetchPlans(planTypeId, authData);
|
|
1558
|
-
return plans.map(
|
|
1603
|
+
return plans.map((p) => {
|
|
1604
|
+
const folder = planToFolder(p);
|
|
1605
|
+
const planId = folder.providerData?.planId || folder.id;
|
|
1606
|
+
return {
|
|
1607
|
+
...folder,
|
|
1608
|
+
isLeaf: true,
|
|
1609
|
+
path: `/ministries/${ministryId}/${planTypeId}/${planId}`
|
|
1610
|
+
};
|
|
1611
|
+
});
|
|
1559
1612
|
}
|
|
1560
1613
|
return [];
|
|
1561
1614
|
}
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
const
|
|
1567
|
-
|
|
1568
|
-
const
|
|
1569
|
-
const
|
|
1570
|
-
|
|
1571
|
-
|
|
1615
|
+
async getPresentations(path, authData) {
|
|
1616
|
+
const { segments, depth } = parsePath(path);
|
|
1617
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1618
|
+
const ministryId = segments[1];
|
|
1619
|
+
const planId = segments[3];
|
|
1620
|
+
const planTypeId = segments[2];
|
|
1621
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1622
|
+
const planFolder = plans.find((p) => {
|
|
1623
|
+
const folder2 = planToFolder(p);
|
|
1624
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1625
|
+
});
|
|
1626
|
+
if (!planFolder) return null;
|
|
1627
|
+
const folder = planToFolder(planFolder);
|
|
1628
|
+
const providerData = folder.providerData;
|
|
1629
|
+
const churchId = providerData?.churchId;
|
|
1630
|
+
const venueId = providerData?.contentId;
|
|
1631
|
+
const planTitle = folder.title || "Plan";
|
|
1632
|
+
if (!churchId) return null;
|
|
1572
1633
|
const pathFn = this.config.endpoints.planItems;
|
|
1573
1634
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1574
1635
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
@@ -1578,49 +1639,101 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1578
1639
|
for (const sectionItem of planItems) {
|
|
1579
1640
|
const presentations = [];
|
|
1580
1641
|
for (const child of sectionItem.children || []) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1642
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1643
|
+
const externalPlan = await fetchFromProviderProxy(
|
|
1644
|
+
"getPresentations",
|
|
1645
|
+
ministryId,
|
|
1646
|
+
child.providerId,
|
|
1647
|
+
child.providerPath,
|
|
1648
|
+
authData
|
|
1649
|
+
);
|
|
1650
|
+
if (externalPlan) {
|
|
1651
|
+
for (const section of externalPlan.sections) {
|
|
1652
|
+
presentations.push(...section.presentations);
|
|
1653
|
+
}
|
|
1654
|
+
allFiles.push(...externalPlan.allFiles);
|
|
1655
|
+
}
|
|
1656
|
+
} else {
|
|
1657
|
+
const presentation = await planItemToPresentation(child, venueFeed);
|
|
1658
|
+
if (presentation) {
|
|
1659
|
+
presentations.push(presentation);
|
|
1660
|
+
allFiles.push(...presentation.files);
|
|
1661
|
+
}
|
|
1585
1662
|
}
|
|
1586
1663
|
}
|
|
1587
1664
|
if (presentations.length > 0 || sectionItem.label) {
|
|
1588
|
-
sections.push({
|
|
1589
|
-
id: sectionItem.id,
|
|
1590
|
-
name: sectionItem.label || "Section",
|
|
1591
|
-
presentations
|
|
1592
|
-
});
|
|
1665
|
+
sections.push({ id: sectionItem.id, name: sectionItem.label || "Section", presentations });
|
|
1593
1666
|
}
|
|
1594
1667
|
}
|
|
1595
|
-
return { id: planId, name:
|
|
1596
|
-
}
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
const
|
|
1602
|
-
|
|
1603
|
-
const
|
|
1604
|
-
const
|
|
1605
|
-
|
|
1668
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
1669
|
+
}
|
|
1670
|
+
async getInstructions(path, authData) {
|
|
1671
|
+
const { segments, depth } = parsePath(path);
|
|
1672
|
+
if (depth < 4 || segments[0] !== "ministries") return null;
|
|
1673
|
+
const ministryId = segments[1];
|
|
1674
|
+
const planId = segments[3];
|
|
1675
|
+
const planTypeId = segments[2];
|
|
1676
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1677
|
+
const planFolder = plans.find((p) => {
|
|
1678
|
+
const folder2 = planToFolder(p);
|
|
1679
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1680
|
+
});
|
|
1681
|
+
if (!planFolder) return null;
|
|
1682
|
+
const folder = planToFolder(planFolder);
|
|
1683
|
+
const providerData = folder.providerData;
|
|
1684
|
+
const churchId = providerData?.churchId;
|
|
1685
|
+
const planTitle = folder.title || "Plan";
|
|
1686
|
+
if (!churchId) return null;
|
|
1606
1687
|
const pathFn = this.config.endpoints.planItems;
|
|
1607
1688
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1608
1689
|
if (!planItems || !Array.isArray(planItems)) return null;
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1690
|
+
const processedItems = await this.processInstructionItems(planItems, ministryId, authData);
|
|
1691
|
+
return { venueName: planTitle, items: processedItems };
|
|
1692
|
+
}
|
|
1693
|
+
async getExpandedInstructions(path, authData) {
|
|
1694
|
+
return this.getInstructions(path, authData);
|
|
1695
|
+
}
|
|
1696
|
+
async processInstructionItems(items, ministryId, authData) {
|
|
1697
|
+
const result = [];
|
|
1698
|
+
for (const item of items) {
|
|
1699
|
+
if (isExternalProviderItem(item) && item.providerId && item.providerPath) {
|
|
1700
|
+
const externalInstructions = await fetchFromProviderProxy(
|
|
1701
|
+
"getExpandedInstructions",
|
|
1702
|
+
ministryId,
|
|
1703
|
+
item.providerId,
|
|
1704
|
+
item.providerPath,
|
|
1705
|
+
authData
|
|
1706
|
+
);
|
|
1707
|
+
if (externalInstructions) {
|
|
1708
|
+
result.push(...externalInstructions.items);
|
|
1709
|
+
}
|
|
1710
|
+
} else {
|
|
1711
|
+
const instructionItem = planItemToInstruction(item);
|
|
1712
|
+
if (item.children && item.children.length > 0) {
|
|
1713
|
+
instructionItem.children = await this.processInstructionItems(item.children, ministryId, authData);
|
|
1714
|
+
}
|
|
1715
|
+
result.push(instructionItem);
|
|
1716
|
+
}
|
|
1717
|
+
}
|
|
1718
|
+
return result;
|
|
1613
1719
|
}
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
const
|
|
1619
|
-
|
|
1620
|
-
const
|
|
1621
|
-
const
|
|
1622
|
-
|
|
1623
|
-
|
|
1720
|
+
async getPlaylist(path, authData, resolution) {
|
|
1721
|
+
const { segments, depth } = parsePath(path);
|
|
1722
|
+
if (depth < 4 || segments[0] !== "ministries") return [];
|
|
1723
|
+
const ministryId = segments[1];
|
|
1724
|
+
const planId = segments[3];
|
|
1725
|
+
const planTypeId = segments[2];
|
|
1726
|
+
const plans = await fetchPlans(planTypeId, authData);
|
|
1727
|
+
const planFolder = plans.find((p) => {
|
|
1728
|
+
const folder2 = planToFolder(p);
|
|
1729
|
+
return folder2.providerData?.planId === planId || folder2.id === planId;
|
|
1730
|
+
});
|
|
1731
|
+
if (!planFolder) return [];
|
|
1732
|
+
const folder = planToFolder(planFolder);
|
|
1733
|
+
const providerData = folder.providerData;
|
|
1734
|
+
const churchId = providerData?.churchId;
|
|
1735
|
+
const venueId = providerData?.contentId;
|
|
1736
|
+
if (!churchId) return [];
|
|
1624
1737
|
const pathFn = this.config.endpoints.planItems;
|
|
1625
1738
|
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1626
1739
|
if (!planItems || !Array.isArray(planItems)) return [];
|
|
@@ -1628,10 +1741,24 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1628
1741
|
const files = [];
|
|
1629
1742
|
for (const sectionItem of planItems) {
|
|
1630
1743
|
for (const child of sectionItem.children || []) {
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1744
|
+
if (isExternalProviderItem(child) && child.providerId && child.providerPath) {
|
|
1745
|
+
const externalFiles = await fetchFromProviderProxy(
|
|
1746
|
+
"getPlaylist",
|
|
1747
|
+
ministryId,
|
|
1748
|
+
child.providerId,
|
|
1749
|
+
child.providerPath,
|
|
1750
|
+
authData,
|
|
1751
|
+
resolution
|
|
1752
|
+
);
|
|
1753
|
+
if (externalFiles) {
|
|
1754
|
+
files.push(...externalFiles);
|
|
1755
|
+
}
|
|
1756
|
+
} else {
|
|
1757
|
+
const itemType = child.itemType;
|
|
1758
|
+
if ((itemType === "lessonSection" || itemType === "section" || itemType === "lessonAction" || itemType === "action" || itemType === "lessonAddOn" || itemType === "addon") && venueFeed) {
|
|
1759
|
+
const itemFiles = getFilesFromVenueFeed(venueFeed, itemType, child.relatedId);
|
|
1760
|
+
files.push(...itemFiles);
|
|
1761
|
+
}
|
|
1635
1762
|
}
|
|
1636
1763
|
}
|
|
1637
1764
|
}
|
|
@@ -1639,81 +1766,62 @@ var B1ChurchProvider = class extends ContentProvider {
|
|
|
1639
1766
|
}
|
|
1640
1767
|
};
|
|
1641
1768
|
|
|
1642
|
-
// src/providers/PlanningCenterProvider.ts
|
|
1643
|
-
var PlanningCenterProvider = class
|
|
1769
|
+
// src/providers/planningCenter/PlanningCenterProvider.ts
|
|
1770
|
+
var PlanningCenterProvider = class {
|
|
1644
1771
|
constructor() {
|
|
1645
|
-
|
|
1772
|
+
this.apiHelper = new ApiHelper();
|
|
1646
1773
|
this.id = "planningcenter";
|
|
1647
1774
|
this.name = "Planning Center";
|
|
1648
|
-
this.logos = {
|
|
1649
|
-
|
|
1650
|
-
dark: "https://www.planningcenter.com/icons/icon-512x512.png"
|
|
1651
|
-
};
|
|
1652
|
-
// Planning Center uses OAuth 2.0 with PKCE (handled by base ContentProvider class)
|
|
1653
|
-
this.config = {
|
|
1654
|
-
id: "planningcenter",
|
|
1655
|
-
name: "Planning Center",
|
|
1656
|
-
apiBase: "https://api.planningcenteronline.com",
|
|
1657
|
-
oauthBase: "https://api.planningcenteronline.com/oauth",
|
|
1658
|
-
clientId: "",
|
|
1659
|
-
// Consumer must provide client_id
|
|
1660
|
-
scopes: ["services"],
|
|
1661
|
-
endpoints: {
|
|
1662
|
-
serviceTypes: "/services/v2/service_types",
|
|
1663
|
-
plans: (serviceTypeId) => `/services/v2/service_types/${serviceTypeId}/plans`,
|
|
1664
|
-
planItems: (serviceTypeId, planId) => `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items`,
|
|
1665
|
-
song: (itemId) => `/services/v2/songs/${itemId}`,
|
|
1666
|
-
arrangement: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}`,
|
|
1667
|
-
arrangementSections: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}/sections`,
|
|
1668
|
-
media: (mediaId) => `/services/v2/media/${mediaId}`,
|
|
1669
|
-
mediaAttachments: (mediaId) => `/services/v2/media/${mediaId}/attachments`
|
|
1670
|
-
}
|
|
1671
|
-
};
|
|
1775
|
+
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
1776
|
+
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"], endpoints: { serviceTypes: "/services/v2/service_types", plans: (serviceTypeId) => `/services/v2/service_types/${serviceTypeId}/plans`, planItems: (serviceTypeId, planId) => `/services/v2/service_types/${serviceTypeId}/plans/${planId}/items`, song: (itemId) => `/services/v2/songs/${itemId}`, arrangement: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}`, arrangementSections: (songId, arrangementId) => `/services/v2/songs/${songId}/arrangements/${arrangementId}/sections`, media: (mediaId) => `/services/v2/media/${mediaId}`, mediaAttachments: (mediaId) => `/services/v2/media/${mediaId}/attachments` } };
|
|
1672
1777
|
this.ONE_WEEK_MS = 6048e5;
|
|
1778
|
+
this.requiresAuth = true;
|
|
1779
|
+
this.authTypes = ["oauth_pkce"];
|
|
1780
|
+
this.capabilities = { browse: true, presentations: true, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false };
|
|
1673
1781
|
}
|
|
1674
|
-
|
|
1675
|
-
return
|
|
1676
|
-
}
|
|
1677
|
-
getCapabilities() {
|
|
1678
|
-
return {
|
|
1679
|
-
browse: true,
|
|
1680
|
-
presentations: true,
|
|
1681
|
-
playlist: false,
|
|
1682
|
-
instructions: false,
|
|
1683
|
-
expandedInstructions: false,
|
|
1684
|
-
mediaLicensing: false
|
|
1685
|
-
};
|
|
1782
|
+
async apiRequest(path, auth) {
|
|
1783
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
1686
1784
|
}
|
|
1687
|
-
async browse(
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
auth
|
|
1692
|
-
);
|
|
1693
|
-
if (!response?.data) return [];
|
|
1694
|
-
return response.data.map((serviceType) => ({
|
|
1785
|
+
async browse(path, auth) {
|
|
1786
|
+
const { segments, depth } = parsePath(path);
|
|
1787
|
+
if (depth === 0) {
|
|
1788
|
+
return [{
|
|
1695
1789
|
type: "folder",
|
|
1696
|
-
id:
|
|
1697
|
-
title:
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
serviceTypeId: serviceType.id
|
|
1701
|
-
}
|
|
1702
|
-
}));
|
|
1790
|
+
id: "serviceTypes-root",
|
|
1791
|
+
title: "Service Types",
|
|
1792
|
+
path: "/serviceTypes"
|
|
1793
|
+
}];
|
|
1703
1794
|
}
|
|
1704
|
-
const
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
case "plan":
|
|
1709
|
-
return this.getPlanItems(folder, auth);
|
|
1710
|
-
default:
|
|
1711
|
-
return [];
|
|
1795
|
+
const root = segments[0];
|
|
1796
|
+
if (root !== "serviceTypes") return [];
|
|
1797
|
+
if (depth === 1) {
|
|
1798
|
+
return this.getServiceTypes(auth);
|
|
1712
1799
|
}
|
|
1800
|
+
if (depth === 2) {
|
|
1801
|
+
const serviceTypeId = segments[1];
|
|
1802
|
+
return this.getPlans(serviceTypeId, path, auth);
|
|
1803
|
+
}
|
|
1804
|
+
if (depth === 3) {
|
|
1805
|
+
const serviceTypeId = segments[1];
|
|
1806
|
+
const planId = segments[2];
|
|
1807
|
+
return this.getPlanItems(serviceTypeId, planId, auth);
|
|
1808
|
+
}
|
|
1809
|
+
return [];
|
|
1810
|
+
}
|
|
1811
|
+
async getServiceTypes(auth) {
|
|
1812
|
+
const response = await this.apiRequest(
|
|
1813
|
+
this.config.endpoints.serviceTypes,
|
|
1814
|
+
auth
|
|
1815
|
+
);
|
|
1816
|
+
if (!response?.data) return [];
|
|
1817
|
+
return response.data.map((serviceType) => ({
|
|
1818
|
+
type: "folder",
|
|
1819
|
+
id: serviceType.id,
|
|
1820
|
+
title: serviceType.attributes.name,
|
|
1821
|
+
path: `/serviceTypes/${serviceType.id}`
|
|
1822
|
+
}));
|
|
1713
1823
|
}
|
|
1714
|
-
async getPlans(
|
|
1715
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
1716
|
-
if (!serviceTypeId) return [];
|
|
1824
|
+
async getPlans(serviceTypeId, currentPath, auth) {
|
|
1717
1825
|
const pathFn = this.config.endpoints.plans;
|
|
1718
1826
|
const response = await this.apiRequest(
|
|
1719
1827
|
`${pathFn(serviceTypeId)}?filter=future&order=sort_date`,
|
|
@@ -1730,73 +1838,46 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1730
1838
|
type: "folder",
|
|
1731
1839
|
id: plan.id,
|
|
1732
1840
|
title: plan.attributes.title || this.formatDate(plan.attributes.sort_date),
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
planId: plan.id,
|
|
1737
|
-
sortDate: plan.attributes.sort_date
|
|
1738
|
-
}
|
|
1841
|
+
isLeaf: true,
|
|
1842
|
+
path: `${currentPath}/${plan.id}`,
|
|
1843
|
+
providerData: { sortDate: plan.attributes.sort_date }
|
|
1739
1844
|
}));
|
|
1740
1845
|
}
|
|
1741
|
-
async getPlanItems(
|
|
1742
|
-
const serviceTypeId = folder.providerData?.serviceTypeId;
|
|
1743
|
-
const planId = folder.providerData?.planId;
|
|
1744
|
-
if (!serviceTypeId || !planId) return [];
|
|
1846
|
+
async getPlanItems(serviceTypeId, planId, auth) {
|
|
1745
1847
|
const pathFn = this.config.endpoints.planItems;
|
|
1746
1848
|
const response = await this.apiRequest(
|
|
1747
1849
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
1748
1850
|
auth
|
|
1749
1851
|
);
|
|
1750
1852
|
if (!response?.data) return [];
|
|
1751
|
-
return response.data.map((item) => ({
|
|
1752
|
-
type: "file",
|
|
1753
|
-
id: item.id,
|
|
1754
|
-
title: item.attributes.title || "",
|
|
1755
|
-
mediaType: "image",
|
|
1756
|
-
url: "",
|
|
1757
|
-
providerData: {
|
|
1758
|
-
itemType: item.attributes.item_type,
|
|
1759
|
-
description: item.attributes.description,
|
|
1760
|
-
length: item.attributes.length,
|
|
1761
|
-
songId: item.relationships?.song?.data?.id,
|
|
1762
|
-
arrangementId: item.relationships?.arrangement?.data?.id
|
|
1763
|
-
}
|
|
1764
|
-
}));
|
|
1853
|
+
return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "", providerData: { itemType: item.attributes.item_type, description: item.attributes.description, length: item.attributes.length, songId: item.relationships?.song?.data?.id, arrangementId: item.relationships?.arrangement?.data?.id } }));
|
|
1765
1854
|
}
|
|
1766
|
-
async getPresentations(
|
|
1767
|
-
const
|
|
1768
|
-
if (
|
|
1769
|
-
const serviceTypeId =
|
|
1770
|
-
const planId =
|
|
1771
|
-
if (!serviceTypeId || !planId) return null;
|
|
1855
|
+
async getPresentations(path, auth) {
|
|
1856
|
+
const { segments, depth } = parsePath(path);
|
|
1857
|
+
if (depth < 3 || segments[0] !== "serviceTypes") return null;
|
|
1858
|
+
const serviceTypeId = segments[1];
|
|
1859
|
+
const planId = segments[2];
|
|
1772
1860
|
const pathFn = this.config.endpoints.planItems;
|
|
1773
1861
|
const response = await this.apiRequest(
|
|
1774
1862
|
`${pathFn(serviceTypeId, planId)}?per_page=100`,
|
|
1775
1863
|
auth
|
|
1776
1864
|
);
|
|
1777
1865
|
if (!response?.data) return null;
|
|
1866
|
+
const plans = await this.getPlans(serviceTypeId, `/serviceTypes/${serviceTypeId}`, auth);
|
|
1867
|
+
const plan = plans.find((p) => p.id === planId);
|
|
1868
|
+
const planTitle = plan?.title || "Plan";
|
|
1778
1869
|
const sections = [];
|
|
1779
1870
|
const allFiles = [];
|
|
1780
1871
|
let currentSection = null;
|
|
1781
1872
|
for (const item of response.data) {
|
|
1782
1873
|
const itemType = item.attributes.item_type;
|
|
1783
1874
|
if (itemType === "header") {
|
|
1784
|
-
if (currentSection && currentSection.presentations.length > 0)
|
|
1785
|
-
|
|
1786
|
-
}
|
|
1787
|
-
currentSection = {
|
|
1788
|
-
id: item.id,
|
|
1789
|
-
name: item.attributes.title || "Section",
|
|
1790
|
-
presentations: []
|
|
1791
|
-
};
|
|
1875
|
+
if (currentSection && currentSection.presentations.length > 0) sections.push(currentSection);
|
|
1876
|
+
currentSection = { id: item.id, name: item.attributes.title || "Section", presentations: [] };
|
|
1792
1877
|
continue;
|
|
1793
1878
|
}
|
|
1794
1879
|
if (!currentSection) {
|
|
1795
|
-
currentSection = {
|
|
1796
|
-
id: `default-${planId}`,
|
|
1797
|
-
name: "Service",
|
|
1798
|
-
presentations: []
|
|
1799
|
-
};
|
|
1880
|
+
currentSection = { id: `default-${planId}`, name: "Service", presentations: [] };
|
|
1800
1881
|
}
|
|
1801
1882
|
const presentation = await this.convertToPresentation(item, auth);
|
|
1802
1883
|
if (presentation) {
|
|
@@ -1807,12 +1888,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1807
1888
|
if (currentSection && currentSection.presentations.length > 0) {
|
|
1808
1889
|
sections.push(currentSection);
|
|
1809
1890
|
}
|
|
1810
|
-
return {
|
|
1811
|
-
id: planId,
|
|
1812
|
-
name: folder.title,
|
|
1813
|
-
sections,
|
|
1814
|
-
allFiles
|
|
1815
|
-
};
|
|
1891
|
+
return { id: planId, name: planTitle, sections, allFiles };
|
|
1816
1892
|
}
|
|
1817
1893
|
async convertToPresentation(item, auth) {
|
|
1818
1894
|
const itemType = item.attributes.item_type;
|
|
@@ -1823,17 +1899,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1823
1899
|
return this.convertMediaToPresentation(item, auth);
|
|
1824
1900
|
}
|
|
1825
1901
|
if (itemType === "item") {
|
|
1826
|
-
return {
|
|
1827
|
-
id: item.id,
|
|
1828
|
-
name: item.attributes.title || "",
|
|
1829
|
-
actionType: "other",
|
|
1830
|
-
files: [],
|
|
1831
|
-
providerData: {
|
|
1832
|
-
itemType: "item",
|
|
1833
|
-
description: item.attributes.description,
|
|
1834
|
-
length: item.attributes.length
|
|
1835
|
-
}
|
|
1836
|
-
};
|
|
1902
|
+
return { id: item.id, name: item.attributes.title || "", actionType: "other", files: [], providerData: { itemType: "item", description: item.attributes.description, length: item.attributes.length } };
|
|
1837
1903
|
}
|
|
1838
1904
|
return null;
|
|
1839
1905
|
}
|
|
@@ -1841,13 +1907,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1841
1907
|
const songId = item.relationships?.song?.data?.id;
|
|
1842
1908
|
const arrangementId = item.relationships?.arrangement?.data?.id;
|
|
1843
1909
|
if (!songId) {
|
|
1844
|
-
return {
|
|
1845
|
-
id: item.id,
|
|
1846
|
-
name: item.attributes.title || "Song",
|
|
1847
|
-
actionType: "other",
|
|
1848
|
-
files: [],
|
|
1849
|
-
providerData: { itemType: "song" }
|
|
1850
|
-
};
|
|
1910
|
+
return { id: item.id, name: item.attributes.title || "Song", actionType: "other", files: [], providerData: { itemType: "song" } };
|
|
1851
1911
|
}
|
|
1852
1912
|
const songFn = this.config.endpoints.song;
|
|
1853
1913
|
const songResponse = await this.apiRequest(songFn(songId), auth);
|
|
@@ -1869,25 +1929,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1869
1929
|
}
|
|
1870
1930
|
const song = songResponse?.data;
|
|
1871
1931
|
const title = song?.attributes?.title || item.attributes.title || "Song";
|
|
1872
|
-
return {
|
|
1873
|
-
id: item.id,
|
|
1874
|
-
name: title,
|
|
1875
|
-
actionType: "other",
|
|
1876
|
-
files: [],
|
|
1877
|
-
providerData: {
|
|
1878
|
-
itemType: "song",
|
|
1879
|
-
title,
|
|
1880
|
-
author: song?.attributes?.author,
|
|
1881
|
-
copyright: song?.attributes?.copyright,
|
|
1882
|
-
ccliNumber: song?.attributes?.ccli_number,
|
|
1883
|
-
arrangementName: arrangement?.attributes?.name,
|
|
1884
|
-
keySignature: arrangement?.attributes?.chord_chart_key,
|
|
1885
|
-
bpm: arrangement?.attributes?.bpm,
|
|
1886
|
-
sequence: arrangement?.attributes?.sequence,
|
|
1887
|
-
sections: sections.map((s) => ({ label: s.label, lyrics: s.lyrics })),
|
|
1888
|
-
length: item.attributes.length
|
|
1889
|
-
}
|
|
1890
|
-
};
|
|
1932
|
+
return { id: item.id, name: title, actionType: "other", files: [], providerData: { itemType: "song", title, author: song?.attributes?.author, copyright: song?.attributes?.copyright, ccliNumber: song?.attributes?.ccli_number, arrangementName: arrangement?.attributes?.name, keySignature: arrangement?.attributes?.chord_chart_key, bpm: arrangement?.attributes?.bpm, sequence: arrangement?.attributes?.sequence, sections: sections.map((s) => ({ label: s.label, lyrics: s.lyrics })), length: item.attributes.length } };
|
|
1891
1933
|
}
|
|
1892
1934
|
async convertMediaToPresentation(item, auth) {
|
|
1893
1935
|
const files = [];
|
|
@@ -1907,25 +1949,10 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1907
1949
|
if (!url) continue;
|
|
1908
1950
|
const contentType = attachment.attributes.content_type;
|
|
1909
1951
|
const explicitType = contentType?.startsWith("video/") ? "video" : void 0;
|
|
1910
|
-
files.push({
|
|
1911
|
-
type: "file",
|
|
1912
|
-
id: attachment.id,
|
|
1913
|
-
title: attachment.attributes.filename,
|
|
1914
|
-
mediaType: detectMediaType(url, explicitType),
|
|
1915
|
-
url
|
|
1916
|
-
});
|
|
1952
|
+
files.push({ type: "file", id: attachment.id, title: attachment.attributes.filename, mediaType: detectMediaType(url, explicitType), url });
|
|
1917
1953
|
}
|
|
1918
1954
|
}
|
|
1919
|
-
return {
|
|
1920
|
-
id: item.id,
|
|
1921
|
-
name: item.attributes.title || "Media",
|
|
1922
|
-
actionType: "play",
|
|
1923
|
-
files,
|
|
1924
|
-
providerData: {
|
|
1925
|
-
itemType: "media",
|
|
1926
|
-
length: item.attributes.length
|
|
1927
|
-
}
|
|
1928
|
-
};
|
|
1955
|
+
return { id: item.id, name: item.attributes.title || "Media", actionType: "play", files, providerData: { itemType: "media", length: item.attributes.length } };
|
|
1929
1956
|
}
|
|
1930
1957
|
formatDate(dateString) {
|
|
1931
1958
|
const date = new Date(dateString);
|
|
@@ -1933,7 +1960,7 @@ var PlanningCenterProvider = class extends ContentProvider {
|
|
|
1933
1960
|
}
|
|
1934
1961
|
};
|
|
1935
1962
|
|
|
1936
|
-
// src/providers/
|
|
1963
|
+
// src/providers/bibleProject/data.json
|
|
1937
1964
|
var data_default = {
|
|
1938
1965
|
collections: [
|
|
1939
1966
|
{
|
|
@@ -4041,10 +4068,9 @@ var data_default = {
|
|
|
4041
4068
|
]
|
|
4042
4069
|
};
|
|
4043
4070
|
|
|
4044
|
-
// src/providers/
|
|
4045
|
-
var BibleProjectProvider = class
|
|
4071
|
+
// src/providers/bibleProject/BibleProjectProvider.ts
|
|
4072
|
+
var BibleProjectProvider = class {
|
|
4046
4073
|
constructor() {
|
|
4047
|
-
super(...arguments);
|
|
4048
4074
|
this.id = "bibleproject";
|
|
4049
4075
|
this.name = "The Bible Project";
|
|
4050
4076
|
this.logos = {
|
|
@@ -4063,74 +4089,109 @@ var BibleProjectProvider = class extends ContentProvider {
|
|
|
4063
4089
|
}
|
|
4064
4090
|
};
|
|
4065
4091
|
this.data = data_default;
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
}
|
|
4070
|
-
getCapabilities() {
|
|
4071
|
-
return {
|
|
4092
|
+
this.requiresAuth = false;
|
|
4093
|
+
this.authTypes = ["none"];
|
|
4094
|
+
this.capabilities = {
|
|
4072
4095
|
browse: true,
|
|
4073
|
-
presentations:
|
|
4074
|
-
playlist:
|
|
4096
|
+
presentations: true,
|
|
4097
|
+
playlist: true,
|
|
4075
4098
|
instructions: false,
|
|
4076
4099
|
expandedInstructions: false,
|
|
4077
4100
|
mediaLicensing: false
|
|
4078
4101
|
};
|
|
4079
4102
|
}
|
|
4080
|
-
async browse(
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
collection.name,
|
|
4085
|
-
collection.image || void 0,
|
|
4086
|
-
{ level: "collection", collectionName: collection.name }
|
|
4087
|
-
));
|
|
4103
|
+
async browse(path, _auth) {
|
|
4104
|
+
const { segments, depth } = parsePath(path);
|
|
4105
|
+
if (depth === 0) {
|
|
4106
|
+
return this.getCollections();
|
|
4088
4107
|
}
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4092
|
-
return this.getLessonFolders(collectionName);
|
|
4108
|
+
if (depth === 1) {
|
|
4109
|
+
const collectionSlug = segments[0];
|
|
4110
|
+
return this.getLessonFolders(collectionSlug, path);
|
|
4093
4111
|
}
|
|
4094
|
-
if (
|
|
4095
|
-
const
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
videoData.id,
|
|
4099
|
-
videoData.title,
|
|
4100
|
-
videoData.videoUrl,
|
|
4101
|
-
{
|
|
4102
|
-
mediaType: "video",
|
|
4103
|
-
muxPlaybackId: videoData.muxPlaybackId
|
|
4104
|
-
}
|
|
4105
|
-
)];
|
|
4106
|
-
}
|
|
4107
|
-
return [];
|
|
4112
|
+
if (depth === 2) {
|
|
4113
|
+
const collectionSlug = segments[0];
|
|
4114
|
+
const videoId = segments[1];
|
|
4115
|
+
return this.getVideoFile(collectionSlug, videoId);
|
|
4108
4116
|
}
|
|
4109
4117
|
return [];
|
|
4110
4118
|
}
|
|
4111
|
-
|
|
4112
|
-
return
|
|
4119
|
+
getCollections() {
|
|
4120
|
+
return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
|
|
4121
|
+
type: "folder",
|
|
4122
|
+
id: this.slugify(collection.name),
|
|
4123
|
+
title: collection.name,
|
|
4124
|
+
image: collection.image || void 0,
|
|
4125
|
+
path: `/${this.slugify(collection.name)}`
|
|
4126
|
+
}));
|
|
4113
4127
|
}
|
|
4114
|
-
getLessonFolders(
|
|
4115
|
-
const collection = this.data.collections.find((c) => c.name ===
|
|
4128
|
+
getLessonFolders(collectionSlug, currentPath) {
|
|
4129
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4116
4130
|
if (!collection) return [];
|
|
4117
|
-
return collection.videos.map((video) =>
|
|
4118
|
-
|
|
4119
|
-
video.
|
|
4120
|
-
video.
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4131
|
+
return collection.videos.map((video) => ({
|
|
4132
|
+
type: "folder",
|
|
4133
|
+
id: video.id,
|
|
4134
|
+
title: video.title,
|
|
4135
|
+
image: video.thumbnailUrl,
|
|
4136
|
+
isLeaf: true,
|
|
4137
|
+
path: `${currentPath}/${video.id}`,
|
|
4138
|
+
providerData: { videoData: video }
|
|
4139
|
+
}));
|
|
4140
|
+
}
|
|
4141
|
+
getVideoFile(collectionSlug, videoId) {
|
|
4142
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4143
|
+
if (!collection) return [];
|
|
4144
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4145
|
+
if (!video) return [];
|
|
4146
|
+
return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId })];
|
|
4147
|
+
}
|
|
4148
|
+
async getPresentations(path, _auth) {
|
|
4149
|
+
const { segments, depth } = parsePath(path);
|
|
4150
|
+
if (depth < 1) return null;
|
|
4151
|
+
const collectionSlug = segments[0];
|
|
4152
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4153
|
+
if (!collection) return null;
|
|
4154
|
+
if (depth === 1) {
|
|
4155
|
+
const allFiles = [];
|
|
4156
|
+
const presentations = collection.videos.map((video) => {
|
|
4157
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4158
|
+
allFiles.push(file);
|
|
4159
|
+
return { id: video.id, name: video.title, actionType: "play", files: [file] };
|
|
4160
|
+
});
|
|
4161
|
+
return { id: this.slugify(collection.name), name: collection.name, image: collection.image || void 0, sections: [{ id: "videos", name: "Videos", presentations }], allFiles };
|
|
4162
|
+
}
|
|
4163
|
+
if (depth === 2) {
|
|
4164
|
+
const videoId = segments[1];
|
|
4165
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4166
|
+
if (!video) return null;
|
|
4167
|
+
const file = { type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId };
|
|
4168
|
+
return { id: video.id, name: video.title, image: video.thumbnailUrl, sections: [{ id: "main", name: "Content", presentations: [{ id: video.id, name: video.title, actionType: "play", files: [file] }] }], allFiles: [file] };
|
|
4169
|
+
}
|
|
4170
|
+
return null;
|
|
4171
|
+
}
|
|
4172
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
4173
|
+
const { segments, depth } = parsePath(path);
|
|
4174
|
+
if (depth < 1) return null;
|
|
4175
|
+
const collectionSlug = segments[0];
|
|
4176
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
4177
|
+
if (!collection) return null;
|
|
4178
|
+
if (depth === 1) {
|
|
4179
|
+
return collection.videos.map((video) => ({ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }));
|
|
4180
|
+
}
|
|
4181
|
+
if (depth === 2) {
|
|
4182
|
+
const videoId = segments[1];
|
|
4183
|
+
const video = collection.videos.find((v) => v.id === videoId);
|
|
4184
|
+
if (!video) return null;
|
|
4185
|
+
return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, image: video.thumbnailUrl, muxPlaybackId: video.muxPlaybackId }];
|
|
4186
|
+
}
|
|
4187
|
+
return null;
|
|
4127
4188
|
}
|
|
4128
4189
|
slugify(text) {
|
|
4129
4190
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
4130
4191
|
}
|
|
4131
4192
|
};
|
|
4132
4193
|
|
|
4133
|
-
// src/providers/
|
|
4194
|
+
// src/providers/highVoltage/data.json
|
|
4134
4195
|
var data_default2 = {
|
|
4135
4196
|
collections: [
|
|
4136
4197
|
{
|
|
@@ -11634,10 +11695,9 @@ var data_default2 = {
|
|
|
11634
11695
|
]
|
|
11635
11696
|
};
|
|
11636
11697
|
|
|
11637
|
-
// src/providers/HighVoltageKidsProvider.ts
|
|
11638
|
-
var HighVoltageKidsProvider = class
|
|
11698
|
+
// src/providers/highVoltage/HighVoltageKidsProvider.ts
|
|
11699
|
+
var HighVoltageKidsProvider = class {
|
|
11639
11700
|
constructor() {
|
|
11640
|
-
super(...arguments);
|
|
11641
11701
|
this.id = "highvoltagekids";
|
|
11642
11702
|
this.name = "High Voltage Kids";
|
|
11643
11703
|
this.logos = {
|
|
@@ -11656,87 +11716,211 @@ var HighVoltageKidsProvider = class extends ContentProvider {
|
|
|
11656
11716
|
}
|
|
11657
11717
|
};
|
|
11658
11718
|
this.data = data_default2;
|
|
11659
|
-
|
|
11660
|
-
|
|
11661
|
-
|
|
11662
|
-
}
|
|
11663
|
-
getCapabilities() {
|
|
11664
|
-
return {
|
|
11719
|
+
this.requiresAuth = false;
|
|
11720
|
+
this.authTypes = ["none"];
|
|
11721
|
+
this.capabilities = {
|
|
11665
11722
|
browse: true,
|
|
11666
|
-
presentations:
|
|
11667
|
-
playlist:
|
|
11723
|
+
presentations: true,
|
|
11724
|
+
playlist: true,
|
|
11668
11725
|
instructions: false,
|
|
11669
|
-
expandedInstructions:
|
|
11726
|
+
expandedInstructions: true,
|
|
11670
11727
|
mediaLicensing: false
|
|
11671
11728
|
};
|
|
11672
11729
|
}
|
|
11673
|
-
async browse(
|
|
11674
|
-
|
|
11675
|
-
|
|
11676
|
-
|
|
11677
|
-
collection.name,
|
|
11678
|
-
void 0,
|
|
11679
|
-
{ level: "collection", collectionName: collection.name }
|
|
11680
|
-
));
|
|
11730
|
+
async browse(path, _auth) {
|
|
11731
|
+
const { segments, depth } = parsePath(path);
|
|
11732
|
+
if (depth === 0) {
|
|
11733
|
+
return this.getCollections();
|
|
11681
11734
|
}
|
|
11682
|
-
|
|
11683
|
-
|
|
11684
|
-
|
|
11685
|
-
return this.getStudyFolders(collectionName);
|
|
11735
|
+
if (depth === 1) {
|
|
11736
|
+
const collectionSlug = segments[0];
|
|
11737
|
+
return this.getStudyFolders(collectionSlug, path);
|
|
11686
11738
|
}
|
|
11687
|
-
if (
|
|
11688
|
-
const
|
|
11689
|
-
|
|
11690
|
-
|
|
11691
|
-
}
|
|
11692
|
-
return [];
|
|
11739
|
+
if (depth === 2) {
|
|
11740
|
+
const collectionSlug = segments[0];
|
|
11741
|
+
const studyId = segments[1];
|
|
11742
|
+
return this.getLessonFolders(collectionSlug, studyId, path);
|
|
11693
11743
|
}
|
|
11694
|
-
if (
|
|
11695
|
-
const
|
|
11696
|
-
|
|
11697
|
-
|
|
11698
|
-
|
|
11699
|
-
file.title,
|
|
11700
|
-
file.url,
|
|
11701
|
-
{ mediaType: file.mediaType }
|
|
11702
|
-
));
|
|
11703
|
-
}
|
|
11704
|
-
return [];
|
|
11744
|
+
if (depth === 3) {
|
|
11745
|
+
const collectionSlug = segments[0];
|
|
11746
|
+
const studyId = segments[1];
|
|
11747
|
+
const lessonId = segments[2];
|
|
11748
|
+
return this.getLessonFiles(collectionSlug, studyId, lessonId);
|
|
11705
11749
|
}
|
|
11706
11750
|
return [];
|
|
11707
11751
|
}
|
|
11708
|
-
|
|
11709
|
-
return
|
|
11752
|
+
getCollections() {
|
|
11753
|
+
return this.data.collections.filter((collection) => collection.folders.length > 0).map((collection) => ({
|
|
11754
|
+
type: "folder",
|
|
11755
|
+
id: this.slugify(collection.name),
|
|
11756
|
+
title: collection.name,
|
|
11757
|
+
path: `/${this.slugify(collection.name)}`
|
|
11758
|
+
}));
|
|
11710
11759
|
}
|
|
11711
|
-
getStudyFolders(
|
|
11712
|
-
const collection = this.data.collections.find((c) => c.name ===
|
|
11760
|
+
getStudyFolders(collectionSlug, currentPath) {
|
|
11761
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11713
11762
|
if (!collection) return [];
|
|
11714
|
-
return collection.folders.map((study) =>
|
|
11715
|
-
|
|
11716
|
-
study.
|
|
11717
|
-
study.
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11763
|
+
return collection.folders.map((study) => ({
|
|
11764
|
+
type: "folder",
|
|
11765
|
+
id: study.id,
|
|
11766
|
+
title: study.name,
|
|
11767
|
+
image: study.image || void 0,
|
|
11768
|
+
path: `${currentPath}/${study.id}`,
|
|
11769
|
+
providerData: { studyData: study }
|
|
11770
|
+
}));
|
|
11771
|
+
}
|
|
11772
|
+
getLessonFolders(collectionSlug, studyId, currentPath) {
|
|
11773
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11774
|
+
if (!collection) return [];
|
|
11775
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11776
|
+
if (!study) return [];
|
|
11777
|
+
return study.lessons.map((lesson) => ({
|
|
11778
|
+
type: "folder",
|
|
11779
|
+
id: lesson.id,
|
|
11780
|
+
title: lesson.name,
|
|
11781
|
+
image: lesson.image || void 0,
|
|
11782
|
+
isLeaf: true,
|
|
11783
|
+
path: `${currentPath}/${lesson.id}`,
|
|
11784
|
+
providerData: { lessonData: lesson, studyName: study.name }
|
|
11785
|
+
}));
|
|
11786
|
+
}
|
|
11787
|
+
getLessonFiles(collectionSlug, studyId, lessonId) {
|
|
11788
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11789
|
+
if (!collection) return [];
|
|
11790
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11791
|
+
if (!study) return [];
|
|
11792
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11793
|
+
if (!lesson?.files) return [];
|
|
11794
|
+
return lesson.files.map((file) => createFile(file.id, file.title, file.url, { mediaType: file.mediaType }));
|
|
11795
|
+
}
|
|
11796
|
+
async getPresentations(path, _auth) {
|
|
11797
|
+
const { segments, depth } = parsePath(path);
|
|
11798
|
+
if (depth < 2) return null;
|
|
11799
|
+
const collectionSlug = segments[0];
|
|
11800
|
+
const studyId = segments[1];
|
|
11801
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11802
|
+
if (!collection) return null;
|
|
11803
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11804
|
+
if (!study) return null;
|
|
11805
|
+
if (depth === 2) {
|
|
11806
|
+
const allFiles = [];
|
|
11807
|
+
const sections = study.lessons.map((lesson) => {
|
|
11808
|
+
const files = lesson.files.map((file) => {
|
|
11809
|
+
const contentFile = { type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image };
|
|
11810
|
+
allFiles.push(contentFile);
|
|
11811
|
+
return contentFile;
|
|
11812
|
+
});
|
|
11813
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11814
|
+
return { id: lesson.id, name: lesson.name, presentations: [presentation] };
|
|
11815
|
+
});
|
|
11816
|
+
return { id: study.id, name: study.name, description: study.description, image: study.image, sections, allFiles };
|
|
11817
|
+
}
|
|
11818
|
+
if (depth === 3) {
|
|
11819
|
+
const lessonId = segments[2];
|
|
11820
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11821
|
+
if (!lesson?.files) return null;
|
|
11822
|
+
const files = lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11823
|
+
const presentation = { id: lesson.id, name: lesson.name, actionType: "play", files };
|
|
11824
|
+
return { id: lesson.id, name: lesson.name, image: lesson.image, sections: [{ id: "main", name: "Content", presentations: [presentation] }], allFiles: files };
|
|
11825
|
+
}
|
|
11826
|
+
return null;
|
|
11827
|
+
}
|
|
11828
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
11829
|
+
const { segments, depth } = parsePath(path);
|
|
11830
|
+
if (depth < 2) return null;
|
|
11831
|
+
const collectionSlug = segments[0];
|
|
11832
|
+
const studyId = segments[1];
|
|
11833
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11834
|
+
if (!collection) return null;
|
|
11835
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11836
|
+
if (!study) return null;
|
|
11837
|
+
if (depth === 2) {
|
|
11838
|
+
const allFiles = [];
|
|
11839
|
+
for (const lesson of study.lessons) {
|
|
11840
|
+
for (const file of lesson.files) {
|
|
11841
|
+
allFiles.push({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image });
|
|
11842
|
+
}
|
|
11734
11843
|
}
|
|
11735
|
-
|
|
11844
|
+
return allFiles;
|
|
11845
|
+
}
|
|
11846
|
+
if (depth === 3) {
|
|
11847
|
+
const lessonId = segments[2];
|
|
11848
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11849
|
+
if (!lesson?.files) return null;
|
|
11850
|
+
return lesson.files.map((file) => ({ type: "file", id: file.id, title: file.title, mediaType: file.mediaType, url: file.url, image: lesson.image }));
|
|
11851
|
+
}
|
|
11852
|
+
return null;
|
|
11853
|
+
}
|
|
11854
|
+
async getExpandedInstructions(path, _auth) {
|
|
11855
|
+
const { segments, depth } = parsePath(path);
|
|
11856
|
+
if (depth < 2) return null;
|
|
11857
|
+
const collectionSlug = segments[0];
|
|
11858
|
+
const studyId = segments[1];
|
|
11859
|
+
const collection = this.data.collections.find((c) => this.slugify(c.name) === collectionSlug);
|
|
11860
|
+
if (!collection) return null;
|
|
11861
|
+
const study = collection.folders.find((s) => s.id === studyId);
|
|
11862
|
+
if (!study) return null;
|
|
11863
|
+
if (depth === 2) {
|
|
11864
|
+
const lessonItems = study.lessons.map((lesson) => {
|
|
11865
|
+
const fileItems = lesson.files.map((file) => ({ id: file.id, itemType: "file", label: file.title, embedUrl: file.url }));
|
|
11866
|
+
return { id: lesson.id, itemType: "action", label: lesson.name, description: "play", children: fileItems };
|
|
11867
|
+
});
|
|
11868
|
+
return { venueName: study.name, items: [{ id: study.id, itemType: "header", label: study.name, children: [{ id: "main", itemType: "section", label: "Content", children: lessonItems }] }] };
|
|
11869
|
+
}
|
|
11870
|
+
if (depth === 3) {
|
|
11871
|
+
const lessonId = segments[2];
|
|
11872
|
+
const lesson = study.lessons.find((l) => l.id === lessonId);
|
|
11873
|
+
if (!lesson?.files) return null;
|
|
11874
|
+
const headerLabel = `${study.name} - ${lesson.name}`;
|
|
11875
|
+
const actionItems = this.groupFilesIntoActions(lesson.files);
|
|
11876
|
+
return { venueName: lesson.name, items: [{ id: lesson.id, itemType: "header", label: headerLabel, children: [{ id: "main", itemType: "section", label: lesson.name, children: actionItems }] }] };
|
|
11877
|
+
}
|
|
11878
|
+
return null;
|
|
11736
11879
|
}
|
|
11737
11880
|
slugify(text) {
|
|
11738
11881
|
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
11739
11882
|
}
|
|
11883
|
+
groupFilesIntoActions(files) {
|
|
11884
|
+
const actionItems = [];
|
|
11885
|
+
let currentGroup = [];
|
|
11886
|
+
let currentBaseName = null;
|
|
11887
|
+
const flushGroup = () => {
|
|
11888
|
+
if (currentGroup.length === 0) return;
|
|
11889
|
+
const children = currentGroup.map((file) => ({
|
|
11890
|
+
id: file.id,
|
|
11891
|
+
itemType: "file",
|
|
11892
|
+
label: file.title,
|
|
11893
|
+
embedUrl: file.url
|
|
11894
|
+
}));
|
|
11895
|
+
const label = currentGroup.length > 1 && currentBaseName ? currentBaseName : currentGroup[0].title;
|
|
11896
|
+
actionItems.push({
|
|
11897
|
+
id: currentGroup[0].id + "-action",
|
|
11898
|
+
itemType: "action",
|
|
11899
|
+
label,
|
|
11900
|
+
description: "play",
|
|
11901
|
+
children
|
|
11902
|
+
});
|
|
11903
|
+
currentGroup = [];
|
|
11904
|
+
currentBaseName = null;
|
|
11905
|
+
};
|
|
11906
|
+
for (const file of files) {
|
|
11907
|
+
const baseName = this.getBaseName(file.title);
|
|
11908
|
+
const isNumbered = baseName !== file.title;
|
|
11909
|
+
if (isNumbered && baseName === currentBaseName) {
|
|
11910
|
+
currentGroup.push(file);
|
|
11911
|
+
} else {
|
|
11912
|
+
flushGroup();
|
|
11913
|
+
currentGroup = [file];
|
|
11914
|
+
currentBaseName = isNumbered ? baseName : null;
|
|
11915
|
+
}
|
|
11916
|
+
}
|
|
11917
|
+
flushGroup();
|
|
11918
|
+
return actionItems;
|
|
11919
|
+
}
|
|
11920
|
+
getBaseName(title) {
|
|
11921
|
+
const match = title.match(/^(.+?)\s*\d+$/);
|
|
11922
|
+
return match ? match[1].trim() : title;
|
|
11923
|
+
}
|
|
11740
11924
|
};
|
|
11741
11925
|
|
|
11742
11926
|
// src/providers/index.ts
|
|
@@ -11821,15 +12005,15 @@ function getProviderConfig(providerId) {
|
|
|
11821
12005
|
const provider = getProvider(providerId);
|
|
11822
12006
|
return provider?.config || null;
|
|
11823
12007
|
}
|
|
11824
|
-
function getAvailableProviders() {
|
|
12008
|
+
function getAvailableProviders(ids) {
|
|
11825
12009
|
const implemented = getAllProviders().map((provider) => ({
|
|
11826
12010
|
id: provider.id,
|
|
11827
12011
|
name: provider.name,
|
|
11828
12012
|
logos: provider.logos,
|
|
11829
12013
|
implemented: true,
|
|
11830
|
-
requiresAuth: provider.requiresAuth
|
|
11831
|
-
authTypes: provider.
|
|
11832
|
-
capabilities: provider.
|
|
12014
|
+
requiresAuth: provider.requiresAuth,
|
|
12015
|
+
authTypes: provider.authTypes,
|
|
12016
|
+
capabilities: provider.capabilities
|
|
11833
12017
|
}));
|
|
11834
12018
|
const comingSoon = unimplementedProviders.map((p) => ({
|
|
11835
12019
|
id: p.id,
|
|
@@ -11840,27 +12024,56 @@ function getAvailableProviders() {
|
|
|
11840
12024
|
authTypes: [],
|
|
11841
12025
|
capabilities: { browse: false, presentations: false, playlist: false, instructions: false, expandedInstructions: false, mediaLicensing: false }
|
|
11842
12026
|
}));
|
|
11843
|
-
|
|
12027
|
+
const all = [...implemented, ...comingSoon];
|
|
12028
|
+
if (ids && ids.length > 0) {
|
|
12029
|
+
const idSet = new Set(ids);
|
|
12030
|
+
return all.filter((provider) => idSet.has(provider.id));
|
|
12031
|
+
}
|
|
12032
|
+
return all;
|
|
11844
12033
|
}
|
|
11845
12034
|
|
|
11846
12035
|
// src/index.ts
|
|
11847
12036
|
var VERSION = "0.0.1";
|
|
11848
12037
|
export {
|
|
11849
12038
|
APlayProvider,
|
|
12039
|
+
ApiHelper,
|
|
11850
12040
|
B1ChurchProvider,
|
|
11851
12041
|
BibleProjectProvider,
|
|
11852
12042
|
ContentProvider,
|
|
12043
|
+
DeviceFlowHelper,
|
|
12044
|
+
FormatConverters_exports as FormatConverters,
|
|
12045
|
+
FormatResolver,
|
|
12046
|
+
HighVoltageKidsProvider,
|
|
11853
12047
|
LessonsChurchProvider,
|
|
12048
|
+
OAuthHelper,
|
|
11854
12049
|
PlanningCenterProvider,
|
|
11855
12050
|
SignPresenterProvider,
|
|
12051
|
+
TokenHelper,
|
|
11856
12052
|
VERSION,
|
|
12053
|
+
appendToPath,
|
|
12054
|
+
buildPath,
|
|
12055
|
+
collapseInstructions,
|
|
12056
|
+
createFile,
|
|
12057
|
+
createFolder,
|
|
11857
12058
|
detectMediaType,
|
|
12059
|
+
expandedInstructionsToPlaylist,
|
|
12060
|
+
expandedInstructionsToPresentations,
|
|
11858
12061
|
getAllProviders,
|
|
11859
12062
|
getAvailableProviders,
|
|
11860
12063
|
getProvider,
|
|
11861
12064
|
getProviderConfig,
|
|
12065
|
+
getSegment,
|
|
12066
|
+
instructionsToPlaylist,
|
|
12067
|
+
instructionsToPresentations,
|
|
11862
12068
|
isContentFile,
|
|
11863
12069
|
isContentFolder,
|
|
12070
|
+
parsePath,
|
|
12071
|
+
playlistToExpandedInstructions,
|
|
12072
|
+
playlistToInstructions,
|
|
12073
|
+
playlistToPresentations,
|
|
12074
|
+
presentationsToExpandedInstructions,
|
|
12075
|
+
presentationsToInstructions,
|
|
12076
|
+
presentationsToPlaylist,
|
|
11864
12077
|
registerProvider
|
|
11865
12078
|
};
|
|
11866
12079
|
//# sourceMappingURL=index.js.map
|