Skip to content

feat(strm): implement STRM output feature with user-agent handling#727

Open
docloulou wants to merge 1 commit intoViren070:mainfrom
docloulou:strm-support
Open

feat(strm): implement STRM output feature with user-agent handling#727
docloulou wants to merge 1 commit intoViren070:mainfrom
docloulou:strm-support

Conversation

@docloulou
Copy link

@docloulou docloulou commented Feb 13, 2026

STRM Output Support (Infuse / Third-Party Player Compatibility)

Summary

Adds a new "STRM Output" feature that allows AIOStreams to serve .strm files instead of direct stream links when accessed by specific media players (like Infuse). This enables seamless integration with players that use .strm files for library management and metadata matching.

How It Works

When enabled, all HTTP stream URLs are routed through a new STRM Gate endpoint (/api/v1/strm-gate/). This endpoint acts as a transparent intermediary:

  1. Stremio requests streams normally and receives the JSON response with gate URLs
  2. When a player opens a stream URL, the gate checks the player's User-Agent header
  3. If the User-Agent matches (e.g., contains "Infuse"): the gate serves a .strm text file containing the actual (potentially proxied) video URL, with a proper Content-Disposition filename
  4. If the User-Agent does not match: the gate performs a transparent 302 redirect to the actual stream URL — behavior is identical to before
Stremio → Gate URL → 302 redirect → Video URL (normal playback)
Infuse  → Gate URL → .strm file   → Video URL (Infuse reads it)

STRM Filename

The .strm filename follows media player conventions for metadata matching:

  • Movies: Title (Year).strm (e.g., Inception (2010).strm)
  • Series: Title (Year) S01E02.strm (e.g., Breaking Bad (2008) S01E01.strm)

The filename is derived from TMDB metadata (title, year) with proper Title Case formatting.

Configuration

A new "STRM Output" settings card is available in the Miscellaneous section (Pro mode):

Option Description
Disabled No STRM processing (default)
Always Always serve .strm files regardless of User-Agent
User-Agent Dependent Serve .strm only when the player's User-Agent matches configured strings

When "User-Agent Dependent" is selected, users can specify a comma-separated list of User-Agent substrings to match (case-insensitive). Default: Infuse.

image image image

Changes

New Files

  • packages/server/src/routes/api/strm.ts — STRM Gate endpoint that decrypts the payload, checks User-Agent, and either serves a .strm file or redirects

Modified Files

  • packages/core/src/db/schemas.ts — Added strmOutput config schema (mode, userAgents)
  • packages/core/src/streams/context.ts — Metadata fetch is triggered when STRM output is active (needed for filename generation)
  • packages/server/src/routes/stremio/stream.ts — Post-processing logic to wrap stream URLs through the STRM gate, with filename generation from metadata
  • packages/server/src/routes/api/index.ts — Export new STRM route
  • packages/server/src/app.ts — Register /strm-gate API route
  • packages/frontend/src/components/menu/miscellaneous.tsx — New "STRM Output" settings card with Mode select and User Agents input

Notes

  • Only streams with HTTP URLs are wrapped (P2P/torrent, YouTube, external URLs are not affected)
  • Proxied stream URLs are preserved — the .strm file contains the already-proxied URL
  • The encrypted gate payload uses the same encryptString/decryptString mechanism as the built-in proxy
  • No breaking changes — the feature is disabled by default

References

Summary by CodeRabbit

Release Notes

  • New Features
    • Added STRM Output settings for Pro mode users with configurable streaming modes
    • Implemented User-Agent dependent streaming to serve .strm files or redirect based on device type
    • Extended metadata fetching to support STRM output configurations
    • Added new API endpoint for STRM gate functionality

- Added `strmOutput` schema to user data for configuring STRM output mode and user agents.
- Updated `StreamContext` to check for STRM output requirements when fetching metadata.
- Introduced STRM gate endpoint to wrap stream URLs based on user-agent conditions.
- Enhanced frontend settings to allow users to configure STRM output options.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 13, 2026

Walkthrough

