Black cleanup

pull/27/head
Luigi311 2022-08-17 22:31:23 -06:00
parent dd319271bd
commit c848106ce7
7 changed files with 1072 additions and 309 deletions

View File

@ -1,10 +1,11 @@
import sys import sys
if __name__ == '__main__': if __name__ == "__main__":
# Check python version 3.6 or higher # Check python version 3.6 or higher
if not (3, 6) <= tuple(map(int, sys.version_info[:2])): if not (3, 6) <= tuple(map(int, sys.version_info[:2])):
print("This script requires Python 3.6 or higher") print("This script requires Python 3.6 or higher")
sys.exit(1) sys.exit(1)
from src.main import main from src.main import main
main() main()

View File

@ -6,6 +6,7 @@ load_dotenv(override=True)
logfile = os.getenv("LOGFILE", "log.log") logfile = os.getenv("LOGFILE", "log.log")
def logger(message: str, log_type=0): def logger(message: str, log_type=0):
debug = str_to_bool(os.getenv("DEBUG", "True")) debug = str_to_bool(os.getenv("DEBUG", "True"))
debug_level = os.getenv("DEBUG_LEVEL", "info").lower() debug_level = os.getenv("DEBUG_LEVEL", "info").lower()
@ -29,6 +30,7 @@ def logger(message: str, log_type=0):
file = open(logfile, "a", encoding="utf-8") file = open(logfile, "a", encoding="utf-8")
file.write(output + "\n") file.write(output + "\n")
# Reimplementation of distutils.util.strtobool due to it being deprecated # Reimplementation of distutils.util.strtobool due to it being deprecated
# Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668 # Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668
def str_to_bool(value: any) -> bool: def str_to_bool(value: any) -> bool:
@ -36,6 +38,7 @@ def str_to_bool(value: any) -> bool:
return False return False
return str(value).lower() in ("y", "yes", "t", "true", "on", "1") return str(value).lower() in ("y", "yes", "t", "true", "on", "1")
# Get mapped value # Get mapped value
def search_mapping(dictionary: dict, key_value: str): def search_mapping(dictionary: dict, key_value: str):
if key_value in dictionary.keys(): if key_value in dictionary.keys():
@ -45,12 +48,22 @@ def search_mapping(dictionary: dict, key_value: str):
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.lower())] return list(dictionary.keys())[
list(dictionary.values()).index(key_value.lower())
]
else: else:
return None return None
def check_skip_logic(library_title, library_type, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping): def check_skip_logic(
library_title,
library_type,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
):
skip_reason = None skip_reason = None
if library_type.lower() in blacklist_library_type: if library_type.lower() in blacklist_library_type:
@ -89,7 +102,7 @@ def generate_library_guids_dict(user_list: dict):
try: try:
show_output_keys = user_list.keys() show_output_keys = user_list.keys()
show_output_keys = ([ dict(x) for x in list(show_output_keys) ]) show_output_keys = [dict(x) for x in list(show_output_keys)]
for show_key in show_output_keys: for show_key in show_output_keys:
for provider_key, provider_value in show_key.items(): for provider_key, provider_value in show_key.items():
# Skip title # Skip title
@ -101,7 +114,9 @@ def generate_library_guids_dict(user_list: dict):
for show_location in provider_value: for show_location in provider_value:
show_output_dict[provider_key.lower()].append(show_location) show_output_dict[provider_key.lower()].append(show_location)
else: else:
show_output_dict[provider_key.lower()].append(provider_value.lower()) show_output_dict[provider_key.lower()].append(
provider_value.lower()
)
except: except:
logger("Generating show_output_dict failed, skipping", 1) logger("Generating show_output_dict failed, skipping", 1)
@ -114,9 +129,13 @@ def generate_library_guids_dict(user_list: dict):
episode_output_dict[episode_key.lower()] = [] episode_output_dict[episode_key.lower()] = []
if episode_key == "locations": if episode_key == "locations":
for episode_location in episode_value: for episode_location in episode_value:
episode_output_dict[episode_key.lower()].append(episode_location) episode_output_dict[episode_key.lower()].append(
episode_location
)
else: else:
episode_output_dict[episode_key.lower()].append(episode_value.lower()) episode_output_dict[episode_key.lower()].append(
episode_value.lower()
)
except: except:
logger("Generating episode_output_dict failed, skipping", 1) logger("Generating episode_output_dict failed, skipping", 1)
@ -135,6 +154,7 @@ def generate_library_guids_dict(user_list: dict):
return show_output_dict, episode_output_dict, movies_output_dict return show_output_dict, episode_output_dict, movies_output_dict
def combine_watched_dicts(dicts: list): def combine_watched_dicts(dicts: list):
combined_dict = {} combined_dict = {}
for single_dict in dicts: for single_dict in dicts:
@ -146,6 +166,7 @@ def combine_watched_dicts(dicts: list):
return combined_dict return combined_dict
def future_thread_executor(args: list, workers: int = -1): def future_thread_executor(args: list, workers: int = -1):
futures_list = [] futures_list = []
results = [] results = []

View File

