Normalize path search separators

This commit is contained in:
2026-05-23 17:28:47 +00:00
parent 169eb122b5
commit 0b5c47f4d1
+113 -2
View File
@@ -757,6 +757,15 @@ function normalizeSearchText(value: string): string {
.replace(/\s+/g, ' ');
}
function normalizePathSearchText(value: string): string {
return value
.toLowerCase()
.replace(/[\\/]+/g, '/')
.replace(/[^a-z0-9/]+/g, ' ')
.trim()
.replace(/\s+/g, ' ');
}
function getTermMatchScore(term: string, field: string): number {
const index = field.indexOf(term);
if (index < 0) return 0;
@@ -771,6 +780,20 @@ function getTermMatchScore(term: string, field: string): number {
return 30;
}
function getPathTermMatchScore(term: string, field: string): number {
const index = field.indexOf(term);
if (index < 0) return 0;
if (index === 0) return 100;
const charBefore = field[index - 1];
if (/\s|\//.test(charBefore)) return 80;
if (index < field.length / 2) return 50;
return 30;
}
function getRelevanceScore(query: string, field: string): number {
const normalizedField = normalizeSearchText(field);
const normalizedQuery = normalizeSearchText(query);
@@ -820,6 +843,54 @@ function getRelevanceScore(query: string, field: string): number {
return bestScore;
}
function getPathRelevanceScore(query: string, field: string): number {
const normalizedField = normalizePathSearchText(field);
const normalizedQuery = normalizePathSearchText(query);
if (!normalizedField || !normalizedQuery) return 0;
let bestScore = 0;
const exactIndex = normalizedField.indexOf(normalizedQuery);
if (exactIndex >= 0) {
if (exactIndex === 0) {
bestScore = 110;
} else {
const charBefore = normalizedField[exactIndex - 1];
if (/\s|\//.test(charBefore)) {
bestScore = 95;
} else if (exactIndex < normalizedField.length / 2) {
bestScore = 75;
} else {
bestScore = 60;
}
}
}
const terms = normalizedQuery.split(' ');
if (terms.length > 1) {
let matchedTerms = 0;
let termScoreTotal = 0;
for (const term of terms) {
const termScore = getPathTermMatchScore(term, normalizedField);
if (termScore > 0) {
matchedTerms += 1;
termScoreTotal += termScore;
}
}
if (matchedTerms > 0) {
const coverage = matchedTerms / terms.length;
const averageScore = termScoreTotal / matchedTerms;
const combinedScore = Math.round(averageScore * (0.6 + (coverage * 0.4)));
if (combinedScore > bestScore) bestScore = combinedScore;
}
}
return bestScore;
}
// Get best relevance score from multiple fields
function getBestScore(query: string, ...fields: (string | null | undefined)[]): number {
let bestScore = 0;
@@ -832,6 +903,40 @@ function getBestScore(query: string, ...fields: (string | null | undefined)[]):
return bestScore;
}
function getMoviePathScore(movie: Movie, query: string): number {
const torrentFields: (string | null | undefined)[] = [];
for (const torrent of Object.values(movie.torrents || {})) {
torrentFields.push(torrent.title, torrent.playable_file);
}
let bestScore = 0;
for (const field of torrentFields) {
if (!field) continue;
const score = getPathRelevanceScore(query, field);
if (score > bestScore) bestScore = score;
}
return bestScore;
}
function getSeriesPathScore(series: Series, query: string): number {
const torrentFields: (string | null | undefined)[] = [];
for (const season of series.seasons || []) {
for (const episode of season.episodes || []) {
for (const torrent of Object.values(episode.torrents || {})) {
torrentFields.push(torrent.title, torrent.playable_file);
}
}
}
let bestScore = 0;
for (const field of torrentFields) {
if (!field) continue;
const score = getPathRelevanceScore(query, field);
if (score > bestScore) bestScore = score;
}
return bestScore;
}
// Check if any person name matches the query - returns matched people with roles
interface PersonMatch {
name: string;
@@ -1000,12 +1105,15 @@ function performSearch(query: string) {
}
// Other metadata matches -> Other category
const otherScore = getBestScore(query,
const otherScore = Math.max(
getBestScore(query,
movie.info?.genres?.join(' '),
movie.info?.keywords?.join(' '),
movie.info?.overview,
movie.info?.tagline,
movie.info?.similar?.map(s => s.title).join(' ')
),
getMoviePathScore(movie, query)
);
if (otherScore > 0) {
allScored.push({
@@ -1090,13 +1198,16 @@ function performSearch(query: string) {
// Other metadata matches -> Other category
if (!processedIds.has(series.id)) {
const otherScore = getBestScore(query,
const otherScore = Math.max(
getBestScore(query,
series.info?.genres?.join(' '),
series.info?.keywords?.join(' '),
series.info?.overview,
series.info?.tagline,
series.info?.similar?.map(s => s.title).join(' '),
series.info?.networks?.join(' ')
),
getSeriesPathScore(series, query)
);
if (otherScore > 0) {
allScored.push({