diff --git a/README.md b/README.md index 38aba65..1c5ec6d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# Kyoo - Media Discovery Platform +# Omnyx - Media Discovery Platform -A modern web application for browsing, managing, and discovering media across multiple categories. Kyoo provides a unified interface for your media library with support for importing from external sources like Playnite, StashAPP, and XBVR. +A modern web application for browsing, managing, and discovering media across multiple categories. Omnyx provides a unified interface for your media library with support for importing from external sources like Playnite, StashAPP, and XBVR. ## Features diff --git a/index.html b/index.html index 21dfe69..459b47c 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - My Google AI Studio App + Omnyx - Media Discovery
diff --git a/metadata.json b/metadata.json index 5c1b172..6f7923c 100644 --- a/metadata.json +++ b/metadata.json @@ -1,5 +1,5 @@ { - "name": "Kyoo - Media Discovery", + "name": "Omnyx - Media Discovery", "description": "A polished media discovery and tracking application inspired by modern anime platforms.", "requestFramePermissions": [] } diff --git a/src/App.tsx b/src/App.tsx index 9161a18..993e4c0 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -77,6 +77,22 @@ function AppContent() { setEnabledCategories(loadedSettings.enabledCategories); // Sync theme with theme context setTheme(loadedSettings.theme); + + // Set custom page title + if (loadedSettings.pageTitle) { + document.title = loadedSettings.pageTitle; + } + + // Set custom favicon + if (loadedSettings.favicon) { + let faviconLink = document.querySelector("link[rel~='icon']") as HTMLLinkElement; + if (!faviconLink) { + faviconLink = document.createElement('link'); + faviconLink.rel = 'icon'; + document.head.appendChild(faviconLink); + } + faviconLink.href = loadedSettings.favicon; + } } } catch (error) { console.error('Failed to load settings from API:', error); @@ -86,6 +102,22 @@ function AppContent() { loadSettingsFromApi(); }, [setTheme]); + // Apply custom colors when settings change + useEffect(() => { + if (settings?.customColors) { + const root = document.documentElement; + const colors = settings.customColors; + + if (colors.primary) root.style.setProperty('--color-primary', colors.primary); + if (colors.secondary) root.style.setProperty('--color-secondary', colors.secondary); + if (colors.background) root.style.setProperty('--color-background', colors.background); + if (colors.surface) root.style.setProperty('--color-surface', colors.surface); + if (colors.text) root.style.setProperty('--color-text', colors.text); + if (colors.muted) root.style.setProperty('--color-muted', colors.muted); + if (colors.border) root.style.setProperty('--color-border', colors.border); + } + }, [settings?.customColors]); + const reloadSettings = async () => { try { const loadedSettings = await fetchSettings(); @@ -94,6 +126,22 @@ function AppContent() { setEnabledCategories(loadedSettings.enabledCategories); // Sync theme with theme context setTheme(loadedSettings.theme); + + // Set custom page title + if (loadedSettings.pageTitle) { + document.title = loadedSettings.pageTitle; + } + + // Set custom favicon + if (loadedSettings.favicon) { + let faviconLink = document.querySelector("link[rel~='icon']") as HTMLLinkElement; + if (!faviconLink) { + faviconLink = document.createElement('link'); + faviconLink.rel = 'icon'; + document.head.appendChild(faviconLink); + } + faviconLink.href = loadedSettings.favicon; + } } } catch (error) { console.error('Failed to reload settings from API:', error); @@ -315,6 +363,7 @@ function AppContent() {
@@ -385,7 +434,7 @@ function AppContent() {
- kyoo + {settings?.pageTitle || 'omnyx'}
Terms @@ -393,7 +442,7 @@ function AppContent() { Contact

- © 2026 Kyoo Media Discovery. All rights reserved. + © 2026 Omnyx Media Discovery. All rights reserved.

diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 12aa8fe..00667ef 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -95,7 +95,7 @@ export default function Header({ )} />
- kyoo + omnyx + + )} +
+ + +
+ +

+ Upload a custom favicon or icon. The image will be converted to Base64 format. +

+ + + {/* Custom Colors */} +
+
+ + +
+
+ {[ + { key: 'primary', label: 'Primary Color' }, + { key: 'secondary', label: 'Secondary Color' }, + { key: 'background', label: 'Background Color' }, + { key: 'surface', label: 'Surface Color' }, + { key: 'text', label: 'Text Color' }, + { key: 'muted', label: 'Muted Text Color' }, + { key: 'border', label: 'Border Color' }, + ].map(({ key, label }) => ( +
+ handleColorChange(key as keyof CustomColors, e.target.value)} + className="w-10 h-10 rounded-lg cursor-pointer border-0" + /> +
+ + handleColorChange(key as keyof CustomColors, e.target.value)} + placeholder="#6d28d9" + className="w-full mt-1 px-2 py-1 rounded-lg bg-muted border border-border/30 text-xs text-foreground placeholder:text-muted-foreground/50 focus:border-[#6d28d9] focus:outline-none transition-all" + /> +
+
+ ))} +
+

