@churchapps/content-providers 0.9.1 → 0.9.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +51 -12
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +51 -12
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -64,7 +64,7 @@ interface ContentFile {
|
|
|
64
64
|
type: "file";
|
|
65
65
|
id: string;
|
|
66
66
|
title: string;
|
|
67
|
-
mediaType: "video" | "image";
|
|
67
|
+
mediaType: "video" | "image" | "audio";
|
|
68
68
|
thumbnail?: string;
|
|
69
69
|
url: string;
|
|
70
70
|
downloadUrl?: string;
|
|
@@ -148,7 +148,7 @@ interface InstructionItem {
|
|
|
148
148
|
children?: InstructionItem[];
|
|
149
149
|
downloadUrl?: string;
|
|
150
150
|
thumbnail?: string;
|
|
151
|
-
mediaType?: "video" | "image";
|
|
151
|
+
mediaType?: "video" | "image" | "audio";
|
|
152
152
|
}
|
|
153
153
|
interface Instructions {
|
|
154
154
|
name?: string;
|
|
@@ -259,7 +259,10 @@ declare class OAuthHelper {
|
|
|
259
259
|
declare function toAuthData(data: Record<string, unknown>, fallbacks?: {
|
|
260
260
|
refreshToken?: string;
|
|
261
261
|
scope?: string;
|
|
262
|
-
|
|
262
|
+
planTypeId?: string;
|
|
263
|
+
}): ContentProviderAuthData & {
|
|
264
|
+
planTypeId?: string;
|
|
265
|
+
};
|
|
263
266
|
declare class TokenHelper {
|
|
264
267
|
isAuthValid(auth: ContentProviderAuthData | null | undefined): boolean;
|
|
265
268
|
isTokenExpired(auth: ContentProviderAuthData): boolean;
|
|
@@ -331,7 +334,11 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
331
334
|
browse(path?: string | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
332
335
|
private extractList;
|
|
333
336
|
private getPlaylists;
|
|
337
|
+
/** image and video/stream slides play directly; web slides only when the url is itself a media file. html/pdf/multi_zone and generic web pages aren't playable in ChurchApps. */
|
|
338
|
+
private slideMediaType;
|
|
339
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
334
340
|
private getMessages;
|
|
341
|
+
private getLegacyMessages;
|
|
335
342
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
336
343
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
337
344
|
generateCodeVerifier(): string;
|
package/dist/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ interface ContentFile {
|
|
|
64
64
|
type: "file";
|
|
65
65
|
id: string;
|
|
66
66
|
title: string;
|
|
67
|
-
mediaType: "video" | "image";
|
|
67
|
+
mediaType: "video" | "image" | "audio";
|
|
68
68
|
thumbnail?: string;
|
|
69
69
|
url: string;
|
|
70
70
|
downloadUrl?: string;
|
|
@@ -148,7 +148,7 @@ interface InstructionItem {
|
|
|
148
148
|
children?: InstructionItem[];
|
|
149
149
|
downloadUrl?: string;
|
|
150
150
|
thumbnail?: string;
|
|
151
|
-
mediaType?: "video" | "image";
|
|
151
|
+
mediaType?: "video" | "image" | "audio";
|
|
152
152
|
}
|
|
153
153
|
interface Instructions {
|
|
154
154
|
name?: string;
|
|
@@ -259,7 +259,10 @@ declare class OAuthHelper {
|
|
|
259
259
|
declare function toAuthData(data: Record<string, unknown>, fallbacks?: {
|
|
260
260
|
refreshToken?: string;
|
|
261
261
|
scope?: string;
|
|
262
|
-
|
|
262
|
+
planTypeId?: string;
|
|
263
|
+
}): ContentProviderAuthData & {
|
|
264
|
+
planTypeId?: string;
|
|
265
|
+
};
|
|
263
266
|
declare class TokenHelper {
|
|
264
267
|
isAuthValid(auth: ContentProviderAuthData | null | undefined): boolean;
|
|
265
268
|
isTokenExpired(auth: ContentProviderAuthData): boolean;
|
|
@@ -331,7 +334,11 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
331
334
|
browse(path?: string | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
332
335
|
private extractList;
|
|
333
336
|
private getPlaylists;
|
|
337
|
+
/** image and video/stream slides play directly; web slides only when the url is itself a media file. html/pdf/multi_zone and generic web pages aren't playable in ChurchApps. */
|
|
338
|
+
private slideMediaType;
|
|
339
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
334
340
|
private getMessages;
|
|
341
|
+
private getLegacyMessages;
|
|
335
342
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
336
343
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
337
344
|
generateCodeVerifier(): string;
|
package/dist/index.js
CHANGED
|
@@ -20,13 +20,15 @@ function navigateToPath(instructions, path) {
|
|
|
20
20
|
|
|
21
21
|
// src/helpers/TokenHelper.ts
|
|
22
22
|
function toAuthData(data, fallbacks) {
|
|
23
|
+
const planTypeId = data.plan_type_id || fallbacks?.planTypeId;
|
|
23
24
|
return {
|
|
24
25
|
access_token: data.access_token,
|
|
25
26
|
refresh_token: data.refresh_token || fallbacks?.refreshToken || "",
|
|
26
27
|
token_type: data.token_type || "Bearer",
|
|
27
28
|
created_at: Math.floor(Date.now() / 1e3),
|
|
28
29
|
expires_in: data.expires_in,
|
|
29
|
-
scope: data.scope || fallbacks?.scope || ""
|
|
30
|
+
scope: data.scope || fallbacks?.scope || "",
|
|
31
|
+
...planTypeId ? { planTypeId } : {}
|
|
30
32
|
};
|
|
31
33
|
}
|
|
32
34
|
var TokenHelper = class {
|
|
@@ -54,7 +56,8 @@ var TokenHelper = class {
|
|
|
54
56
|
return null;
|
|
55
57
|
}
|
|
56
58
|
const data = await response.json();
|
|
57
|
-
|
|
59
|
+
const planTypeId = auth.planTypeId;
|
|
60
|
+
return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope, planTypeId });
|
|
58
61
|
} catch {
|
|
59
62
|
return null;
|
|
60
63
|
}
|
|
@@ -191,7 +194,8 @@ var ApiHelper = class {
|
|
|
191
194
|
const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
|
|
192
195
|
const response = await fetch(url, options);
|
|
193
196
|
if (!response.ok) {
|
|
194
|
-
|
|
197
|
+
const detail = await response.text().catch(() => "");
|
|
198
|
+
console.warn(`[${providerId}] apiRequest failed: ${method} ${url} \u2192 HTTP ${response.status} ${response.statusText}${detail ? ` ${detail.slice(0, 500)}` : ""}`);
|
|
195
199
|
return null;
|
|
196
200
|
}
|
|
197
201
|
return await response.json();
|
|
@@ -252,16 +256,19 @@ var IMAGE_EXTENSIONS = [
|
|
|
252
256
|
".tiff",
|
|
253
257
|
".tif"
|
|
254
258
|
];
|
|
259
|
+
var AUDIO_EXTENSIONS = [".mp3", ".m4a", ".aac", ".wav", ".flac", ".oga"];
|
|
255
260
|
function detectMediaType(url, explicitType) {
|
|
256
261
|
if (explicitType === "video" || explicitType?.startsWith("video/")) return "video";
|
|
257
262
|
if (explicitType === "image" || explicitType?.startsWith("image/")) return "image";
|
|
263
|
+
if (explicitType === "audio" || explicitType?.startsWith("audio/")) return "audio";
|
|
258
264
|
const lower = url.toLowerCase();
|
|
259
265
|
if (VIDEO_EXTENSIONS.some((p) => lower.includes(p)) || lower.includes("stream.mux.com")) return "video";
|
|
266
|
+
if (AUDIO_EXTENSIONS.some((p) => lower.includes(p))) return "audio";
|
|
260
267
|
return "image";
|
|
261
268
|
}
|
|
262
269
|
function isMediaFile(filename) {
|
|
263
270
|
const lower = filename.toLowerCase();
|
|
264
|
-
return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS].some((ext) => lower.endsWith(ext));
|
|
271
|
+
return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
|
|
265
272
|
}
|
|
266
273
|
function createFolder(id, title, path, thumbnail, isLeaf) {
|
|
267
274
|
return { type: "folder", id, title, path, thumbnail, isLeaf };
|
|
@@ -541,13 +548,45 @@ var SignPresenterProvider = class extends BaseProvider {
|
|
|
541
548
|
isLeaf: true
|
|
542
549
|
}));
|
|
543
550
|
}
|
|
551
|
+
/** image and video/stream slides play directly; web slides only when the url is itself a media file. html/pdf/multi_zone and generic web pages aren't playable in ChurchApps. */
|
|
552
|
+
slideMediaType(slideType, url) {
|
|
553
|
+
if (slideType === "image") return "image";
|
|
554
|
+
if (slideType === "video" || slideType === "stream") return "video";
|
|
555
|
+
if (slideType === "web" && isMediaFile(url)) return detectMediaType(url);
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
544
559
|
async getMessages(playlistId, auth) {
|
|
560
|
+
const response = await this.apiRequest(`/v2/playlists/${playlistId}`, auth);
|
|
561
|
+
const messages = this.extractList(response, "messages");
|
|
562
|
+
if (messages.length === 0) return this.getLegacyMessages(playlistId, auth);
|
|
563
|
+
const files = [];
|
|
564
|
+
messages.forEach((msg, messageIndex) => {
|
|
565
|
+
const slides = Array.isArray(msg.slides) ? msg.slides : [];
|
|
566
|
+
slides.forEach((slide, slideIndex) => {
|
|
567
|
+
const data = slide.data || {};
|
|
568
|
+
const url = typeof data.url === "string" ? data.url.trim() : "";
|
|
569
|
+
const mediaType = url ? this.slideMediaType(slide.type, url) : null;
|
|
570
|
+
if (!mediaType) return;
|
|
571
|
+
const file = createFile(`${playlistId}-${messageIndex}-${slideIndex}`, msg.name, url, {
|
|
572
|
+
mediaType,
|
|
573
|
+
thumbnail: msg.thumbnail,
|
|
574
|
+
seconds: slide.seconds,
|
|
575
|
+
loop: data.loop
|
|
576
|
+
});
|
|
577
|
+
file.downloadUrl = url;
|
|
578
|
+
files.push(file);
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
return files;
|
|
582
|
+
}
|
|
583
|
+
async getLegacyMessages(playlistId, auth) {
|
|
545
584
|
const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
|
|
546
585
|
if (!response) return [];
|
|
547
586
|
const files = [];
|
|
548
587
|
for (const msg of this.extractList(response, "messages")) {
|
|
549
|
-
|
|
550
|
-
|
|
588
|
+
const url = [msg.url, msg.thumbnail, msg.image].map((v) => typeof v === "string" ? v.trim() : "").find(Boolean) || "";
|
|
589
|
+
if (!url) continue;
|
|
551
590
|
const file = createFile(msg.id, msg.name, url, {
|
|
552
591
|
mediaType: detectMediaType(url, msg.mediaType),
|
|
553
592
|
thumbnail: msg.thumbnail || msg.image,
|
|
@@ -1396,9 +1435,7 @@ var B1ChurchProvider = class extends BaseProvider {
|
|
|
1396
1435
|
setPairingData(data) {
|
|
1397
1436
|
if (data && typeof data === "object" && "planTypeId" in data) {
|
|
1398
1437
|
const planTypeId = data.planTypeId;
|
|
1399
|
-
|
|
1400
|
-
} else {
|
|
1401
|
-
this.planTypeId = null;
|
|
1438
|
+
if (typeof planTypeId === "string") this.planTypeId = planTypeId;
|
|
1402
1439
|
}
|
|
1403
1440
|
}
|
|
1404
1441
|
async getCurrentPlan(authData) {
|
|
@@ -17761,7 +17798,9 @@ var GoogleDriveProvider = class extends BaseProvider {
|
|
|
17761
17798
|
orderBy: "folder,name_natural",
|
|
17762
17799
|
pageSize: "1000",
|
|
17763
17800
|
supportsAllDrives: "true",
|
|
17764
|
-
includeItemsFromAllDrives
|
|
17801
|
+
// corpora=user (not includeItemsFromAllDrives) — "root" is only meaningful in the user's own corpus, and
|
|
17802
|
+
// combining it with an all-drives search makes Google reject the query, which browse saw as an empty folder.
|
|
17803
|
+
corpora: "user"
|
|
17765
17804
|
});
|
|
17766
17805
|
if (pageToken) params.set("pageToken", pageToken);
|
|
17767
17806
|
const response = await this.apiRequest(`/files?${params.toString()}`, auth);
|
|
@@ -17775,7 +17814,7 @@ var GoogleDriveProvider = class extends BaseProvider {
|
|
|
17775
17814
|
async childItems(folderId, auth) {
|
|
17776
17815
|
const entries = await this.listFiles(`'${folderId}' in parents and trashed=false`, auth);
|
|
17777
17816
|
const folders = entries.filter((e) => e.mimeType === FOLDER_MIME);
|
|
17778
|
-
const mediaFiles = entries.filter((e) => e.mimeType.startsWith("video/")
|
|
17817
|
+
const mediaFiles = entries.filter((e) => e.mimeType !== FOLDER_MIME && !e.mimeType.startsWith("application/vnd.google-apps") && (["video/", "image/", "audio/"].some((prefix) => e.mimeType.startsWith(prefix)) || isMediaFile(e.name)));
|
|
17779
17818
|
return { folders, mediaFiles };
|
|
17780
17819
|
}
|
|
17781
17820
|
/** One batched query marks which sibling folders have subfolders; null = unknown (isLeaf omitted). */
|
|
@@ -17932,7 +17971,7 @@ function getAvailableProviders(ids) {
|
|
|
17932
17971
|
}
|
|
17933
17972
|
|
|
17934
17973
|
// src/index.ts
|
|
17935
|
-
var VERSION = true ? "0.9.
|
|
17974
|
+
var VERSION = true ? "0.9.3" : "dev";
|
|
17936
17975
|
export {
|
|
17937
17976
|
APlayProvider,
|
|
17938
17977
|
B1ChurchProvider,
|