feat(strm): implement STRM output feature with user-agent handling#727
feat(strm): implement STRM output feature with user-agent handling#727docloulou wants to merge 1 commit intoViren070:mainfrom
Conversation
- 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.
WalkthroughThis pull request introduces STRM gate functionality to enable conditional streaming of content via Changes
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
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
Estimated Code Review Effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly Related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Comment |
There was a problem hiding this comment.
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.
| 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}"` | ||
| ); |
There was a problem hiding this comment.
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.
| 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)}`, | ||
| }; |
There was a problem hiding this comment.
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.
STRM Output Support (Infuse / Third-Party Player Compatibility)
Summary
Adds a new "STRM Output" feature that allows AIOStreams to serve
.strmfiles instead of direct stream links when accessed by specific media players (like Infuse). This enables seamless integration with players that use.strmfiles 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:User-Agentheader.strmtext file containing the actual (potentially proxied) video URL, with a properContent-Dispositionfilename302 redirectto the actual stream URL — behavior is identical to beforeSTRM Filename
The
.strmfilename follows media player conventions for metadata matching:Title (Year).strm(e.g.,Inception (2010).strm)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):
.strmfiles regardless of User-Agent.strmonly when the player's User-Agent matches configured stringsWhen "User-Agent Dependent" is selected, users can specify a comma-separated list of User-Agent substrings to match (case-insensitive). Default:
Infuse.Changes
New Files
packages/server/src/routes/api/strm.ts— STRM Gate endpoint that decrypts the payload, checks User-Agent, and either serves a.strmfile or redirectsModified Files
packages/core/src/db/schemas.ts— AddedstrmOutputconfig 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 metadatapackages/server/src/routes/api/index.ts— Export new STRM routepackages/server/src/app.ts— Register/strm-gateAPI routepackages/frontend/src/components/menu/miscellaneous.tsx— New "STRM Output" settings card with Mode select and User Agents inputNotes
.strmfile contains the already-proxied URLencryptString/decryptStringmechanism as the built-in proxyReferences
Summary by CodeRabbit
Release Notes