added manual matching on anime
This commit is contained in:
@@ -96,7 +96,7 @@ export async function searchInExtension(req: any, reply: FastifyReply) {
|
||||
|
||||
export async function getWatchStream(req: WatchStreamRequest, reply: FastifyReply) {
|
||||
try {
|
||||
const { animeId, episode, server, category, ext, source } = req.query;
|
||||
const { animeId, episode, server, category, ext, source, extensionAnimeId } = req.query;
|
||||
|
||||
const extension = getExtension(ext);
|
||||
if (!extension) return { error: "Extension not found" };
|
||||
@@ -107,7 +107,8 @@ export async function getWatchStream(req: WatchStreamRequest, reply: FastifyRepl
|
||||
animeId,
|
||||
source,
|
||||
server,
|
||||
category
|
||||
category,
|
||||
extensionAnimeId
|
||||
);
|
||||
} catch (err) {
|
||||
const error = err as Error;
|
||||
|
||||
@@ -276,7 +276,6 @@ export async function getAnimeInfoExtension(ext: Extension | null, id: string):
|
||||
if (!ext) return { error: "not found" };
|
||||
|
||||
const extName = ext.constructor.name;
|
||||
|
||||
const cached = await getCachedExtension(extName, id);
|
||||
if (cached) {
|
||||
try {
|
||||
@@ -341,6 +340,7 @@ export async function searchAnimeInExtension(ext: Extension | null, name: string
|
||||
averageScore: m.rating || m.score || null,
|
||||
format: 'ANIME',
|
||||
seasonYear: null,
|
||||
url: m.url,
|
||||
isExtensionResult: true,
|
||||
}));
|
||||
}
|
||||
@@ -387,22 +387,41 @@ export async function searchEpisodesInExtension(ext: Extension | null, name: str
|
||||
startDate: { year: 0, month: 0, day: 0 }
|
||||
}
|
||||
});
|
||||
|
||||
if (!matches || matches.length === 0) return [];
|
||||
|
||||
const res = matches[0];
|
||||
const normalizedQuery = normalize(query);
|
||||
const scored = matches.map(match => {
|
||||
const normalizedTitle = normalize(match.title);
|
||||
const score = similarity(normalizedQuery, normalizedTitle);
|
||||
|
||||
let bonus = 0;
|
||||
if (normalizedTitle === normalizedQuery) {
|
||||
bonus = 0.5;
|
||||
} else if (normalizedTitle.toLowerCase().includes(normalizedQuery.toLowerCase())) {
|
||||
bonus = 0.5;
|
||||
}
|
||||
|
||||
const finalScore = score + bonus;
|
||||
|
||||
return {
|
||||
match,
|
||||
score: finalScore
|
||||
};
|
||||
});
|
||||
|
||||
scored.sort((a, b) => b.score - a.score);
|
||||
const bestMatches = scored.filter(s => s.score > 0.4);
|
||||
|
||||
if (bestMatches.length === 0) return [];
|
||||
const res = bestMatches[0].match;
|
||||
if (!res?.id) return [];
|
||||
|
||||
mediaId = res.id;
|
||||
|
||||
} else {
|
||||
mediaId = query;
|
||||
}
|
||||
|
||||
const chapterList = await ext.findEpisodes(mediaId);
|
||||
|
||||
if (!Array.isArray(chapterList)) return [];
|
||||
|
||||
const result: Episode[] = chapterList.map(ep => ({
|
||||
id: ep.id,
|
||||
number: ep.number,
|
||||
@@ -421,25 +440,24 @@ export async function searchEpisodesInExtension(ext: Extension | null, name: str
|
||||
return [];
|
||||
}
|
||||
|
||||
export async function getStreamData(extension: Extension, episode: string, id: string, source: string, server?: string, category?: string): Promise<StreamData> {
|
||||
export async function getStreamData(extension: Extension, episode: string, id: string, source: string, server?: string, category?: string, extensionAnimeId?: string): Promise<StreamData> {
|
||||
const providerName = extension.constructor.name;
|
||||
|
||||
const cacheKey = `anime:stream:${providerName}:${id}:${episode}:${server || 'default'}:${category || 'sub'}`;
|
||||
if (!extensionAnimeId) {
|
||||
const cached = await getCache(cacheKey);
|
||||
|
||||
const cached = await getCache(cacheKey);
|
||||
if (cached) {
|
||||
const isExpired = Date.now() - cached.created_at > CACHE_TTL_MS;
|
||||
|
||||
if (cached) {
|
||||
const isExpired = Date.now() - cached.created_at > CACHE_TTL_MS;
|
||||
|
||||
if (!isExpired) {
|
||||
console.log(`[${providerName}] Stream data cache hit for episode ${episode}`);
|
||||
try {
|
||||
return JSON.parse(cached.result) as StreamData;
|
||||
} catch (e) {
|
||||
console.error(`[${providerName}] Error parsing cached stream data:`, e);
|
||||
if (!isExpired) {
|
||||
console.log(`[${providerName}] Stream data cache hit for episode ${episode}`);
|
||||
try {
|
||||
return JSON.parse(cached.result) as StreamData;
|
||||
} catch (e) {
|
||||
console.error(`[${providerName}] Error parsing cached stream data:`, e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.log(`[${providerName}] Stream data cache expired for episode ${episode}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -448,13 +466,13 @@ export async function getStreamData(extension: Extension, episode: string, id: s
|
||||
}
|
||||
let episodes;
|
||||
|
||||
if (source === "anilist"){
|
||||
const anime: any = await getAnimeById(id)
|
||||
if (source === "anilist" && !extensionAnimeId) {
|
||||
const anime: any = await getAnimeById(id);
|
||||
episodes = await searchEpisodesInExtension(extension, extension.constructor.name, anime.title.romaji);
|
||||
} else {
|
||||
episodes = await extension.findEpisodes(extensionAnimeId ?? id);
|
||||
}
|
||||
else{
|
||||
episodes = await extension.findEpisodes(id);
|
||||
}
|
||||
|
||||
const targetEp = episodes.find(e => e.number === parseInt(episode));
|
||||
|
||||
if (!targetEp) {
|
||||
@@ -466,4 +484,47 @@ export async function getStreamData(extension: Extension, episode: string, id: s
|
||||
|
||||
await setCache(cacheKey, streamData, CACHE_TTL_MS);
|
||||
return streamData;
|
||||
}
|
||||
|
||||
function similarity(s1: string, s2: string): number {
|
||||
const str1 = normalize(s1);
|
||||
const str2 = normalize(s2);
|
||||
|
||||
const longer = str1.length > str2.length ? str1 : str2;
|
||||
const shorter = str1.length > str2.length ? str2 : str1;
|
||||
|
||||
if (longer.length === 0) return 1.0;
|
||||
|
||||
const editDistance = levenshteinDistance(longer, shorter);
|
||||
return (longer.length - editDistance) / longer.length;
|
||||
}
|
||||
|
||||
function levenshteinDistance(s1: string, s2: string): number {
|
||||
const costs: number[] = [];
|
||||
for (let i = 0; i <= s1.length; i++) {
|
||||
let lastValue = i;
|
||||
for (let j = 0; j <= s2.length; j++) {
|
||||
if (i === 0) {
|
||||
costs[j] = j;
|
||||
} else if (j > 0) {
|
||||
let newValue = costs[j - 1];
|
||||
if (s1.charAt(i - 1) !== s2.charAt(j - 1)) {
|
||||
newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1;
|
||||
}
|
||||
costs[j - 1] = lastValue;
|
||||
lastValue = newValue;
|
||||
}
|
||||
}
|
||||
if (i > 0) costs[s2.length] = lastValue;
|
||||
}
|
||||
return costs[s2.length];
|
||||
}
|
||||
|
||||
function normalize(str: string): string {
|
||||
return str
|
||||
.toLowerCase()
|
||||
.replace(/'/g, "'") // decodificar entidades HTML
|
||||
.replace(/[^\w\s]/g, ' ') // convertir puntuación a espacios
|
||||
.replace(/\s+/g, ' ') // normalizar espacios
|
||||
.trim();
|
||||
}
|
||||
@@ -62,6 +62,7 @@ export interface ExtensionSearchOptions {
|
||||
}
|
||||
|
||||
export interface ExtensionSearchResult {
|
||||
url: string;
|
||||
format: string;
|
||||
headers: any;
|
||||
id: string;
|
||||
@@ -158,6 +159,7 @@ export interface WatchStreamQuery {
|
||||
server?: string;
|
||||
category?: string;
|
||||
ext: string;
|
||||
extensionAnimeId?: string;
|
||||
}
|
||||
|
||||
export interface BookParams {
|
||||
|
||||
Reference in New Issue
Block a user