Skip to content

Update Flix-Streams preset provider configuration#902

Open
Thsandorh wants to merge 1 commit into
Viren070:mainfrom
Thsandorh:flix-streams-preset-provider-config
Open

Update Flix-Streams preset provider configuration#902
Thsandorh wants to merge 1 commit into
Viren070:mainfrom
Thsandorh:flix-streams-preset-provider-config

Conversation

@Thsandorh

@Thsandorh Thsandorh commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Refreshes the Flix-Streams marketplace preset to match the current Flix configuration flow.

Changes include:

replaces individual provider toggles with multi-select provider selection removes outdated Signal Vault server selection and uses the current unified Signal Vault config updates movie/series/anime and live TV provider options maps selected providers back to Flix-compatible enable_* config flags adds dynamic provider metadata loading from the Flix configure page with a static fallback keeps compatibility with older saved toggle-based configs Tested with:

pnpm -F core -F server run build

Summary by CodeRabbit

Release Notes

New Features

  • Enhanced provider configuration with multi-select controls for content providers and live TV sources.
  • Added new customisation options for provider quality thresholds, language preferences, and authentication settings.
  • Implemented dynamic configuration support to retrieve provider settings from backend services.

Refreshes the Flix-Streams marketplace preset to match the current Flix configuration flow.

Changes include:

replaces individual provider toggles with multi-select provider selection
removes outdated Signal Vault server selection and uses the current unified Signal Vault config
updates movie/series/anime and live TV provider options
maps selected providers back to Flix-compatible enable_* config flags
adds dynamic provider metadata loading from the Flix configure page with a static fallback
keeps compatibility with older saved toggle-based configs
Tested with:

pnpm -F core -F server run build
@coderabbitai

coderabbitai Bot commented Apr 26, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

The PR introduces dynamic metadata support to the preset system, allowing presets to asynchronously fetch runtime-derived configuration from a backend service. The FlixStreams preset is significantly expanded with a larger schema, updated UI model for provider management, and new provider configuration logic whilst maintaining backward compatibility with legacy flags.

Changes

Cohort / File(s) Summary
FlixStreams Preset Expansion
packages/core/src/presets/flixStreams.ts
Expands marketplaceDefaults to include provider ordering, timeouts, quality settings, and cookie fields. Replaces static provider UI configuration with dynamic multi-select "Enabled providers"/"Enabled live TV sources". Adds getDynamicMetadata() method to fetch and cache provider metadata from backend, parsing HTML labels and default provider ordering. Refactors buildConfig() to normalise legacy provider flags, resolve selected provider arrays with backward compatibility, reset all provider flags, and enforce new top-level config fields.
PresetManager Dynamic Support
packages/core/src/presets/presetManager.ts
Introduces getPresetListDynamic() async method to retrieve PresetMinimalMetadata[] by attempting to call optional getDynamicMetadata() on each preset, falling back to static METADATA if unavailable or on error. Refactors getPresetList() to delegate minimal metadata conversion via new toMinimalMetadata() helper method.
API Status Endpoint
packages/server/src/routes/api/status.ts
Switches statusInfo to use awaited PresetManager.getPresetListDynamic() instead of synchronous getPresetList() for populating settings.presets.

Sequence Diagram

sequenceDiagram
    participant Client
    participant PresetManager
    participant FlixStreams
    participant Backend as Flix Backend
    participant Cache

    Client->>PresetManager: getPresetListDynamic()
    activate PresetManager
    PresetManager->>FlixStreams: getDynamicMetadata()
    activate FlixStreams
    FlixStreams->>Cache: Check cached metadata
    alt Cache Hit
        Cache-->>FlixStreams: Return cached metadata
    else Cache Miss
        FlixStreams->>Backend: Fetch /configure HTML
        Backend-->>FlixStreams: HTML with provider defaults
        FlixStreams->>FlixStreams: Parse provider order & labels
        FlixStreams->>Cache: Store metadata
        Cache-->>FlixStreams: Cached
    end
    FlixStreams-->>PresetManager: PresetMetadata with dynamic options
    deactivate FlixStreams
    PresetManager->>PresetManager: Convert to MinimalMetadata
    PresetManager-->>Client: PresetMinimalMetadata[]
    deactivate PresetManager
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • Viren070

🐰 Dynamic metadata flows, providers in a row,
Legacy flags fade as new configs glow,
Flix whispers its secrets through cache divine,
Presets transform, one schema at a time!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: updating the Flix-Streams preset's provider configuration system, which is the primary focus across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/core/src/presets/presetManager.ts (1)

