@churchapps/content-providers 0.9.4 → 0.11.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 CHANGED
@@ -52,6 +52,7 @@ const plan = await provider.getPresentations(serviceId);
52
52
  - **CbnProvider** - CBN Superbook curriculum
53
53
  - **HighVoltageKidsProvider** - High Voltage Kids curriculum (static catalog)
54
54
  - **LifeChurchProvider** - Life.Church kids curriculum (static catalog)
55
+ - **FreeShowProvider** - FreeShow shows discovered on the local network (mocked announcements playlist)
55
56
 
56
57
  ### Create a Custom Provider
57
58
 
@@ -66,6 +67,7 @@ class MyProvider extends BaseProvider {
66
67
  // (OAuthHelper implements the standard flow; delegate to it like SignPresenterProvider)
67
68
  // device_flow -> initiateDeviceFlow / pollDeviceFlowToken (see DeviceFlowHelper)
68
69
  // form_login -> performLogin
70
+ // network_discovery -> discoverInstances
69
71
  // playlist / instructions / mediaLicensing -> getPlaylist / getInstructions / checkMediaLicense
70
72
  // The protected apiRequest() helper performs authenticated JSON fetches against config.apiBase.
71
73
  }
package/dist/index.cjs CHANGED
@@ -27,16 +27,19 @@ __export(index_exports, {
27
27
  CbnProvider: () => CbnProvider,
28
28
  DeviceFlowHelper: () => DeviceFlowHelper,
29
29
  DropboxProvider: () => DropboxProvider,
30
+ FreeShowProvider: () => FreeShowProvider,
30
31
  HighVoltageKidsProvider: () => HighVoltageKidsProvider,
31
32
  JesusFilmProvider: () => JesusFilmProvider,
32
33
  LessonsChurchProvider: () => LessonsChurchProvider,
33
34
  LifeChurchProvider: () => LifeChurchProvider,
35
+ MOCK_FREESHOW_INSTANCE: () => MOCK_FREESHOW_INSTANCE,
34
36
  OAuthHelper: () => OAuthHelper,
35
37
  PlanningCenterProvider: () => PlanningCenterProvider,
36
38
  SignPresenterProvider: () => SignPresenterProvider,
37
39
  TokenHelper: () => TokenHelper,
38
40
  VERSION: () => VERSION,
39
41
  authIsExpired: () => authIsExpired,
42
+ authToNetworkInstance: () => authToNetworkInstance,
40
43
  getAllProviders: () => getAllProviders,
41
44
  getAvailableProviders: () => getAvailableProviders,
42
45
  getProvider: () => getProvider,
@@ -44,6 +47,7 @@ __export(index_exports, {
44
47
  isContentFile: () => isContentFile,
45
48
  isContentFolder: () => isContentFolder,
46
49
  navigateToPath: () => navigateToPath,
50
+ networkInstanceToAuth: () => networkInstanceToAuth,
47
51
  registerProvider: () => registerProvider,
48
52
  setProviderSecret: () => setProviderSecret,
49
53
  toAuthData: () => toAuthData
@@ -338,6 +342,12 @@ function isMediaFile(filename) {
338
342
  const lower = filename.toLowerCase();
339
343
  return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
340
344
  }
345
+ function isPlaylistMedia(url, fileType, name) {
346
+ if (fileType?.startsWith("video/") || fileType?.startsWith("audio/") || fileType === "video" || fileType === "audio") return true;
347
+ const haystack = `${url} ${name || ""}`.toLowerCase();
348
+ if (haystack.includes("stream.mux.com") || haystack.includes("externalvideos/download")) return true;
349
+ return [...VIDEO_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => haystack.includes(ext));
350
+ }
341
351
  function createFolder(id, title, path, thumbnail, isLeaf) {
342
352
  return { type: "folder", id, title, path, thumbnail, isLeaf };
343
353
  }
@@ -1011,6 +1021,18 @@ var LessonsChurchProvider = class extends BaseProvider {
1011
1021
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
1012
1022
  return filtered.map((addOn) => createFolder(addOn.id, addOn.name, `${currentPath}/${addOn.id}`, addOn.image, true));
1013
1023
  }
1024
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
1025
+ convertFeedDownloads(bundles) {
1026
+ const result = [];
1027
+ for (const bundle of bundles || []) {
1028
+ const files = (bundle.files || []).filter((f) => f.url && !isPlaylistMedia(f.url, f.fileType, f.name));
1029
+ for (const file of files) {
1030
+ const title = (files.length === 1 ? bundle.name || file.name : file.name || bundle.name) || "Download";
1031
+ result.push({ id: file.id, title, url: file.url, fileType: file.fileType, bytes: file.bytes });
1032
+ }
1033
+ }
1034
+ return result;
1035
+ }
1014
1036
  async getInstructions(path, _auth) {
1015
1037
  const venueId = getSegment(path, 4);
1016
1038
  if (venueId) {
@@ -1022,7 +1044,8 @@ var LessonsChurchProvider = class extends BaseProvider {
1022
1044
  if (!planItemsResponse) return null;
1023
1045
  const lessonImage = feedResponse?.lessonImage;
1024
1046
  const sectionActionsMap = buildSectionActionsMap(actionsResponse, lessonImage, feedResponse);
1025
- return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)) };
1047
+ const downloads = this.convertFeedDownloads(feedResponse?.downloads);
1048
+ return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)), downloads: downloads.length > 0 ? downloads : void 0 };
1026
1049
  }
