Files
iptvnator/apps/electron-backend/src/app/api/main.preload.ts
T
4gray 2043b7d8a1 feat(xtream): add category management to hide/show categories
Add the ability to hide irrelevant or empty categories in Xtream playlists.
Users can now manage category visibility through a dialog accessible from
the sidebar header in both VOD/Series and Live TV views.

Features:
- New category management dialog with search filter and select/deselect all
- Visibility settings persisted to database per playlist
- Hidden categories are filtered from sidebar navigation
- New categories added during playlist refresh default to visible
- Database migration for existing installations

Files changed:
- Database: schema.ts, connection.ts (hidden column + migration)
- Backend: category.events.ts, main.preload.ts (IPC handlers)
- Frontend: new dialog component, store method, UI integration
- Translations: en.json
- Documentation: docs/architecture/category-management.md
2026-01-06 21:34:44 +01:00

180 lines
7.9 KiB
TypeScript

import { contextBridge, ipcRenderer } from 'electron';
contextBridge.exposeInMainWorld('electron', {
// Remote control channel change listener
onChannelChange: (
callback: (data: { direction: 'up' | 'down' }) => void
) => {
ipcRenderer.on('CHANNEL_CHANGE', (_event, data) => callback(data));
},
// Player error listener
onPlayerError: (
callback: (data: {
player: string;
error: string;
originalError: string;
}) => void
) => {
ipcRenderer.on('player-error', (_event, data) => callback(data));
},
// EPG progress listener
onEpgProgress: (
callback: (data: {
url: string;
status: 'loading' | 'complete' | 'error';
stats?: { totalChannels: number; totalPrograms: number };
error?: string;
}) => void
) => {
ipcRenderer.on('EPG_PROGRESS_UPDATE', (_event, data) => callback(data));
},
getAppVersion: () => ipcRenderer.invoke('get-app-version'),
platform: process.platform,
fetchPlaylistByUrl: (url: string, title?: string) =>
ipcRenderer.invoke('fetch-playlist-by-url', url, title),
updatePlaylistFromFilePath: (filePath: string, title: string) =>
ipcRenderer.invoke('update-playlist-from-file-path', filePath, title),
openPlaylistFromFile: () => ipcRenderer.invoke('open-playlist-from-file'),
saveFileDialog: (
defaultPath: string,
filters?: { name: string; extensions: string[] }[]
) => ipcRenderer.invoke('save-file-dialog', defaultPath, filters),
writeFile: (filePath: string, content: string) =>
ipcRenderer.invoke('write-file', filePath, content),
setUserAgent: (userAgent: string, referer?: string) =>
ipcRenderer.invoke('set-user-agent', userAgent, referer),
openInMpv: (
url: string,
title: string,
userAgent: string,
referer?: string,
origin?: string
) =>
ipcRenderer.invoke(
'OPEN_MPV_PLAYER',
url,
title,
userAgent,
referer,
origin
),
openInVlc: (
url: string,
title: string,
userAgent: string,
referer?: string,
origin?: string
) =>
ipcRenderer.invoke(
'OPEN_VLC_PLAYER',
url,
title,
userAgent,
referer,
origin
),
autoUpdatePlaylists: (playlists) =>
ipcRenderer.invoke('AUTO_UPDATE', playlists),
fetchEpg: (urls: string[]) =>
ipcRenderer.invoke('FETCH_EPG', { url: urls }),
getChannelPrograms: (channelId: string) =>
ipcRenderer.invoke('GET_CHANNEL_PROGRAMS', { channelId }),
getEpgChannels: () => ipcRenderer.invoke('EPG_GET_CHANNELS'),
getEpgChannelsByRange: (skip: number, limit: number) =>
ipcRenderer.invoke('EPG_GET_CHANNELS_BY_RANGE', { skip, limit }),
forceFetchEpg: (url: string) => ipcRenderer.invoke('EPG_FORCE_FETCH', url),
clearEpgData: () => ipcRenderer.invoke('EPG_CLEAR_ALL'),
checkEpgFreshness: (urls: string[], maxAgeHours?: number) =>
ipcRenderer.invoke('EPG_CHECK_FRESHNESS', { urls, maxAgeHours }),
searchEpgPrograms: (searchTerm: string, limit?: number) =>
ipcRenderer.invoke('EPG_DB_SEARCH_PROGRAMS', searchTerm, limit),
setMpvPlayerPath: (mpvPlayerPath: string) =>
ipcRenderer.invoke('SET_MPV_PLAYER_PATH', mpvPlayerPath),
setVlcPlayerPath: (vlcPlayerPath: string) =>
ipcRenderer.invoke('SET_VLC_PLAYER_PATH', vlcPlayerPath),
updateSettings: (settings: any) =>
ipcRenderer.invoke('SETTINGS_UPDATE', settings),
getAiSettings: () => ipcRenderer.invoke('GET_AI_SETTINGS'),
stalkerRequest: (payload: {
url: string;
macAddress: string;
params: Record<string, string>;
token?: string;
}) => ipcRenderer.invoke('STALKER_REQUEST', payload),
xtreamRequest: (payload: { url: string; params: Record<string, string> }) =>
ipcRenderer.invoke('XTREAM_REQUEST', payload),
// Database operations
dbCreatePlaylist: (playlist: any) =>
ipcRenderer.invoke('DB_CREATE_PLAYLIST', playlist),
dbGetPlaylist: (playlistId: string) =>
ipcRenderer.invoke('DB_GET_PLAYLIST', playlistId),
dbUpdatePlaylist: (playlistId: string, updates: any) =>
ipcRenderer.invoke('DB_UPDATE_PLAYLIST', playlistId, updates),
dbDeletePlaylist: (playlistId: string) =>
ipcRenderer.invoke('DB_DELETE_PLAYLIST', playlistId),
dbDeleteXtreamContent: (playlistId: string) =>
ipcRenderer.invoke('DB_DELETE_XTREAM_CONTENT', playlistId),
dbRestoreXtreamUserData: (
playlistId: string,
favoritedXtreamIds: number[],
recentlyViewedXtreamIds: { xtreamId: number; viewedAt: string }[]
) =>
ipcRenderer.invoke(
'DB_RESTORE_XTREAM_USER_DATA',
playlistId,
favoritedXtreamIds,
recentlyViewedXtreamIds
),
dbHasCategories: (playlistId: string, type: string) =>
ipcRenderer.invoke('DB_HAS_CATEGORIES', playlistId, type),
dbGetCategories: (playlistId: string, type: string) =>
ipcRenderer.invoke('DB_GET_CATEGORIES', playlistId, type),
dbSaveCategories: (playlistId: string, categories: any[], type: string) =>
ipcRenderer.invoke('DB_SAVE_CATEGORIES', playlistId, categories, type),
dbGetAllCategories: (playlistId: string, type: string) =>
ipcRenderer.invoke('DB_GET_ALL_CATEGORIES', playlistId, type),
dbUpdateCategoryVisibility: (categoryIds: number[], hidden: boolean) =>
ipcRenderer.invoke(
'DB_UPDATE_CATEGORY_VISIBILITY',
categoryIds,
hidden
),
dbHasContent: (playlistId: string, type: string) =>
ipcRenderer.invoke('DB_HAS_CONTENT', playlistId, type),
dbGetContent: (playlistId: string, type: string) =>
ipcRenderer.invoke('DB_GET_CONTENT', playlistId, type),
dbSaveContent: (playlistId: string, streams: any[], type: string) =>
ipcRenderer.invoke('DB_SAVE_CONTENT', playlistId, streams, type),
dbSearchContent: (
playlistId: string,
searchTerm: string,
types: string[]
) => ipcRenderer.invoke('DB_SEARCH_CONTENT', playlistId, searchTerm, types),
dbGlobalSearch: (searchTerm: string, types: string[]) =>
ipcRenderer.invoke('DB_GLOBAL_SEARCH', searchTerm, types),
dbGetRecentlyViewed: () => ipcRenderer.invoke('DB_GET_RECENTLY_VIEWED'),
dbClearRecentlyViewed: () => ipcRenderer.invoke('DB_CLEAR_RECENTLY_VIEWED'),
// Favorites
dbAddFavorite: (contentId: number, playlistId: string) =>
ipcRenderer.invoke('DB_ADD_FAVORITE', contentId, playlistId),
dbRemoveFavorite: (contentId: number, playlistId: string) =>
ipcRenderer.invoke('DB_REMOVE_FAVORITE', contentId, playlistId),
dbIsFavorite: (contentId: number, playlistId: string) =>
ipcRenderer.invoke('DB_IS_FAVORITE', contentId, playlistId),
dbGetFavorites: (playlistId: string) =>
ipcRenderer.invoke('DB_GET_FAVORITES', playlistId),
// Recently viewed (playlist-specific)
dbGetRecentItems: (playlistId: string) =>
ipcRenderer.invoke('DB_GET_RECENT_ITEMS', playlistId),
dbAddRecentItem: (contentId: number, playlistId: string) =>
ipcRenderer.invoke('DB_ADD_RECENT_ITEM', contentId, playlistId),
dbClearPlaylistRecentItems: (playlistId: string) =>
ipcRenderer.invoke('DB_CLEAR_PLAYLIST_RECENT_ITEMS', playlistId),
dbRemoveRecentItem: (contentId: number, playlistId: string) =>
ipcRenderer.invoke('DB_REMOVE_RECENT_ITEM', contentId, playlistId),
dbGetContentByXtreamId: (xtreamId: number, playlistId: string) =>
ipcRenderer.invoke('DB_GET_CONTENT_BY_XTREAM_ID', xtreamId, playlistId),
dbDeleteAllPlaylists: () => ipcRenderer.invoke('DB_DELETE_ALL_PLAYLISTS'),
getLocalIpAddresses: () => ipcRenderer.invoke('get-local-ip-addresses'),
});