fix(matching): add torrent hash/downloadId matching, deduplicate by arrQueueId (closes #65)
Docs Check / Markdown lint (push) Failing after 46s
Build and Push Docker Image / build (push) Successful in 1m4s
CI / Security audit (push) Has been cancelled
CI / Swagger Validation & Coverage (push) Has been cancelled
CI / Tests & coverage (push) Has been cancelled
Docs Check / Mermaid diagram parse check (push) Successful in 1m59s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m8s
Docs Check / Markdown lint (push) Failing after 46s
Build and Push Docker Image / build (push) Successful in 1m4s
CI / Security audit (push) Has been cancelled
CI / Swagger Validation & Coverage (push) Has been cancelled
CI / Tests & coverage (push) Has been cancelled
Docs Check / Mermaid diagram parse check (push) Successful in 1m59s
Licence Check / Licence compatibility and copyright header verification (push) Successful in 2m8s
This commit is contained in:
@@ -429,7 +429,20 @@ async function matchTorrents(torrents, context) {
|
||||
|
||||
let matchedAny = false;
|
||||
|
||||
const sonarrMatch = sonarrQueueRecords.find(r => {
|
||||
// Hash-first matching (Issue #65): prefer matching by torrent hash against
|
||||
// each *arr queue record's `downloadId`. `torrent.hash` covers qBittorrent
|
||||
// and rTorrent; `torrent.hashString` covers Transmission. We fall back to
|
||||
// existing title-substring matching only if no hash match was found.
|
||||
const torrentHash = torrent?.hash || torrent?.hashString || null;
|
||||
const hashLower = torrentHash ? String(torrentHash).toLowerCase() : null;
|
||||
const matchesByHash = (r) => {
|
||||
const dl = r && r.downloadId;
|
||||
if (!dl || !hashLower) return false;
|
||||
return String(dl).toLowerCase() === hashLower;
|
||||
};
|
||||
|
||||
let sonarrMatch = hashLower ? sonarrQueueRecords.find(matchesByHash) : null;
|
||||
if (!sonarrMatch) sonarrMatch = sonarrQueueRecords.find(r => {
|
||||
const rTitle = (r.title || r.sourceTitle || '').toLowerCase();
|
||||
return rTitle && (rTitle.includes(torrentNameLower) || torrentNameLower.includes(rTitle));
|
||||
});
|
||||
@@ -480,7 +493,8 @@ async function matchTorrents(torrents, context) {
|
||||
}
|
||||
}
|
||||
|
||||
const radarrMatch = radarrQueueRecords.find(r => {
|
||||
let radarrMatch = hashLower ? radarrQueueRecords.find(matchesByHash) : null;
|
||||
if (!radarrMatch) radarrMatch = radarrQueueRecords.find(r => {
|
||||
const rTitle = (r.title || r.sourceTitle || '').toLowerCase();
|
||||
return rTitle && (rTitle.includes(torrentNameLower) || torrentNameLower.includes(rTitle));
|
||||
});
|
||||
@@ -529,7 +543,8 @@ async function matchTorrents(torrents, context) {
|
||||
}
|
||||
}
|
||||
|
||||
const sonarrHistoryMatch = sonarrHistoryRecords.find(r => {
|
||||
let sonarrHistoryMatch = hashLower ? sonarrHistoryRecords.find(matchesByHash) : null;
|
||||
if (!sonarrHistoryMatch) sonarrHistoryMatch = sonarrHistoryRecords.find(r => {
|
||||
const rTitle = (r.sourceTitle || r.title || '').toLowerCase();
|
||||
return rTitle && (rTitle.includes(torrentNameLower) || torrentNameLower.includes(rTitle));
|
||||
});
|
||||
@@ -562,7 +577,8 @@ async function matchTorrents(torrents, context) {
|
||||
}
|
||||
}
|
||||
|
||||
const radarrHistoryMatch = radarrHistoryRecords.find(r => {
|
||||
let radarrHistoryMatch = hashLower ? radarrHistoryRecords.find(matchesByHash) : null;
|
||||
if (!radarrHistoryMatch) radarrHistoryMatch = radarrHistoryRecords.find(r => {
|
||||
const rTitle = (r.sourceTitle || r.title || '').toLowerCase();
|
||||
return rTitle && (rTitle.includes(torrentNameLower) || torrentNameLower.includes(rTitle));
|
||||
});
|
||||
@@ -598,7 +614,24 @@ async function matchTorrents(torrents, context) {
|
||||
}
|
||||
|
||||
}
|
||||
return matched;
|
||||
|
||||
// Deduplicate by (arrType, arrQueueId) (Issue #65). When a single torrent
|
||||
// (typically a season pack) matches N *arr queue records sharing one
|
||||
// arrQueueId via downstream emission paths, only the first matched download
|
||||
// is retained. Entries without an arrQueueId pass through unchanged.
|
||||
const seen = new Set();
|
||||
const deduped = [];
|
||||
for (const m of matched) {
|
||||
const key = (m && m.arrType && m.arrQueueId != null)
|
||||
? `${m.arrType}:${m.arrQueueId}`
|
||||
: null;
|
||||
if (key) {
|
||||
if (seen.has(key)) continue;
|
||||
seen.add(key);
|
||||
}
|
||||
deduped.push(m);
|
||||
}
|
||||
return deduped;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
|
||||
Reference in New Issue
Block a user