1027
1050
  const { segments } = parsePath(path);
1028
1051
  if (segments[0] === "addons" && segments.length === 3) {
@@ -12562,6 +12585,255 @@ var CbnProvider = class extends BaseProvider {
12562
12585
  }
12563
12586
  };
12564
12587
 
12588
+ // src/providers/freeShow/announcements.show.json
12589
+ var announcements_show_default = [
12590
+ "2f357194dfc",
12591
+ {
12592
+ name: "Announcements",
12593
+ private: false,
12594
+ category: "presentation",
12595
+ settings: {
12596
+ activeLayout: "f2f357194df",
12597
+ template: "default"
12598
+ },
12599
+ timestamps: {
12600
+ created: 1769712923789,
12601
+ modified: 1769713271120,
12602
+ used: 1769713271120
12603
+ },
12604
+ quickAccess: {},
12605
+ meta: {},
12606
+ slides: {
12607
+ da7b854ee5b: {
12608
+ group: "Kids Theme Loop",
12609
+ color: null,
12610
+ settings: {},
12611
+ notes: "",
12612
+ items: []
12613
+ },
12614
+ "5a99fba3e52": {
12615
+ group: "Preteen Theme",
12616
+ color: null,
12617
+ settings: {},
12618
+ notes: "",
12619
+ items: []
12620
+ },
12621
+ "52d2ca79bcd": {
12622
+ group: "Kids Theme Transition to Virtue",
12623
+ color: null,
12624
+ settings: {},
12625
+ notes: "",
12626
+ items: []
12627
+ },
12628
+ cd55e66060b: {
12629
+ group: "Theme",
12630
+ color: null,
12631
+ settings: {},
12632
+ notes: "",
12633
+ items: []
12634
+ }
12635
+ },
12636
+ layouts: {
12637
+ f2f357194df: {
12638
+ name: "Default",
12639
+ notes: "",
12640
+ slides: [
12641
+ {
12642
+ id: "da7b854ee5b",
12643
+ background: "a7b854ee5bf",
12644
+ actions: { nextAfterMedia: true }
12645
+ },
12646
+ {
12647
+ id: "5a99fba3e52",
12648
+ background: "a99fba3e52d",
12649
+ nextTimer: 7
12650
+ },
12651
+ {
12652
+ id: "52d2ca79bcd",
12653
+ background: "2d2ca79bcd5",
12654
+ actions: { nextAfterMedia: true }
12655
+ },
12656
+ {
12657
+ id: "cd55e66060b",
12658
+ background: "d55e66060b8",
12659
+ nextTimer: 7,
12660
+ end: true
12661
+ }
12662
+ ]
12663
+ }
12664
+ },
12665
+ media: {
12666
+ a7b854ee5bf: {
12667
+ name: "Kids Theme Loop",
12668
+ path: "https://stream.mux.com/kDsWzEqyN7TDoMM3Aykfq1QhEo01JhCWO7t2sABictzk/capped-1080p.mp4",
12669
+ type: "video",
12670
+ contentProvider: "amazinglife",
12671
+ muted: false,
12672
+ loop: false
12673
+ },
12674
+ a99fba3e52d: {
12675
+ name: "Preteen Theme",
12676
+ path: "https://www.datocms-assets.com/75190/1746649082-2507_screen_preteen_theme.jpg",
12677
+ type: "image",
12678
+ contentProvider: "amazinglife"
12679
+ },
12680
+ "2d2ca79bcd5": {
12681
+ name: "Kids Theme Transition to Virtue",
12682
+ path: "https://stream.mux.com/101lB00Wj00AMUsjbuNYkNofkNHQXpZeThL3mFZNsSB5A00/capped-1080p.mp4",
12683
+ type: "video",
12684
+ contentProvider: "amazinglife",
12685
+ muted: false,
12686
+ loop: false
12687
+ },
12688
+ d55e66060b8: {
12689
+ name: "Theme",
12690
+ path: "https://www.datocms-assets.com/75190/1746648669-2507_screen_kids_theme.jpg",
12691
+ type: "image",
12692
+ contentProvider: "amazinglife"
12693
+ }
12694
+ }
12695
+ }
12696
+ ];
12697
+
12698
+ // src/providers/freeShow/FreeShowInterfaces.ts
12699
+ var DEFAULT_SLIDE_SECONDS = 5;
12700
+
12701
+ // src/providers/freeShow/FreeShowConverters.ts
12702
+ var MOCK_FREESHOW_INSTANCE = {
12703
+ id: "mock-local",
12704
+ name: "This PC",
12705
+ host: "localhost",
12706
+ ip: "127.0.0.1",
12707
+ port: 5505
12708
+ };
12709
+ function parseShowExport(raw) {
12710
+ if (Array.isArray(raw) && raw.length >= 2 && typeof raw[0] === "string" && raw[1] && typeof raw[1] === "object") {
12711
+ return { id: raw[0], show: raw[1] };
12712
+ }
12713
+ if (raw && typeof raw === "object" && "layouts" in raw && "media" in raw) {
12714
+ const show = raw;
12715
+ return { id: show.id || "show", show };
12716
+ }
12717
+ return null;
12718
+ }
12719
+ function slideSeconds(ref, mediaType) {
12720
+ if (typeof ref.nextTimer === "number" && ref.nextTimer > 0) return ref.nextTimer;
12721
+ if (ref.actions?.nextAfterMedia && mediaType === "video") return void 0;
12722
+ return DEFAULT_SLIDE_SECONDS;
12723
+ }
12724
+ function showToFiles(show) {
12725
+ const layoutId = show.settings?.activeLayout || Object.keys(show.layouts || {})[0];
12726
+ const layout = layoutId ? show.layouts?.[layoutId] : void 0;
12727
+ const refs = layout?.slides || [];
12728
+ const loops = refs.some((ref) => ref.end === true);
12729
+ const files = [];
12730
+ for (const ref of refs) {
12731
+ if (ref.disabled || !ref.background) continue;
12732
+ const media = show.media?.[ref.background];
12733
+ const path = media?.path;
12734
+ if (!path) continue;
12735
+ const mediaType = detectMediaType(path, media?.type);
12736
+ const slide = show.slides?.[ref.id];
12737
+ const title = media?.name || slide?.group || "Slide";
12738
+ files.push(createFile(ref.id, title, path, {
12739
+ mediaType,
12740
+ thumbnail: mediaType === "image" ? path : void 0,
12741
+ seconds: slideSeconds(ref, mediaType),
12742
+ loop: loops,
12743
+ loopVideo: false
12744
+ }));
12745
+ }
12746
+ return files;
12747
+ }
12748
+ function showToFolder(id, show) {
12749
+ const files = showToFiles(show);
12750
+ const thumbnail = files.find((file) => file.mediaType === "image")?.url || files[0]?.thumbnail;
12751
+ return createFolder(id, show.name || "Show", `/${id}`, thumbnail, true);
12752
+ }
12753
+ function networkInstanceToAuth(instance) {
12754
+ return {
12755
+ access_token: instance.id,
12756
+ refresh_token: JSON.stringify(instance),
12757
+ token_type: "network",
12758
+ created_at: Math.floor(Date.now() / 1e3),
12759
+ expires_in: 10 * 365 * 24 * 3600,
12760
+ scope: "freeshow"
12761
+ };
12762
+ }
12763
+ function authToNetworkInstance(auth) {
12764
+ if (!auth?.refresh_token) return null;
12765
+ try {
12766
+ const parsed = JSON.parse(auth.refresh_token);
12767
+ if (parsed?.id && parsed?.name) return parsed;
12768
+ } catch {
12769
+ return null;
12770
+ }
12771
+ return null;
12772
+ }
12773
+
12774
+ // src/providers/freeShow/FreeShowProvider.ts
12775
+ var parsedMock = parseShowExport(announcements_show_default);
12776
+ var MOCK_SHOW_ID = parsedMock?.id || "announcements";
12777
+ var MOCK_SHOW = parsedMock?.show || { name: "Announcements", layouts: {}, media: {}, slides: {} };
12778
+ var FreeShowProvider = class extends BaseProvider {
12779
+ constructor() {
12780
+ super(...arguments);
12781
+ this.id = "freeshow";
12782
+ this.name = "FreeShow";
12783
+ this.logos = {
12784
+ light: "https://freeshow.app/images/favicon.png",
12785
+ dark: "https://freeshow.app/images/favicon.png"
12786
+ };
12787
+ this.config = {
12788
+ id: "freeshow",
12789
+ name: "FreeShow",
12790
+ apiBase: "",
12791
+ oauthBase: "",
12792
+ clientId: "",
12793
+ scopes: []
12794
+ };
12795
+ this.requiresAuth = true;
12796
+ this.authTypes = ["network_discovery"];
12797
+ this.capabilities = {
12798
+ browse: true,
12799
+ playlist: true,
12800
+ instructions: true,
12801
+ mediaLicensing: false
12802
+ };
12803
+ this.discoverer = null;
12804
+ }
12805
+ setDiscoverer(discoverer) {
12806
+ this.discoverer = discoverer;
12807
+ }
12808
+ async discoverInstances() {
12809
+ if (this.discoverer) return this.discoverer();
12810
+ await new Promise((resolve) => setTimeout(resolve, 600));
12811
+ return [MOCK_FREESHOW_INSTANCE];
12812
+ }
12813
+ toAuth(instance) {
12814
+ return networkInstanceToAuth(instance);
12815
+ }
12816
+ selectedInstance(auth) {
12817
+ return authToNetworkInstance(auth);
12818
+ }
12819
+ async browse(path, _auth) {
12820
+ const { depth } = parsePath(path);
12821
+ if (depth === 0) return [showToFolder(MOCK_SHOW_ID, MOCK_SHOW)];
12822
+ return [];
12823
+ }
12824
+ async getPlaylist(path, _auth, _resolution) {
12825
+ const { segments, depth } = parsePath(path);
12826
+ if (depth < 1 || segments[0] !== MOCK_SHOW_ID) return null;
12827
+ const files = showToFiles(MOCK_SHOW);
12828
+ return files.length > 0 ? files : null;
12829
+ }
12830
+ async getInstructions(path, auth) {
12831
+ const files = await this.getPlaylist(path, auth);
12832
+ if (!files) return null;
12833
+ return filesToInstructions(MOCK_SHOW.name || "Show", files);
12834
+ }
12835
+ };
12836
+
12565
12837
  // src/providers/lifeChurch/data.json