This pull request introduces STRM gate functionality to enable conditional streaming of content via .strm files. Changes span the schema layer (adding optional strmOutput configuration), frontend UI (STRM output settings), metadata fetching logic (conditional based on STRM mode), and a complete server-side implementation (new /strm-gate API endpoint with encryption/decryption and user-agent-based routing).

Changes

Cohort / File(s) Summary
Schema Definition
packages/core/src/db/schemas.ts
Added optional strmOutput field to UserDataSchema with nested mode enum ('disabled', 'always', 'userAgent') and optional userAgents string array.
Metadata Fetching Logic
packages/streams/context.ts
Extended metadata fetch condition to trigger when STRM output mode is defined and not 'disabled', in addition to existing bitrate and content-matching criteria.
Frontend Settings UI
packages/frontend/src/components/menu/miscellaneous.tsx
Added STRM Output SettingsCard with Mode Select dropdown and conditional User-Agent TextInput for 'userAgent' mode selection; includes two identical card instances within the same component.
Server API Routing
packages/server/src/app.ts, packages/server/src/routes/api/index.ts
Wired new STRM API route at /strm-gate and exported strmApi module for public consumption.
STRM Gate Endpoint
packages/server/src/routes/api/strm.ts
Implemented new Express route GET /:data/:filename that decrypts payload, validates required fields (url, mode), and conditionally serves .strm file or redirects based on mode and user-agent matching; includes comprehensive error handling and logging.
Stream Response Transformation
packages/server/src/routes/stremio/stream.ts
Added helper functions for filename generation (toTitleCase, generateStrmFilename) and stream wrapping (wrapStreamsWithStrmGate); modified Stremio stream endpoint to wrap HTTP URLs with encrypted STRM gate URLs when STRM output is enabled.

Sequence Diagram(s)

sequenceDiagram
    actor Client as Client<br/>(Infuse, etc.)
    participant Stremio as Stremio<br/>Stream Endpoint
    participant Encryption as Encryption<br/>Service
    participant Gate as STRM Gate<br/>API
    
    Note over Client,Gate: STRM Output Enabled: mode='userAgent'
    
    Stremio->>Encryption: Encrypt payload<br/>(url, mode, userAgents)
    Encryption-->>Stremio: Encrypted data
    Stremio-->>Client: Return .strm file URL<br/>/strm-gate/:data/:filename
    
    Client->>Gate: GET /strm-gate/:data/:filename<br/>with User-Agent header
    Gate->>Encryption: Decrypt data parameter
    Encryption-->>Gate: StrmGatePayload<br/>(url, mode, userAgents)
    
    alt User-Agent Match Found
        Gate-->>Client: 200 OK<br/>Content-Type: text/plain<br/>Body: stream URL
    else No Match
        Gate-->>Client: 302 Redirect<br/>Location: stream URL
    end
Loading
sequenceDiagram
    actor Client as Client
    participant Stremio as Stremio<br/>Stream Endpoint
    participant Metadata as Metadata<br/>Service
    participant Gate as STRM Gate<br/>Builder
    
    Note over Client,Gate: STRM Output Enabled: mode='always'
    
    Client->>Stremio: GET /:type/:id.json<br/>with userData.strmOutput
    Stremio->>Metadata: Fetch metadata<br/>(triggered by STRM mode)
    Metadata-->>Stremio: Metadata response
    Stremio->>Gate: Generate filename &<br/>wrap stream URLs
    Gate-->>Stremio: Modified streams<br/>with .strm gate URLs
    Stremio-->>Client: 200 OK<br/>Wrapped stream URLs
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly Related PRs

Suggested Reviewers

  • Viren070

Poem

🐰 The STRM gate hops to life so fine,
With encrypted paths and logic divine,
Mode by mode, agent by agent we roam,
Gateway gateways guide content home!
Rabbits approve of this streaming care! 🚪✨

