Skip to content

Commit 18f2153

Browse files
committed
feat: enable native integration of third-party Stremio addons
1 parent 42d1dc9 commit 18f2153

8 files changed

Lines changed: 315 additions & 38 deletions

File tree

examples/basic-server.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,17 @@ async function main() {
2626
},
2727
},
2828

29-
stremioAddon: true, // Enable Stremio addon endpoints
29+
stremio: {
30+
enableNativeAddon: true, // Whether to enable the native Stremio addon. can be used for your stremio app
31+
stremioAddons: [ // you can bind additional addons that will be checked during source discovery. has to end with /manifest.json and follow the stremio addon manifest schema
32+
/*
33+
{
34+
id: '', // some id for your reference
35+
url: '', // the url with /manifest.json at the end, for example: https://example.com/addon/manifest.json
36+
}
37+
*/
38+
]
39+
},
3040

3141
// TMDB (required)
3242
tmdb: {

src/core/server.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { createCacheService, CacheService } from './cache.js'
66
import { SourceService } from '../services/source.service.js'
77
import { ProxyService } from '../services/proxy.service.js'
88
import { HealthService } from '../services/health.service.js'
9+
import { StremioService } from 'src/services/stremio.service.js'
910
import { ContentController } from '../controllers/content.controller.js'
1011
import { ProxyController } from '../controllers/proxy.controller.js'
1112
import { HealthController } from '../controllers/health.controller.js'
@@ -27,6 +28,7 @@ export class OMSSServer {
2728
private proxyService: ProxyService
2829
private healthService: HealthService
2930
private tmdbService: TMDBService
31+
private stremioService: StremioService
3032

3133
// Controllers
3234
private contentController: ContentController
@@ -84,21 +86,18 @@ export class OMSSServer {
8486
this.tmdbService = new TMDBService(tmdbApiKey, this.cache, config.tmdb?.cacheTTL || 86400)
8587

8688
// Initialize services
87-
this.sourceService = new SourceService(this.registry, this.cache, this.tmdbService, config.cache?.ttl)
8889
this.proxyService = new ProxyService(config.proxyConfig?.streamPatterns || [])
90+
this.stremioService = new StremioService(config.stremio?.stremioAddons || [], this.proxyService)
91+
this.sourceService = new SourceService(this.registry, this.cache, this.tmdbService, this.stremioService, config.cache?.ttl)
8992
this.healthService = new HealthService(config, this.registry)
9093

9194
// Initialize controllers
9295
this.contentController = new ContentController(this.sourceService)
9396
this.proxyController = new ProxyController(this.proxyService)
9497
this.healthController = new HealthController(this.healthService)
9598

96-
if (config.stremioAddon) {
97-
this.stremioController = new StremioController(
98-
this.sourceService,
99-
config,
100-
this.tmdbService
101-
)
99+
if (config.stremio?.enableNativeAddon) {
100+
this.stremioController = new StremioController(this.sourceService, config, this.tmdbService)
102101
}
103102

104103
// Setup middleware and routes
@@ -189,6 +188,11 @@ export class OMSSServer {
189188

190189
await this.app.listen({ port, host })
191190

191+
const addons = this.config.stremio?.stremioAddons || []
192+
const enabledAddons = addons.filter((a) => a.enabled !== false).length
193+
194+
const stremioStatus = this.config.stremio?.enableNativeAddon ? `Enabled` : 'Disabled'
195+
192196
console.log(`
193197
╔════════════════════════════════════════════════════════╗
194198
║ OMSS Backend Server ║
@@ -198,7 +202,8 @@ export class OMSSServer {
198202
║ Port: ${port.toString().padEnd(42)}
199203
║ Providers: ${this.registry ? this.registry['providers'].size.toString().padEnd(42) : '0'.padEnd(42)}
200204
║ Cache: ${(this.config.cache?.type || 'memory').padEnd(42)}
201-
║ Stremio: ${(this.config.stremioAddon ? 'Enabled' : 'Disabled').padEnd(42)}
205+
║ Stremio: ${stremioStatus.padEnd(42)}
206+
║ Addons: ${`${enabledAddons} enabled`.padEnd(42)}
202207
╠════════════════════════════════════════════════════════╣
203208
║ Endpoints: ║
204209
║ GET / - Health check ║
@@ -207,10 +212,12 @@ export class OMSSServer {
207212
║ - TV sources ║
208213
║ GET /v1/proxy?data=... - Proxy endpoint ║
209214
║ GET /v1/refresh/:responseId - Refresh cache ║`)
210-
if (this.config.stremioAddon) {
215+
216+
if (this.config.stremio?.enableNativeAddon) {
211217
console.log(`║ ║
212218
║ GET /stremio/manifest.json - Stremio manifest ║`)
213219
}
220+
214221
console.log(`╚════════════════════════════════════════════════════════╝
215222
216223
🚀 Server listening at http://${host}:${port}

src/core/types.ts

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ export interface OMSSConfig {
99
host?: string
1010
publicUrl?: string // Full public URL (e.g., https://api.example.com)
1111
cache?: CacheConfig
12-
providers?: ProviderConfig[]
1312
tmdb?: {
1413
apiKey: string
1514
cacheTTL?: number
@@ -20,7 +19,7 @@ export interface OMSSConfig {
2019
}
2120
note?: string
2221
cors?: FastifyCorsOptions
23-
stremioAddon?: boolean
22+
stremio?: StremioConfig
2423
}
2524

2625
export interface CacheConfig {
@@ -36,11 +35,17 @@ export interface CacheConfig {
3635
}
3736
}
3837

39-
export interface ProviderConfig {
40-
id: string
41-
enabled: boolean
42-
priority?: number
43-
config?: Record<string, any>
38+
39+
export interface StremioConfig {
40+
enableNativeAddon: boolean
41+
stremioAddons: StremioAddonConfig[]
42+
}
43+
44+
export interface StremioAddonConfig {
45+
id: string;
46+
url: string;
47+
enabled?: boolean;
48+
timeoutMs?: number;
4449
}
4550

4651
// OMSS Response Types
@@ -165,7 +170,7 @@ export interface ProviderMediaObject {
165170
tmdbId: string
166171
s?: number
167172
e?: number
168-
releaseYear?: string
169-
imdbId?: string
170-
title?: string
173+
releaseYear: string
174+
imdbId: string
175+
title: string
171176
}

src/providers/base-provider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ export abstract class BaseProvider {
169169
/**
170170
* Get proxy base URL
171171
*/
172-
private static getProxyBaseUrl(): string {
172+
public static getProxyBaseUrl(): string {
173173
const config = BaseProvider.proxyConfig
174174

175175
// If baseUrl is explicitly set, use it
@@ -212,7 +212,7 @@ export abstract class BaseProvider {
212212
/**
213213
* Helper: Create proxy URL with full server address
214214
*/
215-
protected createProxyUrl(url: string, headers?: Record<string, string>): string {
215+
public createProxyUrl(url: string, headers?: Record<string, string>): string {
216216
const cleanUrl = this.cleanThirdPartyProxy(url)
217217
const data = JSON.stringify({ url: cleanUrl, headers })
218218
const encodedData = encodeURIComponent(data)

src/services/proxy.service.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,12 @@ export class ProxyService {
104104
validateStatus: (status) => status < 500,
105105
})
106106

107-
const contentType = response.headers['content-type'] || this.getMimeType(proxyData.url)
107+
const contentType = response.headers['content-type'] as string || this.getMimeType(proxyData.url)
108108

109109
// Build headers object
110110
const headers: Record<string, string> = {
111111
'Content-Disposition': 'inline; filename="stream"',
112+
// @ts-ignore
112113
'Cache-Control': response.headers['cache-control'] || 'public, max-age=7200',
113114
'Access-Control-Expose-Headers': 'Content-Disposition, Content-Length, Content-Range, Last-Modified, ETag',
114115
...(response.headers['accept-ranges'] || response.headers['accept-range']
@@ -120,6 +121,7 @@ export class ProxyService {
120121

121122
// Add optional headers if present
122123
if (response.headers['content-length']) {
124+
// @ts-ignore
123125
headers['Content-Length'] = response.headers['content-length']
124126
}
125127
if (response.headers['content-range']) {
@@ -156,7 +158,7 @@ export class ProxyService {
156158
})
157159

158160
// Check if we need to rewrite manifest files
159-
const contentType = response.headers['content-type'] || ''
161+
const contentType = response.headers['content-type'] as string || ''
160162
let responseData = response.data
161163

162164
if (this.isManifestFile(contentType, proxyData.url)) {
@@ -371,7 +373,7 @@ export class ProxyService {
371373
* Create a proxy URL for a given upstream URL
372374
* ALWAYS includes headers from the original request
373375
*/
374-
private createProxyUrl(url: string, headers?: Record<string, string>): string {
376+
public createProxyUrl(url: string, headers?: Record<string, string>): string {
375377
const data = JSON.stringify({ url, headers })
376378
return `/v1/proxy?data=${encodeURIComponent(data)}`
377379
}

src/services/source.service.ts

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { createTMDBValidator } from '../middleware/validation.js'
55
import { OMSSErrors } from '../core/errors.js'
66
import { TMDBService } from '../services/tmdb.service.js'
77
import { v4 as uuidv4 } from 'uuid'
8+
import { StremioService } from './stremio.service.js'
89

910
export class SourceService {
1011
private tmdbValidator: ReturnType<typeof createTMDBValidator>
@@ -14,6 +15,7 @@ export class SourceService {
1415
private registry: ProviderRegistry,
1516
private cache: CacheService,
1617
private tmdbService: TMDBService,
18+
private stremioService: StremioService,
1719
private cacheTTL = { sources: 7200, subtitles: 86400 }
1820
) {
1921
setInterval(() => this.cleanupExpiredMappings(), 60 * 60 * 1000)
@@ -41,11 +43,29 @@ export class SourceService {
4143
const media = await this.tmdbService.getMediaObject('movie', tmdbId)
4244

4345
// Try to get IMDB ID
44-
media.imdbId = await this.tmdbService.getImdbId(tmdbId, 'movie')
46+
media.imdbId = await this.tmdbService.getImdbId(tmdbId, 'movie') ?? ''
4547

46-
// Fetch from providers
47-
const results = await this.fetchFromProviders('movie', media)
48-
const response = this.buildResponse(results)
48+
// Fetch from providers and Stremio addons concurrently
49+
const [providerResults, stremioResult] = await Promise.all([
50+
this.fetchFromProviders('movie', media),
51+
this.stremioService?.hasEnabledAddons()
52+
? this.stremioService.getMovieSources(media).catch((err): ProviderResult => ({
53+
sources: [],
54+
subtitles: [],
55+
diagnostics: [{
56+
code: 'PROVIDER_ERROR',
57+
message: `Stremio integration failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
58+
field: '',
59+
severity: 'error',
60+
}],
61+
}))
62+
: Promise.resolve(null),
63+
])
64+
65+
const allResults: ProviderResult[] = [...providerResults]
66+
if (stremioResult) allResults.push(stremioResult)
67+
68+
const response = this.buildResponse(allResults)
4969

