@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 CHANGED
@@ -71,13 +71,15 @@ function navigateToPath(instructions, path) {
71
71
 
72
72
  // src/helpers/TokenHelper.ts
73
73
  function toAuthData(data, fallbacks) {
74
+ const planTypeId = data.plan_type_id || fallbacks?.planTypeId;
74
75
  return {
75
76
  access_token: data.access_token,
76
77
  refresh_token: data.refresh_token || fallbacks?.refreshToken || "",
77
78
  token_type: data.token_type || "Bearer",
78
79
  created_at: Math.floor(Date.now() / 1e3),
79
80
  expires_in: data.expires_in,
80
- scope: data.scope || fallbacks?.scope || ""
81
+ scope: data.scope || fallbacks?.scope || "",
82
+ ...planTypeId ? { planTypeId } : {}
81
83
  };
82
84
  }
83
85
  var TokenHelper = class {
@@ -105,7 +107,8 @@ var TokenHelper = class {
105
107
  return null;
106
108
  }
107
109
  const data = await response.json();
108
- return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope });
110
+ const planTypeId = auth.planTypeId;
111
+ return toAuthData(data, { refreshToken: auth.refresh_token, scope: auth.scope, planTypeId });
109
112
  } catch {
110
113
  return null;
111
114
  }
@@ -242,7 +245,8 @@ var ApiHelper = class {
242
245
  const options = { method, headers, ...body ? { body: JSON.stringify(body) } : {} };
243
246
  const response = await fetch(url, options);
244
247
  if (!response.ok) {
245
- console.warn(`[${providerId}] apiRequest failed: ${method} ${url} \u2192 HTTP ${response.status} ${response.statusText}`);
248
+ const detail = await response.text().catch(() => "");
249
+ console.warn(`[${providerId}] apiRequest failed: ${method} ${url} \u2192 HTTP ${response.status} ${response.statusText}${detail ? ` ${detail.slice(0, 500)}` : ""}`);
246
250
  return null;
247
251
  }
248
252
  return await response.json();
@@ -303,16 +307,19 @@ var IMAGE_EXTENSIONS = [
303
307
  ".tiff",
304
308
  ".tif"
305
309
  ];
310
+ var AUDIO_EXTENSIONS = [".mp3", ".m4a", ".aac", ".wav", ".flac", ".oga"];
306
311
  function detectMediaType(url, explicitType) {
307
312
  if (explicitType === "video" || explicitType?.startsWith("video/")) return "video";
308
313
  if (explicitType === "image" || explicitType?.startsWith("image/")) return "image";
314
+ if (explicitType === "audio" || explicitType?.startsWith("audio/")) return "audio";
309
315
  const lower = url.toLowerCase();
310
316
  if (VIDEO_EXTENSIONS.some((p) => lower.includes(p)) || lower.includes("stream.mux.com")) return "video";
317
+ if (AUDIO_EXTENSIONS.some((p) => lower.includes(p))) return "audio";
311
318
  return "image";
312
319
  }
313
320
  function isMediaFile(filename) {
314
321
  const lower = filename.toLowerCase();
315
- return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS].some((ext) => lower.endsWith(ext));
322
+ return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
316
323
  }
317
324
  function createFolder(id, title, path, thumbnail, isLeaf) {
318
325
  return { type: "folder", id, title, path, thumbnail, isLeaf };
@@ -592,13 +599,45 @@ var SignPresenterProvider = class extends BaseProvider {
592
599
  isLeaf: true
593
600
  }));
594
601
  }