@ -1,7 +1,14 @@
import asyncio, aiohttp import asyncio, aiohttp
from src.functions import logger, search_mapping, check_skip_logic, generate_library_guids_dict, combine_watched_dicts from src.functions import (
logger,
search_mapping,
check_skip_logic,
generate_library_guids_dict,
combine_watched_dicts,
)
class Jellyfin():
class Jellyfin:
def __init__(self, baseurl, token): def __init__(self, baseurl, token):
self.baseurl = baseurl self.baseurl = baseurl
self.token = token self.token = token
@ -14,16 +21,12 @@ class Jellyfin():
self.users = asyncio.run(self.get_users()) self.users = asyncio.run(self.get_users())
async def query(self, query, query_type, session, identifiers=None): async def query(self, query, query_type, session, identifiers=None):
try: try:
results = None results = None
headers = { headers = {"Accept": "application/json", "X-Emby-Token": self.token}
"Accept": "application/json",
"X-Emby-Token": self.token
}
authorization = ( authorization = (
'MediaBrowser , ' "MediaBrowser , "
'Client="other", ' 'Client="other", '
'Device="script", ' 'Device="script", '
'DeviceId="script", ' 'DeviceId="script", '
@ -32,11 +35,15 @@ class Jellyfin():
headers["X-Emby-Authorization"] = authorization headers["X-Emby-Authorization"] = authorization
if query_type == "get": if query_type == "get":
async with session.get(self.baseurl + query, headers=headers) as response: async with session.get(
self.baseurl + query, headers=headers
) as response:
results = await response.json() results = await response.json()
elif query_type == "post": elif query_type == "post":
async with session.post(self.baseurl + query, headers=headers) as response: async with session.post(
self.baseurl + query, headers=headers
) as response:
results = await response.json() results = await response.json()
# append identifiers to results # append identifiers to results
@ -48,7 +55,6 @@ class Jellyfin():
logger(f"Jellyfin: Query failed {e}", 2) logger(f"Jellyfin: Query failed {e}", 2)
raise Exception(e) raise Exception(e)
async def get_users(self): async def get_users(self):
try: try:
users = {} users = {}
@ -67,34 +73,54 @@ class Jellyfin():
logger(f"Jellyfin: Get users failed {e}", 2) logger(f"Jellyfin: Get users failed {e}", 2)
raise Exception(e) raise Exception(e)
async def get_user_watched(
async def get_user_watched(self, user_name, user_id, library_type, library_id, library_title): self, user_name, user_id, library_type, library_id, library_title
):
try: try:
user_name = user_name.lower() user_name = user_name.lower()
user_watched = {} user_watched = {}
user_watched[user_name] = {} 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
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
if library_type == "Movie": if library_type == "Movie":
user_watched[user_name][library_title] = [] user_watched[user_name][library_title] = []
watched = await self.query(f"/Users/{user_id}/Items?ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds,MediaSources", "get", session) watched = await self.query(
f"/Users/{user_id}/Items?ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds,MediaSources",
"get",
session,
)
for movie in watched["Items"]: for movie in watched["Items"]:
if movie["UserData"]["Played"] == True: if movie["UserData"]["Played"] == True:
movie_guids = {} movie_guids = {}
movie_guids["title"] = movie["Name"] movie_guids["title"] = movie["Name"]
if "ProviderIds" in movie: if "ProviderIds" in movie:
# Lowercase movie["ProviderIds"] keys # Lowercase movie["ProviderIds"] keys
movie_guids = {k.lower(): v for k, v in movie["ProviderIds"].items()} movie_guids = {
k.lower(): v
for k, v in movie["ProviderIds"].items()
}
if "MediaSources" in movie: if "MediaSources" in movie:
movie_guids["locations"] = tuple([x["Path"].split("/")[-1] for x in movie["MediaSources"]]) movie_guids["locations"] = tuple(
[
x["Path"].split("/")[-1]
for x in movie["MediaSources"]
]
)
user_watched[user_name][library_title].append(movie_guids) user_watched[user_name][library_title].append(movie_guids)
# TV Shows # TV Shows
if library_type == "Series": if library_type == "Series":
user_watched[user_name][library_title] = {} user_watched[user_name][library_title] = {}
watched_shows = await self.query(f"/Users/{user_id}/Items?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount", "get", session) watched_shows = await self.query(
f"/Users/{user_id}/Items?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount",
"get",
session,
)
watched_shows_filtered = [] watched_shows_filtered = []
for show in watched_shows["Items"]: for show in watched_shows["Items"]:
if "PlayedPercentage" in show["UserData"]: if "PlayedPercentage" in show["UserData"]:
@ -102,12 +128,21 @@ class Jellyfin():
watched_shows_filtered.append(show) watched_shows_filtered.append(show)
seasons_tasks = [] seasons_tasks = []
for show in watched_shows_filtered: for show in watched_shows_filtered:
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["locations"] = tuple([show["Path"].split("/")[-1]]) show_guids["locations"] = tuple([show["Path"].split("/")[-1]])
show_guids = frozenset(show_guids.items()) show_guids = frozenset(show_guids.items())
identifiers = {"show_guids": show_guids, "show_id": show["Id"]} identifiers = {"show_guids": show_guids, "show_id": show["Id"]}
task = asyncio.ensure_future(self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount", "get", session, frozenset(identifiers.items()))) task = asyncio.ensure_future(
self.query(
f"/Shows/{show['Id']}/Seasons?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount",
"get",
session,
frozenset(identifiers.items()),
)
)
seasons_tasks.append(task) seasons_tasks.append(task)
seasons_watched = await asyncio.gather(*seasons_tasks) seasons_watched = await asyncio.gather(*seasons_tasks)
@ -115,15 +150,21 @@ class Jellyfin():
for seasons in seasons_watched: for seasons in seasons_watched:
seasons_watched_filtered_dict = {} seasons_watched_filtered_dict = {}
seasons_watched_filtered_dict["Identifiers"] = seasons["Identifiers"] seasons_watched_filtered_dict["Identifiers"] = seasons[
"Identifiers"
]
seasons_watched_filtered_dict["Items"] = [] seasons_watched_filtered_dict["Items"] = []
for season in seasons["Items"]: for season in seasons["Items"]:
if "PlayedPercentage" in season["UserData"]: if "PlayedPercentage" in season["UserData"]:
if season["UserData"]["PlayedPercentage"] > 0: if season["UserData"]["PlayedPercentage"] > 0:
seasons_watched_filtered_dict["Items"].append(season) seasons_watched_filtered_dict["Items"].append(
season
)
if seasons_watched_filtered_dict["Items"]: if seasons_watched_filtered_dict["Items"]:
seasons_watched_filtered.append(seasons_watched_filtered_dict) seasons_watched_filtered.append(
seasons_watched_filtered_dict
)
episodes_tasks = [] episodes_tasks = []
for seasons in seasons_watched_filtered: for seasons in seasons_watched_filtered:
@ -132,7 +173,14 @@ class Jellyfin():
season_identifiers = dict(seasons["Identifiers"]) season_identifiers = dict(seasons["Identifiers"])
season_identifiers["season_id"] = season["Id"] season_identifiers["season_id"] = season["Id"]
season_identifiers["season_name"] = season["Name"] season_identifiers["season_name"] = season["Name"]
task = asyncio.ensure_future(self.query(f"/Shows/{season_identifiers['show_id']}/Episodes?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&isPlayed=true&Fields=ProviderIds,MediaSources", "get", session, frozenset(season_identifiers.items()))) task = asyncio.ensure_future(
self.query(
f"/Shows/{season_identifiers['show_id']}/Episodes?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&isPlayed=true&Fields=ProviderIds,MediaSources",
"get",
session,
frozenset(season_identifiers.items()),
)
)
episodes_tasks.append(task) episodes_tasks.append(task)
watched_episodes = await asyncio.gather(*episodes_tasks) watched_episodes = await asyncio.gather(*episodes_tasks)
@ -140,28 +188,72 @@ class Jellyfin():
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 "ProviderIds" in episode or "MediaSources" in episode: if (
episode_identifiers = dict(episodes["Identifiers"]) "ProviderIds" in episode
or "MediaSources" in episode
):
episode_identifiers = dict(
episodes["Identifiers"]
)
show_guids = episode_identifiers["show_guids"] show_guids = episode_identifiers["show_guids"]
if show_guids not in user_watched[user_name][library_title]: if (
user_watched[user_name][library_title][show_guids] = {} show_guids
if episode_identifiers["season_name"] not in user_watched[user_name][library_title][show_guids]: not in user_watched[user_name][
user_watched[user_name][library_title][show_guids][episode_identifiers["season_name"]] = [] library_title
]
):
user_watched[user_name][library_title][
show_guids
] = {}
if (
episode_identifiers["season_name"]
not in user_watched[user_name][
library_title
][show_guids]
):
user_watched[user_name][library_title][
show_guids
][episode_identifiers["season_name"]] = []
episode_guids = {} episode_guids = {}
if "ProviderIds" in episode: if "ProviderIds" in episode:
episode_guids = {k.lower(): v for k, v in episode["ProviderIds"].items()} episode_guids = {
k.lower(): v
for k, v in episode[
"ProviderIds"
].items()
}
if "MediaSources" in episode: if "MediaSources" in episode:
episode_guids["locations"] = tuple([x["Path"].split("/")[-1] for x in episode["MediaSources"]]) episode_guids["locations"] = tuple(
user_watched[user_name][library_title][show_guids][episode_identifiers["season_name"]].append(episode_guids) [
x["Path"].split("/")[-1]
for x in episode["MediaSources"]
]
)
user_watched[user_name][library_title][
show_guids
][episode_identifiers["season_name"]].append(
episode_guids
)
return user_watched return user_watched
except Exception as e: except Exception as e:
logger(f"Jellyfin: Failed to get watched for {user_name} in library {library_title}, Error: {e}", 2) logger(
f"Jellyfin: Failed to get watched for {user_name} in library {library_title}, Error: {e}",
2,
)
raise Exception(e) raise Exception(e)
async def get_users_watched(
async def get_users_watched(self, user_name, user_id, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping): self,
user_name,
user_id,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
):
try: try:
# Get all libraries # Get all libraries
user_name = user_name.lower() user_name = user_name.lower()
@ -173,11 +265,23 @@ class Jellyfin():
for library in libraries["Items"]: for library in libraries["Items"]:
library_id = library["Id"] library_id = library["Id"]
library_title = library["Name"] library_title = library["Name"]
identifiers = {"library_id": library_id, "library_title": library_title} identifiers = {
task = asyncio.ensure_future(self.query(f"/Users/{user_id}/Items?ParentId={library_id}&Filters=IsPlayed&limit=1", "get", session, identifiers=identifiers)) "library_id": library_id,
"library_title": library_title,
}
task = asyncio.ensure_future(
self.query(
f"/Users/{user_id}/Items?ParentId={library_id}&Filters=IsPlayed&limit=1",
"get",
session,
identifiers=identifiers,
)
)
tasks_libraries.append(task) tasks_libraries.append(task)
libraries = await asyncio.gather(*tasks_libraries, return_exceptions=True) libraries = await asyncio.gather(
*tasks_libraries, return_exceptions=True
)
for watched in libraries: for watched in libraries:
if len(watched["Items"]) == 0: if len(watched["Items"]) == 0:
@ -187,14 +291,29 @@ class Jellyfin():
library_title = watched["Identifiers"]["library_title"] library_title = watched["Identifiers"]["library_title"]
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
# Get watched for user # Get watched for user
task = asyncio.ensure_future(self.get_user_watched(user_name, user_id, library_type, library_id, library_title)) task = asyncio.ensure_future(
self.get_user_watched(
user_name, user_id, library_type, library_id, library_title
)
)
tasks_watched.append(task) tasks_watched.append(task)
watched = await asyncio.gather(*tasks_watched, return_exceptions=True) watched = await asyncio.gather(*tasks_watched, return_exceptions=True)
@ -203,14 +322,31 @@ class Jellyfin():
logger(f"Jellyfin: Failed to get users watched, Error: {e}", 2) logger(f"Jellyfin: Failed to get users watched, Error: {e}", 2)
raise Exception(e) raise Exception(e)
async def get_watched(
async def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None): self,
users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping=None,
):
try: try:
users_watched = {} users_watched = {}
watched = [] watched = []
for user_name, user_id in users.items(): for user_name, user_id in users.items():
watched.append(await self.get_users_watched(user_name, user_id, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)) watched.append(
await self.get_users_watched(
user_name,
user_id,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
)
)
for user_watched in watched: for user_watched in watched:
user_watched_temp = combine_watched_dicts(user_watched) user_watched_temp = combine_watched_dicts(user_watched)
@ -224,29 +360,54 @@ class Jellyfin():
logger(f"Jellyfin: Failed to get watched, Error: {e}", 2) logger(f"Jellyfin: Failed to get watched, Error: {e}", 2)
raise Exception(e) raise Exception(e)
async def update_user_watched(
async def update_user_watched(self, user_name, user_id, library, library_id, videos, dryrun): self, user_name, user_id, library, library_id, videos, dryrun
):
try: try:
logger(f"Jellyfin: Updating watched for {user_name} in library {library}", 1) logger(
videos_shows_ids, videos_episodes_ids, videos_movies_ids = generate_library_guids_dict(videos) f"Jellyfin: Updating watched for {user_name} in library {library}", 1
)
(
videos_shows_ids,
videos_episodes_ids,
videos_movies_ids,
) = generate_library_guids_dict(videos)
logger(f"Jellyfin: mark list\nShows: {videos_shows_ids}\nEpisodes: {videos_episodes_ids}\nMovies: {videos_movies_ids}", 1) logger(
f"Jellyfin: mark list\nShows: {videos_shows_ids}\nEpisodes: {videos_episodes_ids}\nMovies: {videos_movies_ids}",
1,
)
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
if videos_movies_ids: if videos_movies_ids:
jellyfin_search = await self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,MediaSources", "get", session) jellyfin_search = await self.query(
f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,MediaSources",
"get",
session,
)
for jellyfin_video in jellyfin_search["Items"]: for jellyfin_video in jellyfin_search["Items"]:
movie_found = False movie_found = False
if "MediaSources" in jellyfin_video: if "MediaSources" in jellyfin_video:
for movie_location in jellyfin_video["MediaSources"]: for movie_location in jellyfin_video["MediaSources"]:
if movie_location["Path"].split("/")[-1] in videos_movies_ids["locations"]: if (
movie_location["Path"].split("/")[-1]
in videos_movies_ids["locations"]
):
movie_found = True movie_found = True
break break
if not movie_found: if not movie_found:
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()
]
):
movie_found = True movie_found = True
break break
@ -255,51 +416,93 @@ class Jellyfin():
msg = f"{jellyfin_video['Name']} as watched for {user_name} in {library} for Jellyfin" msg = f"{jellyfin_video['Name']} as watched for {user_name} in {library} for Jellyfin"
if not dryrun: if not dryrun:
logger(f"Marking {msg}", 0) logger(f"Marking {msg}", 0)
await self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post", session) await self.query(
f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}",
"post",
session,
)
else: else:
logger(f"Dryrun {msg}", 0) logger(f"Dryrun {msg}", 0)
else: else:
logger(f"Jellyfin: Skipping movie {jellyfin_video['Name']} as it is not in mark list for {user_name}", 1) logger(
f"Jellyfin: Skipping movie {jellyfin_video['Name']} as it is not in mark list for {user_name}",
1,
)
# TV Shows # TV Shows
if videos_shows_ids and videos_episodes_ids: if videos_shows_ids and videos_episodes_ids:
jellyfin_search = await self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,Path", "get", session) jellyfin_search = await self.query(
f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,Path",
"get",
session,
)
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
if "Path" in jellyfin_show: if "Path" in jellyfin_show:
if jellyfin_show["Path"].split("/")[-1] in videos_shows_ids["locations"]: if (
jellyfin_show["Path"].split("/")[-1]
in videos_shows_ids["locations"]
):
show_found = True show_found = True
if not show_found: if not show_found:
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
break break
if show_found: if show_found:
logger(f"Jellyfin: Updating watched for {user_name} in library {library} for show {jellyfin_show['Name']}", 1) logger(
f"Jellyfin: Updating watched for {user_name} in library {library} for show {jellyfin_show['Name']}",
1,
)
jellyfin_show_id = jellyfin_show["Id"] jellyfin_show_id = jellyfin_show["Id"]
jellyfin_episodes = await self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds,MediaSources", "get", session) jellyfin_episodes = await self.query(
f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds,MediaSources",
"get",
session,
)
for jellyfin_episode in jellyfin_episodes["Items"]: for jellyfin_episode in jellyfin_episodes["Items"]:
episode_found = False episode_found = False
if "MediaSources" in jellyfin_episode: if "MediaSources" in jellyfin_episode:
for episode_location in jellyfin_episode["MediaSources"]: for episode_location in jellyfin_episode[
if episode_location["Path"].split("/")[-1] in videos_episodes_ids["locations"]: "MediaSources"
]:
if (
episode_location["Path"].split("/")[-1]
in videos_episodes_ids["locations"]
):
episode_found = True episode_found = True
break break
if not episode_found: if not episode_found:
for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items(): for (
if episode_provider_source.lower() in videos_episodes_ids: episode_provider_source,
if episode_provider_id.lower() in videos_episodes_ids[episode_provider_source.lower()]: episode_provider_id,
) in jellyfin_episode["ProviderIds"].items():
if (
episode_provider_source.lower()
in videos_episodes_ids
):
if (
episode_provider_id.lower()
in videos_episodes_ids[
episode_provider_source.lower()
]
):
episode_found = True episode_found = True
break break
@ -308,23 +511,44 @@ class Jellyfin():
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['Name']} as watched for {user_name} in {library} for Jellyfin" msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['Name']} as watched for {user_name} in {library} for Jellyfin"
if not dryrun: if not dryrun:
logger(f"Marked {msg}", 0) logger(f"Marked {msg}", 0)
await self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post", session) await self.query(
f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}",
"post",
session,
)
else: else:
logger(f"Dryrun {msg}", 0) logger(f"Dryrun {msg}", 0)
else: else:
logger(f"Jellyfin: Skipping episode {jellyfin_episode['Name']} as it is not in mark list for {user_name}", 1) logger(
f"Jellyfin: Skipping episode {jellyfin_episode['Name']} as it is not in mark list for {user_name}",
1,
)
else: else:
logger(f"Jellyfin: Skipping show {jellyfin_show['Name']} as it is not in mark list for {user_name}", 1) logger(
f"Jellyfin: Skipping show {jellyfin_show['Name']} as it is not in mark list for {user_name}",
1,
)
if not videos_movies_ids and not videos_shows_ids and not videos_episodes_ids: if (
logger(f"Jellyfin: No videos to mark as watched for {user_name} in library {library}", 1) not videos_movies_ids
and not videos_shows_ids
and not videos_episodes_ids
):
logger(
f"Jellyfin: No videos to mark as watched for {user_name} in library {library}",
1,
)
except Exception as e: except Exception as e:
logger(f"Jellyfin: Error updating watched for {user_name} in library {library}", 2) logger(
f"Jellyfin: Error updating watched for {user_name} in library {library}",
2,
)
raise Exception(e) raise Exception(e)
async def update_watched(
async def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False): self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
):
try: try:
tasks = [] tasks = []
async with aiohttp.ClientSession() as session: async with aiohttp.ClientSession() as session:
@ -353,7 +577,9 @@ class Jellyfin():
logger(f"{user} {user_other} not found in Jellyfin", 2) logger(f"{user} {user_other} not found in Jellyfin", 2)
continue continue
jellyfin_libraries = await self.query(f"/Users/{user_id}/Views", "get", session) jellyfin_libraries = await self.query(
f"/Users/{user_id}/Views", "get", session
)
jellyfin_libraries = [x for x in jellyfin_libraries["Items"]] jellyfin_libraries = [x for x in jellyfin_libraries["Items"]]
for library, videos in libraries.items(): for library, videos in libraries.items():
@ -364,17 +590,29 @@ class Jellyfin():
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 [
if library.lower() not in [x["Name"].lower() for x in jellyfin_libraries]: x["Name"].lower() for x in jellyfin_libraries
]:
if library_other: if library_other:
if library_other.lower() in [x["Name"].lower() for x in jellyfin_libraries]: if library_other.lower() in [
logger(f"Jellyfin: Library {library} not found, but {library_other} found, using {library_other}", 1) x["Name"].lower() for x in jellyfin_libraries
]:
logger(
f"Jellyfin: Library {library} not found, but {library_other} found, using {library_other}",
1,
)
library = library_other library = library_other
else: else:
logger(f"Jellyfin: Library {library} or {library_other} not found in library list", 2) logger(
f"Jellyfin: Library {library} or {library_other} not found in library list",
2,
)
continue continue
else: else:
logger(f"Jellyfin: Library {library} not found in library list", 2) logger(
f"Jellyfin: Library {library} not found in library list",
2,
)
continue continue
library_id = None library_id = None
@ -384,7 +622,9 @@ class Jellyfin():
continue continue
if library_id: if library_id:
task = self.update_user_watched(user_name, user_id, library, library_id, videos, dryrun) task = self.update_user_watched(
user_name, user_id, library, library_id, videos, dryrun
)
tasks.append(task) tasks.append(task)
await asyncio.gather(*tasks, return_exceptions=True) await asyncio.gather(*tasks, return_exceptions=True)

