feat: clean results option

This commit is contained in:
Viren070
2025-01-09 21:03:32 +00:00
parent d4d1e352e3
commit 2d76648706
3 changed files with 127 additions and 0 deletions
+96
View File
@@ -121,6 +121,102 @@ export class AIOStreams {
return true;
});
if (this.config.cleanResults) {
const uniqueStreams: ParsedStream[] = [];
// Group streams by normalized filename
const streamsByHash = filteredResults.reduce((acc, stream) => {
const normalizedFilename = stream.filename
? stream.filename.replace(/[^\p{L}\p{N}]/gu, '').toLowerCase()
: undefined;
if (!normalizedFilename) {
uniqueStreams.push(stream);
return acc;
}
acc[normalizedFilename] = acc[normalizedFilename] || [];
acc[normalizedFilename].push(stream);
return acc;
}, {} as Record<string, ParsedStream[]>);
Object.values(streamsByHash).forEach((groupedStreams) => {
if (groupedStreams.length === 1) {
uniqueStreams.push(groupedStreams[0]);
return;
}
//console.log(`==================\nDetermining unique streams for ${groupedStreams[0].filename} from ${groupedStreams.length} total duplicates`);
//console.log(groupedStreams.map(stream => `Addon ID: ${stream.addon.id}, Provider ID: ${stream.provider?.id}, Provider Cached: ${stream.provider?.cached}`));
// Separate streams into categories
const cachedStreams = groupedStreams.filter(stream => stream.provider?.cached);
const uncachedStreams = groupedStreams.filter(stream => stream.provider && !stream.provider.cached);
const noProviderStreams = groupedStreams.filter(stream => !stream.provider);
// Select uncached streams by addon priority (one per provider)
const selectedUncachedStreams = Object.values(uncachedStreams.reduce((acc, stream) => {
acc[stream.provider!.id] = acc[stream.provider!.id] || [];
acc[stream.provider!.id].push(stream);
return acc;
}, {} as Record<string, ParsedStream[]>)).map(providerGroup => {
return providerGroup.sort((a, b) => {
const aIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === a.addon.id
);
const bIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === b.addon.id
);
return aIndex - bIndex;
})[0];
});
//selectedUncachedStreams.forEach(stream => console.log(`Selected uncached stream for provider ${stream.provider!.id}: Addon ID: ${stream.addon.id}`));
// Select cached streams by provider and addon priority
const selectedCachedStream = cachedStreams.sort((a, b) => {
const aProviderIndex = this.config.services.findIndex(service => service.id === a.provider!.id);
const bProviderIndex = this.config.services.findIndex(service => service.id === b.provider!.id);
if (aProviderIndex !== bProviderIndex) {
return aProviderIndex - bProviderIndex;
}
const aAddonIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === a.addon.id
);
const bAddonIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === b.addon.id
);
return aAddonIndex - bAddonIndex;
})[0];
// Select one non-provider stream (highest addon priority)
const selectedNoProviderStream = noProviderStreams.sort((a, b) => {
const aIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === a.addon.id
);
const bIndex = this.config.addons.findIndex(
addon => `${addon.id}-${JSON.stringify(addon.options)}` === b.addon.id
);
return aIndex - bIndex;
})[0];
// Combine selected streams for this group
if (selectedNoProviderStream) {
//console.log(`Selected no provider stream: Addon ID: ${selectedNoProviderStream.addon.id}`);
uniqueStreams.push(selectedNoProviderStream);
}
if (selectedCachedStream) {
//console.log(`Selected cached stream for provider ${selectedCachedStream.provider!.id} from Addon ID: ${selectedCachedStream.addon.id}`);
uniqueStreams.push(selectedCachedStream);
}
uniqueStreams.push(...selectedUncachedStreams);
});
filteredResults = uniqueStreams;
}
// apply config.maxResultsPerResolution
if (this.config.maxResultsPerResolution) {
const streamsByResolution = filteredResults.reduce((acc, stream) => {
acc[stream.resolution] = acc[stream.resolution] || [];
@@ -155,6 +155,7 @@ export default function Configure() {
const [minMovieSize, setMinMovieSize] = useState<number | null>(null);
const [maxEpisodeSize, setMaxEpisodeSize] = useState<number | null>(null);
const [minEpisodeSize, setMinEpisodeSize] = useState<number | null>(null);
const [cleanResults, setCleanResults] = useState<boolean>(false);
const [maxResultsPerResolution, setMaxResultsPerResolution] = useState<number | null>(null);
const [disableButtons, setDisableButtons] = useState<boolean>(false);
@@ -196,6 +197,7 @@ export default function Configure() {
minMovieSize,
maxEpisodeSize,
minEpisodeSize,
cleanResults,
maxResultsPerResolution,
formatter: formatter || 'gdrive',
addons,
@@ -774,6 +776,34 @@ export default function Configure() {
</div>
</div>
</div>
<div className={styles.section}>
<div className={styles.setting}>
<div className={styles.settingDescription}>
<h2 style={{ padding: '5px' }}>Clean Results</h2>
<p style={{ padding: '5px' }}>
Attempt to remove duplicate results. This can be duplicate files within the same addon, or only keeping the highest priority
service for a particular file. It also considers the addon&apos;s priority when removing duplicates. If you had 2 cached results
for the same file from the same service, it would keep the one with the higher addon priority.
</p>
</div>
<div className={styles.settingInput}>
<input
type="checkbox"
checked={cleanResults}
onChange={(e) => setCleanResults(e.target.checked)}
// move to the right
style={{
marginLeft: 'auto',
marginRight: '20px',
width: '25px',
height: '25px',
}}
/>
</div>
</div>
</div>
<div className={styles.section}>
<div className={styles.setting}>
<div className={styles.settingDescription}>
+1
View File
@@ -140,6 +140,7 @@ export interface Config {
minMovieSize: number | null;
maxEpisodeSize: number | null;
minEpisodeSize: number | null;
cleanResults: boolean;
maxResultsPerResolution: number | null;
addons: {
id: string;