Files
embycovers/homescreen_editor/lib/constants.js
T

389 lines
11 KiB
JavaScript
Raw Normal View History

2026-06-08 21:58:16 +12:00
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');
}