View File

@ -2,13 +2,21 @@ import copy, os, traceback, json, asyncio
from dotenv import load_dotenv from dotenv import load_dotenv
from time import sleep, perf_counter from time import sleep, perf_counter
from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict from src.functions import (
logger,
str_to_bool,
search_mapping,
generate_library_guids_dict,
)
from src.plex import Plex from src.plex import Plex
from src.jellyfin import Jellyfin from src.jellyfin import Jellyfin
load_dotenv(override=True) load_dotenv(override=True)
def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_mapping=None):
def cleanup_watched(
watched_list_1, watched_list_2, user_mapping=None, library_mapping=None
):
modified_watched_list_1 = copy.deepcopy(watched_list_1) modified_watched_list_1 = copy.deepcopy(watched_list_1)
# remove entries from plex_watched that are in jellyfin_watched # remove entries from plex_watched that are in jellyfin_watched
@ -35,10 +43,17 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
elif library_other in watched_list_2[user_2]: elif library_other in watched_list_2[user_2]:
library_2 = library_other library_2 = library_other
else: else:
logger(f"library {library_1} and {library_other} not found in watched list 2", 1) logger(
f"library {library_1} and {library_other} not found in watched list 2",
1,
)
continue continue
_, episode_watched_list_2_keys_dict, movies_watched_list_2_keys_dict = generate_library_guids_dict(watched_list_2[user_2][library_2]) (
_,
episode_watched_list_2_keys_dict,
movies_watched_list_2_keys_dict,
) = generate_library_guids_dict(watched_list_2[user_2][library_2])
# Movies # Movies
if isinstance(watched_list_1[user_1][library_1], list): if isinstance(watched_list_1[user_1][library_1], list):
@ -46,22 +61,39 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
movie_found = False movie_found = False
for movie_key, movie_value in movie.items(): for movie_key, movie_value in movie.items():
if movie_key == "locations": if movie_key == "locations":
if "locations" in movies_watched_list_2_keys_dict.keys(): if (
"locations"
in movies_watched_list_2_keys_dict.keys()
):
for location in movie_value: for location in movie_value:
if location in movies_watched_list_2_keys_dict["locations"]: if (
location
in movies_watched_list_2_keys_dict[
"locations"
]
):
movie_found = True movie_found = True
break break
else: else:
if movie_key in movies_watched_list_2_keys_dict.keys(): if (
if movie_value in movies_watched_list_2_keys_dict[movie_key]: movie_key
in movies_watched_list_2_keys_dict.keys()
):
if (
movie_value
in movies_watched_list_2_keys_dict[
movie_key
]
):
movie_found = True movie_found = True
if movie_found: if movie_found:
logger(f"Removing {movie} from {library_1}", 3) logger(f"Removing {movie} from {library_1}", 3)
modified_watched_list_1[user_1][library_1].remove(movie) modified_watched_list_1[user_1][library_1].remove(
movie
)
break break
# TV Shows # TV Shows
elif isinstance(watched_list_1[user_1][library_1], dict): 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 # Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1
@ -69,39 +101,99 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
for show_key_1 in watched_list_1[user_1][library_1].keys(): for show_key_1 in watched_list_1[user_1][library_1].keys():
show_key_dict = dict(show_key_1) show_key_dict = dict(show_key_1)
for season in watched_list_1[user_1][library_1][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 in watched_list_1[user_1][library_1][
show_key_1
][season]:
episode_found = False episode_found = False
for episode_key, episode_value in episode.items(): for episode_key, episode_value in episode.items():
# If episode_key and episode_value are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1 # If episode_key and episode_value are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1
if episode_key == "locations": if episode_key == "locations":
if "locations" in episode_watched_list_2_keys_dict.keys(): if (
"locations"
in episode_watched_list_2_keys_dict.keys()
):
for location in episode_value: for location in episode_value:
if location in episode_watched_list_2_keys_dict["locations"]: if (
location
in episode_watched_list_2_keys_dict[
"locations"
]
):
episode_found = True episode_found = True
break break
else: else:
if episode_key in episode_watched_list_2_keys_dict.keys(): if (
if episode_value in episode_watched_list_2_keys_dict[episode_key]: episode_key
in episode_watched_list_2_keys_dict.keys()
):
if (
episode_value
in episode_watched_list_2_keys_dict[
episode_key
]
):
episode_found = True episode_found = True
if episode_found: if episode_found:
if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]: if (
logger(f"Removing {episode} from {show_key_dict['title']}", 3) episode
modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode) in modified_watched_list_1[user_1][
library_1
][show_key_1][season]
):
logger(
f"Removing {episode} from {show_key_dict['title']}",
3,
)
modified_watched_list_1[user_1][
library_1
][show_key_1][season].remove(episode)
break break
# Remove empty seasons # Remove empty seasons
if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0: if (
if season in modified_watched_list_1[user_1][library_1][show_key_1]: len(
logger(f"Removing {season} from {show_key_dict['title']} because it is empty", 3) modified_watched_list_1[user_1][library_1][
del modified_watched_list_1[user_1][library_1][show_key_1][season] show_key_1
][season]
)
== 0
):
if (
season
in modified_watched_list_1[user_1][library_1][
show_key_1
]
):
logger(
f"Removing {season} from {show_key_dict['title']} 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 the show is empty, remove the show
if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0: if (
if show_key_1 in modified_watched_list_1[user_1][library_1]: len(
logger(f"Removing {show_key_dict['title']} from {library_1} because it is empty", 1) modified_watched_list_1[user_1][library_1][
del modified_watched_list_1[user_1][library_1][show_key_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 user_1 in watched_list_1:
for library_1 in watched_list_1[user_1]: for library_1 in watched_list_1[user_1]:
@ -119,7 +211,17 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
return modified_watched_list_1 return modified_watched_list_1
def setup_black_white_lists(blacklist_library: str, whitelist_library: str, blacklist_library_type: str, whitelist_library_type: str, blacklist_users: str, whitelist_users: str, library_mapping=None, user_mapping=None):
def setup_black_white_lists(
blacklist_library: str,
whitelist_library: str,
blacklist_library_type: str,
whitelist_library_type: str,
blacklist_users: str,
whitelist_users: str,
library_mapping=None,
user_mapping=None,
):
if blacklist_library: if blacklist_library:
if len(blacklist_library) > 0: if len(blacklist_library) > 0:
blacklist_library = blacklist_library.split(",") blacklist_library = blacklist_library.split(",")
@ -202,9 +304,19 @@ def setup_black_white_lists(blacklist_library: str, whitelist_library: str, blac
whitelist_users = [] whitelist_users = []
logger(f"Whitelist Users: {whitelist_users}", 1) logger(f"Whitelist Users: {whitelist_users}", 1)
return blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users 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):
def setup_users(
server_1, server_2, blacklist_users, whitelist_users, user_mapping=None
):
# generate list of users from server 1 and server 2 # generate list of users from server 1 and server 2
server_1_type = server_1[0] server_1_type = server_1[0]
@ -226,7 +338,6 @@ def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mappi
elif server_2_type == "jellyfin": elif server_2_type == "jellyfin":
server_2_users = [key.lower() for key in server_2_connection.users.keys()] server_2_users = [key.lower() for key in server_2_connection.users.keys()]
# combined list of overlapping users from plex and jellyfin # combined list of overlapping users from plex and jellyfin
users = {} users = {}
@ -268,36 +379,53 @@ def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mappi
if server_1_type == "plex": if server_1_type == "plex":
output_server_1_users = [] output_server_1_users = []
for plex_user in server_1_connection.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(): 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) output_server_1_users.append(plex_user)
elif server_1_type == "jellyfin": elif server_1_type == "jellyfin":
output_server_1_users = {} output_server_1_users = {}
for jellyfin_user, jellyfin_id in server_1_connection.users.items(): 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(): if (
jellyfin_user.lower() in users_filtered.keys()
or jellyfin_user.lower() in users_filtered.values()
):
output_server_1_users[jellyfin_user] = jellyfin_id output_server_1_users[jellyfin_user] = jellyfin_id
if server_2_type == "plex": if server_2_type == "plex":
output_server_2_users = [] output_server_2_users = []
for plex_user in server_2_connection.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(): 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) output_server_2_users.append(plex_user)
elif server_2_type == "jellyfin": elif server_2_type == "jellyfin":
output_server_2_users = {} output_server_2_users = {}
for jellyfin_user, jellyfin_id in server_2_connection.users.items(): 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(): if (
jellyfin_user.lower() in users_filtered.keys()
or jellyfin_user.lower() in users_filtered.values()
):
output_server_2_users[jellyfin_user] = jellyfin_id output_server_2_users[jellyfin_user] = jellyfin_id
if len(output_server_1_users) == 0: if len(output_server_1_users) == 0:
raise Exception(f"No users found for server 1, users found {users} filtered users {users_filtered}") raise Exception(
f"No users found for server 1, users found {users} filtered users {users_filtered}"
)
if len(output_server_2_users) == 0: if len(output_server_2_users) == 0:
raise Exception(f"No users found for server 2, users found {users} filtered users {users_filtered}") 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 1 users: {output_server_1_users}", 1)
logger(f"Server 2 users: {output_server_2_users}", 1) logger(f"Server 2 users: {output_server_2_users}", 1)
return output_server_1_users, output_server_2_users return output_server_1_users, output_server_2_users
def generate_server_connections(): def generate_server_connections():
servers = [] servers = []
@ -313,21 +441,51 @@ def generate_server_connections():
plex_token = plex_token.split(",") plex_token = plex_token.split(",")
if len(plex_baseurl) != len(plex_token): if len(plex_baseurl) != len(plex_token):
raise Exception("PLEX_BASEURL and PLEX_TOKEN must have the same number of entries") raise Exception(
"PLEX_BASEURL and PLEX_TOKEN must have the same number of entries"
)
for i, url in enumerate(plex_baseurl): 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, ssl_bypass=ssl_bypass))) servers.append(
(
"plex",
Plex(
baseurl=url.strip(),
token=plex_token[i].strip(),
username=None,
password=None,
servername=None,
ssl_bypass=ssl_bypass,
),
)
)
if plex_username and plex_password and plex_servername: if plex_username and plex_password and plex_servername:
plex_username = plex_username.split(",") plex_username = plex_username.split(",")
plex_password = plex_password.split(",") plex_password = plex_password.split(",")
plex_servername = plex_servername.split(",") plex_servername = plex_servername.split(",")
if len(plex_username) != len(plex_password) or len(plex_username) != len(plex_servername): if len(plex_username) != len(plex_password) or len(plex_username) != len(
raise Exception("PLEX_USERNAME, PLEX_PASSWORD and PLEX_SERVERNAME must have the same number of entries") 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): 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(), ssl_bypass=ssl_bypass))) servers.append(
(
"plex",
Plex(
baseurl=None,
token=None,
username=username.strip(),
password=plex_password[i].strip(),
servername=plex_servername[i].strip(),
ssl_bypass=ssl_bypass,
),
)
)
jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None) jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None)
jellyfin_token = os.getenv("JELLYFIN_TOKEN", None) jellyfin_token = os.getenv("JELLYFIN_TOKEN", None)
@ -337,10 +495,17 @@ def generate_server_connections():
jellyfin_token = jellyfin_token.split(",") jellyfin_token = jellyfin_token.split(",")
if len(jellyfin_baseurl) != len(jellyfin_token): if len(jellyfin_baseurl) != len(jellyfin_token):
raise Exception("JELLYFIN_BASEURL and JELLYFIN_TOKEN must have the same number of entries") raise Exception(
"JELLYFIN_BASEURL and JELLYFIN_TOKEN must have the same number of entries"
)
for i, baseurl in enumerate(jellyfin_baseurl): for i, baseurl in enumerate(jellyfin_baseurl):
servers.append(("jellyfin", Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip()))) servers.append(
(
"jellyfin",
Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip()),
)
)
return servers return servers
@ -373,7 +538,23 @@ def main_loop():
blacklist_users = os.getenv("BLACKLIST_USERS", None) blacklist_users = os.getenv("BLACKLIST_USERS", None)
whitelist_users = os.getenv("WHITELIST_USERS", None) whitelist_users = os.getenv("WHITELIST_USERS", None)
blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users = setup_black_white_lists(blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users, library_mapping, user_mapping) (
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
blacklist_users,
whitelist_users,
) = setup_black_white_lists(
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
blacklist_users,
whitelist_users,
library_mapping,
user_mapping,
)
# Create server connections # Create server connections
logger("Creating server connections", 1) logger("Creating server connections", 1)
@ -392,12 +573,30 @@ def main_loop():
# Create users list # Create users list
logger("Creating users list", 1) logger("Creating users list", 1)
server_1_users, server_2_users = setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mapping) server_1_users, server_2_users = setup_users(
server_1, server_2, blacklist_users, whitelist_users, user_mapping
)
logger("Creating watched lists", 1) logger("Creating watched lists", 1)
server_1_watched = server_1_connection.get_watched(server_1_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping) server_1_watched = server_1_connection.get_watched(
server_1_users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
)
logger("Finished creating watched list server 1", 1) logger("Finished creating watched list server 1", 1)
server_2_watched = asyncio.run(server_2_connection.get_watched(server_2_users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping)) server_2_watched = asyncio.run(
server_2_connection.get_watched(
server_2_users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
)
)
logger("Finished creating watched list server 2", 1) logger("Finished creating watched list server 2", 1)
logger(f"Server 1 watched: {server_1_watched}", 3) logger(f"Server 1 watched: {server_1_watched}", 3)
logger(f"Server 2 watched: {server_2_watched}", 3) logger(f"Server 2 watched: {server_2_watched}", 3)
@ -407,21 +606,38 @@ def main_loop():
server_2_watched_filtered = copy.deepcopy(server_2_watched) server_2_watched_filtered = copy.deepcopy(server_2_watched)
logger("Cleaning Server 1 Watched", 1) logger("Cleaning Server 1 Watched", 1)
server_1_watched_filtered = cleanup_watched(server_1_watched, server_2_watched, user_mapping, library_mapping) server_1_watched_filtered = cleanup_watched(
server_1_watched, server_2_watched, user_mapping, library_mapping
)
logger("Cleaning Server 2 Watched", 1) logger("Cleaning Server 2 Watched", 1)
server_2_watched_filtered = cleanup_watched(server_2_watched, server_1_watched, user_mapping, library_mapping) 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(
logger(f"server 2 watched that needs to be synced to server 1:\n{server_2_watched_filtered}", 1) 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,
)
server_1_connection.update_watched(
server_2_watched_filtered, user_mapping, library_mapping, dryrun
)
asyncio.run(
server_2_connection.update_watched(
server_1_watched_filtered, user_mapping, library_mapping, dryrun
)
)
server_1_connection.update_watched(server_2_watched_filtered, user_mapping, library_mapping, dryrun)
asyncio.run(server_2_connection.update_watched(server_1_watched_filtered, user_mapping, library_mapping, dryrun))
def main(): def main():
sleep_duration = float(os.getenv("SLEEP_DURATION", "3600")) sleep_duration = float(os.getenv("SLEEP_DURATION", "3600"))
times = [] times = []
while(True): while True:
try: try:
start = perf_counter() start = perf_counter()
main_loop() main_loop()
@ -438,7 +654,6 @@ def main():
else: else:
logger(error, log_type=2) logger(error, log_type=2)
logger(traceback.format_exc(), 2) logger(traceback.format_exc(), 2)
logger(f"Retrying in {sleep_duration}", log_type=0) logger(f"Retrying in {sleep_duration}", log_type=0)
sleep(sleep_duration) sleep(sleep_duration)

