diff --git a/api_service/automate_process.py b/api_service/automate_process.py index 098fe45..750270b 100644 --- a/api_service/automate_process.py +++ b/api_service/automate_process.py @@ -42,7 +42,7 @@ async def create(cls): env_vars['SEER_USER_PSW'], env_vars['SEER_SESSION_TOKEN'] ) - await jellyseer_client.init() # Inizializzazione asincrona + await jellyseer_client.init() # TMDb client tmdb_client = TMDbClient(env_vars['TMDB_API_KEY'], instance.search_size) @@ -55,7 +55,7 @@ async def create(cls): instance.max_content, env_vars.get('JELLYFIN_LIBRARIES') ) - await jellyfin_client.init_existing_content() # Inizializzazione asincrona per JellyfinClient + await jellyfin_client.init_existing_content() instance.media_handler = JellyfinHandler(jellyfin_client, jellyseer_client, tmdb_client, instance.logger, instance.max_similar_movie, instance.max_similar_tv) elif instance.selected_service == 'plex': @@ -65,9 +65,10 @@ async def create(cls): max_content=instance.max_content, library_ids=env_vars.get('PLEX_LIBRARIES') ) + await plex_client.init_existing_content() instance.media_handler = PlexHandler(plex_client, jellyseer_client, tmdb_client, instance.logger, instance.max_similar_movie, instance.max_similar_tv) - return instance # Restituisce un'istanza completamente inizializzata + return instance async def run(self): """Main entry point to start the automation process.""" diff --git a/api_service/handler/jellyfin_handler.py b/api_service/handler/jellyfin_handler.py index 5a93888..1d3ba80 100644 --- a/api_service/handler/jellyfin_handler.py +++ b/api_service/handler/jellyfin_handler.py @@ -71,13 +71,28 @@ async def process_episode(self, user_id, item): async def request_similar_media(self, media_ids, media_type, max_items): """Request similar media (movie/TV show) via Jellyseer.""" + if not media_ids: + self.logger.info("No media IDs provided for similar media request.") + return + + tasks = [] for media in media_ids[:max_items]: - # Check if already requested or downloaded - if await self.jellyseer_client.check_already_requested(media['id'], media_type) or \ - await self.jellyseer_client.check_already_downloaded(media['id'], media_type, self.existing_content): - continue - - # Request media if not already requested or downloaded - await self.jellyseer_client.request_media(media_type, media['id']) - self.request_count += 1 - self.logger.info(f"New request made for {media_type}: {media['title']}!") \ No newline at end of file + media_id = media['id'] + media_title = media['title'] + + # Check if already downloaded or requested + already_requested = await self.jellyseer_client.check_already_requested(media_id, media_type) + already_downloaded = await self.jellyseer_client.check_already_downloaded(media_id, media_type, self.existing_content) + + if not already_requested and not already_downloaded: + tasks.append(self._request_media_and_log(media_type, media_id, media_title)) + else: + self.logger.info(f"Skipping [{media_type}, {media_title}]: already requested or downloaded.") + + await asyncio.gather(*tasks) + + async def _request_media_and_log(self, media_type, media_id, media_title): + """Helper method to request media and log the result.""" + await self.jellyseer_client.request_media(media_type, media_id) + self.request_count += 1 + self.logger.info(f"Requested {media_type}: {media_title}") diff --git a/api_service/handler/plex_handler.py b/api_service/handler/plex_handler.py index 5a42c3d..332ea0c 100644 --- a/api_service/handler/plex_handler.py +++ b/api_service/handler/plex_handler.py @@ -22,6 +22,7 @@ def __init__(self, plex_client:PlexClient, jellyseer_client:SeerClient, tmdb_cli self.max_similar_movie = max_similar_movie self.max_similar_tv = max_similar_tv self.request_count = 0 + self.existing_content = plex_client.existing_content async def process_recent_items(self): """Process recently watched items for Plex (without user context).""" @@ -33,7 +34,7 @@ async def process_recent_items(self): for response_item in recent_items_response: title = response_item.get('title', response_item.get('grandparentTitle')) self.logger.info(f"Processing item: {title}") - tasks.append(self.process_item(None, response_item, title)) # No user context needed for Plex + tasks.append(self.process_item(response_item, title)) # No user context needed for Plex if tasks: await asyncio.gather(*tasks) @@ -43,7 +44,7 @@ async def process_recent_items(self): else: self.logger.warning("Unexpected response format: expected a list") - async def process_item(self, user_id, item, title): + async def process_item(self, item, title): """Process an individual item (movie or TV show episode).""" item_type = item['type'].lower() @@ -53,9 +54,9 @@ async def process_item(self, user_id, item, title): key = self.extract_rating_key(item, item_type) if key: if item_type == 'movie': - await self.process_movie(user_id, key) + await self.process_movie(key) elif item_type == 'episode': - await self.process_episode(user_id, key) + await self.process_episode(key) else: raise ValueError(f"Missing key for {item_type} '{title}'. Cannot process this item. Skipping.") except Exception as e: @@ -66,14 +67,14 @@ def extract_rating_key(self, item, item_type): key = item.get('key') if item_type == 'movie' else item.get('grandparentKey') if item_type == 'episode' else None return key if key else None - async def process_movie(self, user_id, movie_key): + async def process_movie(self, movie_key): """Find similar movies via TMDb and request them via Jellyseer.""" tmdb_id = await self.plex_client.get_metadata_provider_id(movie_key) if tmdb_id: similar_movies = await self.tmdb_client.find_similar_movies(tmdb_id) await self.request_similar_media(similar_movies, 'movie', self.max_similar_movie) - async def process_episode(self, user_id, series_key): + async def process_episode(self, series_key): """Process a TV show episode by finding similar TV shows via TMDb.""" if series_key: tvdb_id = await self.plex_client.get_metadata_provider_id(series_key) @@ -83,11 +84,29 @@ async def process_episode(self, user_id, series_key): async def request_similar_media(self, media_ids, media_type, max_items): """Request similar media (movie/TV show) via Jellyseer.""" - if media_ids: - for media in media_ids[:max_items]: - if not await self.jellyseer_client.check_already_requested(media['id'], media_type): - await self.jellyseer_client.request_media(media_type, media['id']) - self.request_count += 1 - self.logger.info(f"Requested {media_type}: {media['title']}") - else: - self.logger.info(f"Skipping [{media_type}, {media['title']}]: already requested.") + if not media_ids: + self.logger.info("No media IDs provided for similar media request.") + return + + tasks = [] + for media in media_ids[:max_items]: + media_id = media['id'] + media_title = media['title'] + + # Check if already download or requested + already_requested = await self.jellyseer_client.check_already_requested(media_id, media_type) + already_downloaded = await self.jellyseer_client.check_already_downloaded(media_id, media_type, self.existing_content) + + if not already_requested and not already_downloaded: + tasks.append(self._request_media_and_log(media_type, media_id, media_title)) + else: + self.logger.info(f"Skipping [{media_type}, {media_title}]: already requested or downloaded.") + + await asyncio.gather(*tasks) + + async def _request_media_and_log(self, media_type, media_id, media_title): + """Helper method to request media and log the result.""" + await self.jellyseer_client.request_media(media_type, media_id) + self.request_count += 1 + self.logger.info(f"Requested {media_type}: {media_title}") + diff --git a/api_service/services/jellyseer/seer_client.py b/api_service/services/jellyseer/seer_client.py index 4966750..7c43581 100644 --- a/api_service/services/jellyseer/seer_client.py +++ b/api_service/services/jellyseer/seer_client.py @@ -107,6 +107,7 @@ async def _make_request(self, method, endpoint, use_cookie=False, retry_login=Tr """Unified API request handling with error handling.""" url = f"{self.api_url}/{endpoint}" headers, cookies = self._get_auth(use_cookie) or (None, None) + if headers is None: self.logger.error("Authentication missing for %s", url) return None diff --git a/api_service/services/plex/plex_client.py b/api_service/services/plex/plex_client.py index 0ae8a14..736f0ce 100644 --- a/api_service/services/plex/plex_client.py +++ b/api_service/services/plex/plex_client.py @@ -5,6 +5,7 @@ Classes: - PlexClient: A class that handles communication with the Plex API. """ +import asyncio import aiohttp from api_service.config.logger_manager import LoggerManager @@ -188,4 +189,74 @@ async def get_servers(self): return None except aiohttp.ClientError as e: print(f"Errore durante il recupero dei server Plex: {str(e)}") - return None \ No newline at end of file + return None + + async def init_existing_content(self): + self.logger.info('Searching all content in Plex') + self.existing_content = await self.get_all_library_items() + + async def get_all_library_items(self): + """ + Retrieves all items from the specified libraries or all libraries if no specific IDs are provided. + :return: A dictionary of items organized by library name. + """ + results_by_library = {} + libraries = await self.get_libraries() + + if not libraries: + self.logger.error("No libraries found.") + return None + + async with aiohttp.ClientSession() as session: + tasks = [ + self._fetch_library_items(session, library, results_by_library) + for library in libraries + ] + await asyncio.gather(*tasks) + + return results_by_library if results_by_library else None + + async def _fetch_library_items(self, session, library, results_by_library): + """ + Fetch items for a single library and update results_by_library. + """ + library_id = library.get('key') + library_name = library.get('title') + + if not isinstance(library_id, str) or not isinstance(library_name, str): + self.logger.error(f"Invalid library data - ID: {library_id}, Name: {library_name}") + return + + url = f"{self.api_url}/library/sections/{library_id}/all" + + try: + self.logger.debug(f"Requesting URL: {url} with headers: {self.headers} and timeout: {REQUEST_TIMEOUT}") + async with session.get(url, headers=self.headers, timeout=REQUEST_TIMEOUT) as response: + if response.status == 200: + library_items = await response.json() + items = library_items.get('MediaContainer', {}).get('Metadata', []) + + if isinstance(items, list): + # Extract TMDB ID for each element in list + processed_items = [] + for item in items: + library_type = 'tv' if item.get('type') == 'show' else 'movie' + item_id = item.get('key').replace('/children', '') + tmdb_id = await self.get_metadata_provider_id(item_id) + if tmdb_id: + item['tmdb_id'] = tmdb_id + processed_items.append(item) + + results_by_library[library_type] = processed_items + self.logger.info(f"Retrieved {len(processed_items)} items in {library_name} with TMDB IDs") + else: + self.logger.error(f"Expected list for items, got {type(items)}") + else: + self.logger.error("Failed to get items for library %s: %d", library_name, response.status) + + except aiohttp.ClientError as e: + self.logger.error(f"Client error for library {library_name}: {e}") + except asyncio.TimeoutError: + self.logger.error(f"Timeout error for library {library_name}") + except Exception as e: + self.logger.error(f"An unexpected error occurred for library {library_name}: {e}") diff --git a/client/src/assets/logo.png b/client/src/assets/logo.png index 38f6d53..3e8ecaa 100644 Binary files a/client/src/assets/logo.png and b/client/src/assets/logo.png differ diff --git a/client/src/assets/styles/wizard.css b/client/src/assets/styles/wizard.css index 2eb2c80..e1cc2d3 100644 --- a/client/src/assets/styles/wizard.css +++ b/client/src/assets/styles/wizard.css @@ -158,6 +158,13 @@ select { opacity: 0; } + .attached-logo { + width: 100px; + height: auto; + display: block; + margin: 0 auto; + margin-bottom: 30px; + } @media (max-width: 768px) { .wizard-content { diff --git a/client/src/components/ConfigSummary.vue b/client/src/components/ConfigSummary.vue index 2182140..007fd05 100644 --- a/client/src/components/ConfigSummary.vue +++ b/client/src/components/ConfigSummary.vue @@ -1,7 +1,9 @@