better auto matching on anime and books when anilist is the source
This commit is contained in:
@@ -276,7 +276,6 @@ export async function getAnimeInfoExtension(ext: Extension | null, id: string):
|
|||||||
if (!ext) return { error: "not found" };
|
if (!ext) return { error: "not found" };
|
||||||
|
|
||||||
const extName = ext.constructor.name;
|
const extName = ext.constructor.name;
|
||||||
|
|
||||||
const cached = await getCachedExtension(extName, id);
|
const cached = await getCachedExtension(extName, id);
|
||||||
if (cached) {
|
if (cached) {
|
||||||
try {
|
try {
|
||||||
@@ -387,22 +386,41 @@ export async function searchEpisodesInExtension(ext: Extension | null, name: str
|
|||||||
startDate: { year: 0, month: 0, day: 0 }
|
startDate: { year: 0, month: 0, day: 0 }
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!matches || matches.length === 0) return [];
|
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 [];
|
if (!res?.id) return [];
|
||||||
|
|
||||||
mediaId = res.id;
|
mediaId = res.id;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
mediaId = query;
|
mediaId = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chapterList = await ext.findEpisodes(mediaId);
|
const chapterList = await ext.findEpisodes(mediaId);
|
||||||
|
|
||||||
if (!Array.isArray(chapterList)) return [];
|
if (!Array.isArray(chapterList)) return [];
|
||||||
|
|
||||||
const result: Episode[] = chapterList.map(ep => ({
|
const result: Episode[] = chapterList.map(ep => ({
|
||||||
id: ep.id,
|
id: ep.id,
|
||||||
number: ep.number,
|
number: ep.number,
|
||||||
@@ -467,3 +485,46 @@ export async function getStreamData(extension: Extension, episode: string, id: s
|
|||||||
await setCache(cacheKey, streamData, CACHE_TTL_MS);
|
await setCache(cacheKey, streamData, CACHE_TTL_MS);
|
||||||
return streamData;
|
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();
|
||||||
|
}
|
||||||
@@ -394,11 +394,24 @@ async function searchChaptersInExtension(ext: Extension, name: string, searchTit
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const best = matches?.[0];
|
if (!matches?.length) return [];
|
||||||
|
|
||||||
if (!best) { return [] }
|
const nq = normalize(searchTitle);
|
||||||
|
|
||||||
mediaId = best.id;
|
const scored = matches.map(m => {
|
||||||
|
const nt = normalize(m.title);
|
||||||
|
let score = similarity(nq, nt);
|
||||||
|
|
||||||
|
if (nt === nq || nt.includes(nq)) score += 0.5;
|
||||||
|
|
||||||
|
return { m, score };
|
||||||
|
});
|
||||||
|
|
||||||
|
scored.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
if (scored[0].score < 0.4) return [];
|
||||||
|
|
||||||
|
mediaId = scored[0].m.id;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const match = await ext.getMetadata(searchTitle);
|
const match = await ext.getMetadata(searchTitle);
|
||||||
@@ -549,3 +562,46 @@ export async function getChapterContent(bookId: string, chapterId: string, provi
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user