View File

@ -3,12 +3,26 @@ import re, requests
from plexapi.server import PlexServer from plexapi.server import PlexServer
from plexapi.myplex import MyPlexAccount from plexapi.myplex import MyPlexAccount
from src.functions import logger, search_mapping, check_skip_logic, generate_library_guids_dict, future_thread_executor from src.functions import (
logger,
search_mapping,
check_skip_logic,
generate_library_guids_dict,
future_thread_executor,
)
# class plex accept base url and token and username and password but default with none # class plex accept base url and token and username and password but default with none
class Plex: class Plex:
def __init__(self, baseurl=None, token=None, username=None, password=None, servername=None, ssl_bypass=False): def __init__(
self,
baseurl=None,
token=None,
username=None,
password=None,
servername=None,
ssl_bypass=False,
):
self.baseurl = baseurl self.baseurl = baseurl
self.token = token self.token = token
self.username = username self.username = username
@ -44,7 +58,6 @@ class Plex:
logger(f"Plex: Failed to login, Error: {e}", 2) logger(f"Plex: Failed to login, Error: {e}", 2)
raise Exception(e) raise Exception(e)
def get_users(self): def get_users(self):
try: try:
users = self.plex.myPlexAccount().users() users = self.plex.myPlexAccount().users()
@ -57,14 +70,16 @@ class Plex:
logger(f"Plex: Failed to get users, Error: {e}", 2) logger(f"Plex: Failed to get users, Error: {e}", 2)
raise Exception(e) raise Exception(e)
def get_user_watched(self, user, user_plex, library): def get_user_watched(self, user, user_plex, library):
try: try:
user_name = user.title.lower() user_name = user.title.lower()
user_watched = {} user_watched = {}
user_watched[user_name] = {} user_watched[user_name] = {}
logger(f"Plex: Generating watched for {user_name} 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_name][library.title] = [] user_watched[user_name][library.title] = []
@ -73,12 +88,14 @@ class Plex:
for video in library_videos.search(unwatched=False): for video in library_videos.search(unwatched=False):
movie_guids = {} movie_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)
movie_guids[guid_source] = guid_id movie_guids[guid_source] = guid_id
movie_guids["title"] = video.title movie_guids["title"] = video.title
movie_guids["locations"] = tuple([x.split("/")[-1] for x in video.locations]) movie_guids["locations"] = tuple(
[x.split("/")[-1] for x in video.locations]
)
user_watched[user_name][library.title].append(movie_guids) user_watched[user_name][library.title].append(movie_guids)
@ -90,12 +107,16 @@ class Plex:
show_guids = {} show_guids = {}
for show_guid in show.guids: for show_guid in show.guids:
# 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 = (
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1) re.search(r"(.*)://", show_guid.id).group(1).lower()
)
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["title"] = show.title show_guids["title"] = show.title
show_guids["locations"] = tuple([x.split("/")[-1] for x in show.locations]) show_guids["locations"] = tuple(
[x.split("/")[-1] for x in show.locations]
)
show_guids = frozenset(show_guids.items()) show_guids = frozenset(show_guids.items())
for season in show.seasons(): for season in show.seasons():
@ -105,29 +126,51 @@ class Plex:
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 = (
guid_id = re.search(r'://(.*)', guid.id).group(1) re.search(r"(.*)://", guid.id).group(1).lower()
)
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_temp["locations"] = tuple([x.split("/")[-1] for x in episode.locations]) episode_guids_temp["locations"] = tuple(
[x.split("/")[-1] for x in episode.locations]
)
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_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.title not in user_watched[user_name][library.title][show_guids]: if (
user_watched[user_name][library.title][show_guids][season.title] = {} season.title
user_watched[user_name][library.title][show_guids][season.title] = episode_guids not in user_watched[user_name][library.title][
show_guids
]
):
user_watched[user_name][library.title][show_guids][
season.title
] = {}
user_watched[user_name][library.title][show_guids][
season.title
] = episode_guids
return user_watched return user_watched
except Exception as e: except Exception as e:
logger(f"Plex: Failed to get watched for {user_name} in library {library.title}, Error: {e}", 2) logger(
f"Plex: Failed to get watched for {user_name} in library {library.title}, Error: {e}",
2,
)
raise Exception(e) raise Exception(e)
def get_watched(
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping): self,
users,
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
library_mapping,
):
try: try:
# Get all libraries # Get all libraries
users_watched = {} users_watched = {}
@ -137,7 +180,9 @@ class Plex:
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()
@ -145,10 +190,20 @@ class Plex:
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])
@ -164,30 +219,46 @@ class Plex:
logger(f"Plex: Failed to get watched, Error: {e}", 2) logger(f"Plex: Failed to get watched, Error: {e}", 2)
raise Exception(e) raise Exception(e)
def update_user_watched(self, user, user_plex, library, videos, dryrun): def update_user_watched(self, user, user_plex, library, videos, dryrun):
try: try:
logger(f"Plex: Updating watched for {user.title} in library {library}", 1) logger(f"Plex: Updating watched for {user.title} in library {library}", 1)
videos_shows_ids, videos_episodes_ids, videos_movies_ids = generate_library_guids_dict(videos) (
logger(f"Plex: mark list\nShows: {videos_shows_ids}\nEpisodes: {videos_episodes_ids}\nMovies: {videos_movies_ids}", 1) videos_shows_ids,
videos_episodes_ids,
videos_movies_ids,
) = generate_library_guids_dict(videos)
logger(
f"Plex: mark list\nShows: {videos_shows_ids}\nEpisodes: {videos_episodes_ids}\nMovies: {videos_movies_ids}",
1,
)
library_videos = user_plex.library.section(library) library_videos = user_plex.library.section(library)
if videos_movies_ids: if videos_movies_ids:
for movies_search in library_videos.search(unwatched=True): for movies_search in library_videos.search(unwatched=True):
movie_found = False movie_found = False
for movie_location in movies_search.locations: for movie_location in movies_search.locations:
if movie_location.split("/")[-1] in videos_movies_ids["locations"]: if (
movie_location.split("/")[-1]
in videos_movies_ids["locations"]
):
movie_found = True movie_found = True
break break
if not movie_found: if not movie_found:
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 = (
movie_guid_id = re.search(r'://(.*)', movie_guid.id).group(1) re.search(r"(.*)://", movie_guid.id).group(1).lower()
)
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]
):
movie_found = True movie_found = True
break break
@ -199,21 +270,28 @@ class Plex:
else: else:
logger(f"Dryrun {msg}", 0) logger(f"Dryrun {msg}", 0)
else: else:
logger(f"Plex: Skipping movie {movies_search.title} as it is not in mark list for {user.title}", 1) logger(
f"Plex: Skipping movie {movies_search.title} as it is not in mark list for {user.title}",
1,
)
if videos_shows_ids and videos_episodes_ids: if videos_shows_ids and videos_episodes_ids:
for show_search in library_videos.search(unwatched=True): for show_search in library_videos.search(unwatched=True):
show_found = False show_found = False
for show_location in show_search.locations: for show_location in show_search.locations:
if show_location.split("/")[-1] in videos_shows_ids["locations"]: if (
show_location.split("/")[-1]
in videos_shows_ids["locations"]
):
show_found = True show_found = True
break break
if not show_found: if not show_found:
for show_guid in show_search.guids: for show_guid in show_search.guids:
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower() show_guid_source = (
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1) 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 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_source in videos_shows_ids.keys():
@ -226,18 +304,33 @@ class Plex:
episode_found = False episode_found = False
for episode_location in episode_search.locations: for episode_location in episode_search.locations:
if episode_location.split("/")[-1] in videos_episodes_ids["locations"]: if (
episode_location.split("/")[-1]
in videos_episodes_ids["locations"]
):
episode_found = True episode_found = True
break break
if not episode_found: if not episode_found:
for episode_guid in episode_search.guids: for episode_guid in episode_search.guids:
episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower() episode_guid_source = (
episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1) 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_episodes_ids exactly, then the episode is in the list # If episode provider source and episode provider id are in videos_episodes_ids exactly, then the episode is in the list
if episode_guid_source in videos_episodes_ids.keys(): if (
if episode_guid_id in videos_episodes_ids[episode_guid_source]: episode_guid_source
in videos_episodes_ids.keys()
):
if (
episode_guid_id
in videos_episodes_ids[episode_guid_source]
):
episode_found = True episode_found = True
break break
@ -249,19 +342,36 @@ class Plex:
else: else:
logger(f"Dryrun {msg}", 0) logger(f"Dryrun {msg}", 0)
else: else:
logger(f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}", 1) logger(
f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}",
1,
)
else: else:
logger(f"Plex: Skipping show {show_search.title} as it is not in mark list for {user.title}", 1) logger(
f"Plex: Skipping show {show_search.title} as it is not in mark list for {user.title}",
1,
)
if not videos_movies_ids and not videos_shows_ids and not videos_episodes_ids: if (
logger(f"Jellyfin: No videos to mark as watched for {user.title} in library {library}", 1) not videos_movies_ids
and not videos_shows_ids
and not videos_episodes_ids
):
logger(
f"Jellyfin: No videos to mark as watched for {user.title} in library {library}",
1,
)
except Exception as e: except Exception as e:
logger(f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}", 2) logger(
f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}",
2,
)
raise Exception(e) raise Exception(e)
def update_watched(
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False): self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
):
try: try:
args = [] args = []
@ -285,7 +395,9 @@ class Plex:
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
@ -299,18 +411,36 @@ class Plex:
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: if library_other:
if library_other.lower() in [x.title.lower() for x in library_list]: if library_other.lower() in [
logger(f"Plex: Library {library} not found, but {library_other} found, using {library_other}", 1) x.title.lower() for x in library_list
]:
logger(
f"Plex: Library {library} not found, but {library_other} found, using {library_other}",
1,
)
library = library_other library = library_other
else: else:
logger(f"Plex: Library {library} or {library_other} not found in library list", 2) logger(
f"Plex: Library {library} or {library_other} not found in library list",
2,
)
continue continue
else: else:
logger(f"Plex: Library {library} not found in library list", 2) logger(
f"Plex: Library {library} not found in library list", 2
)
continue continue
args.append(
args.append([self.update_user_watched, user, user_plex, library, videos, dryrun]) [
self.update_user_watched,
user,
user_plex,
library,
videos,
dryrun,
]
)
future_thread_executor(args) future_thread_executor(args)
except Exception as e: except Exception as e:

View File

@ -15,33 +15,64 @@ sys.path.append(parent)
from src.main import setup_black_white_lists from src.main import setup_black_white_lists
def test_setup_black_white_lists(): def test_setup_black_white_lists():
# Simple # Simple
blacklist_library = 'library1, library2' blacklist_library = "library1, library2"
whitelist_library = 'library1, library2' whitelist_library = "library1, library2"
blacklist_library_type = 'library_type1, library_type2' blacklist_library_type = "library_type1, library_type2"
whitelist_library_type = 'library_type1, library_type2' whitelist_library_type = "library_type1, library_type2"
blacklist_users = 'user1, user2' blacklist_users = "user1, user2"
whitelist_users = 'user1, user2' whitelist_users = "user1, user2"
results_blacklist_library, return_whitelist_library, return_blacklist_library_type, return_whitelist_library_type, return_blacklist_users, return_whitelist_users = setup_black_white_lists(blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users) (
results_blacklist_library,
return_whitelist_library,
return_blacklist_library_type,
return_whitelist_library_type,
return_blacklist_users,
return_whitelist_users,
) = setup_black_white_lists(
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
blacklist_users,
whitelist_users,
)
assert results_blacklist_library == ['library1', 'library2'] assert results_blacklist_library == ["library1", "library2"]
assert return_whitelist_library == ['library1', 'library2'] assert return_whitelist_library == ["library1", "library2"]
assert return_blacklist_library_type == ['library_type1', 'library_type2'] assert return_blacklist_library_type == ["library_type1", "library_type2"]
assert return_whitelist_library_type == ['library_type1', 'library_type2'] assert return_whitelist_library_type == ["library_type1", "library_type2"]
assert return_blacklist_users == ['user1', 'user2'] assert return_blacklist_users == ["user1", "user2"]
assert return_whitelist_users == ['user1', 'user2'] assert return_whitelist_users == ["user1", "user2"]
# Library Mapping and user mapping # Library Mapping and user mapping
library_mapping = {"library1": "library3"} library_mapping = {"library1": "library3"}
user_mapping = {"user1": "user3"} user_mapping = {"user1": "user3"}
results_blacklist_library, return_whitelist_library, return_blacklist_library_type, return_whitelist_library_type, return_blacklist_users, return_whitelist_users = setup_black_white_lists(blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users, library_mapping, user_mapping) (
results_blacklist_library,
return_whitelist_library,
return_blacklist_library_type,
return_whitelist_library_type,
return_blacklist_users,
return_whitelist_users,
) = setup_black_white_lists(
blacklist_library,
whitelist_library,
blacklist_library_type,
whitelist_library_type,
blacklist_users,
whitelist_users,
library_mapping,
user_mapping,
)
assert results_blacklist_library == ['library1', 'library2', 'library3'] assert results_blacklist_library == ["library1", "library2", "library3"]
assert return_whitelist_library == ['library1', 'library2', 'library3'] assert return_whitelist_library == ["library1", "library2", "library3"]
assert return_blacklist_library_type == ['library_type1', 'library_type2'] assert return_blacklist_library_type == ["library_type1", "library_type2"]
assert return_whitelist_library_type == ['library_type1', 'library_type2'] assert return_whitelist_library_type == ["library_type1", "library_type2"]
assert return_blacklist_users == ['user1', 'user2', 'user3'] assert return_blacklist_users == ["user1", "user2", "user3"]
assert return_whitelist_users == ['user1', 'user2', 'user3'] assert return_whitelist_users == ["user1", "user2", "user3"]

View File

@ -16,86 +16,201 @@ sys.path.append(parent)
from src.main import cleanup_watched from src.main import cleanup_watched
tv_shows_watched_list_1 = { tv_shows_watched_list_1 = {
frozenset({("tvdb", "75710"), ("title", "Criminal Minds"), ("imdb", "tt0452046"), ("locations", ("Criminal Minds",)), ("tmdb", "4057")}): { frozenset(
{
("tvdb", "75710"),
("title", "Criminal Minds"),
("imdb", "tt0452046"),
("locations", ("Criminal Minds",)),
("tmdb", "4057"),
}
): {
"Season 1": [ "Season 1": [
{'imdb': 'tt0550489', 'tmdb': '282843', 'tvdb': '176357', 'locations': ('Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv',)}, {
{'imdb': 'tt0550487', 'tmdb': '282861', 'tvdb': '300385', 'locations': ('Criminal Minds S01E02 Compulsion WEBDL-720p.mkv',)} "imdb": "tt0550489",
"tmdb": "282843",
"tvdb": "176357",
"locations": (
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
),
},
{
"imdb": "tt0550487",
"tmdb": "282861",
"tvdb": "300385",
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
},
] ]
}, },
frozenset({("title", "Test"), ("locations", ("Test",))}): { frozenset({("title", "Test"), ("locations", ("Test",))}): {
"Season 1": [ "Season 1": [
{'locations': ('Test S01E01.mkv',)}, {"locations": ("Test S01E01.mkv",)},
{'locations': ('Test S01E02.mkv',)} {"locations": ("Test S01E02.mkv",)},
] ]
} },
} }
movies_watched_list_1 = [ movies_watched_list_1 = [
{"imdb":"tt2380307", "tmdb":"354912", 'title': 'Coco', 'locations': ('Coco (2017) Remux-1080p.mkv',)}, {
{"tmdbcollection":"448150", "imdb":"tt1431045", "tmdb":"293660", 'title': 'Deadpool', 'locations': ('Deadpool (2016) Remux-1080p.mkv',)}, "imdb": "tt2380307",
"tmdb": "354912",
"title": "Coco",
"locations": ("Coco (2017) Remux-1080p.mkv",),
},
{
"tmdbcollection": "448150",
"imdb": "tt1431045",
"tmdb": "293660",
"title": "Deadpool",
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
},
] ]
tv_shows_watched_list_2 = { tv_shows_watched_list_2 = {
frozenset({("tvdb", "75710"), ("title", "Criminal Minds"), ("imdb", "tt0452046"), ("locations", ("Criminal Minds",)), ("tmdb", "4057")}): { frozenset(
{
("tvdb", "75710"),
("title", "Criminal Minds"),
("imdb", "tt0452046"),
("locations", ("Criminal Minds",)),
("tmdb", "4057"),
}
): {
"Season 1": [ "Season 1": [
{'imdb': 'tt0550487', 'tmdb': '282861', 'tvdb': '300385', 'locations': ('Criminal Minds S01E02 Compulsion WEBDL-720p.mkv',)}, {
{'imdb': 'tt0550498', 'tmdb': '282865', 'tvdb': '300474', 'locations': ("Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",)} "imdb": "tt0550487",
"tmdb": "282861",
"tvdb": "300385",
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
},
{
"imdb": "tt0550498",
"tmdb": "282865",
"tvdb": "300474",
"locations": (
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
),
},
] ]
}, },
frozenset({("title", "Test"), ("locations", ("Test",))}): { frozenset({("title", "Test"), ("locations", ("Test",))}): {
"Season 1": [ "Season 1": [
{'locations': ('Test S01E02.mkv',)}, {"locations": ("Test S01E02.mkv",)},
{'locations': ('Test S01E03.mkv',)} {"locations": ("Test S01E03.mkv",)},
] ]
} },
} }
movies_watched_list_2 = [ movies_watched_list_2 = [
{"imdb":"tt2380307", "tmdb":"354912", 'title': 'Coco', 'locations': ('Coco (2017) Remux-1080p.mkv',)}, {
{'imdb': 'tt0384793', 'tmdb': '9788', 'tvdb': '9103', 'title': 'Accepted', 'locations': ('Accepted (2006) Remux-1080p.mkv',)} "imdb": "tt2380307",
"tmdb": "354912",
"title": "Coco",
"locations": ("Coco (2017) Remux-1080p.mkv",),
},
{
"imdb": "tt0384793",
"tmdb": "9788",
"tvdb": "9103",
"title": "Accepted",
"locations": ("Accepted (2006) Remux-1080p.mkv",),
},
] ]
# Test to see if objects get deleted all the way up to the root. # Test to see if objects get deleted all the way up to the root.
tv_shows_2_watched_list_1 = { tv_shows_2_watched_list_1 = {
frozenset({("tvdb", "75710"), ("title", "Criminal Minds"), ("imdb", "tt0452046"), ("locations", ("Criminal Minds",)), ("tmdb", "4057")}): { frozenset(
{
("tvdb", "75710"),
("title", "Criminal Minds"),
("imdb", "tt0452046"),
("locations", ("Criminal Minds",)),
("tmdb", "4057"),
}
): {
"Season 1": [ "Season 1": [
{'imdb': 'tt0550489', 'tmdb': '282843', 'tvdb': '176357', 'locations': ('Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv',)}, {
"imdb": "tt0550489",
"tmdb": "282843",
"tvdb": "176357",
"locations": (
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
),
},
] ]
} }
} }
expected_tv_show_watched_list_1 = { expected_tv_show_watched_list_1 = {
frozenset({("tvdb", "75710"), ("title", "Criminal Minds"), ("imdb", "tt0452046"), ("locations", ("Criminal Minds",)), ("tmdb", "4057")}): { frozenset(
{
("tvdb", "75710"),
("title", "Criminal Minds"),
("imdb", "tt0452046"),
("locations", ("Criminal Minds",)),
("tmdb", "4057"),
}
): {
"Season 1": [ "Season 1": [
{'imdb': 'tt0550489', 'tmdb': '282843', 'tvdb': '176357', 'locations': ('Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv',)} {
"imdb": "tt0550489",
"tmdb": "282843",
"tvdb": "176357",
"locations": (
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
),
}
] ]
}, },
frozenset({("title", "Test"), ("locations", ("Test",))}): { frozenset({("title", "Test"), ("locations", ("Test",))}): {
"Season 1": [ "Season 1": [{"locations": ("Test S01E01.mkv",)}]
{'locations': ('Test S01E01.mkv',)} },
]
}
} }
expected_movie_watched_list_1 = [ expected_movie_watched_list_1 = [
{"tmdbcollection":"448150", "imdb":"tt1431045", "tmdb":"293660", 'title': 'Deadpool', 'locations': ('Deadpool (2016) Remux-1080p.mkv',)} {
"tmdbcollection": "448150",
"imdb": "tt1431045",
"tmdb": "293660",
"title": "Deadpool",
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
}
] ]
expected_tv_show_watched_list_2 = { expected_tv_show_watched_list_2 = {
frozenset({("tvdb", "75710"), ("title", "Criminal Minds"), ("imdb", "tt0452046"), ("locations", ("Criminal Minds",)), ("tmdb", "4057")}): { frozenset(
{
("tvdb", "75710"),
("title", "Criminal Minds"),
("imdb", "tt0452046"),
("locations", ("Criminal Minds",)),
("tmdb", "4057"),
}
): {
"Season 1": [ "Season 1": [
{'imdb': 'tt0550498', 'tmdb': '282865', 'tvdb': '300474', 'locations': ("Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",)} {
"imdb": "tt0550498",
"tmdb": "282865",
"tvdb": "300474",
"locations": (
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
),
}
] ]
}, },
frozenset({("title", "Test"), ("locations", ("Test",))}): { frozenset({("title", "Test"), ("locations", ("Test",))}): {
"Season 1": [ "Season 1": [{"locations": ("Test S01E03.mkv",)}]
{'locations': ('Test S01E03.mkv',)} },
]
}
} }
expected_movie_watched_list_2 = [ expected_movie_watched_list_2 = [
{'imdb': 'tt0384793', 'tmdb': '9788', 'tvdb': '9103', 'title': 'Accepted', 'locations': ('Accepted (2006) Remux-1080p.mkv',)} {
"imdb": "tt0384793",
"tmdb": "9788",
"tvdb": "9103",
"title": "Accepted",
"locations": ("Accepted (2006) Remux-1080p.mkv",),
}
] ]
@ -104,28 +219,28 @@ def test_simple_cleanup_watched():
"user1": { "user1": {
"TV Shows": tv_shows_watched_list_1, "TV Shows": tv_shows_watched_list_1,
"Movies": movies_watched_list_1, "Movies": movies_watched_list_1,
"Other Shows": tv_shows_2_watched_list_1 "Other Shows": tv_shows_2_watched_list_1,
}, },
} }
user_watched_list_2 = { user_watched_list_2 = {
"user1": { "user1": {
"TV Shows": tv_shows_watched_list_2, "TV Shows": tv_shows_watched_list_2,
"Movies": movies_watched_list_2, "Movies": movies_watched_list_2,
"Other Shows": tv_shows_2_watched_list_1 "Other Shows": tv_shows_2_watched_list_1,
} }
} }
expected_watched_list_1 = { expected_watched_list_1 = {
"user1": { "user1": {
"TV Shows": expected_tv_show_watched_list_1 "TV Shows": expected_tv_show_watched_list_1,
, "Movies": expected_movie_watched_list_1 "Movies": expected_movie_watched_list_1,
} }
} }
expected_watched_list_2 = { expected_watched_list_2 = {
"user1": { "user1": {
"TV Shows": expected_tv_show_watched_list_2 "TV Shows": expected_tv_show_watched_list_2,
, "Movies": expected_movie_watched_list_2 "Movies": expected_movie_watched_list_2,
} }
} }
@ -141,36 +256,46 @@ def test_mapping_cleanup_watched():
"user1": { "user1": {
"TV Shows": tv_shows_watched_list_1, "TV Shows": tv_shows_watched_list_1,
"Movies": movies_watched_list_1, "Movies": movies_watched_list_1,
"Other Shows": tv_shows_2_watched_list_1 "Other Shows": tv_shows_2_watched_list_1,
}, },
} }
user_watched_list_2 = { user_watched_list_2 = {
"user2": { "user2": {
"Shows": tv_shows_watched_list_2, "Shows": tv_shows_watched_list_2,
"Movies": movies_watched_list_2, "Movies": movies_watched_list_2,
"Other Shows": tv_shows_2_watched_list_1 "Other Shows": tv_shows_2_watched_list_1,
} }
} }
expected_watched_list_1 = { expected_watched_list_1 = {
"user1": { "user1": {
"TV Shows": expected_tv_show_watched_list_1 "TV Shows": expected_tv_show_watched_list_1,
, "Movies": expected_movie_watched_list_1 "Movies": expected_movie_watched_list_1,
} }
} }
expected_watched_list_2 = { expected_watched_list_2 = {
"user2": { "user2": {
"Shows": expected_tv_show_watched_list_2 "Shows": expected_tv_show_watched_list_2,
, "Movies": expected_movie_watched_list_2 "Movies": expected_movie_watched_list_2,
} }
} }
user_mapping = {"user1": "user2"} user_mapping = {"user1": "user2"}
library_mapping = {"TV Shows": "Shows"} library_mapping = {"TV Shows": "Shows"}
return_watched_list_1 = cleanup_watched(user_watched_list_1, user_watched_list_2, user_mapping=user_mapping, library_mapping=library_mapping) return_watched_list_1 = cleanup_watched(
return_watched_list_2 = cleanup_watched(user_watched_list_2, user_watched_list_1, user_mapping=user_mapping, library_mapping=library_mapping) user_watched_list_1,
user_watched_list_2,
user_mapping=user_mapping,
library_mapping=library_mapping,
)
return_watched_list_2 = cleanup_watched(
user_watched_list_2,
user_watched_list_1,
user_mapping=user_mapping,
library_mapping=library_mapping,
)
assert return_watched_list_1 == expected_watched_list_1 assert return_watched_list_1 == expected_watched_list_1
assert return_watched_list_2 == expected_watched_list_2 assert return_watched_list_2 == expected_watched_list_2