602
+ /** 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. */
603
+ slideMediaType(slideType, url) {
604
+ if (slideType === "image") return "image";
605
+ if (slideType === "video" || slideType === "stream") return "video";
606
+ if (slideType === "web" && isMediaFile(url)) return detectMediaType(url);
607
+ return null;
608
+ }
609
+ /** The documented player contract; /content/playlists/:id/messages only covers templates 1 and 3 so most playlists come back empty there. */
595
610
  async getMessages(playlistId, auth) {
611
+ const response = await this.apiRequest(`/v2/playlists/${playlistId}`, auth);
612
+ const messages = this.extractList(response, "messages");
613
+ if (messages.length === 0) return this.getLegacyMessages(playlistId, auth);
614
+ const files = [];
615
+ messages.forEach((msg, messageIndex) => {
616
+ const slides = Array.isArray(msg.slides) ? msg.slides : [];
617
+ slides.forEach((slide, slideIndex) => {
618
+ const data = slide.data || {};
619
+ const url = typeof data.url === "string" ? data.url.trim() : "";
620
+ const mediaType = url ? this.slideMediaType(slide.type, url) : null;
621
+ if (!mediaType) return;
622
+ const file = createFile(`${playlistId}-${messageIndex}-${slideIndex}`, msg.name, url, {
623
+ mediaType,
624
+ thumbnail: msg.thumbnail,
625
+ seconds: slide.seconds,
626
+ loop: data.loop
627
+ });
628
+ file.downloadUrl = url;
629
+ files.push(file);
630
+ });
631
+ });
632
+ return files;
633
+ }
634
+ async getLegacyMessages(playlistId, auth) {
596
635
  const response = await this.apiRequest(`/content/playlists/${playlistId}/messages`, auth);
597
636
  if (!response) return [];
598
637
  const files = [];
599
638
  for (const msg of this.extractList(response, "messages")) {
600
- if (!msg.url) continue;
601
- const url = msg.url;
639
+ const url = [msg.url, msg.thumbnail, msg.image].map((v) => typeof v === "string" ? v.trim() : "").find(Boolean) || "";
640
+ if (!url) continue;
602
641
  const file = createFile(msg.id, msg.name, url, {
603
642
  mediaType: detectMediaType(url, msg.mediaType),
604
643
  thumbnail: msg.thumbnail || msg.image,
@@ -1447,9 +1486,7 @@ var B1ChurchProvider = class extends BaseProvider {
1447
1486
  setPairingData(data) {
1448
1487
  if (data && typeof data === "object" && "planTypeId" in data) {
1449
1488
  const planTypeId = data.planTypeId;
1450
- this.planTypeId = typeof planTypeId === "string" ? planTypeId : null;
1451
- } else {
1452
- this.planTypeId = null;
1489
+ if (typeof planTypeId === "string") this.planTypeId = planTypeId;
1453
1490
  }
1454
1491
  }
1455
1492
  async getCurrentPlan(authData) {
@@ -17812,7 +17849,9 @@ var GoogleDriveProvider = class extends BaseProvider {
17812
17849
  orderBy: "folder,name_natural",
17813
17850
  pageSize: "1000",
17814
17851
  supportsAllDrives: "true",
17815
- includeItemsFromAllDrives: "true"
17852
+ // corpora=user (not includeItemsFromAllDrives) — "root" is only meaningful in the user's own corpus, and
17853
+ // combining it with an all-drives search makes Google reject the query, which browse saw as an empty folder.
17854
+ corpora: "user"
17816
17855
  });
17817
17856
  if (pageToken) params.set("pageToken", pageToken);
17818
17857
  const response = await this.apiRequest(`/files?${params.toString()}`, auth);
@@ -17826,7 +17865,7 @@ var GoogleDriveProvider = class extends BaseProvider {
17826
17865
  async childItems(folderId, auth) {
17827
17866
  const entries = await this.listFiles(`'${folderId}' in parents and trashed=false`, auth);
17828
17867
  const folders = entries.filter((e) => e.mimeType === FOLDER_MIME);
17829
- const mediaFiles = entries.filter((e) => e.mimeType.startsWith("video/") || e.mimeType.startsWith("image/"));
17868
+ 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)));
17830
17869
  return { folders, mediaFiles };
17831
17870
  }
17832
17871
  /** One batched query marks which sibling folders have subfolders; null = unknown (isLeaf omitted). */
@@ -17983,7 +18022,7 @@ function getAvailableProviders(ids) {
17983
18022
  }
17984
18023
 
17985
18024
  // src/index.ts
17986
- var VERSION = true ? "0.9.1" : "dev";
18025
+ var VERSION = true ? "0.9.3" : "dev";
17987
18026
  // Annotate the CommonJS export names for ESM import in node:
17988
18027
  0 && (module.exports = {
17989
18028
  APlayProvider,