From dca54cf4fb510530969acce0c778558598fa5a3d Mon Sep 17 00:00:00 2001 From: JChris246 Date: Wed, 8 Mar 2023 21:30:28 -0400 Subject: [PATCH 01/19] feat:add flags to control the direction of syncing --- .env.sample | 6 ++++++ src/main.py | 59 ++++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 51 insertions(+), 14 deletions(-) diff --git a/.env.sample b/.env.sample index 708f826..c8ff463 100644 --- a/.env.sample +++ b/.env.sample @@ -55,6 +55,12 @@ PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2" ## Set to True if running into ssl certificate errors SSL_BYPASS = "False" +## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex +## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers +SYNC_FROM_PLEX_TO_JELLYFIN = "True" +SYNC_FROM_JELLYFIN_TO_PLEX = "True" +SYNC_FROM_PLEX_TO_PLEX = "True" +SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True" # Jellyfin diff --git a/src/main.py b/src/main.py index cb0442a..0b45ab9 100644 --- a/src/main.py +++ b/src/main.py @@ -264,6 +264,35 @@ def update_server_watched( ) +def should_sync_server(server_1_type, server_2_type): + sync_from_plex_to_jellyfin = str_to_bool( + os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True")) + sync_from_jelly_to_plex = str_to_bool( + os.getenv("SYNC_FROM_JELLYFIN_TO_PLEX", "True")) + sync_from_plex_to_plex = str_to_bool( + os.getenv("SYNC_FROM_PLEX_TO_PLEX", "True")) + sync_from_jelly_to_jellyfin = str_to_bool( + os.getenv("SYNC_FROM_JELLYFIN_TO_JELLYFIN", "True")) + + if server_1_type == "plex" and server_2_type == "plex" and not sync_from_plex_to_plex: + logger("Sync between plex and plex is disabled", 1) + return False + + if server_1_type == "plex" and server_2_type == "jellyfin" and not sync_from_jelly_to_plex: + logger("Sync from jellyfin to plex disabled", 1) + return False + + if server_1_type == "jellyfin" and server_2_type == "jellyfin" and not sync_from_jelly_to_jellyfin: + logger("Sync between jellyfin and jellyfin is disabled", 1) + return False + + if server_1_type == "jellyfin" and server_2_type == "plex" and not sync_from_plex_to_jellyfin: + logger("Sync from plex to jellyfin is disabled", 1) + return False + + return True + + def main_loop(): logfile = os.getenv("LOGFILE", "log.log") # Delete logfile if it exists @@ -370,21 +399,23 @@ def main_loop(): 1, ) - update_server_watched( - server_1, - server_2_watched_filtered, - user_mapping, - library_mapping, - dryrun, - ) + if should_sync_server(server_1[0], server_2[0]): + update_server_watched( + server_1, + server_2_watched_filtered, + user_mapping, + library_mapping, + dryrun, + ) - update_server_watched( - server_2, - server_1_watched_filtered, - user_mapping, - library_mapping, - dryrun, - ) + if should_sync_server(server_2[0], server_1[0]): + update_server_watched( + server_2, + server_1_watched_filtered, + user_mapping, + library_mapping, + dryrun, + ) def main(): From dc1fe1159073b906c8fc64a9e3fe153e9c0a9da1 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 21:49:56 -0700 Subject: [PATCH 02/19] Check for response status 200 on jellyfin query --- src/jellyfin.py | 22 ++++++++++++---------- src/main.py | 37 +++++++++++++++++++++++++++---------- src/plex.py | 6 +++++- 3 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/jellyfin.py b/src/jellyfin.py index 4034f7f..2b88a6a 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -38,17 +38,24 @@ class Jellyfin: async with session.get( self.baseurl + query, headers=headers ) as response: + if response.status != 200: + raise Exception( + f"Query failed with status {response.status} {response.reason}" + ) results = await response.json() elif query_type == "post": async with session.post( self.baseurl + query, headers=headers ) as response: + if response.status != 200: + raise Exception( + f"Query failed with status {response.status} {response.reason}" + ) results = await response.json() - if type(results) is str: - logger(f"Jellyfin: Query {query_type} {query} {results}", 2) - raise Exception(results) + if not isinstance(results, list) and not isinstance(results, dict): + raise Exception("Query result is not of type list or dict") # append identifiers to results if identifiers: @@ -57,7 +64,7 @@ class Jellyfin: return results except Exception as e: - logger(f"Jellyfin: Query failed {e}", 2) + logger(f"Jellyfin: Query {query_type} {query}\nResults {results}\n{e}", 2) raise Exception(e) async def get_users(self): @@ -393,12 +400,7 @@ class Jellyfin: # If there are multiple types in library raise error if types is None or len(types) < 1: - all_types = set( - [ - x["Type"] - for x in watched["Items"] - ] - ) + all_types = set([x["Type"] for x in watched["Items"]]) logger( f"Jellyfin: Skipping Library {library_title} found types: {types}, all types: {all_types}", 1, diff --git a/src/main.py b/src/main.py index 0b45ab9..8f281b2 100644 --- a/src/main.py +++ b/src/main.py @@ -18,7 +18,6 @@ load_dotenv(override=True) def setup_users( server_1, server_2, blacklist_users, whitelist_users, user_mapping=None ): - # generate list of users from server 1 and server 2 server_1_type = server_1[0] server_1_connection = server_1[1] @@ -266,27 +265,45 @@ def update_server_watched( def should_sync_server(server_1_type, server_2_type): sync_from_plex_to_jellyfin = str_to_bool( - os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True")) + os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True") + ) sync_from_jelly_to_plex = str_to_bool( - os.getenv("SYNC_FROM_JELLYFIN_TO_PLEX", "True")) - sync_from_plex_to_plex = str_to_bool( - os.getenv("SYNC_FROM_PLEX_TO_PLEX", "True")) + os.getenv("SYNC_FROM_JELLYFIN_TO_PLEX", "True") + ) + sync_from_plex_to_plex = str_to_bool(os.getenv("SYNC_FROM_PLEX_TO_PLEX", "True")) sync_from_jelly_to_jellyfin = str_to_bool( - os.getenv("SYNC_FROM_JELLYFIN_TO_JELLYFIN", "True")) + os.getenv("SYNC_FROM_JELLYFIN_TO_JELLYFIN", "True") + ) - if server_1_type == "plex" and server_2_type == "plex" and not sync_from_plex_to_plex: + if ( + server_1_type == "plex" + and server_2_type == "plex" + and not sync_from_plex_to_plex + ): logger("Sync between plex and plex is disabled", 1) return False - if server_1_type == "plex" and server_2_type == "jellyfin" and not sync_from_jelly_to_plex: + if ( + server_1_type == "plex" + and server_2_type == "jellyfin" + and not sync_from_jelly_to_plex + ): logger("Sync from jellyfin to plex disabled", 1) return False - if server_1_type == "jellyfin" and server_2_type == "jellyfin" and not sync_from_jelly_to_jellyfin: + if ( + server_1_type == "jellyfin" + and server_2_type == "jellyfin" + and not sync_from_jelly_to_jellyfin + ): logger("Sync between jellyfin and jellyfin is disabled", 1) return False - if server_1_type == "jellyfin" and server_2_type == "plex" and not sync_from_plex_to_jellyfin: + if ( + server_1_type == "jellyfin" + and server_2_type == "plex" + and not sync_from_plex_to_jellyfin + ): logger("Sync from plex to jellyfin is disabled", 1) return False diff --git a/src/plex.py b/src/plex.py index 29e8571..a688c3c 100644 --- a/src/plex.py +++ b/src/plex.py @@ -12,6 +12,7 @@ from src.functions import ( future_thread_executor, ) + # Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186 class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter): def init_poolmanager(self, connections, maxsize, block=..., **pool_kwargs): @@ -235,7 +236,10 @@ def update_user_watched(user, user_plex, library, videos, dryrun): ).group(1) # If episode provider source and episode provider id are in videos_episodes_ids exactly, then the episode is in the list - if episode_guid_source in videos_episodes_ids.keys(): + if ( + episode_guid_source + in videos_episodes_ids.keys() + ): if ( episode_guid_id in videos_episodes_ids[episode_guid_source] From 796be47a631a59b500c1945ba7d3d5fc5440d55c Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 12:51:41 -0700 Subject: [PATCH 03/19] Move lots of setup_users to functions --- src/functions.py | 75 ++++++++++++++++++++++++++++++++++ src/main.py | 103 +++++++---------------------------------------- 2 files changed, 90 insertions(+), 88 deletions(-) diff --git a/src/functions.py b/src/functions.py index 500098c..ccf2a50 100644 --- a/src/functions.py +++ b/src/functions.py @@ -455,6 +455,81 @@ def is_episode_in_dict(episode, episode_watched_list_2_keys_dict): return False +def generate_user_list(server): + # generate list of users from server 1 and server 2 + server_type = server[0] + server_connection = server[1] + + server_users = [] + if server_type == "plex": + server_users = [x.title.lower() for x in server_connection.users] + elif server_type == "jellyfin": + server_users = [key.lower() for key in server_connection.users.keys()] + + return server_users + +def combine_user_lists(server_1_users, server_2_users, user_mapping): + # combined list of overlapping users from plex and jellyfin + users = {} + + for server_1_user in server_1_users: + if user_mapping: + jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user) + if jellyfin_plex_mapped_user: + users[server_1_user] = jellyfin_plex_mapped_user + continue + + if server_1_user in server_2_users: + users[server_1_user] = server_1_user + + for server_2_user in server_2_users: + if user_mapping: + plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user) + if plex_jellyfin_mapped_user: + users[plex_jellyfin_mapped_user] = server_2_user + continue + + if server_2_user in server_1_users: + users[server_2_user] = server_2_user + + return users + +def filter_user_lists(users, blacklist_users, whitelist_users): + users_filtered = {} + for user in users: + # whitelist_user is not empty and user lowercase is not in whitelist lowercase + if len(whitelist_users) > 0: + if user not in whitelist_users and users[user] not in whitelist_users: + logger(f"{user} or {users[user]} is not in whitelist", 1) + continue + + if user not in blacklist_users and users[user] not in blacklist_users: + users_filtered[user] = users[user] + + return users_filtered + +def generate_server_users(server, users): + server_users = None + + if server[0] == "plex": + server_users = [] + for plex_user in server[1].users: + if ( + plex_user.title.lower() in users.keys() + or plex_user.title.lower() in users.values() + ): + server_users.append(plex_user) + elif server[0] == "jellyfin": + server_users = {} + for jellyfin_user, jellyfin_id in server[1].users.items(): + if ( + jellyfin_user.lower() in users.keys() + or jellyfin_user.lower() in users.values() + ): + server_users[jellyfin_user] = jellyfin_id + + return server_users + def future_thread_executor(args: list, workers: int = -1): futures_list = [] results = [] diff --git a/src/main.py b/src/main.py index 8f281b2..eec2ea6 100644 --- a/src/main.py +++ b/src/main.py @@ -8,6 +8,10 @@ from src.functions import ( search_mapping, cleanup_watched, setup_black_white_lists, + generate_user_list, + combine_user_lists, + filter_user_lists, + generate_server_users, ) from src.plex import Plex from src.jellyfin import Jellyfin @@ -15,107 +19,30 @@ from src.jellyfin import Jellyfin load_dotenv(override=True) + + def setup_users( server_1, server_2, blacklist_users, whitelist_users, user_mapping=None ): - # generate list of users from server 1 and server 2 - server_1_type = server_1[0] - server_1_connection = server_1[1] - server_2_type = server_2[0] - server_2_connection = server_2[1] - logger(f"Server 1: {server_1_type} {server_1_connection}", 0) - logger(f"Server 2: {server_2_type} {server_2_connection}", 0) - - server_1_users = [] - if server_1_type == "plex": - server_1_users = [x.title.lower() for x in server_1_connection.users] - elif server_1_type == "jellyfin": - server_1_users = [key.lower() for key in server_1_connection.users.keys()] - - server_2_users = [] - if server_2_type == "plex": - server_2_users = [x.title.lower() for x in server_2_connection.users] - elif server_2_type == "jellyfin": - server_2_users = [key.lower() for key in server_2_connection.users.keys()] - - # combined list of overlapping users from plex and jellyfin - users = {} - - for server_1_user in server_1_users: - if user_mapping: - jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user) - if jellyfin_plex_mapped_user: - users[server_1_user] = jellyfin_plex_mapped_user - continue - - if server_1_user in server_2_users: - users[server_1_user] = server_1_user - - for server_2_user in server_2_users: - if user_mapping: - plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user) - if plex_jellyfin_mapped_user: - users[plex_jellyfin_mapped_user] = server_2_user - continue - - if server_2_user in server_1_users: - users[server_2_user] = server_2_user + server_1_users = generate_user_list(server_1) + server_2_users = generate_user_list(server_2) + users = combine_user_lists(server_1_users, server_2_users, user_mapping) logger(f"User list that exist on both servers {users}", 1) - users_filtered = {} - for user in users: - # whitelist_user is not empty and user lowercase is not in whitelist lowercase - if len(whitelist_users) > 0: - if user not in whitelist_users and users[user] not in whitelist_users: - logger(f"{user} or {users[user]} is not in whitelist", 1) - continue - - if user not in blacklist_users and users[user] not in blacklist_users: - users_filtered[user] = users[user] - + users_filtered = filter_user_lists(users, blacklist_users, whitelist_users) logger(f"Filtered user list {users_filtered}", 1) - if server_1_type == "plex": - output_server_1_users = [] - for plex_user in server_1_connection.users: - if ( - plex_user.title.lower() in users_filtered.keys() - or plex_user.title.lower() in users_filtered.values() - ): - output_server_1_users.append(plex_user) - elif server_1_type == "jellyfin": - output_server_1_users = {} - for jellyfin_user, jellyfin_id in server_1_connection.users.items(): - if ( - jellyfin_user.lower() in users_filtered.keys() - or jellyfin_user.lower() in users_filtered.values() - ): - output_server_1_users[jellyfin_user] = jellyfin_id + output_server_1_users = generate_server_users(server_1, users_filtered) + output_server_2_users = generate_server_users(server_2, users_filtered) - if server_2_type == "plex": - output_server_2_users = [] - for plex_user in server_2_connection.users: - if ( - plex_user.title.lower() in users_filtered.keys() - or plex_user.title.lower() in users_filtered.values() - ): - output_server_2_users.append(plex_user) - elif server_2_type == "jellyfin": - output_server_2_users = {} - for jellyfin_user, jellyfin_id in server_2_connection.users.items(): - if ( - jellyfin_user.lower() in users_filtered.keys() - or jellyfin_user.lower() in users_filtered.values() - ): - output_server_2_users[jellyfin_user] = jellyfin_id - - if len(output_server_1_users) == 0: + # Check if users is none or empty + if output_server_1_users is None or len(output_server_1_users) == 0: raise Exception( f"No users found for server 1 {server_1_type}, users found {users}, filtered users {users_filtered}, server 1 users {server_1_connection.users}" ) - if len(output_server_2_users) == 0: + if output_server_2_users is None or len(output_server_2_users) == 0: raise Exception( f"No users found for server 2 {server_2_type}, users found {users} filtered users {users_filtered}, server 2 users {server_2_connection.users}" ) From 2bad887659eaa47738321b30f54eaa755436d423 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 13:10:54 -0700 Subject: [PATCH 04/19] Seperate out functions to seperate scripts. --- src/functions.py | 376 +---------------------------------------------- src/jellyfin.py | 6 +- src/library.py | 118 +++++++++++++++ src/main.py | 9 +- src/plex.py | 7 +- src/users.py | 79 ++++++++++ src/watched.py | 193 ++++++++++++++++++++++++ 7 files changed, 408 insertions(+), 380 deletions(-) create mode 100644 src/library.py create mode 100644 src/users.py create mode 100644 src/watched.py diff --git a/src/functions.py b/src/functions.py index ccf2a50..606ad81 100644 --- a/src/functions.py +++ b/src/functions.py @@ -1,4 +1,4 @@ -import os, copy +import os from concurrent.futures import ThreadPoolExecutor from dotenv import load_dotenv @@ -156,380 +156,6 @@ def setup_black_white_lists( whitelist_users, ) - -def check_skip_logic( - library_title, - library_type, - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - library_mapping, -): - skip_reason = None - - if isinstance(library_type, (list, tuple, set)): - for library_type_item in library_type: - if library_type_item.lower() in blacklist_library_type: - skip_reason = "is blacklist_library_type" - else: - if library_type.lower() in blacklist_library_type: - skip_reason = "is blacklist_library_type" - - if library_title.lower() in [x.lower() for x in blacklist_library]: - skip_reason = "is blacklist_library" - - library_other = None - if library_mapping: - library_other = search_mapping(library_mapping, library_title) - if library_other: - if library_other.lower() in [x.lower() for x in blacklist_library]: - skip_reason = "is blacklist_library" - - if len(whitelist_library_type) > 0: - if isinstance(library_type, (list, tuple, set)): - for library_type_item in library_type: - if library_type_item.lower() not in whitelist_library_type: - skip_reason = "is not whitelist_library_type" - else: - if library_type.lower() not in whitelist_library_type: - skip_reason = "is not whitelist_library_type" - - # if whitelist is not empty and library is not in whitelist - if len(whitelist_library) > 0: - if library_title.lower() not in [x.lower() for x in whitelist_library]: - skip_reason = "is not whitelist_library" - - if library_other: - if library_other.lower() not in [x.lower() for x in whitelist_library]: - skip_reason = "is not whitelist_library" - - return skip_reason - - -def generate_library_guids_dict(user_list: dict): - show_output_dict = {} - episode_output_dict = {} - movies_output_dict = {} - - # Handle the case where user_list is empty or does not contain the expected keys and values - if not user_list: - return show_output_dict, episode_output_dict, movies_output_dict - - try: - show_output_keys = user_list.keys() - show_output_keys = [dict(x) for x in list(show_output_keys)] - for show_key in show_output_keys: - for provider_key, provider_value in show_key.items(): - # Skip title - if provider_key.lower() == "title": - continue - if provider_key.lower() not in show_output_dict: - show_output_dict[provider_key.lower()] = [] - if provider_key.lower() == "locations": - for show_location in provider_value: - show_output_dict[provider_key.lower()].append(show_location) - else: - show_output_dict[provider_key.lower()].append( - provider_value.lower() - ) - except Exception: - logger("Generating show_output_dict failed, skipping", 1) - - try: - for show in user_list: - for season in user_list[show]: - for episode in user_list[show][season]: - for episode_key, episode_value in episode.items(): - if episode_key.lower() not in episode_output_dict: - episode_output_dict[episode_key.lower()] = [] - if episode_key == "locations": - for episode_location in episode_value: - episode_output_dict[episode_key.lower()].append( - episode_location - ) - else: - episode_output_dict[episode_key.lower()].append( - episode_value.lower() - ) - except Exception: - logger("Generating episode_output_dict failed, skipping", 1) - - try: - for movie in user_list: - for movie_key, movie_value in movie.items(): - if movie_key.lower() not in movies_output_dict: - movies_output_dict[movie_key.lower()] = [] - if movie_key == "locations": - for movie_location in movie_value: - movies_output_dict[movie_key.lower()].append(movie_location) - else: - movies_output_dict[movie_key.lower()].append(movie_value.lower()) - except Exception: - logger("Generating movies_output_dict failed, skipping", 1) - - return show_output_dict, episode_output_dict, movies_output_dict - - -def combine_watched_dicts(dicts: list): - combined_dict = {} - for single_dict in dicts: - for key, value in single_dict.items(): - if key not in combined_dict: - combined_dict[key] = {} - for subkey, subvalue in value.items(): - if subkey in combined_dict[key]: - # If the subkey already exists in the combined dictionary, - # check if the values are different and raise an exception if they are - if combined_dict[key][subkey] != subvalue: - raise ValueError( - f"Conflicting values for subkey '{subkey}' under key '{key}'" - ) - else: - # If the subkey does not exist in the combined dictionary, add it - combined_dict[key][subkey] = subvalue - - return combined_dict - - -def cleanup_watched( - watched_list_1, watched_list_2, user_mapping=None, library_mapping=None -): - modified_watched_list_1 = copy.deepcopy(watched_list_1) - - # remove entries from watched_list_1 that are in watched_list_2 - for user_1 in watched_list_1: - user_other = None - if user_mapping: - user_other = search_mapping(user_mapping, user_1) - user_2 = get_other(watched_list_2, user_1, user_other) - if user_2 is None: - continue - - for library_1 in watched_list_1[user_1]: - library_other = None - if library_mapping: - library_other = search_mapping(library_mapping, library_1) - library_2 = get_other(watched_list_2[user_2], library_1, library_other) - if library_2 is None: - continue - - ( - _, - episode_watched_list_2_keys_dict, - movies_watched_list_2_keys_dict, - ) = generate_library_guids_dict(watched_list_2[user_2][library_2]) - - # Movies - if isinstance(watched_list_1[user_1][library_1], list): - for movie in watched_list_1[user_1][library_1]: - if is_movie_in_dict(movie, movies_watched_list_2_keys_dict): - logger(f"Removing {movie} from {library_1}", 3) - modified_watched_list_1[user_1][library_1].remove(movie) - - # TV Shows - elif isinstance(watched_list_1[user_1][library_1], dict): - for show_key_1 in watched_list_1[user_1][library_1].keys(): - show_key_dict = dict(show_key_1) - for season in watched_list_1[user_1][library_1][show_key_1]: - for episode in watched_list_1[user_1][library_1][show_key_1][ - season - ]: - if is_episode_in_dict( - episode, episode_watched_list_2_keys_dict - ): - if ( - episode - in modified_watched_list_1[user_1][library_1][ - show_key_1 - ][season] - ): - logger( - f"Removing {episode} from {show_key_dict['title']}", - 3, - ) - modified_watched_list_1[user_1][library_1][ - show_key_1 - ][season].remove(episode) - - # Remove empty seasons - if ( - len( - modified_watched_list_1[user_1][library_1][show_key_1][ - season - ] - ) - == 0 - ): - if ( - season - in modified_watched_list_1[user_1][library_1][ - show_key_1 - ] - ): - logger( - f"Removing {season} from {show_key_dict['title']} because it is empty", - 3, - ) - del modified_watched_list_1[user_1][library_1][ - show_key_1 - ][season] - - # Remove empty shows - if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: - if show_key_1 in modified_watched_list_1[user_1][library_1]: - logger( - f"Removing {show_key_dict['title']} because it is empty", - 3, - ) - del modified_watched_list_1[user_1][library_1][show_key_1] - - for user_1 in watched_list_1: - for library_1 in watched_list_1[user_1]: - if library_1 in modified_watched_list_1[user_1]: - # If library is empty then remove it - if len(modified_watched_list_1[user_1][library_1]) == 0: - logger(f"Removing {library_1} from {user_1} because it is empty", 1) - del modified_watched_list_1[user_1][library_1] - - if user_1 in modified_watched_list_1: - # If user is empty delete user - if len(modified_watched_list_1[user_1]) == 0: - logger(f"Removing {user_1} from watched list 1 because it is empty", 1) - del modified_watched_list_1[user_1] - - return modified_watched_list_1 - - -def get_other(watched_list_2, object_1, object_2): - if object_1 in watched_list_2: - return object_1 - elif object_2 in watched_list_2: - return object_2 - else: - logger(f"{object_1} and {object_2} not found in watched list 2", 1) - return None - - -def is_movie_in_dict(movie, movies_watched_list_2_keys_dict): - # Iterate through the keys and values of the movie dictionary - for movie_key, movie_value in movie.items(): - # If the key is "locations", check if the "locations" key is present in the movies_watched_list_2_keys_dict dictionary - if movie_key == "locations": - if "locations" in movies_watched_list_2_keys_dict.keys(): - # Iterate through the locations in the movie dictionary - for location in movie_value: - # If the location is in the movies_watched_list_2_keys_dict dictionary, return True - if location in movies_watched_list_2_keys_dict["locations"]: - return True - # If the key is not "locations", check if the movie_key is present in the movies_watched_list_2_keys_dict dictionary - else: - if movie_key in movies_watched_list_2_keys_dict.keys(): - # If the movie_value is in the movies_watched_list_2_keys_dict dictionary, return True - if movie_value in movies_watched_list_2_keys_dict[movie_key]: - return True - - # If the loop completes without finding a match, return False - return False - - -def is_episode_in_dict(episode, episode_watched_list_2_keys_dict): - # Iterate through the keys and values of the episode dictionary - for episode_key, episode_value in episode.items(): - # If the key is "locations", check if the "locations" key is present in the episode_watched_list_2_keys_dict dictionary - if episode_key == "locations": - if "locations" in episode_watched_list_2_keys_dict.keys(): - # Iterate through the locations in the episode dictionary - for location in episode_value: - # If the location is in the episode_watched_list_2_keys_dict dictionary, return True - if location in episode_watched_list_2_keys_dict["locations"]: - return True - # If the key is not "locations", check if the episode_key is present in the episode_watched_list_2_keys_dict dictionary - else: - if episode_key in episode_watched_list_2_keys_dict.keys(): - # If the episode_value is in the episode_watched_list_2_keys_dict dictionary, return True - if episode_value in episode_watched_list_2_keys_dict[episode_key]: - return True - - # If the loop completes without finding a match, return False - return False - - -def generate_user_list(server): - # generate list of users from server 1 and server 2 - server_type = server[0] - server_connection = server[1] - - server_users = [] - if server_type == "plex": - server_users = [x.title.lower() for x in server_connection.users] - elif server_type == "jellyfin": - server_users = [key.lower() for key in server_connection.users.keys()] - - return server_users - -def combine_user_lists(server_1_users, server_2_users, user_mapping): - # combined list of overlapping users from plex and jellyfin - users = {} - - for server_1_user in server_1_users: - if user_mapping: - jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user) - if jellyfin_plex_mapped_user: - users[server_1_user] = jellyfin_plex_mapped_user - continue - - if server_1_user in server_2_users: - users[server_1_user] = server_1_user - - for server_2_user in server_2_users: - if user_mapping: - plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user) - if plex_jellyfin_mapped_user: - users[plex_jellyfin_mapped_user] = server_2_user - continue - - if server_2_user in server_1_users: - users[server_2_user] = server_2_user - - return users - -def filter_user_lists(users, blacklist_users, whitelist_users): - users_filtered = {} - for user in users: - # whitelist_user is not empty and user lowercase is not in whitelist lowercase - if len(whitelist_users) > 0: - if user not in whitelist_users and users[user] not in whitelist_users: - logger(f"{user} or {users[user]} is not in whitelist", 1) - continue - - if user not in blacklist_users and users[user] not in blacklist_users: - users_filtered[user] = users[user] - - return users_filtered - -def generate_server_users(server, users): - server_users = None - - if server[0] == "plex": - server_users = [] - for plex_user in server[1].users: - if ( - plex_user.title.lower() in users.keys() - or plex_user.title.lower() in users.values() - ): - server_users.append(plex_user) - elif server[0] == "jellyfin": - server_users = {} - for jellyfin_user, jellyfin_id in server[1].users.items(): - if ( - jellyfin_user.lower() in users.keys() - or jellyfin_user.lower() in users.values() - ): - server_users[jellyfin_user] = jellyfin_id - - return server_users - def future_thread_executor(args: list, workers: int = -1): futures_list = [] results = [] diff --git a/src/jellyfin.py b/src/jellyfin.py index 2b88a6a..52f0a65 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -1,13 +1,17 @@ import asyncio, aiohttp, traceback + from src.functions import ( logger, search_mapping, +) +from src.library import ( check_skip_logic, generate_library_guids_dict, +) +from src.watched import ( combine_watched_dicts, ) - class Jellyfin: def __init__(self, baseurl, token): self.baseurl = baseurl diff --git a/src/library.py b/src/library.py new file mode 100644 index 0000000..91dcc87 --- /dev/null +++ b/src/library.py @@ -0,0 +1,118 @@ +from src.functions import ( + logger, + search_mapping, +) + +def check_skip_logic( + library_title, + library_type, + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + library_mapping, +): + skip_reason = None + + if isinstance(library_type, (list, tuple, set)): + for library_type_item in library_type: + if library_type_item.lower() in blacklist_library_type: + skip_reason = "is blacklist_library_type" + else: + if library_type.lower() in blacklist_library_type: + skip_reason = "is blacklist_library_type" + + if library_title.lower() in [x.lower() for x in blacklist_library]: + skip_reason = "is blacklist_library" + + library_other = None + if library_mapping: + library_other = search_mapping(library_mapping, library_title) + if library_other: + if library_other.lower() in [x.lower() for x in blacklist_library]: + skip_reason = "is blacklist_library" + + if len(whitelist_library_type) > 0: + if isinstance(library_type, (list, tuple, set)): + for library_type_item in library_type: + if library_type_item.lower() not in whitelist_library_type: + skip_reason = "is not whitelist_library_type" + else: + if library_type.lower() not in whitelist_library_type: + skip_reason = "is not whitelist_library_type" + + # if whitelist is not empty and library is not in whitelist + if len(whitelist_library) > 0: + if library_title.lower() not in [x.lower() for x in whitelist_library]: + skip_reason = "is not whitelist_library" + + if library_other: + if library_other.lower() not in [x.lower() for x in whitelist_library]: + skip_reason = "is not whitelist_library" + + return skip_reason + + +def generate_library_guids_dict(user_list: dict): + show_output_dict = {} + episode_output_dict = {} + movies_output_dict = {} + + # Handle the case where user_list is empty or does not contain the expected keys and values + if not user_list: + return show_output_dict, episode_output_dict, movies_output_dict + + try: + show_output_keys = user_list.keys() + show_output_keys = [dict(x) for x in list(show_output_keys)] + for show_key in show_output_keys: + for provider_key, provider_value in show_key.items(): + # Skip title + if provider_key.lower() == "title": + continue + if provider_key.lower() not in show_output_dict: + show_output_dict[provider_key.lower()] = [] + if provider_key.lower() == "locations": + for show_location in provider_value: + show_output_dict[provider_key.lower()].append(show_location) + else: + show_output_dict[provider_key.lower()].append( + provider_value.lower() + ) + except Exception: + logger("Generating show_output_dict failed, skipping", 1) + + try: + for show in user_list: + for season in user_list[show]: + for episode in user_list[show][season]: + for episode_key, episode_value in episode.items(): + if episode_key.lower() not in episode_output_dict: + episode_output_dict[episode_key.lower()] = [] + if episode_key == "locations": + for episode_location in episode_value: + episode_output_dict[episode_key.lower()].append( + episode_location + ) + else: + episode_output_dict[episode_key.lower()].append( + episode_value.lower() + ) + except Exception: + logger("Generating episode_output_dict failed, skipping", 1) + + try: + for movie in user_list: + for movie_key, movie_value in movie.items(): + if movie_key.lower() not in movies_output_dict: + movies_output_dict[movie_key.lower()] = [] + if movie_key == "locations": + for movie_location in movie_value: + movies_output_dict[movie_key.lower()].append(movie_location) + else: + movies_output_dict[movie_key.lower()].append(movie_value.lower()) + except Exception: + logger("Generating movies_output_dict failed, skipping", 1) + + return show_output_dict, episode_output_dict, movies_output_dict + \ No newline at end of file diff --git a/src/main.py b/src/main.py index eec2ea6..c053303 100644 --- a/src/main.py +++ b/src/main.py @@ -5,14 +5,19 @@ from time import sleep, perf_counter from src.functions import ( logger, str_to_bool, - search_mapping, - cleanup_watched, setup_black_white_lists, + +) +from src.users import ( generate_user_list, combine_user_lists, filter_user_lists, generate_server_users, ) +from src.watched import ( + cleanup_watched, +) + from src.plex import Plex from src.jellyfin import Jellyfin diff --git a/src/plex.py b/src/plex.py index a688c3c..acea0ed 100644 --- a/src/plex.py +++ b/src/plex.py @@ -7,10 +7,13 @@ from plexapi.myplex import MyPlexAccount from src.functions import ( logger, search_mapping, - check_skip_logic, - generate_library_guids_dict, future_thread_executor, ) +from src.library import ( + check_skip_logic, + generate_library_guids_dict, +) + # Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186 diff --git a/src/users.py b/src/users.py new file mode 100644 index 0000000..5cc69d8 --- /dev/null +++ b/src/users.py @@ -0,0 +1,79 @@ +from src.functions import ( + logger, + search_mapping, +) + +def generate_user_list(server): + # generate list of users from server 1 and server 2 + server_type = server[0] + server_connection = server[1] + + server_users = [] + if server_type == "plex": + server_users = [x.title.lower() for x in server_connection.users] + elif server_type == "jellyfin": + server_users = [key.lower() for key in server_connection.users.keys()] + + return server_users + +def combine_user_lists(server_1_users, server_2_users, user_mapping): + # combined list of overlapping users from plex and jellyfin + users = {} + + for server_1_user in server_1_users: + if user_mapping: + jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user) + if jellyfin_plex_mapped_user: + users[server_1_user] = jellyfin_plex_mapped_user + continue + + if server_1_user in server_2_users: + users[server_1_user] = server_1_user + + for server_2_user in server_2_users: + if user_mapping: + plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user) + if plex_jellyfin_mapped_user: + users[plex_jellyfin_mapped_user] = server_2_user + continue + + if server_2_user in server_1_users: + users[server_2_user] = server_2_user + + return users + +def filter_user_lists(users, blacklist_users, whitelist_users): + users_filtered = {} + for user in users: + # whitelist_user is not empty and user lowercase is not in whitelist lowercase + if len(whitelist_users) > 0: + if user not in whitelist_users and users[user] not in whitelist_users: + logger(f"{user} or {users[user]} is not in whitelist", 1) + continue + + if user not in blacklist_users and users[user] not in blacklist_users: + users_filtered[user] = users[user] + + return users_filtered + +def generate_server_users(server, users): + server_users = None + + if server[0] == "plex": + server_users = [] + for plex_user in server[1].users: + if ( + plex_user.title.lower() in users.keys() + or plex_user.title.lower() in users.values() + ): + server_users.append(plex_user) + elif server[0] == "jellyfin": + server_users = {} + for jellyfin_user, jellyfin_id in server[1].users.items(): + if ( + jellyfin_user.lower() in users.keys() + or jellyfin_user.lower() in users.values() + ): + server_users[jellyfin_user] = jellyfin_id + + return server_users \ No newline at end of file diff --git a/src/watched.py b/src/watched.py new file mode 100644 index 0000000..155feef --- /dev/null +++ b/src/watched.py @@ -0,0 +1,193 @@ +import copy + +from src.functions import ( + logger, + search_mapping, +) + +from src.library import ( + generate_library_guids_dict +) + +def combine_watched_dicts(dicts: list): + combined_dict = {} + for single_dict in dicts: + for key, value in single_dict.items(): + if key not in combined_dict: + combined_dict[key] = {} + for subkey, subvalue in value.items(): + if subkey in combined_dict[key]: + # If the subkey already exists in the combined dictionary, + # check if the values are different and raise an exception if they are + if combined_dict[key][subkey] != subvalue: + raise ValueError( + f"Conflicting values for subkey '{subkey}' under key '{key}'" + ) + else: + # If the subkey does not exist in the combined dictionary, add it + combined_dict[key][subkey] = subvalue + + return combined_dict + + +def cleanup_watched( + watched_list_1, watched_list_2, user_mapping=None, library_mapping=None +): + modified_watched_list_1 = copy.deepcopy(watched_list_1) + + # remove entries from watched_list_1 that are in watched_list_2 + for user_1 in watched_list_1: + user_other = None + if user_mapping: + user_other = search_mapping(user_mapping, user_1) + user_2 = get_other(watched_list_2, user_1, user_other) + if user_2 is None: + continue + + for library_1 in watched_list_1[user_1]: + library_other = None + if library_mapping: + library_other = search_mapping(library_mapping, library_1) + library_2 = get_other(watched_list_2[user_2], library_1, library_other) + if library_2 is None: + continue + + ( + _, + episode_watched_list_2_keys_dict, + movies_watched_list_2_keys_dict, + ) = generate_library_guids_dict(watched_list_2[user_2][library_2]) + + # Movies + if isinstance(watched_list_1[user_1][library_1], list): + for movie in watched_list_1[user_1][library_1]: + if is_movie_in_dict(movie, movies_watched_list_2_keys_dict): + logger(f"Removing {movie} from {library_1}", 3) + modified_watched_list_1[user_1][library_1].remove(movie) + + # TV Shows + elif isinstance(watched_list_1[user_1][library_1], dict): + for show_key_1 in watched_list_1[user_1][library_1].keys(): + show_key_dict = dict(show_key_1) + for season in watched_list_1[user_1][library_1][show_key_1]: + for episode in watched_list_1[user_1][library_1][show_key_1][ + season + ]: + if is_episode_in_dict( + episode, episode_watched_list_2_keys_dict + ): + if ( + episode + in modified_watched_list_1[user_1][library_1][ + show_key_1 + ][season] + ): + logger( + f"Removing {episode} from {show_key_dict['title']}", + 3, + ) + modified_watched_list_1[user_1][library_1][ + show_key_1 + ][season].remove(episode) + + # Remove empty seasons + if ( + len( + modified_watched_list_1[user_1][library_1][show_key_1][ + season + ] + ) + == 0 + ): + if ( + season + in modified_watched_list_1[user_1][library_1][ + show_key_1 + ] + ): + logger( + f"Removing {season} from {show_key_dict['title']} because it is empty", + 3, + ) + del modified_watched_list_1[user_1][library_1][ + show_key_1 + ][season] + + # Remove empty shows + if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: + if show_key_1 in modified_watched_list_1[user_1][library_1]: + logger( + f"Removing {show_key_dict['title']} because it is empty", + 3, + ) + del modified_watched_list_1[user_1][library_1][show_key_1] + + for user_1 in watched_list_1: + for library_1 in watched_list_1[user_1]: + if library_1 in modified_watched_list_1[user_1]: + # If library is empty then remove it + if len(modified_watched_list_1[user_1][library_1]) == 0: + logger(f"Removing {library_1} from {user_1} because it is empty", 1) + del modified_watched_list_1[user_1][library_1] + + if user_1 in modified_watched_list_1: + # If user is empty delete user + if len(modified_watched_list_1[user_1]) == 0: + logger(f"Removing {user_1} from watched list 1 because it is empty", 1) + del modified_watched_list_1[user_1] + + return modified_watched_list_1 + + +def get_other(watched_list_2, object_1, object_2): + if object_1 in watched_list_2: + return object_1 + elif object_2 in watched_list_2: + return object_2 + else: + logger(f"{object_1} and {object_2} not found in watched list 2", 1) + return None + + +def is_movie_in_dict(movie, movies_watched_list_2_keys_dict): + # Iterate through the keys and values of the movie dictionary + for movie_key, movie_value in movie.items(): + # If the key is "locations", check if the "locations" key is present in the movies_watched_list_2_keys_dict dictionary + if movie_key == "locations": + if "locations" in movies_watched_list_2_keys_dict.keys(): + # Iterate through the locations in the movie dictionary + for location in movie_value: + # If the location is in the movies_watched_list_2_keys_dict dictionary, return True + if location in movies_watched_list_2_keys_dict["locations"]: + return True + # If the key is not "locations", check if the movie_key is present in the movies_watched_list_2_keys_dict dictionary + else: + if movie_key in movies_watched_list_2_keys_dict.keys(): + # If the movie_value is in the movies_watched_list_2_keys_dict dictionary, return True + if movie_value in movies_watched_list_2_keys_dict[movie_key]: + return True + + # If the loop completes without finding a match, return False + return False + + +def is_episode_in_dict(episode, episode_watched_list_2_keys_dict): + # Iterate through the keys and values of the episode dictionary + for episode_key, episode_value in episode.items(): + # If the key is "locations", check if the "locations" key is present in the episode_watched_list_2_keys_dict dictionary + if episode_key == "locations": + if "locations" in episode_watched_list_2_keys_dict.keys(): + # Iterate through the locations in the episode dictionary + for location in episode_value: + # If the location is in the episode_watched_list_2_keys_dict dictionary, return True + if location in episode_watched_list_2_keys_dict["locations"]: + return True + # If the key is not "locations", check if the episode_key is present in the episode_watched_list_2_keys_dict dictionary + else: + if episode_key in episode_watched_list_2_keys_dict.keys(): + # If the episode_value is in the episode_watched_list_2_keys_dict dictionary, return True + if episode_value in episode_watched_list_2_keys_dict[episode_key]: + return True + + # If the loop completes without finding a match, return False + return False From 03de3affd795d5cfa9d75e0741cff4b9154115c0 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 13:28:54 -0700 Subject: [PATCH 05/19] Cleanup, seperate black/white lists setup --- README.md | 1 - src/black_white.py | 140 +++++++++++++++++++++++++++++++++++++++++++++ src/functions.py | 101 -------------------------------- src/jellyfin.py | 1 + src/library.py | 2 +- src/main.py | 5 +- src/users.py | 10 +++- src/watched.py | 5 +- test/test_main.py | 2 +- 9 files changed, 153 insertions(+), 114 deletions(-) create mode 100644 src/black_white.py diff --git a/README.md b/README.md index 6473355..c34ed60 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,6 @@ JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096" JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2" ``` - ## Installation ### Baremetal diff --git a/src/black_white.py b/src/black_white.py new file mode 100644 index 0000000..7763ecc --- /dev/null +++ b/src/black_white.py @@ -0,0 +1,140 @@ +from src.functions import logger, search_mapping + + +def setup_black_white_lists( + blacklist_library: str, + whitelist_library: str, + blacklist_library_type: str, + whitelist_library_type: str, + blacklist_users: str, + whitelist_users: str, + library_mapping=None, + user_mapping=None, +): + + blacklist_library, blacklist_library_type, blacklist_users = setup_black_lists( + blacklist_library, + blacklist_library_type, + blacklist_users, + library_mapping, + user_mapping, + ) + + whitelist_library, whitelist_library_type, whitelist_users = setup_white_lists( + whitelist_library, + whitelist_library_type, + whitelist_users, + library_mapping, + user_mapping, + ) + + return ( + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + blacklist_users, + whitelist_users, + ) + + +def setup_black_lists( + blacklist_library, + blacklist_library_type, + blacklist_users, + library_mapping=None, + user_mapping=None, +): + if blacklist_library: + if len(blacklist_library) > 0: + blacklist_library = blacklist_library.split(",") + blacklist_library = [x.strip() for x in blacklist_library] + if library_mapping: + temp_library = [] + for library in blacklist_library: + library_other = search_mapping(library_mapping, library) + if library_other: + temp_library.append(library_other) + + blacklist_library = blacklist_library + temp_library + else: + blacklist_library = [] + logger(f"Blacklist Library: {blacklist_library}", 1) + + if blacklist_library_type: + if len(blacklist_library_type) > 0: + blacklist_library_type = blacklist_library_type.split(",") + blacklist_library_type = [x.lower().strip() for x in blacklist_library_type] + else: + blacklist_library_type = [] + logger(f"Blacklist Library Type: {blacklist_library_type}", 1) + + if blacklist_users: + if len(blacklist_users) > 0: + blacklist_users = blacklist_users.split(",") + blacklist_users = [x.lower().strip() for x in blacklist_users] + if user_mapping: + temp_users = [] + for user in blacklist_users: + user_other = search_mapping(user_mapping, user) + if user_other: + temp_users.append(user_other) + + blacklist_users = blacklist_users + temp_users + else: + blacklist_users = [] + logger(f"Blacklist Users: {blacklist_users}", 1) + + return blacklist_library, blacklist_library_type, blacklist_users + + +def setup_white_lists( + whitelist_library, + whitelist_library_type, + whitelist_users, + library_mapping=None, + user_mapping=None, +): + if whitelist_library: + if len(whitelist_library) > 0: + whitelist_library = whitelist_library.split(",") + whitelist_library = [x.strip() for x in whitelist_library] + if library_mapping: + temp_library = [] + for library in whitelist_library: + library_other = search_mapping(library_mapping, library) + if library_other: + temp_library.append(library_other) + + whitelist_library = whitelist_library + temp_library + else: + whitelist_library = [] + logger(f"Whitelist Library: {whitelist_library}", 1) + + if whitelist_library_type: + if len(whitelist_library_type) > 0: + whitelist_library_type = whitelist_library_type.split(",") + whitelist_library_type = [x.lower().strip() for x in whitelist_library_type] + else: + whitelist_library_type = [] + logger(f"Whitelist Library Type: {whitelist_library_type}", 1) + + if whitelist_users: + if len(whitelist_users) > 0: + whitelist_users = whitelist_users.split(",") + whitelist_users = [x.lower().strip() for x in whitelist_users] + if user_mapping: + temp_users = [] + for user in whitelist_users: + user_other = search_mapping(user_mapping, user) + if user_other: + temp_users.append(user_other) + + whitelist_users = whitelist_users + temp_users + else: + whitelist_users = [] + else: + whitelist_users = [] + logger(f"Whitelist Users: {whitelist_users}", 1) + + return whitelist_library, whitelist_library_type, whitelist_users diff --git a/src/functions.py b/src/functions.py index 606ad81..5f5ffcd 100644 --- a/src/functions.py +++ b/src/functions.py @@ -55,107 +55,6 @@ def search_mapping(dictionary: dict, key_value: str): return None -def setup_black_white_lists( - blacklist_library: str, - whitelist_library: str, - blacklist_library_type: str, - whitelist_library_type: str, - blacklist_users: str, - whitelist_users: str, - library_mapping=None, - user_mapping=None, -): - if blacklist_library: - if len(blacklist_library) > 0: - blacklist_library = blacklist_library.split(",") - blacklist_library = [x.strip() for x in blacklist_library] - if library_mapping: - temp_library = [] - for library in blacklist_library: - library_other = search_mapping(library_mapping, library) - if library_other: - temp_library.append(library_other) - - blacklist_library = blacklist_library + temp_library - else: - blacklist_library = [] - logger(f"Blacklist Library: {blacklist_library}", 1) - - if whitelist_library: - if len(whitelist_library) > 0: - whitelist_library = whitelist_library.split(",") - whitelist_library = [x.strip() for x in whitelist_library] - if library_mapping: - temp_library = [] - for library in whitelist_library: - library_other = search_mapping(library_mapping, library) - if library_other: - temp_library.append(library_other) - - whitelist_library = whitelist_library + temp_library - else: - whitelist_library = [] - logger(f"Whitelist Library: {whitelist_library}", 1) - - if blacklist_library_type: - if len(blacklist_library_type) > 0: - blacklist_library_type = blacklist_library_type.split(",") - blacklist_library_type = [x.lower().strip() for x in blacklist_library_type] - else: - blacklist_library_type = [] - logger(f"Blacklist Library Type: {blacklist_library_type}", 1) - - if whitelist_library_type: - if len(whitelist_library_type) > 0: - whitelist_library_type = whitelist_library_type.split(",") - whitelist_library_type = [x.lower().strip() for x in whitelist_library_type] - else: - whitelist_library_type = [] - logger(f"Whitelist Library Type: {whitelist_library_type}", 1) - - if blacklist_users: - if len(blacklist_users) > 0: - blacklist_users = blacklist_users.split(",") - blacklist_users = [x.lower().strip() for x in blacklist_users] - if user_mapping: - temp_users = [] - for user in blacklist_users: - user_other = search_mapping(user_mapping, user) - if user_other: - temp_users.append(user_other) - - blacklist_users = blacklist_users + temp_users - else: - blacklist_users = [] - logger(f"Blacklist Users: {blacklist_users}", 1) - - if whitelist_users: - if len(whitelist_users) > 0: - whitelist_users = whitelist_users.split(",") - whitelist_users = [x.lower().strip() for x in whitelist_users] - if user_mapping: - temp_users = [] - for user in whitelist_users: - user_other = search_mapping(user_mapping, user) - if user_other: - temp_users.append(user_other) - - whitelist_users = whitelist_users + temp_users - else: - whitelist_users = [] - else: - whitelist_users = [] - logger(f"Whitelist Users: {whitelist_users}", 1) - - return ( - blacklist_library, - whitelist_library, - blacklist_library_type, - whitelist_library_type, - blacklist_users, - whitelist_users, - ) - def future_thread_executor(args: list, workers: int = -1): futures_list = [] results = [] diff --git a/src/jellyfin.py b/src/jellyfin.py index 52f0a65..ce0119b 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -12,6 +12,7 @@ from src.watched import ( combine_watched_dicts, ) + class Jellyfin: def __init__(self, baseurl, token): self.baseurl = baseurl diff --git a/src/library.py b/src/library.py index 91dcc87..c4fd66e 100644 --- a/src/library.py +++ b/src/library.py @@ -3,6 +3,7 @@ from src.functions import ( search_mapping, ) + def check_skip_logic( library_title, library_type, @@ -115,4 +116,3 @@ def generate_library_guids_dict(user_list: dict): logger("Generating movies_output_dict failed, skipping", 1) return show_output_dict, episode_output_dict, movies_output_dict - \ No newline at end of file diff --git a/src/main.py b/src/main.py index c053303..4076e9a 100644 --- a/src/main.py +++ b/src/main.py @@ -5,8 +5,6 @@ from time import sleep, perf_counter from src.functions import ( logger, str_to_bool, - setup_black_white_lists, - ) from src.users import ( generate_user_list, @@ -17,6 +15,7 @@ from src.users import ( from src.watched import ( cleanup_watched, ) +from src.black_white import setup_black_white_lists from src.plex import Plex from src.jellyfin import Jellyfin @@ -24,8 +23,6 @@ from src.jellyfin import Jellyfin load_dotenv(override=True) - - def setup_users( server_1, server_2, blacklist_users, whitelist_users, user_mapping=None ): diff --git a/src/users.py b/src/users.py index 5cc69d8..2a888e8 100644 --- a/src/users.py +++ b/src/users.py @@ -3,11 +3,12 @@ from src.functions import ( search_mapping, ) + def generate_user_list(server): # generate list of users from server 1 and server 2 server_type = server[0] server_connection = server[1] - + server_users = [] if server_type == "plex": server_users = [x.title.lower() for x in server_connection.users] @@ -16,6 +17,7 @@ def generate_user_list(server): return server_users + def combine_user_lists(server_1_users, server_2_users, user_mapping): # combined list of overlapping users from plex and jellyfin users = {} @@ -42,6 +44,7 @@ def combine_user_lists(server_1_users, server_2_users, user_mapping): return users + def filter_user_lists(users, blacklist_users, whitelist_users): users_filtered = {} for user in users: @@ -56,6 +59,7 @@ def filter_user_lists(users, blacklist_users, whitelist_users): return users_filtered + def generate_server_users(server, users): server_users = None @@ -75,5 +79,5 @@ def generate_server_users(server, users): or jellyfin_user.lower() in users.values() ): server_users[jellyfin_user] = jellyfin_id - - return server_users \ No newline at end of file + + return server_users diff --git a/src/watched.py b/src/watched.py index 155feef..fce1708 100644 --- a/src/watched.py +++ b/src/watched.py @@ -5,9 +5,8 @@ from src.functions import ( search_mapping, ) -from src.library import ( - generate_library_guids_dict -) +from src.library import generate_library_guids_dict + def combine_watched_dicts(dicts: list): combined_dict = {} diff --git a/test/test_main.py b/test/test_main.py index fbe990b..611ba12 100644 --- a/test/test_main.py +++ b/test/test_main.py @@ -13,7 +13,7 @@ parent = os.path.dirname(current) # the sys.path. sys.path.append(parent) -from src.main import setup_black_white_lists +from src.black_white import setup_black_white_lists def test_setup_black_white_lists(): From 1f16fcb8eb9314426633ff0a2787b3f13994e447 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 13:47:24 -0700 Subject: [PATCH 06/19] Seperate check_skip_logic, append reasons --- src/library.py | 73 ++++++++++++++++++++++++++++++++++++-------------- src/watched.py | 6 ++--- 2 files changed, 56 insertions(+), 23 deletions(-) diff --git a/src/library.py b/src/library.py index c4fd66e..fb08a1a 100644 --- a/src/library.py +++ b/src/library.py @@ -11,49 +11,82 @@ def check_skip_logic( whitelist_library, blacklist_library_type, whitelist_library_type, - library_mapping, + library_mapping=None, ): skip_reason = None - - if isinstance(library_type, (list, tuple, set)): - for library_type_item in library_type: - if library_type_item.lower() in blacklist_library_type: - skip_reason = "is blacklist_library_type" - else: - if library_type.lower() in blacklist_library_type: - skip_reason = "is blacklist_library_type" - - if library_title.lower() in [x.lower() for x in blacklist_library]: - skip_reason = "is blacklist_library" - library_other = None if library_mapping: library_other = search_mapping(library_mapping, library_title) + + skip_reason_black = check_blacklist_logic(library_title, library_type, blacklist_library, blacklist_library_type, library_mapping, library_other) + skip_reason_white = check_whitelist_logic(library_title, library_type, whitelist_library, whitelist_library_type, library_mapping, library_other) + + # Combine skip reasons + if skip_reason_black: + skip_reason = skip_reason_black + + if skip_reason_white: + if skip_reason: + skip_reason = skip_reason + " and " + skip_reason_white + else: + skip_reason = skip_reason_white + + return skip_reason + +def check_blacklist_logic(library_title, library_type, blacklist_library, blacklist_library_type, library_mapping=None, library_other=None): + skip_reason = None + if isinstance(library_type, (list, tuple, set)): + for library_type_item in library_type: + if library_type_item.lower() in blacklist_library_type: + skip_reason = "is in blacklist_library_type" + else: + if library_type.lower() in blacklist_library_type: + skip_reason = "is in blacklist_library_type" + + if library_title.lower() in [x.lower() for x in blacklist_library]: + if skip_reason: + skip_reason = skip_reason + " and " + "is in blacklist_library" + else: + skip_reason = "is in blacklist_library" + + if library_other: if library_other.lower() in [x.lower() for x in blacklist_library]: - skip_reason = "is blacklist_library" + if skip_reason: + skip_reason = skip_reason + " and " + "is in blacklist_library" + else: + skip_reason = "is in blacklist_library" + + return skip_reason +def check_whitelist_logic(library_title, library_type, whitelist_library, whitelist_library_type, library_mapping=None, library_other=None): + skip_reason = None if len(whitelist_library_type) > 0: if isinstance(library_type, (list, tuple, set)): for library_type_item in library_type: if library_type_item.lower() not in whitelist_library_type: - skip_reason = "is not whitelist_library_type" + skip_reason = "is not in whitelist_library_type" else: if library_type.lower() not in whitelist_library_type: - skip_reason = "is not whitelist_library_type" + skip_reason = "is not in whitelist_library_type" # if whitelist is not empty and library is not in whitelist if len(whitelist_library) > 0: if library_title.lower() not in [x.lower() for x in whitelist_library]: - skip_reason = "is not whitelist_library" + if skip_reason: + skip_reason = skip_reason + " and " + "is not in whitelist_library" + else: + skip_reason = "is not in whitelist_library" if library_other: if library_other.lower() not in [x.lower() for x in whitelist_library]: - skip_reason = "is not whitelist_library" - + if skip_reason: + skip_reason = skip_reason + " and " + "is not in whitelist_library" + else: + skip_reason = "is not in whitelist_library" + return skip_reason - def generate_library_guids_dict(user_list: dict): show_output_dict = {} episode_output_dict = {} diff --git a/src/watched.py b/src/watched.py index fce1708..1cd3a03 100644 --- a/src/watched.py +++ b/src/watched.py @@ -138,10 +138,10 @@ def cleanup_watched( return modified_watched_list_1 -def get_other(watched_list_2, object_1, object_2): - if object_1 in watched_list_2: +def get_other(watched_list, object_1, object_2): + if object_1 in watched_list: return object_1 - elif object_2 in watched_list_2: + elif object_2 in watched_list: return object_2 else: logger(f"{object_1} and {object_2} not found in watched list 2", 1) From ed24948dee673510972d3978fffa1f47432ae34d Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 13:59:19 -0700 Subject: [PATCH 07/19] Better logging on library skip --- src/jellyfin.py | 2 +- src/library.py | 29 ++++++++++++++--------------- src/plex.py | 2 +- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/src/jellyfin.py b/src/jellyfin.py index ce0119b..166818b 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -398,7 +398,7 @@ class Jellyfin: if skip_reason: logger( - f"Jellyfin: Skipping library {library_title} {skip_reason}", + f"Jellyfin: Skipping library {library_title}: {skip_reason}", 1, ) continue diff --git a/src/library.py b/src/library.py index fb08a1a..b002fda 100644 --- a/src/library.py +++ b/src/library.py @@ -38,24 +38,23 @@ def check_blacklist_logic(library_title, library_type, blacklist_library, blackl if isinstance(library_type, (list, tuple, set)): for library_type_item in library_type: if library_type_item.lower() in blacklist_library_type: - skip_reason = "is in blacklist_library_type" + skip_reason = f"{library_type_item} is in blacklist_library_type" else: if library_type.lower() in blacklist_library_type: - skip_reason = "is in blacklist_library_type" + skip_reason = f"{library_type} is in blacklist_library_type" if library_title.lower() in [x.lower() for x in blacklist_library]: if skip_reason: - skip_reason = skip_reason + " and " + "is in blacklist_library" + skip_reason = skip_reason + " and " + f"{library_title} is in blacklist_library" else: - skip_reason = "is in blacklist_library" - + skip_reason = f"{library_title} is in blacklist_library" if library_other: if library_other.lower() in [x.lower() for x in blacklist_library]: if skip_reason: - skip_reason = skip_reason + " and " + "is in blacklist_library" + skip_reason = skip_reason + " and " + f"{library_other} is in blacklist_library" else: - skip_reason = "is in blacklist_library" + skip_reason = f"{library_other} is in blacklist_library" return skip_reason @@ -65,25 +64,25 @@ def check_whitelist_logic(library_title, library_type, whitelist_library, whitel if isinstance(library_type, (list, tuple, set)): for library_type_item in library_type: if library_type_item.lower() not in whitelist_library_type: - skip_reason = "is not in whitelist_library_type" + skip_reason = f"{library_type_item} is not in whitelist_library_type" else: if library_type.lower() not in whitelist_library_type: - skip_reason = "is not in whitelist_library_type" + skip_reason = f"{library_type} is not in whitelist_library_type" # if whitelist is not empty and library is not in whitelist if len(whitelist_library) > 0: if library_title.lower() not in [x.lower() for x in whitelist_library]: if skip_reason: - skip_reason = skip_reason + " and " + "is not in whitelist_library" + skip_reason = skip_reason + " and " + f"{library_title} is not in whitelist_library" else: - skip_reason = "is not in whitelist_library" - + skip_reason = f"{library_title} is not in whitelist_library" + if library_other: - if library_other.lower() not in [x.lower() for x in whitelist_library]: + if library_other.lower() not in [x.lower() for x in whitelist_library]: if skip_reason: - skip_reason = skip_reason + " and " + "is not in whitelist_library" + skip_reason = skip_reason + " and " + f"{library_other} is not in whitelist_library" else: - skip_reason = "is not in whitelist_library" + skip_reason = f"{library_other} is not in whitelist_library" return skip_reason diff --git a/src/plex.py b/src/plex.py index acea0ed..10b8859 100644 --- a/src/plex.py +++ b/src/plex.py @@ -388,7 +388,7 @@ class Plex: if skip_reason: logger( - f"Plex: Skipping library {library_title} {skip_reason}", 1 + f"Plex: Skipping library {library_title}: {skip_reason}", 1 ) continue From 404089dfca7a5122b33c98d0d83f07cdf1f986f8 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 15:02:53 -0700 Subject: [PATCH 08/19] Seperate generate_library_guids_dict --- src/library.py | 114 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 87 insertions(+), 27 deletions(-) diff --git a/src/library.py b/src/library.py index b002fda..b80bb43 100644 --- a/src/library.py +++ b/src/library.py @@ -17,14 +17,28 @@ def check_skip_logic( library_other = None if library_mapping: library_other = search_mapping(library_mapping, library_title) - - skip_reason_black = check_blacklist_logic(library_title, library_type, blacklist_library, blacklist_library_type, library_mapping, library_other) - skip_reason_white = check_whitelist_logic(library_title, library_type, whitelist_library, whitelist_library_type, library_mapping, library_other) - + + skip_reason_black = check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_mapping, + library_other, + ) + skip_reason_white = check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_mapping, + library_other, + ) + # Combine skip reasons if skip_reason_black: skip_reason = skip_reason_black - + if skip_reason_white: if skip_reason: skip_reason = skip_reason + " and " + skip_reason_white @@ -33,7 +47,15 @@ def check_skip_logic( return skip_reason -def check_blacklist_logic(library_title, library_type, blacklist_library, blacklist_library_type, library_mapping=None, library_other=None): + +def check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_mapping=None, + library_other=None, +): skip_reason = None if isinstance(library_type, (list, tuple, set)): for library_type_item in library_type: @@ -45,26 +67,40 @@ def check_blacklist_logic(library_title, library_type, blacklist_library, blackl if library_title.lower() in [x.lower() for x in blacklist_library]: if skip_reason: - skip_reason = skip_reason + " and " + f"{library_title} is in blacklist_library" + skip_reason = ( + skip_reason + " and " + f"{library_title} is in blacklist_library" + ) else: skip_reason = f"{library_title} is in blacklist_library" - + if library_other: if library_other.lower() in [x.lower() for x in blacklist_library]: if skip_reason: - skip_reason = skip_reason + " and " + f"{library_other} is in blacklist_library" + skip_reason = ( + skip_reason + " and " + f"{library_other} is in blacklist_library" + ) else: skip_reason = f"{library_other} is in blacklist_library" - + return skip_reason -def check_whitelist_logic(library_title, library_type, whitelist_library, whitelist_library_type, library_mapping=None, library_other=None): + +def check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_mapping=None, + library_other=None, +): skip_reason = None if len(whitelist_library_type) > 0: if isinstance(library_type, (list, tuple, set)): for library_type_item in library_type: if library_type_item.lower() not in whitelist_library_type: - skip_reason = f"{library_type_item} is not in whitelist_library_type" + skip_reason = ( + f"{library_type_item} is not in whitelist_library_type" + ) else: if library_type.lower() not in whitelist_library_type: skip_reason = f"{library_type} is not in whitelist_library_type" @@ -73,29 +109,31 @@ def check_whitelist_logic(library_title, library_type, whitelist_library, whitel if len(whitelist_library) > 0: if library_title.lower() not in [x.lower() for x in whitelist_library]: if skip_reason: - skip_reason = skip_reason + " and " + f"{library_title} is not in whitelist_library" + skip_reason = ( + skip_reason + + " and " + + f"{library_title} is not in whitelist_library" + ) else: skip_reason = f"{library_title} is not in whitelist_library" - + if library_other: - if library_other.lower() not in [x.lower() for x in whitelist_library]: + if library_other.lower() not in [x.lower() for x in whitelist_library]: if skip_reason: - skip_reason = skip_reason + " and " + f"{library_other} is not in whitelist_library" + skip_reason = ( + skip_reason + + " and " + + f"{library_other} is not in whitelist_library" + ) else: skip_reason = f"{library_other} is not in whitelist_library" - + return skip_reason -def generate_library_guids_dict(user_list: dict): - show_output_dict = {} - episode_output_dict = {} - movies_output_dict = {} - - # Handle the case where user_list is empty or does not contain the expected keys and values - if not user_list: - return show_output_dict, episode_output_dict, movies_output_dict - +def show_title_dict(user_list: dict): try: + show_output_dict = {} + show_output_keys = user_list.keys() show_output_keys = [dict(x) for x in list(show_output_keys)] for show_key in show_output_keys: @@ -112,10 +150,15 @@ def generate_library_guids_dict(user_list: dict): show_output_dict[provider_key.lower()].append( provider_value.lower() ) + + return show_output_dict except Exception: logger("Generating show_output_dict failed, skipping", 1) - + return {} + +def episode_title_dict(user_list: dict): try: + episode_output_dict = {} for show in user_list: for season in user_list[show]: for episode in user_list[show][season]: @@ -131,10 +174,15 @@ def generate_library_guids_dict(user_list: dict): episode_output_dict[episode_key.lower()].append( episode_value.lower() ) + + return episode_output_dict except Exception: logger("Generating episode_output_dict failed, skipping", 1) + return {} +def movies_title_dict(user_list: dict): try: + movies_output_dict = {} for movie in user_list: for movie_key, movie_value in movie.items(): if movie_key.lower() not in movies_output_dict: @@ -144,7 +192,19 @@ def generate_library_guids_dict(user_list: dict): movies_output_dict[movie_key.lower()].append(movie_location) else: movies_output_dict[movie_key.lower()].append(movie_value.lower()) + + return movies_output_dict except Exception: logger("Generating movies_output_dict failed, skipping", 1) + return {} + +def generate_library_guids_dict(user_list: dict): + # Handle the case where user_list is empty or does not contain the expected keys and values + if not user_list: + return {}, {}, {} + + show_output_dict = show_title_dict(user_list) + episode_output_dict = episode_title_dict(user_list) + movies_output_dict = movies_title_dict(user_list) return show_output_dict, episode_output_dict, movies_output_dict From 1ee055faf5a2e42fd99cf726fdc82921074e9d0a Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Fri, 27 Jan 2023 15:04:16 -0700 Subject: [PATCH 09/19] format --- src/library.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/library.py b/src/library.py index b80bb43..7e53d1a 100644 --- a/src/library.py +++ b/src/library.py @@ -130,6 +130,7 @@ def check_whitelist_logic( return skip_reason + def show_title_dict(user_list: dict): try: show_output_dict = {} @@ -150,12 +151,13 @@ def show_title_dict(user_list: dict): show_output_dict[provider_key.lower()].append( provider_value.lower() ) - + return show_output_dict except Exception: logger("Generating show_output_dict failed, skipping", 1) return {} - + + def episode_title_dict(user_list: dict): try: episode_output_dict = {} @@ -174,12 +176,13 @@ def episode_title_dict(user_list: dict): episode_output_dict[episode_key.lower()].append( episode_value.lower() ) - + return episode_output_dict except Exception: logger("Generating episode_output_dict failed, skipping", 1) return {} + def movies_title_dict(user_list: dict): try: movies_output_dict = {} @@ -192,12 +195,13 @@ def movies_title_dict(user_list: dict): movies_output_dict[movie_key.lower()].append(movie_location) else: movies_output_dict[movie_key.lower()].append(movie_value.lower()) - + return movies_output_dict except Exception: logger("Generating movies_output_dict failed, skipping", 1) return {} + def generate_library_guids_dict(user_list: dict): # Handle the case where user_list is empty or does not contain the expected keys and values if not user_list: @@ -205,6 +209,6 @@ def generate_library_guids_dict(user_list: dict): show_output_dict = show_title_dict(user_list) episode_output_dict = episode_title_dict(user_list) - movies_output_dict = movies_title_dict(user_list) + movies_output_dict = movies_title_dict(user_list) return show_output_dict, episode_output_dict, movies_output_dict From b2a06b8fd3623c02fa8be58d8e415eb7d46c03b0 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 22:01:44 -0700 Subject: [PATCH 10/19] Add tests for black_white and watched --- test/test_black_white.py | 78 ++ ...ain_cleanup_watched.py => test_watched.py} | 711 ++++++++++-------- 2 files changed, 488 insertions(+), 301 deletions(-) create mode 100644 test/test_black_white.py rename test/{test_main_cleanup_watched.py => test_watched.py} (67%) diff --git a/test/test_black_white.py b/test/test_black_white.py new file mode 100644 index 0000000..be50007 --- /dev/null +++ b/test/test_black_white.py @@ -0,0 +1,78 @@ +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +from src.black_white import setup_black_white_lists + + +def test_setup_black_white_lists(): + # Simple + blacklist_library = "library1, library2" + whitelist_library = "library1, library2" + blacklist_library_type = "library_type1, library_type2" + whitelist_library_type = "library_type1, library_type2" + blacklist_users = "user1, user2" + whitelist_users = "user1, user2" + + ( + results_blacklist_library, + return_whitelist_library, + return_blacklist_library_type, + return_whitelist_library_type, + return_blacklist_users, + return_whitelist_users, + ) = setup_black_white_lists( + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + blacklist_users, + whitelist_users, + ) + + assert results_blacklist_library == ["library1", "library2"] + assert return_whitelist_library == ["library1", "library2"] + assert return_blacklist_library_type == ["library_type1", "library_type2"] + assert return_whitelist_library_type == ["library_type1", "library_type2"] + assert return_blacklist_users == ["user1", "user2"] + assert return_whitelist_users == ["user1", "user2"] + + # Library Mapping and user mapping + library_mapping = {"library1": "library3"} + user_mapping = {"user1": "user3"} + + ( + results_blacklist_library, + return_whitelist_library, + return_blacklist_library_type, + return_whitelist_library_type, + return_blacklist_users, + return_whitelist_users, + ) = setup_black_white_lists( + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + blacklist_users, + whitelist_users, + library_mapping, + user_mapping, + ) + + assert results_blacklist_library == ["library1", "library2", "library3"] + assert return_whitelist_library == ["library1", "library2", "library3"] + assert return_blacklist_library_type == ["library_type1", "library_type2"] + assert return_whitelist_library_type == ["library_type1", "library_type2"] + assert return_blacklist_users == ["user1", "user2", "user3"] + assert return_whitelist_users == ["user1", "user2", "user3"] diff --git a/test/test_main_cleanup_watched.py b/test/test_watched.py similarity index 67% rename from test/test_main_cleanup_watched.py rename to test/test_watched.py index 2625d49..5f3de96 100644 --- a/test/test_main_cleanup_watched.py +++ b/test/test_watched.py @@ -1,301 +1,410 @@ -import sys -import os - -# getting the name of the directory -# where the this file is present. -current = os.path.dirname(os.path.realpath(__file__)) - -# Getting the parent directory name -# where the current directory is present. -parent = os.path.dirname(current) - -# adding the parent directory to -# the sys.path. -sys.path.append(parent) - -from src.main import cleanup_watched - -tv_shows_watched_list_1 = { - frozenset( - { - ("tvdb", "75710"), - ("title", "Criminal Minds"), - ("imdb", "tt0452046"), - ("locations", ("Criminal Minds",)), - ("tmdb", "4057"), - } - ): { - "Season 1": [ - { - "imdb": "tt0550489", - "tmdb": "282843", - "tvdb": "176357", - "locations": ( - "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", - ), - }, - { - "imdb": "tt0550487", - "tmdb": "282861", - "tvdb": "300385", - "locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",), - }, - ] - }, - frozenset({("title", "Test"), ("locations", ("Test",))}): { - "Season 1": [ - {"locations": ("Test S01E01.mkv",)}, - {"locations": ("Test S01E02.mkv",)}, - ] - }, -} - -movies_watched_list_1 = [ - { - "imdb": "tt2380307", - "tmdb": "354912", - "title": "Coco", - "locations": ("Coco (2017) Remux-1080p.mkv",), - }, - { - "tmdbcollection": "448150", - "imdb": "tt1431045", - "tmdb": "293660", - "title": "Deadpool", - "locations": ("Deadpool (2016) Remux-1080p.mkv",), - }, -] - -tv_shows_watched_list_2 = { - frozenset( - { - ("tvdb", "75710"), - ("title", "Criminal Minds"), - ("imdb", "tt0452046"), - ("locations", ("Criminal Minds",)), - ("tmdb", "4057"), - } - ): { - "Season 1": [ - { - "imdb": "tt0550487", - "tmdb": "282861", - "tvdb": "300385", - "locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",), - }, - { - "imdb": "tt0550498", - "tmdb": "282865", - "tvdb": "300474", - "locations": ( - "Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv", - ), - }, - ] - }, - frozenset({("title", "Test"), ("locations", ("Test",))}): { - "Season 1": [ - {"locations": ("Test S01E02.mkv",)}, - {"locations": ("Test S01E03.mkv",)}, - ] - }, -} - -movies_watched_list_2 = [ - { - "imdb": "tt2380307", - "tmdb": "354912", - "title": "Coco", - "locations": ("Coco (2017) Remux-1080p.mkv",), - }, - { - "imdb": "tt0384793", - "tmdb": "9788", - "tvdb": "9103", - "title": "Accepted", - "locations": ("Accepted (2006) Remux-1080p.mkv",), - }, -] - -# Test to see if objects get deleted all the way up to the root. -tv_shows_2_watched_list_1 = { - frozenset( - { - ("tvdb", "75710"), - ("title", "Criminal Minds"), - ("imdb", "tt0452046"), - ("locations", ("Criminal Minds",)), - ("tmdb", "4057"), - } - ): { - "Season 1": [ - { - "imdb": "tt0550489", - "tmdb": "282843", - "tvdb": "176357", - "locations": ( - "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", - ), - }, - ] - } -} - -expected_tv_show_watched_list_1 = { - frozenset( - { - ("tvdb", "75710"), - ("title", "Criminal Minds"), - ("imdb", "tt0452046"), - ("locations", ("Criminal Minds",)), - ("tmdb", "4057"), - } - ): { - "Season 1": [ - { - "imdb": "tt0550489", - "tmdb": "282843", - "tvdb": "176357", - "locations": ( - "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", - ), - } - ] - }, - frozenset({("title", "Test"), ("locations", ("Test",))}): { - "Season 1": [{"locations": ("Test S01E01.mkv",)}] - }, -} - -expected_movie_watched_list_1 = [ - { - "tmdbcollection": "448150", - "imdb": "tt1431045", - "tmdb": "293660", - "title": "Deadpool", - "locations": ("Deadpool (2016) Remux-1080p.mkv",), - } -] - -expected_tv_show_watched_list_2 = { - frozenset( - { - ("tvdb", "75710"), - ("title", "Criminal Minds"), - ("imdb", "tt0452046"), - ("locations", ("Criminal Minds",)), - ("tmdb", "4057"), - } - ): { - "Season 1": [ - { - "imdb": "tt0550498", - "tmdb": "282865", - "tvdb": "300474", - "locations": ( - "Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv", - ), - } - ] - }, - frozenset({("title", "Test"), ("locations", ("Test",))}): { - "Season 1": [{"locations": ("Test S01E03.mkv",)}] - }, -} - -expected_movie_watched_list_2 = [ - { - "imdb": "tt0384793", - "tmdb": "9788", - "tvdb": "9103", - "title": "Accepted", - "locations": ("Accepted (2006) Remux-1080p.mkv",), - } -] - - -def test_simple_cleanup_watched(): - user_watched_list_1 = { - "user1": { - "TV Shows": tv_shows_watched_list_1, - "Movies": movies_watched_list_1, - "Other Shows": tv_shows_2_watched_list_1, - }, - } - user_watched_list_2 = { - "user1": { - "TV Shows": tv_shows_watched_list_2, - "Movies": movies_watched_list_2, - "Other Shows": tv_shows_2_watched_list_1, - } - } - - expected_watched_list_1 = { - "user1": { - "TV Shows": expected_tv_show_watched_list_1, - "Movies": expected_movie_watched_list_1, - } - } - - expected_watched_list_2 = { - "user1": { - "TV Shows": expected_tv_show_watched_list_2, - "Movies": expected_movie_watched_list_2, - } - } - - return_watched_list_1 = cleanup_watched(user_watched_list_1, user_watched_list_2) - return_watched_list_2 = cleanup_watched(user_watched_list_2, user_watched_list_1) - - assert return_watched_list_1 == expected_watched_list_1 - assert return_watched_list_2 == expected_watched_list_2 - - -def test_mapping_cleanup_watched(): - user_watched_list_1 = { - "user1": { - "TV Shows": tv_shows_watched_list_1, - "Movies": movies_watched_list_1, - "Other Shows": tv_shows_2_watched_list_1, - }, - } - user_watched_list_2 = { - "user2": { - "Shows": tv_shows_watched_list_2, - "Movies": movies_watched_list_2, - "Other Shows": tv_shows_2_watched_list_1, - } - } - - expected_watched_list_1 = { - "user1": { - "TV Shows": expected_tv_show_watched_list_1, - "Movies": expected_movie_watched_list_1, - } - } - - expected_watched_list_2 = { - "user2": { - "Shows": expected_tv_show_watched_list_2, - "Movies": expected_movie_watched_list_2, - } - } - - user_mapping = {"user1": "user2"} - library_mapping = {"TV Shows": "Shows"} - - return_watched_list_1 = cleanup_watched( - user_watched_list_1, - user_watched_list_2, - user_mapping=user_mapping, - library_mapping=library_mapping, - ) - return_watched_list_2 = cleanup_watched( - user_watched_list_2, - user_watched_list_1, - user_mapping=user_mapping, - library_mapping=library_mapping, - ) - - assert return_watched_list_1 == expected_watched_list_1 - assert return_watched_list_2 == expected_watched_list_2 +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +from src.watched import cleanup_watched, combine_watched_dicts + +tv_shows_watched_list_1 = { + frozenset( + { + ("tvdb", "75710"), + ("title", "Criminal Minds"), + ("imdb", "tt0452046"), + ("locations", ("Criminal Minds",)), + ("tmdb", "4057"), + } + ): { + "Season 1": [ + { + "imdb": "tt0550489", + "tmdb": "282843", + "tvdb": "176357", + "locations": ( + "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", + ), + }, + { + "imdb": "tt0550487", + "tmdb": "282861", + "tvdb": "300385", + "locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",), + }, + ] + }, + frozenset({("title", "Test"), ("locations", ("Test",))}): { + "Season 1": [ + {"locations": ("Test S01E01.mkv",)}, + {"locations": ("Test S01E02.mkv",)}, + ] + }, +} + +movies_watched_list_1 = [ + { + "imdb": "tt2380307", + "tmdb": "354912", + "title": "Coco", + "locations": ("Coco (2017) Remux-1080p.mkv",), + }, + { + "tmdbcollection": "448150", + "imdb": "tt1431045", + "tmdb": "293660", + "title": "Deadpool", + "locations": ("Deadpool (2016) Remux-1080p.mkv",), + }, +] + +tv_shows_watched_list_2 = { + frozenset( + { + ("tvdb", "75710"), + ("title", "Criminal Minds"), + ("imdb", "tt0452046"), + ("locations", ("Criminal Minds",)), + ("tmdb", "4057"), + } + ): { + "Season 1": [ + { + "imdb": "tt0550487", + "tmdb": "282861", + "tvdb": "300385", + "locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",), + }, + { + "imdb": "tt0550498", + "tmdb": "282865", + "tvdb": "300474", + "locations": ( + "Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv", + ), + }, + ] + }, + frozenset({("title", "Test"), ("locations", ("Test",))}): { + "Season 1": [ + {"locations": ("Test S01E02.mkv",)}, + {"locations": ("Test S01E03.mkv",)}, + ] + }, +} + +movies_watched_list_2 = [ + { + "imdb": "tt2380307", + "tmdb": "354912", + "title": "Coco", + "locations": ("Coco (2017) Remux-1080p.mkv",), + }, + { + "imdb": "tt0384793", + "tmdb": "9788", + "tvdb": "9103", + "title": "Accepted", + "locations": ("Accepted (2006) Remux-1080p.mkv",), + }, +] + +# Test to see if objects get deleted all the way up to the root. +tv_shows_2_watched_list_1 = { + frozenset( + { + ("tvdb", "75710"), + ("title", "Criminal Minds"), + ("imdb", "tt0452046"), + ("locations", ("Criminal Minds",)), + ("tmdb", "4057"), + } + ): { + "Season 1": [ + { + "imdb": "tt0550489", + "tmdb": "282843", + "tvdb": "176357", + "locations": ( + "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", + ), + }, + ] + } +} + +expected_tv_show_watched_list_1 = { + frozenset( + { + ("tvdb", "75710"), + ("title", "Criminal Minds"), + ("imdb", "tt0452046"), + ("locations", ("Criminal Minds",)), + ("tmdb", "4057"), + } + ): { + "Season 1": [ + { + "imdb": "tt0550489", + "tmdb": "282843", + "tvdb": "176357", + "locations": ( + "Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv", + ), + } + ] + }, + frozenset({("title", "Test"), ("locations", ("Test",))}): { + "Season 1": [{"locations": ("Test S01E01.mkv",)}] + }, +} + +expected_movie_watched_list_1 = [ + { + "tmdbcollection": "448150", + "imdb": "tt1431045", + "tmdb": "293660", + "title": "Deadpool", + "locations": ("Deadpool (2016) Remux-1080p.mkv",), + } +] + +expected_tv_show_watched_list_2 = { + frozenset( + { + ("tvdb", "75710"), + ("title", "Criminal Minds"), + ("imdb", "tt0452046"), + ("locations", ("Criminal Minds",)), + ("tmdb", "4057"), + } + ): { + "Season 1": [ + { + "imdb": "tt0550498", + "tmdb": "282865", + "tvdb": "300474", + "locations": ( + "Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv", + ), + } + ] + }, + frozenset({("title", "Test"), ("locations", ("Test",))}): { + "Season 1": [{"locations": ("Test S01E03.mkv",)}] + }, +} + +expected_movie_watched_list_2 = [ + { + "imdb": "tt0384793", + "tmdb": "9788", + "tvdb": "9103", + "title": "Accepted", + "locations": ("Accepted (2006) Remux-1080p.mkv",), + } +] + + +def test_simple_cleanup_watched(): + user_watched_list_1 = { + "user1": { + "TV Shows": tv_shows_watched_list_1, + "Movies": movies_watched_list_1, + "Other Shows": tv_shows_2_watched_list_1, + }, + } + user_watched_list_2 = { + "user1": { + "TV Shows": tv_shows_watched_list_2, + "Movies": movies_watched_list_2, + "Other Shows": tv_shows_2_watched_list_1, + } + } + + expected_watched_list_1 = { + "user1": { + "TV Shows": expected_tv_show_watched_list_1, + "Movies": expected_movie_watched_list_1, + } + } + + expected_watched_list_2 = { + "user1": { + "TV Shows": expected_tv_show_watched_list_2, + "Movies": expected_movie_watched_list_2, + } + } + + return_watched_list_1 = cleanup_watched(user_watched_list_1, user_watched_list_2) + return_watched_list_2 = cleanup_watched(user_watched_list_2, user_watched_list_1) + + assert return_watched_list_1 == expected_watched_list_1 + assert return_watched_list_2 == expected_watched_list_2 + + +def test_mapping_cleanup_watched(): + user_watched_list_1 = { + "user1": { + "TV Shows": tv_shows_watched_list_1, + "Movies": movies_watched_list_1, + "Other Shows": tv_shows_2_watched_list_1, + }, + } + user_watched_list_2 = { + "user2": { + "Shows": tv_shows_watched_list_2, + "Movies": movies_watched_list_2, + "Other Shows": tv_shows_2_watched_list_1, + } + } + + expected_watched_list_1 = { + "user1": { + "TV Shows": expected_tv_show_watched_list_1, + "Movies": expected_movie_watched_list_1, + } + } + + expected_watched_list_2 = { + "user2": { + "Shows": expected_tv_show_watched_list_2, + "Movies": expected_movie_watched_list_2, + } + } + + user_mapping = {"user1": "user2"} + library_mapping = {"TV Shows": "Shows"} + + return_watched_list_1 = cleanup_watched( + user_watched_list_1, + user_watched_list_2, + user_mapping=user_mapping, + library_mapping=library_mapping, + ) + return_watched_list_2 = cleanup_watched( + user_watched_list_2, + user_watched_list_1, + user_mapping=user_mapping, + library_mapping=library_mapping, + ) + + assert return_watched_list_1 == expected_watched_list_1 + assert return_watched_list_2 == expected_watched_list_2 + + +def test_combine_watched_dicts(): + input = [ + { + "test3": { + "Anime Movies": [ + { + "title": "Ponyo", + "tmdb": "12429", + "imdb": "tt0876563", + "locations": ("Ponyo (2008) Bluray-1080p.mkv",), + }, + { + "title": "Spirited Away", + "tmdb": "129", + "imdb": "tt0245429", + "locations": ("Spirited Away (2001) Bluray-1080p.mkv",), + }, + { + "title": "Castle in the Sky", + "tmdb": "10515", + "imdb": "tt0092067", + "locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",), + }, + ] + } + }, + {"test3": {"Anime Shows": {}}}, + {"test3": {"Cartoon Shows": {}}}, + { + "test3": { + "Shows": { + frozenset( + { + ("tmdb", "64464"), + ("tvdb", "301824"), + ("tvrage", "45210"), + ("title", "11.22.63"), + ("locations", ("11.22.63",)), + ("imdb", "tt2879552"), + } + ): { + "Season 1": [ + { + "imdb": "tt4460418", + "title": "The Rabbit Hole", + "locations": ( + "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", + ), + } + ] + } + } + } + }, + {"test3": {"Subbed Anime": {}}}, + ] + expected = { + "test3": { + "Anime Movies": [ + { + "title": "Ponyo", + "tmdb": "12429", + "imdb": "tt0876563", + "locations": ("Ponyo (2008) Bluray-1080p.mkv",), + }, + { + "title": "Spirited Away", + "tmdb": "129", + "imdb": "tt0245429", + "locations": ("Spirited Away (2001) Bluray-1080p.mkv",), + }, + { + "title": "Castle in the Sky", + "tmdb": "10515", + "imdb": "tt0092067", + "locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",), + }, + ], + "Anime Shows": {}, + "Cartoon Shows": {}, + "Shows": { + frozenset( + { + ("tmdb", "64464"), + ("tvdb", "301824"), + ("tvrage", "45210"), + ("title", "11.22.63"), + ("locations", ("11.22.63",)), + ("imdb", "tt2879552"), + } + ): { + "Season 1": [ + { + "imdb": "tt4460418", + "title": "The Rabbit Hole", + "locations": ( + "11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv", + ), + } + ] + } + }, + "Subbed Anime": {}, + } + } + + assert combine_watched_dicts(input) == expected From 7087d75efbe7865266d5715e945a498c886a580e Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 22:15:03 -0700 Subject: [PATCH 11/19] Fix exception --- src/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main.py b/src/main.py index 4076e9a..ccdbf6c 100644 --- a/src/main.py +++ b/src/main.py @@ -41,12 +41,12 @@ def setup_users( # Check if users is none or empty if output_server_1_users is None or len(output_server_1_users) == 0: raise Exception( - f"No users found for server 1 {server_1_type}, users found {users}, filtered users {users_filtered}, server 1 users {server_1_connection.users}" + f"No users found for server 1 {server_1[0]}, users found {users}, filtered users {users_filtered}, server 1 users {server_1[1].users}" ) if output_server_2_users is None or len(output_server_2_users) == 0: raise Exception( - f"No users found for server 2 {server_2_type}, users found {users} filtered users {users_filtered}, server 2 users {server_2_connection.users}" + f"No users found for server 2 {server_2[0]}, users found {users} filtered users {users_filtered}, server 2 users {server_2[1].users}" ) logger(f"Server 1 users: {output_server_1_users}", 1) From 5824e6c0cc562c3dea96c306a78b336092f0b797 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 22:21:40 -0700 Subject: [PATCH 12/19] cleanup --- src/plex.py | 4 ++-- test/test_watched.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/plex.py b/src/plex.py index 10b8859..8302e9e 100644 --- a/src/plex.py +++ b/src/plex.py @@ -53,7 +53,7 @@ def get_user_library_watched_show(show): m = re.match(r"(.*)://(.*)", guid.id) guid_source, guid_id = m.group(1).lower(), m.group(2) episode_guids_temp[guid_source] = guid_id - except: + except Exception: logger( f"Plex: Failed to get guids for {episode.title} in {show.title}, Using location only", 1, @@ -70,7 +70,7 @@ def get_user_library_watched_show(show): return show_guids, episode_guids - except Exception as e: + except Exception: return {}, {} diff --git a/test/test_watched.py b/test/test_watched.py index 5f3de96..8257457 100644 --- a/test/test_watched.py +++ b/test/test_watched.py @@ -302,7 +302,7 @@ def test_mapping_cleanup_watched(): def test_combine_watched_dicts(): - input = [ + input_watched = [ { "test3": { "Anime Movies": [ @@ -407,4 +407,4 @@ def test_combine_watched_dicts(): } } - assert combine_watched_dicts(input) == expected + assert combine_watched_dicts(input_watched) == expected From 3e15120e2ab5ec32587483aa1063e8aab8258e1d Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 23:17:54 -0700 Subject: [PATCH 13/19] Fix library whitelist, add library tests --- src/black_white.py | 1 - src/library.py | 32 +++-- src/plex.py | 1 - test/test_library.py | 302 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 317 insertions(+), 19 deletions(-) create mode 100644 test/test_library.py diff --git a/src/black_white.py b/src/black_white.py index 7763ecc..6b142c0 100644 --- a/src/black_white.py +++ b/src/black_white.py @@ -11,7 +11,6 @@ def setup_black_white_lists( library_mapping=None, user_mapping=None, ): - blacklist_library, blacklist_library_type, blacklist_users = setup_black_lists( blacklist_library, blacklist_library_type, diff --git a/src/library.py b/src/library.py index 7e53d1a..65cb3e3 100644 --- a/src/library.py +++ b/src/library.py @@ -23,7 +23,6 @@ def check_skip_logic( library_type, blacklist_library, blacklist_library_type, - library_mapping, library_other, ) skip_reason_white = check_whitelist_logic( @@ -31,7 +30,6 @@ def check_skip_logic( library_type, whitelist_library, whitelist_library_type, - library_mapping, library_other, ) @@ -53,7 +51,6 @@ def check_blacklist_logic( library_type, blacklist_library, blacklist_library_type, - library_mapping=None, library_other=None, ): skip_reason = None @@ -90,7 +87,6 @@ def check_whitelist_logic( library_type, whitelist_library, whitelist_library_type, - library_mapping=None, library_other=None, ): skip_reason = None @@ -107,26 +103,28 @@ def check_whitelist_logic( # if whitelist is not empty and library is not in whitelist if len(whitelist_library) > 0: - if library_title.lower() not in [x.lower() for x in whitelist_library]: - if skip_reason: - skip_reason = ( - skip_reason - + " and " - + f"{library_title} is not in whitelist_library" - ) - else: - skip_reason = f"{library_title} is not in whitelist_library" - if library_other: - if library_other.lower() not in [x.lower() for x in whitelist_library]: + if library_title.lower() not in [ + x.lower() for x in whitelist_library + ] and library_other.lower() not in [x.lower() for x in whitelist_library]: if skip_reason: skip_reason = ( skip_reason + " and " - + f"{library_other} is not in whitelist_library" + + f"{library_title} is not in whitelist_library" ) else: - skip_reason = f"{library_other} is not in whitelist_library" + skip_reason = f"{library_title} is not in whitelist_library" + else: + if library_title.lower() not in [x.lower() for x in whitelist_library]: + if skip_reason: + skip_reason = ( + skip_reason + + " and " + + f"{library_title} is not in whitelist_library" + ) + else: + skip_reason = f"{library_title} is not in whitelist_library" return skip_reason diff --git a/src/plex.py b/src/plex.py index 8302e9e..9fbb94d 100644 --- a/src/plex.py +++ b/src/plex.py @@ -15,7 +15,6 @@ from src.library import ( ) - # Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186 class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter): def init_poolmanager(self, connections, maxsize, block=..., **pool_kwargs): diff --git a/test/test_library.py b/test/test_library.py new file mode 100644 index 0000000..2ffd1da --- /dev/null +++ b/test/test_library.py @@ -0,0 +1,302 @@ +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +from src.functions import ( + search_mapping, +) + +from src.library import ( + check_skip_logic, + check_blacklist_logic, + check_whitelist_logic, + show_title_dict, + episode_title_dict, + movies_title_dict, + generate_library_guids_dict, +) + +blacklist_library = ["TV Shows"] +whitelist_library = ["Movies"] +blacklist_library_type = ["episodes"] +whitelist_library_type = ["movies"] +library_mapping = {"Shows": "TV Shows", "Movie": "Movies"} + +show_list = { + frozenset( + { + ("locations", ("The Last of Us",)), + ("tmdb", "100088"), + ("imdb", "tt3581920"), + ("tvdb", "392256"), + ("title", "The Last of Us"), + } + ): { + "Season 1": [ + { + "imdb": "tt11957006", + "tmdb": "2181581", + "tvdb": "8444132", + "locations": ( + "The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv", + ), + } + ] + } +} +movie_list = [ + { + "title": "Coco", + "imdb": "tt2380307", + "tmdb": "354912", + "locations": ("Coco (2017) Remux-2160p.mkv", "Coco (2017) Remux-1080p.mkv"), + } +] + +show_titles = { + "imdb": ["tt3581920"], + "locations": ["The Last of Us"], + "tmdb": ["100088"], + "tvdb": ["392256"], +} +episode_titles = { + "imdb": ["tt11957006"], + "locations": [ + "The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv" + ], + "tmdb": ["2181581"], + "tvdb": ["8444132"], +} +movie_titles = { + "imdb": ["tt2380307"], + "locations": ["Coco (2017) Remux-2160p.mkv", "Coco (2017) Remux-1080p.mkv"], + "title": ["coco"], + "tmdb": ["354912"], +} + + +def test_check_skip_logic(): + # Failes + library_title = "Test" + library_type = "movies" + skip_reason = check_skip_logic( + library_title, + library_type, + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + library_mapping, + ) + + assert skip_reason == "Test is not in whitelist_library" + + library_title = "Shows" + library_type = "episodes" + skip_reason = check_skip_logic( + library_title, + library_type, + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + library_mapping, + ) + + assert ( + skip_reason + == "episodes is in blacklist_library_type and TV Shows is in blacklist_library and " + + "episodes is not in whitelist_library_type and Shows is not in whitelist_library" + ) + + # Passes + library_title = "Movie" + library_type = "movies" + skip_reason = check_skip_logic( + library_title, + library_type, + blacklist_library, + whitelist_library, + blacklist_library_type, + whitelist_library_type, + library_mapping, + ) + + assert skip_reason == None + + +def test_check_blacklist_logic(): + # Fails + library_title = "Shows" + library_type = "episodes" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_other, + ) + + assert ( + skip_reason + == "episodes is in blacklist_library_type and TV Shows is in blacklist_library" + ) + + library_title = "TV Shows" + library_type = "episodes" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_other, + ) + + assert ( + skip_reason + == "episodes is in blacklist_library_type and TV Shows is in blacklist_library" + ) + + # Passes + library_title = "Movie" + library_type = "movies" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_other, + ) + + assert skip_reason == None + + library_title = "Movies" + library_type = "movies" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_blacklist_logic( + library_title, + library_type, + blacklist_library, + blacklist_library_type, + library_other, + ) + + assert skip_reason == None + + +def test_check_whitelist_logic(): + # Fails + library_title = "Shows" + library_type = "episodes" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_other, + ) + + assert ( + skip_reason + == "episodes is not in whitelist_library_type and Shows is not in whitelist_library" + ) + + library_title = "TV Shows" + library_type = "episodes" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_other, + ) + + assert ( + skip_reason + == "episodes is not in whitelist_library_type and TV Shows is not in whitelist_library" + ) + + # Passes + library_title = "Movie" + library_type = "movies" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_other, + ) + + assert skip_reason == None + + library_title = "Movies" + library_type = "movies" + library_other = search_mapping(library_mapping, library_title) + skip_reason = check_whitelist_logic( + library_title, + library_type, + whitelist_library, + whitelist_library_type, + library_other, + ) + + assert skip_reason == None + + +def test_show_title_dict(): + show_titles_dict = show_title_dict(show_list) + + assert show_titles_dict == show_titles + + +def test_episode_title_dict(): + episode_titles_dict = episode_title_dict(show_list) + + assert episode_titles_dict == episode_titles + + +def test_movies_title_dict(): + movies_titles_dict = movies_title_dict(movie_list) + + assert movies_titles_dict == movie_titles + + +def test_generate_library_guids_dict(): + # Test with shows + ( + show_titles_dict, + episode_titles_dict, + movies_titles_dict, + ) = generate_library_guids_dict(show_list) + + assert show_titles_dict == show_titles + assert episode_titles_dict == episode_titles + assert movies_titles_dict == {} + + # Test with movies + ( + show_titles_dict, + episode_titles_dict, + movies_titles_dict, + ) = generate_library_guids_dict(movie_list) + + assert show_titles_dict == {} + assert episode_titles_dict == {} + assert movies_titles_dict == movie_titles From 98f96ed5c7beaeb50aa832c5b195807e8b43e619 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Wed, 8 Mar 2023 23:48:54 -0700 Subject: [PATCH 14/19] Fix user being added when shouldnt. Add test_users --- src/users.py | 12 ++++++------ test/test_users.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 6 deletions(-) create mode 100644 test/test_users.py diff --git a/src/users.py b/src/users.py index 2a888e8..6075be8 100644 --- a/src/users.py +++ b/src/users.py @@ -24,9 +24,9 @@ def combine_user_lists(server_1_users, server_2_users, user_mapping): for server_1_user in server_1_users: if user_mapping: - jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user) - if jellyfin_plex_mapped_user: - users[server_1_user] = jellyfin_plex_mapped_user + mapped_user = search_mapping(user_mapping, server_1_user) + if mapped_user in server_2_users: + users[server_1_user] = mapped_user continue if server_1_user in server_2_users: @@ -34,9 +34,9 @@ def combine_user_lists(server_1_users, server_2_users, user_mapping): for server_2_user in server_2_users: if user_mapping: - plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user) - if plex_jellyfin_mapped_user: - users[plex_jellyfin_mapped_user] = server_2_user + mapped_user = search_mapping(user_mapping, server_2_user) + if mapped_user in server_1_users: + users[mapped_user] = server_2_user continue if server_2_user in server_1_users: diff --git a/test/test_users.py b/test/test_users.py new file mode 100644 index 0000000..fc3785b --- /dev/null +++ b/test/test_users.py @@ -0,0 +1,39 @@ +import sys +import os + +# getting the name of the directory +# where the this file is present. +current = os.path.dirname(os.path.realpath(__file__)) + +# Getting the parent directory name +# where the current directory is present. +parent = os.path.dirname(current) + +# adding the parent directory to +# the sys.path. +sys.path.append(parent) + +from src.users import ( + combine_user_lists, + filter_user_lists, +) + + +def test_combine_user_lists(): + server_1_users = ["test", "test3", "luigi311"] + server_2_users = ["luigi311", "test2", "test3"] + user_mapping = {"test2": "test"} + + combined = combine_user_lists(server_1_users, server_2_users, user_mapping) + + assert combined == {"luigi311": "luigi311", "test": "test2", "test3": "test3"} + + +def test_filter_user_lists(): + users = {"luigi311": "luigi311", "test": "test2", "test3": "test3"} + blacklist_users = ["test3"] + whitelist_users = ["test", "luigi311"] + + filtered = filter_user_lists(users, blacklist_users, whitelist_users) + + assert filtered == {"test": "test2", "luigi311": "luigi311"} From 9af6c9057c02b189e2d091e303af188bffc68550 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Thu, 9 Mar 2023 00:36:55 -0700 Subject: [PATCH 15/19] Simplify plex update_user_watched --- src/plex.py | 106 +++++++++++++--------------------------------------- 1 file changed, 26 insertions(+), 80 deletions(-) diff --git a/src/plex.py b/src/plex.py index 9fbb94d..335f67d 100644 --- a/src/plex.py +++ b/src/plex.py @@ -144,6 +144,29 @@ def get_user_library_watched(user, user_plex, library): return {} +def find_video(plex_search, video_ids): + try: + for location in plex_search.locations: + if location.split("/")[-1] in video_ids["locations"]: + return True + + + for guid in plex_search.guids: + guid_source = ( + re.search(r"(.*)://", guid.id).group(1).lower() + ) + guid_id = re.search(r"://(.*)", guid.id).group(1) + + # If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list + if guid_source in video_ids.keys(): + if guid_id in video_ids[guid_source]: + return True + + return False + except Exception: + return False + + def update_user_watched(user, user_plex, library, videos, dryrun): try: logger(f"Plex: Updating watched for {user.title} in library {library}", 1) @@ -160,26 +183,7 @@ def update_user_watched(user, user_plex, library, videos, dryrun): library_videos = user_plex.library.section(library) if videos_movies_ids: for movies_search in library_videos.search(unwatched=True): - movie_found = False - for movie_location in movies_search.locations: - if movie_location.split("/")[-1] in videos_movies_ids["locations"]: - movie_found = True - break - - if not movie_found: - for movie_guid in movies_search.guids: - movie_guid_source = ( - re.search(r"(.*)://", movie_guid.id).group(1).lower() - ) - movie_guid_id = re.search(r"://(.*)", movie_guid.id).group(1) - - # If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list - if movie_guid_source in videos_movies_ids.keys(): - if movie_guid_id in videos_movies_ids[movie_guid_source]: - movie_found = True - break - - if movie_found: + if find_video(movies_search, videos_movies_ids): msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex" if not dryrun: logger(f"Marked {msg}", 0) @@ -194,67 +198,9 @@ def update_user_watched(user, user_plex, library, videos, dryrun): if videos_shows_ids and videos_episodes_ids: for show_search in library_videos.search(unwatched=True): - show_found = False - for show_location in show_search.locations: - if show_location.split("/")[-1] in videos_shows_ids["locations"]: - show_found = True - break - - if not show_found: - for show_guid in show_search.guids: - show_guid_source = ( - re.search(r"(.*)://", show_guid.id).group(1).lower() - ) - show_guid_id = re.search(r"://(.*)", show_guid.id).group(1) - - # If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list - if show_guid_source in videos_shows_ids.keys(): - if show_guid_id in videos_shows_ids[show_guid_source]: - show_found = True - break - - if show_found: + if find_video(show_search, videos_shows_ids): for episode_search in show_search.episodes(): - episode_found = False - - for episode_location in episode_search.locations: - if ( - episode_location.split("/")[-1] - in videos_episodes_ids["locations"] - ): - episode_found = True - break - - if not episode_found: - try: - for episode_guid in episode_search.guids: - episode_guid_source = ( - re.search(r"(.*)://", episode_guid.id) - .group(1) - .lower() - ) - episode_guid_id = re.search( - r"://(.*)", episode_guid.id - ).group(1) - - # If episode provider source and episode provider id are in videos_episodes_ids exactly, then the episode is in the list - if ( - episode_guid_source - in videos_episodes_ids.keys() - ): - if ( - episode_guid_id - in videos_episodes_ids[episode_guid_source] - ): - episode_found = True - break - except Exception as e: - logger( - f"Plex: Failed to get episode guid for {episode_search.title}, Error: {e}", - 1, - ) - - if episode_found: + if find_video(episode_search, videos_episodes_ids): msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex" if not dryrun: logger(f"Marked {msg}", 0) From 9041fee7ad1aacc0efb371c829e92a478e9e555a Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Thu, 9 Mar 2023 00:48:29 -0700 Subject: [PATCH 16/19] Format --- src/plex.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/plex.py b/src/plex.py index 335f67d..205d3ac 100644 --- a/src/plex.py +++ b/src/plex.py @@ -150,18 +150,15 @@ def find_video(plex_search, video_ids): if location.split("/")[-1] in video_ids["locations"]: return True - for guid in plex_search.guids: - guid_source = ( - re.search(r"(.*)://", guid.id).group(1).lower() - ) + guid_source = re.search(r"(.*)://", guid.id).group(1).lower() guid_id = re.search(r"://(.*)", guid.id).group(1) # If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list if guid_source in video_ids.keys(): if guid_id in video_ids[guid_source]: return True - + return False except Exception: return False From 9f004797fcec0fff42be3471571bbf00701c51af Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Thu, 9 Mar 2023 00:53:07 -0700 Subject: [PATCH 17/19] Force format on save in vscode --- .vscode/settings.json | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index de288e1..10338f0 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,7 @@ { - "python.formatting.provider": "black" + "[python]" : { + "editor.formatOnSave": true, + }, + "python.formatting.provider": "black", + } \ No newline at end of file From cadd65d69b724cf29e614cdf95a7d40a6c2cbfc7 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Thu, 9 Mar 2023 01:29:11 -0700 Subject: [PATCH 18/19] Update issue templates (#50) * Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 31 +++++++++++++++++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 20 +++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..298ced2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,31 @@ +--- +name: Bug report +about: Create a report to help us improve +title: "[BUG]" +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Logs** +If applicable, add logs to help explain your problem ideally with DEBUG set to true, be sure to remove sensitive information + +**Type:** +- [ ] Docker +- [ ] Native + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..4a439c2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: "[Feature Request]" +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. From 7832e41a3b25d8b75b06a0ac6b996232872d2789 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Thu, 9 Mar 2023 01:32:27 -0700 Subject: [PATCH 19/19] Add sync from to to readme --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index c34ed60..a17e56e 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,13 @@ PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2" SSL_BYPASS = "False" +## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex +## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers +SYNC_FROM_PLEX_TO_JELLYFIN = "True" +SYNC_FROM_JELLYFIN_TO_PLEX = "True" +SYNC_FROM_PLEX_TO_PLEX = "True" +SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True" + # Jellyfin