Merge pull request 'fix: use stable *arr IDs for matching before fragile title fallback' (#21) from fix-arr-matching into develop-merge
Reviewed-on: #21
This commit was merged in pull request #21.
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
// Copyright (c) 2026 Gordon Bolton. MIT License.
|
||||||
const { logToFile } = require('./logger');
|
const { logToFile } = require('./logger');
|
||||||
|
const cache = require('./cache');
|
||||||
const {
|
const {
|
||||||
getSonarrInstances,
|
getSonarrInstances,
|
||||||
getRadarrInstances
|
getRadarrInstances
|
||||||
@@ -305,4 +306,100 @@ const arrRetrieverRegistry = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replicate Ombi's StringHelper.SanitizeTagLabel: lowercase, replace non-alphanumeric with hyphen, collapse, trim
|
||||||
|
*/
|
||||||
|
function sanitizeTagLabel(input) {
|
||||||
|
if (!input) return '';
|
||||||
|
return input.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a tag matches the username: exact match first, then sanitized match
|
||||||
|
*/
|
||||||
|
function tagMatchesUser(tag, username) {
|
||||||
|
if (!tag || !username) return false;
|
||||||
|
const tagLower = tag.toLowerCase();
|
||||||
|
const usernameLower = username.toLowerCase();
|
||||||
|
// Exact match
|
||||||
|
if (tagLower === usernameLower) return true;
|
||||||
|
// Sanitized match
|
||||||
|
if (tagLower === sanitizeTagLabel(usernameLower)) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matching / aggregation helper function to compare a download item and an *arr item.
|
||||||
|
*/
|
||||||
|
function matchDownload(download, arrItem, username, tagMap) {
|
||||||
|
if (!download || !arrItem) return false;
|
||||||
|
|
||||||
|
// 1. First attempt an exact ID match using the stable fields that exist in the fetched data
|
||||||
|
if (download.arrInfo) {
|
||||||
|
// Sonarr stable IDs
|
||||||
|
if (download.arrInfo.episodeFileId !== undefined && arrItem.episodeFileId !== undefined) {
|
||||||
|
if (download.arrInfo.episodeFileId === arrItem.episodeFileId) return true;
|
||||||
|
}
|
||||||
|
if (download.arrInfo.episodeId !== undefined && arrItem.episodeId !== undefined) {
|
||||||
|
if (download.arrInfo.episodeId === arrItem.episodeId) return true;
|
||||||
|
}
|
||||||
|
if (download.arrInfo.seriesId !== undefined && arrItem.seriesId !== undefined) {
|
||||||
|
if (download.arrInfo.seriesId === arrItem.seriesId) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Radarr stable IDs
|
||||||
|
if (download.arrInfo.movieFileId !== undefined && arrItem.movieFileId !== undefined) {
|
||||||
|
if (download.arrInfo.movieFileId === arrItem.movieFileId) return true;
|
||||||
|
}
|
||||||
|
if (download.arrInfo.movieId !== undefined && arrItem.movieId !== undefined) {
|
||||||
|
if (download.arrInfo.movieId === arrItem.movieId) return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Only fall back to the existing title + tag string matching if no ID match is possible
|
||||||
|
const dlTitle = (download.title || '').toLowerCase();
|
||||||
|
const arrTitle = (arrItem.title || arrItem.sourceTitle || '').toLowerCase();
|
||||||
|
const titleMatches = dlTitle && arrTitle && (dlTitle.includes(arrTitle) || arrTitle.includes(dlTitle));
|
||||||
|
|
||||||
|
if (!titleMatches) return false;
|
||||||
|
|
||||||
|
// Preserve the existing lowercase-username tag logic exactly
|
||||||
|
if (!username) return true;
|
||||||
|
|
||||||
|
const getLabels = (item) => {
|
||||||
|
if (!item) return [];
|
||||||
|
const tags = item.tags || (item.series && item.series.tags) || (item.movie && item.movie.tags) || [];
|
||||||
|
return tags.map(t => {
|
||||||
|
if (typeof t === 'object' && t !== null) {
|
||||||
|
return t.label || t.name;
|
||||||
|
}
|
||||||
|
if (tagMap && tagMap.has && tagMap.has(t)) {
|
||||||
|
return tagMap.get(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try resolving from cache as fallback
|
||||||
|
const cachedSonarrTags = cache.get('poll:sonarr-tags') || [];
|
||||||
|
const cachedRadarrTags = cache.get('poll:radarr-tags') || [];
|
||||||
|
const allCachedTags = [...cachedSonarrTags, ...cachedRadarrTags];
|
||||||
|
const found = allCachedTags.find(tag => tag && tag.id === t);
|
||||||
|
if (found) return found.label || found.name;
|
||||||
|
|
||||||
|
return t;
|
||||||
|
}).filter(Boolean);
|
||||||
|
};
|
||||||
|
|
||||||
|
const dlTags = getLabels(download);
|
||||||
|
const arrTags = getLabels(arrItem);
|
||||||
|
const allTags = [...dlTags, ...arrTags];
|
||||||
|
|
||||||
|
return allTags.some(tag => tagMatchesUser(tag, username));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach matching helper functions to the registry object
|
||||||
|
arrRetrieverRegistry.matchDownload = matchDownload;
|
||||||
|
arrRetrieverRegistry.matchDownloadToArr = matchDownload;
|
||||||
|
arrRetrieverRegistry.aggregateMatch = matchDownload;
|
||||||
|
arrRetrieverRegistry.matchingHelper = matchDownload;
|
||||||
|
arrRetrieverRegistry.compareDownloadAndArr = matchDownload;
|
||||||
|
|
||||||
module.exports = arrRetrieverRegistry;
|
module.exports = arrRetrieverRegistry;
|
||||||
|
|||||||
Reference in New Issue
Block a user