Files
profilarr/src/lib/server/sync/mappings.ts
Sam Chau 97c21b9572 feat: condition improvements
- refactor cards into unified component with modes
- add placeholders to dropdown selects
- style autocomplete similar to other ui components
- add placeholders to number inputs
- show any in language conditions
- add boolean for except langauge
2026-01-22 15:17:18 +10:30

544 lines
18 KiB
TypeScript

/**
* Arr API mappings
* Constants for transforming PCD data to arr API format
* Based on Radarr/Sonarr API specifications
*/
// Note: This is a subset of the full ArrType from arr/types.ts
// Only includes types that support quality profiles/custom formats
export type SyncArrType = 'radarr' | 'sonarr';
// =============================================================================
// Indexer Flags
// =============================================================================
export const INDEXER_FLAGS = {
radarr: {
freeleech: 1,
halfleech: 2,
double_upload: 4,
internal: 32,
scene: 128,
freeleech_75: 256,
freeleech_25: 512,
nuked: 2048,
ptp_golden: 8,
ptp_approved: 16
},
sonarr: {
freeleech: 1,
halfleech: 2,
double_upload: 4,
internal: 8,
scene: 16,
freeleech_75: 32,
freeleech_25: 64,
nuked: 128
}
} as const;
// =============================================================================
// Sources
// =============================================================================
export const SOURCES = {
radarr: {
cam: 1,
telesync: 2,
telecine: 3,
workprint: 4,
dvd: 5,
tv: 6,
web_dl: 7,
webrip: 8,
bluray: 9
},
sonarr: {
television: 1,
television_raw: 2,
web_dl: 3,
webrip: 4,
dvd: 5,
bluray: 6,
bluray_raw: 7
}
} as const;
// =============================================================================
// Quality Modifiers (Radarr only)
// =============================================================================
export const QUALITY_MODIFIERS = {
none: 0,
regional: 1,
screener: 2,
rawhd: 3,
brdisk: 4,
remux: 5
} as const;
// =============================================================================
// Release Types (Sonarr only)
// =============================================================================
export const RELEASE_TYPES = {
none: 0,
single_episode: 1,
multi_episode: 2,
season_pack: 3
} as const;
// =============================================================================
// Resolutions
// =============================================================================
export const RESOLUTIONS: Record<string, number> = {
'360p': 360,
'480p': 480,
'540p': 540,
'576p': 576,
'720p': 720,
'1080p': 1080,
'2160p': 2160
};
// =============================================================================
// Quality Definitions
// =============================================================================
export interface QualityDefinition {
id: number;
name: string;
source: string;
resolution: number;
}
export const QUALITIES: Record<SyncArrType, Record<string, QualityDefinition>> = {
radarr: {
Unknown: { id: 0, name: 'Unknown', source: 'unknown', resolution: 0 },
SDTV: { id: 1, name: 'SDTV', source: 'tv', resolution: 480 },
DVD: { id: 2, name: 'DVD', source: 'dvd', resolution: 480 },
'WEBDL-1080p': { id: 3, name: 'WEBDL-1080p', source: 'webdl', resolution: 1080 },
'HDTV-720p': { id: 4, name: 'HDTV-720p', source: 'tv', resolution: 720 },
'WEBDL-720p': { id: 5, name: 'WEBDL-720p', source: 'webdl', resolution: 720 },
'Bluray-720p': { id: 6, name: 'Bluray-720p', source: 'bluray', resolution: 720 },
'Bluray-1080p': { id: 7, name: 'Bluray-1080p', source: 'bluray', resolution: 1080 },
'WEBDL-480p': { id: 8, name: 'WEBDL-480p', source: 'webdl', resolution: 480 },
'HDTV-1080p': { id: 9, name: 'HDTV-1080p', source: 'tv', resolution: 1080 },
'Raw-HD': { id: 10, name: 'Raw-HD', source: 'tv', resolution: 1080 },
'WEBRip-480p': { id: 12, name: 'WEBRip-480p', source: 'webrip', resolution: 480 },
'WEBRip-720p': { id: 14, name: 'WEBRip-720p', source: 'webrip', resolution: 720 },
'WEBRip-1080p': { id: 15, name: 'WEBRip-1080p', source: 'webrip', resolution: 1080 },
'HDTV-2160p': { id: 16, name: 'HDTV-2160p', source: 'tv', resolution: 2160 },
'WEBRip-2160p': { id: 17, name: 'WEBRip-2160p', source: 'webrip', resolution: 2160 },
'WEBDL-2160p': { id: 18, name: 'WEBDL-2160p', source: 'webdl', resolution: 2160 },
'Bluray-2160p': { id: 19, name: 'Bluray-2160p', source: 'bluray', resolution: 2160 },
'Bluray-480p': { id: 20, name: 'Bluray-480p', source: 'bluray', resolution: 480 },
'Bluray-576p': { id: 21, name: 'Bluray-576p', source: 'bluray', resolution: 576 },
'BR-DISK': { id: 22, name: 'BR-DISK', source: 'bluray', resolution: 1080 },
'DVD-R': { id: 23, name: 'DVD-R', source: 'dvd', resolution: 480 },
WORKPRINT: { id: 24, name: 'WORKPRINT', source: 'workprint', resolution: 0 },
CAM: { id: 25, name: 'CAM', source: 'cam', resolution: 0 },
TELESYNC: { id: 26, name: 'TELESYNC', source: 'telesync', resolution: 0 },
TELECINE: { id: 27, name: 'TELECINE', source: 'telecine', resolution: 0 },
DVDSCR: { id: 28, name: 'DVDSCR', source: 'dvd', resolution: 480 },
REGIONAL: { id: 29, name: 'REGIONAL', source: 'dvd', resolution: 480 },
'Remux-1080p': { id: 30, name: 'Remux-1080p', source: 'bluray', resolution: 1080 },
'Remux-2160p': { id: 31, name: 'Remux-2160p', source: 'bluray', resolution: 2160 }
},
sonarr: {
Unknown: { id: 0, name: 'Unknown', source: 'unknown', resolution: 0 },
SDTV: { id: 1, name: 'SDTV', source: 'television', resolution: 480 },
DVD: { id: 2, name: 'DVD', source: 'dvd', resolution: 480 },
'WEBDL-1080p': { id: 3, name: 'WEBDL-1080p', source: 'web', resolution: 1080 },
'HDTV-720p': { id: 4, name: 'HDTV-720p', source: 'television', resolution: 720 },
'WEBDL-720p': { id: 5, name: 'WEBDL-720p', source: 'web', resolution: 720 },
'Bluray-720p': { id: 6, name: 'Bluray-720p', source: 'bluray', resolution: 720 },
'Bluray-1080p': { id: 7, name: 'Bluray-1080p', source: 'bluray', resolution: 1080 },
'WEBDL-480p': { id: 8, name: 'WEBDL-480p', source: 'web', resolution: 480 },
'HDTV-1080p': { id: 9, name: 'HDTV-1080p', source: 'television', resolution: 1080 },
'Raw-HD': { id: 10, name: 'Raw-HD', source: 'televisionRaw', resolution: 1080 },
'WEBRip-480p': { id: 12, name: 'WEBRip-480p', source: 'webRip', resolution: 480 },
'Bluray-480p': { id: 13, name: 'Bluray-480p', source: 'bluray', resolution: 480 },
'WEBRip-720p': { id: 14, name: 'WEBRip-720p', source: 'webRip', resolution: 720 },
'WEBRip-1080p': { id: 15, name: 'WEBRip-1080p', source: 'webRip', resolution: 1080 },
'HDTV-2160p': { id: 16, name: 'HDTV-2160p', source: 'television', resolution: 2160 },
'WEBRip-2160p': { id: 17, name: 'WEBRip-2160p', source: 'webRip', resolution: 2160 },
'WEBDL-2160p': { id: 18, name: 'WEBDL-2160p', source: 'web', resolution: 2160 },
'Bluray-2160p': { id: 19, name: 'Bluray-2160p', source: 'bluray', resolution: 2160 },
'Bluray-1080p Remux': {
id: 20,
name: 'Bluray-1080p Remux',
source: 'blurayRaw',
resolution: 1080
},
'Bluray-2160p Remux': {
id: 21,
name: 'Bluray-2160p Remux',
source: 'blurayRaw',
resolution: 2160
},
'Bluray-576p': { id: 22, name: 'Bluray-576p', source: 'bluray', resolution: 576 }
}
};
// =============================================================================
// Languages
// =============================================================================
export interface LanguageDefinition {
id: number;
name: string;
}
export const LANGUAGES: Record<SyncArrType, Record<string, LanguageDefinition>> = {
radarr: {
any: { id: -1, name: 'Any' },
original: { id: -2, name: 'Original' },
unknown: { id: 0, name: 'Unknown' },
english: { id: 1, name: 'English' },
french: { id: 2, name: 'French' },
spanish: { id: 3, name: 'Spanish' },
german: { id: 4, name: 'German' },
italian: { id: 5, name: 'Italian' },
danish: { id: 6, name: 'Danish' },
dutch: { id: 7, name: 'Dutch' },
japanese: { id: 8, name: 'Japanese' },
icelandic: { id: 9, name: 'Icelandic' },
chinese: { id: 10, name: 'Chinese' },
russian: { id: 11, name: 'Russian' },
polish: { id: 12, name: 'Polish' },
vietnamese: { id: 13, name: 'Vietnamese' },
swedish: { id: 14, name: 'Swedish' },
norwegian: { id: 15, name: 'Norwegian' },
finnish: { id: 16, name: 'Finnish' },
turkish: { id: 17, name: 'Turkish' },
portuguese: { id: 18, name: 'Portuguese' },
flemish: { id: 19, name: 'Flemish' },
greek: { id: 20, name: 'Greek' },
korean: { id: 21, name: 'Korean' },
hungarian: { id: 22, name: 'Hungarian' },
hebrew: { id: 23, name: 'Hebrew' },
lithuanian: { id: 24, name: 'Lithuanian' },
czech: { id: 25, name: 'Czech' },
hindi: { id: 26, name: 'Hindi' },
romanian: { id: 27, name: 'Romanian' },
thai: { id: 28, name: 'Thai' },
bulgarian: { id: 29, name: 'Bulgarian' },
'portuguese (brazil)': { id: 30, name: 'Portuguese (Brazil)' },
arabic: { id: 31, name: 'Arabic' },
ukrainian: { id: 32, name: 'Ukrainian' },
persian: { id: 33, name: 'Persian' },
bengali: { id: 34, name: 'Bengali' },
slovak: { id: 35, name: 'Slovak' },
latvian: { id: 36, name: 'Latvian' },
'spanish (latino)': { id: 37, name: 'Spanish (Latino)' },
catalan: { id: 38, name: 'Catalan' },
croatian: { id: 39, name: 'Croatian' },
serbian: { id: 40, name: 'Serbian' },
bosnian: { id: 41, name: 'Bosnian' },
estonian: { id: 42, name: 'Estonian' },
tamil: { id: 43, name: 'Tamil' },
indonesian: { id: 44, name: 'Indonesian' },
telugu: { id: 45, name: 'Telugu' },
macedonian: { id: 46, name: 'Macedonian' },
slovenian: { id: 47, name: 'Slovenian' },
malayalam: { id: 48, name: 'Malayalam' },
kannada: { id: 49, name: 'Kannada' },
albanian: { id: 50, name: 'Albanian' },
afrikaans: { id: 51, name: 'Afrikaans' },
marathi: { id: 52, name: 'Marathi' },
tagalog: { id: 53, name: 'Tagalog' },
urdu: { id: 54, name: 'Urdu' },
romansh: { id: 55, name: 'Romansh' },
mongolian: { id: 56, name: 'Mongolian' },
georgian: { id: 57, name: 'Georgian' }
},
sonarr: {
unknown: { id: 0, name: 'Unknown' },
english: { id: 1, name: 'English' },
french: { id: 2, name: 'French' },
spanish: { id: 3, name: 'Spanish' },
german: { id: 4, name: 'German' },
italian: { id: 5, name: 'Italian' },
danish: { id: 6, name: 'Danish' },
dutch: { id: 7, name: 'Dutch' },
japanese: { id: 8, name: 'Japanese' },
icelandic: { id: 9, name: 'Icelandic' },
chinese: { id: 10, name: 'Chinese' },
russian: { id: 11, name: 'Russian' },
polish: { id: 12, name: 'Polish' },
vietnamese: { id: 13, name: 'Vietnamese' },
swedish: { id: 14, name: 'Swedish' },
norwegian: { id: 15, name: 'Norwegian' },
finnish: { id: 16, name: 'Finnish' },
turkish: { id: 17, name: 'Turkish' },
portuguese: { id: 18, name: 'Portuguese' },
flemish: { id: 19, name: 'Flemish' },
greek: { id: 20, name: 'Greek' },
korean: { id: 21, name: 'Korean' },
hungarian: { id: 22, name: 'Hungarian' },
hebrew: { id: 23, name: 'Hebrew' },
lithuanian: { id: 24, name: 'Lithuanian' },
czech: { id: 25, name: 'Czech' },
arabic: { id: 26, name: 'Arabic' },
hindi: { id: 27, name: 'Hindi' },
bulgarian: { id: 28, name: 'Bulgarian' },
malayalam: { id: 29, name: 'Malayalam' },
ukrainian: { id: 30, name: 'Ukrainian' },
slovak: { id: 31, name: 'Slovak' },
thai: { id: 32, name: 'Thai' },
'portuguese (brazil)': { id: 33, name: 'Portuguese (Brazil)' },
'spanish (latino)': { id: 34, name: 'Spanish (Latino)' },
romanian: { id: 35, name: 'Romanian' },
latvian: { id: 36, name: 'Latvian' },
persian: { id: 37, name: 'Persian' },
catalan: { id: 38, name: 'Catalan' },
croatian: { id: 39, name: 'Croatian' },
serbian: { id: 40, name: 'Serbian' },
bosnian: { id: 41, name: 'Bosnian' },
estonian: { id: 42, name: 'Estonian' },
tamil: { id: 43, name: 'Tamil' },
indonesian: { id: 44, name: 'Indonesian' },
macedonian: { id: 45, name: 'Macedonian' },
slovenian: { id: 46, name: 'Slovenian' },
original: { id: -2, name: 'Original' }
}
};
// =============================================================================
// Name Mapping Utilities
// =============================================================================
/**
* Maps quality names between PCD and arr API formats
* Handles Remux naming differences and alternate spellings
*/
const REMUX_MAPPINGS: Record<SyncArrType, Record<string, string>> = {
sonarr: {
'Remux-1080p': 'Bluray-1080p Remux',
'Remux-2160p': 'Bluray-2160p Remux'
},
radarr: {
'Remux-1080p': 'Remux-1080p',
'Remux-2160p': 'Remux-2160p'
}
};
const ALTERNATE_QUALITY_NAMES: Record<string, string> = {
'BR-Disk': 'BR-DISK',
BRDISK: 'BR-DISK',
BR_DISK: 'BR-DISK',
'BLURAY-DISK': 'BR-DISK',
BLURAY_DISK: 'BR-DISK',
BLURAYDISK: 'BR-DISK',
Telecine: 'TELECINE',
TeleCine: 'TELECINE',
Telesync: 'TELESYNC',
TeleSync: 'TELESYNC'
};
/**
* Map a quality name to the arr API format
*/
export function mapQualityName(name: string, arrType: SyncArrType): string {
if (!name) return name;
// Check remux mappings first
if (REMUX_MAPPINGS[arrType][name]) {
return REMUX_MAPPINGS[arrType][name];
}
// Check alternate spellings
const normalized = name.toUpperCase().replace(/-/g, '').replace(/_/g, '');
for (const [alt, standard] of Object.entries(ALTERNATE_QUALITY_NAMES)) {
if (normalized === alt.toUpperCase().replace(/-/g, '').replace(/_/g, '')) {
return standard;
}
}
return name;
}
/**
* Normalize language name for lookup
*/
export function normalizeLanguageName(name: string): string {
if (!name) return name;
return name.toLowerCase().replace(/-/g, ' ').replace(/_/g, ' ');
}
// =============================================================================
// Source Name Aliases (normalize YAML source names to API keys)
// =============================================================================
const SOURCE_ALIASES: Record<SyncArrType, Record<string, string>> = {
radarr: {
// YAML uses "television", Radarr API uses "tv"
television: 'tv',
hdtv: 'tv',
// Common variations
webdl: 'web_dl',
'web-dl': 'web_dl',
web: 'web_dl',
web_rip: 'webrip',
'web-rip': 'webrip'
},
sonarr: {
// Sonarr uses "television" directly, but add common aliases
hdtv: 'television',
tv: 'television',
webdl: 'web_dl',
'web-dl': 'web_dl',
web: 'web_dl',
web_rip: 'webrip',
'web-rip': 'webrip'
}
};
// =============================================================================
// Value Resolvers
// =============================================================================
/**
* Get indexer flag value
*/
export function getIndexerFlag(flag: string, arrType: SyncArrType): number {
const flags = INDEXER_FLAGS[arrType];
return flags[flag.toLowerCase() as keyof typeof flags] ?? 0;
}
/**
* Normalize source name using aliases
*/
function normalizeSourceName(source: string, arrType: SyncArrType): string {
const normalized = source.toLowerCase().replace(/ /g, '_').replace(/-/g, '_');
return SOURCE_ALIASES[arrType][normalized] ?? normalized;
}
/**
* Get source value
*/
export function getSource(source: string, arrType: SyncArrType): number {
const normalizedSource = normalizeSourceName(source, arrType);
const sources = SOURCES[arrType];
return sources[normalizedSource as keyof typeof sources] ?? 0;
}
/**
* Get resolution value
*/
export function getResolution(resolution: string): number {
return RESOLUTIONS[resolution.toLowerCase()] ?? 0;
}
/**
* Get quality modifier value (Radarr only)
*/
export function getQualityModifier(modifier: string): number {
return QUALITY_MODIFIERS[modifier.toLowerCase() as keyof typeof QUALITY_MODIFIERS] ?? 0;
}
/**
* Get release type value (Sonarr only)
*/
export function getReleaseType(releaseType: string): number {
return RELEASE_TYPES[releaseType.toLowerCase() as keyof typeof RELEASE_TYPES] ?? 0;
}
/**
* Get quality definition
*/
export function getQuality(name: string, arrType: SyncArrType): QualityDefinition | undefined {
const mappedName = mapQualityName(name, arrType);
return QUALITIES[arrType][mappedName];
}
/**
* Get all qualities for an arr type
*/
export function getAllQualities(arrType: SyncArrType): Record<string, QualityDefinition> {
return QUALITIES[arrType];
}
/**
* Get language definition
*/
export function getLanguage(name: string, arrType: SyncArrType): LanguageDefinition {
const normalized = normalizeLanguageName(name);
const languages = LANGUAGES[arrType];
return languages[normalized] ?? languages['unknown'];
}
/**
* Get language for profile (Sonarr always uses Original)
*/
export function getLanguageForProfile(name: string, arrType: SyncArrType): LanguageDefinition {
// Sonarr profiles don't use language settings
if (arrType === 'sonarr') {
return { id: -2, name: 'Original' };
}
if (name === 'any' || !name) {
return LANGUAGES.radarr['any'];
}
return getLanguage(name, arrType);
}
/**
* Get all Radarr languages as an array (for UI dropdowns)
* Returns languages sorted by name, with Any and Original at the top
*/
export function getRadarrLanguages(): LanguageDefinition[] {
const languages = Object.values(LANGUAGES.radarr);
// Sort: Any first, Original second, then alphabetically
return languages.sort((a, b) => {
if (a.id === -1) return -1;
if (b.id === -1) return 1;
if (a.id === -2) return -1;
if (b.id === -2) return 1;
return a.name.localeCompare(b.name);
});
}
/**
* Language with arr type support information (for conditions UI)
*/
export interface LanguageWithSupport {
name: string;
radarr: boolean;
sonarr: boolean;
}
/**
* Get all languages with their arr type support (for conditions page)
* Returns sorted array with Original first, then alphabetically
*/
export function getLanguagesWithSupport(): LanguageWithSupport[] {
const radarrLangs = new Set(Object.values(LANGUAGES.radarr).map((l) => l.name));
const sonarrLangs = new Set(Object.values(LANGUAGES.sonarr).map((l) => l.name));
// Combine all language names
const allNames = new Set([...radarrLangs, ...sonarrLangs]);
// Build result with support flags
const result: LanguageWithSupport[] = [];
for (const name of allNames) {
result.push({
name,
radarr: radarrLangs.has(name),
sonarr: sonarrLangs.has(name)
});
}
// Sort: Any first, Original second, then alphabetically
return result.sort((a, b) => {
if (a.name === 'Any') return -1;
if (b.name === 'Any') return 1;
if (a.name === 'Original') return -1;
if (b.name === 'Original') return 1;
return a.name.localeCompare(b.name);
});
}