diff --git a/main.py b/main.py index 7e7615f..8f2b37d 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,7 @@ import copy, os from dotenv import load_dotenv +from src.functions import logger, str_to_bool from src.plex import Plex from src.jellyfin import Jellyfin @@ -60,7 +61,14 @@ def cleanup_watched(watched_list_1, watched_list_2): return modified_watched_list_1 -if __name__ == "__main__": +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() @@ -71,6 +79,7 @@ if __name__ == "__main__": blacklist_library = [x.lower().trim() for x in blacklist_library] else: blacklist_library = [] + logger(f"Blacklist Library: {blacklist_library}", 1) whitelist_library = os.getenv("WHITELIST_LIBRARY") if whitelist_library: @@ -79,7 +88,7 @@ if __name__ == "__main__": whitelist_library = [x.lower().strip() for x in whitelist_library] else: whitelist_library = [] - + logger(f"Whitelist Library: {whitelist_library}", 1) blacklist_library_type = os.getenv("BLACKLIST_LIBRARY_TYPE") if blacklist_library_type: @@ -88,6 +97,7 @@ if __name__ == "__main__": 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) whitelist_library_type = os.getenv("WHITELIST_LIBRARY_TYPE") if whitelist_library_type: @@ -96,15 +106,16 @@ if __name__ == "__main__": 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) blacklist_users = os.getenv("BLACKLIST_USERS") 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 @@ -116,20 +127,21 @@ if __name__ == "__main__": whitelist_users = [] else: whitelist_users = [] + logger(f"Whitelist Users: {whitelist_users}", 1) - users_filtered = [] - # 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] - + 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 and user.lower() not in whitelist_users: - print(f"{user} is not in whitelist") + logger(f"{user} is not in whitelist", 1) else: if user.lower() not in blacklist_users: users_filtered.append(user) @@ -144,10 +156,15 @@ if __name__ == "__main__": if jellyfin_user.lower() in users_filtered: jellyfin_users[jellyfin_user] = jellyfin_id + 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") if len(jellyfin_users) == 0: + logger("No users found", 2) raise Exception("No users found") plex_watched = plex.get_plex_watched(plex_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type) @@ -158,14 +175,18 @@ if __name__ == "__main__": jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched) plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered) - print(f"Plex Watched: {plex_watched}") + 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) - print(f"Jellyfin Watched: {jellyfin_watched}") + logger(f"jellyfin_watched that needs to be synced to plex:\n{jellyfin_watched}", 1) # Update watched status - plex.update_watched(jellyfin_watched) - print("Plex watched updated") + plex.update_watched(jellyfin_watched, dryrun) + jellyfin.update_watched(plex_watched, dryrun) - jellyfin.update_watched(plex_watched) - print("Jellyfin watched updated") \ No newline at end of file + +if __name__ == "__main__": + sleep_timer = float(os.getenv("SLEEP_TIMER", "3600")) + while(True): + main() + sleep(sleep_timer) \ No newline at end of file diff --git a/src/functions.py b/src/functions.py new file mode 100644 index 0000000..b5ce86d --- /dev/null +++ b/src/functions.py @@ -0,0 +1,29 @@ +import os +from dotenv import load_dotenv +load_dotenv(override=True) + +logfile = os.getenv("LOGFILE","log.log") + +def logger(message, log_type=0): + + output = str(message) + if log_type == 0: + pass + elif log_type == 1: + output = f"[INFO]: {output}" + elif log_type == 2: + output = f"[ERROR]: {output}" + else: + output = None + + if output is not None: + print(output) + file = open(logfile, "a", encoding="utf-8") + file.write(output + "\n") + +# Reimplementation of distutils.util.strtobool due to it being deprecated +# Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668 +def str_to_bool(value: any) -> bool: + if not value: + return False + return str(value).lower() in ("y", "yes", "t", "true", "on", "1") diff --git a/src/jellyfin.py b/src/jellyfin.py index 47ffe16..8ea012d 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -1,5 +1,6 @@ import requests, os from dotenv import load_dotenv +from src.functions import logger load_dotenv(override=True) @@ -39,8 +40,8 @@ class Jellyfin(): return response.json() except Exception as e: - print(e) - print(response) + logger(e, 2) + logger(response, 2) def get_users(self): users = {} @@ -66,7 +67,7 @@ class Jellyfin(): for library in libraries: library_title = library["Name"] - print(f"Jellyfin: Generating watched for {user_name} in library {library_title}") + 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 @@ -128,7 +129,7 @@ class Jellyfin(): return users_watched - def update_watched(self, watched_list): + def update_watched(self, watched_list, dryrun=False): for user, libraries in watched_list.items(): user_id = None @@ -138,7 +139,7 @@ class Jellyfin(): break if not user_id: - print(f"{user} not found in Jellyfin") + logger(f"{user} not found in Jellyfin", 2) break jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"] @@ -162,8 +163,12 @@ class Jellyfin(): 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(): - print(f"Marking {jellyfin_video['Name']} as watched for {user}") - self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post") + msg = f"{jellyfin_video['Name']} as watched for {user}" + if not dryrun: + logger(f"Marking {msg}", 0) + self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post") + else: + logger(f"Dryrun {msg}", 0) break # TV Shows @@ -183,7 +188,11 @@ class Jellyfin(): 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(): - print(f"Marked {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in Jellyfin") - self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post") + msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in 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 209ef4d..3c6213c 100644 --- a/src/plex.py +++ b/src/plex.py @@ -1,5 +1,7 @@ import re, os from dotenv import load_dotenv + +from src.functions import logger from plexapi.server import PlexServer from plexapi.myplex import MyPlexAccount @@ -23,14 +25,13 @@ class Plex: def plex_login(self): if self.baseurl: - # if self.username and self.password are not None or empty strings - if self.username and self.password: + if self.token: + # Login via token + plex = PlexServer(self.baseurl, self.token) + elif self.username and self.password: # Login via plex account account = MyPlexAccount(self.username, self.password) plex = account.resource(self.baseurl).connect() - elif self.token: - # Login via token - plex = PlexServer(self.baseurl, self.token) else: raise Exception("No plex credentials provided") else: @@ -106,7 +107,7 @@ class Plex: 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: - print(f"Plex: Generating watched for {user.title} in library {library_title}") + 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: @@ -118,7 +119,7 @@ class Plex: return users_watched - def update_watched(self, watched_list): + def update_watched(self, watched_list, dryrun=False): for user, libraries in watched_list.items(): for index, value in enumerate(self.users): if user.lower() == value.title.lower(): @@ -130,7 +131,7 @@ class Plex: else: user_plex = PlexServer(self.baseurl, user.get_token(self.plex.machineIdentifier)) - print(f"Updating watched for {user.title}") + logger(f"Updating watched for {user.title}", 1) for library, videos in libraries.items(): library_videos = user_plex.library.section(library) @@ -143,8 +144,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: - movies_search.markWatched() - print(f"Marked {movies_search.title} watched") + msg = f"{movies_search.title} watched" + if not dryrun: + logger(f"Marked {msg}", 0) + movies_search.markWatched() + else: + logger(f"Dyrun {msg}", 0) break elif library_videos.type == "show": @@ -161,6 +166,10 @@ 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: - episode_search.markWatched() - print(f"Marked {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 Plex" + if not dryrun: + logger(f"Marked {msg}", 0) + episode_search.markWatched() + else: + logger(f"Dryrun {msg}", 0) break