diff --git a/packages/addon/src/addon.ts b/packages/addon/src/addon.ts index 91d074ba..f2b88330 100644 --- a/packages/addon/src/addon.ts +++ b/packages/addon/src/addon.ts @@ -54,8 +54,12 @@ export class AIOStreams { this.config = config; // Pre-compile regex patterns if they exist if (this.config.regexSortPatterns) { - const regexSortPatterns = this.config.regexSortPatterns.split(/\s+/).filter(Boolean); - this.preCompiledRegexPatterns = regexSortPatterns.map(pattern => new RegExp(pattern)); + const regexSortPatterns = this.config.regexSortPatterns + .split(/\s+/) + .filter(Boolean); + this.preCompiledRegexPatterns = regexSortPatterns.map( + (pattern) => new RegExp(pattern) + ); } } @@ -377,22 +381,35 @@ export class AIOStreams { // 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 && safeRegexTest(regexExclude, parsedStream.filename)) { + if ( + parsedStream.filename && + safeRegexTest(regexExclude, parsedStream.filename) + ) { skipReasons.excludeRegex++; return false; } - if (parsedStream.indexers && safeRegexTest(regexExclude, parsedStream.indexers)) { + if ( + parsedStream.indexers && + safeRegexTest(regexExclude, parsedStream.indexers) + ) { skipReasons.excludeRegex++; return false; } } - + if (includePattern) { const regexInclude = new RegExp(includePattern, 'i'); - if (!((parsedStream.filename && safeRegexTest(regexInclude, parsedStream.filename)) || (parsedStream.indexers && safeRegexTest(regexInclude, parsedStream.indexers)))) { + if ( + !( + (parsedStream.filename && + safeRegexTest(regexInclude, parsedStream.filename)) || + (parsedStream.indexers && + safeRegexTest(regexInclude, parsedStream.indexers)) + ) + ) { skipReasons.requiredRegex++; return false; } @@ -677,7 +694,11 @@ export class AIOStreams { // Identify streams that require proxying const streamsToProxy = parsedStreams .map((stream, index) => ({ stream, index })) - .filter(({ stream }) => stream.url && this.shouldProxyStream(stream, mediaFlowConfig, stremThruConfig)); + .filter( + ({ stream }) => + stream.url && + this.shouldProxyStream(stream, mediaFlowConfig, stremThruConfig) + ); const proxiedUrls = streamsToProxy.length ? mediaFlowConfig.mediaFlowEnabled @@ -795,18 +816,20 @@ export class AIOStreams { ); } else if (field === 'regexSort') { if (!this.config.regexSortPatterns) return 0; - + try { for (let i = 0; i < this.preCompiledRegexPatterns.length; i++) { const regex = this.preCompiledRegexPatterns[i]; const aMatch = a.filename ? safeRegexTest(regex, a.filename) : false; const bMatch = b.filename ? safeRegexTest(regex, b.filename) : false; - + // If both match or both don't match, continue to next pattern if ((aMatch && bMatch) || (!aMatch && !bMatch)) continue; - + // If one matches and the other doesn't, use direction to determine order - const direction = this.config.sortBy.find((sort) => Object.keys(sort)[0] === 'regexSort')?.direction; + const direction = this.config.sortBy.find( + (sort) => Object.keys(sort)[0] === 'regexSort' + )?.direction; if (direction === 'asc') { // In ascending order, matching files come last return aMatch ? 1 : -1; @@ -815,7 +838,7 @@ export class AIOStreams { return aMatch ? -1 : 1; } } - + // If we get here, no patterns matched or all patterns matched the same way return 0; } catch (e) { diff --git a/packages/addon/src/config.ts b/packages/addon/src/config.ts index 6fda61eb..e3c2eb5d 100644 --- a/packages/addon/src/config.ts +++ b/packages/addon/src/config.ts @@ -383,7 +383,10 @@ export function validateConfig( ); } - if (config.mediaFlowConfig?.mediaFlowEnabled && config.stremThruConfig?.stremThruEnabled) { + if ( + config.mediaFlowConfig?.mediaFlowEnabled && + config.stremThruConfig?.stremThruEnabled + ) { return createResponse( false, 'multipleProxyServices', @@ -464,7 +467,7 @@ export function validateConfig( 'Regex filtering requires an API key to be set' ); } - + if (config.regexFilters.excludePattern) { try { new RegExp(config.regexFilters.excludePattern); @@ -476,7 +479,7 @@ export function validateConfig( ); } } - + if (config.regexFilters.includePattern) { try { new RegExp(config.regexFilters.includePattern); diff --git a/packages/addon/src/utils/regex.ts b/packages/addon/src/utils/regex.ts index 68ec91ac..96d48f0e 100644 --- a/packages/addon/src/utils/regex.ts +++ b/packages/addon/src/utils/regex.ts @@ -9,7 +9,11 @@ const DEFAULT_TIMEOUT = 1000; // 1 second timeout * @param timeoutMs Optional timeout in milliseconds (default: 1000ms) * @returns boolean indicating if the pattern matches the string */ -export function safeRegexTest(pattern: RegExp, str: string, timeoutMs: number = DEFAULT_TIMEOUT): boolean { +export function safeRegexTest( + pattern: RegExp, + str: string, + timeoutMs: number = DEFAULT_TIMEOUT +): boolean { try { return isMatch(pattern, str, { timeout: timeoutMs }); } catch (error) { @@ -25,7 +29,11 @@ export function safeRegexTest(pattern: RegExp, str: string, timeoutMs: number = * @param timeoutMs Optional timeout in milliseconds (default: 1000ms) * @returns The first match or undefined if no match or timeout */ -export function safeRegexMatch(pattern: RegExp, str: string, timeoutMs: number = DEFAULT_TIMEOUT): string | undefined { +export function safeRegexMatch( + pattern: RegExp, + str: string, + timeoutMs: number = DEFAULT_TIMEOUT +): string | undefined { try { const match = firstMatch(pattern, str, { timeout: timeoutMs }); return match?.match; @@ -42,12 +50,16 @@ export function safeRegexMatch(pattern: RegExp, str: string, timeoutMs: number = * @param timeoutMs Optional timeout in milliseconds (default: 1000ms) * @returns Array of matches or empty array if no matches or timeout */ -export function safeRegexMatches(pattern: RegExp, str: string, timeoutMs: number = DEFAULT_TIMEOUT): string[] { +export function safeRegexMatches( + pattern: RegExp, + str: string, + timeoutMs: number = DEFAULT_TIMEOUT +): string[] { try { const matches = Array.from(pattern[Symbol.matchAll](str)); - return matches.map(m => m[0]); + return matches.map((m) => m[0]); } catch (error) { console.error(`Regex matches timed out after ${timeoutMs}ms:`, error); return []; } -} \ No newline at end of file +} diff --git a/packages/frontend/src/app/configure/page.tsx b/packages/frontend/src/app/configure/page.tsx index 864f892b..f4d3fbe1 100644 --- a/packages/frontend/src/app/configure/page.tsx +++ b/packages/frontend/src/app/configure/page.tsx @@ -302,10 +302,13 @@ export default function Configure() { }, addons, services, - regexFilters: (regexFilters.excludePattern || regexFilters.includePattern) ? { - excludePattern: regexFilters.excludePattern || undefined, - includePattern: regexFilters.includePattern || undefined - } : undefined, + regexFilters: + regexFilters.excludePattern || regexFilters.includePattern + ? { + excludePattern: regexFilters.excludePattern || undefined, + includePattern: regexFilters.includePattern || undefined, + } + : undefined, regexSortPatterns: regexSortPatterns, }; return config; @@ -1034,54 +1037,63 @@ export default function Configure() { {showApiKeyInput && (
- Configure regex patterns to filter streams. These filters will be applied in addition to keyword filters. + Configure regex patterns to filter streams. These filters will + be applied in addition to keyword filters.
- Enter a regex pattern to exclude streams. Streams will be excluded if their filename OR indexers match this 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 - })} + onChange={(e) => + setRegexFilters({ + ...regexFilters, + excludePattern: e.target.value, + }) + } placeholder="Example: \b(0neshot|1XBET)\b" className={styles.input} />
Example patterns:
- - \b(0neshot|1XBET|24xHD)\b (exclude 0neshot, 1XBET, and 24xHD releases)
-
- - ^.*Hi10.*$ (exclude Hi10 profile releases)
+ - \b(0neshot|1XBET|24xHD)\b (exclude 0neshot, 1XBET, and 24xHD
+ releases)
+
- ^.*Hi10.*$ (exclude Hi10 profile releases)
- Enter a regex pattern to include streams. Only streams whose filename or indexers match this pattern will be included. + 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 - })} + onChange={(e) => + setRegexFilters({ + ...regexFilters, + includePattern: e.target.value, + }) + } placeholder="Example: \b(3L|BiZKiT)\b" className={styles.input} />
Example patterns:
-
- - \b(3L|BiZKiT|BLURANiUM)\b (only include 3L, BiZKiT, and BLURANiUM releases)
+
- \b(3L|BiZKiT|BLURANiUM)\b (only include 3L, BiZKiT,
+ and BLURANiUM releases)
- Enter space-separated regex patterns to sort streams. Streams will be sorted based on the order of matching patterns. - Matching files will come first in descending order, and last in ascending order for each pattern. + Enter space-separated regex patterns to sort streams. Streams will + be sorted based on the order of matching patterns. Matching files + will come first in descending order, and last in ascending order + for each pattern.
Example patterns:
-
- - \b(3L|BiZKiT|BLURANiUM)\b \b(FraMeSToR)\b (sort 3L/BiZKiT/BLURANiUM releases first, then FraMeSToR releases)
+
- \b(3L|BiZKiT|BLURANiUM)\b \b(FraMeSToR)\b (sort
+ 3L/BiZKiT/BLURANiUM releases first, then FraMeSToR releases)