commit
d40bbcc073
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
254
main.py
254
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)
|
||||
except KeyboardInterrupt:
|
||||
logger("Exiting", log_type=0)
|
||||
os._exit(0)
|
||||
|
||||
sleep(sleep_timer)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
178
src/jellyfin.py
178
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
|
||||
|
||||
104
src/plex.py
104
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()
|
||||
|
|
|
|||
Loading…
Reference in New Issue