diff --git a/packages/addon/src/addon.ts b/packages/addon/src/addon.ts index 369f77a8..55af8bec 100644 --- a/packages/addon/src/addon.ts +++ b/packages/addon/src/addon.ts @@ -106,6 +106,8 @@ export class AIOStreams { sizeFilters: 0, duplicateStreams: 0, streamLimiters: 0, + excludeRegex: 0, + requiredRegex: 0, }; logger.info( @@ -341,6 +343,32 @@ export class AIOStreams { return false; } } + + // apply regex filters if API key is set + if (this.config.apiKey && this.config.regexFilters) { + const { excludePattern, includePattern } = this.config.regexFilters; + + if (excludePattern) { + const regexExclude = new RegExp(excludePattern, 'i'); + if (parsedStream.filename && regexExclude.test(parsedStream.filename)) { + skipReasons.excludeRegex++; + return false; + } + if (parsedStream.indexers && regexExclude.test(parsedStream.indexers)) { + skipReasons.excludeRegex++; + return false; + } + } + + if (includePattern) { + const regexInclude = new RegExp(includePattern, 'i'); + if (!((parsedStream.filename && regexInclude.test(parsedStream.filename)) || (parsedStream.indexers && regexInclude.test(parsedStream.indexers)))) { + skipReasons.requiredRegex++; + return false; + } + } + } + return true; }); diff --git a/packages/addon/src/config.ts b/packages/addon/src/config.ts index 421a3b5b..55eaccf7 100644 --- a/packages/addon/src/config.ts +++ b/packages/addon/src/config.ts @@ -431,5 +431,39 @@ export function validateConfig( } }); + if (config.regexFilters) { + if (!config.apiKey) { + return createResponse( + false, + 'missingApiKey', + 'Regex filtering requires an API key to be set' + ); + } + + if (config.regexFilters.excludePattern) { + try { + new RegExp(config.regexFilters.excludePattern); + } catch (e) { + return createResponse( + false, + 'invalidExcludeRegex', + 'Invalid exclude regex pattern' + ); + } + } + + if (config.regexFilters.includePattern) { + try { + new RegExp(config.regexFilters.includePattern); + } catch (e) { + return createResponse( + false, + 'invalidIncludeRegex', + 'Invalid include regex pattern' + ); + } + } + } + return createResponse(true, null, null); } diff --git a/packages/frontend/src/app/configure/page.module.css b/packages/frontend/src/app/configure/page.module.css index 264f775f..6faa4baf 100644 --- a/packages/frontend/src/app/configure/page.module.css +++ b/packages/frontend/src/app/configure/page.module.css @@ -231,3 +231,20 @@ ); /* Slight background highlight on click */ border-color: rgba(255, 255, 255, 0.815); /* More visible border on click */ } + +.input { + width: 100%; + padding: 8px; + margin: 4px 0; + border: 1px solid #ccc; + border-radius: 4px; + font-family: monospace; +} + +.helpText { + font-size: 0.8em; + color: #666; + margin-top: 4px; + font-family: monospace; + white-space: pre-line; +} diff --git a/packages/frontend/src/app/configure/page.tsx b/packages/frontend/src/app/configure/page.tsx index 5a68ed35..0ae4246b 100644 --- a/packages/frontend/src/app/configure/page.tsx +++ b/packages/frontend/src/app/configure/page.tsx @@ -215,6 +215,10 @@ export default function Configure() { ); const [showApiKeyInput, setShowApiKeyInput] = useState(false); const [manifestUrl, setManifestUrl] = useState(null); + const [regexFilters, setRegexFilters] = useState<{ + excludePattern?: string; + includePattern?: string; + }>({}); useEffect(() => { // get config from the server @@ -276,6 +280,10 @@ export default function Configure() { }, addons, services, + regexFilters: (regexFilters.excludePattern || regexFilters.includePattern) ? { + excludePattern: regexFilters.excludePattern || undefined, + includePattern: regexFilters.includePattern || undefined + } : undefined, }; return config; }; @@ -544,6 +552,7 @@ export default function Configure() { value: filter, })) || [] ); + setRegexFilters(decodedConfig.regexFilters || {}); setServices(loadValidServices(decodedConfig.services)); setMaxMovieSize( @@ -998,6 +1007,63 @@ export default function Configure() { + {showApiKeyInput && ( +
+
+

Regex Filtering

+

+ Configure regex patterns to filter streams. These filters will be applied in addition to keyword filters. +

+
+
+
+

Exclude Pattern

+

+ Enter a regex pattern to exclude streams. Streams will be excluded if their filename OR indexers match this pattern. +

+ setRegexFilters({ + ...regexFilters, + excludePattern: e.target.value + })} + placeholder="Example: \b(0neshot|1XBET)" + className={styles.input} + /> +

+ Example patterns: +
+ - \b(0neshot|1XBET|24xHD) (exclude 0neshot, 1XBET, and 24xHD releases) +
+ - ^.*Hi10.*$ (exclude Hi10 profile releases) +

+
+
+

Include Pattern

+

+ Enter a regex pattern to include streams. Only streams whose filename or indexers match this pattern will be included. +

+ setRegexFilters({ + ...regexFilters, + includePattern: e.target.value + })} + placeholder="Example: \b(3L|BiZKiT)" + className={styles.input} + /> +

+ Example patterns: +
+ - \b(3L|BiZKiT|BLURANiUM) (only include 3L, BiZKiT, and BLURANiUM releases) +

+
+
+
+ )} +
diff --git a/packages/types/src/types.ts b/packages/types/src/types.ts index dafe467b..7f5166ce 100644 --- a/packages/types/src/types.ts +++ b/packages/types/src/types.ts @@ -159,6 +159,10 @@ export interface Config { maxResultsPerResolution: number | null; excludeFilters: string[] | null; strictIncludeFilters: string[] | null; + regexFilters?: { + excludePattern?: string; + includePattern?: string; + }; mediaFlowConfig?: { mediaFlowEnabled: boolean; proxyUrl: string;