@churchapps/content-providers 0.9.0 → 0.9.2
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 +46 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +46 -9
- 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;
|
|
@@ -331,7 +331,11 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
331
331
|
browse(path?: string | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
332
332
|
private extractList;
|
|
333
333
|
private getPlaylists;
|
|
334
|
+
/** 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. */
|
|
335
|
+
private slideMediaType;
|
|
336
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
334
337
|
private getMessages;
|
|
338
|
+
private getLegacyMessages;
|
|
335
339
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
336
340
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
337
341
|
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;
|
|
@@ -331,7 +331,11 @@ declare class SignPresenterProvider extends BaseProvider {
|
|
|
331
331
|
browse(path?: string | null, auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
|
|
332
332
|
private extractList;
|
|
333
333
|
private getPlaylists;
|
|
334
|
+
/** 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. */
|
|
335
|
+
private slideMediaType;
|
|
336
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
334
337
|
private getMessages;
|
|
338
|
+
private getLegacyMessages;
|
|
335
339
|
getPlaylist(path: string, auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
|
|
336
340
|
getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
|
|
337
341
|
generateCodeVerifier(): string;
|
package/dist/index.js
CHANGED
|
@@ -191,7 +191,8 @@ var ApiHelper = class {
|
|
|
191
191
|
const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
|
|
192
192
|
const response = await fetch(url, options);
|
|
193
193
|
if (!response.ok) {
|
|
194
|
-
|
|
194
|
+
const detail = await response.text().catch(() => "");
|
|
195
|
+
console.warn(`[${providerId}] apiRequest failed: ${method} ${url} \u2192 HTTP ${response.status} ${response.statusText}${detail ? ` ${detail.slice(0, 500)}` : ""}`);
|
|
195
196
|
return null;
|
|
196
197
|
}
|
|
197
198
|
return await response.json();
|
|
@@ -252,16 +253,19 @@ var IMAGE_EXTENSIONS = [
|
|
|
252
253
|
".tiff",
|
|
253
254
|
".tif"
|
|
254
255
|
];
|
|
256
|
+
var AUDIO_EXTENSIONS = [".mp3", ".m4a", ".aac", ".wav", ".flac", ".oga"];
|
|
255
257
|
function detectMediaType(url, explicitType) {
|
|
256
258
|
if (explicitType === "video" || explicitType?.startsWith("video/")) return "video";
|
|
257
259
|
if (explicitType === "image" || explicitType?.startsWith("image/")) return "image";
|
|
260
|
+
if (explicitType === "audio" || explicitType?.startsWith("audio/")) return "audio";
|
|
258
261
|
const lower = url.toLowerCase();
|
|
259
262
|
if (VIDEO_EXTENSIONS.some((p) => lower.includes(p)) || lower.includes("stream.mux.com")) return "video";
|
|
263
|
+
if (AUDIO_EXTENSIONS.some((p) => lower.includes(p))) return "audio";
|
|
260
264
|
return "image";
|
|
261
265
|
}
|
|
262
266
|
function isMediaFile(filename) {
|
|
263
267
|
const lower = filename.toLowerCase();
|
|
264
|
-
return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS].some((ext) => lower.endsWith(ext));
|
|
268
|
+
return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
|
|
265
269
|
}
|
|
266
270
|
function createFolder(id, title, path, thumbnail, isLeaf) {
|
|
267
271
|
return { type: "folder", id, title, path, thumbnail, isLeaf };
|
|
@@ -541,13 +545,45 @@ var SignPresenterProvider = class extends BaseProvider {
|
|
|
541
545
|
isLeaf: true
|
|
542
546
|
}));
|
|
543
547
|
}
|
|
548
|
+
/** 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. */
|
|
549
|
+
slideMediaType(slideType, url) {
|
|
550
|
+
if (slideType === "image") return "image";
|
|
551
|
+
if (slideType === "video" || slideType === "stream") return "video";
|
|
552
|
+
if (slideType === "web" && isMediaFile(url)) return detectMediaType(url);
|
|
553
|
+
return null;
|
|
554
|
+
}
|
|
555
|
+
/** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
|
|
544
556
|
async getMessages(playlistId, auth) {
|
|
557
|
+
const response = await this.apiRequest(`/v2/playlists/${playlistId}`, auth);
|
|
558
|
+
const messages = this.extractList(response, "messages");
|
|
559
|
+
if (messages.length === 0) return this.getLegacyMessages(playlistId, auth);
|
|
560
|
+
const files = [];
|
|
561
|
+
messages.forEach((msg, messageIndex) => {
|
|
562
|
+
const slides = Array.isArray(msg.slides) ? msg.slides : [];
|
|
563
|
+
slides.forEach((slide, slideIndex) => {
|
|
564
|
+
const data = slide.data || {};
|
|
565
|
+
const url = typeof data.url === "string" ? data.url.trim() : "";
|
|
566
|
+
const mediaType = url ? this.slideMediaType(slide.type, url) : null;
|
|
567
|
+
if (!mediaType) return;
|
|
568
|
+
const file = createFile(`${playlistId}-${messageIndex}-${slideIndex}`, msg.name, url, {
|
|
569
|
+
mediaType,
|
|
570
|
+
thumbnail: msg.thumbnail,
|
|
571
|
+
seconds: slide.seconds,
|
|
572
|
+
loop: data.loop
|
|
573
|
+
});
|
|
574
|
+
file.downloadUrl = url;
|
|
575
|
+
files.push(file);
|
|
576
|
+
});
|
|
577
|
+
});
|
|
578
|
+
return files;
|
|
579
|
+
}
|
|
580
|
+
async getLegacyMessages(playlistId, auth) {
|
|
545
581
|
const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
|
|
546
582
|
if (!response) return [];
|
|
547
583
|
const files = [];
|
|
548
584
|
for (const msg of this.extractList(response, "messages")) {
|
|
549
|
-
|
|
550
|
-
|
|
585
|
+
const url = [msg.url, msg.thumbnail, msg.image].map((v) => typeof v === "string" ? v.trim() : "").find(Boolean) || "";
|
|
586
|
+
if (!url) continue;
|
|
551
587
|
const file = createFile(msg.id, msg.name, url, {
|
|
552
588
|
mediaType: detectMediaType(url, msg.mediaType),
|
|
553
589
|
thumbnail: msg.thumbnail || msg.image,
|
|
@@ -17723,8 +17759,7 @@ var GoogleDriveProvider = class extends BaseProvider {
|
|
|
17723
17759
|
apiBase: "https://www.googleapis.com/drive/v3",
|
|
17724
17760
|
// Token endpoint host; the user-facing consent screen is accounts.google.com (see buildAuthUrlFromChallenge)
|
|
17725
17761
|
oauthBase: "https://oauth2.googleapis.com",
|
|
17726
|
-
|
|
17727
|
-
clientId: "",
|
|
17762
|
+
clientId: "214868247432-hao323qmsfu96h345udaifs8f1nc7i71.apps.googleusercontent.com",
|
|
17728
17763
|
// Google requires client_secret even for PKCE public clients; injected server-side via setProviderSecret("googledrive", ...)
|
|
17729
17764
|
get clientSecret() {
|
|
17730
17765
|
return getProviderSecret("googledrive");
|
|
@@ -17762,7 +17797,9 @@ var GoogleDriveProvider = class extends BaseProvider {
|
|
|
17762
17797
|
orderBy: "folder,name_natural",
|
|
17763
17798
|
pageSize: "1000",
|
|
17764
17799
|
supportsAllDrives: "true",
|
|
17765
|
-
includeItemsFromAllDrives
|
|
17800
|
+
// corpora=user (not includeItemsFromAllDrives) — "root" is only meaningful in the user's own corpus, and
|
|
17801
|
+
// combining it with an all-drives search makes Google reject the query, which browse saw as an empty folder.
|
|
17802
|
+
corpora: "user"
|
|
17766
17803
|
});
|
|
17767
17804
|
if (pageToken) params.set("pageToken", pageToken);
|
|
17768
17805
|
const response = await this.apiRequest(`/files?${params.toString()}`, auth);
|
|
@@ -17776,7 +17813,7 @@ var GoogleDriveProvider = class extends BaseProvider {
|
|
|
17776
17813
|
async childItems(folderId, auth) {
|
|
17777
17814
|
const entries = await this.listFiles(`'${folderId}' in parents and trashed=false`, auth);
|
|
17778
17815
|
const folders = entries.filter((e) => e.mimeType === FOLDER_MIME);
|
|
17779
|
-
const mediaFiles = entries.filter((e) => e.mimeType.startsWith("video/")
|
|
17816
|
+
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)));
|
|
17780
17817
|
return { folders, mediaFiles };
|
|
17781
17818
|
}
|
|
17782
17819
|
/** One batched query marks which sibling folders have subfolders; null = unknown (isLeaf omitted). */
|
|
@@ -17933,7 +17970,7 @@ function getAvailableProviders(ids) {
|
|
|
17933
17970
|
}
|
|
17934
17971
|
|
|
17935
17972
|
// src/index.ts
|
|
17936
|
-
var VERSION = true ? "0.9.
|
|
17973
|
+
var VERSION = true ? "0.9.2" : "dev";
|
|
17937
17974
|
export {
|
|
17938
17975
|
APlayProvider,
|
|
17939
17976
|
B1ChurchProvider,
|