recognize: название из метабазы санитизируется перед попаданием в план
- чистка стоит на каждой точке входа значения метабазы в план — сборка матча, копия кандидата для ревью, набор закреплённых значений источника и его чтение: гарантия, поставленная только на запись, обходится данными, сохранёнными прежними версиями - название, непригодное как имя каталога (пустое или без единой буквы и цифры), не подставляется — раздача уходит в review с названной причиной - гейт подтверждения матча не сдвинут: сравнение с планом идёт по значениям провайдера, чистится только копия, уходящая дальше
This commit is contained in:
@@ -4,6 +4,8 @@ import (
|
||||
"log/slog"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/metadata"
|
||||
)
|
||||
|
||||
// homoglyphPairs — курируемая таблица визуально неотличимых кирилло-латинских
|
||||
@@ -34,17 +36,49 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
// sanitizeTitle чистит человекочитаемое поле плана как недоверенный вывод LLM:
|
||||
// SanitizeTitle чистит человекочитаемое поле плана как недоверенный вывод LLM:
|
||||
// (1) убирает управляющие и zero-width символы; (2) сводит пробелы к одиночным и
|
||||
// обрезает края; (3) сворачивает homoglyph-двойники. Порядок важен: strip делаем
|
||||
// до collapse, чтобы удаление zero-width не оставляло сдвоенных пробелов.
|
||||
func sanitizeTitle(s string) string {
|
||||
func SanitizeTitle(s string) string {
|
||||
s = stripControl(s)
|
||||
s = collapseSpaces(s)
|
||||
s = foldHomoglyphs(s)
|
||||
return s
|
||||
}
|
||||
|
||||
// UsableTitle отвечает, годится ли название как компонент пути к файлу. Годится
|
||||
// то, в чём после санитайзинга осталась хотя бы одна буква или цифра: layout
|
||||
// снимает разделители и обрезает края от точек и пробелов, поэтому «.», «...»,
|
||||
// «-» и прочая голая пунктуация схлопываются там в пустое имя каталога, а
|
||||
// ведущая точка вдобавок делает каталог скрытым для Jellyfin.
|
||||
//
|
||||
// Проверять этим предикатом надо УЖЕ санитизированное значение: невидимые
|
||||
// символы буквами не являются, но и не мешают — их снимает SanitizeTitle.
|
||||
// Экспортирована вместе с SanitizeTitle, потому что тот же вопрос задаёт worker
|
||||
// при закреплении выбранного человеком источника: правило одно, домов у него
|
||||
// быть не должно.
|
||||
func UsableTitle(s string) bool {
|
||||
for _, r := range s {
|
||||
if unicode.IsLetter(r) || unicode.IsDigit(r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// sanitizeCandidate чистит человекочитаемые названия кандидата метабазы. Зовётся
|
||||
// на КОПИИ кандидата в момент, когда он уходит из сверки дальше — в список для
|
||||
// review, в хранилище, на экран и в закрепляемое человеком значение. Значения,
|
||||
// по которым ищется сильный матч, при этом не меняются: сравнение обязано идти
|
||||
// по тому, что отдал провайдер (см. metadata-match), иначе кандидат, отличающийся
|
||||
// от плана невидимым символом, начал бы совпадать там, где прежде уходил в review.
|
||||
func sanitizeCandidate(c metadata.Candidate) metadata.Candidate {
|
||||
c.Title = SanitizeTitle(c.Title)
|
||||
c.OriginalTitle = SanitizeTitle(c.OriginalTitle)
|
||||
return c
|
||||
}
|
||||
|
||||
// sanitizePlan применяет санитайзинг к человекочитаемым полям плана. files[].src
|
||||
// НЕ трогаем: они обязаны байт-в-байт совпадать с реальными файлами торрента, и
|
||||
// homoglyph там — настоящий mismatch (отклоняется валидацией, уходит в review),
|
||||
@@ -57,7 +91,7 @@ func sanitizePlan(p *Plan, log *slog.Logger) {
|
||||
}
|
||||
|
||||
func sanitizeField(v, field string, log *slog.Logger) string {
|
||||
clean := sanitizeTitle(v)
|
||||
clean := SanitizeTitle(v)
|
||||
if clean != v && log != nil {
|
||||
log.Debug("recognition plan field sanitized", "field", field, "before", v, "after", clean)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user