171-186: Type the optional getDynamicMetadata hook instead of as any.

Casting to any to call getDynamicMetadata works but leaks type-checking. Declaring it as an optional static method on the Preset base (or a side interface) would let TS verify the signature/return type while still allowing presets to opt in.

♻️ Sketch
-      PRESET_LIST.map(async (presetId) => {
-        const preset = this.fromId(presetId);
-        let metadata = preset.METADATA;
-        if (typeof (preset as any).getDynamicMetadata === 'function') {
-          try {
-            metadata = await (preset as any).getDynamicMetadata();
-          } catch {
-            metadata = preset.METADATA;
-          }
-        }
-        return this.toMinimalMetadata(metadata);
+      PRESET_LIST.map(async (presetId) => {
+        const preset = this.fromId(presetId) as typeof Preset & {
+          getDynamicMetadata?: () => Promise<PresetMetadata>;
+        };
+        let metadata = preset.METADATA;
+        if (typeof preset.getDynamicMetadata === 'function') {
+          try {
+            metadata = await preset.getDynamicMetadata();
+          } catch {
+            metadata = preset.METADATA;
+          }
+        }
+        return this.toMinimalMetadata(metadata);

A cleaner long-term option is to add static getDynamicMetadata?(): Promise<PresetMetadata> to the Preset base class so all presets share a typed contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/presets/presetManager.ts` around lines 171 - 186, The code
in getPresetListDynamic casts presets to any to call getDynamicMetadata, losing
type safety; add an optional static method signature on the Preset base (e.g.,
declare static getDynamicMetadata?: () => Promise<PresetMetadata> or add it to a
side interface implemented by concrete preset classes) so TypeScript can
type-check the hook, then replace the any cast with a typed access (e.g., treat
preset as typeof Preset or the new interface) and call getDynamicMetadata with
proper typing; keep using this.fromId, PRESET_LIST, this.toMinimalMetadata and
fallback to preset.METADATA on error as before.
packages/core/src/presets/flixStreams.ts (1)

779-783: Avoid shadowing the file-scope providerIds / liveTvProviderIds.

These locals shadow the module-level constants of the same name (lines 395-396) used elsewhere in buildConfig. Consider renaming, e.g. remoteProviderIds / remoteLiveTvProviderIds, to keep future edits unambiguous.

♻️ Suggested rename
-    const providerIds = providerOrder.slice(0, firstLiveIndex);
-    const liveTvProviderIds = providerOrder.slice(firstLiveIndex);
+    const remoteProviderIds = providerOrder.slice(0, firstLiveIndex);
+    const remoteLiveTvProviderIds = providerOrder.slice(firstLiveIndex);
@@
-    return {
-      providerOptions: providerIds.map(toOption),
-      liveTvProviderOptions: liveTvProviderIds.map(toOption),
-      defaultProviderSelection: providerIds.filter(
-        (id) => defaultConfig[id] === true
-      ),
-      defaultLiveTvProviderSelection: liveTvProviderIds.filter(
-        (id) => defaultConfig[id] === true
-      ),
-    };
+    return {
+      providerOptions: remoteProviderIds.map(toOption),
+      liveTvProviderOptions: remoteLiveTvProviderIds.map(toOption),
+      defaultProviderSelection: remoteProviderIds.filter(
+        (id) => defaultConfig[id] === true
+      ),
+      defaultLiveTvProviderSelection: remoteLiveTvProviderIds.filter(
+        (id) => defaultConfig[id] === true
+      ),
+    };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/presets/flixStreams.ts` around lines 779 - 783, The local
variables providerIds and liveTvProviderIds in the block computing
liveStartIndex/firstLiveIndex are shadowing file-scoped constants used elsewhere
in buildConfig; rename the locals (for example to remoteProviderIds and
remoteLiveTvProviderIds) and update all references in that scope (the slice
results produced from providerOrder using liveStartIndex/firstLiveIndex) so the
module-level providerIds/liveTvProviderIds remain unshadowed and unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/core/src/presets/flixStreams.ts`:
- Around line 779-783: The current logic uses liveStartIndex >= 0 ?
liveStartIndex : providerOrder.length which silently treats all providers as
non-live when the 'enable_box13_emby_live_tv' divider is missing; update the
code around providerOrder, liveStartIndex, firstLiveIndex, providerIds and
liveTvProviderIds to detect the missing divider and instead fall back to the
static METADATA (or its provider list) for providerIds and set liveTvProviderIds
to an explicit empty list (or another safe static value); ensure you reference
providerOrder and 'enable_box13_emby_live_tv' to decide the branch so the UI
shows the static metadata rather than an empty dynamic live list.
- Around line 725-753: getRemoteProviderMetadata currently blocks callers on a
new fetch (using AbortSignal.timeout(3000)) each cache miss and has no in-flight
dedup, causing /api/status latency; change it so the method returns an existing
cached.value immediately if present even when expired (stale-while-revalidate)
and only blocks when there is no cache at all, and add an in-flight promise
dedup (e.g., store a Promise in a new static remoteProviderMetadataPromise
field) so concurrent callers share the same fetch: when cache is absent await
the fetch, set remoteProviderMetadataCache and clear the promise on completion;
when cache exists but expired, return cache.value immediately and if no
in-flight promise start the fetch in background to refresh
remoteProviderMetadataCache (or set short expiry/null on error), referencing
getRemoteProviderMetadata, remoteProviderMetadataCache,
remoteProviderMetadataPromise and METADATA.URL to locate where to implement
these changes.

---

Nitpick comments:
In `@packages/core/src/presets/flixStreams.ts`:
- Around line 779-783: The local variables providerIds and liveTvProviderIds in
the block computing liveStartIndex/firstLiveIndex are shadowing file-scoped
constants used elsewhere in buildConfig; rename the locals (for example to
remoteProviderIds and remoteLiveTvProviderIds) and update all references in that
scope (the slice results produced from providerOrder using
liveStartIndex/firstLiveIndex) so the module-level providerIds/liveTvProviderIds
remain unshadowed and unambiguous.

In `@packages/core/src/presets/presetManager.ts`:
- Around line 171-186: The code in getPresetListDynamic casts presets to any to
call getDynamicMetadata, losing type safety; add an optional static method
signature on the Preset base (e.g., declare static getDynamicMetadata?: () =>
Promise<PresetMetadata> or add it to a side interface implemented by concrete
preset classes) so TypeScript can type-check the hook, then replace the any cast
with a typed access (e.g., treat preset as typeof Preset or the new interface)
and call getDynamicMetadata with proper typing; keep using this.fromId,
PRESET_LIST, this.toMinimalMetadata and fallback to preset.METADATA on error as
before.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: b4368791-9fe4-4514-824a-e07a771b26c3

📥 Commits

Reviewing files that changed from the base of the PR and between 9f3cda5 and 41a58bb.

📒 Files selected for processing (3)
  • packages/core/src/presets/flixStreams.ts
  • packages/core/src/presets/presetManager.ts
  • packages/server/src/routes/api/status.ts

Comment on lines +725 to +753
private static async getRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> {
const cached = this.remoteProviderMetadataCache;
if (cached && cached.expiresAt > Date.now()) {
return cached.value;
}

try {
const baseUrl = String(this.METADATA.URL).replace(/\/$/, '');
const response = await fetch(`${baseUrl}/configure`, {
signal: AbortSignal.timeout(3000),
});
if (!response.ok) {
throw new Error(`Flix configure fetch failed: ${response.status}`);
}
const html = await response.text();
const metadata = this.parseRemoteProviderMetadata(html);
this.remoteProviderMetadataCache = {
expiresAt: Date.now() + 10 * 60 * 1000,
value: metadata,
};
return metadata;
} catch {
this.remoteProviderMetadataCache = {
expiresAt: Date.now() + 60 * 1000,
value: null,
};
return null;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

/api/status now blocks on a remote /configure fetch every cache cycle.

Because getDynamicMetadata is awaited inside PresetManager.getPresetListDynamic() (called from the status route), any cold or expired cache cycle here adds up to 3 s of latency to /api/status. Concurrent status requests during a cache miss will each trigger a separate fetch (no in-flight dedup), which can amplify load on the Flix backend during transient outages.

Consider one of:

  • Stale-while-revalidate: keep returning the previous (possibly stale) value immediately and revalidate in the background once expired. Only block when there is no cache entry at all.
  • Promise dedup: cache the in-flight Promise<FlixProviderMetadata | null> so concurrent callers share a single fetch.
  • Background warmer: kick off the refresh on a timer/startup hook and let /api/status always read from the in-memory snapshot.
♻️ Stale-while-revalidate + in-flight dedup sketch
   private static remoteProviderMetadataCache:
     | {
         expiresAt: number;
         value: FlixProviderMetadata | null;
       }
     | undefined;
+  private static remoteProviderMetadataInflight:
+    | Promise<FlixProviderMetadata | null>
+    | undefined;
@@
   private static async getRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> {
     const cached = this.remoteProviderMetadataCache;
-    if (cached && cached.expiresAt > Date.now()) {
+    const fresh = cached && cached.expiresAt > Date.now();
+    if (fresh) {
       return cached.value;
     }
+    // Serve stale immediately; revalidate in the background.
+    if (cached && !this.remoteProviderMetadataInflight) {
+      this.remoteProviderMetadataInflight = this.fetchRemoteProviderMetadata()
+        .finally(() => {
+          this.remoteProviderMetadataInflight = undefined;
+        });
+      return cached.value;
+    }
+    // No cache yet — share the in-flight promise across concurrent callers.
+    this.remoteProviderMetadataInflight ??= this.fetchRemoteProviderMetadata()
+      .finally(() => {
+        this.remoteProviderMetadataInflight = undefined;
+      });
+    return this.remoteProviderMetadataInflight;
+  }
 
+  private static async fetchRemoteProviderMetadata(): Promise<FlixProviderMetadata | null> {
     try {
       const baseUrl = String(this.METADATA.URL).replace(/\/$/, '');
       /* …unchanged fetch + parse + cache write… */
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/presets/flixStreams.ts` around lines 725 - 753,
getRemoteProviderMetadata currently blocks callers on a new fetch (using
AbortSignal.timeout(3000)) each cache miss and has no in-flight dedup, causing
/api/status latency; change it so the method returns an existing cached.value
immediately if present even when expired (stale-while-revalidate) and only
blocks when there is no cache at all, and add an in-flight promise dedup (e.g.,
store a Promise in a new static remoteProviderMetadataPromise field) so
concurrent callers share the same fetch: when cache is absent await the fetch,
set remoteProviderMetadataCache and clear the promise on completion; when cache
exists but expired, return cache.value immediately and if no in-flight promise
start the fetch in background to refresh remoteProviderMetadataCache (or set
short expiry/null on error), referencing getRemoteProviderMetadata,
remoteProviderMetadataCache, remoteProviderMetadataPromise and METADATA.URL to
locate where to implement these changes.

Comment on lines +779 to +783
const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv');
const firstLiveIndex =
liveStartIndex >= 0 ? liveStartIndex : providerOrder.length;
const providerIds = providerOrder.slice(0, firstLiveIndex);
const liveTvProviderIds = providerOrder.slice(firstLiveIndex);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fall back to static metadata when the live-TV divider isn't found.

liveStartIndex >= 0 ? liveStartIndex : providerOrder.length silently classifies every provider as a non-live one when enable_box13_emby_live_tv is missing from upstream provider_order. That ships a UI with an empty "Enabled live TV sources" list while still claiming to be dynamic — worse than just falling back to the static METADATA.

🛡️ Defensive fallback
-    const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv');
-    const firstLiveIndex =
-      liveStartIndex >= 0 ? liveStartIndex : providerOrder.length;
-    const providerIds = providerOrder.slice(0, firstLiveIndex);
-    const liveTvProviderIds = providerOrder.slice(firstLiveIndex);
+    const liveStartIndex = providerOrder.indexOf('enable_box13_emby_live_tv');
+    if (liveStartIndex < 0) {
+      // Upstream changed the live-TV divider; fall back to static metadata.
+      return null;
+    }
+    const providerIds = providerOrder.slice(0, liveStartIndex);
+    const liveTvProviderIds = providerOrder.slice(liveStartIndex);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/core/src/presets/flixStreams.ts` around lines 779 - 783, The current
logic uses liveStartIndex >= 0 ? liveStartIndex : providerOrder.length which
silently treats all providers as non-live when the 'enable_box13_emby_live_tv'
divider is missing; update the code around providerOrder, liveStartIndex,
firstLiveIndex, providerIds and liveTvProviderIds to detect the missing divider
and instead fall back to the static METADATA (or its provider list) for
providerIds and set liveTvProviderIds to an explicit empty list (or another safe
static value); ensure you reference providerOrder and
'enable_box13_emby_live_tv' to decide the branch so the UI shows the static
metadata rather than an empty dynamic live list.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant