Fix username differences in watch list. Add python version check. More error handling.
This commit is contained in:
403
main.py
403
main.py
@@ -1,397 +1,10 @@
|
|||||||
import copy, os, traceback, json
|
import sys
|
||||||
from dotenv import load_dotenv
|
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
# Check python version 3.6 or higher
|
||||||
|
if not (3, 6) <= tuple(map(int, sys.version_info[:2])):
|
||||||
|
print("This script requires Python 3.6 or higher")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict, future_thread_executor
|
from src.main import main
|
||||||
from src.plex import Plex
|
main()
|
||||||
from src.jellyfin import Jellyfin
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
|
||||||
|
|
||||||
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
|
|
||||||
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
|
|
||||||
|
|
||||||
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"library {library_1} and {library_other} not found in watched list 2", 1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Movies
|
|
||||||
if isinstance(watched_list_1[user_1][library_1], list):
|
|
||||||
for item in watched_list_1[user_1][library_1]:
|
|
||||||
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]:
|
|
||||||
logger(f"Removing {item} from {library_1}", 3)
|
|
||||||
modified_watched_list_1[user_1][library_1].remove(item)
|
|
||||||
|
|
||||||
|
|
||||||
# TV Shows
|
|
||||||
elif isinstance(watched_list_1[user_1][library_1], dict):
|
|
||||||
# Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1
|
|
||||||
_, episode_watched_list_2_keys_dict, _ = generate_library_guids_dict(watched_list_2[user_2][library_2], 1)
|
|
||||||
|
|
||||||
for show_key_1 in watched_list_1[user_1][library_1].keys():
|
|
||||||
show_key_dict = dict(show_key_1)
|
|
||||||
for season in watched_list_1[user_1][library_1][show_key_1]:
|
|
||||||
for episode in watched_list_1[user_1][library_1][show_key_1][season]:
|
|
||||||
for episode_key, episode_item in episode.items():
|
|
||||||
# If episode_key and episode_item are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1
|
|
||||||
if episode_key in episode_watched_list_2_keys_dict.keys():
|
|
||||||
if episode_item in episode_watched_list_2_keys_dict[episode_key]:
|
|
||||||
if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]:
|
|
||||||
logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 3)
|
|
||||||
modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode)
|
|
||||||
|
|
||||||
# Remove empty seasons
|
|
||||||
if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0:
|
|
||||||
if season in modified_watched_list_1[user_1][library_1][show_key_1]:
|
|
||||||
logger(f"Removing {season} from {library_1} because it is empty", 3)
|
|
||||||
del modified_watched_list_1[user_1][library_1][show_key_1][season]
|
|
||||||
|
|
||||||
# If the show is empty, remove the show
|
|
||||||
if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0:
|
|
||||||
if show_key_1 in modified_watched_list_1[user_1][library_1]:
|
|
||||||
logger(f"Removing {show_key_dict['title']} from {library_1} because it is empty", 1)
|
|
||||||
del modified_watched_list_1[user_1][library_1][show_key_1]
|
|
||||||
|
|
||||||
for user_1 in watched_list_1:
|
|
||||||
for library_1 in watched_list_1[user_1]:
|
|
||||||
if library_1 in modified_watched_list_1[user_1]:
|
|
||||||
# If library is empty then remove it
|
|
||||||
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
|
||||||
logger(f"Removing {library_1} from {user_1} because it is empty", 1)
|
|
||||||
del modified_watched_list_1[user_1][library_1]
|
|
||||||
|
|
||||||
if user_1 in modified_watched_list_1:
|
|
||||||
# If user is empty delete user
|
|
||||||
if len(modified_watched_list_1[user_1]) == 0:
|
|
||||||
logger(f"Removing {user_1} from watched list 1 because it is empty", 1)
|
|
||||||
del modified_watched_list_1[user_1]
|
|
||||||
|
|
||||||
return modified_watched_list_1
|
|
||||||
|
|
||||||
def 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.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.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)
|
|
||||||
|
|
||||||
blacklist_library_type = os.getenv("BLACKLIST_LIBRARY_TYPE")
|
|
||||||
if blacklist_library_type:
|
|
||||||
if len(blacklist_library_type) > 0:
|
|
||||||
blacklist_library_type = blacklist_library_type.split(",")
|
|
||||||
blacklist_library_type = [x.lower().strip() for x in blacklist_library_type]
|
|
||||||
else:
|
|
||||||
blacklist_library_type = []
|
|
||||||
logger(f"Blacklist Library Type: {blacklist_library_type}", 1)
|
|
||||||
|
|
||||||
whitelist_library_type = os.getenv("WHITELIST_LIBRARY_TYPE")
|
|
||||||
if whitelist_library_type:
|
|
||||||
if len(whitelist_library_type) > 0:
|
|
||||||
whitelist_library_type = whitelist_library_type.split(",")
|
|
||||||
whitelist_library_type = [x.lower().strip() for x in whitelist_library_type]
|
|
||||||
else:
|
|
||||||
whitelist_library_type = []
|
|
||||||
logger(f"Whitelist Library Type: {whitelist_library_type}", 1)
|
|
||||||
|
|
||||||
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]
|
|
||||||
else:
|
|
||||||
blacklist_users = []
|
|
||||||
logger(f"Blacklist Users: {blacklist_users}", 1)
|
|
||||||
|
|
||||||
whitelist_users = os.getenv("WHITELIST_USERS")
|
|
||||||
if whitelist_users:
|
|
||||||
if len(whitelist_users) > 0:
|
|
||||||
whitelist_users = whitelist_users.split(",")
|
|
||||||
whitelist_users = [x.lower().strip() for x in whitelist_users]
|
|
||||||
else:
|
|
||||||
whitelist_users = []
|
|
||||||
else:
|
|
||||||
whitelist_users = []
|
|
||||||
logger(f"Whitelist Users: {whitelist_users}", 1)
|
|
||||||
|
|
||||||
return blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users
|
|
||||||
|
|
||||||
def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mapping=None):
|
|
||||||
|
|
||||||
# generate list of users from server 1 and server 2
|
|
||||||
server_1_type = server_1[0]
|
|
||||||
server_1_connection = server_1[1]
|
|
||||||
server_2_type = server_2[0]
|
|
||||||
server_2_connection = server_2[1]
|
|
||||||
|
|
||||||
server_1_users = []
|
|
||||||
if server_1_type == "plex":
|
|
||||||
server_1_users = [ x.title.lower() for x in server_1_connection.users ]
|
|
||||||
elif server_1_type == "jellyfin":
|
|
||||||
server_1_users = [ key.lower() for key in server_1_connection.users.keys() ]
|
|
||||||
|
|
||||||
server_2_users = []
|
|
||||||
if server_2_type == "plex":
|
|
||||||
server_2_users = [ x.title.lower() for x in server_2_connection.users ]
|
|
||||||
elif server_2_type == "jellyfin":
|
|
||||||
server_2_users = [ key.lower() for key in server_2_connection.users.keys() ]
|
|
||||||
|
|
||||||
|
|
||||||
# combined list of overlapping users from plex and jellyfin
|
|
||||||
users = {}
|
|
||||||
|
|
||||||
for server_1_user in server_1_users:
|
|
||||||
if user_mapping:
|
|
||||||
jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user)
|
|
||||||
if jellyfin_plex_mapped_user:
|
|
||||||
users[server_1_user] = jellyfin_plex_mapped_user
|
|
||||||
continue
|
|
||||||
|
|
||||||
if server_1_user in server_2_users:
|
|
||||||
users[server_1_user] = server_1_user
|
|
||||||
|
|
||||||
for server_2_user in server_2_users:
|
|
||||||
if user_mapping:
|
|
||||||
plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user)
|
|
||||||
if plex_jellyfin_mapped_user:
|
|
||||||
users[plex_jellyfin_mapped_user] = server_2_user
|
|
||||||
continue
|
|
||||||
|
|
||||||
if server_2_user in server_1_users:
|
|
||||||
users[server_2_user] = server_2_user
|
|
||||||
|
|
||||||
logger(f"User list that exist on both servers {users}", 1)
|
|
||||||
|
|
||||||
users_filtered = {}
|
|
||||||
for user in users:
|
|
||||||
# whitelist_user is not empty and user lowercase is not in whitelist lowercase
|
|
||||||
if len(whitelist_users) > 0:
|
|
||||||
if user not in whitelist_users and users[user] not in whitelist_users:
|
|
||||||
logger(f"{user} or {users[user]} is not in whitelist", 1)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if user not in blacklist_users and users[user] not in blacklist_users:
|
|
||||||
users_filtered[user] = users[user]
|
|
||||||
|
|
||||||
logger(f"Filtered user list {users_filtered}", 1)
|
|
||||||
|
|
||||||
if server_1_type == "plex":
|
|
||||||
output_server_1_users = []
|
|
||||||
for plex_user in server_1_connection.users:
|
|
||||||
if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values():
|
|
||||||
output_server_1_users.append(plex_user)
|
|
||||||
elif server_1_type == "jellyfin":
|
|
||||||
output_server_1_users = {}
|
|
||||||
for jellyfin_user, jellyfin_id in server_1_connection.users.items():
|
|
||||||
if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values():
|
|
||||||
output_server_1_users[jellyfin_user] = jellyfin_id
|
|
||||||
|
|
||||||
if server_2_type == "plex":
|
|
||||||
output_server_2_users = []
|
|
||||||
for plex_user in server_2_connection.users:
|
|
||||||
if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values():
|
|
||||||
output_server_2_users.append(plex_user)
|
|
||||||
elif server_2_type == "jellyfin":
|
|
||||||
output_server_2_users = {}
|
|
||||||
for jellyfin_user, jellyfin_id in server_2_connection.users.items():
|
|
||||||
if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values():
|
|
||||||
output_server_2_users[jellyfin_user] = jellyfin_id
|
|
||||||
|
|
||||||
if len(output_server_1_users) == 0:
|
|
||||||
raise Exception(f"No users found for server 1, users found {users} filtered users {users_filtered}")
|
|
||||||
|
|
||||||
if len(output_server_2_users) == 0:
|
|
||||||
raise Exception(f"No users found for server 2, users found {users} filtered users {users_filtered}")
|
|
||||||
|
|
||||||
logger(f"Server 1 users: {output_server_1_users}", 1)
|
|
||||||
logger(f"Server 2 users: {output_server_2_users}", 1)
|
|
||||||
|
|
||||||
return output_server_1_users, output_server_2_users
|
|
||||||
|
|
||||||
def generate_server_connections():
|
|
||||||
servers = []
|
|
||||||
|
|
||||||
plex_baseurl = os.getenv("PLEX_BASEURL", None)
|
|
||||||
plex_token = os.getenv("PLEX_TOKEN", None)
|
|
||||||
plex_username = os.getenv("PLEX_USERNAME", None)
|
|
||||||
plex_password = os.getenv("PLEX_PASSWORD", None)
|
|
||||||
plex_servername = os.getenv("PLEX_SERVERNAME", None)
|
|
||||||
|
|
||||||
if plex_baseurl and plex_token:
|
|
||||||
plex_baseurl = plex_baseurl.split(",")
|
|
||||||
plex_token = plex_token.split(",")
|
|
||||||
|
|
||||||
if len(plex_baseurl) != len(plex_token):
|
|
||||||
raise Exception("PLEX_BASEURL and PLEX_TOKEN must have the same number of entries")
|
|
||||||
|
|
||||||
for i, url in enumerate(plex_baseurl):
|
|
||||||
servers.append(("plex", Plex(baseurl=url.strip(), token=plex_token[i].strip(), username=None, password=None, servername=None)))
|
|
||||||
|
|
||||||
if plex_username and plex_password and plex_servername:
|
|
||||||
plex_username = plex_username.split(",")
|
|
||||||
plex_password = plex_password.split(",")
|
|
||||||
plex_servername = plex_servername.split(",")
|
|
||||||
|
|
||||||
if len(plex_username) != len(plex_password) or len(plex_username) != len(plex_servername):
|
|
||||||
raise Exception("PLEX_USERNAME, PLEX_PASSWORD and PLEX_SERVERNAME must have the same number of entries")
|
|
||||||
|
|
||||||
for i, username in enumerate(plex_username):
|
|
||||||
servers.append(("plex", Plex(baseurl=None, token=None, username=username.strip(), password=plex_password[i].strip(), servername=plex_servername[i].strip())))
|
|
||||||
|
|
||||||
jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None)
|
|
||||||
jellyfin_token = os.getenv("JELLYFIN_TOKEN", None)
|
|
||||||
|
|
||||||
if jellyfin_baseurl and jellyfin_token:
|
|
||||||
jellyfin_baseurl = jellyfin_baseurl.split(",")
|
|
||||||
jellyfin_token = jellyfin_token.split(",")
|
|
||||||
|
|
||||||
if len(jellyfin_baseurl) != len(jellyfin_token):
|
|
||||||
raise Exception("JELLYFIN_BASEURL and JELLYFIN_TOKEN must have the same number of entries")
|
|
||||||
|
|
||||||
for i, baseurl in enumerate(jellyfin_baseurl):
|
|
||||||
servers.append(("jellyfin", Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip())))
|
|
||||||
|
|
||||||
return servers
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 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 server connections
|
|
||||||
servers = generate_server_connections()
|
|
||||||
|
|
||||||
for server_1 in servers:
|
|
||||||
# If server is the final server in the list, then we are done with the loop
|
|
||||||
if server_1 == servers[-1]:
|
|
||||||
break
|
|
||||||
|
|
||||||
# Start server_2 at the next server in the list
|
|
||||||
for server_2 in servers[servers.index(server_1) + 1:]:
|
|
||||||
|
|
||||||
server_1_connection = server_1[1]
|
|
||||||
server_2_connection = server_2[1]
|
|
||||||
|
|
||||||
# Create users list
|
|
||||||
server_1_users, server_2_users = setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mapping)
|
|
||||||
|
|
||||||
args = [[server_1_connection.get_watched, server_1_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping]
|
|
||||||
, [server_2_connection.get_watched, server_2_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping]]
|
|
||||||
|
|
||||||
results = future_thread_executor(args)
|
|
||||||
server_1_watched = results[0]
|
|
||||||
server_2_watched = results[1]
|
|
||||||
|
|
||||||
# clone watched so it isnt modified in the cleanup function so all duplicates are actually removed
|
|
||||||
server_1_watched_filtered = copy.deepcopy(server_1_watched)
|
|
||||||
server_2_watched_filtered = copy.deepcopy(server_2_watched)
|
|
||||||
|
|
||||||
logger("Cleaning Server 1 Watched", 1)
|
|
||||||
server_1_watched_filtered = cleanup_watched(server_1_watched, server_2_watched, user_mapping, library_mapping)
|
|
||||||
|
|
||||||
logger("Cleaning Server 2 Watched", 1)
|
|
||||||
server_2_watched_filtered = cleanup_watched(server_2_watched, server_1_watched, user_mapping, library_mapping)
|
|
||||||
|
|
||||||
logger(f"server 1 watched that needs to be synced to server 2:\n{server_1_watched_filtered}", 1)
|
|
||||||
logger(f"server 2 watched that needs to be synced to server 1:\n{server_2_watched_filtered}", 1)
|
|
||||||
|
|
||||||
args= [[server_1_connection.update_watched, server_2_watched_filtered, user_mapping, library_mapping, dryrun]
|
|
||||||
, [server_2_connection.update_watched, server_1_watched_filtered, user_mapping, library_mapping, dryrun]]
|
|
||||||
|
|
||||||
future_thread_executor(args)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sleep_duration = float(os.getenv("SLEEP_DURATION", "3600"))
|
|
||||||
|
|
||||||
while(True):
|
|
||||||
try:
|
|
||||||
main()
|
|
||||||
logger(f"Looping in {sleep_duration}")
|
|
||||||
sleep(sleep_duration)
|
|
||||||
except Exception as error:
|
|
||||||
if isinstance(error, list):
|
|
||||||
for message in error:
|
|
||||||
logger(message, log_type=2)
|
|
||||||
else:
|
|
||||||
logger(error, log_type=2)
|
|
||||||
|
|
||||||
|
|
||||||
logger(traceback.format_exc(), 2)
|
|
||||||
logger(f"Retrying in {sleep_duration}", log_type=0)
|
|
||||||
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
logger("Exiting", log_type=0)
|
|
||||||
os._exit(0)
|
|
||||||
|
|||||||
@@ -39,11 +39,11 @@ def search_mapping(dictionary: dict, key_value: str):
|
|||||||
if key_value in dictionary.keys():
|
if key_value in dictionary.keys():
|
||||||
return dictionary[key_value]
|
return dictionary[key_value]
|
||||||
elif key_value.lower() in dictionary.keys():
|
elif key_value.lower() in dictionary.keys():
|
||||||
return dictionary[key_value]
|
return dictionary[key_value.lower()]
|
||||||
elif key_value in dictionary.values():
|
elif key_value in dictionary.values():
|
||||||
return list(dictionary.keys())[list(dictionary.values()).index(key_value)]
|
return list(dictionary.keys())[list(dictionary.values()).index(key_value)]
|
||||||
elif key_value.lower() in dictionary.values():
|
elif key_value.lower() in dictionary.values():
|
||||||
return list(dictionary.keys())[list(dictionary.values()).index(key_value)]
|
return list(dictionary.keys())[list(dictionary.values()).index(key_value.lower())]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -116,11 +116,14 @@ def generate_library_guids_dict(user_list: dict, generate_output: int):
|
|||||||
|
|
||||||
return show_output_dict, episode_output_dict, movies_output_dict
|
return show_output_dict, episode_output_dict, movies_output_dict
|
||||||
|
|
||||||
def future_thread_executor(args: list):
|
def future_thread_executor(args: list, workers: int = -1):
|
||||||
futures_list = []
|
futures_list = []
|
||||||
results = []
|
results = []
|
||||||
|
workers=1
|
||||||
|
if workers == -1:
|
||||||
|
workers = min(32, os.cpu_count()*1.25)
|
||||||
|
|
||||||
with ThreadPoolExecutor() as executor:
|
with ThreadPoolExecutor(max_workers=workers) as executor:
|
||||||
for arg in args:
|
for arg in args:
|
||||||
# * arg unpacks the list into actual arguments
|
# * arg unpacks the list into actual arguments
|
||||||
futures_list.append(executor.submit(*arg))
|
futures_list.append(executor.submit(*arg))
|
||||||
|
|||||||
357
src/jellyfin.py
357
src/jellyfin.py
@@ -39,216 +39,241 @@ class Jellyfin():
|
|||||||
response = self.session.post(self.baseurl + query, headers=headers)
|
response = self.session.post(self.baseurl + query, headers=headers)
|
||||||
|
|
||||||
return response.json()
|
return response.json()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger(e, 2)
|
logger(f"Jellyfin: Query failed {e}", 2)
|
||||||
logger(response, 2)
|
raise Exception(e)
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
users = {}
|
try:
|
||||||
|
users = {}
|
||||||
|
|
||||||
query = "/Users"
|
query = "/Users"
|
||||||
response = self.query(query, "get")
|
response = self.query(query, "get")
|
||||||
|
|
||||||
# If reponse is not empty
|
# If reponse is not empty
|
||||||
if response:
|
if response:
|
||||||
for user in response:
|
for user in response:
|
||||||
users[user["Name"]] = user["Id"]
|
users[user["Name"]] = user["Id"]
|
||||||
|
|
||||||
return users
|
return users
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Jellyfin: Get users failed {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def get_user_watched(self, user_name, user_id, library_type, library_id, library_title):
|
def get_user_watched(self, user_name, user_id, library_type, library_id, library_title):
|
||||||
user_watched = {}
|
try:
|
||||||
user_watched[user_name] = {}
|
user_name = user_name.lower()
|
||||||
|
user_watched = {}
|
||||||
|
user_watched[user_name] = {}
|
||||||
|
|
||||||
logger(f"Jellyfin: Generating watched for {user_name} in library {library_title}", 0)
|
logger(f"Jellyfin: Generating watched for {user_name} in library {library_title}", 0)
|
||||||
# Movies
|
# Movies
|
||||||
if library_type == "Movie":
|
if library_type == "Movie":
|
||||||
user_watched[user_name][library_title] = []
|
user_watched[user_name][library_title] = []
|
||||||
watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds", "get")
|
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"]:
|
for movie in watched["Items"]:
|
||||||
if movie["UserData"]["Played"] == True:
|
if movie["UserData"]["Played"] == True:
|
||||||
if movie["ProviderIds"]:
|
if movie["ProviderIds"]:
|
||||||
# Lowercase movie["ProviderIds"] keys
|
# Lowercase movie["ProviderIds"] keys
|
||||||
movie["ProviderIds"] = {k.lower(): v for k, v in movie["ProviderIds"].items()}
|
movie["ProviderIds"] = {k.lower(): v for k, v in movie["ProviderIds"].items()}
|
||||||
user_watched[user_name][library_title].append(movie["ProviderIds"])
|
user_watched[user_name][library_title].append(movie["ProviderIds"])
|
||||||
|
|
||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type == "Episode":
|
if library_type == "Episode":
|
||||||
user_watched[user_name][library_title] = {}
|
user_watched[user_name][library_title] = {}
|
||||||
watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Fields=ItemCounts,ProviderIds", "get")
|
watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"]
|
watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"]
|
||||||
|
|
||||||
for show in watched_shows:
|
for show in watched_shows:
|
||||||
show_guids = {k.lower(): v for k, v in show["ProviderIds"].items()}
|
show_guids = {k.lower(): v for k, v in show["ProviderIds"].items()}
|
||||||
show_guids["title"] = show["Name"]
|
show_guids["title"] = show["Name"]
|
||||||
show_guids = frozenset(show_guids.items())
|
show_guids = frozenset(show_guids.items())
|
||||||
seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
if len(seasons["Items"]) > 0:
|
if len(seasons["Items"]) > 0:
|
||||||
for season in seasons["Items"]:
|
for season in seasons["Items"]:
|
||||||
episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
if len(episodes["Items"]) > 0:
|
if len(episodes["Items"]) > 0:
|
||||||
for episode in episodes["Items"]:
|
for episode in episodes["Items"]:
|
||||||
if episode["UserData"]["Played"] == True:
|
if episode["UserData"]["Played"] == True:
|
||||||
if episode["ProviderIds"]:
|
if episode["ProviderIds"]:
|
||||||
if show_guids not in user_watched[user_name][library_title]:
|
if show_guids not in user_watched[user_name][library_title]:
|
||||||
user_watched[user_name][library_title][show_guids] = {}
|
user_watched[user_name][library_title][show_guids] = {}
|
||||||
if season["Name"] not in user_watched[user_name][library_title][show_guids]:
|
if season["Name"] not in user_watched[user_name][library_title][show_guids]:
|
||||||
user_watched[user_name][library_title][show_guids][season["Name"]] = []
|
user_watched[user_name][library_title][show_guids][season["Name"]] = []
|
||||||
|
|
||||||
# Lowercase episode["ProviderIds"] keys
|
# Lowercase episode["ProviderIds"] keys
|
||||||
episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
||||||
user_watched[user_name][library_title][show_guids][season["Name"]].append(episode["ProviderIds"])
|
user_watched[user_name][library_title][show_guids][season["Name"]].append(episode["ProviderIds"])
|
||||||
|
|
||||||
return user_watched
|
return user_watched
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Jellyfin: Failed to get watched for {user_name} in library {library_title}, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
|
||||||
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None):
|
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None):
|
||||||
users_watched = {}
|
try:
|
||||||
args = []
|
users_watched = {}
|
||||||
|
args = []
|
||||||
|
|
||||||
for user_name, user_id in users.items():
|
for user_name, user_id in users.items():
|
||||||
# Get all libraries
|
# Get all libraries
|
||||||
user_name = user_name.lower()
|
user_name = user_name.lower()
|
||||||
|
|
||||||
libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||||
|
|
||||||
for library in libraries:
|
for library in libraries:
|
||||||
library_title = library["Name"]
|
library_title = library["Name"]
|
||||||
library_id = library["Id"]
|
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")
|
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:
|
if len(watched["Items"]) == 0:
|
||||||
logger(f"Jellyfin: No watched items found in library {library_title}", 1)
|
logger(f"Jellyfin: No watched items found in library {library_title}", 1)
|
||||||
continue
|
continue
|
||||||
else:
|
else:
|
||||||
library_type = watched["Items"][0]["Type"]
|
library_type = watched["Items"][0]["Type"]
|
||||||
|
|
||||||
skip_reason = check_skip_logic(library_title, library_type, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)
|
skip_reason = check_skip_logic(library_title, library_type, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)
|
||||||
|
|
||||||
if skip_reason:
|
if skip_reason:
|
||||||
logger(f"Jellyfin: Skipping library {library_title} {skip_reason}", 1)
|
logger(f"Jellyfin: Skipping library {library_title} {skip_reason}", 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
args.append([self.get_user_watched, user_name, user_id, library_type, library_id, library_title])
|
args.append([self.get_user_watched, user_name, user_id, library_type, library_id, library_title])
|
||||||
|
|
||||||
for user_watched in future_thread_executor(args):
|
for user_watched in future_thread_executor(args):
|
||||||
for user, user_watched_temp in user_watched.items():
|
for user, user_watched_temp in user_watched.items():
|
||||||
if user not in users_watched:
|
if user not in users_watched:
|
||||||
users_watched[user] = {}
|
users_watched[user] = {}
|
||||||
users_watched[user].update(user_watched_temp)
|
users_watched[user].update(user_watched_temp)
|
||||||
|
|
||||||
return users_watched
|
|
||||||
|
|
||||||
|
return users_watched
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Jellyfin: Failed to get watched, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def update_user_watched(self, user, user_id, library, library_id, videos, dryrun):
|
def update_user_watched(self, user, user_id, library, library_id, videos, dryrun):
|
||||||
logger(f"Jellyfin: Updating watched for {user} in library {library}", 1)
|
try:
|
||||||
library_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&limit=1", "get")
|
logger(f"Jellyfin: Updating watched for {user} in library {library}", 1)
|
||||||
library_type = library_search["Items"][0]["Type"]
|
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"]
|
||||||
|
|
||||||
# Movies
|
# Movies
|
||||||
if library_type == "Movie":
|
if library_type == "Movie":
|
||||||
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
||||||
|
|
||||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
||||||
for jellyfin_video in jellyfin_search["Items"]:
|
for jellyfin_video in jellyfin_search["Items"]:
|
||||||
if str_to_bool(jellyfin_video["UserData"]["Played"]) == False:
|
if str_to_bool(jellyfin_video["UserData"]["Played"]) == False:
|
||||||
jellyfin_video_id = jellyfin_video["Id"]
|
jellyfin_video_id = jellyfin_video["Id"]
|
||||||
|
|
||||||
for movie_provider_source, movie_provider_id in jellyfin_video["ProviderIds"].items():
|
for movie_provider_source, movie_provider_id in jellyfin_video["ProviderIds"].items():
|
||||||
if movie_provider_source.lower() in videos_movies_ids:
|
if movie_provider_source.lower() in videos_movies_ids:
|
||||||
if movie_provider_id.lower() in videos_movies_ids[movie_provider_source.lower()]:
|
if movie_provider_id.lower() in videos_movies_ids[movie_provider_source.lower()]:
|
||||||
msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin"
|
msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marking {msg}", 0)
|
logger(f"Marking {msg}", 0)
|
||||||
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post")
|
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post")
|
||||||
else:
|
else:
|
||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
break
|
break
|
||||||
|
|
||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type == "Episode":
|
if library_type == "Episode":
|
||||||
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
||||||
|
|
||||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
||||||
jellyfin_shows = [x for x in jellyfin_search["Items"]]
|
jellyfin_shows = [x for x in jellyfin_search["Items"]]
|
||||||
|
|
||||||
for jellyfin_show in jellyfin_shows:
|
for jellyfin_show in jellyfin_shows:
|
||||||
show_found = False
|
show_found = False
|
||||||
for show_provider_source, show_provider_id in jellyfin_show["ProviderIds"].items():
|
for show_provider_source, show_provider_id in jellyfin_show["ProviderIds"].items():
|
||||||
if show_provider_source.lower() in videos_shows_ids:
|
if show_provider_source.lower() in videos_shows_ids:
|
||||||
if show_provider_id.lower() in videos_shows_ids[show_provider_source.lower()]:
|
if show_provider_id.lower() in videos_shows_ids[show_provider_source.lower()]:
|
||||||
show_found = True
|
show_found = True
|
||||||
jellyfin_show_id = jellyfin_show["Id"]
|
jellyfin_show_id = jellyfin_show["Id"]
|
||||||
jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
for jellyfin_episode in jellyfin_episodes["Items"]:
|
for jellyfin_episode in jellyfin_episodes["Items"]:
|
||||||
if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False:
|
if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False:
|
||||||
jellyfin_episode_id = jellyfin_episode["Id"]
|
jellyfin_episode_id = jellyfin_episode["Id"]
|
||||||
|
|
||||||
for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items():
|
for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items():
|
||||||
if episode_provider_source.lower() in videos_episode_ids:
|
if episode_provider_source.lower() in videos_episode_ids:
|
||||||
if episode_provider_id.lower() in videos_episode_ids[episode_provider_source.lower()]:
|
if episode_provider_id.lower() in videos_episode_ids[episode_provider_source.lower()]:
|
||||||
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['IndexNumber']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin"
|
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['IndexNumber']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marked {msg}", 0)
|
logger(f"Marked {msg}", 0)
|
||||||
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post")
|
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post")
|
||||||
else:
|
else:
|
||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
break
|
break
|
||||||
|
|
||||||
if show_found:
|
if show_found:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Jellyfin: Error updating watched for {user} in library {library}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
|
||||||
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
||||||
args = []
|
try:
|
||||||
for user, libraries in watched_list.items():
|
args = []
|
||||||
user_other = None
|
for user, libraries in watched_list.items():
|
||||||
if user_mapping:
|
user_other = None
|
||||||
if user in user_mapping.keys():
|
if user_mapping:
|
||||||
user_other = user_mapping[user]
|
if user in user_mapping.keys():
|
||||||
elif user in user_mapping.values():
|
user_other = user_mapping[user]
|
||||||
user_other = search_mapping(user_mapping, user)
|
elif user in user_mapping.values():
|
||||||
|
user_other = search_mapping(user_mapping, user)
|
||||||
|
|
||||||
user_id = None
|
user_id = None
|
||||||
for key in self.users.keys():
|
for key in self.users.keys():
|
||||||
if user.lower() == key.lower():
|
if user.lower() == key.lower():
|
||||||
user_id = self.users[key]
|
user_id = self.users[key]
|
||||||
break
|
break
|
||||||
elif user_other and user_other.lower() == key.lower():
|
elif user_other and user_other.lower() == key.lower():
|
||||||
user_id = self.users[key]
|
user_id = self.users[key]
|
||||||
break
|
break
|
||||||
|
|
||||||
if not user_id:
|
if not user_id:
|
||||||
logger(f"{user} {user_other} not found in Jellyfin", 2)
|
logger(f"{user} {user_other} not found in Jellyfin", 2)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||||
|
|
||||||
for library, videos in libraries.items():
|
for library, videos in libraries.items():
|
||||||
library_other = None
|
library_other = None
|
||||||
if library_mapping:
|
if library_mapping:
|
||||||
if library in library_mapping.keys():
|
if library in library_mapping.keys():
|
||||||
library_other = library_mapping[library]
|
library_other = library_mapping[library]
|
||||||
elif library in library_mapping.values():
|
elif library in library_mapping.values():
|
||||||
library_other = search_mapping(library_mapping, library)
|
library_other = search_mapping(library_mapping, library)
|
||||||
|
|
||||||
|
|
||||||
if library.lower() not in [x["Name"].lower() for x in jellyfin_libraries]:
|
if library.lower() not in [x["Name"].lower() for x in jellyfin_libraries]:
|
||||||
if library_other and library_other.lower() in [x["Name"].lower() for x in jellyfin_libraries]:
|
if library_other:
|
||||||
logger(f"Plex: Library {library} not found, but {library_other} found, using {library_other}", 1)
|
if library_other.lower() in [x["Name"].lower() for x in jellyfin_libraries]:
|
||||||
library = library_other
|
logger(f"Jellyfin: Library {library} not found, but {library_other} found, using {library_other}", 1)
|
||||||
else:
|
library = library_other
|
||||||
logger(f"Library {library} {library_other} not found in Plex library list", 2)
|
else:
|
||||||
continue
|
logger(f"Jellyfin: Library {library} or {library_other} not found in library list", 2)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger(f"Jellyfin: Library {library} not found in library list", 2)
|
||||||
|
continue
|
||||||
|
|
||||||
|
library_id = None
|
||||||
|
for jellyfin_library in jellyfin_libraries:
|
||||||
|
if jellyfin_library["Name"] == library:
|
||||||
|
library_id = jellyfin_library["Id"]
|
||||||
|
continue
|
||||||
|
|
||||||
library_id = None
|
if library_id:
|
||||||
for jellyfin_library in jellyfin_libraries:
|
args.append([self.update_user_watched, user, user_id, library, library_id, videos, dryrun])
|
||||||
if jellyfin_library["Name"] == library:
|
|
||||||
library_id = jellyfin_library["Id"]
|
|
||||||
continue
|
|
||||||
|
|
||||||
if library_id:
|
future_thread_executor(args)
|
||||||
args.append([self.update_user_watched, user, user_id, library, library_id, videos, dryrun])
|
except Exception as e:
|
||||||
|
logger(f"Jellyfin: Error updating watched", 2)
|
||||||
future_thread_executor(args)
|
raise Exception(e)
|
||||||
|
|||||||
400
src/main.py
Normal file
400
src/main.py
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
import copy, os, traceback, json
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
from time import sleep
|
||||||
|
|
||||||
|
from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict, future_thread_executor
|
||||||
|
from src.plex import Plex
|
||||||
|
from src.jellyfin import Jellyfin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
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"library {library_1} and {library_other} not found in watched list 2", 1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Movies
|
||||||
|
if isinstance(watched_list_1[user_1][library_1], list):
|
||||||
|
for item in watched_list_1[user_1][library_1]:
|
||||||
|
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]:
|
||||||
|
logger(f"Removing {item} from {library_1}", 3)
|
||||||
|
modified_watched_list_1[user_1][library_1].remove(item)
|
||||||
|
|
||||||
|
|
||||||
|
# TV Shows
|
||||||
|
elif isinstance(watched_list_1[user_1][library_1], dict):
|
||||||
|
# Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1
|
||||||
|
_, episode_watched_list_2_keys_dict, _ = generate_library_guids_dict(watched_list_2[user_2][library_2], 1)
|
||||||
|
|
||||||
|
for show_key_1 in watched_list_1[user_1][library_1].keys():
|
||||||
|
show_key_dict = dict(show_key_1)
|
||||||
|
for season in watched_list_1[user_1][library_1][show_key_1]:
|
||||||
|
for episode in watched_list_1[user_1][library_1][show_key_1][season]:
|
||||||
|
for episode_key, episode_item in episode.items():
|
||||||
|
# If episode_key and episode_item are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1
|
||||||
|
if episode_key in episode_watched_list_2_keys_dict.keys():
|
||||||
|
if episode_item in episode_watched_list_2_keys_dict[episode_key]:
|
||||||
|
if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]:
|
||||||
|
logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 3)
|
||||||
|
modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode)
|
||||||
|
|
||||||
|
# Remove empty seasons
|
||||||
|
if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0:
|
||||||
|
if season in modified_watched_list_1[user_1][library_1][show_key_1]:
|
||||||
|
logger(f"Removing {season} from {library_1} because it is empty", 3)
|
||||||
|
del modified_watched_list_1[user_1][library_1][show_key_1][season]
|
||||||
|
|
||||||
|
# If the show is empty, remove the show
|
||||||
|
if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0:
|
||||||
|
if show_key_1 in modified_watched_list_1[user_1][library_1]:
|
||||||
|
logger(f"Removing {show_key_dict['title']} from {library_1} because it is empty", 1)
|
||||||
|
del modified_watched_list_1[user_1][library_1][show_key_1]
|
||||||
|
|
||||||
|
for user_1 in watched_list_1:
|
||||||
|
for library_1 in watched_list_1[user_1]:
|
||||||
|
if library_1 in modified_watched_list_1[user_1]:
|
||||||
|
# If library is empty then remove it
|
||||||
|
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
||||||
|
logger(f"Removing {library_1} from {user_1} because it is empty", 1)
|
||||||
|
del modified_watched_list_1[user_1][library_1]
|
||||||
|
|
||||||
|
if user_1 in modified_watched_list_1:
|
||||||
|
# If user is empty delete user
|
||||||
|
if len(modified_watched_list_1[user_1]) == 0:
|
||||||
|
logger(f"Removing {user_1} from watched list 1 because it is empty", 1)
|
||||||
|
del modified_watched_list_1[user_1]
|
||||||
|
|
||||||
|
return modified_watched_list_1
|
||||||
|
|
||||||
|
def 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.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.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)
|
||||||
|
|
||||||
|
blacklist_library_type = os.getenv("BLACKLIST_LIBRARY_TYPE")
|
||||||
|
if blacklist_library_type:
|
||||||
|
if len(blacklist_library_type) > 0:
|
||||||
|
blacklist_library_type = blacklist_library_type.split(",")
|
||||||
|
blacklist_library_type = [x.lower().strip() for x in blacklist_library_type]
|
||||||
|
else:
|
||||||
|
blacklist_library_type = []
|
||||||
|
logger(f"Blacklist Library Type: {blacklist_library_type}", 1)
|
||||||
|
|
||||||
|
whitelist_library_type = os.getenv("WHITELIST_LIBRARY_TYPE")
|
||||||
|
if whitelist_library_type:
|
||||||
|
if len(whitelist_library_type) > 0:
|
||||||
|
whitelist_library_type = whitelist_library_type.split(",")
|
||||||
|
whitelist_library_type = [x.lower().strip() for x in whitelist_library_type]
|
||||||
|
else:
|
||||||
|
whitelist_library_type = []
|
||||||
|
logger(f"Whitelist Library Type: {whitelist_library_type}", 1)
|
||||||
|
|
||||||
|
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]
|
||||||
|
else:
|
||||||
|
blacklist_users = []
|
||||||
|
logger(f"Blacklist Users: {blacklist_users}", 1)
|
||||||
|
|
||||||
|
whitelist_users = os.getenv("WHITELIST_USERS")
|
||||||
|
if whitelist_users:
|
||||||
|
if len(whitelist_users) > 0:
|
||||||
|
whitelist_users = whitelist_users.split(",")
|
||||||
|
whitelist_users = [x.lower().strip() for x in whitelist_users]
|
||||||
|
else:
|
||||||
|
whitelist_users = []
|
||||||
|
else:
|
||||||
|
whitelist_users = []
|
||||||
|
logger(f"Whitelist Users: {whitelist_users}", 1)
|
||||||
|
|
||||||
|
return blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users
|
||||||
|
|
||||||
|
def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mapping=None):
|
||||||
|
|
||||||
|
# generate list of users from server 1 and server 2
|
||||||
|
server_1_type = server_1[0]
|
||||||
|
server_1_connection = server_1[1]
|
||||||
|
server_2_type = server_2[0]
|
||||||
|
server_2_connection = server_2[1]
|
||||||
|
|
||||||
|
server_1_users = []
|
||||||
|
if server_1_type == "plex":
|
||||||
|
server_1_users = [ x.title.lower() for x in server_1_connection.users ]
|
||||||
|
elif server_1_type == "jellyfin":
|
||||||
|
server_1_users = [ key.lower() for key in server_1_connection.users.keys() ]
|
||||||
|
|
||||||
|
server_2_users = []
|
||||||
|
if server_2_type == "plex":
|
||||||
|
server_2_users = [ x.title.lower() for x in server_2_connection.users ]
|
||||||
|
elif server_2_type == "jellyfin":
|
||||||
|
server_2_users = [ key.lower() for key in server_2_connection.users.keys() ]
|
||||||
|
|
||||||
|
|
||||||
|
# combined list of overlapping users from plex and jellyfin
|
||||||
|
users = {}
|
||||||
|
|
||||||
|
for server_1_user in server_1_users:
|
||||||
|
if user_mapping:
|
||||||
|
jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user)
|
||||||
|
if jellyfin_plex_mapped_user:
|
||||||
|
users[server_1_user] = jellyfin_plex_mapped_user
|
||||||
|
continue
|
||||||
|
|
||||||
|
if server_1_user in server_2_users:
|
||||||
|
users[server_1_user] = server_1_user
|
||||||
|
|
||||||
|
for server_2_user in server_2_users:
|
||||||
|
if user_mapping:
|
||||||
|
plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user)
|
||||||
|
if plex_jellyfin_mapped_user:
|
||||||
|
users[plex_jellyfin_mapped_user] = server_2_user
|
||||||
|
continue
|
||||||
|
|
||||||
|
if server_2_user in server_1_users:
|
||||||
|
users[server_2_user] = server_2_user
|
||||||
|
|
||||||
|
logger(f"User list that exist on both servers {users}", 1)
|
||||||
|
|
||||||
|
users_filtered = {}
|
||||||
|
for user in users:
|
||||||
|
# whitelist_user is not empty and user lowercase is not in whitelist lowercase
|
||||||
|
if len(whitelist_users) > 0:
|
||||||
|
if user not in whitelist_users and users[user] not in whitelist_users:
|
||||||
|
logger(f"{user} or {users[user]} is not in whitelist", 1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if user not in blacklist_users and users[user] not in blacklist_users:
|
||||||
|
users_filtered[user] = users[user]
|
||||||
|
|
||||||
|
logger(f"Filtered user list {users_filtered}", 1)
|
||||||
|
|
||||||
|
if server_1_type == "plex":
|
||||||
|
output_server_1_users = []
|
||||||
|
for plex_user in server_1_connection.users:
|
||||||
|
if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values():
|
||||||
|
output_server_1_users.append(plex_user)
|
||||||
|
elif server_1_type == "jellyfin":
|
||||||
|
output_server_1_users = {}
|
||||||
|
for jellyfin_user, jellyfin_id in server_1_connection.users.items():
|
||||||
|
if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values():
|
||||||
|
output_server_1_users[jellyfin_user] = jellyfin_id
|
||||||
|
|
||||||
|
if server_2_type == "plex":
|
||||||
|
output_server_2_users = []
|
||||||
|
for plex_user in server_2_connection.users:
|
||||||
|
if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values():
|
||||||
|
output_server_2_users.append(plex_user)
|
||||||
|
elif server_2_type == "jellyfin":
|
||||||
|
output_server_2_users = {}
|
||||||
|
for jellyfin_user, jellyfin_id in server_2_connection.users.items():
|
||||||
|
if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values():
|
||||||
|
output_server_2_users[jellyfin_user] = jellyfin_id
|
||||||
|
|
||||||
|
if len(output_server_1_users) == 0:
|
||||||
|
raise Exception(f"No users found for server 1, users found {users} filtered users {users_filtered}")
|
||||||
|
|
||||||
|
if len(output_server_2_users) == 0:
|
||||||
|
raise Exception(f"No users found for server 2, users found {users} filtered users {users_filtered}")
|
||||||
|
|
||||||
|
logger(f"Server 1 users: {output_server_1_users}", 1)
|
||||||
|
logger(f"Server 2 users: {output_server_2_users}", 1)
|
||||||
|
|
||||||
|
return output_server_1_users, output_server_2_users
|
||||||
|
|
||||||
|
def generate_server_connections():
|
||||||
|
servers = []
|
||||||
|
|
||||||
|
plex_baseurl = os.getenv("PLEX_BASEURL", None)
|
||||||
|
plex_token = os.getenv("PLEX_TOKEN", None)
|
||||||
|
plex_username = os.getenv("PLEX_USERNAME", None)
|
||||||
|
plex_password = os.getenv("PLEX_PASSWORD", None)
|
||||||
|
plex_servername = os.getenv("PLEX_SERVERNAME", None)
|
||||||
|
|
||||||
|
if plex_baseurl and plex_token:
|
||||||
|
plex_baseurl = plex_baseurl.split(",")
|
||||||
|
plex_token = plex_token.split(",")
|
||||||
|
|
||||||
|
if len(plex_baseurl) != len(plex_token):
|
||||||
|
raise Exception("PLEX_BASEURL and PLEX_TOKEN must have the same number of entries")
|
||||||
|
|
||||||
|
for i, url in enumerate(plex_baseurl):
|
||||||
|
servers.append(("plex", Plex(baseurl=url.strip(), token=plex_token[i].strip(), username=None, password=None, servername=None)))
|
||||||
|
|
||||||
|
if plex_username and plex_password and plex_servername:
|
||||||
|
plex_username = plex_username.split(",")
|
||||||
|
plex_password = plex_password.split(",")
|
||||||
|
plex_servername = plex_servername.split(",")
|
||||||
|
|
||||||
|
if len(plex_username) != len(plex_password) or len(plex_username) != len(plex_servername):
|
||||||
|
raise Exception("PLEX_USERNAME, PLEX_PASSWORD and PLEX_SERVERNAME must have the same number of entries")
|
||||||
|
|
||||||
|
for i, username in enumerate(plex_username):
|
||||||
|
servers.append(("plex", Plex(baseurl=None, token=None, username=username.strip(), password=plex_password[i].strip(), servername=plex_servername[i].strip())))
|
||||||
|
|
||||||
|
jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None)
|
||||||
|
jellyfin_token = os.getenv("JELLYFIN_TOKEN", None)
|
||||||
|
|
||||||
|
if jellyfin_baseurl and jellyfin_token:
|
||||||
|
jellyfin_baseurl = jellyfin_baseurl.split(",")
|
||||||
|
jellyfin_token = jellyfin_token.split(",")
|
||||||
|
|
||||||
|
if len(jellyfin_baseurl) != len(jellyfin_token):
|
||||||
|
raise Exception("JELLYFIN_BASEURL and JELLYFIN_TOKEN must have the same number of entries")
|
||||||
|
|
||||||
|
for i, baseurl in enumerate(jellyfin_baseurl):
|
||||||
|
servers.append(("jellyfin", Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip())))
|
||||||
|
|
||||||
|
return servers
|
||||||
|
|
||||||
|
def main_loop():
|
||||||
|
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)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
# 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 server connections
|
||||||
|
servers = generate_server_connections()
|
||||||
|
|
||||||
|
for server_1 in servers:
|
||||||
|
# If server is the final server in the list, then we are done with the loop
|
||||||
|
if server_1 == servers[-1]:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Start server_2 at the next server in the list
|
||||||
|
for server_2 in servers[servers.index(server_1) + 1:]:
|
||||||
|
|
||||||
|
server_1_connection = server_1[1]
|
||||||
|
server_2_connection = server_2[1]
|
||||||
|
|
||||||
|
# Create users list
|
||||||
|
server_1_users, server_2_users = setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mapping)
|
||||||
|
|
||||||
|
args = [[server_1_connection.get_watched, server_1_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping]
|
||||||
|
, [server_2_connection.get_watched, server_2_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping]]
|
||||||
|
|
||||||
|
results = future_thread_executor(args)
|
||||||
|
server_1_watched = results[0]
|
||||||
|
server_2_watched = results[1]
|
||||||
|
logger(f"Server 1 watched: {server_1_watched}", 3)
|
||||||
|
logger(f"Server 2 watched: {server_2_watched}", 3)
|
||||||
|
|
||||||
|
# clone watched so it isnt modified in the cleanup function so all duplicates are actually removed
|
||||||
|
server_1_watched_filtered = copy.deepcopy(server_1_watched)
|
||||||
|
server_2_watched_filtered = copy.deepcopy(server_2_watched)
|
||||||
|
|
||||||
|
logger("Cleaning Server 1 Watched", 1)
|
||||||
|
server_1_watched_filtered = cleanup_watched(server_1_watched, server_2_watched, user_mapping, library_mapping)
|
||||||
|
|
||||||
|
logger("Cleaning Server 2 Watched", 1)
|
||||||
|
server_2_watched_filtered = cleanup_watched(server_2_watched, server_1_watched, user_mapping, library_mapping)
|
||||||
|
|
||||||
|
logger(f"server 1 watched that needs to be synced to server 2:\n{server_1_watched_filtered}", 1)
|
||||||
|
logger(f"server 2 watched that needs to be synced to server 1:\n{server_2_watched_filtered}", 1)
|
||||||
|
|
||||||
|
args= [[server_1_connection.update_watched, server_2_watched_filtered, user_mapping, library_mapping, dryrun]
|
||||||
|
, [server_2_connection.update_watched, server_1_watched_filtered, user_mapping, library_mapping, dryrun]]
|
||||||
|
|
||||||
|
future_thread_executor(args)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
sleep_duration = float(os.getenv("SLEEP_DURATION", "3600"))
|
||||||
|
|
||||||
|
while(True):
|
||||||
|
try:
|
||||||
|
main_loop()
|
||||||
|
logger(f"Looping in {sleep_duration}")
|
||||||
|
sleep(sleep_duration)
|
||||||
|
except Exception as error:
|
||||||
|
if isinstance(error, list):
|
||||||
|
for message in error:
|
||||||
|
logger(message, log_type=2)
|
||||||
|
else:
|
||||||
|
logger(error, log_type=2)
|
||||||
|
|
||||||
|
|
||||||
|
logger(traceback.format_exc(), 2)
|
||||||
|
logger(f"Retrying in {sleep_duration}", log_type=0)
|
||||||
|
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
logger("Exiting", log_type=0)
|
||||||
|
os._exit(0)
|
||||||
352
src/plex.py
352
src/plex.py
@@ -37,209 +37,235 @@ class Plex:
|
|||||||
logger(f"Plex: Failed to login, {msg}, Error: {e}", 2)
|
logger(f"Plex: Failed to login, {msg}, Error: {e}", 2)
|
||||||
else:
|
else:
|
||||||
logger(f"Plex: Failed to login, Error: {e}", 2)
|
logger(f"Plex: Failed to login, Error: {e}", 2)
|
||||||
return None
|
raise Exception(e)
|
||||||
|
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
users = self.plex.myPlexAccount().users()
|
try:
|
||||||
|
users = self.plex.myPlexAccount().users()
|
||||||
|
|
||||||
# append self to users
|
# append self to users
|
||||||
users.append(self.plex.myPlexAccount())
|
users.append(self.plex.myPlexAccount())
|
||||||
|
|
||||||
return users
|
return users
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to get users, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def get_user_watched(self, user, user_plex, library):
|
def get_user_watched(self, user, user_plex, library):
|
||||||
user_watched = {}
|
try:
|
||||||
user_watched[user.title] = {}
|
user_name = user.title.lower()
|
||||||
|
user_watched = {}
|
||||||
|
user_watched[user_name] = {}
|
||||||
|
|
||||||
logger(f"Plex: Generating watched for {user.title} in library {library.title}", 0)
|
logger(f"Plex: Generating watched for {user_name} in library {library.title}", 0)
|
||||||
|
|
||||||
if library.type == "movie":
|
if library.type == "movie":
|
||||||
user_watched[user.title][library.title] = []
|
user_watched[user_name][library.title] = []
|
||||||
|
|
||||||
library_videos = user_plex.library.section(library.title)
|
library_videos = user_plex.library.section(library.title)
|
||||||
for video in library_videos.search(unmatched=False, unwatched=False):
|
for video in library_videos.search(unmatched=False, unwatched=False):
|
||||||
guids = {}
|
guids = {}
|
||||||
for guid in video.guids:
|
for guid in video.guids:
|
||||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||||
guids[guid_source] = guid_id
|
guids[guid_source] = guid_id
|
||||||
user_watched[user.title][library.title].append(guids)
|
user_watched[user_name][library.title].append(guids)
|
||||||
|
|
||||||
elif library.type == "show":
|
elif library.type == "show":
|
||||||
user_watched[user.title][library.title] = {}
|
user_watched[user_name][library.title] = {}
|
||||||
|
|
||||||
library_videos = user_plex.library.section(library.title)
|
library_videos = user_plex.library.section(library.title)
|
||||||
for show in library_videos.search(unmatched=False, unwatched=False):
|
for show in library_videos.search(unmatched=False, unwatched=False):
|
||||||
show_guids = {}
|
show_guids = {}
|
||||||
for show_guid in show.guids:
|
for show_guid in show.guids:
|
||||||
show_guids["title"] = show.title
|
show_guids["title"] = show.title
|
||||||
# Extract after :// from guid.id
|
# Extract after :// from guid.id
|
||||||
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower()
|
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower()
|
||||||
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1)
|
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1)
|
||||||
show_guids[show_guid_source] = show_guid_id
|
show_guids[show_guid_source] = show_guid_id
|
||||||
show_guids = frozenset(show_guids.items())
|
show_guids = frozenset(show_guids.items())
|
||||||
|
|
||||||
for season in show.seasons():
|
for season in show.seasons():
|
||||||
episode_guids = []
|
episode_guids = []
|
||||||
for episode in season.episodes():
|
for episode in season.episodes():
|
||||||
if episode.viewCount > 0:
|
if episode.viewCount > 0:
|
||||||
episode_guids_temp = {}
|
episode_guids_temp = {}
|
||||||
for guid in episode.guids:
|
for guid in episode.guids:
|
||||||
# Extract after :// from guid.id
|
# Extract after :// from guid.id
|
||||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||||
episode_guids_temp[guid_source] = guid_id
|
episode_guids_temp[guid_source] = guid_id
|
||||||
|
|
||||||
episode_guids.append(episode_guids_temp)
|
episode_guids.append(episode_guids_temp)
|
||||||
|
|
||||||
if episode_guids:
|
if episode_guids:
|
||||||
# append show, season, episode
|
# append show, season, episode
|
||||||
if show_guids not in user_watched[user.title][library.title]:
|
if show_guids not in user_watched[user_name][library.title]:
|
||||||
user_watched[user.title][library.title][show_guids] = {}
|
user_watched[user_name][library.title][show_guids] = {}
|
||||||
if season.title not in user_watched[user.title][library.title][show_guids]:
|
if season.title not in user_watched[user_name][library.title][show_guids]:
|
||||||
user_watched[user.title][library.title][show_guids][season.title] = {}
|
user_watched[user_name][library.title][show_guids][season.title] = {}
|
||||||
user_watched[user.title][library.title][show_guids][season.title] = episode_guids
|
user_watched[user_name][library.title][show_guids][season.title] = episode_guids
|
||||||
|
|
||||||
|
|
||||||
return user_watched
|
return user_watched
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to get watched for {user_name} in library {library.title}, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
|
||||||
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping):
|
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping):
|
||||||
# Get all libraries
|
try:
|
||||||
users_watched = {}
|
# Get all libraries
|
||||||
args = []
|
users_watched = {}
|
||||||
|
args = []
|
||||||
|
|
||||||
for user in users:
|
for user in users:
|
||||||
if self.admin_user == user:
|
if self.admin_user == user:
|
||||||
user_plex = self.plex
|
user_plex = self.plex
|
||||||
else:
|
else:
|
||||||
user_plex = PlexServer(self.plex._baseurl, user.get_token(self.plex.machineIdentifier))
|
user_plex = PlexServer(self.plex._baseurl, user.get_token(self.plex.machineIdentifier))
|
||||||
|
|
||||||
libraries = user_plex.library.sections()
|
libraries = user_plex.library.sections()
|
||||||
|
|
||||||
for library in libraries:
|
for library in libraries:
|
||||||
library_title = library.title
|
library_title = library.title
|
||||||
library_type = library.type
|
library_type = library.type
|
||||||
|
|
||||||
skip_reason = check_skip_logic(library_title, library_type, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)
|
skip_reason = check_skip_logic(library_title, library_type, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)
|
||||||
|
|
||||||
if skip_reason:
|
if skip_reason:
|
||||||
logger(f"Plex: Skipping library {library_title} {skip_reason}", 1)
|
logger(f"Plex: Skipping library {library_title} {skip_reason}", 1)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
args.append([self.get_user_watched, user, user_plex, library])
|
args.append([self.get_user_watched, user, user_plex, library])
|
||||||
|
|
||||||
for user_watched in future_thread_executor(args):
|
for user_watched in future_thread_executor(args):
|
||||||
for user, user_watched_temp in user_watched.items():
|
for user, user_watched_temp in user_watched.items():
|
||||||
if user not in users_watched:
|
if user not in users_watched:
|
||||||
users_watched[user] = {}
|
users_watched[user] = {}
|
||||||
users_watched[user].update(user_watched_temp)
|
users_watched[user].update(user_watched_temp)
|
||||||
|
|
||||||
|
return users_watched
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to get watched, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
return users_watched
|
|
||||||
|
|
||||||
def update_user_watched (self, user, user_plex, library, videos, dryrun):
|
def update_user_watched (self, user, user_plex, library, videos, dryrun):
|
||||||
logger(f"Plex: Updating watched for {user.title} in library {library}", 1)
|
try:
|
||||||
library_videos = user_plex.library.section(library)
|
logger(f"Plex: Updating watched for {user.title} in library {library}", 1)
|
||||||
|
library_videos = user_plex.library.section(library)
|
||||||
|
|
||||||
if library_videos.type == "movie":
|
if library_videos.type == "movie":
|
||||||
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
||||||
for movies_search in library_videos.search(unmatched=False, unwatched=True):
|
for movies_search in library_videos.search(unmatched=False, unwatched=True):
|
||||||
for movie_guid in movies_search.guids:
|
for movie_guid in movies_search.guids:
|
||||||
movie_guid_source = re.search(r'(.*)://', movie_guid.id).group(1).lower()
|
movie_guid_source = re.search(r'(.*)://', movie_guid.id).group(1).lower()
|
||||||
movie_guid_id = re.search(r'://(.*)', movie_guid.id).group(1)
|
movie_guid_id = re.search(r'://(.*)', movie_guid.id).group(1)
|
||||||
|
|
||||||
# If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list
|
# If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list
|
||||||
if movie_guid_source in videos_movies_ids.keys():
|
if movie_guid_source in videos_movies_ids.keys():
|
||||||
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
||||||
if movies_search.viewCount == 0:
|
if movies_search.viewCount == 0:
|
||||||
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marked {msg}", 0)
|
logger(f"Marked {msg}", 0)
|
||||||
movies_search.markWatched()
|
movies_search.markWatched()
|
||||||
else:
|
else:
|
||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
elif library_videos.type == "show":
|
||||||
|
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
||||||
|
|
||||||
|
for show_search in library_videos.search(unmatched=False, unwatched=True):
|
||||||
|
show_found = False
|
||||||
|
for show_guid in show_search.guids:
|
||||||
|
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower()
|
||||||
|
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1)
|
||||||
|
|
||||||
|
# If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list
|
||||||
|
if show_guid_source in videos_shows_ids.keys():
|
||||||
|
if show_guid_id in videos_shows_ids[show_guid_source]:
|
||||||
|
show_found = True
|
||||||
|
for episode_search in show_search.episodes():
|
||||||
|
for episode_guid in episode_search.guids:
|
||||||
|
episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower()
|
||||||
|
episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1)
|
||||||
|
|
||||||
|
# If episode provider source and episode provider id are in videos_episode_ids exactly, then the episode is in the list
|
||||||
|
if episode_guid_source in videos_episode_ids.keys():
|
||||||
|
if episode_guid_id in videos_episode_ids[episode_guid_source]:
|
||||||
|
if episode_search.viewCount == 0:
|
||||||
|
msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex"
|
||||||
|
if not dryrun:
|
||||||
|
logger(f"Marked {msg}", 0)
|
||||||
|
episode_search.markWatched()
|
||||||
|
else:
|
||||||
|
logger(f"Dryrun {msg}", 0)
|
||||||
|
break
|
||||||
|
|
||||||
|
if show_found:
|
||||||
break
|
break
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}", 2)
|
||||||
elif library_videos.type == "show":
|
raise Exception(e)
|
||||||
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
|
||||||
|
|
||||||
for show_search in library_videos.search(unmatched=False, unwatched=True):
|
|
||||||
show_found = False
|
|
||||||
for show_guid in show_search.guids:
|
|
||||||
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower()
|
|
||||||
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1)
|
|
||||||
|
|
||||||
# If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list
|
|
||||||
if show_guid_source in videos_shows_ids.keys():
|
|
||||||
if show_guid_id in videos_shows_ids[show_guid_source]:
|
|
||||||
show_found = True
|
|
||||||
for episode_search in show_search.episodes():
|
|
||||||
for episode_guid in episode_search.guids:
|
|
||||||
episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower()
|
|
||||||
episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1)
|
|
||||||
|
|
||||||
# If episode provider source and episode provider id are in videos_episode_ids exactly, then the episode is in the list
|
|
||||||
if episode_guid_source in videos_episode_ids.keys():
|
|
||||||
if episode_guid_id in videos_episode_ids[episode_guid_source]:
|
|
||||||
if episode_search.viewCount == 0:
|
|
||||||
msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex"
|
|
||||||
if not dryrun:
|
|
||||||
logger(f"Marked {msg}", 0)
|
|
||||||
episode_search.markWatched()
|
|
||||||
else:
|
|
||||||
logger(f"Dryrun {msg}", 0)
|
|
||||||
break
|
|
||||||
|
|
||||||
if show_found:
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
||||||
args = []
|
try:
|
||||||
|
args = []
|
||||||
|
|
||||||
for user, libraries in watched_list.items():
|
for user, libraries in watched_list.items():
|
||||||
user_other = None
|
user_other = None
|
||||||
# If type of user is dict
|
# If type of user is dict
|
||||||
if user_mapping:
|
if user_mapping:
|
||||||
if user in user_mapping.keys():
|
if user in user_mapping.keys():
|
||||||
user_other = user_mapping[user]
|
user_other = user_mapping[user]
|
||||||
elif user in user_mapping.values():
|
elif user in user_mapping.values():
|
||||||
user_other = search_mapping(user_mapping, user)
|
user_other = search_mapping(user_mapping, user)
|
||||||
|
|
||||||
for index, value in enumerate(self.users):
|
for index, value in enumerate(self.users):
|
||||||
if user.lower() == value.title.lower():
|
if user.lower() == value.title.lower():
|
||||||
user = self.users[index]
|
user = self.users[index]
|
||||||
break
|
break
|
||||||
elif user_other and user_other.lower() == value.title.lower():
|
elif user_other and user_other.lower() == value.title.lower():
|
||||||
user = self.users[index]
|
user = self.users[index]
|
||||||
break
|
break
|
||||||
|
|
||||||
if self.admin_user == user:
|
if self.admin_user == user:
|
||||||
user_plex = self.plex
|
user_plex = self.plex
|
||||||
else:
|
else:
|
||||||
user_plex = PlexServer(self.plex._baseurl, user.get_token(self.plex.machineIdentifier))
|
user_plex = PlexServer(self.plex._baseurl, user.get_token(self.plex.machineIdentifier))
|
||||||
|
|
||||||
for library, videos in libraries.items():
|
for library, videos in libraries.items():
|
||||||
library_other = None
|
library_other = None
|
||||||
if library_mapping:
|
if library_mapping:
|
||||||
if library in library_mapping.keys():
|
if library in library_mapping.keys():
|
||||||
library_other = library_mapping[library]
|
library_other = library_mapping[library]
|
||||||
elif library in library_mapping.values():
|
elif library in library_mapping.values():
|
||||||
library_other = search_mapping(library_mapping, library)
|
library_other = search_mapping(library_mapping, library)
|
||||||
|
|
||||||
# if library in plex library list
|
# if library in plex library list
|
||||||
library_list = user_plex.library.sections()
|
library_list = user_plex.library.sections()
|
||||||
if library.lower() not in [x.title.lower() for x in library_list]:
|
if library.lower() not in [x.title.lower() for x in library_list]:
|
||||||
if library_other and library_other.lower() in [x.title.lower() for x in library_list]:
|
if library_other:
|
||||||
logger(f"Plex: Library {library} not found, but {library_other} found, using {library_other}", 1)
|
if library_other.lower() in [x.title.lower() for x in library_list]:
|
||||||
library = library_other
|
logger(f"Plex: Library {library} not found, but {library_other} found, using {library_other}", 1)
|
||||||
else:
|
library = library_other
|
||||||
logger(f"Library {library} {library_other} not found in Plex library list", 2)
|
else:
|
||||||
continue
|
logger(f"Plex: Library {library} or {library_other} not found in library list", 2)
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
logger(f"Plex: Library {library} not found in library list", 2)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
args.append([self.update_user_watched, user, user_plex, library, videos, dryrun])
|
args.append([self.update_user_watched, user, user_plex, library, videos, dryrun])
|
||||||
|
|
||||||
future_thread_executor(args)
|
future_thread_executor(args)
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to update watched, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|||||||
Reference in New Issue
Block a user