12566
12838
  var data_default3 = {
12567
12839
  generatedAt: "2026-05-09T20:14:53.089Z",
@@ -17792,7 +18064,10 @@ var GoCurriculumProvider = class extends BaseProvider {
17792
18064
  if (!files) return null;
17793
18065
  const collection = (await this.getData()).catalog.find((c) => c.id === segments[0]);
17794
18066
  const lesson = collection?.lessons.find((l) => l.id === segments[1]);
17795
- return filesToInstructions(lesson?.name || "Lesson", files);
18067
+ const instructions = filesToInstructions(lesson?.name || "Lesson", files);
18068
+ const downloads = (lesson?.resources || []).filter((r) => r.url && !isPlaylistMedia(r.url, void 0, r.file)).map((r) => ({ id: slugify(r.file || r.title), title: r.title || r.file, url: r.url }));
18069
+ if (downloads.length > 0) instructions.downloads = downloads;
18070
+ return instructions;
17796
18071
  }
17797
18072
  generateCodeVerifier() {
17798
18073
  return this.oauthHelper.generateCodeVerifier();
@@ -17963,14 +18238,6 @@ var unimplementedProviders = [
17963
18238
  dark: "https://www.awana.org/wp-content/uploads/2025/04/awana-logo-white.svg"
17964
18239
  }
17965
18240
  },
17966
- {
17967
- id: "freeshow",
17968
- name: "FreeShow",
17969
- logos: {
17970
- light: "https://freeshow.app/images/favicon.png",
17971
- dark: "https://freeshow.app/images/favicon.png"
17972
- }
17973
- },
17974
18241
  {
17975
18242
  id: "iteachchurch",
17976
18243
  name: "iTeachChurch",
@@ -18002,7 +18269,8 @@ function initializeProviders() {
18002
18269
  new LifeChurchProvider(),
18003
18270
  new PlanningCenterProvider(),
18004
18271
  new SignPresenterProvider(),
18005
- new CbnProvider()
18272
+ new CbnProvider(),
18273
+ new FreeShowProvider()
18006
18274
  ];
18007
18275
  for (const provider of providers) registerProvider(provider);
18008
18276
  }
@@ -18039,7 +18307,7 @@ function getAvailableProviders(ids) {
18039
18307
  }
18040
18308
 
18041
18309
  // src/index.ts
18042
- var VERSION = true ? "0.9.4" : "dev";
18310
+ var VERSION = true ? "0.11.0" : "dev";
18043
18311
  // Annotate the CommonJS export names for ESM import in node:
18044
18312
  0 && (module.exports = {
18045
18313
  APlayProvider,
@@ -18049,16 +18317,19 @@ var VERSION = true ? "0.9.4" : "dev";
18049
18317
  CbnProvider,
18050
18318
  DeviceFlowHelper,
18051
18319
  DropboxProvider,
18320
+ FreeShowProvider,
18052
18321
  HighVoltageKidsProvider,
18053
18322
  JesusFilmProvider,
18054
18323
  LessonsChurchProvider,
18055
18324
  LifeChurchProvider,
18325
+ MOCK_FREESHOW_INSTANCE,
18056
18326
  OAuthHelper,
18057
18327
  PlanningCenterProvider,
18058
18328
  SignPresenterProvider,
18059
18329
  TokenHelper,
18060
18330
  VERSION,
18061
18331
  authIsExpired,
18332
+ authToNetworkInstance,
18062
18333
  getAllProviders,
18063
18334
  getAvailableProviders,
18064
18335
  getProvider,
@@ -18066,6 +18337,7 @@ var VERSION = true ? "0.9.4" : "dev";
18066
18337
  isContentFile,
18067
18338
  isContentFolder,
18068
18339
  navigateToPath,
18340
+ networkInstanceToAuth,
18069
18341
  registerProvider,
18070
18342
  setProviderSecret,
18071
18343
  toAuthData