🚥 Pre-merge checks | ✅ 3 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main objective of the pull request, which is implementing a STRM output feature with user-agent handling across the entire codebase.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@packages/server/src/routes/api/strm.ts`:
- Around line 55-65: The code uses the URL param-derived filename and emits it
directly in the Content-Disposition header (see filename and strmFilename
variables); you must sanitize/normalise the filename before use to
remove/control characters, quotes, slashes and limit length, and provide a safe
fallback (e.g. "stream.strm") if the result is empty. Update the logic that
builds strmFilename to: strip surrounding quotes, remove or replace control
characters and path separators, constrain to a reasonable max length, ensure it
ends with ".strm", and then use that sanitized value in the Content-Disposition
header.

In `@packages/server/src/routes/stremio/stream.ts`:
- Around line 69-96: The current wrapStreamsWithStrmGate transforms any stream
with a truthy stream.url, but you should only wrap HTTP(S) URLs to avoid
breaking other schemes; update the guard in wrapStreamsWithStrmGate to
explicitly allow only URLs whose scheme is "http:" or "https:" (e.g., parse
stream.url or test its prefix) and skip wrapping for other schemes so non-HTTP
streams return unchanged while keeping the rest of the encryption/URL-rewrite
logic intact.

Comment on lines +55 to +65
const strmFilename = filename.endsWith('.strm')
? filename
: `${filename}.strm`;

logger.info(`Serving STRM file: ${strmFilename}`);

res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader(
'Content-Disposition',
`attachment; filename="${strmFilename}"`
);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sanitise STRM filenames before emitting Content-Disposition.

The filename is taken from a URL param; quotes or control characters can break the header or yield invalid filenames. Normalise it before use and provide a safe fallback.

🔧 Suggested fix
-      const strmFilename = filename.endsWith('.strm')
-        ? filename
-        : `${filename}.strm`;
+      const rawFilename = filename.endsWith('.strm')
+        ? filename
+        : `${filename}.strm`;
+      const strmFilename =
+        rawFilename
+          .replace(/[\r\n"]/g, '')
+          .replace(/[\\/]/g, '_')
+          .trim() || 'stream.strm';
🤖 Prompt for AI Agents
In `@packages/server/src/routes/api/strm.ts` around lines 55 - 65, The code uses
the URL param-derived filename and emits it directly in the Content-Disposition
header (see filename and strmFilename variables); you must sanitize/normalise
the filename before use to remove/control characters, quotes, slashes and limit
length, and provide a safe fallback (e.g. "stream.strm") if the result is empty.
Update the logic that builds strmFilename to: strip surrounding quotes, remove
or replace control characters and path separators, constrain to a reasonable max
length, ensure it ends with ".strm", and then use that sanitized value in the
Content-Disposition header.

Comment on lines +69 to +96
function wrapStreamsWithStrmGate(
result: AIOStreamResponse,
strmFilename: string,
strmMode: 'always' | 'userAgent',
userAgents: string[]
): AIOStreamResponse {
const wrappedStreams = result.streams.map((stream) => {
// Only wrap streams that have an HTTP url
if (!stream.url) {
return stream;
}

const payload = JSON.stringify({
url: stream.url,
mode: strmMode,
userAgents,
});

const encrypted = encryptString(payload);
if (!encrypted.success || !encrypted.data) {
logger.warn('Failed to encrypt STRM gate payload, keeping original URL');
return stream;
}

return {
...stream,
url: `${Env.BASE_URL}/api/v${constants.API_VERSION}/strm-gate/${encrypted.data}/${encodeURIComponent(strmFilename)}`,
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Only wrap HTTP(S) stream URLs before STRM gating.

The guard only checks for a URL’s presence, so non-HTTP schemes could be wrapped and break playback (contrary to the stated behaviour). Consider restricting to HTTP(S) explicitly.

💡 Suggested fix
-    if (!stream.url) {
+    if (!stream.url || !/^https?:\/\//i.test(stream.url)) {
       return stream;
     }
🤖 Prompt for AI Agents
In `@packages/server/src/routes/stremio/stream.ts` around lines 69 - 96, The
current wrapStreamsWithStrmGate transforms any stream with a truthy stream.url,
but you should only wrap HTTP(S) URLs to avoid breaking other schemes; update
the guard in wrapStreamsWithStrmGate to explicitly allow only URLs whose scheme
is "http:" or "https:" (e.g., parse stream.url or test its prefix) and skip
wrapping for other schemes so non-HTTP streams return unchanged while keeping
the rest of the encryption/URL-rewrite logic intact.

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