mirror of
https://github.com/Viren070/AIOStreams.git
synced 2025-12-01 23:14:04 +01:00
feat(seadex): add release group fallback matching (#521)
Co-authored-by: Viren070 <viren070@protonmail.com>
This commit is contained in:
@@ -82,9 +82,7 @@ const SizeFilter = z.object({
|
|||||||
// max: z.number().min(1).optional(),
|
// max: z.number().min(1).optional(),
|
||||||
// })
|
// })
|
||||||
.optional(),
|
.optional(),
|
||||||
anime: z
|
anime: z.tuple([z.number().min(0), z.number().min(0)]).optional(),
|
||||||
.tuple([z.number().min(0), z.number().min(0)])
|
|
||||||
.optional(),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const SizeFilterOptions = z.object({
|
const SizeFilterOptions = z.object({
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ export abstract class BaseFormatter {
|
|||||||
network: stream.parsedFile?.network || null,
|
network: stream.parsedFile?.network || null,
|
||||||
container: stream.parsedFile?.container || null,
|
container: stream.parsedFile?.container || null,
|
||||||
extension: stream.parsedFile?.extension || null,
|
extension: stream.parsedFile?.extension || null,
|
||||||
seadex: (stream.seadex?.isSeadex && !stream.seadex?.isBest) ?? false,
|
seadex: stream.seadex?.isSeadex ?? false,
|
||||||
seadexBest: stream.seadex?.isBest ?? false,
|
seadexBest: stream.seadex?.isBest ?? false,
|
||||||
},
|
},
|
||||||
addon: {
|
addon: {
|
||||||
|
|||||||
@@ -507,7 +507,7 @@ export abstract class StreamExpressionEngine {
|
|||||||
return streams.filter((stream) => stream.seadex?.isBest === true);
|
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);
|
return streams.filter((stream) => stream.seadex?.isSeadex === true);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -40,7 +40,8 @@ class StreamPrecomputer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Precompute SeaDex status for anime streams
|
* 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(
|
private async precomputeSeaDex(
|
||||||
streams: ParsedStream[],
|
streams: ParsedStream[],
|
||||||
@@ -80,39 +81,62 @@ class StreamPrecomputer {
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
seadexResult.bestHashes.size === 0 &&
|
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}`);
|
logger.debug(`No SeaDex releases found for AniList ID ${anilistId}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let seadexBestCount = 0;
|
let seadexBestCount = 0;
|
||||||
let seadexCount = 0;
|
let seadexCount = 0;
|
||||||
|
let seadexGroupFallbackCount = 0;
|
||||||
|
|
||||||
for (const stream of streams) {
|
for (const stream of streams) {
|
||||||
const infoHash = stream.torrent?.infoHash?.toLowerCase();
|
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);
|
// Fallback to release group matching
|
||||||
const isSeadex = seadexResult.allHashes.has(infoHash);
|
const releaseGroup = stream.parsedFile?.releaseGroup?.toLowerCase();
|
||||||
|
if (releaseGroup) {
|
||||||
|
const isBestGroup = seadexResult.bestGroups.has(releaseGroup);
|
||||||
|
const isSeadexGroup = seadexResult.allGroups.has(releaseGroup);
|
||||||
|
|
||||||
if (isSeadex) {
|
if (isBestGroup || isSeadexGroup) {
|
||||||
stream.seadex = {
|
stream.seadex = {
|
||||||
isBest,
|
isBest: isBestGroup,
|
||||||
isSeadex: true,
|
isSeadex: true,
|
||||||
};
|
};
|
||||||
|
if (isBestGroup) {
|
||||||
if (isBest) {
|
seadexBestCount++;
|
||||||
seadexBestCount++;
|
}
|
||||||
|
seadexCount++;
|
||||||
|
seadexGroupFallbackCount++;
|
||||||
}
|
}
|
||||||
seadexCount++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (seadexCount > 0) {
|
if (seadexCount > 0) {
|
||||||
logger.info(
|
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}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1082,9 +1082,9 @@ export const SORT_CRITERIA_DETAILS: Record<
|
|||||||
description:
|
description:
|
||||||
'Whether the stream is a SeaDex release (curated best anime releases from releases.moe)',
|
'Whether the stream is a SeaDex release (curated best anime releases from releases.moe)',
|
||||||
ascendingDescription:
|
ascendingDescription:
|
||||||
'Non-SeaDex releases are preferred over SeaDex releases',
|
'Streams that are not listed on SeaDex are preferred',
|
||||||
descendingDescription:
|
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;
|
} as const;
|
||||||
|
|
||||||
|
|||||||
@@ -19,11 +19,19 @@ const seadexCache = Cache.getInstance<string, SeaDexResult>(
|
|||||||
export interface SeaDexResult {
|
export interface SeaDexResult {
|
||||||
bestHashes: Set<string>;
|
bestHashes: Set<string>;
|
||||||
allHashes: Set<string>;
|
allHashes: Set<string>;
|
||||||
|
bestGroups: Set<string>;
|
||||||
|
allGroups: Set<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SeaDexTagResult {
|
||||||
|
isBest: boolean;
|
||||||
|
isSeadex: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeaDexTorrent {
|
interface SeaDexTorrent {
|
||||||
infoHash: string;
|
infoHash: string;
|
||||||
isBest: boolean;
|
isBest: boolean;
|
||||||
|
releaseGroup?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SeaDexEntry {
|
interface SeaDexEntry {
|
||||||
@@ -77,7 +85,12 @@ export class SeaDexApi {
|
|||||||
logger.warn(
|
logger.warn(
|
||||||
`SeaDex API returned ${response.status} for AniList ID ${anilistId}`
|
`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;
|
const data = (await response.json()) as SeaDexResponse;
|
||||||
@@ -88,6 +101,8 @@ export class SeaDexApi {
|
|||||||
const emptyResult: SeaDexResult = {
|
const emptyResult: SeaDexResult = {
|
||||||
bestHashes: new Set(),
|
bestHashes: new Set(),
|
||||||
allHashes: new Set(),
|
allHashes: new Set(),
|
||||||
|
bestGroups: new Set(),
|
||||||
|
allGroups: new Set(),
|
||||||
};
|
};
|
||||||
await seadexCache.set(cacheKey, emptyResult, SEADEX_CACHE_TTL);
|
await seadexCache.set(cacheKey, emptyResult, SEADEX_CACHE_TTL);
|
||||||
return emptyResult;
|
return emptyResult;
|
||||||
@@ -95,6 +110,8 @@ export class SeaDexApi {
|
|||||||
|
|
||||||
const bestHashes = new Set<string>();
|
const bestHashes = new Set<string>();
|
||||||
const allHashes = new Set<string>();
|
const allHashes = new Set<string>();
|
||||||
|
const bestGroups = new Set<string>();
|
||||||
|
const allGroups = new Set<string>();
|
||||||
|
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
const trsArray = item.expand?.trs;
|
const trsArray = item.expand?.trs;
|
||||||
@@ -114,13 +131,29 @@ export class SeaDexApi {
|
|||||||
bestHashes.add(infoHash);
|
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(
|
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);
|
await seadexCache.set(cacheKey, result, SEADEX_CACHE_TTL);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -131,6 +164,8 @@ export class SeaDexApi {
|
|||||||
return {
|
return {
|
||||||
bestHashes: new Set(),
|
bestHashes: new Set(),
|
||||||
allHashes: new Set(),
|
allHashes: new Set(),
|
||||||
|
bestGroups: new Set(),
|
||||||
|
allGroups: new Set(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user