@churchapps/content-providers 0.4.0 → 0.5.0
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/README.md +14 -16
- package/dist/index.cjs +442 -840
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +70 -48
- package/dist/index.d.ts +70 -48
- package/dist/index.js +437 -839
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,33 +18,81 @@ function navigateToPath(instructions, path) {
|
|
|
18
18
|
return current;
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// src/helpers/TokenHelper.ts
|
|
22
|
+
function toAuthData(data, fallbacks) {
|
|
23
|
+
return {
|
|
24
|
+
access_token: data.access_token,
|
|
25
|
+
refresh_token: data.refresh_token || fallbacks?.refreshToken || "",
|
|
26
|
+
token_type: data.token_type || "Bearer",
|
|
27
|
+
created_at: Math.floor(Date.now() / 1e3),
|
|
28
|
+
expires_in: data.expires_in,
|
|
29
|
+
scope: data.scope || fallbacks?.scope || ""
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
var TokenHelper = class {
|
|
33
|
+
isAuthValid(auth) {
|
|
34
|
+
if (!auth) return false;
|
|
35
|
+
return !this.isTokenExpired(auth);
|
|
36
|
+
}
|
|
37
|
+
isTokenExpired(auth) {
|
|
38
|
+
if (!auth.created_at || !auth.expires_in) return true;
|
|
39
|
+
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
40
|
+
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
41
|
+
}
|
|
42
|
+
async refreshToken(config, auth) {
|
|
43
|
+
if (!auth.refresh_token) return null;
|
|
44
|
+
try {
|
|
45
|
+
const params = new URLSearchParams({
|
|
46
|
+
grant_type: "refresh_token",
|
|
47
|
+
refresh_token: auth.refresh_token,
|
|
48
|
+
client_id: config.clientId
|
|
49
|
+
});
|
|
50
|
+
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
51
|
+
if (!response.ok) {
|
|
52
|
+
console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
|
|
57
|
+
} catch {
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
21
63
|
// src/helpers/OAuthHelper.ts
|
|
64
|
+
function generateCodeVerifier() {
|
|
65
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
|
|
66
|
+
const length = 64;
|
|
67
|
+
const array = new Uint8Array(length);
|
|
68
|
+
crypto.getRandomValues(array);
|
|
69
|
+
let result = "";
|
|
70
|
+
for (let i = 0; i < length; i++) {
|
|
71
|
+
result += chars.charAt(array[i] % chars.length);
|
|
72
|
+
}
|
|
73
|
+
return result;
|
|
74
|
+
}
|
|
75
|
+
async function generateCodeChallenge(verifier) {
|
|
76
|
+
const encoder = new TextEncoder();
|
|
77
|
+
const data = encoder.encode(verifier);
|
|
78
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
79
|
+
const hashArray = new Uint8Array(hashBuffer);
|
|
80
|
+
let binary = "";
|
|
81
|
+
for (let i = 0; i < hashArray.length; i++) {
|
|
82
|
+
binary += String.fromCharCode(hashArray[i]);
|
|
83
|
+
}
|
|
84
|
+
const base64 = btoa(binary);
|
|
85
|
+
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
86
|
+
}
|
|
22
87
|
var OAuthHelper = class {
|
|
23
88
|
generateCodeVerifier() {
|
|
24
|
-
|
|
25
|
-
const length = 64;
|
|
26
|
-
const array = new Uint8Array(length);
|
|
27
|
-
crypto.getRandomValues(array);
|
|
28
|
-
let result = "";
|
|
29
|
-
for (let i = 0; i < length; i++) {
|
|
30
|
-
result += chars.charAt(array[i] % chars.length);
|
|
31
|
-
}
|
|
32
|
-
return result;
|
|
89
|
+
return generateCodeVerifier();
|
|
33
90
|
}
|
|
34
91
|
async generateCodeChallenge(verifier) {
|
|
35
|
-
|
|
36
|
-
const data = encoder.encode(verifier);
|
|
37
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
38
|
-
const hashArray = new Uint8Array(hashBuffer);
|
|
39
|
-
let binary = "";
|
|
40
|
-
for (let i = 0; i < hashArray.length; i++) {
|
|
41
|
-
binary += String.fromCharCode(hashArray[i]);
|
|
42
|
-
}
|
|
43
|
-
const base64 = btoa(binary);
|
|
44
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
92
|
+
return generateCodeChallenge(verifier);
|
|
45
93
|
}
|
|
46
94
|
async buildAuthUrl(config, codeVerifier, redirectUri, state) {
|
|
47
|
-
const codeChallenge = await
|
|
95
|
+
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
48
96
|
const params = new URLSearchParams({
|
|
49
97
|
response_type: "code",
|
|
50
98
|
client_id: config.clientId,
|
|
@@ -71,39 +119,7 @@ var OAuthHelper = class {
|
|
|
71
119
|
return null;
|
|
72
120
|
}
|
|
73
121
|
const data = await response.json();
|
|
74
|
-
return
|
|
75
|
-
} catch {
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
// src/helpers/TokenHelper.ts
|
|
82
|
-
var TokenHelper = class {
|
|
83
|
-
isAuthValid(auth) {
|
|
84
|
-
if (!auth) return false;
|
|
85
|
-
return !this.isTokenExpired(auth);
|
|
86
|
-
}
|
|
87
|
-
isTokenExpired(auth) {
|
|
88
|
-
if (!auth.created_at || !auth.expires_in) return true;
|
|
89
|
-
const expiresAt = (auth.created_at + auth.expires_in) * 1e3;
|
|
90
|
-
return Date.now() > expiresAt - 5 * 60 * 1e3;
|
|
91
|
-
}
|
|
92
|
-
async refreshToken(config, auth) {
|
|
93
|
-
if (!auth.refresh_token) return null;
|
|
94
|
-
try {
|
|
95
|
-
const params = new URLSearchParams({
|
|
96
|
-
grant_type: "refresh_token",
|
|
97
|
-
refresh_token: auth.refresh_token,
|
|
98
|
-
client_id: config.clientId
|
|
99
|
-
});
|
|
100
|
-
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: params.toString() });
|
|
101
|
-
if (!response.ok) {
|
|
102
|
-
console.warn(`[TokenHelper] Token refresh failed: ${response.status} ${response.statusText}`);
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
const data = await response.json();
|
|
106
|
-
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 };
|
|
122
|
+
return toAuthData(data, { scope: config.scopes.join(" ") });
|
|
107
123
|
} catch {
|
|
108
124
|
return null;
|
|
109
125
|
}
|
|
@@ -130,7 +146,7 @@ var DeviceFlowHelper = class {
|
|
|
130
146
|
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 }) });
|
|
131
147
|
if (response.ok) {
|
|
132
148
|
const data = await response.json();
|
|
133
|
-
return
|
|
149
|
+
return toAuthData(data, { scope: config.scopes.join(" ") });
|
|
134
150
|
}
|
|
135
151
|
const errorData = await response.json();
|
|
136
152
|
switch (errorData.error) {
|
|
@@ -180,6 +196,19 @@ var ApiHelper = class {
|
|
|
180
196
|
}
|
|
181
197
|
};
|
|
182
198
|
|
|
199
|
+
// src/providers/BaseProvider.ts
|
|
200
|
+
var BaseProvider = class {
|
|
201
|
+
constructor() {
|
|
202
|
+
this.apiHelper = new ApiHelper();
|
|
203
|
+
}
|
|
204
|
+
apiRequest(path, auth, method = "GET", body) {
|
|
205
|
+
return this.apiHelper.apiRequest(this.config, this.id, path, auth, method, body);
|
|
206
|
+
}
|
|
207
|
+
supportsDeviceFlow() {
|
|
208
|
+
return !!this.config.supportsDeviceFlow && !!this.config.deviceAuthEndpoint;
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
183
212
|
// src/pathUtils.ts
|
|
184
213
|
function parsePath(path) {
|
|
185
214
|
if (!path || path === "/" || path === "") {
|
|
@@ -193,26 +222,6 @@ function getSegment(path, index) {
|
|
|
193
222
|
return segments[index] ?? null;
|
|
194
223
|
}
|
|
195
224
|
|
|
196
|
-
// src/providers/aPlay/APlayApi.ts
|
|
197
|
-
var API_BASE = "https://api-prod.amazingkids.app";
|
|
198
|
-
async function checkMediaLicense(mediaId, auth) {
|
|
199
|
-
if (!auth) return null;
|
|
200
|
-
try {
|
|
201
|
-
const url = `${API_BASE}/prod/reports/media/license-check`;
|
|
202
|
-
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] }) });
|
|
203
|
-
if (!response.ok) return null;
|
|
204
|
-
const data = await response.json();
|
|
205
|
-
const licenseData = Array.isArray(data) ? data : data.data || [];
|
|
206
|
-
const result = licenseData.find((item) => item.mediaId === mediaId);
|
|
207
|
-
if (result?.isLicensed) {
|
|
208
|
-
return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
|
|
209
|
-
}
|
|
210
|
-
return { mediaId, status: "not_licensed", message: "Media is not licensed" };
|
|
211
|
-
} catch {
|
|
212
|
-
return { mediaId, status: "unknown", message: "Unable to verify license status" };
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
225
|
// src/utils.ts
|
|
217
226
|
var IMAGE_DURATION_SECONDS = 15;
|
|
218
227
|
function slugify(text) {
|
|
@@ -250,6 +259,31 @@ function createFile(id, title, url, options) {
|
|
|
250
259
|
function generateId() {
|
|
251
260
|
return "gen-" + Math.random().toString(36).substring(2, 11);
|
|
252
261
|
}
|
|
262
|
+
function fileToActionItem(file) {
|
|
263
|
+
return {
|
|
264
|
+
id: file.id + "-action",
|
|
265
|
+
itemType: "action",
|
|
266
|
+
label: file.title,
|
|
267
|
+
actionType: "play",
|
|
268
|
+
seconds: file.seconds || void 0,
|
|
269
|
+
children: [
|
|
270
|
+
{
|
|
271
|
+
id: file.id,
|
|
272
|
+
itemType: "file",
|
|
273
|
+
label: file.title,
|
|
274
|
+
seconds: file.seconds || void 0,
|
|
275
|
+
downloadUrl: file.downloadUrl || file.url,
|
|
276
|
+
thumbnail: file.thumbnail,
|
|
277
|
+
mediaType: file.mediaType
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
function filesToInstructions(name, files, section) {
|
|
283
|
+
const actions = files.map(fileToActionItem);
|
|
284
|
+
const items = section ? [{ id: section.id, itemType: "section", label: section.label ?? name, children: actions }] : actions;
|
|
285
|
+
return { name, items };
|
|
286
|
+
}
|
|
253
287
|
function instructionsToPlaylist(instructions) {
|
|
254
288
|
const files = [];
|
|
255
289
|
function extractFiles(items) {
|
|
@@ -264,6 +298,26 @@ function instructionsToPlaylist(instructions) {
|
|
|
264
298
|
return files;
|
|
265
299
|
}
|
|
266
300
|
|
|
301
|
+
// src/providers/aPlay/APlayApi.ts
|
|
302
|
+
var API_BASE = "https://api-prod.amazingkids.app";
|
|
303
|
+
async function checkMediaLicense(mediaId, auth) {
|
|
304
|
+
if (!auth) return null;
|
|
305
|
+
try {
|
|
306
|
+
const url = `${API_BASE}/prod/reports/media/license-check`;
|
|
307
|
+
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] }) });
|
|
308
|
+
if (!response.ok) return null;
|
|
309
|
+
const data = await response.json();
|
|
310
|
+
const licenseData = Array.isArray(data) ? data : data.data || [];
|
|
311
|
+
const result = licenseData.find((item) => item.mediaId === mediaId);
|
|
312
|
+
if (result?.isLicensed) {
|
|
313
|
+
return { mediaId, status: "valid", message: "Media is licensed for playback", expiresAt: result.expiresAt };
|
|
314
|
+
}
|
|
315
|
+
return { mediaId, status: "not_licensed", message: "Media is not licensed" };
|
|
316
|
+
} catch {
|
|
317
|
+
return { mediaId, status: "unknown", message: "Unable to verify license status" };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
267
321
|
// src/providers/aPlay/APlayConverters.ts
|
|
268
322
|
function extractLibraryId(path) {
|
|
269
323
|
const { segments, depth } = parsePath(path);
|
|
@@ -330,25 +384,6 @@ function convertProductsToFolders(products, currentPath) {
|
|
|
330
384
|
return createFolder(id, p.title || p.name, `${currentPath}/products/${id}`, p.image);
|
|
331
385
|
});
|
|
332
386
|
}
|
|
333
|
-
function convertFilesToInstructions(files, _libraryId) {
|
|
334
|
-
const items = files.map((file) => ({
|
|
335
|
-
id: file.id + "-action",
|
|
336
|
-
itemType: "action",
|
|
337
|
-
label: file.title,
|
|
338
|
-
actionType: "play",
|
|
339
|
-
children: [
|
|
340
|
-
{
|
|
341
|
-
id: file.id,
|
|
342
|
-
itemType: "file",
|
|
343
|
-
label: file.title,
|
|
344
|
-
seconds: file.seconds,
|
|
345
|
-
downloadUrl: file.url,
|
|
346
|
-
thumbnail: file.thumbnail
|
|
347
|
-
}
|
|
348
|
-
]
|
|
349
|
-
}));
|
|
350
|
-
return { name: "Library", items };
|
|
351
|
-
}
|
|
352
387
|
|
|
353
388
|
// src/providers/aPlay/APlayProvider.ts
|
|
354
389
|
function extractArray(response, ...keys) {
|
|
@@ -361,20 +396,17 @@ function extractArray(response, ...keys) {
|
|
|
361
396
|
}
|
|
362
397
|
return [];
|
|
363
398
|
}
|
|
364
|
-
var APlayProvider = class {
|
|
399
|
+
var APlayProvider = class extends BaseProvider {
|
|
365
400
|
constructor() {
|
|
366
|
-
|
|
401
|
+
super(...arguments);
|
|
367
402
|
this.id = "aplay";
|
|
368
403
|
this.name = "APlay";
|
|
369
404
|
this.logos = { light: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg", dark: "https://www.joinamazing.com/_assets/v11/3ba846c5afd7e73d27bc4d87b63d423e7ae2dc73.svg" };
|
|
370
|
-
this.config = { id: "aplay", name: "APlay", apiBase: API_BASE, oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"]
|
|
405
|
+
this.config = { id: "aplay", name: "APlay", apiBase: API_BASE, oauthBase: "https://api.joinamazing.com/prod/aims/oauth", clientId: "xFJFq7yNYuXXXMx0YBiQ", scopes: ["openid", "profile", "email"] };
|
|
371
406
|
this.requiresAuth = true;
|
|
372
407
|
this.authTypes = ["oauth_pkce"];
|
|
373
408
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: true };
|
|
374
409
|
}
|
|
375
|
-
async apiRequest(path, auth) {
|
|
376
|
-
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
377
|
-
}
|
|
378
410
|
async browse(path, auth) {
|
|
379
411
|
const { segments, depth } = parsePath(path);
|
|
380
412
|
if (depth === 0) {
|
|
@@ -390,12 +422,12 @@ var APlayProvider = class {
|
|
|
390
422
|
return [];
|
|
391
423
|
}
|
|
392
424
|
async getModules(auth) {
|
|
393
|
-
const response = await this.apiRequest(
|
|
425
|
+
const response = await this.apiRequest("/prod/curriculum/modules", auth);
|
|
394
426
|
const modules = extractArray(response, "data", "modules");
|
|
395
427
|
return convertModulesToFolders(modules);
|
|
396
428
|
}
|
|
397
429
|
async getModuleContent(moduleId, currentPath, auth) {
|
|
398
|
-
const response = await this.apiRequest(
|
|
430
|
+
const response = await this.apiRequest("/prod/curriculum/modules", auth);
|
|
399
431
|
const modules = extractArray(response, "data", "modules");
|
|
400
432
|
const module = modules.find((m) => (m.id || m.moduleId) === moduleId);
|
|
401
433
|
if (!module) return [];
|
|
@@ -411,14 +443,12 @@ var APlayProvider = class {
|
|
|
411
443
|
}
|
|
412
444
|
}
|
|
413
445
|
async getLibraryFolders(productId, currentPath, auth) {
|
|
414
|
-
const
|
|
415
|
-
const response = await this.apiRequest(pathFn(productId), auth);
|
|
446
|
+
const response = await this.apiRequest(`/prod/curriculum/modules/products/${productId}/libraries`, auth);
|
|
416
447
|
const libraries = extractArray(response, "data", "libraries");
|
|
417
448
|
return convertLibrariesToFolders(libraries, currentPath);
|
|
418
449
|
}
|
|
419
450
|
async getMediaFiles(libraryId, auth) {
|
|
420
|
-
const
|
|
421
|
-
const response = await this.apiRequest(pathFn(libraryId), auth);
|
|
451
|
+
const response = await this.apiRequest(`/prod/creators/libraries/${libraryId}/media`, auth);
|
|
422
452
|
const mediaItems = extractArray(response, "data", "media");
|
|
423
453
|
return convertMediaToFiles(mediaItems);
|
|
424
454
|
}
|
|
@@ -433,63 +463,47 @@ var APlayProvider = class {
|
|
|
433
463
|
if (!libraryId) return null;
|
|
434
464
|
const files = await this.getMediaFiles(libraryId, auth);
|
|
435
465
|
if (files.length === 0) return null;
|
|
436
|
-
return
|
|
466
|
+
return filesToInstructions("Library", files);
|
|
437
467
|
}
|
|
438
468
|
async checkMediaLicense(mediaId, auth) {
|
|
439
469
|
return checkMediaLicense(mediaId, auth);
|
|
440
470
|
}
|
|
441
|
-
supportsDeviceFlow() {
|
|
442
|
-
return false;
|
|
443
|
-
}
|
|
444
471
|
};
|
|
445
472
|
|
|
446
473
|
// src/providers/signPresenter/SignPresenterProvider.ts
|
|
447
|
-
var SignPresenterProvider = class {
|
|
474
|
+
var SignPresenterProvider = class extends BaseProvider {
|
|
448
475
|
constructor() {
|
|
449
|
-
|
|
476
|
+
super(...arguments);
|
|
450
477
|
this.oauthHelper = new OAuthHelper();
|
|
451
478
|
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
452
479
|
this.id = "signpresenter";
|
|
453
480
|
this.name = "SignPresenter";
|
|
454
481
|
this.logos = { light: "https://signpresenter.com/files/shared/images/logo.png", dark: "https://signpresenter.com/files/shared/images/logo.png" };
|
|
455
|
-
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"
|
|
482
|
+
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" };
|
|
456
483
|
this.requiresAuth = true;
|
|
457
484
|
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
458
485
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
459
486
|
}
|
|
460
|
-
async apiRequest(path, auth) {
|
|
461
|
-
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
462
|
-
}
|
|
463
487
|
async browse(path, auth) {
|
|
464
488
|
const { segments, depth } = parsePath(path);
|
|
465
489
|
if (depth === 0) {
|
|
466
|
-
return [
|
|
467
|
-
{
|
|
468
|
-
type: "folder",
|
|
469
|
-
id: "playlists-root",
|
|
470
|
-
title: "Playlists",
|
|
471
|
-
path: "/playlists"
|
|
472
|
-
}
|
|
473
|
-
];
|
|
474
|
-
}
|
|
475
|
-
const root = segments[0];
|
|
476
|
-
if (root !== "playlists") return [];
|
|
477
|
-
if (depth === 1) {
|
|
478
|
-
return this.getPlaylists(auth);
|
|
479
|
-
}
|
|
480
|
-
if (depth === 2) {
|
|
481
|
-
const playlistId = segments[1];
|
|
482
|
-
return this.getMessages(playlistId, auth);
|
|
490
|
+
return [{ type: "folder", id: "playlists-root", title: "Playlists", path: "/playlists" }];
|
|
483
491
|
}
|
|
492
|
+
if (segments[0] !== "playlists") return [];
|
|
493
|
+
if (depth === 1) return this.getPlaylists(auth);
|
|
494
|
+
if (depth === 2) return this.getMessages(segments[1], auth);
|
|
484
495
|
return [];
|
|
485
496
|
}
|
|
497
|
+
extractList(response, key) {
|
|
498
|
+
if (Array.isArray(response)) return response;
|
|
499
|
+
const record = response;
|
|
500
|
+
const list = record?.data || record?.[key] || [];
|
|
501
|
+
return Array.isArray(list) ? list : [];
|
|
502
|
+
}
|
|
486
503
|
async getPlaylists(auth) {
|
|
487
|
-
const
|
|
488
|
-
const response = await this.apiRequest(apiPath, auth);
|
|
504
|
+
const response = await this.apiRequest("/content/playlists", auth);
|
|
489
505
|
if (!response) return [];
|
|
490
|
-
|
|
491
|
-
if (!Array.isArray(playlists)) return [];
|
|
492
|
-
return playlists.map((p) => ({
|
|
506
|
+
return this.extractList(response, "playlists").map((p) => ({
|
|
493
507
|
type: "folder",
|
|
494
508
|
id: p.id,
|
|
495
509
|
title: p.name,
|
|
@@ -499,26 +513,17 @@ var SignPresenterProvider = class {
|
|
|
499
513
|
}));
|
|
500
514
|
}
|
|
501
515
|
async getMessages(playlistId, auth) {
|
|
502
|
-
const
|
|
503
|
-
const response = await this.apiRequest(pathFn(playlistId), auth);
|
|
516
|
+
const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
|
|
504
517
|
if (!response) return [];
|
|
505
|
-
const messages = Array.isArray(response) ? response : response.data || response.messages || [];
|
|
506
|
-
if (!Array.isArray(messages)) return [];
|
|
507
518
|
const files = [];
|
|
508
|
-
for (const msg of messages) {
|
|
519
|
+
for (const msg of this.extractList(response, "messages")) {
|
|
509
520
|
if (!msg.url) continue;
|
|
510
521
|
const url = msg.url;
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
msg.
|
|
514
|
-
msg.
|
|
515
|
-
|
|
516
|
-
{
|
|
517
|
-
mediaType: detectMediaType(url, msg.mediaType),
|
|
518
|
-
thumbnail: msg.thumbnail || msg.image,
|
|
519
|
-
seconds
|
|
520
|
-
}
|
|
521
|
-
);
|
|
522
|
+
const file = createFile(msg.id, msg.name, url, {
|
|
523
|
+
mediaType: detectMediaType(url, msg.mediaType),
|
|
524
|
+
thumbnail: msg.thumbnail || msg.image,
|
|
525
|
+
seconds: msg.seconds
|
|
526
|
+
});
|
|
522
527
|
file.downloadUrl = url;
|
|
523
528
|
files.push(file);
|
|
524
529
|
}
|
|
@@ -527,8 +532,7 @@ var SignPresenterProvider = class {
|
|
|
527
532
|
async getPlaylist(path, auth, _resolution) {
|
|
528
533
|
const { segments, depth } = parsePath(path);
|
|
529
534
|
if (depth < 2 || segments[0] !== "playlists") return null;
|
|
530
|
-
const
|
|
531
|
-
const files = await this.getMessages(playlistId, auth);
|
|
535
|
+
const files = await this.getMessages(segments[1], auth);
|
|
532
536
|
return files.length > 0 ? files : null;
|
|
533
537
|
}
|
|
534
538
|
async getInstructions(path, auth) {
|
|
@@ -538,43 +542,11 @@ var SignPresenterProvider = class {
|
|
|
538
542
|
const files = await this.getMessages(playlistId, auth);
|
|
539
543
|
if (files.length === 0) return null;
|
|
540
544
|
const playlists = await this.getPlaylists(auth);
|
|
541
|
-
const
|
|
542
|
-
const
|
|
543
|
-
|
|
544
|
-
id: file.id + "-action",
|
|
545
|
-
itemType: "action",
|
|
546
|
-
label: file.title,
|
|
547
|
-
actionType: "play",
|
|
548
|
-
seconds: file.seconds,
|
|
549
|
-
children: [
|
|
550
|
-
{
|
|
551
|
-
id: file.id,
|
|
552
|
-
itemType: "file",
|
|
553
|
-
label: file.title,
|
|
554
|
-
seconds: file.seconds,
|
|
555
|
-
downloadUrl: file.downloadUrl || file.url,
|
|
556
|
-
thumbnail: file.thumbnail
|
|
557
|
-
}
|
|
558
|
-
]
|
|
559
|
-
}));
|
|
560
|
-
const sectionItem = {
|
|
561
|
-
id: playlistId + "-section",
|
|
562
|
-
itemType: "section",
|
|
563
|
-
label: title,
|
|
564
|
-
children: actionItems
|
|
565
|
-
};
|
|
566
|
-
const headerItem = {
|
|
567
|
-
id: playlistId + "-header",
|
|
568
|
-
itemType: "header",
|
|
569
|
-
label: title,
|
|
570
|
-
children: [sectionItem]
|
|
571
|
-
};
|
|
572
|
-
return { name: title, items: [headerItem] };
|
|
545
|
+
const title = playlists.find((p) => p.id === playlistId)?.title || "Playlist";
|
|
546
|
+
const instructions = filesToInstructions(title, files, { id: playlistId + "-section", label: title });
|
|
547
|
+
return { name: title, items: [{ id: playlistId + "-header", itemType: "header", label: title, children: instructions.items }] };
|
|
573
548
|
}
|
|
574
549
|
// Auth methods
|
|
575
|
-
supportsDeviceFlow() {
|
|
576
|
-
return this.deviceFlowHelper.supportsDeviceFlow(this.config);
|
|
577
|
-
}
|
|
578
550
|
generateCodeVerifier() {
|
|
579
551
|
return this.oauthHelper.generateCodeVerifier();
|
|
580
552
|
}
|
|
@@ -621,6 +593,23 @@ function truncateForLabel(text) {
|
|
|
621
593
|
const truncated = text.length > cutoff ? text.substring(0, cutoff) : text;
|
|
622
594
|
return stripMarkdown(truncated) + (text.length > cutoff ? "..." : "");
|
|
623
595
|
}
|
|
596
|
+
function getEmbedUrl(apiType, relatedId) {
|
|
597
|
+
if (!relatedId) return void 0;
|
|
598
|
+
const baseUrl = "https://lessons.church";
|
|
599
|
+
switch (apiType) {
|
|
600
|
+
case "action":
|
|
601
|
+
case "lessonAction":
|
|
602
|
+
return `${baseUrl}/embed/action/${relatedId}`;
|
|
603
|
+
case "addon":
|
|
604
|
+
case "lessonAddOn":
|
|
605
|
+
return `${baseUrl}/embed/addon/${relatedId}`;
|
|
606
|
+
case "section":
|
|
607
|
+
case "lessonSection":
|
|
608
|
+
return `${baseUrl}/embed/section/${relatedId}`;
|
|
609
|
+
default:
|
|
610
|
+
return void 0;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
624
613
|
async function convertAddOnToFile(addOn) {
|
|
625
614
|
const apiPath = `/addOns/public/${addOn.id}`;
|
|
626
615
|
const detail = await apiRequest(apiPath);
|
|
@@ -767,12 +756,13 @@ async function convertAddOnCategoryToInstructions(category) {
|
|
|
767
756
|
}
|
|
768
757
|
|
|
769
758
|
// src/providers/lessonsChurch/LessonsChurchProvider.ts
|
|
770
|
-
var LessonsChurchProvider = class {
|
|
759
|
+
var LessonsChurchProvider = class extends BaseProvider {
|
|
771
760
|
constructor() {
|
|
761
|
+
super(...arguments);
|
|
772
762
|
this.id = "lessonschurch";
|
|
773
763
|
this.name = "Lessons.church";
|
|
774
764
|
this.logos = { light: "https://lessons.church/images/logo.png", dark: "https://lessons.church/images/logo-dark.png" };
|
|
775
|
-
this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: []
|
|
765
|
+
this.config = { id: "lessonschurch", name: "Lessons.church", apiBase: API_BASE2, oauthBase: "", clientId: "", scopes: [] };
|
|
776
766
|
this.requiresAuth = false;
|
|
777
767
|
this.authTypes = ["none"];
|
|
778
768
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
@@ -828,28 +818,25 @@ var LessonsChurchProvider = class {
|
|
|
828
818
|
return [];
|
|
829
819
|
}
|
|
830
820
|
async getPrograms() {
|
|
831
|
-
const response = await apiRequest(
|
|
821
|
+
const response = await apiRequest("/programs/public");
|
|
832
822
|
if (!response) return [];
|
|
833
823
|
const programs = Array.isArray(response) ? response : [];
|
|
834
824
|
return programs.map((p) => createFolder(p.id, p.name, `/lessons/${p.id}`, p.image));
|
|
835
825
|
}
|
|
836
826
|
async getStudies(programId, currentPath) {
|
|
837
|
-
const
|
|
838
|
-
const response = await apiRequest(pathFn(programId));
|
|
827
|
+
const response = await apiRequest(`/studies/public/program/${programId}`);
|
|
839
828
|
if (!response) return [];
|
|
840
829
|
const studies = Array.isArray(response) ? response : [];
|
|
841
830
|
return studies.map((s) => createFolder(s.id, s.name, `${currentPath}/${s.id}`, s.image)).sort((a, b) => a.title.localeCompare(b.title));
|
|
842
831
|
}
|
|
843
832
|
async getLessons(studyId, currentPath) {
|
|
844
|
-
const
|
|
845
|
-
const response = await apiRequest(pathFn(studyId));
|
|
833
|
+
const response = await apiRequest(`/lessons/public/study/${studyId}`);
|
|
846
834
|
if (!response) return [];
|
|
847
835
|
const lessons = Array.isArray(response) ? response : [];
|
|
848
836
|
return lessons.map((l) => createFolder(l.id, l.name || l.title, `${currentPath}/${l.id}`, l.image));
|
|
849
837
|
}
|
|
850
838
|
async getVenues(lessonId, currentPath) {
|
|
851
|
-
const
|
|
852
|
-
const response = await apiRequest(pathFn(lessonId));
|
|
839
|
+
const response = await apiRequest(`/venues/public/lesson/${lessonId}`);
|
|
853
840
|
if (!response) return [];
|
|
854
841
|
const lessonResponse = await apiRequest(`/lessons/public/${lessonId}`);
|
|
855
842
|
const lessonImage = lessonResponse?.image;
|
|
@@ -872,7 +859,7 @@ var LessonsChurchProvider = class {
|
|
|
872
859
|
return file ? [file] : [];
|
|
873
860
|
}
|
|
874
861
|
async getAddOnCategories() {
|
|
875
|
-
const response = await apiRequest(
|
|
862
|
+
const response = await apiRequest("/addOns/public");
|
|
876
863
|
if (!response) return [];
|
|
877
864
|
const addOns = Array.isArray(response) ? response : [];
|
|
878
865
|
const categories = Array.from(new Set(addOns.map((a) => a.category).filter(Boolean)));
|
|
@@ -880,7 +867,7 @@ var LessonsChurchProvider = class {
|
|
|
880
867
|
}
|
|
881
868
|
async getAddOnsByCategory(category, currentPath) {
|
|
882
869
|
const decodedCategory = decodeURIComponent(category);
|
|
883
|
-
const response = await apiRequest(
|
|
870
|
+
const response = await apiRequest("/addOns/public");
|
|
884
871
|
if (!response) return [];
|
|
885
872
|
const allAddOns = Array.isArray(response) ? response : [];
|
|
886
873
|
const filtered = allAddOns.filter((a) => a.category === decodedCategory);
|
|
@@ -923,9 +910,6 @@ var LessonsChurchProvider = class {
|
|
|
923
910
|
}
|
|
924
911
|
return null;
|
|
925
912
|
}
|
|
926
|
-
supportsDeviceFlow() {
|
|
927
|
-
return false;
|
|
928
|
-
}
|
|
929
913
|
};
|
|
930
914
|
|
|
931
915
|
// src/providers/registry.ts
|
|
@@ -941,18 +925,6 @@ function registerProvider(provider) {
|
|
|
941
925
|
}
|
|
942
926
|
|
|
943
927
|
// src/providers/b1Church/B1ChurchAuth.ts
|
|
944
|
-
async function generateCodeChallenge(verifier) {
|
|
945
|
-
const encoder = new TextEncoder();
|
|
946
|
-
const data = encoder.encode(verifier);
|
|
947
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
948
|
-
const hashArray = new Uint8Array(hashBuffer);
|
|
949
|
-
let binary = "";
|
|
950
|
-
for (let i = 0; i < hashArray.length; i++) {
|
|
951
|
-
binary += String.fromCharCode(hashArray[i]);
|
|
952
|
-
}
|
|
953
|
-
const base64 = btoa(binary);
|
|
954
|
-
return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
955
|
-
}
|
|
956
928
|
async function buildB1AuthUrl(config, appBase, redirectUri, codeVerifier, state) {
|
|
957
929
|
const codeChallenge = await generateCodeChallenge(codeVerifier);
|
|
958
930
|
const oauthParams = new URLSearchParams({
|
|
@@ -976,7 +948,7 @@ async function exchangeCodeForTokensWithPKCE(config, code, redirectUri, codeVeri
|
|
|
976
948
|
return null;
|
|
977
949
|
}
|
|
978
950
|
const data = await response.json();
|
|
979
|
-
return
|
|
951
|
+
return toAuthData(data, { scope: config.scopes.join(" ") });
|
|
980
952
|
} catch {
|
|
981
953
|
return null;
|
|
982
954
|
}
|
|
@@ -990,7 +962,7 @@ async function exchangeCodeForTokensWithSecret(config, code, redirectUri, client
|
|
|
990
962
|
return null;
|
|
991
963
|
}
|
|
992
964
|
const data = await response.json();
|
|
993
|
-
return
|
|
965
|
+
return toAuthData(data, { scope: config.scopes.join(" ") });
|
|
994
966
|
} catch {
|
|
995
967
|
return null;
|
|
996
968
|
}
|
|
@@ -1002,7 +974,7 @@ async function refreshTokenWithSecret(config, auth, clientSecret) {
|
|
|
1002
974
|
const response = await fetch(`${config.oauthBase}/token`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(params) });
|
|
1003
975
|
if (!response.ok) return null;
|
|
1004
976
|
const data = await response.json();
|
|
1005
|
-
return
|
|
977
|
+
return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
|
|
1006
978
|
} catch {
|
|
1007
979
|
return null;
|
|
1008
980
|
}
|
|
@@ -1027,78 +999,32 @@ async function authFetch(url, auth) {
|
|
|
1027
999
|
}
|
|
1028
1000
|
}
|
|
1029
1001
|
async function fetchMinistries(auth) {
|
|
1030
|
-
|
|
1031
|
-
return result || [];
|
|
1002
|
+
return await authFetch(`${API_BASE3}/membership/groups/tag/ministry`, auth) || [];
|
|
1032
1003
|
}
|
|
1033
1004
|
async function fetchPlanTypes(ministryId, auth) {
|
|
1034
|
-
|
|
1035
|
-
return result || [];
|
|
1005
|
+
return await authFetch(`${API_BASE3}/doing/planTypes/ministryId/${ministryId}`, auth) || [];
|
|
1036
1006
|
}
|
|
1037
1007
|
async function fetchPlans(planTypeId, auth) {
|
|
1038
|
-
|
|
1039
|
-
return result || [];
|
|
1008
|
+
return await authFetch(`${API_BASE3}/doing/plans/types/${planTypeId}`, auth) || [];
|
|
1040
1009
|
}
|
|
1041
|
-
var DOING_API_BASE = "https://api.churchapps.org/doing";
|
|
1042
1010
|
async function fetchCurrentPlanByType(planTypeId) {
|
|
1043
|
-
|
|
1044
|
-
const response = await fetch(`${DOING_API_BASE}/plans/public/current/${planTypeId}`, { method: "GET", headers: { Accept: "application/json" } });
|
|
1045
|
-
if (!response.ok) {
|
|
1046
|
-
console.warn(`[B1Church] fetchCurrentPlanByType failed: HTTP ${response.status} for planTypeId=${planTypeId}`);
|
|
1047
|
-
return null;
|
|
1048
|
-
}
|
|
1049
|
-
return await response.json();
|
|
1050
|
-
} catch (err) {
|
|
1051
|
-
console.error(`[B1Church] fetchCurrentPlanByType error: planTypeId=${planTypeId}`, err);
|
|
1052
|
-
return null;
|
|
1053
|
-
}
|
|
1011
|
+
return authFetch(`${API_BASE3}/doing/plans/public/current/${planTypeId}`);
|
|
1054
1012
|
}
|
|
1055
1013
|
async function fetchPlanItems(churchId, planId) {
|
|
1056
|
-
|
|
1057
|
-
const response = await fetch(`${DOING_API_BASE}/planItems/presenter/${churchId}/${planId}`, { method: "GET", headers: { Accept: "application/json" } });
|
|
1058
|
-
if (!response.ok) return [];
|
|
1059
|
-
return await response.json() || [];
|
|
1060
|
-
} catch {
|
|
1061
|
-
return [];
|
|
1062
|
-
}
|
|
1014
|
+
return await authFetch(`${API_BASE3}/doing/planItems/presenter/${churchId}/${planId}`) || [];
|
|
1063
1015
|
}
|
|
1064
1016
|
async function fetchVenueFeed(venueId) {
|
|
1065
|
-
|
|
1066
|
-
const response = await fetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
|
|
1067
|
-
if (!response.ok) {
|
|
1068
|
-
console.warn(`[B1Church] fetchVenueFeed failed: HTTP ${response.status} for venueId=${venueId}`);
|
|
1069
|
-
return null;
|
|
1070
|
-
}
|
|
1071
|
-
return await response.json();
|
|
1072
|
-
} catch (err) {
|
|
1073
|
-
console.error(`[B1Church] fetchVenueFeed error: venueId=${venueId}`, err);
|
|
1074
|
-
return null;
|
|
1075
|
-
}
|
|
1017
|
+
return authFetch(`${LESSONS_API_BASE}/venues/public/feed/${venueId}`);
|
|
1076
1018
|
}
|
|
1077
1019
|
async function fetchVenueActions(venueId) {
|
|
1078
|
-
|
|
1079
|
-
const response = await fetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`, { method: "GET", headers: { Accept: "application/json" } });
|
|
1080
|
-
if (!response.ok) {
|
|
1081
|
-
console.warn(`[B1Church] fetchVenueActions failed: HTTP ${response.status} for venueId=${venueId}`);
|
|
1082
|
-
return null;
|
|
1083
|
-
}
|
|
1084
|
-
return await response.json();
|
|
1085
|
-
} catch (err) {
|
|
1086
|
-
console.error(`[B1Church] fetchVenueActions error: venueId=${venueId}`, err);
|
|
1087
|
-
return null;
|
|
1088
|
-
}
|
|
1020
|
+
return authFetch(`${LESSONS_API_BASE}/venues/public/actions/${venueId}`);
|
|
1089
1021
|
}
|
|
1090
1022
|
async function fetchVenueImages(venueIds) {
|
|
1091
1023
|
const map = /* @__PURE__ */ new Map();
|
|
1092
1024
|
if (venueIds.length === 0) return map;
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
if (!response.ok) return map;
|
|
1097
|
-
const data = await response.json();
|
|
1098
|
-
for (const row of data) {
|
|
1099
|
-
if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
|
|
1100
|
-
}
|
|
1101
|
-
} catch {
|
|
1025
|
+
const data = await authFetch(`${LESSONS_API_BASE}/venues/public/images?ids=${venueIds.join(",")}`);
|
|
1026
|
+
for (const row of data || []) {
|
|
1027
|
+
if (row.venueId && row.lessonImage) map.set(row.venueId, row.lessonImage);
|
|
1102
1028
|
}
|
|
1103
1029
|
return map;
|
|
1104
1030
|
}
|
|
@@ -1187,18 +1113,7 @@ function getFileFromProviderFileItem(item) {
|
|
|
1187
1113
|
};
|
|
1188
1114
|
}
|
|
1189
1115
|
function planItemToInstruction(item, thumbnail) {
|
|
1190
|
-
|
|
1191
|
-
switch (item.itemType) {
|
|
1192
|
-
case "lessonSection":
|
|
1193
|
-
itemType = "section";
|
|
1194
|
-
break;
|
|
1195
|
-
case "lessonAction":
|
|
1196
|
-
itemType = "action";
|
|
1197
|
-
break;
|
|
1198
|
-
case "lessonAddOn":
|
|
1199
|
-
itemType = "action";
|
|
1200
|
-
break;
|
|
1201
|
-
}
|
|
1116
|
+
const itemType = normalizeItemType(item.itemType);
|
|
1202
1117
|
const isFileType = itemType === "file" || item.link && !item.children?.length;
|
|
1203
1118
|
return {
|
|
1204
1119
|
id: item.id,
|
|
@@ -1212,23 +1127,6 @@ function planItemToInstruction(item, thumbnail) {
|
|
|
1212
1127
|
children: item.children?.map((child) => planItemToInstruction(child, thumbnail))
|
|
1213
1128
|
};
|
|
1214
1129
|
}
|
|
1215
|
-
function getEmbedUrl(apiType, relatedId) {
|
|
1216
|
-
if (!relatedId) return void 0;
|
|
1217
|
-
const baseUrl = "https://lessons.church";
|
|
1218
|
-
switch (apiType) {
|
|
1219
|
-
case "action":
|
|
1220
|
-
case "lessonAction":
|
|
1221
|
-
return `${baseUrl}/embed/action/${relatedId}`;
|
|
1222
|
-
case "addon":
|
|
1223
|
-
case "lessonAddOn":
|
|
1224
|
-
return `${baseUrl}/embed/addon/${relatedId}`;
|
|
1225
|
-
case "section":
|
|
1226
|
-
case "lessonSection":
|
|
1227
|
-
return `${baseUrl}/embed/section/${relatedId}`;
|
|
1228
|
-
default:
|
|
1229
|
-
return void 0;
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
1130
|
function buildSectionActionsMap2(actionsResponse, thumbnail) {
|
|
1233
1131
|
const sectionActionsMap = /* @__PURE__ */ new Map();
|
|
1234
1132
|
if (actionsResponse?.sections) {
|
|
@@ -1342,9 +1240,9 @@ function isExternalProviderItem(item) {
|
|
|
1342
1240
|
const itemType = item.itemType || "";
|
|
1343
1241
|
return itemType.startsWith("provider");
|
|
1344
1242
|
}
|
|
1345
|
-
var B1ChurchProvider = class {
|
|
1243
|
+
var B1ChurchProvider = class extends BaseProvider {
|
|
1346
1244
|
constructor() {
|
|
1347
|
-
|
|
1245
|
+
super(...arguments);
|
|
1348
1246
|
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
1349
1247
|
// Unified cache for external provider data to avoid duplicate calls across methods
|
|
1350
1248
|
this.externalContentCache = {
|
|
@@ -1355,16 +1253,13 @@ var B1ChurchProvider = class {
|
|
|
1355
1253
|
this.id = "b1church";
|
|
1356
1254
|
this.name = "B1.Church";
|
|
1357
1255
|
this.logos = { light: "https://b1.church/b1-church-logo.png", dark: "https://b1.church/b1-church-logo.png" };
|
|
1358
|
-
this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE3}/doing`, oauthBase: `${API_BASE3}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize"
|
|
1256
|
+
this.config = { id: "b1church", name: "B1.Church", apiBase: `${API_BASE3}/doing`, oauthBase: `${API_BASE3}/membership/oauth`, clientId: "nsowldn58dk", scopes: ["plans"], supportsDeviceFlow: true, deviceAuthEndpoint: "/device/authorize" };
|
|
1359
1257
|
this.appBase = "https://admin.b1.church";
|
|
1360
1258
|
this.requiresAuth = true;
|
|
1361
1259
|
this.authTypes = ["oauth_pkce", "device_flow"];
|
|
1362
1260
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
1363
1261
|
this.planTypeId = null;
|
|
1364
1262
|
}
|
|
1365
|
-
async apiRequest(path, authData) {
|
|
1366
|
-
return this.apiHelper.apiRequest(this.config, this.id, path, authData);
|
|
1367
|
-
}
|
|
1368
1263
|
async buildAuthUrl(codeVerifier, redirectUri, state) {
|
|
1369
1264
|
return buildB1AuthUrl(this.config, this.appBase, redirectUri, codeVerifier, state);
|
|
1370
1265
|
}
|
|
@@ -1510,8 +1405,7 @@ var B1ChurchProvider = class {
|
|
|
1510
1405
|
console.warn("[B1Church getInstructions] planFolder missing churchId:", planFolder.id);
|
|
1511
1406
|
return null;
|
|
1512
1407
|
}
|
|
1513
|
-
const
|
|
1514
|
-
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1408
|
+
const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
|
|
1515
1409
|
if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
|
|
1516
1410
|
const externalInstructions = await fetchFromProviderProxy(
|
|
1517
1411
|
"getInstructions",
|
|
@@ -1619,8 +1513,7 @@ var B1ChurchProvider = class {
|
|
|
1619
1513
|
console.warn("[B1Church] getPlaylist: planFolder missing churchId:", planFolder.id);
|
|
1620
1514
|
return null;
|
|
1621
1515
|
}
|
|
1622
|
-
const
|
|
1623
|
-
const planItems = await this.apiRequest(pathFn(churchId, planId), authData);
|
|
1516
|
+
const planItems = await this.apiRequest(`/planFeed/presenter/${churchId}/${planId}`, authData);
|
|
1624
1517
|
if ((!planItems || planItems.length === 0) && planFolder.providerId && planFolder.providerPlanId) {
|
|
1625
1518
|
const externalFiles = await fetchFromProviderProxy(
|
|
1626
1519
|
"getPlaylist",
|
|
@@ -1715,9 +1608,6 @@ var B1ChurchProvider = class {
|
|
|
1715
1608
|
}
|
|
1716
1609
|
return files;
|
|
1717
1610
|
}
|
|
1718
|
-
supportsDeviceFlow() {
|
|
1719
|
-
return !!this.config.supportsDeviceFlow;
|
|
1720
|
-
}
|
|
1721
1611
|
};
|
|
1722
1612
|
|
|
1723
1613
|
// src/providers/dropbox/DropboxConverters.ts
|
|
@@ -1743,38 +1633,13 @@ function filterMediaEntries(entries) {
|
|
|
1743
1633
|
}
|
|
1744
1634
|
return { folders, mediaFiles };
|
|
1745
1635
|
}
|
|
1746
|
-
function buildInstructionsFromFiles(files, folderName) {
|
|
1747
|
-
const actionItems = files.map((file) => ({
|
|
1748
|
-
id: file.id + "-action",
|
|
1749
|
-
itemType: "action",
|
|
1750
|
-
label: file.title,
|
|
1751
|
-
actionType: "play",
|
|
1752
|
-
children: [
|
|
1753
|
-
{
|
|
1754
|
-
id: file.id,
|
|
1755
|
-
itemType: "file",
|
|
1756
|
-
label: file.title,
|
|
1757
|
-
seconds: file.seconds,
|
|
1758
|
-
downloadUrl: file.downloadUrl || file.url,
|
|
1759
|
-
thumbnail: file.thumbnail
|
|
1760
|
-
}
|
|
1761
|
-
]
|
|
1762
|
-
}));
|
|
1763
|
-
const sectionItem = {
|
|
1764
|
-
id: "dropbox-section",
|
|
1765
|
-
itemType: "section",
|
|
1766
|
-
label: folderName,
|
|
1767
|
-
children: actionItems
|
|
1768
|
-
};
|
|
1769
|
-
return { name: folderName, items: [sectionItem] };
|
|
1770
|
-
}
|
|
1771
1636
|
|
|
1772
1637
|
// src/providers/dropbox/DropboxProvider.ts
|
|
1773
1638
|
var MAX_PAGINATION_CALLS = 50;
|
|
1774
|
-
var DropboxProvider = class {
|
|
1639
|
+
var DropboxProvider = class extends BaseProvider {
|
|
1775
1640
|
constructor() {
|
|
1641
|
+
super(...arguments);
|
|
1776
1642
|
this.oauthHelper = new OAuthHelper();
|
|
1777
|
-
this.apiHelper = new ApiHelper();
|
|
1778
1643
|
this.id = "dropbox";
|
|
1779
1644
|
this.name = "Dropbox";
|
|
1780
1645
|
this.logos = {
|
|
@@ -1787,12 +1652,7 @@ var DropboxProvider = class {
|
|
|
1787
1652
|
apiBase: "https://api.dropboxapi.com",
|
|
1788
1653
|
oauthBase: "https://www.dropbox.com/oauth2",
|
|
1789
1654
|
clientId: "9io0q0q9angdz9j",
|
|
1790
|
-
scopes: []
|
|
1791
|
-
endpoints: {
|
|
1792
|
-
listFolder: "/2/files/list_folder",
|
|
1793
|
-
listFolderContinue: "/2/files/list_folder/continue",
|
|
1794
|
-
getTemporaryLink: "/2/files/get_temporary_link"
|
|
1795
|
-
}
|
|
1655
|
+
scopes: []
|
|
1796
1656
|
};
|
|
1797
1657
|
this.requiresAuth = true;
|
|
1798
1658
|
this.authTypes = ["oauth_pkce"];
|
|
@@ -1809,12 +1669,12 @@ var DropboxProvider = class {
|
|
|
1809
1669
|
console.error("[Dropbox] No auth token provided");
|
|
1810
1670
|
return Promise.resolve(null);
|
|
1811
1671
|
}
|
|
1812
|
-
return this.
|
|
1672
|
+
return this.apiRequest(endpoint, auth, "POST", body);
|
|
1813
1673
|
}
|
|
1814
1674
|
async listAllEntries(dropboxPath, auth) {
|
|
1815
1675
|
const allEntries = [];
|
|
1816
1676
|
const response = await this.dropboxPost(
|
|
1817
|
-
|
|
1677
|
+
"/2/files/list_folder",
|
|
1818
1678
|
{ path: dropboxPath, recursive: false, include_media_info: false },
|
|
1819
1679
|
auth
|
|
1820
1680
|
);
|
|
@@ -1825,7 +1685,7 @@ var DropboxProvider = class {
|
|
|
1825
1685
|
let calls = 0;
|
|
1826
1686
|
while (hasMore && calls < MAX_PAGINATION_CALLS) {
|
|
1827
1687
|
const cont = await this.dropboxPost(
|
|
1828
|
-
|
|
1688
|
+
"/2/files/list_folder/continue",
|
|
1829
1689
|
{ cursor },
|
|
1830
1690
|
auth
|
|
1831
1691
|
);
|
|
@@ -1875,7 +1735,7 @@ var DropboxProvider = class {
|
|
|
1875
1735
|
}
|
|
1876
1736
|
async getTemporaryLink(filePath, auth) {
|
|
1877
1737
|
const response = await this.dropboxPost(
|
|
1878
|
-
|
|
1738
|
+
"/2/files/get_temporary_link",
|
|
1879
1739
|
{ path: filePath },
|
|
1880
1740
|
auth
|
|
1881
1741
|
);
|
|
@@ -1896,7 +1756,7 @@ var DropboxProvider = class {
|
|
|
1896
1756
|
// -- Leaf detection --
|
|
1897
1757
|
async checkIsLeaf(folderPath, auth) {
|
|
1898
1758
|
const response = await this.dropboxPost(
|
|
1899
|
-
|
|
1759
|
+
"/2/files/list_folder",
|
|
1900
1760
|
{ path: folderPath, recursive: false, include_media_info: false },
|
|
1901
1761
|
auth
|
|
1902
1762
|
);
|
|
@@ -1915,31 +1775,29 @@ var DropboxProvider = class {
|
|
|
1915
1775
|
const fileItems = await this.resolveFileLinks(mediaFiles, auth);
|
|
1916
1776
|
return [...folderItems, ...fileItems];
|
|
1917
1777
|
}
|
|
1918
|
-
|
|
1778
|
+
/** Resolve a folder path to playable files (list + media filter + link resolution). */
|
|
1779
|
+
async resolveMediaFiles(path, auth) {
|
|
1919
1780
|
if (!path || path === "/") return null;
|
|
1920
1781
|
const dropboxPath = path.startsWith("/") ? path : "/" + path;
|
|
1921
1782
|
const allEntries = await this.listAllEntries(dropboxPath, auth);
|
|
1922
1783
|
const { mediaFiles } = filterMediaEntries(allEntries);
|
|
1923
1784
|
if (mediaFiles.length === 0) return null;
|
|
1924
1785
|
const files = await this.resolveFileLinks(mediaFiles, auth);
|
|
1925
|
-
|
|
1786
|
+
if (files.length === 0) return null;
|
|
1787
|
+
return { files, dropboxPath };
|
|
1788
|
+
}
|
|
1789
|
+
async getPlaylist(path, auth, _resolution) {
|
|
1790
|
+
const resolved = await this.resolveMediaFiles(path, auth);
|
|
1791
|
+
return resolved?.files ?? null;
|
|
1926
1792
|
}
|
|
1927
1793
|
async getInstructions(path, auth) {
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
const
|
|
1931
|
-
const { mediaFiles } = filterMediaEntries(allEntries);
|
|
1932
|
-
if (mediaFiles.length === 0) return null;
|
|
1933
|
-
const files = await this.resolveFileLinks(mediaFiles, auth);
|
|
1934
|
-
if (files.length === 0) return null;
|
|
1935
|
-
const segments = dropboxPath.split("/").filter(Boolean);
|
|
1794
|
+
const resolved = await this.resolveMediaFiles(path, auth);
|
|
1795
|
+
if (!resolved) return null;
|
|
1796
|
+
const segments = resolved.dropboxPath.split("/").filter(Boolean);
|
|
1936
1797
|
const folderName = segments[segments.length - 1] || "Dropbox";
|
|
1937
|
-
return
|
|
1798
|
+
return filesToInstructions(folderName, resolved.files, { id: "dropbox-section", label: folderName });
|
|
1938
1799
|
}
|
|
1939
1800
|
// -- Auth methods --
|
|
1940
|
-
supportsDeviceFlow() {
|
|
1941
|
-
return false;
|
|
1942
|
-
}
|
|
1943
1801
|
generateCodeVerifier() {
|
|
1944
1802
|
return this.oauthHelper.generateCodeVerifier();
|
|
1945
1803
|
}
|
|
@@ -1976,61 +1834,43 @@ var DropboxProvider = class {
|
|
|
1976
1834
|
body: params.toString()
|
|
1977
1835
|
});
|
|
1978
1836
|
if (!response.ok) return null;
|
|
1979
|
-
|
|
1980
|
-
return {
|
|
1981
|
-
access_token: data.access_token,
|
|
1982
|
-
refresh_token: data.refresh_token,
|
|
1983
|
-
token_type: data.token_type || "Bearer",
|
|
1984
|
-
created_at: Math.floor(Date.now() / 1e3),
|
|
1985
|
-
expires_in: data.expires_in,
|
|
1986
|
-
scope: data.scope || ""
|
|
1987
|
-
};
|
|
1837
|
+
return toAuthData(await response.json());
|
|
1988
1838
|
} catch {
|
|
1989
1839
|
return null;
|
|
1990
1840
|
}
|
|
1991
1841
|
}
|
|
1992
1842
|
};
|
|
1993
1843
|
|
|
1994
|
-
// src/providers/planningCenter/
|
|
1995
|
-
var
|
|
1844
|
+
// src/providers/planningCenter/PlanningCenterProvider.ts
|
|
1845
|
+
var ONE_WEEK_MS = 6048e5;
|
|
1996
1846
|
function formatDate(dateString) {
|
|
1997
|
-
|
|
1998
|
-
return date.toISOString().slice(0, 10);
|
|
1847
|
+
return new Date(dateString).toISOString().slice(0, 10);
|
|
1999
1848
|
}
|
|
2000
|
-
|
|
2001
|
-
// src/providers/planningCenter/PlanningCenterProvider.ts
|
|
2002
|
-
var PlanningCenterProvider = class {
|
|
1849
|
+
var PlanningCenterProvider = class extends BaseProvider {
|
|
2003
1850
|
constructor() {
|
|
2004
|
-
|
|
1851
|
+
super(...arguments);
|
|
2005
1852
|
this.id = "planningcenter";
|
|
2006
1853
|
this.name = "Planning Center";
|
|
2007
1854
|
this.logos = { light: "https://www.planningcenter.com/icons/icon-512x512.png", dark: "https://www.planningcenter.com/icons/icon-512x512.png" };
|
|
2008
|
-
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"]
|
|
2009
|
-
this.ONE_WEEK_MS = 6048e5;
|
|
1855
|
+
this.config = { id: "planningcenter", name: "Planning Center", apiBase: "https://api.planningcenteronline.com", oauthBase: "https://api.planningcenteronline.com/oauth", clientId: "", scopes: ["services"] };
|
|
2010
1856
|
this.requiresAuth = true;
|
|
2011
1857
|
this.authTypes = ["oauth_pkce"];
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
async apiRequest(path, auth) {
|
|
2015
|
-
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
1858
|
+
// Plan items come back without media URLs, so playlist/instructions are not actually supported yet.
|
|
1859
|
+
this.capabilities = { browse: true, playlist: false, instructions: false, mediaLicensing: false };
|
|
2016
1860
|
}
|
|
2017
1861
|
async browse(path, auth) {
|
|
2018
1862
|
const { segments, depth } = parsePath(path);
|
|
2019
1863
|
if (depth === 0) {
|
|
2020
1864
|
return [{ type: "folder", id: "serviceTypes-root", title: "Service Types", path: "/serviceTypes" }];
|
|
2021
1865
|
}
|
|
2022
|
-
|
|
2023
|
-
if (root !== "serviceTypes") return [];
|
|
1866
|
+
if (segments[0] !== "serviceTypes") return [];
|
|
2024
1867
|
if (depth === 1) return this.getServiceTypes(auth);
|
|
2025
1868
|
if (depth === 2) return this.getPlans(segments[1], path, auth);
|
|
2026
1869
|
if (depth === 3) return this.getPlanItems(segments[1], segments[2], auth);
|
|
2027
1870
|
return [];
|
|
2028
1871
|
}
|
|
2029
1872
|
async getServiceTypes(auth) {
|
|
2030
|
-
const response = await this.apiRequest(
|
|
2031
|
-
this.config.endpoints.serviceTypes,
|
|
2032
|
-
auth
|
|
2033
|
-
);
|
|
1873
|
+
const response = await this.apiRequest("/services/v2/service_types", auth);
|
|
2034
1874
|
if (!response?.data) return [];
|
|
2035
1875
|
return response.data.map((serviceType) => ({
|
|
2036
1876
|
type: "folder",
|
|
@@ -2040,9 +1880,8 @@ var PlanningCenterProvider = class {
|
|
|
2040
1880
|
}));
|
|
2041
1881
|
}
|
|
2042
1882
|
async getPlans(serviceTypeId, currentPath, auth) {
|
|
2043
|
-
const pathFn = this.config.endpoints.plans;
|
|
2044
1883
|
const response = await this.apiRequest(
|
|
2045
|
-
|
|
1884
|
+
`/services/v2/service_types/${serviceTypeId}/plans?filter=future&order=sort_date`,
|
|
2046
1885
|
auth
|
|
2047
1886
|
);
|
|
2048
1887
|
if (!response?.data) return [];
|
|
@@ -2050,7 +1889,7 @@ var PlanningCenterProvider = class {
|
|
|
2050
1889
|
const filteredPlans = response.data.filter((plan) => {
|
|
2051
1890
|
if (plan.attributes.items_count === 0) return false;
|
|
2052
1891
|
const planDate = new Date(plan.attributes.sort_date).getTime();
|
|
2053
|
-
return planDate < now +
|
|
1892
|
+
return planDate < now + ONE_WEEK_MS;
|
|
2054
1893
|
});
|
|
2055
1894
|
return filteredPlans.map((plan) => ({
|
|
2056
1895
|
type: "folder",
|
|
@@ -2061,17 +1900,13 @@ var PlanningCenterProvider = class {
|
|
|
2061
1900
|
}));
|
|
2062
1901
|
}
|
|
2063
1902
|
async getPlanItems(serviceTypeId, planId, auth) {
|
|
2064
|
-
const pathFn = this.config.endpoints.planItems;
|
|
2065
1903
|
const response = await this.apiRequest(
|
|
2066
|
-
|
|
1904
|
+
`/services/v2/service_types/${serviceTypeId}/plans/${planId}/items?per_page=100`,
|
|
2067
1905
|
auth
|
|
2068
1906
|
);
|
|
2069
1907
|
if (!response?.data) return [];
|
|
2070
1908
|
return response.data.map((item) => ({ type: "file", id: item.id, title: item.attributes.title || "", mediaType: "image", url: "" }));
|
|
2071
1909
|
}
|
|
2072
|
-
supportsDeviceFlow() {
|
|
2073
|
-
return false;
|
|
2074
|
-
}
|
|
2075
1910
|
};
|
|
2076
1911
|
|
|
2077
1912
|
// src/providers/bibleProject/data.json
|
|
@@ -4466,8 +4301,9 @@ var data_default = {
|
|
|
4466
4301
|
};
|
|
4467
4302
|
|
|
4468
4303
|
// src/providers/bibleProject/BibleProjectProvider.ts
|
|
4469
|
-
var BibleProjectProvider = class {
|
|
4304
|
+
var BibleProjectProvider = class extends BaseProvider {
|
|
4470
4305
|
constructor() {
|
|
4306
|
+
super(...arguments);
|
|
4471
4307
|
this.id = "bibleproject";
|
|
4472
4308
|
this.name = "The Bible Project";
|
|
4473
4309
|
this.logos = {
|
|
@@ -4480,10 +4316,8 @@ var BibleProjectProvider = class {
|
|
|
4480
4316
|
apiBase: "https://bibleproject.com",
|
|
4481
4317
|
oauthBase: "",
|
|
4482
4318
|
clientId: "",
|
|
4483
|
-
scopes: []
|
|
4484
|
-
endpoints: { downloads: "/downloads/" }
|
|
4319
|
+
scopes: []
|
|
4485
4320
|
};
|
|
4486
|
-
this.data = data_default;
|
|
4487
4321
|
this.requiresAuth = false;
|
|
4488
4322
|
this.authTypes = ["none"];
|
|
4489
4323
|
this.capabilities = {
|
|
@@ -4492,133 +4326,71 @@ var BibleProjectProvider = class {
|
|
|
4492
4326
|
instructions: true,
|
|
4493
4327
|
mediaLicensing: false
|
|
4494
4328
|
};
|
|
4329
|
+
this.data = data_default;
|
|
4495
4330
|
}
|
|
4496
4331
|
getMuxThumbnail(muxPlaybackId, width = 400, height = 225) {
|
|
4497
4332
|
return `https://image.mux.com/${muxPlaybackId}/thumbnail.jpg?width=${width}&height=${height}`;
|
|
4498
4333
|
}
|
|
4499
|
-
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
return this.getCollections();
|
|
4503
|
-
}
|
|
4504
|
-
if (depth === 1) {
|
|
4505
|
-
const collectionSlug = segments[0];
|
|
4506
|
-
return this.getLessonFolders(collectionSlug, path);
|
|
4507
|
-
}
|
|
4508
|
-
if (depth === 2) {
|
|
4509
|
-
const collectionSlug = segments[0];
|
|
4510
|
-
const videoId = segments[1];
|
|
4511
|
-
return this.getVideoFile(collectionSlug, videoId);
|
|
4512
|
-
}
|
|
4513
|
-
return [];
|
|
4514
|
-
}
|
|
4515
|
-
getCollections() {
|
|
4516
|
-
return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
|
|
4517
|
-
type: "folder",
|
|
4518
|
-
id: slugify(collection.name),
|
|
4519
|
-
title: collection.name,
|
|
4520
|
-
thumbnail: collection.image || void 0,
|
|
4521
|
-
path: `/${slugify(collection.name)}`
|
|
4522
|
-
}));
|
|
4523
|
-
}
|
|
4524
|
-
getLessonFolders(collectionSlug, currentPath) {
|
|
4525
|
-
const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
|
|
4526
|
-
if (!collection) return [];
|
|
4527
|
-
return collection.videos.map((video) => ({
|
|
4528
|
-
type: "folder",
|
|
4529
|
-
id: video.id,
|
|
4530
|
-
title: video.title,
|
|
4334
|
+
videoToFile(video) {
|
|
4335
|
+
return createFile(video.id, video.title, video.videoUrl, {
|
|
4336
|
+
mediaType: "video",
|
|
4531
4337
|
thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
})
|
|
4535
|
-
}
|
|
4536
|
-
getVideoFile(collectionSlug, videoId) {
|
|
4537
|
-
const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
|
|
4538
|
-
if (!collection) return [];
|
|
4539
|
-
const video = collection.videos.find((v) => v.id === videoId);
|
|
4540
|
-
if (!video) return [];
|
|
4541
|
-
return [createFile(video.id, video.title, video.videoUrl, { mediaType: "video", muxPlaybackId: video.muxPlaybackId, seconds: 0 })];
|
|
4338
|
+
muxPlaybackId: video.muxPlaybackId,
|
|
4339
|
+
seconds: 0
|
|
4340
|
+
});
|
|
4542
4341
|
}
|
|
4543
|
-
|
|
4342
|
+
/** Resolve a path to its collection and files: all videos at depth 1, a single video at depth 2. */
|
|
4343
|
+
resolve(path) {
|
|
4544
4344
|
const { segments, depth } = parsePath(path);
|
|
4545
|
-
if (depth < 1) return null;
|
|
4546
|
-
const
|
|
4547
|
-
const collection = this.data.collections.find((c) => slugify(c.name) === collectionSlug);
|
|
4345
|
+
if (depth < 1 || depth > 2) return null;
|
|
4346
|
+
const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
|
|
4548
4347
|
if (!collection) return null;
|
|
4549
4348
|
if (depth === 1) {
|
|
4550
|
-
|
|
4551
|
-
return files.length > 0 ? files : null;
|
|
4349
|
+
return { collection, files: collection.videos.map((v) => this.videoToFile(v)), name: collection.name };
|
|
4552
4350
|
}
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
if (!video) return null;
|
|
4557
|
-
return [{ type: "file", id: video.id, title: video.title, mediaType: "video", url: video.videoUrl, thumbnail: this.getMuxThumbnail(video.muxPlaybackId), muxPlaybackId: video.muxPlaybackId, seconds: 0 }];
|
|
4558
|
-
}
|
|
4559
|
-
return null;
|
|
4351
|
+
const video = collection.videos.find((v) => v.id === segments[1]);
|
|
4352
|
+
if (!video) return null;
|
|
4353
|
+
return { collection, files: [this.videoToFile(video)], name: video.title };
|
|
4560
4354
|
}
|
|
4561
|
-
async
|
|
4355
|
+
async browse(path, _auth) {
|
|
4562
4356
|
const { segments, depth } = parsePath(path);
|
|
4563
|
-
if (depth
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4357
|
+
if (depth === 0) {
|
|
4358
|
+
return this.data.collections.filter((collection) => collection.videos.length > 0).map((collection) => ({
|
|
4359
|
+
type: "folder",
|
|
4360
|
+
id: slugify(collection.name),
|
|
4361
|
+
title: collection.name,
|
|
4362
|
+
thumbnail: collection.image || void 0,
|
|
4363
|
+
path: `/${slugify(collection.name)}`
|
|
4364
|
+
}));
|
|
4365
|
+
}
|
|
4567
4366
|
if (depth === 1) {
|
|
4568
|
-
const
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
label: video.title,
|
|
4578
|
-
downloadUrl: video.videoUrl,
|
|
4579
|
-
thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
|
|
4580
|
-
}
|
|
4581
|
-
]
|
|
4367
|
+
const collection = this.data.collections.find((c) => slugify(c.name) === segments[0]);
|
|
4368
|
+
if (!collection) return [];
|
|
4369
|
+
return collection.videos.map((video) => ({
|
|
4370
|
+
type: "folder",
|
|
4371
|
+
id: video.id,
|
|
4372
|
+
title: video.title,
|
|
4373
|
+
thumbnail: this.getMuxThumbnail(video.muxPlaybackId),
|
|
4374
|
+
isLeaf: true,
|
|
4375
|
+
path: `${path}/${video.id}`
|
|
4582
4376
|
}));
|
|
4583
|
-
const sectionItem = {
|
|
4584
|
-
id: slugify(collection.name) + "-section",
|
|
4585
|
-
itemType: "section",
|
|
4586
|
-
label: collection.name,
|
|
4587
|
-
children: actionItems
|
|
4588
|
-
};
|
|
4589
|
-
return { name: collection.name, items: [sectionItem] };
|
|
4590
4377
|
}
|
|
4591
4378
|
if (depth === 2) {
|
|
4592
|
-
|
|
4593
|
-
const video = collection.videos.find((v) => v.id === videoId);
|
|
4594
|
-
if (!video) return null;
|
|
4595
|
-
const actionItem = {
|
|
4596
|
-
id: video.id + "-action",
|
|
4597
|
-
itemType: "action",
|
|
4598
|
-
label: video.title,
|
|
4599
|
-
actionType: "play",
|
|
4600
|
-
children: [
|
|
4601
|
-
{
|
|
4602
|
-
id: video.id,
|
|
4603
|
-
itemType: "file",
|
|
4604
|
-
label: video.title,
|
|
4605
|
-
downloadUrl: video.videoUrl,
|
|
4606
|
-
thumbnail: this.getMuxThumbnail(video.muxPlaybackId)
|
|
4607
|
-
}
|
|
4608
|
-
]
|
|
4609
|
-
};
|
|
4610
|
-
const sectionItem = {
|
|
4611
|
-
id: slugify(collection.name) + "-section",
|
|
4612
|
-
itemType: "section",
|
|
4613
|
-
label: collection.name,
|
|
4614
|
-
children: [actionItem]
|
|
4615
|
-
};
|
|
4616
|
-
return { name: video.title, items: [sectionItem] };
|
|
4379
|
+
return this.resolve(path)?.files ?? [];
|
|
4617
4380
|
}
|
|
4618
|
-
return
|
|
4381
|
+
return [];
|
|
4619
4382
|
}
|
|
4620
|
-
|
|
4621
|
-
|
|
4383
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
4384
|
+
const resolved = this.resolve(path);
|
|
4385
|
+
return resolved && resolved.files.length > 0 ? resolved.files : null;
|
|
4386
|
+
}
|
|
4387
|
+
async getInstructions(path, _auth) {
|
|
4388
|
+
const resolved = this.resolve(path);
|
|
4389
|
+
if (!resolved) return null;
|
|
4390
|
+
return filesToInstructions(resolved.name, resolved.files, {
|
|
4391
|
+
id: slugify(resolved.collection.name) + "-section",
|
|
4392
|
+
label: resolved.collection.name
|
|
4393
|
+
});
|
|
4622
4394
|
}
|
|
4623
4395
|
};
|
|
4624
4396
|
|
|
@@ -12242,8 +12014,9 @@ function buildLessonInstructions(lesson) {
|
|
|
12242
12014
|
}
|
|
12243
12015
|
|
|
12244
12016
|
// src/providers/highVoltage/HighVoltageKidsProvider.ts
|
|
12245
|
-
var HighVoltageKidsProvider = class {
|
|
12017
|
+
var HighVoltageKidsProvider = class extends BaseProvider {
|
|
12246
12018
|
constructor() {
|
|
12019
|
+
super(...arguments);
|
|
12247
12020
|
this.id = "highvoltagekids";
|
|
12248
12021
|
this.name = "High Voltage Kids";
|
|
12249
12022
|
this.logos = {
|
|
@@ -12256,8 +12029,7 @@ var HighVoltageKidsProvider = class {
|
|
|
12256
12029
|
apiBase: "https://highvoltagekids.com",
|
|
12257
12030
|
oauthBase: "",
|
|
12258
12031
|
clientId: "",
|
|
12259
|
-
scopes: []
|
|
12260
|
-
endpoints: { downloads: "/membership-downloads/" }
|
|
12032
|
+
scopes: []
|
|
12261
12033
|
};
|
|
12262
12034
|
this.data = data_default2;
|
|
12263
12035
|
this.requiresAuth = false;
|
|
@@ -12307,9 +12079,6 @@ var HighVoltageKidsProvider = class {
|
|
|
12307
12079
|
}
|
|
12308
12080
|
return null;
|
|
12309
12081
|
}
|
|
12310
|
-
supportsDeviceFlow() {
|
|
12311
|
-
return false;
|
|
12312
|
-
}
|
|
12313
12082
|
};
|
|
12314
12083
|
|
|
12315
12084
|
// src/providers/jesusFilm/JesusFilmProvider.ts
|
|
@@ -12321,8 +12090,14 @@ var CATEGORY_MAP = {
|
|
|
12321
12090
|
"series": "series",
|
|
12322
12091
|
"collections": "collection"
|
|
12323
12092
|
};
|
|
12324
|
-
var
|
|
12093
|
+
var CATEGORY_NAMES = {
|
|
12094
|
+
"feature-films": "Feature Films",
|
|
12095
|
+
"series": "Series",
|
|
12096
|
+
"collections": "Collections"
|
|
12097
|
+
};
|
|
12098
|
+
var JesusFilmProvider = class extends BaseProvider {
|
|
12325
12099
|
constructor() {
|
|
12100
|
+
super(...arguments);
|
|
12326
12101
|
this.id = "jesusfilm";
|
|
12327
12102
|
this.name = "Jesus Film Project";
|
|
12328
12103
|
this.logos = {
|
|
@@ -12335,8 +12110,7 @@ var JesusFilmProvider = class {
|
|
|
12335
12110
|
apiBase: API_BASE4,
|
|
12336
12111
|
oauthBase: "",
|
|
12337
12112
|
clientId: "",
|
|
12338
|
-
scopes: []
|
|
12339
|
-
endpoints: { watch: "/watch" }
|
|
12113
|
+
scopes: []
|
|
12340
12114
|
};
|
|
12341
12115
|
this.requiresAuth = false;
|
|
12342
12116
|
this.authTypes = ["none"];
|
|
@@ -12347,6 +12121,7 @@ var JesusFilmProvider = class {
|
|
|
12347
12121
|
mediaLicensing: false
|
|
12348
12122
|
};
|
|
12349
12123
|
}
|
|
12124
|
+
// Not ApiHelper: Arclight needs the apiKey query param and callers rely on throw-on-error.
|
|
12350
12125
|
async fetchApi(path) {
|
|
12351
12126
|
const separator = path.includes("?") ? "&" : "?";
|
|
12352
12127
|
const url = `${API_BASE4}${path}${separator}apiKey=${API_KEY}`;
|
|
@@ -12354,6 +12129,12 @@ var JesusFilmProvider = class {
|
|
|
12354
12129
|
if (!response.ok) throw new Error(`Arclight API error: ${response.status}`);
|
|
12355
12130
|
return response.json();
|
|
12356
12131
|
}
|
|
12132
|
+
async fetchCategoryComponents(subType) {
|
|
12133
|
+
const data = await this.fetchApi(
|
|
12134
|
+
`/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
|
|
12135
|
+
);
|
|
12136
|
+
return data._embedded.mediaComponents;
|
|
12137
|
+
}
|
|
12357
12138
|
async browse(path, _auth) {
|
|
12358
12139
|
const { segments, depth } = parsePath(path);
|
|
12359
12140
|
if (depth === 0) return this.getCategories();
|
|
@@ -12376,19 +12157,14 @@ var JesusFilmProvider = class {
|
|
|
12376
12157
|
async getItemsInCategory(category) {
|
|
12377
12158
|
const subType = CATEGORY_MAP[category];
|
|
12378
12159
|
if (!subType) return [];
|
|
12379
|
-
const
|
|
12380
|
-
|
|
12381
|
-
|
|
12382
|
-
|
|
12383
|
-
|
|
12384
|
-
|
|
12385
|
-
|
|
12386
|
-
|
|
12387
|
-
`/${category}/${item.mediaComponentId}`,
|
|
12388
|
-
item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
|
|
12389
|
-
isLeaf
|
|
12390
|
-
);
|
|
12391
|
-
});
|
|
12160
|
+
const components = await this.fetchCategoryComponents(subType);
|
|
12161
|
+
return components.map((item) => createFolder(
|
|
12162
|
+
item.mediaComponentId,
|
|
12163
|
+
item.title,
|
|
12164
|
+
`/${category}/${item.mediaComponentId}`,
|
|
12165
|
+
item.imageUrls.mobileCinematicHigh || item.imageUrls.videoStill || item.imageUrls.thumbnail,
|
|
12166
|
+
item.containsCount === 0
|
|
12167
|
+
));
|
|
12392
12168
|
}
|
|
12393
12169
|
async getVideoFile(mediaComponentId) {
|
|
12394
12170
|
const variant = await this.fetchApi(
|
|
@@ -12403,11 +12179,10 @@ var JesusFilmProvider = class {
|
|
|
12403
12179
|
const title = component?.title || mediaComponentId;
|
|
12404
12180
|
const seconds = component ? Math.round(component.lengthInMilliseconds / 1e3) : 0;
|
|
12405
12181
|
const thumbnail = component?.imageUrls.mobileCinematicHigh || component?.imageUrls.videoStill;
|
|
12406
|
-
const muxPlaybackId = this.extractMuxPlaybackId(downloadUrl);
|
|
12407
12182
|
return [
|
|
12408
12183
|
createFile(mediaComponentId, title, downloadUrl, {
|
|
12409
12184
|
mediaType: "video",
|
|
12410
|
-
muxPlaybackId,
|
|
12185
|
+
muxPlaybackId: this.extractMuxPlaybackId(downloadUrl),
|
|
12411
12186
|
seconds,
|
|
12412
12187
|
thumbnail
|
|
12413
12188
|
})
|
|
@@ -12419,9 +12194,8 @@ var JesusFilmProvider = class {
|
|
|
12419
12194
|
);
|
|
12420
12195
|
const childIds = linksData.linkedMediaComponentIds?.contains;
|
|
12421
12196
|
if (!childIds || childIds.length === 0) return [];
|
|
12422
|
-
const idsParam = childIds.join(",");
|
|
12423
12197
|
const childrenData = await this.fetchApi(
|
|
12424
|
-
`/media-components?ids=${
|
|
12198
|
+
`/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
|
|
12425
12199
|
);
|
|
12426
12200
|
if (!childrenData._embedded?.mediaComponents) return [];
|
|
12427
12201
|
return childrenData._embedded.mediaComponents.map((item) => createFolder(
|
|
@@ -12436,36 +12210,6 @@ var JesusFilmProvider = class {
|
|
|
12436
12210
|
const match = url.match(/stream\.mux\.com\/([^/]+)/);
|
|
12437
12211
|
return match ? match[1] : void 0;
|
|
12438
12212
|
}
|
|
12439
|
-
async getPlaylist(path, _auth, _resolution) {
|
|
12440
|
-
const { segments, depth } = parsePath(path);
|
|
12441
|
-
if (depth < 1) return null;
|
|
12442
|
-
const category = segments[0];
|
|
12443
|
-
if (depth >= 3) {
|
|
12444
|
-
const lastId = segments[depth - 1];
|
|
12445
|
-
const items = await this.getVideoFile(lastId);
|
|
12446
|
-
const files2 = items.filter((item) => item.type === "file");
|
|
12447
|
-
if (files2.length > 0) return files2;
|
|
12448
|
-
const containerFiles = await this.fetchContainerVideoFiles(lastId);
|
|
12449
|
-
return containerFiles.length > 0 ? containerFiles : null;
|
|
12450
|
-
}
|
|
12451
|
-
if (depth === 2) {
|
|
12452
|
-
if (category === "feature-films") {
|
|
12453
|
-
const items = await this.getVideoFile(segments[1]);
|
|
12454
|
-
if (items.length === 0) return null;
|
|
12455
|
-
return items.filter((item) => item.type === "file");
|
|
12456
|
-
}
|
|
12457
|
-
const files2 = await this.fetchContainerVideoFiles(segments[1]);
|
|
12458
|
-
return files2.length > 0 ? files2 : null;
|
|
12459
|
-
}
|
|
12460
|
-
const subType = CATEGORY_MAP[category];
|
|
12461
|
-
if (!subType) return null;
|
|
12462
|
-
const data = await this.fetchApi(
|
|
12463
|
-
`/media-components?filter=master&subTypes=${subType}&languageIds=${LANGUAGE_ID}&limit=100`
|
|
12464
|
-
);
|
|
12465
|
-
const contentComponents = data._embedded.mediaComponents.filter((item) => item.containsCount === 0);
|
|
12466
|
-
const files = await this.fetchVideoFiles(contentComponents);
|
|
12467
|
-
return files.length > 0 ? files : null;
|
|
12468
|
-
}
|
|
12469
12213
|
async fetchVideoFiles(components) {
|
|
12470
12214
|
const files = [];
|
|
12471
12215
|
for (const component of components) {
|
|
@@ -12496,9 +12240,8 @@ var JesusFilmProvider = class {
|
|
|
12496
12240
|
);
|
|
12497
12241
|
const childIds = linksData.linkedMediaComponentIds?.contains;
|
|
12498
12242
|
if (!childIds || childIds.length === 0) return [];
|
|
12499
|
-
const idsParam = childIds.join(",");
|
|
12500
12243
|
const childrenData = await this.fetchApi(
|
|
12501
|
-
`/media-components?ids=${
|
|
12244
|
+
`/media-components?ids=${childIds.join(",")}&languageIds=${LANGUAGE_ID}`
|
|
12502
12245
|
);
|
|
12503
12246
|
if (!childrenData._embedded?.mediaComponents) return [];
|
|
12504
12247
|
const contentItems = childrenData._embedded.mediaComponents.filter((c) => c.containsCount === 0);
|
|
@@ -12510,64 +12253,47 @@ var JesusFilmProvider = class {
|
|
|
12510
12253
|
}
|
|
12511
12254
|
return files;
|
|
12512
12255
|
}
|
|
12256
|
+
async getPlaylist(path, _auth, _resolution) {
|
|
12257
|
+
const { segments, depth } = parsePath(path);
|
|
12258
|
+
if (depth < 1) return null;
|
|
12259
|
+
const category = segments[0];
|
|
12260
|
+
if (depth >= 3) {
|
|
12261
|
+
const lastId = segments[depth - 1];
|
|
12262
|
+
const items = await this.getVideoFile(lastId);
|
|
12263
|
+
const files2 = items.filter((item) => item.type === "file");
|
|
12264
|
+
if (files2.length > 0) return files2;
|
|
12265
|
+
const containerFiles = await this.fetchContainerVideoFiles(lastId);
|
|
12266
|
+
return containerFiles.length > 0 ? containerFiles : null;
|
|
12267
|
+
}
|
|
12268
|
+
if (depth === 2) {
|
|
12269
|
+
if (category === "feature-films") {
|
|
12270
|
+
const items = await this.getVideoFile(segments[1]);
|
|
12271
|
+
if (items.length === 0) return null;
|
|
12272
|
+
return items.filter((item) => item.type === "file");
|
|
12273
|
+
}
|
|
12274
|
+
const files2 = await this.fetchContainerVideoFiles(segments[1]);
|
|
12275
|
+
return files2.length > 0 ? files2 : null;
|
|
12276
|
+
}
|
|
12277
|
+
const subType = CATEGORY_MAP[category];
|
|
12278
|
+
if (!subType) return null;
|
|
12279
|
+
const components = await this.fetchCategoryComponents(subType);
|
|
12280
|
+
const files = await this.fetchVideoFiles(components.filter((item) => item.containsCount === 0));
|
|
12281
|
+
return files.length > 0 ? files : null;
|
|
12282
|
+
}
|
|
12513
12283
|
async buildVideoInstructions(mediaComponentId, category) {
|
|
12514
12284
|
const items = await this.getVideoFile(mediaComponentId);
|
|
12515
|
-
|
|
12516
|
-
|
|
12517
|
-
|
|
12518
|
-
const actionItem = {
|
|
12519
|
-
id: mediaComponentId + "-action",
|
|
12520
|
-
itemType: "action",
|
|
12521
|
-
label: file.title,
|
|
12522
|
-
actionType: "play",
|
|
12523
|
-
children: [
|
|
12524
|
-
{
|
|
12525
|
-
id: mediaComponentId,
|
|
12526
|
-
itemType: "file",
|
|
12527
|
-
label: file.title,
|
|
12528
|
-
downloadUrl: file.url,
|
|
12529
|
-
thumbnail: file.thumbnail
|
|
12530
|
-
}
|
|
12531
|
-
]
|
|
12532
|
-
};
|
|
12533
|
-
const sectionItem = {
|
|
12534
|
-
id: category + "-section",
|
|
12535
|
-
itemType: "section",
|
|
12536
|
-
label: file.title,
|
|
12537
|
-
children: [actionItem]
|
|
12538
|
-
};
|
|
12539
|
-
return { name: file.title, items: [sectionItem] };
|
|
12285
|
+
const file = items.find((item) => item.type === "file");
|
|
12286
|
+
if (!file) return null;
|
|
12287
|
+
return filesToInstructions(file.title, [file], { id: category + "-section", label: file.title });
|
|
12540
12288
|
}
|
|
12541
|
-
async buildContainerInstructions(containerId
|
|
12289
|
+
async buildContainerInstructions(containerId) {
|
|
12542
12290
|
const containerData = await this.fetchApi(
|
|
12543
12291
|
`/media-components?ids=${containerId}&languageIds=${LANGUAGE_ID}`
|
|
12544
12292
|
);
|
|
12545
|
-
const
|
|
12546
|
-
const containerName = container?.title || containerId;
|
|
12293
|
+
const containerName = containerData._embedded?.mediaComponents?.[0]?.title || containerId;
|
|
12547
12294
|
const videoFiles = await this.fetchContainerVideoFiles(containerId);
|
|
12548
12295
|
if (videoFiles.length === 0) return { name: containerName, items: [] };
|
|
12549
|
-
|
|
12550
|
-
id: file.id + "-action",
|
|
12551
|
-
itemType: "action",
|
|
12552
|
-
label: file.title,
|
|
12553
|
-
actionType: "play",
|
|
12554
|
-
children: [
|
|
12555
|
-
{
|
|
12556
|
-
id: file.id,
|
|
12557
|
-
itemType: "file",
|
|
12558
|
-
label: file.title,
|
|
12559
|
-
downloadUrl: file.url,
|
|
12560
|
-
thumbnail: file.thumbnail
|
|
12561
|
-
}
|
|
12562
|
-
]
|
|
12563
|
-
}));
|
|
12564
|
-
const sectionItem = {
|
|
12565
|
-
id: containerId + "-section",
|
|
12566
|
-
itemType: "section",
|
|
12567
|
-
label: containerName,
|
|
12568
|
-
children: actionItems
|
|
12569
|
-
};
|
|
12570
|
-
return { name: containerName, items: [sectionItem] };
|
|
12296
|
+
return filesToInstructions(containerName, videoFiles, { id: containerId + "-section", label: containerName });
|
|
12571
12297
|
}
|
|
12572
12298
|
async getInstructions(path, _auth) {
|
|
12573
12299
|
const { segments, depth } = parsePath(path);
|
|
@@ -12576,61 +12302,19 @@ var JesusFilmProvider = class {
|
|
|
12576
12302
|
const subType = CATEGORY_MAP[category];
|
|
12577
12303
|
if (!subType) return null;
|
|
12578
12304
|
if (depth === 1) {
|
|
12579
|
-
const
|
|
12580
|
-
|
|
12581
|
-
);
|
|
12582
|
-
|
|
12583
|
-
const actionItems = await Promise.all(
|
|
12584
|
-
data._embedded.mediaComponents.map(async (component) => {
|
|
12585
|
-
let downloadUrl;
|
|
12586
|
-
if (component.containsCount === 0) {
|
|
12587
|
-
try {
|
|
12588
|
-
const variant = await this.fetchApi(
|
|
12589
|
-
`/media-components/${component.mediaComponentId}/languages/${LANGUAGE_ID}?platform=web`
|
|
12590
|
-
);
|
|
12591
|
-
downloadUrl = variant.downloadUrls.high?.url || variant.downloadUrls.low?.url;
|
|
12592
|
-
} catch {
|
|
12593
|
-
}
|
|
12594
|
-
}
|
|
12595
|
-
return {
|
|
12596
|
-
id: component.mediaComponentId + "-action",
|
|
12597
|
-
itemType: "action",
|
|
12598
|
-
label: component.title,
|
|
12599
|
-
actionType: "play",
|
|
12600
|
-
children: downloadUrl ? [
|
|
12601
|
-
{
|
|
12602
|
-
id: component.mediaComponentId,
|
|
12603
|
-
itemType: "file",
|
|
12604
|
-
label: component.title,
|
|
12605
|
-
downloadUrl,
|
|
12606
|
-
thumbnail: component.imageUrls.mobileCinematicHigh || component.imageUrls.videoStill
|
|
12607
|
-
}
|
|
12608
|
-
] : []
|
|
12609
|
-
};
|
|
12610
|
-
})
|
|
12611
|
-
);
|
|
12612
|
-
const sectionItem = {
|
|
12613
|
-
id: category + "-section",
|
|
12614
|
-
itemType: "section",
|
|
12615
|
-
label: categoryName,
|
|
12616
|
-
children: actionItems.filter((a) => a.children && a.children.length > 0)
|
|
12617
|
-
};
|
|
12618
|
-
return { name: categoryName, items: [sectionItem] };
|
|
12305
|
+
const categoryName = CATEGORY_NAMES[category];
|
|
12306
|
+
const components = await this.fetchCategoryComponents(subType);
|
|
12307
|
+
const files = await this.fetchVideoFiles(components.filter((c) => c.containsCount === 0));
|
|
12308
|
+
return filesToInstructions(categoryName, files, { id: category + "-section", label: categoryName });
|
|
12619
12309
|
}
|
|
12620
12310
|
if (depth === 2) {
|
|
12621
12311
|
if (category === "feature-films") return this.buildVideoInstructions(segments[1], category);
|
|
12622
|
-
return this.buildContainerInstructions(segments[1]
|
|
12623
|
-
}
|
|
12624
|
-
if (depth >= 3) {
|
|
12625
|
-
const lastId = segments[depth - 1];
|
|
12626
|
-
const videoResult = await this.buildVideoInstructions(lastId, category);
|
|
12627
|
-
if (videoResult) return videoResult;
|
|
12628
|
-
return this.buildContainerInstructions(lastId, category);
|
|
12312
|
+
return this.buildContainerInstructions(segments[1]);
|
|
12629
12313
|
}
|
|
12630
|
-
|
|
12631
|
-
|
|
12632
|
-
|
|
12633
|
-
return
|
|
12314
|
+
const lastId = segments[depth - 1];
|
|
12315
|
+
const videoResult = await this.buildVideoInstructions(lastId, category);
|
|
12316
|
+
if (videoResult) return videoResult;
|
|
12317
|
+
return this.buildContainerInstructions(lastId);
|
|
12634
12318
|
}
|
|
12635
12319
|
};
|
|
12636
12320
|
|
|
@@ -12674,35 +12358,15 @@ function convertPlaylistToFiles(playlist) {
|
|
|
12674
12358
|
return file;
|
|
12675
12359
|
});
|
|
12676
12360
|
}
|
|
12677
|
-
function convertPlaylistToInstructions(playlist) {
|
|
12678
|
-
const files = convertPlaylistToFiles(playlist);
|
|
12679
|
-
const items = files.map((file) => ({
|
|
12680
|
-
id: file.id + "-action",
|
|
12681
|
-
itemType: "action",
|
|
12682
|
-
label: file.title,
|
|
12683
|
-
actionType: "play",
|
|
12684
|
-
children: [
|
|
12685
|
-
{
|
|
12686
|
-
id: file.id,
|
|
12687
|
-
itemType: "file",
|
|
12688
|
-
label: file.title,
|
|
12689
|
-
seconds: file.seconds,
|
|
12690
|
-
downloadUrl: file.url,
|
|
12691
|
-
thumbnail: file.thumbnail
|
|
12692
|
-
}
|
|
12693
|
-
]
|
|
12694
|
-
}));
|
|
12695
|
-
return { name: playlist.lesson_title || "Lesson", items };
|
|
12696
|
-
}
|
|
12697
12361
|
|
|
12698
12362
|
// src/providers/cbn/logo.ts
|
|
12699
12363
|
var CBN_LOGO = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAZAAAAGQCAMAAAC3Ycb+AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA/tpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDcuMi1jMDAwIDc5LjFiNjVhNzliNCwgMjAyMi8wNi8xMy0yMjowMTowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ1dWlkOjVEMjA4OTI0OTNCRkRCMTE5MTRBODU5MEQzMTUwOEM4IiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjczNTFDMjdFNENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjczNTFDMjdENENEMDExRURCNUVBQkI4RUVDOUM5MkUwIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIElsbHVzdHJhdG9yIENDIDIwMTcgKE1hY2ludG9zaCkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0idXVpZDo4NGJiYzc1OC1iNjBhLTFlNGQtODJlMS1mYTQyNzhlMmZhMTciIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OTQzMjNlNWMtYjBlMS00YTliLWJjZWUtODljYjRlOGVjZjAwIi8+IDxkYzp0aXRsZT4gPHJkZjpBbHQ+IDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+Q0JOLUZMQU1FPC9yZGY6bGk+IDwvcmRmOkFsdD4gPC9kYzp0aXRsZT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz40pvOUAAAC2VBMVEUdmtYhnNf8/v/7/f4emtb9/v8fm9by+f0nntjU7Pf6/f4gm9b2+/0lntfk8/q43/L3+/5Nr9/5/P4poNg1pdovotnj8voknddQsd84ptsqoNjp9fve8PnX7fgpn9hRsd/1+/3M6Pba7vhmuuO/4vMinNfG5vUtodmGyenm9Pq64PKOzOrz+f2c0+15w+fw+PyVz+zx+fyq2fCl1+9tvuREq91ateGx3PHv+Pz4/P6d0+0mnthxwOXf8fng8fkroNlhuOI3pduk1u7B4/Ts9/zq9ftAqdxHrd54w+ft9/yu2/CKy+pOsN88qNxft+I+qdy74fNIrd7n9PtDq93z+v1Ws+BrveTS6/eW0OxCq91/xuiz3fFvvuXN6fYzpNqZ0e2Ly+oxo9q94fO94vPc7/nF5fSn2O/o9ft2wuYwotk2pdtVs+Du9/ySzutqvOQjnddPsN8uotl3wubY7vjC5PSEyOl1weZpvORSsuBctuHl9Po5p9vT6/eMzOo7p9uBx+iv2/GTz+vh8vq03fFguOLs9vvV7Pe23/JXtOBkuuMon9i+4vOQzetfuOKg1e6Jyurr9vvi8vp+xuhnu+Op2e/R6vf0+v3c8Pnb7/h+xehLr946p9ssodmj1u40pNqUz+zd8PlJrt57xOdXtOFdt+Kw3PFAqtxbtuG33/J8xOd6xOet2vDI5vW54PJwv+VBqt1FrN2a0u1luuOAxuhsveSh1e6s2vCNzOqt2/Ci1e7E5PTJ5/XH5vXL6PY9qNx0weZ9xedYteHK5/WFyOmCx+hnu+TQ6vZovOS13vLD5PSb0u1Mr9/O6fay3fG84fPZ7vhiueNKrt5zwObP6vZZteFjueOm1+9TsuCf1O6o2O+DyOhUs+DF5fWPzeuX0ezW7fhGrN0wo9qOzetet+IypNqr2fC13vGY0exvv+WRzuue1O2IyumHyulywOVuvuU/qdye0+3A4/T///96A0GMAAAA83RSTlP//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wB12P98AAAWOklEQVR42uzd5YMUx9YG8JpZN3ZZdHFblg3u7u7uGtzdITgkwUJCsBCc4CRA8BASEgJxtxtPbpKrr52/4EUvK3VqWk5V9+ye5+vOdE3Xb2e6u1QAx1cRXAUMwmEQBuEwCINwGIRBOAzCIBwG4TAIg3AYhEE4DMIgHAZhEA6DcBiEQTgMwiAcBmEQDoMwCIdBOAzCIBwGYRAOgzAIh0EYhMMgHAZhEA6DMAiHQRiEwyAMwmEQDoMwCIdBsmdfcwbxVeLWt2EQX2WT2BrDID7KcCGWDWQQH2WKEOnjGcQ/eU/cyZAgg/gm9e+KfFmTQfyS8XdBRM/9DOKTpBS5J1KkPIP4JFXugYikxxjEH1kVeV8k+iKD+COHxYM0YRBf5PRDEFGLQfyQ4F7/i+Sv5vd/C9+L5C+Q/Y9AxE8M4oOUzSJyhUG8z1+ygIgVDOJ5+jXNAhJoyyCeZ0DWr0jsLQbxOqOzgohfizKIx4kokk1kcAyDeJwT2UDE0SCDeJvy2UHEmwzicdJyiFxkEG8zJAdIsYEM4ml+yAEi0qYyiKf3WS/mFNnEIJ7mXE4QMZJBvMzIXCCBWwziYT7MBSKWVWMQDy8igdwiExnEwwzODSJGM4h3eUcCMmIqg3iWJyQgoh6DeJY2MhCxgUG8ymkpSFoig3iU4VIQ8TaDeJSf5SAJcQziTYrKQcQ3DOIvELGaQfwFsqQxg3iRChiIWMkgXmQgChLbjkE8yHsoiBjCIB7kJA5SLJNBzGcfDiI6MYj5dFKABJoziPFMUYCI+QxiPBkqkEBJBjGckkKZ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";
|
|
12700
12364
|
|
|
12701
12365
|
// src/providers/cbn/CbnProvider.ts
|
|
12702
12366
|
var API_BASE5 = "https://sbamemberdev.wpenginepowered.com/wp-json/superbook/v1";
|
|
12703
|
-
var CbnProvider = class {
|
|
12367
|
+
var CbnProvider = class extends BaseProvider {
|
|
12704
12368
|
constructor() {
|
|
12705
|
-
|
|
12369
|
+
super(...arguments);
|
|
12706
12370
|
this.deviceFlowHelper = new DeviceFlowHelper();
|
|
12707
12371
|
this.id = "cbn";
|
|
12708
12372
|
this.name = "CBN";
|
|
@@ -12715,66 +12379,48 @@ var CbnProvider = class {
|
|
|
12715
12379
|
clientId: "freeplay",
|
|
12716
12380
|
scopes: [],
|
|
12717
12381
|
supportsDeviceFlow: true,
|
|
12718
|
-
deviceAuthEndpoint: "/device"
|
|
12719
|
-
endpoints: {
|
|
12720
|
-
catalog: "/catalog",
|
|
12721
|
-
courseDetail: (courseId) => `/catalog/${courseId}`,
|
|
12722
|
-
lessonPlaylist: (lessonId) => `/lesson-playlist/${lessonId}`
|
|
12723
|
-
}
|
|
12382
|
+
deviceAuthEndpoint: "/device"
|
|
12724
12383
|
};
|
|
12725
12384
|
this.requiresAuth = true;
|
|
12726
12385
|
this.authTypes = ["device_flow"];
|
|
12727
12386
|
this.capabilities = { browse: true, playlist: true, instructions: true, mediaLicensing: false };
|
|
12728
12387
|
}
|
|
12729
|
-
async apiRequest(path, auth) {
|
|
12730
|
-
return this.apiHelper.apiRequest(this.config, this.id, path, auth);
|
|
12731
|
-
}
|
|
12732
12388
|
async browse(path, auth) {
|
|
12733
12389
|
const { segments, depth } = parsePath(path);
|
|
12734
12390
|
if (depth === 0) {
|
|
12735
12391
|
return [{ type: "folder", id: "catalog-root", title: "Catalog", path: "/catalog" }];
|
|
12736
12392
|
}
|
|
12737
12393
|
if (segments[0] !== "catalog") return [];
|
|
12738
|
-
if (depth === 1)
|
|
12739
|
-
|
|
12740
|
-
|
|
12394
|
+
if (depth === 1) {
|
|
12395
|
+
const response = await this.apiRequest("/catalog", auth);
|
|
12396
|
+
return Array.isArray(response) ? convertCoursesToFolders(response) : [];
|
|
12397
|
+
}
|
|
12398
|
+
if (depth === 2) {
|
|
12399
|
+
const response = await this.apiRequest(`/catalog/${segments[1]}`, auth);
|
|
12400
|
+
return response?.lessons ? convertLessonsToFolders(response.lessons, path) : [];
|
|
12401
|
+
}
|
|
12402
|
+
if (depth === 3) {
|
|
12403
|
+
const resolved = await this.resolveFiles(path, auth);
|
|
12404
|
+
return resolved?.files ?? [];
|
|
12405
|
+
}
|
|
12741
12406
|
return [];
|
|
12742
12407
|
}
|
|
12743
|
-
|
|
12744
|
-
|
|
12745
|
-
if (!Array.isArray(response)) return [];
|
|
12746
|
-
return convertCoursesToFolders(response);
|
|
12747
|
-
}
|
|
12748
|
-
async getLessons(courseId, coursePath, auth) {
|
|
12749
|
-
const pathFn = this.config.endpoints.courseDetail;
|
|
12750
|
-
const response = await this.apiRequest(pathFn(courseId), auth);
|
|
12751
|
-
if (!response?.lessons) return [];
|
|
12752
|
-
return convertLessonsToFolders(response.lessons, coursePath);
|
|
12753
|
-
}
|
|
12754
|
-
async getVideos(lessonId, auth) {
|
|
12755
|
-
const playlist = await this.fetchPlaylist(lessonId, auth);
|
|
12756
|
-
return playlist ? convertPlaylistToFiles(playlist) : [];
|
|
12757
|
-
}
|
|
12758
|
-
async fetchPlaylist(lessonId, auth) {
|
|
12759
|
-
const pathFn = this.config.endpoints.lessonPlaylist;
|
|
12760
|
-
return this.apiRequest(pathFn(lessonId), auth);
|
|
12761
|
-
}
|
|
12762
|
-
async getPlaylist(path, auth, _resolution) {
|
|
12408
|
+
/** Resolve a leaf path (/catalog/{courseId}/{lessonId}) to its playlist files. */
|
|
12409
|
+
async resolveFiles(path, auth) {
|
|
12763
12410
|
const { segments, depth } = parsePath(path);
|
|
12764
12411
|
if (depth !== 3 || segments[0] !== "catalog") return null;
|
|
12765
|
-
const playlist = await this.
|
|
12412
|
+
const playlist = await this.apiRequest(`/lesson-playlist/${segments[2]}`, auth);
|
|
12766
12413
|
if (!playlist || playlist.playlist.length === 0) return null;
|
|
12767
|
-
return convertPlaylistToFiles(playlist);
|
|
12414
|
+
return { files: convertPlaylistToFiles(playlist), lessonTitle: playlist.lesson_title };
|
|
12768
12415
|
}
|
|
12769
|
-
async
|
|
12770
|
-
const
|
|
12771
|
-
|
|
12772
|
-
const playlist = await this.fetchPlaylist(segments[2], auth);
|
|
12773
|
-
if (!playlist || playlist.playlist.length === 0) return null;
|
|
12774
|
-
return convertPlaylistToInstructions(playlist);
|
|
12416
|
+
async getPlaylist(path, auth, _resolution) {
|
|
12417
|
+
const resolved = await this.resolveFiles(path, auth);
|
|
12418
|
+
return resolved?.files ?? null;
|
|
12775
12419
|
}
|
|
12776
|
-
|
|
12777
|
-
|
|
12420
|
+
async getInstructions(path, auth) {
|
|
12421
|
+
const resolved = await this.resolveFiles(path, auth);
|
|
12422
|
+
if (!resolved) return null;
|
|
12423
|
+
return filesToInstructions(resolved.lessonTitle || "Lesson", resolved.files);
|
|
12778
12424
|
}
|
|
12779
12425
|
async initiateDeviceFlow() {
|
|
12780
12426
|
return this.deviceFlowHelper.initiateDeviceFlow(this.config);
|
|
@@ -16405,8 +16051,9 @@ var data_default3 = {
|
|
|
16405
16051
|
};
|
|
16406
16052
|
|
|
16407
16053
|
// src/providers/lifeChurch/LifeChurchProvider.ts
|
|
16408
|
-
var LifeChurchProvider = class {
|
|
16054
|
+
var LifeChurchProvider = class extends BaseProvider {
|
|
16409
16055
|
constructor() {
|
|
16056
|
+
super(...arguments);
|
|
16410
16057
|
this.id = "lifechurch";
|
|
16411
16058
|
this.name = "Life.Church";
|
|
16412
16059
|
this.logos = {
|
|
@@ -16419,8 +16066,7 @@ var LifeChurchProvider = class {
|
|
|
16419
16066
|
apiBase: "https://www.life.church",
|
|
16420
16067
|
oauthBase: "",
|
|
16421
16068
|
clientId: "",
|
|
16422
|
-
scopes: []
|
|
16423
|
-
endpoints: {}
|
|
16069
|
+
scopes: []
|
|
16424
16070
|
};
|
|
16425
16071
|
this.requiresAuth = false;
|
|
16426
16072
|
this.authTypes = ["none"];
|
|
@@ -16435,20 +16081,14 @@ var LifeChurchProvider = class {
|
|
|
16435
16081
|
findSeries(seriesId) {
|
|
16436
16082
|
return this.data.series.find((s) => s.id === seriesId);
|
|
16437
16083
|
}
|
|
16438
|
-
findUnit(series, unitId) {
|
|
16439
|
-
return series.units.find((u) => u.id === unitId);
|
|
16440
|
-
}
|
|
16441
|
-
findLesson(unit, lessonId) {
|
|
16442
|
-
return unit.lessons.find((l) => l.id === lessonId);
|
|
16443
|
-
}
|
|
16444
16084
|
findScheduled(scheduledId) {
|
|
16445
16085
|
return this.data.scheduledLessons?.find((s) => s.id === scheduledId);
|
|
16446
16086
|
}
|
|
16447
16087
|
/** Resolve a scheduled-lesson reference to the underlying lesson + unit + series. */
|
|
16448
16088
|
resolveScheduled(sched) {
|
|
16449
16089
|
const series = this.findSeries(sched.seriesId);
|
|
16450
|
-
const unit = series
|
|
16451
|
-
const lesson = unit
|
|
16090
|
+
const unit = series?.units.find((u) => u.id === sched.unitId);
|
|
16091
|
+
const lesson = unit?.lessons.find((l) => l.id === sched.lessonId);
|
|
16452
16092
|
if (!series || !unit || !lesson) return null;
|
|
16453
16093
|
return { series, unit, lesson };
|
|
16454
16094
|
}
|
|
@@ -16463,17 +16103,40 @@ var LifeChurchProvider = class {
|
|
|
16463
16103
|
providerData: { sourceUrl: lesson.sourceUrl }
|
|
16464
16104
|
};
|
|
16465
16105
|
}
|
|
16106
|
+
/** Resolve a leaf-or-unit path to its files plus the name/section used for instructions. */
|
|
16107
|
+
resolve(path) {
|
|
16108
|
+
const { segments, depth } = parsePath(path);
|
|
16109
|
+
if (segments[0] === "scheduled") {
|
|
16110
|
+
if (depth !== 2) return null;
|
|
16111
|
+
const sched = this.findScheduled(segments[1]);
|
|
16112
|
+
const resolved = sched && this.resolveScheduled(sched);
|
|
16113
|
+
if (!sched || !resolved) return null;
|
|
16114
|
+
const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
|
|
16115
|
+
return {
|
|
16116
|
+
files: [this.lessonToFile(resolved.lesson, label)],
|
|
16117
|
+
name: label,
|
|
16118
|
+
section: { id: `scheduled-${sched.id}-section`, label: "This Sunday" }
|
|
16119
|
+
};
|
|
16120
|
+
}
|
|
16121
|
+
const series = this.findSeries(segments[0]);
|
|
16122
|
+
const unit = series?.units.find((u) => u.id === segments[1]);
|
|
16123
|
+
if (!unit) return null;
|
|
16124
|
+
if (depth === 2) {
|
|
16125
|
+
return { files: unit.lessons.map((l) => this.lessonToFile(l)), name: unit.name, section: { id: `${unit.id}-section`, label: unit.name } };
|
|
16126
|
+
}
|
|
16127
|
+
if (depth === 3) {
|
|
16128
|
+
const lesson = unit.lessons.find((l) => l.id === segments[2]);
|
|
16129
|
+
if (!lesson) return null;
|
|
16130
|
+
return { files: [this.lessonToFile(lesson)], name: lesson.title, section: { id: `${unit.id}-section`, label: unit.name } };
|
|
16131
|
+
}
|
|
16132
|
+
return null;
|
|
16133
|
+
}
|
|
16466
16134
|
async browse(path, _auth) {
|
|
16467
16135
|
const { segments, depth } = parsePath(path);
|
|
16468
16136
|
if (depth === 0) {
|
|
16469
16137
|
const items = [];
|
|
16470
16138
|
if (this.data.scheduledLessons && this.data.scheduledLessons.length > 0) {
|
|
16471
|
-
items.push({
|
|
16472
|
-
type: "folder",
|
|
16473
|
-
id: "scheduled",
|
|
16474
|
-
title: "This Sunday",
|
|
16475
|
-
path: "/scheduled"
|
|
16476
|
-
});
|
|
16139
|
+
items.push({ type: "folder", id: "scheduled", title: "This Sunday", path: "/scheduled" });
|
|
16477
16140
|
}
|
|
16478
16141
|
for (const s of this.data.series) {
|
|
16479
16142
|
items.push({ type: "folder", id: s.id, title: s.name, thumbnail: s.thumbnail, path: `/${s.id}` });
|
|
@@ -16494,13 +16157,7 @@ var LifeChurchProvider = class {
|
|
|
16494
16157
|
};
|
|
16495
16158
|
});
|
|
16496
16159
|
}
|
|
16497
|
-
|
|
16498
|
-
const sched = this.findScheduled(segments[1]);
|
|
16499
|
-
const resolved = sched && this.resolveScheduled(sched);
|
|
16500
|
-
if (!sched || !resolved) return [];
|
|
16501
|
-
return [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)];
|
|
16502
|
-
}
|
|
16503
|
-
return [];
|
|
16160
|
+
return this.resolve(path)?.files ?? [];
|
|
16504
16161
|
}
|
|
16505
16162
|
if (depth === 1) {
|
|
16506
16163
|
const series = this.findSeries(segments[0]);
|
|
@@ -16515,8 +16172,8 @@ var LifeChurchProvider = class {
|
|
|
16515
16172
|
}
|
|
16516
16173
|
if (depth === 2) {
|
|
16517
16174
|
const series = this.findSeries(segments[0]);
|
|
16518
|
-
const unit = series
|
|
16519
|
-
if (!
|
|
16175
|
+
const unit = series?.units.find((u) => u.id === segments[1]);
|
|
16176
|
+
if (!unit) return [];
|
|
16520
16177
|
return unit.lessons.map((l) => ({
|
|
16521
16178
|
type: "folder",
|
|
16522
16179
|
id: l.id,
|
|
@@ -16527,78 +16184,18 @@ var LifeChurchProvider = class {
|
|
|
16527
16184
|
}));
|
|
16528
16185
|
}
|
|
16529
16186
|
if (depth === 3) {
|
|
16530
|
-
|
|
16531
|
-
const unit = series && this.findUnit(series, segments[1]);
|
|
16532
|
-
const lesson = unit && this.findLesson(unit, segments[2]);
|
|
16533
|
-
if (!lesson) return [];
|
|
16534
|
-
return [this.lessonToFile(lesson)];
|
|
16187
|
+
return this.resolve(path)?.files ?? [];
|
|
16535
16188
|
}
|
|
16536
16189
|
return [];
|
|
16537
16190
|
}
|
|
16538
16191
|
async getPlaylist(path, _auth, _resolution) {
|
|
16539
|
-
const
|
|
16540
|
-
|
|
16541
|
-
const sched = this.findScheduled(segments[1]);
|
|
16542
|
-
const resolved = sched && this.resolveScheduled(sched);
|
|
16543
|
-
return resolved ? [this.lessonToFile(resolved.lesson, `${sched.ageGroup} \u2014 ${resolved.lesson.title}`)] : null;
|
|
16544
|
-
}
|
|
16545
|
-
if (depth === 3) {
|
|
16546
|
-
const series = this.findSeries(segments[0]);
|
|
16547
|
-
const unit = series && this.findUnit(series, segments[1]);
|
|
16548
|
-
const lesson = unit && this.findLesson(unit, segments[2]);
|
|
16549
|
-
return lesson ? [this.lessonToFile(lesson)] : null;
|
|
16550
|
-
}
|
|
16551
|
-
if (depth === 2 && segments[0] !== "scheduled") {
|
|
16552
|
-
const series = this.findSeries(segments[0]);
|
|
16553
|
-
const unit = series && this.findUnit(series, segments[1]);
|
|
16554
|
-
if (!unit) return null;
|
|
16555
|
-
const files = unit.lessons.map((l) => this.lessonToFile(l));
|
|
16556
|
-
return files.length > 0 ? files : null;
|
|
16557
|
-
}
|
|
16558
|
-
return null;
|
|
16192
|
+
const resolved = this.resolve(path);
|
|
16193
|
+
return resolved && resolved.files.length > 0 ? resolved.files : null;
|
|
16559
16194
|
}
|
|
16560
16195
|
async getInstructions(path, _auth) {
|
|
16561
|
-
const
|
|
16562
|
-
|
|
16563
|
-
|
|
16564
|
-
itemType: "action",
|
|
16565
|
-
label: label ?? lesson.title,
|
|
16566
|
-
actionType: "play",
|
|
16567
|
-
children: [
|
|
16568
|
-
{
|
|
16569
|
-
id: lesson.id,
|
|
16570
|
-
itemType: "file",
|
|
16571
|
-
label: label ?? lesson.title,
|
|
16572
|
-
downloadUrl: lesson.videoUrl,
|
|
16573
|
-
thumbnail: lesson.thumbnail,
|
|
16574
|
-
mediaType: "video"
|
|
16575
|
-
}
|
|
16576
|
-
]
|
|
16577
|
-
});
|
|
16578
|
-
if (segments[0] === "scheduled" && depth === 2) {
|
|
16579
|
-
const sched = this.findScheduled(segments[1]);
|
|
16580
|
-
const resolved = sched && this.resolveScheduled(sched);
|
|
16581
|
-
if (!sched || !resolved) return null;
|
|
16582
|
-
const label = `${sched.ageGroup} \u2014 ${resolved.lesson.title}`;
|
|
16583
|
-
return {
|
|
16584
|
-
name: label,
|
|
16585
|
-
items: [{ id: `scheduled-${sched.id}-section`, itemType: "section", label: "This Sunday", children: [buildItem(resolved.lesson, label)] }]
|
|
16586
|
-
};
|
|
16587
|
-
}
|
|
16588
|
-
if (depth === 3) {
|
|
16589
|
-
const series = this.findSeries(segments[0]);
|
|
16590
|
-
const unit = series && this.findUnit(series, segments[1]);
|
|
16591
|
-
const lesson = unit && this.findLesson(unit, segments[2]);
|
|
16592
|
-
if (!series || !unit || !lesson) return null;
|
|
16593
|
-
return { name: lesson.title, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: [buildItem(lesson)] }] };
|
|
16594
|
-
}
|
|
16595
|
-
if (depth === 2 && segments[0] !== "scheduled") {
|
|
16596
|
-
const series = this.findSeries(segments[0]);
|
|
16597
|
-
const unit = series && this.findUnit(series, segments[1]);
|
|
16598
|
-
if (!unit) return null;
|
|
16599
|
-
return { name: unit.name, items: [{ id: `${unit.id}-section`, itemType: "section", label: unit.name, children: unit.lessons.map((l) => buildItem(l)) }] };
|
|
16600
|
-
}
|
|
16601
|
-
return null;
|
|
16196
|
+
const resolved = this.resolve(path);
|
|
16197
|
+
if (!resolved) return null;
|
|
16198
|
+
return filesToInstructions(resolved.name, resolved.files, resolved.section);
|
|
16602
16199
|
}
|
|
16603
16200
|
/**
|
|
16604
16201
|
* Returns the most recent scheduled lesson from data.scheduledLessons.
|
|
@@ -16626,9 +16223,6 @@ var LifeChurchProvider = class {
|
|
|
16626
16223
|
]
|
|
16627
16224
|
};
|
|
16628
16225
|
}
|
|
16629
|
-
supportsDeviceFlow() {
|
|
16630
|
-
return false;
|
|
16631
|
-
}
|
|
16632
16226
|
};
|
|
16633
16227
|
|
|
16634
16228
|
// src/providers/index.ts
|
|
@@ -16723,17 +16317,20 @@ function getAvailableProviders(ids) {
|
|
|
16723
16317
|
}
|
|
16724
16318
|
|
|
16725
16319
|
// src/index.ts
|
|
16726
|
-
var VERSION = "0.
|
|
16320
|
+
var VERSION = true ? "0.5.0" : "dev";
|
|
16727
16321
|
export {
|
|
16728
16322
|
APlayProvider,
|
|
16729
16323
|
B1ChurchProvider,
|
|
16324
|
+
BaseProvider,
|
|
16730
16325
|
BibleProjectProvider,
|
|
16731
16326
|
CbnProvider,
|
|
16327
|
+
DeviceFlowHelper,
|
|
16732
16328
|
DropboxProvider,
|
|
16733
16329
|
HighVoltageKidsProvider,
|
|
16734
16330
|
JesusFilmProvider,
|
|
16735
16331
|
LessonsChurchProvider,
|
|
16736
16332
|
LifeChurchProvider,
|
|
16333
|
+
OAuthHelper,
|
|
16737
16334
|
PlanningCenterProvider,
|
|
16738
16335
|
SignPresenterProvider,
|
|
16739
16336
|
TokenHelper,
|
|
@@ -16745,6 +16342,7 @@ export {
|
|
|
16745
16342
|
isContentFile,
|
|
16746
16343
|
isContentFolder,
|
|
16747
16344
|
navigateToPath,
|
|
16748
|
-
registerProvider
|
|
16345
|
+
registerProvider,
|
|
16346
|
+
toAuthData
|
|
16749
16347
|
};
|
|
16750
16348
|
//# sourceMappingURL=index.js.map
|