@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.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
- console.warn(`[${providerId}] apiRequest failed: ${method} ${url} \u2192 HTTP ${response.status} ${response.statusText}`);
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
- if (!msg.url) continue;
550
- const url = msg.url;
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
- // TODO owner action: Google Cloud OAuth web client id (drive.readonly scope) — fill before publishing
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: "true"
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/") || e.mimeType.startsWith("image/"));
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.0" : "dev";
17973
+ var VERSION = true ? "0.9.2" : "dev";
17937
17974
  export {
17938
17975
  APlayProvider,
17939
17976
  B1ChurchProvider,