From c1b097e3500167b6ab4c93239e1e0719691d71ac Mon Sep 17 00:00:00 2001 From: Yann Leretaille Date: Thu, 5 Feb 2026 05:43:42 +0000 Subject: [PATCH 1/2] chore(server): add route parameter types for @types/express-serve-static-core 5.1.1 compatibility --- packages/server/src/middlewares/userData.ts | 11 +++- packages/server/src/routes/api/debrid.ts | 11 +++- packages/server/src/routes/api/proxy.ts | 14 ++++- .../server/src/routes/builtins/easynews.ts | 38 +++++++++++-- packages/server/src/routes/builtins/eztv.ts | 23 ++++++-- packages/server/src/routes/builtins/gdrive.ts | 54 +++++++++++++++++-- packages/server/src/routes/builtins/knaben.ts | 24 +++++++-- .../server/src/routes/builtins/newznab.ts | 23 +++++++- .../server/src/routes/builtins/prowlarr.ts | 24 +++++++-- packages/server/src/routes/builtins/seadex.ts | 24 +++++++-- .../src/routes/builtins/torbox-search.ts | 23 +++++++- .../src/routes/builtins/torrent-galaxy.ts | 25 +++++++-- .../server/src/routes/builtins/torznab.ts | 24 +++++++-- .../server/src/routes/stremio/addonCatalog.ts | 17 +++++- packages/server/src/routes/stremio/alias.ts | 2 +- packages/server/src/routes/stremio/catalog.ts | 18 ++++++- packages/server/src/routes/stremio/meta.ts | 15 +++++- packages/server/src/routes/stremio/stream.ts | 15 +++++- .../server/src/routes/stremio/subtitle.ts | 16 +++++- pnpm-lock.yaml | 8 +-- 20 files changed, 360 insertions(+), 49 deletions(-) diff --git a/packages/server/src/middlewares/userData.ts b/packages/server/src/middlewares/userData.ts index bbe9e5da5..5a106350f 100644 --- a/packages/server/src/middlewares/userData.ts +++ b/packages/server/src/middlewares/userData.ts @@ -23,8 +23,15 @@ const VALID_RESOURCES = [ 'streams', ]; +interface UserDataParams { + uuid?: string; + encryptedPassword?: string; + // match Express.Request to keep middleware flexible + [key: string]: string | string[] | undefined; +} + export const userDataMiddleware = async ( - req: Request, + req: Request, res: Response, next: NextFunction ) => { @@ -82,7 +89,7 @@ export const userDataMiddleware = async ( // decrypt the encrypted password const { success: successfulDecryption, data: decryptedPassword } = - decryptString(encryptedPassword!); + decryptString(encryptedPassword); if (!successfulDecryption) { if (constants.RESOURCES.includes(resource as Resource)) { res.status(200).json( diff --git a/packages/server/src/routes/api/debrid.ts b/packages/server/src/routes/api/debrid.ts index 7dd2a5e96..681411f82 100644 --- a/packages/server/src/routes/api/debrid.ts +++ b/packages/server/src/routes/api/debrid.ts @@ -38,9 +38,16 @@ router.use((req: Request, res: Response, next: NextFunction) => { } }); +interface PlaybackParams { + encryptedStoreAuth?: string; + fileInfo?: string; + metadataId?: string; + filename?: string; +} + router.get( '/playback/:encryptedStoreAuth/:fileInfo/:metadataId/:filename', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { try { const { encryptedStoreAuth, @@ -48,7 +55,7 @@ router.get( metadataId, filename, } = req.params; - if (!encodedFileInfo || !metadataId || !filename) { + if (!encryptedStoreAuth || !encodedFileInfo || !metadataId || !filename) { throw new APIError( constants.ErrorCode.BAD_REQUEST, undefined, diff --git a/packages/server/src/routes/api/proxy.ts b/packages/server/src/routes/api/proxy.ts index a88113558..723ea6286 100644 --- a/packages/server/src/routes/api/proxy.ts +++ b/packages/server/src/routes/api/proxy.ts @@ -192,9 +192,14 @@ router.get( } ); +interface ProxyParams { + encryptedAuthAndData?: string; + filename?: string; // optional +} + router.all( '/:encryptedAuthAndData{/:filename}', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const startTime = Date.now(); const requestId = Math.random().toString(36).substring(7); let upstreamResponse: Dispatcher.ResponseData | undefined; @@ -205,6 +210,13 @@ router.all( try { // decrypt and authenticate the request const { encryptedAuthAndData } = req.params; + if (!encryptedAuthAndData) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'EncryptedAuthAndData is required' + ); + } // const [encodeMode, encryptedAuth, encryptedData] = // encryptedAuthAndData.split('.'); const parts = encryptedAuthAndData.split('.'); diff --git a/packages/server/src/routes/builtins/easynews.ts b/packages/server/src/routes/builtins/easynews.ts index 99a69c12e..8fcb2fec7 100644 --- a/packages/server/src/routes/builtins/easynews.ts +++ b/packages/server/src/routes/builtins/easynews.ts @@ -18,9 +18,13 @@ import { createResponse } from '../../utils/responses.js'; const router: Router = Router(); const logger = createLogger('server'); +interface EasynewsManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { @@ -37,10 +41,23 @@ router.get( } ); +interface EasynewsStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new EasynewsSearchAddon( @@ -63,15 +80,30 @@ router.get( * NZB endpoint - fetches NZB from Easynews and serves it * This endpoint is needed because Easynews requires a POST request to fetch NZBs */ +interface EasynewsNzbParams { + encodedAuth?: string; + encodedParams?: string; + aiostreamsAuth?: string; // optional + filename?: string; +} + router.get( '/nzb/:encodedAuth/:encodedParams{/:aiostreamsAuth}/:filename.nzb', easynewsNzbRateLimiter, - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedAuth, encodedParams, aiostreamsAuth: encodedAiostreamsAuth, + filename, } = req.params; + if (!encodedAuth || !encodedParams || !filename) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'EncodedAuth, encodedParams, and filename are required' + ); + } try { // Decode and validate auth credentials diff --git a/packages/server/src/routes/builtins/eztv.ts b/packages/server/src/routes/builtins/eztv.ts index f9fc8c86f..fb7834719 100644 --- a/packages/server/src/routes/builtins/eztv.ts +++ b/packages/server/src/routes/builtins/eztv.ts @@ -1,11 +1,15 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { EztvAddon, fromUrlSafeBase64 } from '@aiostreams/core'; +import { EztvAddon, fromUrlSafeBase64, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); +interface EztvManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { const manifest = new EztvAddon( @@ -21,10 +25,23 @@ router.get( } ); +interface EztvStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new EztvAddon( diff --git a/packages/server/src/routes/builtins/gdrive.ts b/packages/server/src/routes/builtins/gdrive.ts index 7d8919cf4..029b694cc 100644 --- a/packages/server/src/routes/builtins/gdrive.ts +++ b/packages/server/src/routes/builtins/gdrive.ts @@ -1,12 +1,16 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { createLogger, fromUrlSafeBase64, GDriveAddon } from '@aiostreams/core'; +import { createLogger, fromUrlSafeBase64, GDriveAddon, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface GDriveManifestParams { + encodedConfig?: string; // optional +} + router.get( '{/:encodedConfig}/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; const config = encodedConfig ? JSON.parse(fromUrlSafeBase64(encodedConfig)) @@ -23,10 +27,23 @@ router.get( } ); +interface GDriveMetaParams { + encodedConfig?: string; + type?: string; + id?: string; +} + router.get( '/:encodedConfig/meta/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!encodedConfig || !type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'EncodedConfig, type, and id are required' + ); + } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { @@ -41,10 +58,24 @@ router.get( } ); +interface GDriveCatalogParams { + encodedConfig?: string; + type?: string; + id?: string; + extras?: string; // optional +} + router.get( '/:encodedConfig/catalog/:type/:id{/:extras}.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id, extras } = req.params; + if (!encodedConfig || !type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'EncodedConfig, type, and id are required' + ); + } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { @@ -59,10 +90,23 @@ router.get( } ); +interface GDriveStreamParams { + encodedConfig?: string; + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!encodedConfig || !type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'EncodedConfig, type, and id are required' + ); + } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { diff --git a/packages/server/src/routes/builtins/knaben.ts b/packages/server/src/routes/builtins/knaben.ts index f4c85a09c..a516ac91d 100644 --- a/packages/server/src/routes/builtins/knaben.ts +++ b/packages/server/src/routes/builtins/knaben.ts @@ -1,13 +1,16 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { KnabenAddon, fromUrlSafeBase64 } from '@aiostreams/core'; -import { createLogger } from '@aiostreams/core'; +import { KnabenAddon, fromUrlSafeBase64, createLogger, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface KnabenManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { const manifest = new KnabenAddon( @@ -23,10 +26,23 @@ router.get( } ); +interface KnabenStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new KnabenAddon( diff --git a/packages/server/src/routes/builtins/newznab.ts b/packages/server/src/routes/builtins/newznab.ts index 6ebe8778e..948a120e0 100644 --- a/packages/server/src/routes/builtins/newznab.ts +++ b/packages/server/src/routes/builtins/newznab.ts @@ -3,14 +3,20 @@ import { NewznabAddon, createLogger, fromUrlSafeBase64, + APIError, + constants, } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface NewznabManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { @@ -27,10 +33,23 @@ router.get( } ); +interface NewznabStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new NewznabAddon( diff --git a/packages/server/src/routes/builtins/prowlarr.ts b/packages/server/src/routes/builtins/prowlarr.ts index 460cd922e..a4dbf7413 100644 --- a/packages/server/src/routes/builtins/prowlarr.ts +++ b/packages/server/src/routes/builtins/prowlarr.ts @@ -1,13 +1,16 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { ProwlarrAddon, fromUrlSafeBase64 } from '@aiostreams/core'; -import { createLogger } from '@aiostreams/core'; +import { ProwlarrAddon, fromUrlSafeBase64, createLogger, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface ProwlarrManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { @@ -24,10 +27,23 @@ router.get( } ); +interface ProwlarrStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new ProwlarrAddon( diff --git a/packages/server/src/routes/builtins/seadex.ts b/packages/server/src/routes/builtins/seadex.ts index bec1eec15..1d916488c 100644 --- a/packages/server/src/routes/builtins/seadex.ts +++ b/packages/server/src/routes/builtins/seadex.ts @@ -1,13 +1,16 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { SeaDexAddon, fromUrlSafeBase64 } from '@aiostreams/core'; -import { createLogger } from '@aiostreams/core'; +import { SeaDexAddon, fromUrlSafeBase64, createLogger, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface SeaDexManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { const manifest = new SeaDexAddon( @@ -24,10 +27,23 @@ router.get( } ); +interface SeaDexStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const streams = await new SeaDexAddon( encodedConfig diff --git a/packages/server/src/routes/builtins/torbox-search.ts b/packages/server/src/routes/builtins/torbox-search.ts index f993a5073..2e62a3e47 100644 --- a/packages/server/src/routes/builtins/torbox-search.ts +++ b/packages/server/src/routes/builtins/torbox-search.ts @@ -4,15 +4,21 @@ import { TorBoxSearchAddon, TorBoxSearchAddonError, fromUrlSafeBase64, + APIError, + constants, } from '@aiostreams/core'; import { createResponse } from '../../utils/responses.js'; const router: Router = Router(); const logger = createLogger('builtins:torbox-search'); +interface TorboxManifestParams { + encodedConfig?: string; // optional +} + router.get( '{/:encodedConfig}/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { const manifest = encodedConfig @@ -42,10 +48,23 @@ router.get( } ); +interface TorboxStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new TorBoxSearchAddon( diff --git a/packages/server/src/routes/builtins/torrent-galaxy.ts b/packages/server/src/routes/builtins/torrent-galaxy.ts index 2f3b9bc59..16b2ccf8d 100644 --- a/packages/server/src/routes/builtins/torrent-galaxy.ts +++ b/packages/server/src/routes/builtins/torrent-galaxy.ts @@ -4,15 +4,21 @@ import { AIOStreamResponse, TorrentGalaxyAddon, fromUrlSafeBase64, + createLogger, + APIError, + constants, } from '@aiostreams/core'; -import { createLogger } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface TorrentGalaxyManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { @@ -29,10 +35,23 @@ router.get( } ); +interface TorrentGalaxyStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new TorrentGalaxyAddon( diff --git a/packages/server/src/routes/builtins/torznab.ts b/packages/server/src/routes/builtins/torznab.ts index ef086ec67..b725a339b 100644 --- a/packages/server/src/routes/builtins/torznab.ts +++ b/packages/server/src/routes/builtins/torznab.ts @@ -1,13 +1,16 @@ import { Router, Request, Response, NextFunction } from 'express'; -import { TorznabAddon, fromUrlSafeBase64 } from '@aiostreams/core'; -import { createLogger } from '@aiostreams/core'; +import { TorznabAddon, fromUrlSafeBase64, createLogger, APIError, constants } from '@aiostreams/core'; const router: Router = Router(); const logger = createLogger('server'); +interface TorznabManifestParams { + encodedConfig?: string; // optional +} + router.get( '/:encodedConfig/manifest.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig } = req.params; try { @@ -24,10 +27,23 @@ router.get( } ); +interface TorznabStreamParams { + encodedConfig?: string; // optional + type?: string; + id?: string; +} + router.get( '/:encodedConfig/stream/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; + if (!type || !id) { + throw new APIError( + constants.ErrorCode.BAD_REQUEST, + undefined, + 'Type and id are required' + ); + } try { const addon = new TorznabAddon( diff --git a/packages/server/src/routes/stremio/addonCatalog.ts b/packages/server/src/routes/stremio/addonCatalog.ts index decf2d85a..371266417 100644 --- a/packages/server/src/routes/stremio/addonCatalog.ts +++ b/packages/server/src/routes/stremio/addonCatalog.ts @@ -9,9 +9,14 @@ import { const logger = createLogger('server'); const router: Router = Router(); +interface AddonCatalogParams { + type?: string; + id?: string; +} + router.get( '/:type/:id.json', - async (req: Request, res: Response, next) => { + async (req: Request, res: Response, next) => { if (!req.userData) { res.status(200).json({ addons: [ @@ -26,6 +31,16 @@ router.get( try { const { type, id } = req.params; + if (!type || !id) { + res.status(200).json({ + addons: [ + StremioTransformer.createErrorAddonCatalog({ + errorDescription: 'Missing URL Parameters: Type and id are required', + }), + ], + }); + return; + } logger.debug('Addon catalog request received', { type, id, diff --git a/packages/server/src/routes/stremio/alias.ts b/packages/server/src/routes/stremio/alias.ts index 2fd402f71..432542d3e 100644 --- a/packages/server/src/routes/stremio/alias.ts +++ b/packages/server/src/routes/stremio/alias.ts @@ -6,7 +6,7 @@ const router: Router = Router(); interface AliasParams { alias: string; - [key: string]: string; + wildcardPath?: string | string[]; // optional } router.get( diff --git a/packages/server/src/routes/stremio/catalog.ts b/packages/server/src/routes/stremio/catalog.ts index 7454497d8..ab3a1a542 100644 --- a/packages/server/src/routes/stremio/catalog.ts +++ b/packages/server/src/routes/stremio/catalog.ts @@ -12,9 +12,15 @@ const router: Router = Router(); router.use(stremioCatalogRateLimiter); +interface CatalogParams { + type?: string; + id?: string; + extras?: string; // optional +} + router.get( '/:type/:id{/:extras}.json', - async (req: Request, res: Response, next) => { + async (req: Request, res: Response, next) => { const transformer = new StremioTransformer(req.userData); if (!req.userData) { res.status(200).json( @@ -29,6 +35,16 @@ router.get( try { const { type, id, extras } = req.params; + if (!type || !id) { + res.status(200).json( + transformer.transformCatalog({ + success: false, + data: [], + errors: [{ description: 'Missing URL Parameters: Type and id are required' }], + }) + ); + return; + } res .status(200) diff --git a/packages/server/src/routes/stremio/meta.ts b/packages/server/src/routes/stremio/meta.ts index 4b6869d9e..bc7b3b908 100644 --- a/packages/server/src/routes/stremio/meta.ts +++ b/packages/server/src/routes/stremio/meta.ts @@ -13,9 +13,14 @@ const router: Router = Router(); router.use(stremioMetaRateLimiter); +interface MetaParams { + type?: string; + id?: string; +} + router.get( '/:type/:id.json', - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { if (!req.userData) { res.status(200).json({ meta: StremioTransformer.createErrorMeta({ @@ -27,6 +32,14 @@ router.get( const transformer = new StremioTransformer(req.userData); try { const { type, id } = req.params; + if (!type || !id) { + res.status(200).json({ + meta: StremioTransformer.createErrorMeta({ + errorDescription: 'Missing URL Parameters: Type and id are required', + }), + }); + return; + } logger.debug('Meta request received', { type, id, diff --git a/packages/server/src/routes/stremio/stream.ts b/packages/server/src/routes/stremio/stream.ts index 8c2f3e394..dcde513a9 100644 --- a/packages/server/src/routes/stremio/stream.ts +++ b/packages/server/src/routes/stremio/stream.ts @@ -16,10 +16,15 @@ const logger = createLogger('server'); router.use(stremioStreamRateLimiter); +interface StreamParams { + type?: string; + id?: string; +} + router.get( '/:type/:id.json', async ( - req: Request, + req: Request, res: Response, next: NextFunction ) => { @@ -44,6 +49,14 @@ router.get( try { const { type, id } = req.params; + if (!type || !id) { + res.status(200).json( + StremioTransformer.createDynamicError('stream', { + errorDescription: 'Missing URL Parameters: Type and id are required', + }) + ); + return; + } const aiostreams = await new AIOStreams(req.userData).initialise(); diff --git a/packages/server/src/routes/stremio/subtitle.ts b/packages/server/src/routes/stremio/subtitle.ts index 1cf744cb2..d937abfa0 100644 --- a/packages/server/src/routes/stremio/subtitle.ts +++ b/packages/server/src/routes/stremio/subtitle.ts @@ -12,9 +12,15 @@ const router: Router = Router(); router.use(stremioSubtitleRateLimiter); +interface SubtitleParams { + type?: string; + id?: string; + extras?: string; // optional +} + router.get( '/:type/:id{/:extras}.json', - async (req: Request, res: Response, next) => { + async (req: Request, res: Response, next) => { if (!req.userData) { res.status(200).json( StremioTransformer.createDynamicError('subtitles', { @@ -26,6 +32,14 @@ router.get( const transformer = new StremioTransformer(req.userData); try { const { type, id, extras } = req.params; + if (!type || !id) { + res.status(200).json( + StremioTransformer.createDynamicError('subtitles', { + errorDescription: 'Missing URL Parameters: Type and id are required', + }) + ); + return; + } res .status(200) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7077a8de9..ea5368770 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1855,8 +1855,8 @@ packages: '@types/express-rate-limit@5.1.3': resolution: {integrity: sha512-H+TYy3K53uPU2TqPGFYaiWc2xJV6+bIFkDd/Ma2/h67Pa6ARk9kWE0p/K9OH1Okm0et9Sfm66fmXoAxsH2PHXg==} - '@types/express-serve-static-core@5.1.0': - resolution: {integrity: sha512-jnHMsrd0Mwa9Cf4IdOzbz543y4XJepXrbia2T4b6+spXC2We3t1y6K44D3mR8XMFSXMCf3/l7rCgddfx7UNVBA==} + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} @@ -6429,7 +6429,7 @@ snapshots: dependencies: '@types/express': 5.0.6 - '@types/express-serve-static-core@5.1.0': + '@types/express-serve-static-core@5.1.1': dependencies: '@types/node': 20.19.26 '@types/qs': 6.14.0 @@ -6439,7 +6439,7 @@ snapshots: '@types/express@5.0.6': dependencies: '@types/body-parser': 1.19.6 - '@types/express-serve-static-core': 5.1.0 + '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 '@types/hast@3.0.4': From 2241fd35c3eb121c0cb601d89ca54fd4c7c0807b Mon Sep 17 00:00:00 2001 From: Yann Leretaille Date: Thu, 5 Feb 2026 07:28:26 +0000 Subject: [PATCH 2/2] fix: remove unnecessary validation checks --- packages/server/src/routes/api/debrid.ts | 15 ++----- packages/server/src/routes/api/proxy.ts | 9 +---- .../server/src/routes/builtins/easynews.ts | 30 +++++--------- packages/server/src/routes/builtins/eztv.ts | 11 +----- packages/server/src/routes/builtins/gdrive.ts | 39 +++++-------------- packages/server/src/routes/builtins/knaben.ts | 11 +----- .../server/src/routes/builtins/newznab.ts | 11 +----- .../server/src/routes/builtins/prowlarr.ts | 11 +----- packages/server/src/routes/builtins/seadex.ts | 11 +----- .../src/routes/builtins/torbox-search.ts | 11 +----- .../src/routes/builtins/torrent-galaxy.ts | 11 +----- .../server/src/routes/builtins/torznab.ts | 11 +----- .../server/src/routes/stremio/addonCatalog.ts | 14 +------ packages/server/src/routes/stremio/alias.ts | 2 +- packages/server/src/routes/stremio/catalog.ts | 14 +------ packages/server/src/routes/stremio/meta.ts | 12 +----- packages/server/src/routes/stremio/stream.ts | 12 +----- .../server/src/routes/stremio/subtitle.ts | 16 ++------ 18 files changed, 52 insertions(+), 199 deletions(-) diff --git a/packages/server/src/routes/api/debrid.ts b/packages/server/src/routes/api/debrid.ts index 681411f82..0863411ae 100644 --- a/packages/server/src/routes/api/debrid.ts +++ b/packages/server/src/routes/api/debrid.ts @@ -39,10 +39,10 @@ router.use((req: Request, res: Response, next: NextFunction) => { }); interface PlaybackParams { - encryptedStoreAuth?: string; - fileInfo?: string; - metadataId?: string; - filename?: string; + encryptedStoreAuth: string; + fileInfo: string; + metadataId: string; + filename: string; } router.get( @@ -55,13 +55,6 @@ router.get( metadataId, filename, } = req.params; - if (!encryptedStoreAuth || !encodedFileInfo || !metadataId || !filename) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Encrypted store auth, file info, metadata id and filename are required' - ); - } let fileInfo: FileInfo | undefined; diff --git a/packages/server/src/routes/api/proxy.ts b/packages/server/src/routes/api/proxy.ts index 723ea6286..16585664d 100644 --- a/packages/server/src/routes/api/proxy.ts +++ b/packages/server/src/routes/api/proxy.ts @@ -193,7 +193,7 @@ router.get( ); interface ProxyParams { - encryptedAuthAndData?: string; + encryptedAuthAndData: string; filename?: string; // optional } @@ -210,13 +210,6 @@ router.all( try { // decrypt and authenticate the request const { encryptedAuthAndData } = req.params; - if (!encryptedAuthAndData) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'EncryptedAuthAndData is required' - ); - } // const [encodeMode, encryptedAuth, encryptedData] = // encryptedAuthAndData.split('.'); const parts = encryptedAuthAndData.split('.'); diff --git a/packages/server/src/routes/builtins/easynews.ts b/packages/server/src/routes/builtins/easynews.ts index 8fcb2fec7..f54ae6345 100644 --- a/packages/server/src/routes/builtins/easynews.ts +++ b/packages/server/src/routes/builtins/easynews.ts @@ -43,21 +43,14 @@ router.get( interface EasynewsStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new EasynewsSearchAddon( @@ -81,29 +74,24 @@ router.get( * This endpoint is needed because Easynews requires a POST request to fetch NZBs */ interface EasynewsNzbParams { - encodedAuth?: string; - encodedParams?: string; + encodedAuth: string; + encodedParams: string; aiostreamsAuth?: string; // optional - filename?: string; + filename: string; + // match Express.Request to allow chaining of easynewsNzbRateLimiter middleware + [key: string]: string | string[] | undefined; } router.get( '/nzb/:encodedAuth/:encodedParams{/:aiostreamsAuth}/:filename.nzb', easynewsNzbRateLimiter, - async (req: Request, res: Response, next: NextFunction) => { + async (req: Request, res: Response, next: NextFunction) => { const { encodedAuth, encodedParams, aiostreamsAuth: encodedAiostreamsAuth, filename, - } = req.params; - if (!encodedAuth || !encodedParams || !filename) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'EncodedAuth, encodedParams, and filename are required' - ); - } + } = req.params as EasynewsNzbParams; try { // Decode and validate auth credentials diff --git a/packages/server/src/routes/builtins/eztv.ts b/packages/server/src/routes/builtins/eztv.ts index fb7834719..b65bb99e7 100644 --- a/packages/server/src/routes/builtins/eztv.ts +++ b/packages/server/src/routes/builtins/eztv.ts @@ -27,21 +27,14 @@ router.get( interface EztvStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new EztvAddon( diff --git a/packages/server/src/routes/builtins/gdrive.ts b/packages/server/src/routes/builtins/gdrive.ts index 029b694cc..0023de99b 100644 --- a/packages/server/src/routes/builtins/gdrive.ts +++ b/packages/server/src/routes/builtins/gdrive.ts @@ -28,22 +28,15 @@ router.get( ); interface GDriveMetaParams { - encodedConfig?: string; - type?: string; - id?: string; + encodedConfig: string; + type: string; + id: string; } router.get( '/:encodedConfig/meta/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!encodedConfig || !type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'EncodedConfig, type, and id are required' - ); - } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { @@ -59,9 +52,9 @@ router.get( ); interface GDriveCatalogParams { - encodedConfig?: string; - type?: string; - id?: string; + encodedConfig: string; + type: string; + id: string; extras?: string; // optional } @@ -69,13 +62,6 @@ router.get( '/:encodedConfig/catalog/:type/:id{/:extras}.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id, extras } = req.params; - if (!encodedConfig || !type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'EncodedConfig, type, and id are required' - ); - } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { @@ -91,22 +77,15 @@ router.get( ); interface GDriveStreamParams { - encodedConfig?: string; - type?: string; - id?: string; + encodedConfig: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!encodedConfig || !type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'EncodedConfig, type, and id are required' - ); - } const config = JSON.parse(fromUrlSafeBase64(encodedConfig)); try { diff --git a/packages/server/src/routes/builtins/knaben.ts b/packages/server/src/routes/builtins/knaben.ts index a516ac91d..242904c0a 100644 --- a/packages/server/src/routes/builtins/knaben.ts +++ b/packages/server/src/routes/builtins/knaben.ts @@ -28,21 +28,14 @@ router.get( interface KnabenStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new KnabenAddon( diff --git a/packages/server/src/routes/builtins/newznab.ts b/packages/server/src/routes/builtins/newznab.ts index 948a120e0..8be63d5a7 100644 --- a/packages/server/src/routes/builtins/newznab.ts +++ b/packages/server/src/routes/builtins/newznab.ts @@ -35,21 +35,14 @@ router.get( interface NewznabStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new NewznabAddon( diff --git a/packages/server/src/routes/builtins/prowlarr.ts b/packages/server/src/routes/builtins/prowlarr.ts index a4dbf7413..a31703bac 100644 --- a/packages/server/src/routes/builtins/prowlarr.ts +++ b/packages/server/src/routes/builtins/prowlarr.ts @@ -29,21 +29,14 @@ router.get( interface ProwlarrStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new ProwlarrAddon( diff --git a/packages/server/src/routes/builtins/seadex.ts b/packages/server/src/routes/builtins/seadex.ts index 1d916488c..d2342bef0 100644 --- a/packages/server/src/routes/builtins/seadex.ts +++ b/packages/server/src/routes/builtins/seadex.ts @@ -29,21 +29,14 @@ router.get( interface SeaDexStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const streams = await new SeaDexAddon( encodedConfig diff --git a/packages/server/src/routes/builtins/torbox-search.ts b/packages/server/src/routes/builtins/torbox-search.ts index 2e62a3e47..435323075 100644 --- a/packages/server/src/routes/builtins/torbox-search.ts +++ b/packages/server/src/routes/builtins/torbox-search.ts @@ -50,21 +50,14 @@ router.get( interface TorboxStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new TorBoxSearchAddon( diff --git a/packages/server/src/routes/builtins/torrent-galaxy.ts b/packages/server/src/routes/builtins/torrent-galaxy.ts index 16b2ccf8d..aa80dc444 100644 --- a/packages/server/src/routes/builtins/torrent-galaxy.ts +++ b/packages/server/src/routes/builtins/torrent-galaxy.ts @@ -37,21 +37,14 @@ router.get( interface TorrentGalaxyStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new TorrentGalaxyAddon( diff --git a/packages/server/src/routes/builtins/torznab.ts b/packages/server/src/routes/builtins/torznab.ts index b725a339b..c71c87633 100644 --- a/packages/server/src/routes/builtins/torznab.ts +++ b/packages/server/src/routes/builtins/torznab.ts @@ -29,21 +29,14 @@ router.get( interface TorznabStreamParams { encodedConfig?: string; // optional - type?: string; - id?: string; + type: string; + id: string; } router.get( '/:encodedConfig/stream/:type/:id.json', async (req: Request, res: Response, next: NextFunction) => { const { encodedConfig, type, id } = req.params; - if (!type || !id) { - throw new APIError( - constants.ErrorCode.BAD_REQUEST, - undefined, - 'Type and id are required' - ); - } try { const addon = new TorznabAddon( diff --git a/packages/server/src/routes/stremio/addonCatalog.ts b/packages/server/src/routes/stremio/addonCatalog.ts index 371266417..899160444 100644 --- a/packages/server/src/routes/stremio/addonCatalog.ts +++ b/packages/server/src/routes/stremio/addonCatalog.ts @@ -10,8 +10,8 @@ const logger = createLogger('server'); const router: Router = Router(); interface AddonCatalogParams { - type?: string; - id?: string; + type: string; + id: string; } router.get( @@ -31,16 +31,6 @@ router.get( try { const { type, id } = req.params; - if (!type || !id) { - res.status(200).json({ - addons: [ - StremioTransformer.createErrorAddonCatalog({ - errorDescription: 'Missing URL Parameters: Type and id are required', - }), - ], - }); - return; - } logger.debug('Addon catalog request received', { type, id, diff --git a/packages/server/src/routes/stremio/alias.ts b/packages/server/src/routes/stremio/alias.ts index 432542d3e..170fd6bf5 100644 --- a/packages/server/src/routes/stremio/alias.ts +++ b/packages/server/src/routes/stremio/alias.ts @@ -6,7 +6,7 @@ const router: Router = Router(); interface AliasParams { alias: string; - wildcardPath?: string | string[]; // optional + wildcardPath?: string | string[]; // optional (wildcard route) } router.get( diff --git a/packages/server/src/routes/stremio/catalog.ts b/packages/server/src/routes/stremio/catalog.ts index ab3a1a542..fa159b23d 100644 --- a/packages/server/src/routes/stremio/catalog.ts +++ b/packages/server/src/routes/stremio/catalog.ts @@ -13,8 +13,8 @@ const router: Router = Router(); router.use(stremioCatalogRateLimiter); interface CatalogParams { - type?: string; - id?: string; + type: string; + id: string; extras?: string; // optional } @@ -35,16 +35,6 @@ router.get( try { const { type, id, extras } = req.params; - if (!type || !id) { - res.status(200).json( - transformer.transformCatalog({ - success: false, - data: [], - errors: [{ description: 'Missing URL Parameters: Type and id are required' }], - }) - ); - return; - } res .status(200) diff --git a/packages/server/src/routes/stremio/meta.ts b/packages/server/src/routes/stremio/meta.ts index bc7b3b908..d843a532a 100644 --- a/packages/server/src/routes/stremio/meta.ts +++ b/packages/server/src/routes/stremio/meta.ts @@ -14,8 +14,8 @@ const router: Router = Router(); router.use(stremioMetaRateLimiter); interface MetaParams { - type?: string; - id?: string; + type: string; + id: string; } router.get( @@ -32,14 +32,6 @@ router.get( const transformer = new StremioTransformer(req.userData); try { const { type, id } = req.params; - if (!type || !id) { - res.status(200).json({ - meta: StremioTransformer.createErrorMeta({ - errorDescription: 'Missing URL Parameters: Type and id are required', - }), - }); - return; - } logger.debug('Meta request received', { type, id, diff --git a/packages/server/src/routes/stremio/stream.ts b/packages/server/src/routes/stremio/stream.ts index dcde513a9..6555a5157 100644 --- a/packages/server/src/routes/stremio/stream.ts +++ b/packages/server/src/routes/stremio/stream.ts @@ -17,8 +17,8 @@ const logger = createLogger('server'); router.use(stremioStreamRateLimiter); interface StreamParams { - type?: string; - id?: string; + type: string; + id: string; } router.get( @@ -49,14 +49,6 @@ router.get( try { const { type, id } = req.params; - if (!type || !id) { - res.status(200).json( - StremioTransformer.createDynamicError('stream', { - errorDescription: 'Missing URL Parameters: Type and id are required', - }) - ); - return; - } const aiostreams = await new AIOStreams(req.userData).initialise(); diff --git a/packages/server/src/routes/stremio/subtitle.ts b/packages/server/src/routes/stremio/subtitle.ts index d937abfa0..bb20784da 100644 --- a/packages/server/src/routes/stremio/subtitle.ts +++ b/packages/server/src/routes/stremio/subtitle.ts @@ -1,4 +1,4 @@ -import { Router, Request, Response } from 'express'; +import { Router, Request, Response, NextFunction } from 'express'; import { AIOStreams, SubtitleResponse, @@ -13,14 +13,14 @@ const router: Router = Router(); router.use(stremioSubtitleRateLimiter); interface SubtitleParams { - type?: string; - id?: string; + type: string; + id: string; extras?: string; // optional } router.get( '/:type/:id{/:extras}.json', - async (req: Request, res: Response, next) => { + async (req: Request, res: Response, next: NextFunction) => { if (!req.userData) { res.status(200).json( StremioTransformer.createDynamicError('subtitles', { @@ -32,14 +32,6 @@ router.get( const transformer = new StremioTransformer(req.userData); try { const { type, id, extras } = req.params; - if (!type || !id) { - res.status(200).json( - StremioTransformer.createDynamicError('subtitles', { - errorDescription: 'Missing URL Parameters: Type and id are required', - }) - ); - return; - } res .status(200)