// Copyright (c) 2026 Gordon Bolton. MIT License. const axios = require('axios'); const cache = require('../utils/cache'); // Return all resolved tag labels for a series/movie. // For Radarr: tags is array of IDs, tagMap is id -> label mapping. // For Sonarr: tags are objects with a label property. function extractAllTags(tags, tagMap) { if (!tags || tags.length === 0) return []; if (tagMap) { return tags.map(id => tagMap.get(id)).filter(Boolean); } return tags.map(t => t && t.label).filter(Boolean); } // Return the tag label that matches the current username, or null. function extractUserTag(tags, tagMap, username) { const allLabels = extractAllTags(tags, tagMap); if (!allLabels.length) return null; if (username) { const match = allLabels.find(label => tagMatchesUser(label, username)); if (match) return match; } return null; } // 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(); // Exact match (handles users whose tags weren't mangled) if (tagLower === username) return true; // Sanitized match (handles Ombi-mangled tags for email-style usernames) if (tagLower === sanitizeTagLabel(username)) return true; return false; } // Fetch all Emby users and return a Map displayName> (and sanitized variants). // Result is cached for 60s to avoid hammering Emby on every dashboard poll. async function getEmbyUsers() { const cached = cache.get('emby:users'); if (cached) return cached; try { const response = await axios.get(`${process.env.EMBY_URL}/Users`, { headers: { 'X-MediaBrowser-Token': process.env.EMBY_API_KEY } }); // Build map: both raw lowercase and sanitized form -> display name const map = new Map(); for (const u of response.data) { const name = u.Name || ''; map.set(name.toLowerCase(), name); map.set(sanitizeTagLabel(name), name); } cache.set('emby:users', map, 60000); return map; } catch (err) { console.error('[Dashboard] Failed to fetch Emby users:', err.message); return new Map(); } } // Classify each tag label: matched to a known Emby user, or unmatched. // Returns array of { label, matchedUser: string|null } function buildTagBadges(allTags, embyUserMap) { return allTags.map(label => { const lower = label.toLowerCase(); const sanitized = sanitizeTagLabel(label); const displayName = embyUserMap.get(lower) || embyUserMap.get(sanitized) || null; return { label, matchedUser: displayName }; }); } module.exports = { extractAllTags, extractUserTag, sanitizeTagLabel, tagMatchesUser, getEmbyUsers, buildTagBadges };