5070
// Throw error if no sources found
5171
if (response.sources.length === 0) {
@@ -87,11 +107,29 @@ export class SourceService {
87107
const media = await this.tmdbService.getMediaObject('tv', tmdbId, season, episode)
88108

89109
// Try to get IMDB ID
90-
media.imdbId = await this.tmdbService.getImdbId(tmdbId, 'tv')
110+
media.imdbId = await this.tmdbService.getImdbId(tmdbId, 'tv') ?? ''
91111

92-
// Fetch from providers
93-
const results = await this.fetchFromProviders('tv', media)
94-
const response = this.buildResponse(results)
112+
// Fetch from providers and Stremio addons concurrently
113+
const [providerResults, stremioResult] = await Promise.all([
114+
this.fetchFromProviders('tv', media),
115+
this.stremioService?.hasEnabledAddons()
116+
? this.stremioService.getTVSources(media).catch((err): ProviderResult => ({
117+
sources: [],
118+
subtitles: [],
119+
diagnostics: [{
120+
code: 'PROVIDER_ERROR',
121+
message: `Stremio integration failed: ${err instanceof Error ? err.message : 'Unknown error'}`,
122+
field: '',
123+
severity: 'error',
124+
}],
125+
}))
126+
: Promise.resolve(null),
127+
])
128+
129+
const allResults: ProviderResult[] = [...providerResults]
130+
if (stremioResult) allResults.push(stremioResult)
131+
132+
const response = this.buildResponse(allResults)
95133

96134
// Throw error if no sources found
97135
if (response.sources.length === 0) {

0 commit comments

Comments
 (0)