Files
embycovers/homescreen_editor/lib/constants.js
T
2026-06-08 21:58:16 +12:00

389 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export const GENRES = {
'2042': 'Action',
'1212': 'Sci-Fi',
'4910': 'Crime',
'62': 'Drama',
'82': 'Comedy',
'293': 'Animation',
'36': 'Documentary',
'5024': 'Horror',
'4835': 'Romance',
'4428': 'Thriller',
'5709': 'War',
'14124': 'Western',
'218': 'Food',
'396654': 'Reality',
'19618': 'Travel',
'17565': 'Mini Series',
'2008': 'Mystery',
'235': 'Family',
'480': 'Fantasy'
};
export const SECTION_TYPES = [
{ value: 'resume', label: 'Resume / Next Up' },
{ value: 'items', label: 'Items (filtered)' },
{ value: 'userviews', label: 'Libraries' },
{ value: 'boxset', label: 'Box Set' },
{ value: 'collections', label: 'Collections' },
{ value: 'latestepisodereleases', label: 'Latest episode releases' },
{ value: 'latestmoviereleases', label: 'Latest movie releases' },
{ value: 'latestmediablock', label: 'Latest media' }
];
export const COLLECTION_TYPES = [
{ value: '', label: '(none)' },
{ value: 'movies', label: 'Movies' },
{ value: 'tvshows', label: 'TV Shows' },
{ value: 'boxsets', label: 'Box Sets' }
];
export const ITEM_TYPES = ['Movie', 'Series', 'Episode', 'BoxSet'];
export const SORT_OPTIONS = [
{ value: '', label: '(none)' },
{ value: 'default', label: 'Default (boxset)' },
{ value: 'DatePlayed', label: 'Date played' },
{ value: 'DateLastContentAdded,SortName', label: 'Date added' },
{ value: 'ProductionYear,PremiereDate,SortName', label: 'Release year' },
{ value: 'CommunityRating', label: 'Community rating' },
{ value: 'CriticRating,SortName', label: 'Critic rating' },
{ value: 'DateCreated,SortName', label: 'Date created' },
{ value: 'Random', label: 'Random' },
{ value: 'SortName', label: 'Name' }
];
export const IMAGE_TYPES = [
{ value: '', label: 'Default' },
{ value: 'Thumb', label: 'Thumb' },
{ value: 'Primary', label: 'Primary / Poster' }
];
export const PAGE_ICONS = {
edit: 'edit',
sync: 'sync',
collections: 'collections',
settings: 'settings'
};
export const SECTION_ICONS = {
resume: 'resume',
items: 'items',
userviews: 'userviews',
boxset: 'boxset',
collections: 'collections',
latestepisodereleases: 'latestepisodereleases',
latestmoviereleases: 'latestmoviereleases',
latestmediablock: 'latestmediablock'
};
export function genId() {
return crypto.randomUUID().replace(/-/g, '').slice(0, 32);
}
export function createEmptySection(userId) {
return {
UserId: userId,
Name: 'New Section',
CustomName: 'New Section',
Id: genId(),
SectionType: 'items',
ImageType: 'Thumb',
CollectionType: 'movies',
SortBy: 'Random',
SortOrder: 'Descending',
Monitor: [],
ItemTypes: ['Movie'],
ExcludedFolders: [],
CardSizeOffset: 0,
IncludeNextUpInResume: true,
Query: {
StudioIds: [],
TagIds: [],
GenreIds: [],
CollectionTypes: [],
IsPlayed: false
}
};
}
export function createRecentlyWatchedSection(userId, userName = '') {
return {
UserId: userId,
Name: `Recently Watched${userName ? ` - ${userName}` : ''}`,
CustomName: `Recently Watched${userName ? ` - ${userName}` : ''}`,
Id: genId(),
SectionType: 'items',
ImageType: 'Thumb',
CollectionType: '',
SortBy: 'DatePlayed',
SortOrder: 'Descending',
Monitor: [],
ItemTypes: ['Movie', 'Series'],
ExcludedFolders: [],
CardSizeOffset: 0,
IncludeNextUpInResume: true,
Query: {
StudioIds: [],
TagIds: [],
GenreIds: [],
CollectionTypes: [],
IsPlayed: true
}
};
}
export function createBoxSetSection(userId, collectionName, collectionId) {
return {
UserId: userId,
Name: collectionName || 'New Collection',
CustomName: collectionName || 'New Collection',
Id: genId(),
SectionType: 'boxset',
ImageType: 'Thumb',
ItemTypes: [],
SortBy: 'Random',
SortOrder: 'Descending',
Monitor: [],
ExcludedFolders: [],
CardSizeOffset: 0,
IncludeNextUpInResume: true,
ParentItem: {
Name: collectionName || 'New Collection',
Id: String(collectionId || '')
},
ParentId: String(collectionId || '')
};
}
export function getSectionTypeLabel(type) {
const found = SECTION_TYPES.find((t) => t.value === type);
return found ? found.label : type;
}
export function getSectionIconName(type) {
return SECTION_ICONS[type] || 'spark';
}
export function getGenreNames(genreIds) {
if (!genreIds || genreIds.length === 0) return '';
return genreIds.map((id) => GENRES[id] || id).join(', ');
}
export function extractUserName(sections) {
for (const s of sections) {
const name = s.CustomName || s.Name || '';
const extracted = extractWatchlistOwnerName(name);
if (extracted && extracted !== name) return extracted;
}
return null;
}
export function isWatchlistSection(section) {
if (!section) return false;
const name = `${section.CustomName || ''} ${section.Name || ''}`.toLowerCase();
return !!section.Query?.IsFavorite || name.includes('watchlist') || name.includes('watch list');
}
export function isUpNextSection(section) {
if (!section) return false;
if (section.SectionType === 'resume') return true;
const name = `${section.CustomName || ''} ${section.Name || ''}`.trim().toLowerCase();
return name === 'up next' || name === 'next up' || name === 'resume / up next';
}
export function isNewToEmbySection(section) {
if (!section) return false;
const name = `${section.CustomName || ''} ${section.Name || ''}`.toLowerCase();
return name.includes('new to emby');
}
export function isRecentlyWatchedSection(section) {
if (!section) return false;
const name = `${section.CustomName || ''} ${section.Name || ''}`.toLowerCase();
return section.Query?.IsPlayed === true || name.includes('recently watched');
}
export function isFixedOrderSection(section) {
if (!section) return false;
return (
isUpNextSection(section) ||
isWatchlistSection(section) ||
isNewToEmbySection(section) ||
isRecentlyWatchedSection(section) ||
section.SectionType === 'latestepisodereleases' ||
section.SectionType === 'latestmoviereleases' ||
section.SectionType === 'latestmediablock' ||
section.SectionType === 'userviews'
);
}
function renameWatchlistLabel(label, targetName) {
if (!label || !targetName) return label;
if (/^\s*watch\s+list\s*$/i.test(label)) return 'Watch List';
if (/^\s*watchlist\s*$/i.test(label)) return 'Watchlist';
if (/watch\s+list/i.test(label)) {
return label.replace(/^.*?watch\s+list/i, `${targetName}'s Watch List`);
}
if (/watchlist/i.test(label)) {
return label.replace(/^.*?watchlist/i, `${targetName}'s Watchlist`);
}
return label;
}
function extractWatchlistOwnerName(label) {
if (!label) return '';
const normalized = String(label).trim();
if (!/watchlist|watch\s+list/i.test(normalized)) return '';
return normalized
.replace(/\bwatchlist\b/i, '')
.replace(/\bwatch\s+list\b/i, '')
.replace(/[']s$/i, '')
.trim();
}
function normalizeNameForCompare(name) {
return String(name || '')
.toLowerCase()
.replace(/[']/g, '')
.replace(/[^a-z0-9]+/g, '');
}
function labelsMatchUser(label, targetName) {
const owner = extractWatchlistOwnerName(label);
if (!owner || !targetName) return false;
return normalizeNameForCompare(owner) === normalizeNameForCompare(targetName);
}
function getPreferredUserName(user) {
return user?.embyName || user?.name || '';
}
export function getWatchlistLabelsForTarget(sourceSection, targetUser) {
const existingTargetWatchlist = targetUser?.sections?.find((section) => isWatchlistSection(section));
const targetName = getPreferredUserName(targetUser);
if (existingTargetWatchlist) {
const existingName = existingTargetWatchlist.CustomName || existingTargetWatchlist.Name || '';
if (!targetName || labelsMatchUser(existingName, targetName)) {
return {
Name: existingTargetWatchlist.Name || sourceSection.Name,
CustomName: existingTargetWatchlist.CustomName || existingTargetWatchlist.Name || sourceSection.CustomName || sourceSection.Name
};
}
return {
Name: renameWatchlistLabel(existingTargetWatchlist.Name || sourceSection.Name, targetName),
CustomName: renameWatchlistLabel(
existingTargetWatchlist.CustomName || existingTargetWatchlist.Name || sourceSection.CustomName || sourceSection.Name,
targetName
)
};
}
return {
Name: renameWatchlistLabel(sourceSection.Name, targetName),
CustomName: renameWatchlistLabel(sourceSection.CustomName, targetName)
};
}
export function applySectionStandards(sourceSection, targetUser) {
const section = JSON.parse(JSON.stringify(sourceSection || {}));
if (isUpNextSection(section)) {
section.Name = 'Up Next';
section.CustomName = 'Up Next';
return section;
}
if (isWatchlistSection(section)) {
const labels = getWatchlistLabelsForTarget(section, targetUser);
if (labels.Name) section.Name = labels.Name;
if (labels.CustomName) section.CustomName = labels.CustomName;
return section;
}
if (isNewToEmbySection(section)) {
section.Name = 'New to Emby';
section.CustomName = 'New to Emby';
section.SortBy = 'DateLastContentAdded,SortName';
section.SortOrder = 'Descending';
return section;
}
if (isRecentlyWatchedSection(section)) {
const targetName = getPreferredUserName(targetUser);
const label = `Recently Watched${targetName ? ` - ${targetName}` : ''}`;
section.Name = label;
section.CustomName = label;
section.SortBy = 'DatePlayed';
section.SortOrder = 'Descending';
return section;
}
if (!isFixedOrderSection(section) && ['items', 'collections', 'boxset'].includes(section.SectionType)) {
section.SortBy = 'Random';
section.SortOrder = 'Descending';
}
if (section.SectionType === 'userviews') {
section.Name = 'Libraries';
section.CustomName = 'Libraries';
}
return section;
}
/**
* Build SQL UPDATE statements from modified user data.
* Each user's entire homescreensettings JSON is replaced.
*/
export function generateSQL(users, originalUsers) {
const statements = [];
for (const user of users) {
if (!user.sections || user.sections.length === 0) continue;
const original = originalUsers.find((u) => u.id === user.id);
if (!original) continue;
const origJSON = JSON.stringify({ Sections: original.sections });
const newJSON = JSON.stringify({ Sections: user.sections });
if (origJSON === newJSON) continue;
const escapedValue = newJSON.replace(/'/g, "''");
statements.push(
`-- User: ${user.name} (DB ID: ${user.id})`,
`UPDATE UserSettings SET Value = '${escapedValue}' WHERE UserId = ${user.id} AND UserSettingsKeyId = (SELECT UserSettingsKeyId FROM UserSettingsKeys WHERE Name = 'homescreensettings');`,
''
);
}
if (statements.length === 0) {
return '-- No changes detected';
}
return [
'-- ===========================================',
'-- Emby Home Screen Settings Update',
`-- Generated: ${new Date().toISOString()}`,
'-- ===========================================',
'-- IMPORTANT: Stop Emby before running this!',
'-- sqlite3 /path/to/users.db < this_file.sql',
'-- Then restart Emby.',
'-- ===========================================',
'',
'BEGIN TRANSACTION;',
'',
...statements,
'COMMIT;'
].join('\n');
}