@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/dist/index.d.cts CHANGED
@@ -38,7 +38,14 @@ interface DeviceFlowState {
38
38
  error?: string;
39
39
  pollCount?: number;
40
40
  }
41
- type AuthType = "none" | "oauth_pkce" | "device_flow" | "form_login";
41
+ type AuthType = "none" | "oauth_pkce" | "device_flow" | "form_login" | "network_discovery";
42
+ interface NetworkInstance {
43
+ id: string;
44
+ name: string;
45
+ host: string;
46
+ ip: string;
47
+ port: number;
48
+ }
42
49
  interface ProviderLogos {
43
50
  light: string;
44
51
  dark: string;
@@ -116,6 +123,11 @@ interface FeedFileInterface {
116
123
  seconds?: number;
117
124
  fileType?: string;
118
125
  thumbnail?: string;
126
+ bytes?: number;
127
+ }
128
+ interface FeedDownloadInterface {
129
+ name?: string;
130
+ files?: FeedFileInterface[];
119
131
  }
120
132
  interface FeedActionInterface {
121
133
  id?: string;
@@ -135,6 +147,7 @@ interface FeedVenueInterface {
135
147
  lessonName?: string;
136
148
  lessonDescription?: string;
137
149
  lessonImage?: string;
150
+ downloads?: FeedDownloadInterface[];
138
151
  sections?: FeedSectionInterface[];
139
152
  }
140
153
  interface InstructionItem {
@@ -150,9 +163,18 @@ interface InstructionItem {
150
163
  thumbnail?: string;
151
164
  mediaType?: "video" | "image" | "audio";
152
165
  }
166
+ /** An extra file the teacher downloads/prints (zip, PDF, leader guide) — never part of the presented playlist. */
167
+ interface ContentDownload {
168
+ id?: string;
169
+ title: string;
170
+ url: string;
171
+ fileType?: string;
172
+ bytes?: number;
173
+ }
153
174
  interface Instructions {
154
175
  name?: string;
155
176
  items: InstructionItem[];
177
+ downloads?: ContentDownload[];
156
178
  }
157
179
  interface MessageFileInterface {
158
180
  id?: string;
@@ -233,6 +255,7 @@ interface IProvider {
233
255
  initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
234
256
  pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
235
257
  performLogin?(email: string, password: string): Promise<ContentProviderAuthData | null>;
258
+ discoverInstances?(): Promise<NetworkInstance[]>;
236
259
  getPlaylist?(path: string, auth?: ContentProviderAuthData | null, resolution?: number): Promise<ContentFile[] | null>;
237
260
  getInstructions?(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
238
261
  getCurrentPlan?(auth?: ContentProviderAuthData | null): Promise<CurrentPlan | null>;
@@ -380,6 +403,8 @@ declare class LessonsChurchProvider extends BaseProvider {
380
403
  private getAddOnFiles;
381
404
  private getAddOnCategories;
382
405
  private getAddOnsByCategory;
406
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
407
+ private convertFeedDownloads;
383
408
  getInstructions(path: string, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
384
409
  }
385
410
 
@@ -546,6 +571,30 @@ declare class CbnProvider extends BaseProvider {
546
571
  pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
547
572
  }
548
573
 
574
+ type FreeShowDiscoverer = () => Promise<NetworkInstance[]>;
575
+
576
+ declare class FreeShowProvider extends BaseProvider {
577
+ readonly id = "freeshow";
578
+ readonly name = "FreeShow";
579
+ readonly logos: ProviderLogos;
580
+ readonly config: ContentProviderConfig;
581
+ readonly requiresAuth = true;
582
+ readonly authTypes: AuthType[];
583
+ readonly capabilities: ProviderCapabilities;
584
+ private discoverer;
585
+ setDiscoverer(discoverer: FreeShowDiscoverer | null): void;
586
+ discoverInstances(): Promise<NetworkInstance[]>;
587
+ toAuth(instance: NetworkInstance): ContentProviderAuthData;
588
+ selectedInstance(auth?: ContentProviderAuthData | null): NetworkInstance | null;
589
+ browse(path?: string | null, _auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
590
+ getPlaylist(path: string, _auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
591
+ getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
592
+ }
593
+
594
+ declare const MOCK_FREESHOW_INSTANCE: NetworkInstance;
595
+ declare function networkInstanceToAuth(instance: NetworkInstance): ContentProviderAuthData;
596
+ declare function authToNetworkInstance(auth?: ContentProviderAuthData | null): NetworkInstance | null;
597
+
549
598
  /** Browses Life.Church's kids curriculum from a static catalog (series → units → lessons), plus a weekly scheduled lesson index. */
550
599
  declare class LifeChurchProvider extends BaseProvider {
551
600
  readonly id = "lifechurch";
@@ -589,4 +638,4 @@ declare function getAvailableProviders(ids?: string[]): ProviderInfo[];
589
638
  */
590
639
  declare const VERSION: string;
591
640
 
592
- export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
641
+ export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentDownload, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedDownloadInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, type FreeShowDiscoverer, FreeShowProvider, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, MOCK_FREESHOW_INSTANCE, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, type NetworkInstance, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, authToNetworkInstance, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, networkInstanceToAuth, registerProvider, setProviderSecret, toAuthData };
package/dist/index.d.ts CHANGED
@@ -38,7 +38,14 @@ interface DeviceFlowState {
38
38
  error?: string;
39
39
  pollCount?: number;
40
40
  }
41
- type AuthType = "none" | "oauth_pkce" | "device_flow" | "form_login";
41
+ type AuthType = "none" | "oauth_pkce" | "device_flow" | "form_login" | "network_discovery";
42
+ interface NetworkInstance {
43
+ id: string;
44
+ name: string;
45
+ host: string;
46
+ ip: string;
47
+ port: number;
48
+ }
42
49
  interface ProviderLogos {
43
50
  light: string;
44
51
  dark: string;
@@ -116,6 +123,11 @@ interface FeedFileInterface {
116
123
  seconds?: number;
117
124
  fileType?: string;
118
125
  thumbnail?: string;
126
+ bytes?: number;
127
+ }
128
+ interface FeedDownloadInterface {
129
+ name?: string;
130
+ files?: FeedFileInterface[];
119
131
  }
120
132
  interface FeedActionInterface {
121
133
  id?: string;
@@ -135,6 +147,7 @@ interface FeedVenueInterface {
135
147
  lessonName?: string;
136
148
  lessonDescription?: string;
137
149
  lessonImage?: string;
150
+ downloads?: FeedDownloadInterface[];
138
151
  sections?: FeedSectionInterface[];
139
152
  }
140
153
  interface InstructionItem {
@@ -150,9 +163,18 @@ interface InstructionItem {
150
163
  thumbnail?: string;
151
164
  mediaType?: "video" | "image" | "audio";
152
165
  }
166
+ /** An extra file the teacher downloads/prints (zip, PDF, leader guide) — never part of the presented playlist. */
167
+ interface ContentDownload {
168
+ id?: string;
169
+ title: string;
170
+ url: string;
171
+ fileType?: string;
172
+ bytes?: number;
173
+ }
153
174
  interface Instructions {
154
175
  name?: string;
155
176
  items: InstructionItem[];
177
+ downloads?: ContentDownload[];
156
178
  }
157
179
  interface MessageFileInterface {
158
180
  id?: string;
@@ -233,6 +255,7 @@ interface IProvider {
233
255
  initiateDeviceFlow?(): Promise<DeviceAuthorizationResponse | null>;
234
256
  pollDeviceFlowToken?(deviceCode: string): Promise<DeviceFlowPollResult>;
235
257
  performLogin?(email: string, password: string): Promise<ContentProviderAuthData | null>;
258
+ discoverInstances?(): Promise<NetworkInstance[]>;
236
259
  getPlaylist?(path: string, auth?: ContentProviderAuthData | null, resolution?: number): Promise<ContentFile[] | null>;
237
260
  getInstructions?(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
238
261
  getCurrentPlan?(auth?: ContentProviderAuthData | null): Promise<CurrentPlan | null>;
@@ -380,6 +403,8 @@ declare class LessonsChurchProvider extends BaseProvider {
380
403
  private getAddOnFiles;
381
404
  private getAddOnCategories;
382
405
  private getAddOnsByCategory;
406
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
407
+ private convertFeedDownloads;
383
408
  getInstructions(path: string, _auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
384
409
  }
385
410
 
@@ -546,6 +571,30 @@ declare class CbnProvider extends BaseProvider {
546
571
  pollDeviceFlowToken(deviceCode: string): Promise<DeviceFlowPollResult>;
547
572
  }
548
573
 
574
+ type FreeShowDiscoverer = () => Promise<NetworkInstance[]>;
575
+
576
+ declare class FreeShowProvider extends BaseProvider {
577
+ readonly id = "freeshow";
578
+ readonly name = "FreeShow";
579
+ readonly logos: ProviderLogos;
580
+ readonly config: ContentProviderConfig;
581
+ readonly requiresAuth = true;
582
+ readonly authTypes: AuthType[];
583
+ readonly capabilities: ProviderCapabilities;
584
+ private discoverer;
585
+ setDiscoverer(discoverer: FreeShowDiscoverer | null): void;
586
+ discoverInstances(): Promise<NetworkInstance[]>;
587
+ toAuth(instance: NetworkInstance): ContentProviderAuthData;
588
+ selectedInstance(auth?: ContentProviderAuthData | null): NetworkInstance | null;
589
+ browse(path?: string | null, _auth?: ContentProviderAuthData | null): Promise<ContentItem[]>;
590
+ getPlaylist(path: string, _auth?: ContentProviderAuthData | null, _resolution?: number): Promise<ContentFile[] | null>;
591
+ getInstructions(path: string, auth?: ContentProviderAuthData | null): Promise<Instructions | null>;
592
+ }
593
+
594
+ declare const MOCK_FREESHOW_INSTANCE: NetworkInstance;
595
+ declare function networkInstanceToAuth(instance: NetworkInstance): ContentProviderAuthData;
596
+ declare function authToNetworkInstance(auth?: ContentProviderAuthData | null): NetworkInstance | null;
597
+
549
598
  /** Browses Life.Church's kids curriculum from a static catalog (series → units → lessons), plus a weekly scheduled lesson index. */
550
599
  declare class LifeChurchProvider extends BaseProvider {
551
600
  readonly id = "lifechurch";
@@ -589,4 +638,4 @@ declare function getAvailableProviders(ids?: string[]): ProviderInfo[];
589
638
  */
590
639
  declare const VERSION: string;
591
640
 
592
- export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, registerProvider, setProviderSecret, toAuthData };
641
+ export { APlayProvider, type AuthType, B1ChurchProvider, BaseProvider, BibleProjectProvider, CbnProvider, type ContentDownload, type ContentFile, type ContentFolder, type ContentItem, type ContentProviderAuthData, type ContentProviderConfig, type CurrentPlan, type DeviceAuthorizationResponse, DeviceFlowHelper, type DeviceFlowPollResult, type DeviceFlowState, DropboxProvider, type EndpointValue, type EndpointsConfig, type FeedActionInterface, type FeedDownloadInterface, type FeedFileInterface, type FeedSectionInterface, type FeedVenueInterface, type FreeShowDiscoverer, FreeShowProvider, HighVoltageKidsProvider, type IProvider, type InstructionItem, type Instructions, JesusFilmProvider, LessonsChurchProvider, LifeChurchProvider, MOCK_FREESHOW_INSTANCE, type MediaLicenseResult, type MediaLicenseStatus, type MessageFileInterface, type NetworkInstance, OAuthHelper, type Plan, type PlanPresentation, type PlanSection, type PlanTimelineItem, PlanningCenterProvider, type ProviderCapabilities, type ProviderInfo, type ProviderLogos, SignPresenterProvider, TokenHelper, VERSION, type VenueActionInterface, type VenueActionsResponseInterface, type VenueSectionActionsInterface, authIsExpired, authToNetworkInstance, getAllProviders, getAvailableProviders, getProvider, getProviderConfig, isContentFile, isContentFolder, navigateToPath, networkInstanceToAuth, registerProvider, setProviderSecret, toAuthData };
package/dist/index.js CHANGED
@@ -286,6 +286,12 @@ function isMediaFile(filename) {
286
286
  const lower = filename.toLowerCase();
287
287
  return [...VIDEO_EXTENSIONS, ...IMAGE_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => lower.endsWith(ext));
288
288
  }
289
+ function isPlaylistMedia(url, fileType, name) {
290
+ if (fileType?.startsWith("video/") || fileType?.startsWith("audio/") || fileType === "video" || fileType === "audio") return true;
291
+ const haystack = `${url} ${name || ""}`.toLowerCase();
292
+ if (haystack.includes("stream.mux.com") || haystack.includes("externalvideos/download")) return true;
293
+ return [...VIDEO_EXTENSIONS, ...AUDIO_EXTENSIONS].some((ext) => haystack.includes(ext));
294
+ }
289
295
  function createFolder(id, title, path, thumbnail, isLeaf) {
290
296
  return { type: "folder", id, title, path, thumbnail, isLeaf };
291
297
  }
@@ -959,6 +965,18 @@ var LessonsChurchProvider = class extends BaseProvider {
959
965
  const filtered = allAddOns.filter((a) => a.category === decodedCategory);
960
966
  return filtered.map((addOn) => createFolder(addOn.id, addOn.name, `${currentPath}/${addOn.id}`, addOn.image, true));
961
967
  }
968
+ /** Flatten feed download bundles into printable files, keeping playlist media (videos already in the run-of-show) out. */
969
+ convertFeedDownloads(bundles) {
970
+ const result = [];
971
+ for (const bundle of bundles || []) {
972
+ const files = (bundle.files || []).filter((f) => f.url && !isPlaylistMedia(f.url, f.fileType, f.name));
973
+ for (const file of files) {
974
+ const title = (files.length === 1 ? bundle.name || file.name : file.name || bundle.name) || "Download";
975
+ result.push({ id: file.id, title, url: file.url, fileType: file.fileType, bytes: file.bytes });
976
+ }
977
+ }
978
+ return result;
979
+ }
962
980
  async getInstructions(path, _auth) {
963
981
  const venueId = getSegment(path, 4);
964
982
  if (venueId) {
@@ -970,7 +988,8 @@ var LessonsChurchProvider = class extends BaseProvider {
970
988
  if (!planItemsResponse) return null;
971
989
  const lessonImage = feedResponse?.lessonImage;
972
990
  const sectionActionsMap = buildSectionActionsMap(actionsResponse, lessonImage, feedResponse);
973
- return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)) };
991
+ const downloads = this.convertFeedDownloads(feedResponse?.downloads);
992
+ return { name: planItemsResponse.venueName, items: (planItemsResponse.items || []).map((item) => processInstructionItem(item, sectionActionsMap, lessonImage)), downloads: downloads.length > 0 ? downloads : void 0 };
974
993
  }
975
994
  const { segments } = parsePath(path);
976
995
  if (segments[0] === "addons" && segments.length === 3) {
@@ -12510,6 +12529,255 @@ var CbnProvider = class extends BaseProvider {
12510
12529
  }
12511
12530
  };
12512
12531
 
12532
+ // src/providers/freeShow/announcements.show.json
12533
+ var announcements_show_default = [
12534
+ "2f357194dfc",
12535
+ {
12536
+ name: "Announcements",
12537
+ private: false,
12538
+ category: "presentation",
12539
+ settings: {
12540
+ activeLayout: "f2f357194df",
12541
+ template: "default"
12542
+ },
12543
+ timestamps: {
12544
+ created: 1769712923789,
12545
+ modified: 1769713271120,
12546
+ used: 1769713271120
12547
+ },
12548
+ quickAccess: {},
12549
+ meta: {},
12550
+ slides: {
12551
+ da7b854ee5b: {
12552
+ group: "Kids Theme Loop",
12553
+ color: null,
12554
+ settings: {},
12555
+ notes: "",
12556
+ items: []
12557
+ },
12558
+ "5a99fba3e52": {
12559
+ group: "Preteen Theme",
12560
+ color: null,
12561
+ settings: {},
12562
+ notes: "",
12563
+ items: []
12564
+ },
12565
+ "52d2ca79bcd": {
12566
+ group: "Kids Theme Transition to Virtue",
12567
+ color: null,
12568
+ settings: {},
12569
+ notes: "",
12570
+ items: []
12571
+ },
12572
+ cd55e66060b: {
12573
+ group: "Theme",
12574
+ color: null,
12575
+ settings: {},
12576
+ notes: "",
12577
+ items: []
12578
+ }
12579
+ },
12580
+ layouts: {
12581
+ f2f357194df: {
12582
+ name: "Default",
12583
+ notes: "",
12584
+ slides: [
12585
+ {
12586
+ id: "da7b854ee5b",
12587
+ background: "a7b854ee5bf",
12588
+ actions: { nextAfterMedia: true }
12589
+ },
12590
+ {
12591
+ id: "5a99fba3e52",
12592
+ background: "a99fba3e52d",
12593
+ nextTimer: 7
12594
+ },
12595
+ {
12596
+ id: "52d2ca79bcd",
12597
+ background: "2d2ca79bcd5",
12598
+ actions: { nextAfterMedia: true }
12599
+ },
12600
+ {
12601
+ id: "cd55e66060b",
12602
+ background: "d55e66060b8",
12603
+ nextTimer: 7,
12604
+ end: true
12605
+ }
12606
+ ]
12607
+ }
12608
+ },
12609
+ media: {
12610
+ a7b854ee5bf: {
12611
+ name: "Kids Theme Loop",
12612
+ path: "https://stream.mux.com/kDsWzEqyN7TDoMM3Aykfq1QhEo01JhCWO7t2sABictzk/capped-1080p.mp4",
12613
+ type: "video",
12614
+ contentProvider: "amazinglife",
12615
+ muted: false,
12616
+ loop: false
12617
+ },
12618
+ a99fba3e52d: {
12619
+ name: "Preteen Theme",
12620
+ path: "https://www.datocms-assets.com/75190/1746649082-2507_screen_preteen_theme.jpg",
12621
+ type: "image",
12622
+ contentProvider: "amazinglife"
12623
+ },
12624
+ "2d2ca79bcd5": {
12625
+ name: "Kids Theme Transition to Virtue",
12626
+ path: "https://stream.mux.com/101lB00Wj00AMUsjbuNYkNofkNHQXpZeThL3mFZNsSB5A00/capped-1080p.mp4",
12627
+ type: "video",
12628
+ contentProvider: "amazinglife",
12629
+ muted: false,
12630
+ loop: false
12631
+ },
12632
+ d55e66060b8: {
12633
+ name: "Theme",
12634
+ path: "https://www.datocms-assets.com/75190/1746648669-2507_screen_kids_theme.jpg",
12635
+ type: "image",
12636
+ contentProvider: "amazinglife"
12637
+ }
12638
+ }
12639
+ }
12640
+ ];
12641
+
12642
+ // src/providers/freeShow/FreeShowInterfaces.ts
12643
+ var DEFAULT_SLIDE_SECONDS = 5;
12644
+
12645
+ // src/providers/freeShow/FreeShowConverters.ts
12646
+ var MOCK_FREESHOW_INSTANCE = {
12647
+ id: "mock-local",
12648
+ name: "This PC",
12649
+ host: "localhost",
12650
+ ip: "127.0.0.1",
12651
+ port: 5505
12652
+ };
12653
+ function parseShowExport(raw) {
12654
+ if (Array.isArray(raw) && raw.length >= 2 && typeof raw[0] === "string" && raw[1] && typeof raw[1] === "object") {
12655
+ return { id: raw[0], show: raw[1] };
12656
+ }
12657
+ if (raw && typeof raw === "object" && "layouts" in raw && "media" in raw) {
12658
+ const show = raw;
12659
+ return { id: show.id || "show", show };
12660
+ }
12661
+ return null;
12662
+ }
12663
+ function slideSeconds(ref, mediaType) {
12664
+ if (typeof ref.nextTimer === "number" && ref.nextTimer > 0) return ref.nextTimer;
12665
+ if (ref.actions?.nextAfterMedia && mediaType === "video") return void 0;
12666
+ return DEFAULT_SLIDE_SECONDS;
12667
+ }
12668
+ function showToFiles(show) {
12669
+ const layoutId = show.settings?.activeLayout || Object.keys(show.layouts || {})[0];
12670
+ const layout = layoutId ? show.layouts?.[layoutId] : void 0;
12671
+ const refs = layout?.slides || [];
12672
+ const loops = refs.some((ref) => ref.end === true);
12673
+ const files = [];
12674
+ for (const ref of refs) {
12675
+ if (ref.disabled || !ref.background) continue;
12676
+ const media = show.media?.[ref.background];
12677
+ const path = media?.path;
12678
+ if (!path) continue;
12679
+ const mediaType = detectMediaType(path, media?.type);
12680
+ const slide = show.slides?.[ref.id];
12681
+ const title = media?.name || slide?.group || "Slide";
12682
+ files.push(createFile(ref.id, title, path, {
12683
+ mediaType,
12684
+ thumbnail: mediaType === "image" ? path : void 0,
12685
+ seconds: slideSeconds(ref, mediaType),
12686
+ loop: loops,
12687
+ loopVideo: false
12688
+ }));
12689
+ }
12690
+ return files;
12691
+ }
12692
+ function showToFolder(id, show) {
12693
+ const files = showToFiles(show);
12694
+ const thumbnail = files.find((file) => file.mediaType === "image")?.url || files[0]?.thumbnail;
12695
+ return createFolder(id, show.name || "Show", `/${id}`, thumbnail, true);
12696
+ }
12697
+ function networkInstanceToAuth(instance) {
12698
+ return {
12699
+ access_token: instance.id,
12700
+ refresh_token: JSON.stringify(instance),
12701
+ token_type: "network",
12702
+ created_at: Math.floor(Date.now() / 1e3),
12703
+ expires_in: 10 * 365 * 24 * 3600,
12704
+ scope: "freeshow"
12705
+ };
12706
+ }
12707
+ function authToNetworkInstance(auth) {
12708
+ if (!auth?.refresh_token) return null;
12709
+ try {
12710
+ const parsed = JSON.parse(auth.refresh_token);
12711
+ if (parsed?.id && parsed?.name) return parsed;
12712
+ } catch {
12713
+ return null;
12714
+ }
12715
+ return null;
12716
+ }
12717
+
12718
+ // src/providers/freeShow/FreeShowProvider.ts
12719
+ var parsedMock = parseShowExport(announcements_show_default);
12720
+ var MOCK_SHOW_ID = parsedMock?.id || "announcements";
12721
+ var MOCK_SHOW = parsedMock?.show || { name: "Announcements", layouts: {}, media: {}, slides: {} };
12722
+ var FreeShowProvider = class extends BaseProvider {
12723
+ constructor() {
12724
+ super(...arguments);
12725
+ this.id = "freeshow";
12726
+ this.name = "FreeShow";
12727
+ this.logos = {
12728
+ light: "https://freeshow.app/images/favicon.png",
12729
+ dark: "https://freeshow.app/images/favicon.png"
12730
+ };
12731
+ this.config = {
12732
+ id: "freeshow",
12733
+ name: "FreeShow",
12734
+ apiBase: "",
12735
+ oauthBase: "",
12736
+ clientId: "",
12737
+ scopes: []
12738
+ };
12739
+ this.requiresAuth = true;
12740
+ this.authTypes = ["network_discovery"];
12741
+ this.capabilities = {
12742
+ browse: true,
12743
+ playlist: true,
12744
+ instructions: true,
12745
+ mediaLicensing: false
12746
+ };
12747
+ this.discoverer = null;
12748
+ }
12749
+ setDiscoverer(discoverer) {
12750
+ this.discoverer = discoverer;
12751
+ }
12752
+ async discoverInstances() {
12753
+ if (this.discoverer) return this.discoverer();
12754
+ await new Promise((resolve) => setTimeout(resolve, 600));
12755
+ return [MOCK_FREESHOW_INSTANCE];
12756
+ }
12757
+ toAuth(instance) {
12758
+ return networkInstanceToAuth(instance);
12759
+ }
12760
+ selectedInstance(auth) {
12761
+ return authToNetworkInstance(auth);
12762
+ }
12763
+ async browse(path, _auth) {
12764
+ const { depth } = parsePath(path);
12765
+ if (depth === 0) return [showToFolder(MOCK_SHOW_ID, MOCK_SHOW)];
12766
+ return [];
12767
+ }
12768
+ async getPlaylist(path, _auth, _resolution) {
12769
+ const { segments, depth } = parsePath(path);
12770
+ if (depth < 1 || segments[0] !== MOCK_SHOW_ID) return null;
12771
+ const files = showToFiles(MOCK_SHOW);
12772
+ return files.length > 0 ? files : null;
12773
+ }
12774
+ async getInstructions(path, auth) {
12775
+ const files = await this.getPlaylist(path, auth);
12776
+ if (!files) return null;
12777
+ return filesToInstructions(MOCK_SHOW.name || "Show", files);
12778
+ }
12779
+ };
12780
+
12513
12781
  // src/providers/lifeChurch/data.json
12514
12782
  var data_default3 = {
12515
12783
  generatedAt: "2026-05-09T20:14:53.089Z",
@@ -17740,7 +18008,10 @@ var GoCurriculumProvider = class extends BaseProvider {
17740
18008
  if (!files) return null;
17741
18009
  const collection = (await this.getData()).catalog.find((c) => c.id === segments[0]);
17742
18010
  const lesson = collection?.lessons.find((l) => l.id === segments[1]);
17743
- return filesToInstructions(lesson?.name || "Lesson", files);
18011
+ const instructions = filesToInstructions(lesson?.name || "Lesson", files);
18012
+ 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 }));
18013
+ if (downloads.length > 0) instructions.downloads = downloads;
18014
+ return instructions;
17744
18015
  }
17745
18016
  generateCodeVerifier() {
17746
18017
  return this.oauthHelper.generateCodeVerifier();
@@ -17911,14 +18182,6 @@ var unimplementedProviders = [
17911
18182
  dark: "https://www.awana.org/wp-content/uploads/2025/04/awana-logo-white.svg"
17912
18183
  }
17913
18184
  },
17914
- {
17915
- id: "freeshow",
17916
- name: "FreeShow",
17917
- logos: {
17918
- light: "https://freeshow.app/images/favicon.png",
17919
- dark: "https://freeshow.app/images/favicon.png"
17920
- }
17921
- },
17922
18185
  {
17923
18186
  id: "iteachchurch",
17924
18187
  name: "iTeachChurch",
@@ -17950,7 +18213,8 @@ function initializeProviders() {
17950
18213
  new LifeChurchProvider(),
17951
18214
  new PlanningCenterProvider(),
17952
18215
  new SignPresenterProvider(),
17953
- new CbnProvider()
18216
+ new CbnProvider(),
18217
+ new FreeShowProvider()
17954
18218
  ];
17955
18219
  for (const provider of providers) registerProvider(provider);
17956
18220
  }
@@ -17987,7 +18251,7 @@ function getAvailableProviders(ids) {
17987
18251
  }
17988
18252
 
17989
18253
  // src/index.ts
17990
- var VERSION = true ? "0.9.4" : "dev";
18254
+ var VERSION = true ? "0.11.0" : "dev";
17991
18255
  export {
17992
18256
  APlayProvider,
17993
18257
  B1ChurchProvider,
@@ -17996,16 +18260,19 @@ export {
17996
18260
  CbnProvider,
17997
18261
  DeviceFlowHelper,
17998
18262
  DropboxProvider,
18263
+ FreeShowProvider,
17999
18264
  HighVoltageKidsProvider,
18000
18265
  JesusFilmProvider,
18001
18266
  LessonsChurchProvider,
18002
18267
  LifeChurchProvider,
18268
+ MOCK_FREESHOW_INSTANCE,
18003
18269
  OAuthHelper,
18004
18270
  PlanningCenterProvider,
18005
18271
  SignPresenterProvider,
18006
18272
  TokenHelper,
18007
18273
  VERSION,
18008
18274
  authIsExpired,
18275
+ authToNetworkInstance,
18009
18276
  getAllProviders,
18010
18277
  getAvailableProviders,
18011
18278
  getProvider,
@@ -18013,6 +18280,7 @@ export {
18013
18280
  isContentFile,
18014
18281
  isContentFolder,
18015
18282
  navigateToPath,
18283
+ networkInstanceToAuth,
18016
18284
  registerProvider,
18017
18285
  setProviderSecret,
18018
18286
  toAuthData