feat(seadex): add release group fallback matching (#521)

Co-authored-by: Viren070 <viren070@protonmail.com>
This commit is contained in:
cats
2025-12-01 18:14:39 +03:00
committed by GitHub
parent f7c1124ebc
commit 7ed51bc6f0
6 changed files with 83 additions and 26 deletions
+1 -3
View File
@@ -82,9 +82,7 @@ const SizeFilter = z.object({
// max: z.number().min(1).optional(),
// })
.optional(),
anime: z
.tuple([z.number().min(0), z.number().min(0)])
.optional(),
anime: z.tuple([z.number().min(0), z.number().min(0)]).optional(),
});
const SizeFilterOptions = z.object({
+1 -1
View File
@@ -326,7 +326,7 @@ export abstract class BaseFormatter {
network: stream.parsedFile?.network || null,
container: stream.parsedFile?.container || null,
extension: stream.parsedFile?.extension || null,
seadex: (stream.seadex?.isSeadex && !stream.seadex?.isBest) ?? false,
seadex: stream.seadex?.isSeadex ?? false,
seadexBest: stream.seadex?.isBest ?? false,
},
addon: {
+1 -1
View File
@@ -507,7 +507,7 @@ export abstract class StreamExpressionEngine {
return streams.filter((stream) => stream.seadex?.isBest === true);
}
// Return all SeaDex releases (best or regular)
// Return all SeaDex releases (includes group fallback matches)
return streams.filter((stream) => stream.seadex?.isSeadex === true);
};
+40 -16
View File
@@ -40,7 +40,8 @@ class StreamPrecomputer {
/**
* Precompute SeaDex status for anime streams
* Tags streams with seadex.isBest and seadex.isSeadex based on infoHash matching
* Tags streams with seadex.isBest and seadex.isSeadex
* First tries to match by infoHash, then falls back to release group matching
*/
private async precomputeSeaDex(
streams: ParsedStream[],
@@ -80,39 +81,62 @@ class StreamPrecomputer {
if (
seadexResult.bestHashes.size === 0 &&
seadexResult.allHashes.size === 0
seadexResult.allHashes.size === 0 &&
seadexResult.bestGroups.size === 0 &&
seadexResult.allGroups.size === 0
) {
logger.debug(`No SeaDex releases found for AniList ID ${anilistId}`);
return;
}
let seadexBestCount = 0;
let seadexCount = 0;
let seadexGroupFallbackCount = 0;
for (const stream of streams) {
const infoHash = stream.torrent?.infoHash?.toLowerCase();
if (!infoHash) {
continue;
// First try hash matching
if (infoHash) {
const isBest = seadexResult.bestHashes.has(infoHash);
const isSeadex = seadexResult.allHashes.has(infoHash);
if (isSeadex) {
stream.seadex = {
isBest,
isSeadex: true,
};
if (isBest) {
seadexBestCount++;
}
seadexCount++;
continue;
}
}
const isBest = seadexResult.bestHashes.has(infoHash);
const isSeadex = seadexResult.allHashes.has(infoHash);
// Fallback to release group matching
const releaseGroup = stream.parsedFile?.releaseGroup?.toLowerCase();
if (releaseGroup) {
const isBestGroup = seadexResult.bestGroups.has(releaseGroup);
const isSeadexGroup = seadexResult.allGroups.has(releaseGroup);
if (isSeadex) {
stream.seadex = {
isBest,
isSeadex: true,
};
if (isBest) {
seadexBestCount++;
if (isBestGroup || isSeadexGroup) {
stream.seadex = {
isBest: isBestGroup,
isSeadex: true,
};
if (isBestGroup) {
seadexBestCount++;
}
seadexCount++;
seadexGroupFallbackCount++;
}
seadexCount++;
}
}
if (seadexCount > 0) {
logger.info(
`Tagged ${seadexCount} streams as SeaDex releases (${seadexBestCount} best) for AniList ID ${anilistId}`
`Tagged ${seadexCount} streams as SeaDex releases (${seadexBestCount} best, ${seadexGroupFallbackCount} via group fallback) for AniList ID ${anilistId}`
);
}
}
+2 -2
View File
@@ -1082,9 +1082,9 @@ export const SORT_CRITERIA_DETAILS: Record<
description:
'Whether the stream is a SeaDex release (curated best anime releases from releases.moe)',
ascendingDescription:
'Non-SeaDex releases are preferred over SeaDex releases',
'Streams that are not listed on SeaDex are preferred',
descendingDescription:
'SeaDex Best releases are preferred, then other SeaDex releases, then non-SeaDex releases',
'Streams that are marked as the Best release on SeaDex are preferred, followed by the Alternative release',
},
} as const;
+38 -3
View File
@@ -19,11 +19,19 @@ const seadexCache = Cache.getInstance<string, SeaDexResult>(
export interface SeaDexResult {
bestHashes: Set<string>;
allHashes: Set<string>;
bestGroups: Set<string>;
allGroups: Set<string>;
}
export interface SeaDexTagResult {
isBest: boolean;
isSeadex: boolean;
}
interface SeaDexTorrent {
infoHash: string;
isBest: boolean;
releaseGroup?: string;
}
interface SeaDexEntry {
@@ -77,7 +85,12 @@ export class SeaDexApi {
logger.warn(
`SeaDex API returned ${response.status} for AniList ID ${anilistId}`
);
return { bestHashes: new Set(), allHashes: new Set() };
return {
bestHashes: new Set(),
allHashes: new Set(),
bestGroups: new Set(),
allGroups: new Set(),
};
}
const data = (await response.json()) as SeaDexResponse;
@@ -88,6 +101,8 @@ export class SeaDexApi {
const emptyResult: SeaDexResult = {
bestHashes: new Set(),
allHashes: new Set(),
bestGroups: new Set(),
allGroups: new Set(),
};
await seadexCache.set(cacheKey, emptyResult, SEADEX_CACHE_TTL);
return emptyResult;
@@ -95,6 +110,8 @@ export class SeaDexApi {
const bestHashes = new Set<string>();
const allHashes = new Set<string>();
const bestGroups = new Set<string>();
const allGroups = new Set<string>();
for (const item of items) {
const trsArray = item.expand?.trs;
@@ -114,13 +131,29 @@ export class SeaDexApi {
bestHashes.add(infoHash);
}
}
// Collect release groups
for (const torrent of trsArray) {
const releaseGroup = torrent.releaseGroup?.toLowerCase();
if (!releaseGroup) continue;
allGroups.add(releaseGroup);
if (torrent.isBest) {
bestGroups.add(releaseGroup);
}
}
}
logger.info(
`Found ${bestHashes.size} best and ${allHashes.size} total SeaDex hashes for AniList ID ${anilistId}`
`Found ${bestHashes.size} best hashes, ${allHashes.size} total hashes, ${bestGroups.size} best groups, ${allGroups.size} total groups for AniList ID ${anilistId}`
);
const result: SeaDexResult = { bestHashes, allHashes };
const result: SeaDexResult = {
bestHashes,
allHashes,
bestGroups,
allGroups,
};
await seadexCache.set(cacheKey, result, SEADEX_CACHE_TTL);
return result;
} catch (error) {
@@ -131,6 +164,8 @@ export class SeaDexApi {
return {
bestHashes: new Set(),
allHashes: new Set(),
bestGroups: new Set(),
allGroups: new Set(),
};
}
}