From ed536fdc8176d811073817405c134ed09f786b7b Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Sat, 11 Jun 2022 13:29:30 -0600 Subject: [PATCH 1/3] Fix plex login via username --- .env.sample | 31 ++++++++++++++++++------------- src/plex.py | 30 +++++++++++++++++++----------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/.env.sample b/.env.sample index 30ec78f..c2e54fc 100644 --- a/.env.sample +++ b/.env.sample @@ -1,33 +1,38 @@ -# Do not mark any shows/movies as played and instead just output to log if they would of been marked. +## Do not mark any shows/movies as played and instead just output to log if they would of been marked. DRYRUN = "True" -# Additional logging information +## Additional logging information DEBUG = "True" -# How often to run the script in seconds +## How often to run the script in seconds SLEEP_DURATION = "3600" -# Log file where all output will be written to +## Log file where all output will be written to LOGFILE = "log.log" -# Map usernames between plex and jellyfin in the event that they are different, order does not matter +## Map usernames between plex and jellyfin in the event that they are different, order does not matter #USER_MAPPING = { "testuser2": "testuser3" } -# Map libraries between plex and jellyfin in the even that they are different, order does not matter +## Map libraries between plex and jellyfin in the even that they are different, order does not matter #LIBRARY_MAPPING = { "Shows": "TV Shows" } -# URL of the plex server, use hostname or IP address if the hostname is not resolving correctly + +## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers +## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly PLEX_BASEURL = "http://localhost:32400" -# Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/ +## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/ PLEX_TOKEN = "SuperSecretToken" -# If not using plex token then use username and password of the server admin +## If not using plex token then use username and password of the server admin along with the servername #PLEX_USERNAME = "" #PLEX_PASSWORD = "" +#PLEX_SERVERNAME = "Plex Server" + -# Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly +## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly JELLYFIN_BASEURL = "http://localhost:8096" -# Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key +## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key JELLYFIN_TOKEN = "SuperSecretToken" -# Blacklisting/Whitelisting libraries, library types such as Movies, TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded. + +## Blacklisting/Whitelisting libraries, library types such as Movies/TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded. #BLACKLIST_LIBRARY = "" #WHITELIST_LIBRARY = "" #BLACKLIST_LIBRARY_TYPE = "" #WHITELIST_LIBRARY_TYPE = "" #BLACKLIST_USERS = "" -WHITELIST_USERS = "testuser1,testuser2" \ No newline at end of file +WHITELIST_USERS = "testuser1,testuser2" diff --git a/src/plex.py b/src/plex.py index d91b906..9ab9aa7 100644 --- a/src/plex.py +++ b/src/plex.py @@ -11,6 +11,7 @@ plex_token = os.getenv("PLEX_TOKEN") username = os.getenv("PLEX_USERNAME") password = os.getenv("PLEX_PASSWORD") +servername = os.getenv("PLEX_SERVERNAME") # class plex accept base url and token and username and password but default with none class Plex: @@ -19,25 +20,32 @@ def __init__(self): self.token = plex_token self.username = username self.password = password + self.servername = servername self.plex = self.plex_login() self.admin_user = self.plex.myPlexAccount() self.users = self.get_plex_users() def plex_login(self): - if self.baseurl: - if self.token: - # Login via token - plex = PlexServer(self.baseurl, self.token) - elif self.username and self.password: + try: + if self.baseurl and self.token: + # Login via token + plex = PlexServer(self.baseurl, self.token) + elif self.username and self.password and self.servername: # Login via plex account account = MyPlexAccount(self.username, self.password) - plex = account.resource(self.baseurl).connect() + plex = account.resource(self.servername).connect() else: - raise Exception("No plex credentials provided") - else: - raise Exception("No plex baseurl provided") - - return plex + raise Exception("No complete plex credentials provided") + + return plex + except Exception as e: + if self.username or self.password: + msg = f"Failed to login via plex account {self.username}" + logger(f"Plex: Failed to login, {msg}, Error: {e}", 2) + else: + logger(f"Plex: Failed to login, Error: {e}", 2) + return None + def get_plex_users(self): users = self.plex.myPlexAccount().users() From f3ce1520848cbb36dd5c5875c25907d1af337787 Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Sat, 11 Jun 2022 13:41:03 -0600 Subject: [PATCH 2/3] Simplify plex marking logic --- main.py | 42 +++++++++++++++++++++--------------------- src/functions.py | 2 +- src/jellyfin.py | 30 +++++++++++++++--------------- src/plex.py | 42 +++++++++++++++++++++--------------------- 4 files changed, 58 insertions(+), 58 deletions(-) diff --git a/main.py b/main.py index 5d47dc8..fe32e40 100644 --- a/main.py +++ b/main.py @@ -10,7 +10,7 @@ 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_1 in watched_list_1: user_other = None @@ -47,7 +47,7 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m 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]: @@ -60,22 +60,22 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m 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 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_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_1]) == 0: del modified_watched_list_1[user_1] @@ -95,10 +95,10 @@ def setup_black_white_lists(library_mapping=None): if library_other: temp_library.append(library_other) - blacklist_library = blacklist_library + temp_library + blacklist_library = blacklist_library + temp_library else: blacklist_library = [] - + logger(f"Blacklist Library: {blacklist_library}", 1) whitelist_library = os.getenv("WHITELIST_LIBRARY") @@ -140,11 +140,11 @@ def setup_black_white_lists(library_mapping=None): 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") if whitelist_users: if len(whitelist_users) > 0: @@ -159,11 +159,11 @@ def setup_black_white_lists(library_mapping=None): 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 = {} @@ -173,10 +173,10 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N 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) @@ -186,9 +186,9 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N if jellyfin_user in plex_users: users[jellyfin_user] = jellyfin_user - + 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 @@ -196,17 +196,17 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N 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.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.keys() or jellyfin_user.lower() in users_filtered.values(): @@ -267,11 +267,11 @@ def main(): # Update watched status plex.update_watched(jellyfin_watched, user_mapping, library_mapping, dryrun) jellyfin.update_watched(plex_watched, user_mapping, library_mapping, dryrun) - + if __name__ == "__main__": sleep_timer = float(os.getenv("SLEEP_TIMER", "3600")) - + while(True): try: main() diff --git a/src/functions.py b/src/functions.py index f3e3de8..b371212 100644 --- a/src/functions.py +++ b/src/functions.py @@ -67,7 +67,7 @@ def check_skip_logic(library_title, library_type, blacklist_library, whitelist_l 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" diff --git a/src/jellyfin.py b/src/jellyfin.py index 7bcbbce..5cd0a9c 100644 --- a/src/jellyfin.py +++ b/src/jellyfin.py @@ -14,7 +14,7 @@ def __init__(self): if not self.baseurl: raise Exception("Jellyfin baseurl not set") - + if not self.token: raise Exception("Jellyfin token not set") @@ -27,7 +27,7 @@ def query(self, query, query_type): if query_type == "get": response = requests.get(self.baseurl + query, headers={"accept":"application/json", "X-Emby-Token": self.token}) - + elif query_type == "post": authorization = ( 'MediaBrowser , ' @@ -42,19 +42,19 @@ def query(self, query, query_type): except Exception as e: logger(e, 2) logger(response, 2) - + def get_users(self): users = {} query = "/Users" response = self.query(query, "get") - + # If reponse is not empty if response: for user in response: users[user["Name"]] = user["Id"] - return users + return users def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None): users_watched = {} @@ -64,12 +64,12 @@ def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blac user_name = user_name.lower() libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"] - + for library in libraries: library_title = library["Name"] library_id = library["Id"] 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 @@ -123,7 +123,7 @@ def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blac # 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, user_mapping=None, library_mapping=None, dryrun=False): @@ -135,7 +135,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, 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 @@ -145,13 +145,13 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, if user.lower() == key.lower(): user_id = self.users[key] break - + if not user_id: logger(f"{user} not found in Jellyfin", 2) break - + jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"] - + for library, videos in libraries.items(): if library_mapping: library_other = None @@ -160,7 +160,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, 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 @@ -174,7 +174,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, if jellyfin_library["Name"] == library: library_id = jellyfin_library["Id"] 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") @@ -196,7 +196,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, else: logger(f"Dryrun {msg}", 0) break - + # TV Shows if library_type == "Episode": jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false", "get") diff --git a/src/plex.py b/src/plex.py index 9ab9aa7..e8b0350 100644 --- a/src/plex.py +++ b/src/plex.py @@ -36,7 +36,7 @@ def plex_login(self): plex = account.resource(self.servername).connect() else: raise Exception("No complete plex credentials provided") - + return plex except Exception as e: if self.username or self.password: @@ -45,14 +45,14 @@ def plex_login(self): else: logger(f"Plex: Failed to login, Error: {e}", 2) return None - + def get_plex_users(self): users = self.plex.myPlexAccount().users() - + # append self to users users.append(self.plex.myPlexAccount()) - + return users def get_plex_user_watched(self, user, library): @@ -60,9 +60,9 @@ def get_plex_user_watched(self, user, library): user_plex = self.plex else: user_plex = PlexServer(self.baseurl, user.get_token(self.plex.machineIdentifier)) - + watched = None - + if library.type == "movie": watched = [] library_videos = user_plex.library.section(library.title) @@ -81,22 +81,22 @@ def get_plex_user_watched(self, user, library): for season in show.seasons(): guids = [] for episode in season.episodes(): - if episode.viewCount > 0: - guids_temp = {} + if episode.viewCount > 0: + guids_temp = {} for guid in episode.guids: # Extract after :// from guid.id guid_source = re.search(r'(.*)://', guid.id).group(1).lower() guid_id = re.search(r'://(.*)', guid.id).group(1) guids_temp[guid_source] = guid_id - - guids.append(guids_temp) - + + guids.append(guids_temp) + if guids: # append show, season, episode if show.title not in watched: watched[show.title] = {} if season.title not in watched[show.title]: - watched[show.title][season.title] = {} + watched[show.title][season.title] = {} watched[show.title][season.title] = guids return watched @@ -116,7 +116,7 @@ def get_plex_watched(self, users, blacklist_library, whitelist_library, blacklis if skip_reason: logger(f"Plex: Skipping library {library_title} {skip_reason}", 1) continue - + for user in users: logger(f"Plex: Generating watched for {user.title} in library {library_title}", 0) user_name = user.title.lower() @@ -127,9 +127,9 @@ def get_plex_watched(self, users, blacklist_library, whitelist_library, blacklis 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, user_mapping=None, library_mapping=None, dryrun=False): for user, libraries in watched_list.items(): if user_mapping: @@ -139,7 +139,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, 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 @@ -162,7 +162,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, 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 @@ -192,7 +192,7 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, else: logger(f"Dryrun {msg}", 0) break - + elif library_videos.type == "show": for show_search in library_videos.search(unmatched=False, unwatched=True): if show_search.title in videos: @@ -201,9 +201,9 @@ def update_watched(self, watched_list, user_mapping=None, library_mapping=None, for guid in episode_search.guids: guid_source = re.search(r'(.*)://', guid.id).group(1).lower() guid_id = re.search(r'://(.*)', guid.id).group(1) - for show, seasons in videos.items(): - for season, episodes in seasons.items(): - for episode in episodes: + for show in videos: + for season in videos[show]: + for episode in videos[show][season]: for episode_keys, episode_id in episode.items(): if episode_keys == guid_source and episode_id == guid_id: if episode_search.viewCount == 0: From ef48014233ec938073309d47f6b0f8234730a55a Mon Sep 17 00:00:00 2001 From: Luigi311 Date: Sat, 11 Jun 2022 13:44:44 -0600 Subject: [PATCH 3/3] Add codacy badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 7ec79e2..6b108b2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # JellyPlex-Watched +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/26b47c5db63942f28f02f207f692dc85)](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com&utm_medium=referral&utm_content=luigi311/JellyPlex-Watched&utm_campaign=Badge_Grade) + Sync watched between jellyfin and plex ## Description