@startuml activity-matching !theme plain title sofarr — Download Matching Activity Diagram start :Read cached data from MemoryCache; note right poll:sab-queue, poll:sab-history, poll:sonarr-queue, poll:sonarr-history, poll:radarr-queue, poll:radarr-history, poll:sonarr-tags, poll:radarr-tags, poll:qbittorrent end note :Build **seriesMap** from Sonarr queue records (seriesId → embedded series object); :Build **moviesMap** from Radarr queue records (movieId → embedded movie object); :Build **sonarrTagMap** (tagId → label) Build **radarrTagMap** (tagId → label); if (showAll?) then (yes) :Fetch full Emby user list Build **embyUserMap** (lowerName → displayName) [cached 60s]; endif :Initialise **userDownloads** = []; partition "Process SABnzbd Queue Slots" { while (More queue slots?) is (yes) :Get slot filename (nzbName); :nzbNameLower = nzbName.toLowerCase(); if (Title matches Sonarr **queue** record?) then (yes) :series = seriesMap.get(match.seriesId)\n|| match.series; if (series exists?) then (yes) :allTags = extractAllTags(series.tags, sonarrTagMap) matchedUserTag = extractUserTag(series.tags, sonarrTagMap, username); if (showAll AND hasAnyTag?) then (yes) :Build download object (type=series) Add coverArt, status, progress, speed, eta Add allTags, matchedUserTag Add tagBadges = buildTagBadges(allTags, embyUserMap) Add importIssues if any Add admin fields (paths, arrLink); :Push to **userDownloads**; elseif (NOT showAll AND matchedUserTag?) then (yes) :Build download object (type=series) Add matchedUserTag; :Push to **userDownloads**; endif endif endif if (Title matches Radarr **queue** record?) then (yes) :movie = moviesMap.get(match.movieId)\n|| match.movie; if (movie exists?) then (yes) :allTags = extractAllTags(movie.tags, radarrTagMap) matchedUserTag = extractUserTag(movie.tags, radarrTagMap, username); if (showAll AND hasAnyTag?) then (yes) :Build download object (type=movie) Add coverArt, status, progress, speed, eta Add allTags, matchedUserTag, tagBadges Add importIssues if any Add admin fields (paths, arrLink); :Push to **userDownloads**; elseif (NOT showAll AND matchedUserTag?) then (yes) :Build download object (type=movie) Add matchedUserTag; :Push to **userDownloads**; endif endif endif endwhile (no) } partition "Process SABnzbd History Slots" { while (More history slots?) is (yes) :Get slot name (nzbName); :nzbNameLower = nzbName.toLowerCase(); if (Title matches Sonarr **history** record?) then (yes) :series = seriesMap.get(match.seriesId)\n|| match.series; if (series found?) then (yes) :extractAllTags + extractUserTag(username) Build download (type=series, completedAt) Add allTags, matchedUserTag, tagBadges if showAll; :Push to **userDownloads** if showAll+anyTag or matchedUserTag; endif endif if (Title matches Radarr **history** record?) then (yes) :movie = moviesMap.get(match.movieId)\n|| match.movie; if (movie found?) then (yes) :extractAllTags + extractUserTag(username) Build download (type=movie, completedAt) Add allTags, matchedUserTag, tagBadges if showAll; :Push to **userDownloads** if showAll+anyTag or matchedUserTag; endif endif endwhile (no) } partition "Process qBittorrent Torrents" { while (More torrents?) is (yes) :Get torrent name; :torrentNameLower = name.toLowerCase(); if (Matches Sonarr **queue**?) then (yes) :Resolve series → check tag; :mapTorrentToDownload() + enrich; :Push if matches → **continue**; elseif (Matches Radarr **queue**?) then (yes) :Resolve movie → check tag; :mapTorrentToDownload() + enrich; :Push if matches → **continue**; elseif (Matches Sonarr **history**?) then (yes) :Resolve series via seriesMap; :mapTorrentToDownload() + enrich; :Push if matches → **continue**; elseif (Matches Radarr **history**?) then (yes) :Resolve movie via moviesMap; :mapTorrentToDownload() + enrich; :Push if matches → **continue**; else (no match) :Skip torrent (unmatched); endif endwhile (no) } :Return JSON response { user, isAdmin, downloads: userDownloads }; stop legend right **Title Matching Logic** (bidirectional substring, case-insensitive): ""rTitle.includes(dlTitle) || dlTitle.includes(rTitle)"" **Tag Matching Logic** (tagMatchesUser): 1. Exact: tag.toLowerCase() === username 2. Sanitised: sanitizeTagLabel(tag) === sanitizeTagLabel(username) (handles Ombi-mangled email-style usernames) **extractAllTags**: returns all resolved tag labels **extractUserTag**: returns the ONE label matching current user **buildTagBadges**: classifies each tag against full Emby user list → { label, matchedUser: displayName | null } end legend @enduml