feat: implement regex-based stream filtering with API key protection (#155)

This commit is contained in:
Vidhin
2025-05-01 17:20:44 -05:00
committed by GitHub
parent 69247632f9
commit b7e512b3d9
5 changed files with 149 additions and 0 deletions
+28
View File
@@ -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;
});
+34
View File
@@ -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);
}
@@ -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;
}
@@ -215,6 +215,10 @@ export default function Configure() {
);
const [showApiKeyInput, setShowApiKeyInput] = useState<boolean>(false);
const [manifestUrl, setManifestUrl] = useState<string | null>(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() {
</div>
</div>
{showApiKeyInput && (
<div className={styles.section}>
<div>
<h2 style={{ padding: '5px', margin: '0px ' }}>Regex Filtering</h2>
<p style={{ margin: '5px 0 12px 5px' }}>
Configure regex patterns to filter streams. These filters will be applied in addition to keyword filters.
</p>
</div>
<div style={{ marginBottom: '0px' }}>
<div className={styles.section}>
<h3 style={{ margin: '2px 0 2px 0' }}>Exclude Pattern</h3>
<p style={{ margin: '10px 0 10px 0' }}>
Enter a regex pattern to exclude streams. Streams will be excluded if their filename OR indexers match this pattern.
</p>
<input
type="text"
value={regexFilters.excludePattern || ''}
onChange={(e) => setRegexFilters({
...regexFilters,
excludePattern: e.target.value
})}
placeholder="Example: \b(0neshot|1XBET)"
className={styles.input}
/>
<p className={styles.helpText}>
Example patterns:
<br />
- \b(0neshot|1XBET|24xHD) (exclude 0neshot, 1XBET, and 24xHD releases)
<br />
- ^.*Hi10.*$ (exclude Hi10 profile releases)
</p>
</div>
<div className={styles.section} style={{ marginBottom: '0px' }}>
<h3 style={{ margin: '2px 0 2px 0' }}>Include Pattern</h3>
<p style={{ margin: '10px 0 10px 0' }}>
Enter a regex pattern to include streams. Only streams whose filename or indexers match this pattern will be included.
</p>
<input
type="text"
value={regexFilters.includePattern || ''}
onChange={(e) => setRegexFilters({
...regexFilters,
includePattern: e.target.value
})}
placeholder="Example: \b(3L|BiZKiT)"
className={styles.input}
/>
<p className={styles.helpText}>
Example patterns:
<br />
- \b(3L|BiZKiT|BLURANiUM) (only include 3L, BiZKiT, and BLURANiUM releases)
</p>
</div>
</div>
</div>
)}
<div className={styles.section}>
<div className={styles.slidersSetting}>
<div>
+4
View File
@@ -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;