diff --git a/.env.sample b/.env.sample index 9d88ece..5a725fc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,6 +1,9 @@ DRYRUN = "True" +DEBUG = "True" SLEEP_DURATION = "3600" LOGFILE = "log.log" +#USER_MAPPING = { "testuser2": "testuser3" } +#LIBRARY_MAPPING = { "Shows": "TV Shows" } PLEX_BASEURL = "http://localhost:32400" PLEX_TOKEN = "SuperSecretToken" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb14fe5..cbb63c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,8 +24,6 @@ jobs: uses: docker/metadata-action@v3 with: images: ${{ secrets.DOCKER_USERNAME }}/jellyplex-watched # list of Docker images to use as base name for tags - flavor: | - latest=true tags: | type=ref,event=branch type=ref,event=pr diff --git a/main.py b/main.py index 4ed4e90..5d47dc8 100644 --- a/main.py +++ b/main.py @@ -1,92 +1,119 @@ -import copy, os, traceback +import copy, os, traceback, json from dotenv import load_dotenv from time import sleep -from src.functions import logger, str_to_bool +from src.functions import logger, str_to_bool, search_mapping from src.plex import Plex from src.jellyfin import Jellyfin load_dotenv(override=True) -def cleanup_watched(watched_list_1, watched_list_2): +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 plex_watched that are in jellyfin_watched - for user in watched_list_1: - if user in modified_watched_list_1: - for library in watched_list_1[user]: - if library in modified_watched_list_1[user]: - for item in watched_list_1[user][library]: - if item in modified_watched_list_1[user][library]: - if user in watched_list_2 and library in watched_list_2[user]: - # Movies - if isinstance(watched_list_1[user][library], list): - for watch_list_1_key, watch_list_1_value in item.items(): - for watch_list_2_item in watched_list_2[user][library]: - for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items(): - if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value: - if item in modified_watched_list_1[user][library]: - modified_watched_list_1[user][library].remove(item) - - # TV Shows - elif isinstance(watched_list_1[user][library], dict): - if item in watched_list_2[user][library]: - for season in watched_list_1[user][library][item]: - if season in watched_list_2[user][library][item]: - for episode in watched_list_1[user][library][item][season]: - for watch_list_1_episode_key, watch_list_1_episode_value in episode.items(): - for watch_list_2_episode in watched_list_2[user][library][item][season]: - for watch_list_2_episode_key, watch_list_2_episode_value in watch_list_2_episode.items(): - if watch_list_1_episode_key == watch_list_2_episode_key and watch_list_1_episode_value == watch_list_2_episode_value: - if episode in modified_watched_list_1[user][library][item][season]: - modified_watched_list_1[user][library][item][season].remove(episode) - - # If season is empty, remove season - if len(modified_watched_list_1[user][library][item][season]) == 0: - if season in modified_watched_list_1[user][library][item]: - del modified_watched_list_1[user][library][item][season] + for user_1 in watched_list_1: + user_other = None + if user_mapping: + user_other = search_mapping(user_mapping, user_1) + if user_1 in modified_watched_list_1: + if user_1 in watched_list_2: + user_2 = user_1 + elif user_other in watched_list_2: + user_2 = user_other + else: + logger(f"User {user_1} and {user_other} not found in watched list 2", 1) + continue - # If the show is empty, remove the show - if len(modified_watched_list_1[user][library][item]) == 0: - if item in modified_watched_list_1[user][library]: - del modified_watched_list_1[user][library][item] + for library_1 in watched_list_1[user_1]: + library_other = None + if library_mapping: + library_other = search_mapping(library_mapping, library_1) + if library_1 in modified_watched_list_1[user_1]: + if library_1 in watched_list_2[user_2]: + library_2 = library_1 + elif library_other in watched_list_2[user_2]: + library_2 = library_other + else: + logger(f"User {library_1} and {library_other} not found in watched list 2", 1) + continue + for item in watched_list_1[user_1][library_1]: + if item in modified_watched_list_1[user_1][library_1]: + # Movies + if isinstance(watched_list_1[user_1][library_1], list): + for watch_list_1_key, watch_list_1_value in item.items(): + for watch_list_2_item in watched_list_2[user_2][library_2]: + for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items(): + if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value: + if item in modified_watched_list_1[user_1][library_1]: + modified_watched_list_1[user_1][library_1].remove(item) + + # TV Shows + elif isinstance(watched_list_1[user_1][library_1], dict): + if item in watched_list_2[user_2][library_2]: + for season in watched_list_1[user_1][library_1][item]: + if season in watched_list_2[user_2][library_2][item]: + for episode in watched_list_1[user_1][library_1][item][season]: + for watch_list_1_episode_key, watch_list_1_episode_value in episode.items(): + for watch_list_2_episode in watched_list_2[user_2][library_2][item][season]: + for watch_list_2_episode_key, watch_list_2_episode_value in watch_list_2_episode.items(): + if watch_list_1_episode_key == watch_list_2_episode_key and watch_list_1_episode_value == watch_list_2_episode_value: + if episode in modified_watched_list_1[user_1][library_1][item][season]: + modified_watched_list_1[user_1][library_1][item][season].remove(episode) + + # If season is empty, remove season + if len(modified_watched_list_1[user_1][library_1][item][season]) == 0: + if season in modified_watched_list_1[user_1][library_1][item]: + del modified_watched_list_1[user_1][library_1][item][season] + + # If the show is empty, remove the show + if len(modified_watched_list_1[user_1][library_1][item]) == 0: + if item in modified_watched_list_1[user_1][library_1]: + del modified_watched_list_1[user_1][library_1][item] # If library is empty then remove it - if len(modified_watched_list_1[user][library]) == 0: - if library in modified_watched_list_1[user]: - del modified_watched_list_1[user][library] + if len(modified_watched_list_1[user_1][library_1]) == 0: + if library_1 in modified_watched_list_1[user_1]: + del modified_watched_list_1[user_1][library_1] # If user is empty delete user - if len(modified_watched_list_1[user]) == 0: - del modified_watched_list_1[user] + if len(modified_watched_list_1[user_1]) == 0: + del modified_watched_list_1[user_1] return modified_watched_list_1 -def main(): - logfile = os.getenv("LOGFILE","log.log") - # Delete logfile if it exists - if os.path.exists(logfile): - os.remove(logfile) - - dryrun = str_to_bool(os.getenv("DRYRUN", "False")) - logger(f"Dryrun: {dryrun}", 1) - plex = Plex() - jellyfin = Jellyfin() - +def setup_black_white_lists(library_mapping=None): blacklist_library = os.getenv("BLACKLIST_LIBRARY") if blacklist_library: if len(blacklist_library) > 0: blacklist_library = blacklist_library.split(",") - blacklist_library = [x.lower().trim() for x in blacklist_library] + 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) whitelist_library = os.getenv("WHITELIST_LIBRARY") if whitelist_library: if len(whitelist_library) > 0: whitelist_library = whitelist_library.split(",") - whitelist_library = [x.lower().strip() for x in whitelist_library] + 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) @@ -113,13 +140,12 @@ def main(): if blacklist_users: if len(blacklist_users) > 0: blacklist_users = blacklist_users.split(",") - blacklist_users = [x.lower().strip() for x in blacklist_users] + blacklist_users = [x.lower().strip() for x in blacklist_users] else: blacklist_users = [] logger(f"Blacklist Users: {blacklist_users}", 1) whitelist_users = os.getenv("WHITELIST_USERS") - # print whitelist_users object type if whitelist_users: if len(whitelist_users) > 0: whitelist_users = whitelist_users.split(",") @@ -130,60 +156,117 @@ def main(): 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 setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=None): + # generate list of users from plex.users plex_users = [ x.title.lower() for x in plex.users ] jellyfin_users = [ key.lower() for key in jellyfin.users.keys() ] # combined list of overlapping users from plex and jellyfin - users = [x for x in plex_users if x in jellyfin_users] + users = {} + + for plex_user in plex_users: + if user_mapping: + jellyfin_plex_mapped_user = search_mapping(user_mapping, plex_user) + if jellyfin_plex_mapped_user: + users[plex_user] = jellyfin_plex_mapped_user + continue + + if plex_user in jellyfin_users: + users[plex_user] = plex_user + + for jellyfin_user in jellyfin_users: + if user_mapping: + plex_jellyfin_mapped_user = search_mapping(user_mapping, jellyfin_user) + if plex_jellyfin_mapped_user: + users[plex_jellyfin_mapped_user] = jellyfin_user + continue + + if jellyfin_user in plex_users: + users[jellyfin_user] = jellyfin_user + logger(f"User list that exist on both servers {users}", 1) - users_filtered = [] + users_filtered = {} for user in users: # whitelist_user is not empty and user lowercase is not in whitelist lowercase - if len(whitelist_users) > 0 and user.lower() not in whitelist_users: - logger(f"{user} is not in whitelist", 1) - else: - if user.lower() not in blacklist_users: - users_filtered.append(user) + 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] + logger(f"Filtered user list {users_filtered}", 1) + plex_users = [] for plex_user in plex.users: - if plex_user.title.lower() in users_filtered: + if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values(): plex_users.append(plex_user) jellyfin_users = {} for jellyfin_user, jellyfin_id in jellyfin.users.items(): - if jellyfin_user.lower() in users_filtered: + if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values(): jellyfin_users[jellyfin_user] = jellyfin_id + if len(plex_users) == 0: + raise Exception(f"No plex users found, users found {users} filtered users {users_filtered}") + + if len(jellyfin_users) == 0: + raise Exception(f"No jellyfin users found, users found {users} filtered users {users_filtered}") + logger(f"plex_users: {plex_users}", 1) logger(f"jellyfin_users: {jellyfin_users}", 1) - if len(plex_users) == 0: - logger("No users found", 2) - raise Exception("No users found") + return plex_users, jellyfin_users - if len(jellyfin_users) == 0: - logger("No users found", 2) - raise Exception("No users found") +def main(): + logfile = os.getenv("LOGFILE","log.log") + # Delete logfile if it exists + if os.path.exists(logfile): + os.remove(logfile) - plex_watched = plex.get_plex_watched(plex_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type) - jellyfin_watched = jellyfin.get_jellyfin_watched(jellyfin_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type) + dryrun = str_to_bool(os.getenv("DRYRUN", "False")) + logger(f"Dryrun: {dryrun}", 1) + + user_mapping = os.getenv("USER_MAPPING") + if user_mapping: + user_mapping = json.loads(user_mapping.lower()) + logger(f"User Mapping: {user_mapping}", 1) + + library_mapping = os.getenv("LIBRARY_MAPPING") + if library_mapping: + library_mapping = json.loads(library_mapping) + logger(f"Library Mapping: {library_mapping}", 1) + + plex = Plex() + jellyfin = Jellyfin() + + # Create (black/white)lists + blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users = setup_black_white_lists(library_mapping) + + # Create users list + plex_users, jellyfin_users = setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping) + + plex_watched = plex.get_plex_watched(plex_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) + jellyfin_watched = jellyfin.get_jellyfin_watched(jellyfin_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) # clone watched so it isnt modified in the cleanup function so all duplicates are actually removed plex_watched_filtered = copy.deepcopy(plex_watched) jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched) - plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered) + plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered, user_mapping, library_mapping) logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1) - jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered) + jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered, user_mapping, library_mapping) logger(f"jellyfin_watched that needs to be synced to plex:\n{jellyfin_watched}", 1) # Update watched status - plex.update_watched(jellyfin_watched, dryrun) - jellyfin.update_watched(plex_watched, dryrun) + plex.update_watched(jellyfin_watched, user_mapping, library_mapping, dryrun) + jellyfin.update_watched(plex_watched, user_mapping, library_mapping, dryrun) if __name__ == "__main__": @@ -202,7 +285,10 @@ if __name__ == "__main__": logger(traceback.format_exc(), 2) - logger("Retrying in {sleep_timer}", log_type=0) + logger(f"Retrying in {sleep_timer}", log_type=0) - - sleep(sleep_timer) \ No newline at end of file + except KeyboardInterrupt: + logger("Exiting", log_type=0) + os._exit(0) + + sleep(sleep_timer) diff --git a/src/functions.py b/src/functions.py index b5ce86d..3a61ae4 100644 --- a/src/functions.py +++ b/src/functions.py @@ -5,11 +5,12 @@ load_dotenv(override=True) logfile = os.getenv("LOGFILE","log.log") def logger(message, log_type=0): - + debug = str_to_bool(os.getenv("DEBUG", "True")) + output = str(message) if log_type == 0: pass - elif log_type == 1: + elif log_type == 1 and debug: output = f"[INFO]: {output}" elif log_type == 2: output = f"[ERROR]: {output}" @@ -27,3 +28,12 @@ def str_to_bool(value: any) -> bool: if not value: return False return str(value).lower() in ("y", "yes", "t", "true", "on", "1") + +# Get mapped value +def search_mapping(dictionary: dict, key_value: str): + if key_value in dictionary.keys(): + return dictionary[key_value] + elif key_value in dictionary.values(): + return list(dictionary.keys())[list(dictionary.values()).index(key_value)] + else: + return None diff --git a/src/jellyfin.py b/src/jellyfin.py index 8ea012d..4b15fa5 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -1,6 +1,6 @@ import requests, os from dotenv import load_dotenv -from src.functions import logger +from src.functions import logger, search_mapping, str_to_bool load_dotenv(override=True) @@ -56,7 +56,7 @@ class Jellyfin(): return users - def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type): + def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None): users_watched = {} for user_name, user_id in users.items(): @@ -67,70 +67,105 @@ class Jellyfin(): for library in libraries: library_title = library["Name"] - logger(f"Jellyfin: Generating watched for {user_name} in library {library_title}", 0) - library_id = library["Id"] - # if whitelist is not empty and library is not in whitelist - if len(whitelist_library) > 0 and library_title.lower() not in [x.lower() for x in whitelist_library]: - pass + watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Filters=IsPlayed&limit=1", "get") + + if len(watched["Items"]) == 0: + logger(f"Jellyfin: No watched items found in library {library_title}", 1) + continue else: - if library_title.lower() not in [x.lower() for x in blacklist_library]: - watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Filters=IsPlayed&limit=1", "get") - - if len(watched["Items"]) == 0: - pass - else: - library_type = watched["Items"][0]["Type"] + library_type = watched["Items"][0]["Type"] - # if Type in blacklist_library_type then break - if library_type in blacklist_library_type or (len(whitelist_library_type) > 0 and library_type.lower() not in whitelist_library_type): - break - - # Movies - if library_type == "Movie": - watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds", "get") - for movie in watched["Items"]: - if movie["UserData"]["Played"] == True: - if movie["ProviderIds"]: - if user_name not in users_watched: - users_watched[user_name] = {} - if library_title not in users_watched[user_name]: - users_watched[user_name][library_title] = [] - # Lowercase movie["ProviderIds"] keys - movie["ProviderIds"] = {k.lower(): v for k, v in movie["ProviderIds"].items()} - users_watched[user_name][library_title].append(movie["ProviderIds"]) + if library_type.lower() in blacklist_library_type: + logger(f"Jellyfin: Library type {library_type} is blacklist_library_type", 1) + continue - # TV Shows - if library_type == "Episode": - watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}", "get") - watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"] + if library_title.lower() in [x.lower() for x in blacklist_library]: + logger(f"Jellyfin: Library {library_title} is blacklist_library", 1) + continue - for show in watched_shows: - seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts", "get") - if len(seasons["Items"]) > 0: - for season in seasons["Items"]: - episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get") - if len(episodes["Items"]) > 0: - for episode in episodes["Items"]: - if episode["UserData"]["Played"] == True: - if episode["ProviderIds"]: - if user_name not in users_watched: - users_watched[user_name] = {} - if library_title not in users_watched[user_name]: - users_watched[user_name][library_title] = {} - if show["Name"] not in users_watched[user_name][library_title]: - users_watched[user_name][library_title][show["Name"]] = {} - if season["Name"] not in users_watched[user_name][library_title][show["Name"]]: - users_watched[user_name][library_title][show["Name"]][season["Name"]] = [] - - # Lowercase episode["ProviderIds"] keys - episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()} - users_watched[user_name][library_title][show["Name"]][season["Name"]].append(episode["ProviderIds"]) + library_other = None + if library_mapping: + library_other = search_mapping(library_mapping, library_title) + if library_other: + library_other.lower() + if library_other not in [x.lower() for x in blacklist_library]: + logger(f"Jellyfin: Library {library_other} is blacklist_library", 1) + continue + + if len(whitelist_library_type) > 0: + if library_type.lower() not in whitelist_library_type: + logger(f"Jellyfin: Library type {library_type} is not whitelist_library_type", 1) + continue + + # 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]: + logger(f"Jellyfin: Library {library_title} is not whitelist_library", 1) + continue + + if library_other: + if library_other not in [x.lower() for x in whitelist_library]: + logger(f"Jellyfin: Library {library_other} is not whitelist_library", 1) + continue + + logger(f"Jellyfin: Generating watched for {user_name} in library {library_title}", 0) + # Movies + if library_type == "Movie": + watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds", "get") + for movie in watched["Items"]: + if movie["UserData"]["Played"] == True: + if movie["ProviderIds"]: + if user_name not in users_watched: + users_watched[user_name] = {} + if library_title not in users_watched[user_name]: + users_watched[user_name][library_title] = [] + # Lowercase movie["ProviderIds"] keys + movie["ProviderIds"] = {k.lower(): v for k, v in movie["ProviderIds"].items()} + users_watched[user_name][library_title].append(movie["ProviderIds"]) + + # TV Shows + if library_type == "Episode": + watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}", "get") + watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"] + + for show in watched_shows: + seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts", "get") + if len(seasons["Items"]) > 0: + for season in seasons["Items"]: + episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get") + if len(episodes["Items"]) > 0: + for episode in episodes["Items"]: + if episode["UserData"]["Played"] == True: + if episode["ProviderIds"]: + if user_name not in users_watched: + users_watched[user_name] = {} + if library_title not in users_watched[user_name]: + users_watched[user_name][library_title] = {} + if show["Name"] not in users_watched[user_name][library_title]: + users_watched[user_name][library_title][show["Name"]] = {} + if season["Name"] not in users_watched[user_name][library_title][show["Name"]]: + users_watched[user_name][library_title][show["Name"]][season["Name"]] = [] + + # Lowercase episode["ProviderIds"] keys + episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()} + users_watched[user_name][library_title][show["Name"]][season["Name"]].append(episode["ProviderIds"]) return users_watched - def update_watched(self, watched_list, dryrun=False): + def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False): for user, libraries in watched_list.items(): + if user_mapping: + user_other = None + + if user in user_mapping.keys(): + user_other = user_mapping[user] + elif user in user_mapping.values(): + user_other = search_mapping(user_mapping, user) + + if user_other: + logger(f"Swapping user {user} with {user_other}", 1) + user = user_other user_id = None for key, value in self.users.items(): @@ -145,12 +180,30 @@ class Jellyfin(): jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"] for library, videos in libraries.items(): + if library_mapping: + library_other = None + + if library in library_mapping.keys(): + library_other = library_mapping[library] + elif library in library_mapping.values(): + library_other = search_mapping(library_mapping, library) + + if library_other: + logger(f"Swapping library {library} with {library_other}", 1) + library = library_other + + if library not in [x["Name"] for x in jellyfin_libraries]: + logger(f"{library} not found in Jellyfin", 2) + continue + library_id = None for jellyfin_library in jellyfin_libraries: if jellyfin_library["Name"] == library: library_id = jellyfin_library["Id"] - break + continue + if library_id: + logger(f"Jellyfin: Updating watched for {user} in library {library}", 1) library_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&limit=1", "get") library_type = library_search["Items"][0]["Type"] @@ -158,12 +211,12 @@ class Jellyfin(): if library_type == "Movie": jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get") for jellyfin_video in jellyfin_search["Items"]: - if jellyfin_video["UserData"]["Played"] == False: + if str_to_bool(jellyfin_video["UserData"]["Played"]) == False: jellyfin_video_id = jellyfin_video["Id"] for video in videos: for key, value in jellyfin_video["ProviderIds"].items(): if key.lower() in video.keys() and value.lower() == video[key.lower()].lower(): - msg = f"{jellyfin_video['Name']} as watched for {user}" + msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin" if not dryrun: logger(f"Marking {msg}", 0) self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post") @@ -181,18 +234,17 @@ class Jellyfin(): jellyfin_show_id = jellyfin_show["Id"] jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get") for jellyfin_episode in jellyfin_episodes["Items"]: - if jellyfin_episode["UserData"]["Played"] == False: + if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False: jellyfin_episode_id = jellyfin_episode["Id"] for show in videos: for season in videos[show]: for episode in videos[show][season]: for key, value in jellyfin_episode["ProviderIds"].items(): if key.lower() in episode.keys() and value.lower() == episode[key.lower()].lower(): - msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in Jellyfin" + msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin" if not dryrun: logger(f"Marked {msg}", 0) self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post") else: logger(f"Dryrun {msg}", 0) break - \ No newline at end of file diff --git a/src/plex.py b/src/plex.py index 3c6213c..9efb05d 100644 --- a/src/plex.py +++ b/src/plex.py @@ -1,7 +1,7 @@ import re, os from dotenv import load_dotenv -from src.functions import logger +from src.functions import logger, search_mapping from plexapi.server import PlexServer from plexapi.myplex import MyPlexAccount @@ -93,7 +93,7 @@ class Plex: return watched - def get_plex_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type): + def get_plex_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping): # Get all libraries libraries = self.plex.library.sections() users_watched = {} @@ -101,26 +101,68 @@ class Plex: # for not in blacklist for library in libraries: library_title = library.title + library_type = library.type + + if library_type.lower() in blacklist_library_type: + logger(f"Plex: Library type {library_type} is blacklist_library_type", 1) + continue + + if library_title.lower() in [x.lower() for x in blacklist_library]: + logger(f"Plex: Library {library_title} is blacklist_library", 1) + continue + + library_other = None + if library_mapping: + library_other = search_mapping(library_mapping, library_title) + if library_other: + library_other.lower() + if library_other not in [x.lower() for x in blacklist_library]: + logger(f"Plex: Library {library_other} is blacklist_library", 1) + continue + + if len(whitelist_library_type) > 0: + if library_type.lower() not in whitelist_library_type: + logger(f"Plex: Library type {library_type} is not whitelist_library_type", 1) + continue + # if whitelist is not empty and library is not in whitelist - if (len(whitelist_library) > 0 and library_title.lower() not in [x.lower() for x in whitelist_library]) or (len(whitelist_library_type) > 0 and library_title.type() not in [x.lower() for x in whitelist_library_type]): - pass - else: - if library_title.lower() not in [x.lower() for x in blacklist_library] and library.type not in [x.lower() for x in blacklist_library_type]: - for user in users: - logger(f"Plex: Generating watched for {user.title} in library {library_title}", 0) - user_name = user.title.lower() - watched = self.get_plex_user_watched(user, library) - if watched: - if user_name not in users_watched: - users_watched[user_name] = {} - if library_title not in users_watched[user_name]: - users_watched[user_name][library_title] = [] - users_watched[user_name][library_title] = watched + if len(whitelist_library) > 0: + if library_title.lower() not in [x.lower() for x in whitelist_library]: + logger(f"Plex: Library {library_title} is not whitelist_library", 1) + continue + + if library_other: + if library_other not in [x.lower() for x in whitelist_library]: + logger(f"Plex: Library {library_other} is not whitelist_library", 1) + continue + + for user in users: + logger(f"Plex: Generating watched for {user.title} in library {library_title}", 0) + user_name = user.title.lower() + watched = self.get_plex_user_watched(user, library) + if watched: + if user_name not in users_watched: + users_watched[user_name] = {} + if library_title not in users_watched[user_name]: + users_watched[user_name][library_title] = [] + users_watched[user_name][library_title] = watched return users_watched - def update_watched(self, watched_list, dryrun=False): + def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False): for user, libraries in watched_list.items(): + if user_mapping: + user_other = None + + if user in user_mapping.keys(): + user_other = user_mapping[user] + elif user in user_mapping.values(): + user_other = search_mapping(user_mapping, user) + + if user_other: + logger(f"Swapping user {user} with {user_other}", 1) + user = user_other + for index, value in enumerate(self.users): if user.lower() == value.title.lower(): user = self.users[index] @@ -131,10 +173,28 @@ class Plex: else: user_plex = PlexServer(self.baseurl, user.get_token(self.plex.machineIdentifier)) - logger(f"Updating watched for {user.title}", 1) for library, videos in libraries.items(): + if library_mapping: + library_other = None + + if library in library_mapping.keys(): + library_other = library_mapping[library] + elif library in library_mapping.values(): + library_other = search_mapping(library_mapping, library) + + if library_other: + logger(f"Swapping library {library} with {library_other}", 1) + library = library_other + + # if library in plex library list + library_list = user_plex.library.sections() + if library.lower() not in [x.title.lower() for x in library_list]: + logger(f"Library {library} not found in Plex library list", 2) + continue + + logger(f"Plex: Updating watched for {user.title} in library {library}", 1) library_videos = user_plex.library.section(library) - + if library_videos.type == "movie": for movies_search in library_videos.search(unmatched=False, unwatched=True): for guid in movies_search.guids: @@ -144,12 +204,12 @@ class Plex: for video_keys, video_id in video.items(): if video_keys == guid_source and video_id == guid_id: if movies_search.viewCount == 0: - msg = f"{movies_search.title} watched" + msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex" if not dryrun: logger(f"Marked {msg}", 0) movies_search.markWatched() else: - logger(f"Dyrun {msg}", 0) + logger(f"Dryrun {msg}", 0) break elif library_videos.type == "show": @@ -166,7 +226,7 @@ class Plex: for episode_keys, episode_id in episode.items(): if episode_keys == guid_source and episode_id == guid_id: if episode_search.viewCount == 0: - msg = f"{show_search.title} {season_search.title} {episode_search.title} as watched for {user.title} in Plex" + msg = f"{show_search.title} {season_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex" if not dryrun: logger(f"Marked {msg}", 0) episode_search.markWatched()