fix: use string matching to map to correct title in Aniwatch
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
import { findBestMatchingTitle } from "~/libs/findBestMatchingTitle";
|
||||||
|
|
||||||
import { Episode, type EpisodesResponse } from "./episode";
|
import { Episode, type EpisodesResponse } from "./episode";
|
||||||
|
|
||||||
export async function getEpisodesFromAniwatch(
|
export async function getEpisodesFromAniwatch(
|
||||||
@@ -8,9 +10,12 @@ export async function getEpisodesFromAniwatch(
|
|||||||
.then(({ fetchTitleFromAnilist }) =>
|
.then(({ fetchTitleFromAnilist }) =>
|
||||||
fetchTitleFromAnilist(aniListId, undefined),
|
fetchTitleFromAnilist(aniListId, undefined),
|
||||||
)
|
)
|
||||||
.then((title) => title?.title?.english ?? title?.title?.userPreferred);
|
.then((title) => ({
|
||||||
|
english: title?.title?.english,
|
||||||
|
userPreferred: title?.title?.userPreferred,
|
||||||
|
}));
|
||||||
|
|
||||||
if (!animeTitle) {
|
if (!animeTitle.english && !animeTitle.userPreferred) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +60,23 @@ export async function getEpisodesFromAniwatch(
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAniwatchId(animeTitle: string): Promise<string | undefined> {
|
function getAniwatchId(
|
||||||
|
animeTitle: Partial<{ english: string; userPreferred: string }>,
|
||||||
|
): Promise<string | undefined> {
|
||||||
return fetch(
|
return fetch(
|
||||||
`https://aniwatch.up.railway.app/anime/search?q=${encodeURIComponent(animeTitle)}`,
|
`https://aniwatch.up.railway.app/anime/search?q=${encodeURIComponent(animeTitle.english ?? animeTitle.userPreferred!)}`,
|
||||||
)
|
)
|
||||||
.then((res) => res.json<AniwatchSearchResponse>())
|
.then((res) => res.json<AniwatchSearchResponse>())
|
||||||
.then(({ animes }) => animes[0]?.id);
|
.then(({ animes }) => {
|
||||||
|
const bestMatchingTitle = findBestMatchingTitle(
|
||||||
|
animeTitle,
|
||||||
|
animes.map((anime) => ({
|
||||||
|
english: anime.name,
|
||||||
|
userPreferred: anime.jname,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
return animes.find((anime) => anime.name === bestMatchingTitle)?.id;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AniwatchEpisodesResponse {
|
export interface AniwatchEpisodesResponse {
|
||||||
|
|||||||
113
src/libs/findBestMatchingTitle.ts
Normal file
113
src/libs/findBestMatchingTitle.ts
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
function findBestMatch(mainString: string, targets: string[]): string | null {
|
||||||
|
if (targets.length === 0) return null;
|
||||||
|
|
||||||
|
let bestMatch = targets[0];
|
||||||
|
let highestScore = stringMatchingAlgorithm(mainString, bestMatch);
|
||||||
|
|
||||||
|
for (let i = 1; i < targets.length; i++) {
|
||||||
|
const currentScore = stringMatchingAlgorithm(mainString, targets[i]);
|
||||||
|
if (currentScore > highestScore) {
|
||||||
|
highestScore = currentScore;
|
||||||
|
bestMatch = targets[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestMatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Title = {
|
||||||
|
english: string;
|
||||||
|
userPreferred: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const findBestMatchingTitle = (
|
||||||
|
title: Partial<Title>,
|
||||||
|
titles: Partial<Title>[],
|
||||||
|
): string | null => {
|
||||||
|
const { english, userPreferred } = title;
|
||||||
|
|
||||||
|
const userPreferredBestMatch = userPreferred
|
||||||
|
? findBestMatch(
|
||||||
|
userPreferred,
|
||||||
|
titles
|
||||||
|
.map((title) => title.userPreferred)
|
||||||
|
.filter((title) => title !== undefined),
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
const englishBestMatch = english
|
||||||
|
? findBestMatch(
|
||||||
|
english,
|
||||||
|
titles
|
||||||
|
.map((title) => title.english)
|
||||||
|
.filter((title) => title !== undefined),
|
||||||
|
)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const userPreferredScore = userPreferredBestMatch
|
||||||
|
? stringMatchingAlgorithm(userPreferred!, userPreferredBestMatch)
|
||||||
|
: 0;
|
||||||
|
const englishScore = englishBestMatch
|
||||||
|
? stringMatchingAlgorithm(english!, englishBestMatch)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
console.log(title.english, englishScore);
|
||||||
|
console.log(title.userPreferred, userPreferredScore);
|
||||||
|
if (userPreferredScore >= englishScore) {
|
||||||
|
return userPreferredBestMatch;
|
||||||
|
} else {
|
||||||
|
return englishBestMatch;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function stringMatchingAlgorithm(s1: string, s2: string): number {
|
||||||
|
// current implementation is the Jaro-Winkler algorithm
|
||||||
|
// copied from https://discord.com/channels/987492554486452315/1273988465222090783/1273988465222090783
|
||||||
|
const m = s1.length;
|
||||||
|
const n = s2.length;
|
||||||
|
|
||||||
|
if (m === 0 && n === 0) return 1.0;
|
||||||
|
if (m === 0 || n === 0) return 0.0;
|
||||||
|
|
||||||
|
const matchDistance = Math.floor(Math.max(m, n) / 2) - 1;
|
||||||
|
const s1Matches = new Array(m).fill(false);
|
||||||
|
const s2Matches = new Array(n).fill(false);
|
||||||
|
|
||||||
|
let matches = 0;
|
||||||
|
let transpositions = 0;
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
const start = Math.max(0, i - matchDistance);
|
||||||
|
const end = Math.min(n - 1, i + matchDistance);
|
||||||
|
|
||||||
|
for (let j = start; j <= end; j++) {
|
||||||
|
if (s2Matches[j]) continue;
|
||||||
|
if (s1[i] !== s2[j]) continue;
|
||||||
|
s1Matches[i] = true;
|
||||||
|
s2Matches[j] = true;
|
||||||
|
matches++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (matches === 0) return 0.0;
|
||||||
|
|
||||||
|
let k = 0;
|
||||||
|
for (let i = 0; i < m; i++) {
|
||||||
|
if (!s1Matches[i]) continue;
|
||||||
|
while (!s2Matches[k]) k++;
|
||||||
|
if (s1[i] !== s2[k]) transpositions++;
|
||||||
|
k++;
|
||||||
|
}
|
||||||
|
|
||||||
|
transpositions /= 2;
|
||||||
|
|
||||||
|
const jaro =
|
||||||
|
(matches / m + matches / n + (matches - transpositions) / matches) / 3;
|
||||||
|
|
||||||
|
const prefix = Math.min(
|
||||||
|
4,
|
||||||
|
[...s1].findIndex((ch, i) => s1[i] !== s2[i]),
|
||||||
|
);
|
||||||
|
const p = 0.1;
|
||||||
|
|
||||||
|
return jaro + prefix * p * (1 - jaro);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user