+ Leave color fields empty to use the default theme colors. +

+
+ + diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx index 013eccf..964c84e 100644 --- a/src/components/Sidebar.tsx +++ b/src/components/Sidebar.tsx @@ -31,9 +31,10 @@ import { CATEGORY_PATHS } from '@/constants'; interface SidebarProps { enabledCategories: MediaCategory[]; onToggleCategory: (category: MediaCategory) => void; + pageTitle?: string; } -export default function Sidebar({ enabledCategories, onToggleCategory }: SidebarProps) { +export default function Sidebar({ enabledCategories, onToggleCategory, pageTitle }: SidebarProps) { const [isMediaExpanded, setIsMediaExpanded] = useState(true); const [isMobileOpen, setIsMobileOpen] = useState(false); const { theme, setTheme } = useTheme(); @@ -120,7 +121,7 @@ export default function Sidebar({ enabledCategories, onToggleCategory }: Sidebar
- kyoo + {pageTitle || 'omnyx'}
diff --git a/src/lib/api/converters.ts b/src/lib/api/converters.ts index 973f00d..484e394 100644 --- a/src/lib/api/converters.ts +++ b/src/lib/api/converters.ts @@ -170,6 +170,12 @@ export function convertApiToSettings(apiItem: ApiSettingsItem): UserSettings { language: apiItem.language || 'en', theme: (apiItem.theme as 'light' | 'dark' | 'system') || 'system', jellyfinLibraryMappings: apiItem.jellyfin_library_mappings, + + // Page Settings + pageTitle: apiItem.page_title, + favicon: apiItem.favicon, + customColors: apiItem.custom_colors ? JSON.parse(apiItem.custom_colors) : undefined, + createdAt: apiItem.created_at, updatedAt: apiItem.updated_at, }; @@ -186,5 +192,10 @@ export function convertSettingsToApi(settings: UserSettings): CreateSettingsInpu language: settings.language, theme: settings.theme, jellyfin_library_mappings: settings.jellyfinLibraryMappings, + + // Page Settings + page_title: settings.pageTitle, + favicon: settings.favicon, + custom_colors: settings.customColors ? JSON.stringify(settings.customColors) : undefined, }; } diff --git a/src/lib/api/types.ts b/src/lib/api/types.ts index f2efe2d..5af55e2 100644 --- a/src/lib/api/types.ts +++ b/src/lib/api/types.ts @@ -193,6 +193,12 @@ export interface ApiSettingsItem { language: string; theme: string; jellyfin_library_mappings?: string; + + // Page Settings + page_title?: string; + favicon?: string; + custom_colors?: string; // JSON string of CustomColors + created_at?: string; updated_at?: string; } @@ -207,6 +213,11 @@ export interface CreateSettingsInput { language?: string; theme?: string; jellyfin_library_mappings?: string; + + // Page Settings + page_title?: string; + favicon?: string; + custom_colors?: string; } export interface UpdateSettingsInput extends Partial {} diff --git a/src/lib/jellyfinImporter.ts b/src/lib/jellyfinImporter.ts index a117218..a74e2ff 100644 --- a/src/lib/jellyfinImporter.ts +++ b/src/lib/jellyfinImporter.ts @@ -1,7 +1,7 @@ /** * Jellyfin Importer Module * - * This module provides functionality to import media from a Jellyfin media server into the Kyoo media database. + * This module provides functionality to import media from a Jellyfin media server into the Omnyx media database. * It supports importing movies, TV series (including episodes), music albums, and cast members. * The module handles library mapping to categorize content appropriately and supports both new imports * and updates to existing entries. @@ -25,7 +25,7 @@ export interface JellyfinConfig { } /** - * Mapping configuration for Jellyfin libraries to Kyoo categories + * Mapping configuration for Jellyfin libraries to Omnyx categories */ export interface LibraryMapping { /** Name of the Jellyfin library */ @@ -838,10 +838,10 @@ function convertJellyfinPersonToCast(person: JellyfinPerson, config: JellyfinCon } /** - * Imports media from a Jellyfin instance into the Kyoo media database + * Imports media from a Jellyfin instance into the Omnyx media database * * This function performs the following steps: - * 1. Fetches existing media and cast from Kyoo to check for duplicates + * 1. Fetches existing media and cast from Omnyx to check for duplicates * 2. Fetches Jellyfin libraries for category mapping (if library mappings are provided) * 3. Imports movies (if enabled) * 4. Imports TV series with episodes (if enabled) @@ -895,7 +895,7 @@ export async function importFromJellyfin( logCallback('Starting Jellyfin import...'); // Step 0: Fetch existing media and cast to check for duplicates - logCallback('Fetching existing media from Kyoo API...'); + logCallback('Fetching existing media from Omnyx API...'); const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=10000`); const existingMediaData = await existingMediaResponse.json(); const existingMedia = new Map( @@ -903,7 +903,7 @@ export async function importFromJellyfin( ); logCallback(`Found ${existingMedia.size} existing media items in database`); - logCallback('Fetching existing cast from Kyoo API...'); + logCallback('Fetching existing cast from Omnyx API...'); const existingCastResponse = await fetch(`${BASE_URL}/api/cast`); const existingCastData = await existingCastResponse.json(); const existingCast = new Map( @@ -1297,15 +1297,15 @@ export async function cleanupJellyfinMedia( try { logCallback('Starting Jellyfin cleanup...'); - // Fetch all existing media from Kyoo API - logCallback('Fetching existing media from Kyoo API...'); + // Fetch all existing media from Omnyx API + logCallback('Fetching existing media from Omnyx API...'); const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=10000`); const existingMediaData = await existingMediaResponse.json(); const jellyfinMedia = (existingMediaData.data?.items || []).filter((m: Media) => m.source === 'jellyfin'); logCallback(`Found ${jellyfinMedia.length} Jellyfin media items in database`); - // Fetch all existing cast from Kyoo API - logCallback('Fetching existing cast from Kyoo API...'); + // Fetch all existing cast from Omnyx API + logCallback('Fetching existing cast from Omnyx API...'); const existingCastResponse = await fetch(`${BASE_URL}/api/cast`); const existingCastData = await existingCastResponse.json(); const jellyfinCast = (existingCastData.data?.items || []).filter((c: Staff) => c.photo && c.photo.includes(normalizeUrl(config.url))); diff --git a/src/lib/playniteImporter.ts b/src/lib/playniteImporter.ts index a104da8..f055bf3 100644 --- a/src/lib/playniteImporter.ts +++ b/src/lib/playniteImporter.ts @@ -1,8 +1,8 @@ /** * Playnite Importer Module * - * This module provides functionality to import games from a Playnite library into the Kyoo media database. - * It fetches game data from the Playnite API, converts it to the Kyoo media format, and handles both + * This module provides functionality to import games from a Playnite library into the Omnyx media database. + * It fetches game data from the Playnite API, converts it to the Omnyx media format, and handles both * new imports and updates to existing entries. * * @module playniteImporter @@ -216,14 +216,14 @@ async function fetchGameIcon(baseUrl: string, headers: Record, g } */ /** - * Imports games from a Playnite library into the Kyoo media database + * Imports games from a Playnite library into the Omnyx media database * * This function performs the following steps: - * 1. Fetches existing media from Kyoo to check for duplicates + * 1. Fetches existing media from Omnyx to check for duplicates * 2. Fetches all games from the Playnite API * 3. Fetches detailed information for each game - * 4. Converts Playnite game data to Kyoo media format - * 5. Imports or updates each game in the Kyoo database + * 4. Converts Playnite game data to Omnyx media format + * 5. Imports or updates each game in the Omnyx database * * @param config - Configuration for connecting to Playnite * @param logCallback - Callback function for logging progress messages @@ -264,7 +264,7 @@ export async function importFromPlaynite( logCallback('Starting Playnite import...'); // Step 0: Fetch existing media to check for duplicates and enable updates - logCallback('Fetching existing media from Kyoo API...'); + logCallback('Fetching existing media from Omnyx API...'); const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=1000`); const existingMediaData = await existingMediaResponse.json(); const existingMedia = new Map( diff --git a/src/lib/stashappImporter.ts b/src/lib/stashappImporter.ts index b4cf92a..8fdfa3c 100644 --- a/src/lib/stashappImporter.ts +++ b/src/lib/stashappImporter.ts @@ -2,7 +2,7 @@ * StashAPP Importer Module * * This module provides functionality to import adult video content and performers from a StashAPP instance - * into the Kyoo media database. It fetches scene and performer data via GraphQL, converts it to the Kyoo + * into the Omnyx media database. It fetches scene and performer data via GraphQL, converts it to the Omnyx * media format, and handles both new imports and updates to existing entries. * * @module stashappImporter @@ -226,7 +226,7 @@ function isPathBlacklisted(filePath: string, blacklist: string[]): boolean { * Updates or creates actor entries from StashAPP performers * * This function fetches all performers from StashAPP and updates or creates - * corresponding actor entries in the Kyoo database. + * corresponding actor entries in the Omnyx database. * * @param config - Configuration for connecting to StashAPP * @param logCallback - Callback function for logging progress messages @@ -251,8 +251,8 @@ export async function updateActorsFromStashAPP( try { logCallback('Starting StashAPP actor update...'); - // Fetch existing cast from Kyoo API - logCallback('Fetching existing cast from Kyoo API...'); + // Fetch existing cast from Omnyx API + logCallback('Fetching existing cast from Omnyx API...'); const existingCastResponse = await fetch(`${BASE_URL}/api/cast`); const existingCastData = await existingCastResponse.json(); const existingActors = new Map( @@ -456,10 +456,10 @@ export async function updateActorsFromStashAPP( } /** - * Imports scenes and performers from a StashAPP instance into the Kyoo media database + * Imports scenes and performers from a StashAPP instance into the Omnyx media database * * This function performs the following steps: - * 1. Fetches existing media and cast from Kyoo to check for duplicates + * 1. Fetches existing media and cast from Omnyx to check for duplicates * 2. Fetches all scenes from StashAPP via GraphQL * 3. Extracts unique performers from all scenes * 4. Imports or updates performers first @@ -499,7 +499,7 @@ export async function importFromStashAPP( logCallback('Starting StashAPP import...'); // Step 0: Fetch existing media and cast to check for duplicates - logCallback('Fetching existing media from Kyoo API...'); + logCallback('Fetching existing media from Omnyx API...'); const existingMediaResponse = await fetch(`${BASE_URL}/api/media`); const existingMediaData = await existingMediaResponse.json(); const existingTitles = new Set( @@ -507,7 +507,7 @@ export async function importFromStashAPP( ); logCallback(`Found ${existingTitles.size} existing videos in database`); - logCallback('Fetching existing cast from Kyoo API...'); + logCallback('Fetching existing cast from Omnyx API...'); const existingCastResponse = await fetch(`${BASE_URL}/api/cast`, {}); const existingCastData = await existingCastResponse.json(); const existingActors = new Map( diff --git a/src/lib/xbvrImporter.ts b/src/lib/xbvrImporter.ts index 05c39e9..b4c4f98 100644 --- a/src/lib/xbvrImporter.ts +++ b/src/lib/xbvrImporter.ts @@ -1,7 +1,7 @@ /** * XBVR Importer Module * - * This module provides functionality to import VR adult video content from an XBVR instance into the Kyoo media database. + * This module provides functionality to import VR adult video content from an XBVR instance into the Omnyx media database. * It fetches scene data from the DeoVR API endpoint, extracts actors and video details, and handles both new imports * and updates to existing entries. The module specifically filters for content in the 'Recent' scene group. * @@ -124,10 +124,10 @@ export type LogCallback = (message: string) => void; export type ProgressCallback = (progress: Partial) => void; /** - * Imports VR adult videos and actors from an XBVR instance into the Kyoo media database + * Imports VR adult videos and actors from an XBVR instance into the Omnyx media database * * This function performs the following steps: - * 1. Fetches existing media and cast from Kyoo to check for duplicates + * 1. Fetches existing media and cast from Omnyx to check for duplicates * 2. Fetches the scene list from the DeoVR API endpoint * 3. Extracts videos from the 'Recent' scene group * 4. Fetches detailed information for each video @@ -170,7 +170,7 @@ export async function importFromXBVR( logCallback('Starting DeoVR import...'); // Step 0: Fetch existing media and cast to check for duplicates - logCallback('Fetching existing media from Kyoo API...'); + logCallback('Fetching existing media from Omnyx API...'); const existingMediaResponse = await fetch(`${BASE_URL}/api/media?limit=1000`); const existingMediaData = await existingMediaResponse.json(); const existingTitles = new Set( @@ -178,7 +178,7 @@ export async function importFromXBVR( ); logCallback(`Found ${existingTitles.size} existing videos in database`); - logCallback('Fetching existing cast from Kyoo API...'); + logCallback('Fetching existing cast from Omnyx API...'); const existingCastResponse = await fetch(`${BASE_URL}/api/cast?limit=1000`); const existingCastData = await existingCastResponse.json(); const existingActors = new Map( diff --git a/src/types.ts b/src/types.ts index 8cd7554..e240dcc 100644 --- a/src/types.ts +++ b/src/types.ts @@ -119,10 +119,26 @@ export interface UserSettings { language: string; theme: 'light' | 'dark' | 'system'; jellyfinLibraryMappings?: string; // JSON string of LibraryMapping[] + + // Page Settings + pageTitle?: string; // Custom page title + favicon?: string; // Base64 encoded favicon/image + customColors?: CustomColors; // Custom color scheme + createdAt?: string; updatedAt?: string; } +export interface CustomColors { + primary?: string; // Primary accent color (hex) + secondary?: string; // Secondary accent color (hex) + background?: string; // Background color (hex) + surface?: string; // Surface/card color (hex) + text?: string; // Text color (hex) + muted?: string; // Muted text color (hex) + border?: string; // Border color (hex) +} + // Source to Category mapping - ensures sources are only used with appropriate categories export const SOURCE_CATEGORY_MAPPING: Record = { 'xbvr': ['Adult'], diff --git a/typedoc.json b/typedoc.json index edfd73e..206f513 100644 --- a/typedoc.json +++ b/typedoc.json @@ -7,7 +7,7 @@ "./src/lib/xbvrImporter.ts" ], "out": "docs", - "name": "Kyoo Importer Documentation", + "name": "Omnyx Importer Documentation", "theme": "default", "excludePrivate": true, "excludeProtected": false,