Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9afc00443c | ||
|
|
3ec177ea64 | ||
|
|
b360c9fd0b | ||
|
|
1ed791b1ed | ||
|
|
f19b1a3063 | ||
|
|
190a72bd3c | ||
|
|
c848106ce7 | ||
|
|
dd319271bd | ||
|
|
16879cc728 | ||
|
|
942ec3533f | ||
|
|
9f6edfc91a | ||
|
|
827ace2e97 | ||
|
|
f6b57a1b4d | ||
|
|
88a7526721 | ||
|
|
1efb4d8543 |
@@ -56,7 +56,7 @@ Keep in sync all your users watched history between jellyfin and plex servers lo
|
||||
|
||||
#### With .env
|
||||
|
||||
- Create a .env file similar to .env.sample and set the MNEMONIC variable to your seed phrase
|
||||
- Create a .env file similar to .env.sample and set the variables to match your setup
|
||||
|
||||
- Run
|
||||
|
||||
|
||||
3
main.py
3
main.py
@@ -1,10 +1,11 @@
|
||||
import sys
|
||||
|
||||
if __name__ == '__main__':
|
||||
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.main import main
|
||||
|
||||
main()
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
plexapi
|
||||
requests
|
||||
python-dotenv
|
||||
aiohttp
|
||||
|
||||
@@ -4,7 +4,8 @@ from dotenv import load_dotenv
|
||||
|
||||
load_dotenv(override=True)
|
||||
|
||||
logfile = os.getenv("LOGFILE","log.log")
|
||||
logfile = os.getenv("LOGFILE", "log.log")
|
||||
|
||||
|
||||
def logger(message: str, log_type=0):
|
||||
debug = str_to_bool(os.getenv("DEBUG", "True"))
|
||||
@@ -13,12 +14,14 @@ def logger(message: str, log_type=0):
|
||||
output = str(message)
|
||||
if log_type == 0:
|
||||
pass
|
||||
elif log_type == 1 and (debug or debug_level == "info"):
|
||||
elif log_type == 1 and (debug and debug_level == "info"):
|
||||
output = f"[INFO]: {output}"
|
||||
elif log_type == 2:
|
||||
output = f"[ERROR]: {output}"
|
||||
elif log_type == 3 and (debug and debug_level == "debug"):
|
||||
output = f"[DEBUG]: {output}"
|
||||
elif log_type == 4:
|
||||
output = f"[WARNING]: {output}"
|
||||
else:
|
||||
output = None
|
||||
|
||||
@@ -27,6 +30,7 @@ def logger(message: str, log_type=0):
|
||||
file = open(logfile, "a", encoding="utf-8")
|
||||
file.write(output + "\n")
|
||||
|
||||
|
||||
# Reimplementation of distutils.util.strtobool due to it being deprecated
|
||||
# Source: https://github.com/PostHog/posthog/blob/01e184c29d2c10c43166f1d40a334abbc3f99d8a/posthog/utils.py#L668
|
||||
def str_to_bool(value: any) -> bool:
|
||||
@@ -34,6 +38,7 @@ def str_to_bool(value: any) -> bool:
|
||||
return False
|
||||
return str(value).lower() in ("y", "yes", "t", "true", "on", "1")
|
||||
|
||||
|
||||
# Get mapped value
|
||||
def search_mapping(dictionary: dict, key_value: str):
|
||||
if key_value in dictionary.keys():
|
||||
@@ -43,12 +48,22 @@ def search_mapping(dictionary: dict, key_value: str):
|
||||
elif key_value in dictionary.values():
|
||||
return list(dictionary.keys())[list(dictionary.values()).index(key_value)]
|
||||
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:
|
||||
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
|
||||
|
||||
if library_type.lower() in blacklist_library_type:
|
||||
@@ -87,7 +102,7 @@ def generate_library_guids_dict(user_list: dict):
|
||||
|
||||
try:
|
||||
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 provider_key, provider_value in show_key.items():
|
||||
# Skip title
|
||||
@@ -99,9 +114,11 @@ def generate_library_guids_dict(user_list: dict):
|
||||
for show_location in provider_value:
|
||||
show_output_dict[provider_key.lower()].append(show_location)
|
||||
else:
|
||||
show_output_dict[provider_key.lower()].append(provider_value.lower())
|
||||
except:
|
||||
pass
|
||||
show_output_dict[provider_key.lower()].append(
|
||||
provider_value.lower()
|
||||
)
|
||||
except Exception:
|
||||
logger("Generating show_output_dict failed, skipping", 1)
|
||||
|
||||
try:
|
||||
for show in user_list:
|
||||
@@ -112,11 +129,15 @@ def generate_library_guids_dict(user_list: dict):
|
||||
episode_output_dict[episode_key.lower()] = []
|
||||
if episode_key == "locations":
|
||||
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:
|
||||
episode_output_dict[episode_key.lower()].append(episode_value.lower())
|
||||
except:
|
||||
pass
|
||||
episode_output_dict[episode_key.lower()].append(
|
||||
episode_value.lower()
|
||||
)
|
||||
except Exception:
|
||||
logger("Generating episode_output_dict failed, skipping", 1)
|
||||
|
||||
try:
|
||||
for movie in user_list:
|
||||
@@ -128,17 +149,30 @@ def generate_library_guids_dict(user_list: dict):
|
||||
movies_output_dict[movie_key.lower()].append(movie_location)
|
||||
else:
|
||||
movies_output_dict[movie_key.lower()].append(movie_value.lower())
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
logger("Generating movies_output_dict failed, skipping", 1)
|
||||
|
||||
return show_output_dict, episode_output_dict, movies_output_dict
|
||||
|
||||
|
||||
def combine_watched_dicts(dicts: list):
|
||||
combined_dict = {}
|
||||
for single_dict in dicts:
|
||||
for key, value in single_dict.items():
|
||||
if key not in combined_dict:
|
||||
combined_dict[key] = {}
|
||||
for subkey, subvalue in value.items():
|
||||
combined_dict[key][subkey] = subvalue
|
||||
|
||||
return combined_dict
|
||||
|
||||
|
||||
def future_thread_executor(args: list, workers: int = -1):
|
||||
futures_list = []
|
||||
results = []
|
||||
|
||||
if workers == -1:
|
||||
workers = min(32, os.cpu_count()*1.25)
|
||||
workers = min(32, os.cpu_count() * 1.25)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=workers) as executor:
|
||||
for arg in args:
|
||||
|
||||
753
src/jellyfin.py
753
src/jellyfin.py
@@ -1,11 +1,17 @@
|
||||
import requests
|
||||
from src.functions import logger, search_mapping, str_to_bool, check_skip_logic, generate_library_guids_dict, future_thread_executor
|
||||
import asyncio, aiohttp
|
||||
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):
|
||||
self.baseurl = baseurl
|
||||
self.token = token
|
||||
self.session = requests.Session()
|
||||
|
||||
if not self.baseurl:
|
||||
raise Exception("Jellyfin baseurl not set")
|
||||
@@ -13,43 +19,49 @@ class Jellyfin():
|
||||
if not self.token:
|
||||
raise Exception("Jellyfin token not set")
|
||||
|
||||
self.users = self.get_users()
|
||||
self.users = asyncio.run(self.get_users())
|
||||
|
||||
|
||||
def query(self, query, query_type):
|
||||
async def query(self, query, query_type, session, identifiers=None):
|
||||
try:
|
||||
response = None
|
||||
results = None
|
||||
headers = {"Accept": "application/json", "X-Emby-Token": self.token}
|
||||
authorization = (
|
||||
"MediaBrowser , "
|
||||
'Client="other", '
|
||||
'Device="script", '
|
||||
'DeviceId="script", '
|
||||
'Version="0.0.0"'
|
||||
)
|
||||
headers["X-Emby-Authorization"] = authorization
|
||||
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"X-Emby-Token": self.token
|
||||
}
|
||||
if query_type == "get":
|
||||
response = self.session.get(self.baseurl + query, headers=headers)
|
||||
async with session.get(
|
||||
self.baseurl + query, headers=headers
|
||||
) as response:
|
||||
results = await response.json()
|
||||
|
||||
elif query_type == "post":
|
||||
authorization = (
|
||||
'MediaBrowser , '
|
||||
'Client="other", '
|
||||
'Device="script", '
|
||||
'DeviceId="script", '
|
||||
'Version="0.0.0"'
|
||||
)
|
||||
headers["X-Emby-Authorization"] = authorization
|
||||
response = self.session.post(self.baseurl + query, headers=headers)
|
||||
async with session.post(
|
||||
self.baseurl + query, headers=headers
|
||||
) as response:
|
||||
results = await response.json()
|
||||
|
||||
return response.json()
|
||||
# append identifiers to results
|
||||
if identifiers:
|
||||
results["Identifiers"] = identifiers
|
||||
return results
|
||||
|
||||
except Exception as e:
|
||||
logger(f"Jellyfin: Query failed {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
def get_users(self):
|
||||
async def get_users(self):
|
||||
try:
|
||||
users = {}
|
||||
|
||||
query = "/Users"
|
||||
response = self.query(query, "get")
|
||||
query_string = "/Users"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
response = await self.query(query_string, "get", session)
|
||||
|
||||
# If reponse is not empty
|
||||
if response:
|
||||
@@ -61,98 +73,289 @@ class Jellyfin():
|
||||
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):
|
||||
async def get_user_watched(
|
||||
self, user_name, user_id, library_type, library_id, library_title
|
||||
):
|
||||
try:
|
||||
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
|
||||
if library_type == "Movie":
|
||||
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,MediaSources", "get")
|
||||
for movie in watched["Items"]:
|
||||
if movie["UserData"]["Played"] == True:
|
||||
movie_guids = {}
|
||||
movie_guids["title"] = movie["Name"]
|
||||
if movie["ProviderIds"]:
|
||||
# Lowercase movie["ProviderIds"] keys
|
||||
movie_guids = {k.lower(): v for k, v in movie["ProviderIds"].items()}
|
||||
if movie["MediaSources"]:
|
||||
movie_guids["locations"] = tuple([x["Path"].split("/")[-1] for x in movie["MediaSources"]])
|
||||
user_watched[user_name][library_title].append(movie_guids)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
if library_type == "Movie":
|
||||
user_watched[user_name][library_title] = []
|
||||
watched = await self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?ParentId={library_id}&Filters=IsPlayed&Fields=ItemCounts,ProviderIds,MediaSources",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
for movie in watched["Items"]:
|
||||
if movie["UserData"]["Played"] is True:
|
||||
movie_guids = {}
|
||||
movie_guids["title"] = movie["Name"]
|
||||
if "ProviderIds" in movie:
|
||||
# Lowercase movie["ProviderIds"] keys
|
||||
movie_guids = {
|
||||
k.lower(): v
|
||||
for k, v in movie["ProviderIds"].items()
|
||||
}
|
||||
if "MediaSources" in movie:
|
||||
movie_guids["locations"] = tuple(
|
||||
[
|
||||
x["Path"].split("/")[-1]
|
||||
for x in movie["MediaSources"]
|
||||
]
|
||||
)
|
||||
user_watched[user_name][library_title].append(movie_guids)
|
||||
|
||||
# TV Shows
|
||||
if library_type == "Episode":
|
||||
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,Path", "get")
|
||||
watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"]
|
||||
# TV Shows
|
||||
if library_type == "Series":
|
||||
user_watched[user_name][library_title] = {}
|
||||
watched_shows = await self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
watched_shows_filtered = []
|
||||
for show in watched_shows["Items"]:
|
||||
if "PlayedPercentage" in show["UserData"]:
|
||||
if show["UserData"]["PlayedPercentage"] > 0:
|
||||
watched_shows_filtered.append(show)
|
||||
seasons_tasks = []
|
||||
for show in watched_shows_filtered:
|
||||
show_guids = {
|
||||
k.lower(): v for k, v in show["ProviderIds"].items()
|
||||
}
|
||||
show_guids["title"] = show["Name"]
|
||||
show_guids["locations"] = tuple([show["Path"].split("/")[-1]])
|
||||
show_guids = frozenset(show_guids.items())
|
||||
identifiers = {"show_guids": show_guids, "show_id": show["Id"]}
|
||||
task = asyncio.ensure_future(
|
||||
self.query(
|
||||
f"/Shows/{show['Id']}/Seasons"
|
||||
+ f"?userId={user_id}&isPlaceHolder=false&Fields=ProviderIds,RecursiveItemCount",
|
||||
"get",
|
||||
session,
|
||||
frozenset(identifiers.items()),
|
||||
)
|
||||
)
|
||||
seasons_tasks.append(task)
|
||||
|
||||
for show in watched_shows:
|
||||
show_guids = {k.lower(): v for k, v in show["ProviderIds"].items()}
|
||||
show_guids["title"] = show["Name"]
|
||||
show_guids["locations"] = tuple([show["Path"].split("/")[-1]])
|
||||
show_guids = frozenset(show_guids.items())
|
||||
seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||
if len(seasons["Items"]) > 0:
|
||||
seasons_watched = await asyncio.gather(*seasons_tasks)
|
||||
seasons_watched_filtered = []
|
||||
|
||||
for seasons in seasons_watched:
|
||||
seasons_watched_filtered_dict = {}
|
||||
seasons_watched_filtered_dict["Identifiers"] = seasons[
|
||||
"Identifiers"
|
||||
]
|
||||
seasons_watched_filtered_dict["Items"] = []
|
||||
for season in seasons["Items"]:
|
||||
episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds,MediaSources", "get")
|
||||
if len(episodes["Items"]) > 0:
|
||||
for episode in episodes["Items"]:
|
||||
if episode["UserData"]["Played"] == True:
|
||||
if episode["ProviderIds"] or episode["MediaSources"]:
|
||||
if show_guids not in user_watched[user_name][library_title]:
|
||||
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"]] = []
|
||||
if "PlayedPercentage" in season["UserData"]:
|
||||
if season["UserData"]["PlayedPercentage"] > 0:
|
||||
seasons_watched_filtered_dict["Items"].append(
|
||||
season
|
||||
)
|
||||
|
||||
# Lowercase episode["ProviderIds"] keys
|
||||
episode_guids = {}
|
||||
if episode["ProviderIds"]:
|
||||
episode_guids = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
||||
if episode["MediaSources"]:
|
||||
episode_guids["locations"] = tuple([x["Path"].split("/")[-1] for x in episode["MediaSources"]])
|
||||
user_watched[user_name][library_title][show_guids][season["Name"]].append(episode_guids)
|
||||
if seasons_watched_filtered_dict["Items"]:
|
||||
seasons_watched_filtered.append(
|
||||
seasons_watched_filtered_dict
|
||||
)
|
||||
|
||||
episodes_tasks = []
|
||||
for seasons in seasons_watched_filtered:
|
||||
if len(seasons["Items"]) > 0:
|
||||
for season in seasons["Items"]:
|
||||
season_identifiers = dict(seasons["Identifiers"])
|
||||
season_identifiers["season_id"] = season["Id"]
|
||||
season_identifiers["season_name"] = season["Name"]
|
||||
task = asyncio.ensure_future(
|
||||
self.query(
|
||||
f"/Shows/{season_identifiers['show_id']}/Episodes"
|
||||
+ f"?seasonId={season['Id']}&userId={user_id}&isPlaceHolder=false&isPlayed=true&Fields=ProviderIds,MediaSources",
|
||||
"get",
|
||||
session,
|
||||
frozenset(season_identifiers.items()),
|
||||
)
|
||||
)
|
||||
episodes_tasks.append(task)
|
||||
|
||||
watched_episodes = await asyncio.gather(*episodes_tasks)
|
||||
for episodes in watched_episodes:
|
||||
if len(episodes["Items"]) > 0:
|
||||
for episode in episodes["Items"]:
|
||||
if episode["UserData"]["Played"] is True:
|
||||
if (
|
||||
"ProviderIds" in episode
|
||||
or "MediaSources" in episode
|
||||
):
|
||||
episode_identifiers = dict(
|
||||
episodes["Identifiers"]
|
||||
)
|
||||
show_guids = episode_identifiers["show_guids"]
|
||||
if (
|
||||
show_guids
|
||||
not in user_watched[user_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 = {}
|
||||
if "ProviderIds" in episode:
|
||||
episode_guids = {
|
||||
k.lower(): v
|
||||
for k, v in episode[
|
||||
"ProviderIds"
|
||||
].items()
|
||||
}
|
||||
if "MediaSources" in episode:
|
||||
episode_guids["locations"] = tuple(
|
||||
[
|
||||
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
|
||||
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)
|
||||
|
||||
|
||||
def get_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None):
|
||||
async def get_users_watched(
|
||||
self,
|
||||
user_name,
|
||||
user_id,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
library_mapping,
|
||||
):
|
||||
try:
|
||||
users_watched = {}
|
||||
args = []
|
||||
# Get all libraries
|
||||
user_name = user_name.lower()
|
||||
tasks_watched = []
|
||||
|
||||
for user_name, user_id in users.items():
|
||||
# Get all libraries
|
||||
user_name = user_name.lower()
|
||||
|
||||
libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||
|
||||
for library in libraries:
|
||||
library_title = library["Name"]
|
||||
tasks_libraries = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
libraries = await self.query(f"/Users/{user_id}/Views", "get", session)
|
||||
for library in libraries["Items"]:
|
||||
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")
|
||||
library_title = library["Name"]
|
||||
identifiers = {
|
||||
"library_id": library_id,
|
||||
"library_title": library_title,
|
||||
}
|
||||
task = asyncio.ensure_future(
|
||||
self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?ParentId={library_id}&Filters=IsPlayed&limit=1",
|
||||
"get",
|
||||
session,
|
||||
identifiers=identifiers,
|
||||
)
|
||||
)
|
||||
tasks_libraries.append(task)
|
||||
|
||||
libraries = await asyncio.gather(
|
||||
*tasks_libraries, return_exceptions=True
|
||||
)
|
||||
|
||||
for watched in libraries:
|
||||
if len(watched["Items"]) == 0:
|
||||
logger(f"Jellyfin: No watched items found in library {library_title}", 1)
|
||||
continue
|
||||
else:
|
||||
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)
|
||||
library_id = watched["Identifiers"]["library_id"]
|
||||
library_title = watched["Identifiers"]["library_title"]
|
||||
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,
|
||||
)
|
||||
|
||||
if skip_reason:
|
||||
logger(f"Jellyfin: Skipping library {library_title} {skip_reason}", 1)
|
||||
logger(
|
||||
f"Jellyfin: Skipping library {library_title} {skip_reason}",
|
||||
1,
|
||||
)
|
||||
continue
|
||||
|
||||
args.append([self.get_user_watched, user_name, user_id, library_type, library_id, library_title])
|
||||
# Get watched for user
|
||||
task = asyncio.ensure_future(
|
||||
self.get_user_watched(
|
||||
user_name, user_id, library_type, library_id, library_title
|
||||
)
|
||||
)
|
||||
tasks_watched.append(task)
|
||||
|
||||
for user_watched in future_thread_executor(args):
|
||||
for user, user_watched_temp in user_watched.items():
|
||||
watched = await asyncio.gather(*tasks_watched, return_exceptions=True)
|
||||
return watched
|
||||
except Exception as e:
|
||||
logger(f"Jellyfin: Failed to get users watched, Error: {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
async def get_watched(
|
||||
self,
|
||||
users,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
library_mapping=None,
|
||||
):
|
||||
try:
|
||||
users_watched = {}
|
||||
watched = []
|
||||
|
||||
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,
|
||||
)
|
||||
)
|
||||
|
||||
for user_watched in watched:
|
||||
user_watched_temp = combine_watched_dicts(user_watched)
|
||||
for user, user_watched_temp in user_watched_temp.items():
|
||||
if user not in users_watched:
|
||||
users_watched[user] = {}
|
||||
users_watched[user].update(user_watched_temp)
|
||||
@@ -162,28 +365,56 @@ class Jellyfin():
|
||||
logger(f"Jellyfin: Failed to get watched, Error: {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
def update_user_watched(self, user_name, user_id, library, library_id, videos, dryrun):
|
||||
async def update_user_watched(
|
||||
self, user_name, user_id, library, library_id, videos, dryrun
|
||||
):
|
||||
try:
|
||||
logger(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: 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)
|
||||
|
||||
if videos_movies_ids:
|
||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,MediaSources", "get")
|
||||
for jellyfin_video in jellyfin_search["Items"]:
|
||||
logger(
|
||||
f"Jellyfin: mark list\nShows: {videos_shows_ids}\nEpisodes: {videos_episodes_ids}\nMovies: {videos_movies_ids}",
|
||||
1,
|
||||
)
|
||||
async with aiohttp.ClientSession() as session:
|
||||
if videos_movies_ids:
|
||||
jellyfin_search = await self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}"
|
||||
+ "&isPlayed=false&Fields=ItemCounts,ProviderIds,MediaSources",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
for jellyfin_video in jellyfin_search["Items"]:
|
||||
movie_found = False
|
||||
|
||||
if "MediaSources" in jellyfin_video:
|
||||
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
|
||||
break
|
||||
|
||||
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_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
|
||||
break
|
||||
|
||||
@@ -192,136 +423,224 @@ class Jellyfin():
|
||||
msg = f"{jellyfin_video['Name']} as watched for {user_name} in {library} for Jellyfin"
|
||||
if not dryrun:
|
||||
logger(f"Marking {msg}", 0)
|
||||
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}", "post")
|
||||
await self.query(
|
||||
f"/Users/{user_id}/PlayedItems/{jellyfin_video_id}",
|
||||
"post",
|
||||
session,
|
||||
)
|
||||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
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
|
||||
if videos_shows_ids and videos_episodes_ids:
|
||||
jellyfin_search = await self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?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"]]
|
||||
|
||||
for jellyfin_show in jellyfin_shows:
|
||||
show_found = False
|
||||
|
||||
# TV Shows
|
||||
if videos_shows_ids and videos_episodes_ids:
|
||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds,Path", "get")
|
||||
jellyfin_shows = [x for x in jellyfin_search["Items"]]
|
||||
if "Path" in jellyfin_show:
|
||||
if (
|
||||
jellyfin_show["Path"].split("/")[-1]
|
||||
in videos_shows_ids["locations"]
|
||||
):
|
||||
show_found = True
|
||||
|
||||
for jellyfin_show in jellyfin_shows:
|
||||
show_found = False
|
||||
|
||||
if "Path" in jellyfin_show:
|
||||
if jellyfin_show["Path"].split("/")[-1] in videos_shows_ids["locations"]:
|
||||
show_found = True
|
||||
|
||||
if not show_found:
|
||||
for show_provider_source, show_provider_id in jellyfin_show["ProviderIds"].items():
|
||||
if show_provider_source.lower() in videos_shows_ids:
|
||||
if show_provider_id.lower() in videos_shows_ids[show_provider_source.lower()]:
|
||||
show_found = True
|
||||
break
|
||||
|
||||
if show_found:
|
||||
logger(f"Jellyfin: Updating watched for {user_name} in library {library} for show {jellyfin_show['Name']}", 1)
|
||||
jellyfin_show_id = jellyfin_show["Id"]
|
||||
jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds,MediaSources", "get")
|
||||
|
||||
for jellyfin_episode in jellyfin_episodes["Items"]:
|
||||
episode_found = False
|
||||
|
||||
if "MediaSources" in jellyfin_episode:
|
||||
for episode_location in jellyfin_episode["MediaSources"]:
|
||||
if episode_location["Path"].split("/")[-1] in videos_episodes_ids["locations"]:
|
||||
episode_found = True
|
||||
if not show_found:
|
||||
for show_provider_source, show_provider_id in jellyfin_show[
|
||||
"ProviderIds"
|
||||
].items():
|
||||
if show_provider_source.lower() in videos_shows_ids:
|
||||
if (
|
||||
show_provider_id.lower()
|
||||
in videos_shows_ids[
|
||||
show_provider_source.lower()
|
||||
]
|
||||
):
|
||||
show_found = True
|
||||
break
|
||||
|
||||
if not episode_found:
|
||||
for episode_provider_source, 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()]:
|
||||
if show_found:
|
||||
logger(
|
||||
f"Jellyfin: Updating watched for {user_name} in library {library} for show {jellyfin_show['Name']}",
|
||||
1,
|
||||
)
|
||||
jellyfin_show_id = jellyfin_show["Id"]
|
||||
jellyfin_episodes = await self.query(
|
||||
f"/Shows/{jellyfin_show_id}/Episodes"
|
||||
+ f"?userId={user_id}&Fields=ItemCounts,ProviderIds,MediaSources",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
|
||||
for jellyfin_episode in jellyfin_episodes["Items"]:
|
||||
episode_found = False
|
||||
|
||||
if "MediaSources" in jellyfin_episode:
|
||||
for episode_location in jellyfin_episode[
|
||||
"MediaSources"
|
||||
]:
|
||||
if (
|
||||
episode_location["Path"].split("/")[-1]
|
||||
in videos_episodes_ids["locations"]
|
||||
):
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
if episode_found:
|
||||
jellyfin_episode_id = jellyfin_episode["Id"]
|
||||
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['Name']} as watched for {user_name} in {library} for Jellyfin"
|
||||
if not dryrun:
|
||||
logger(f"Marked {msg}", 0)
|
||||
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post")
|
||||
if not episode_found:
|
||||
for (
|
||||
episode_provider_source,
|
||||
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
|
||||
break
|
||||
|
||||
if episode_found:
|
||||
jellyfin_episode_id = jellyfin_episode["Id"]
|
||||
msg = (
|
||||
f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['Name']}"
|
||||
+ f" as watched for {user_name} in {library} for Jellyfin"
|
||||
)
|
||||
if not dryrun:
|
||||
logger(f"Marked {msg}", 0)
|
||||
await self.query(
|
||||
f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}",
|
||||
"post",
|
||||
session,
|
||||
)
|
||||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
else:
|
||||
logger(f"Jellyfin: Skipping episode {jellyfin_episode['Name']} as it is not in mark list for {user_name}", 1)
|
||||
else:
|
||||
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:
|
||||
logger(f"Jellyfin: No videos to mark as watched for {user_name} in library {library}", 1)
|
||||
|
||||
except Exception as e:
|
||||
logger(f"Jellyfin: Error updating watched for {user_name} in library {library}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
||||
try:
|
||||
args = []
|
||||
for user, libraries in watched_list.items():
|
||||
logger(f"Jellyfin: Updating for entry {user}, {libraries}", 1)
|
||||
user_other = None
|
||||
user_name = None
|
||||
if user_mapping:
|
||||
if user in user_mapping.keys():
|
||||
user_other = user_mapping[user]
|
||||
elif user in user_mapping.values():
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
|
||||
user_id = None
|
||||
for key in self.users.keys():
|
||||
if user.lower() == key.lower():
|
||||
user_id = self.users[key]
|
||||
user_name = key
|
||||
break
|
||||
elif user_other and user_other.lower() == key.lower():
|
||||
user_id = self.users[key]
|
||||
user_name = key
|
||||
break
|
||||
|
||||
if not user_id:
|
||||
logger(f"{user} {user_other} not found in Jellyfin", 2)
|
||||
continue
|
||||
|
||||
jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||
|
||||
for library, videos in libraries.items():
|
||||
library_other = None
|
||||
if library_mapping:
|
||||
if library in library_mapping.keys():
|
||||
library_other = library_mapping[library]
|
||||
elif library in library_mapping.values():
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
|
||||
|
||||
if library.lower() not in [x["Name"].lower() for x in jellyfin_libraries]:
|
||||
if library_other:
|
||||
if library_other.lower() in [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
|
||||
else:
|
||||
logger(f"Jellyfin: Library {library} or {library_other} not found in library list", 2)
|
||||
continue
|
||||
logger(
|
||||
f"Jellyfin: Skipping episode {jellyfin_episode['Name']} as it is not in mark list for {user_name}",
|
||||
1,
|
||||
)
|
||||
else:
|
||||
logger(f"Jellyfin: Library {library} not found in library list", 2)
|
||||
continue
|
||||
logger(
|
||||
f"Jellyfin: Skipping show {jellyfin_show['Name']} as it is not in mark list for {user_name}",
|
||||
1,
|
||||
)
|
||||
|
||||
library_id = None
|
||||
for jellyfin_library in jellyfin_libraries:
|
||||
if jellyfin_library["Name"] == library:
|
||||
library_id = jellyfin_library["Id"]
|
||||
continue
|
||||
if (
|
||||
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,
|
||||
)
|
||||
|
||||
if library_id:
|
||||
args.append([self.update_user_watched, user_name, user_id, library, library_id, videos, dryrun])
|
||||
|
||||
future_thread_executor(args)
|
||||
except Exception as e:
|
||||
logger(f"Jellyfin: Error updating watched", 2)
|
||||
logger(
|
||||
f"Jellyfin: Error updating watched for {user_name} in library {library}, {e}",
|
||||
2,
|
||||
)
|
||||
raise Exception(e)
|
||||
|
||||
async def update_watched(
|
||||
self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
|
||||
):
|
||||
try:
|
||||
tasks = []
|
||||
async with aiohttp.ClientSession() as session:
|
||||
for user, libraries in watched_list.items():
|
||||
logger(f"Jellyfin: Updating for entry {user}, {libraries}", 1)
|
||||
user_other = None
|
||||
user_name = None
|
||||
if user_mapping:
|
||||
if user in user_mapping.keys():
|
||||
user_other = user_mapping[user]
|
||||
elif user in user_mapping.values():
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
|
||||
user_id = None
|
||||
for key in self.users.keys():
|
||||
if user.lower() == key.lower():
|
||||
user_id = self.users[key]
|
||||
user_name = key
|
||||
break
|
||||
elif user_other and user_other.lower() == key.lower():
|
||||
user_id = self.users[key]
|
||||
user_name = key
|
||||
break
|
||||
|
||||
if not user_id:
|
||||
logger(f"{user} {user_other} not found in Jellyfin", 2)
|
||||
continue
|
||||
|
||||
jellyfin_libraries = await self.query(
|
||||
f"/Users/{user_id}/Views", "get", session
|
||||
)
|
||||
jellyfin_libraries = [x for x in jellyfin_libraries["Items"]]
|
||||
|
||||
for library, videos in libraries.items():
|
||||
library_other = None
|
||||
if library_mapping:
|
||||
if library in library_mapping.keys():
|
||||
library_other = library_mapping[library]
|
||||
elif library in library_mapping.values():
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
|
||||
if library.lower() not in [
|
||||
x["Name"].lower() for x in jellyfin_libraries
|
||||
]:
|
||||
if library_other:
|
||||
if library_other.lower() in [
|
||||
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
|
||||
else:
|
||||
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
|
||||
|
||||
if library_id:
|
||||
task = self.update_user_watched(
|
||||
user_name, user_id, library, library_id, videos, dryrun
|
||||
)
|
||||
tasks.append(task)
|
||||
|
||||
await asyncio.gather(*tasks, return_exceptions=True)
|
||||
except Exception as e:
|
||||
logger(f"Jellyfin: Error updating watched, {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
385
src/main.py
385
src/main.py
@@ -1,14 +1,22 @@
|
||||
import copy, os, traceback, json
|
||||
import copy, os, traceback, json, asyncio
|
||||
from dotenv import load_dotenv
|
||||
from time import sleep
|
||||
from time import sleep, perf_counter
|
||||
|
||||
from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict, future_thread_executor
|
||||
from src.functions import (
|
||||
logger,
|
||||
str_to_bool,
|
||||
search_mapping,
|
||||
generate_library_guids_dict,
|
||||
)
|
||||
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):
|
||||
|
||||
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
|
||||
@@ -35,79 +43,165 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
|
||||
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)
|
||||
logger(
|
||||
f"library {library_1} and {library_other} not found in watched list 2",
|
||||
1,
|
||||
)
|
||||
continue
|
||||
|
||||
(
|
||||
_,
|
||||
episode_watched_list_2_keys_dict,
|
||||
movies_watched_list_2_keys_dict,
|
||||
) = generate_library_guids_dict(watched_list_2[user_2][library_2])
|
||||
|
||||
# Movies
|
||||
if isinstance(watched_list_1[user_1][library_1], list):
|
||||
_, _, movies_watched_list_2_keys_dict = generate_library_guids_dict(watched_list_2[user_2][library_2])
|
||||
for movie in watched_list_1[user_1][library_1]:
|
||||
movie_found = False
|
||||
for movie_key, movie_value in movie.items():
|
||||
if movie_key == "locations":
|
||||
for location in movie_value:
|
||||
if location in movies_watched_list_2_keys_dict["locations"]:
|
||||
movie_found = True
|
||||
break
|
||||
if (
|
||||
"locations"
|
||||
in movies_watched_list_2_keys_dict.keys()
|
||||
):
|
||||
for location in movie_value:
|
||||
if (
|
||||
location
|
||||
in movies_watched_list_2_keys_dict[
|
||||
"locations"
|
||||
]
|
||||
):
|
||||
movie_found = True
|
||||
break
|
||||
else:
|
||||
if movie_key in movies_watched_list_2_keys_dict.keys():
|
||||
if movie_value in movies_watched_list_2_keys_dict[movie_key]:
|
||||
if (
|
||||
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
|
||||
|
||||
if movie_found:
|
||||
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
|
||||
|
||||
|
||||
# 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])
|
||||
|
||||
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 in watched_list_1[user_1][library_1][
|
||||
show_key_1
|
||||
][season]:
|
||||
episode_found = False
|
||||
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 == "locations":
|
||||
for location in episode_value:
|
||||
if location in episode_watched_list_2_keys_dict["locations"]:
|
||||
episode_found = True
|
||||
break
|
||||
if (
|
||||
"locations"
|
||||
in episode_watched_list_2_keys_dict.keys()
|
||||
):
|
||||
for location in episode_value:
|
||||
if (
|
||||
location
|
||||
in episode_watched_list_2_keys_dict[
|
||||
"locations"
|
||||
]
|
||||
):
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
else:
|
||||
if episode_key in episode_watched_list_2_keys_dict.keys():
|
||||
if episode_value in episode_watched_list_2_keys_dict[episode_key]:
|
||||
if (
|
||||
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
|
||||
|
||||
if episode_found:
|
||||
if 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)
|
||||
if (
|
||||
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
|
||||
|
||||
# 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 {show_key_dict['title']} because it is empty", 3)
|
||||
del modified_watched_list_1[user_1][library_1][show_key_1][season]
|
||||
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 {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 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]
|
||||
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]
|
||||
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
|
||||
@@ -117,7 +211,17 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
|
||||
|
||||
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 len(blacklist_library) > 0:
|
||||
blacklist_library = blacklist_library.split(",")
|
||||
@@ -200,35 +304,46 @@ def setup_black_white_lists(blacklist_library: str, whitelist_library: str, blac
|
||||
whitelist_users = []
|
||||
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
|
||||
server_1_type = server_1[0]
|
||||
server_1_connection = server_1[1]
|
||||
server_2_type = server_2[0]
|
||||
server_2_connection = server_2[1]
|
||||
print(f"Server 1: {server_1_type} {server_1_connection}")
|
||||
print(f"Server 2: {server_2_type} {server_2_connection}")
|
||||
|
||||
server_1_users = []
|
||||
if server_1_type == "plex":
|
||||
server_1_users = [ x.title.lower() for x in server_1_connection.users ]
|
||||
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_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 ]
|
||||
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() ]
|
||||
|
||||
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)
|
||||
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
|
||||
@@ -238,7 +353,7 @@ def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mappi
|
||||
|
||||
for server_2_user in server_2_users:
|
||||
if user_mapping:
|
||||
plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user)
|
||||
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
|
||||
@@ -264,36 +379,53 @@ def setup_users(server_1, server_2, blacklist_users, whitelist_users, user_mappi
|
||||
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():
|
||||
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():
|
||||
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():
|
||||
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():
|
||||
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}")
|
||||
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}")
|
||||
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 = []
|
||||
|
||||
@@ -302,27 +434,58 @@ def generate_server_connections():
|
||||
plex_username = os.getenv("PLEX_USERNAME", None)
|
||||
plex_password = os.getenv("PLEX_PASSWORD", None)
|
||||
plex_servername = os.getenv("PLEX_SERVERNAME", None)
|
||||
ssl_bypass = str_to_bool(os.getenv("SSL_BYPASS", "False"))
|
||||
|
||||
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")
|
||||
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)))
|
||||
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:
|
||||
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")
|
||||
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())))
|
||||
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_token = os.getenv("JELLYFIN_TOKEN", None)
|
||||
@@ -332,15 +495,23 @@ def generate_server_connections():
|
||||
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")
|
||||
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())))
|
||||
servers.append(
|
||||
(
|
||||
"jellyfin",
|
||||
Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip()),
|
||||
)
|
||||
)
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
def main_loop():
|
||||
logfile = os.getenv("LOGFILE","log.log")
|
||||
logfile = os.getenv("LOGFILE", "log.log")
|
||||
# Delete logfile if it exists
|
||||
if os.path.exists(logfile):
|
||||
os.remove(logfile)
|
||||
@@ -367,7 +538,23 @@ def main_loop():
|
||||
blacklist_users = os.getenv("BLACKLIST_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
|
||||
logger("Creating server connections", 1)
|
||||
@@ -379,51 +566,86 @@ def main_loop():
|
||||
break
|
||||
|
||||
# Start server_2 at the next server in the list
|
||||
for server_2 in servers[servers.index(server_1) + 1:]:
|
||||
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
|
||||
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)
|
||||
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]
|
||||
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)
|
||||
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(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)
|
||||
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)
|
||||
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)
|
||||
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]]
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
future_thread_executor(args)
|
||||
|
||||
def main():
|
||||
sleep_duration = float(os.getenv("SLEEP_DURATION", "3600"))
|
||||
|
||||
while(True):
|
||||
times = []
|
||||
while True:
|
||||
try:
|
||||
start = perf_counter()
|
||||
main_loop()
|
||||
end = perf_counter()
|
||||
times.append(end - start)
|
||||
|
||||
if len(times) > 0:
|
||||
logger(f"Average time: {sum(times) / len(times)}", 0)
|
||||
|
||||
logger(f"Looping in {sleep_duration}")
|
||||
sleep(sleep_duration)
|
||||
|
||||
except Exception as error:
|
||||
if isinstance(error, list):
|
||||
for message in error:
|
||||
@@ -431,7 +653,6 @@ def main():
|
||||
else:
|
||||
logger(error, log_type=2)
|
||||
|
||||
|
||||
logger(traceback.format_exc(), 2)
|
||||
logger(f"Retrying in {sleep_duration}", log_type=0)
|
||||
sleep(sleep_duration)
|
||||
|
||||
488
src/plex.py
488
src/plex.py
@@ -3,25 +3,268 @@ import re, requests
|
||||
from plexapi.server import PlexServer
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
def get_user_watched(user, user_plex, library):
|
||||
try:
|
||||
user_name = user.title.lower()
|
||||
user_watched = {}
|
||||
user_watched[user_name] = {}
|
||||
|
||||
logger(
|
||||
f"Plex: Generating watched for {user_name} in library {library.title}",
|
||||
0,
|
||||
)
|
||||
|
||||
if library.type == "movie":
|
||||
user_watched[user_name][library.title] = []
|
||||
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
for video in library_videos.search(unwatched=False):
|
||||
movie_guids = {}
|
||||
for guid in video.guids:
|
||||
guid_source = re.search(r"(.*)://", guid.id).group(1).lower()
|
||||
guid_id = re.search(r"://(.*)", guid.id).group(1)
|
||||
movie_guids[guid_source] = guid_id
|
||||
|
||||
movie_guids["title"] = video.title
|
||||
movie_guids["locations"] = tuple(
|
||||
[x.split("/")[-1] for x in video.locations]
|
||||
)
|
||||
|
||||
user_watched[user_name][library.title].append(movie_guids)
|
||||
|
||||
elif library.type == "show":
|
||||
user_watched[user_name][library.title] = {}
|
||||
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
for show in library_videos.search(unwatched=False):
|
||||
show_guids = {}
|
||||
for show_guid in show.guids:
|
||||
# Extract after :// from guid.id
|
||||
show_guid_source = (
|
||||
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["title"] = show.title
|
||||
show_guids["locations"] = tuple(
|
||||
[x.split("/")[-1] for x in show.locations]
|
||||
)
|
||||
show_guids = frozenset(show_guids.items())
|
||||
|
||||
for season in show.seasons():
|
||||
episode_guids = []
|
||||
for episode in season.episodes():
|
||||
if episode.viewCount > 0:
|
||||
episode_guids_temp = {}
|
||||
for guid in episode.guids:
|
||||
# Extract after :// from guid.id
|
||||
guid_source = (
|
||||
re.search(r"(.*)://", guid.id).group(1).lower()
|
||||
)
|
||||
guid_id = re.search(r"://(.*)", guid.id).group(1)
|
||||
episode_guids_temp[guid_source] = guid_id
|
||||
|
||||
episode_guids_temp["locations"] = tuple(
|
||||
[x.split("/")[-1] for x in episode.locations]
|
||||
)
|
||||
episode_guids.append(episode_guids_temp)
|
||||
|
||||
if episode_guids:
|
||||
# append show, season, episode
|
||||
if show_guids not in user_watched[user_name][library.title]:
|
||||
user_watched[user_name][library.title][show_guids] = {}
|
||||
if (
|
||||
season.title
|
||||
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
|
||||
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 update_user_watched(user, user_plex, library, videos, dryrun):
|
||||
try:
|
||||
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,
|
||||
)
|
||||
|
||||
library_videos = user_plex.library.section(library)
|
||||
if videos_movies_ids:
|
||||
for movies_search in library_videos.search(unwatched=True):
|
||||
movie_found = False
|
||||
for movie_location in movies_search.locations:
|
||||
if movie_location.split("/")[-1] in videos_movies_ids["locations"]:
|
||||
movie_found = True
|
||||
break
|
||||
|
||||
if not movie_found:
|
||||
for movie_guid in movies_search.guids:
|
||||
movie_guid_source = (
|
||||
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_guid_source in videos_movies_ids.keys():
|
||||
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
||||
movie_found = True
|
||||
break
|
||||
|
||||
if movie_found:
|
||||
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
||||
if not dryrun:
|
||||
logger(f"Marked {msg}", 0)
|
||||
movies_search.markWatched()
|
||||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
else:
|
||||
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:
|
||||
for show_search in library_videos.search(unwatched=True):
|
||||
show_found = False
|
||||
for show_location in show_search.locations:
|
||||
if show_location.split("/")[-1] in videos_shows_ids["locations"]:
|
||||
show_found = True
|
||||
break
|
||||
|
||||
if not show_found:
|
||||
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
|
||||
break
|
||||
|
||||
if show_found:
|
||||
for episode_search in show_search.episodes():
|
||||
episode_found = False
|
||||
|
||||
for episode_location in episode_search.locations:
|
||||
if (
|
||||
episode_location.split("/")[-1]
|
||||
in videos_episodes_ids["locations"]
|
||||
):
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
if not episode_found:
|
||||
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_episodes_ids exactly, then the episode is in the list
|
||||
if episode_guid_source in videos_episodes_ids.keys():
|
||||
if (
|
||||
episode_guid_id
|
||||
in videos_episodes_ids[episode_guid_source]
|
||||
):
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
if episode_found:
|
||||
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)
|
||||
else:
|
||||
logger(
|
||||
f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}",
|
||||
1,
|
||||
)
|
||||
else:
|
||||
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:
|
||||
logger(
|
||||
f"Jellyfin: No videos to mark as watched for {user.title} in library {library}",
|
||||
1,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger(
|
||||
f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}",
|
||||
2,
|
||||
)
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
# class plex accept base url and token and username and password but default with none
|
||||
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.token = token
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.servername = servername
|
||||
self.plex = self.login()
|
||||
self.plex = self.login(ssl_bypass)
|
||||
self.admin_user = self.plex.myPlexAccount()
|
||||
self.users = self.get_users()
|
||||
|
||||
def login(self):
|
||||
def login(self, ssl_bypass=False):
|
||||
try:
|
||||
if self.baseurl and self.token:
|
||||
# Login via token
|
||||
# Login via token
|
||||
if ssl_bypass:
|
||||
session = requests.Session()
|
||||
session.verify = False
|
||||
plex = PlexServer(self.baseurl, self.token, session=session)
|
||||
else:
|
||||
plex = PlexServer(self.baseurl, self.token)
|
||||
elif self.username and self.password and self.servername:
|
||||
# Login via plex account
|
||||
@@ -39,7 +282,6 @@ class Plex:
|
||||
logger(f"Plex: Failed to login, Error: {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
def get_users(self):
|
||||
try:
|
||||
users = self.plex.myPlexAccount().users()
|
||||
@@ -52,76 +294,15 @@ class Plex:
|
||||
logger(f"Plex: Failed to get users, Error: {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
def get_user_watched(self, user, user_plex, library):
|
||||
try:
|
||||
user_name = user.title.lower()
|
||||
user_watched = {}
|
||||
user_watched[user_name] = {}
|
||||
|
||||
logger(f"Plex: Generating watched for {user_name} in library {library.title}", 0)
|
||||
|
||||
if library.type == "movie":
|
||||
user_watched[user_name][library.title] = []
|
||||
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
for video in library_videos.search(unwatched=False):
|
||||
movie_guids = {}
|
||||
for guid in video.guids:
|
||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||
movie_guids[guid_source] = guid_id
|
||||
|
||||
movie_guids["title"] = video.title
|
||||
movie_guids["locations"] = tuple([x.split("/")[-1] for x in video.locations])
|
||||
|
||||
user_watched[user_name][library.title].append(movie_guids)
|
||||
|
||||
elif library.type == "show":
|
||||
user_watched[user_name][library.title] = {}
|
||||
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
for show in library_videos.search(unwatched=False):
|
||||
show_guids = {}
|
||||
for show_guid in show.guids:
|
||||
# Extract after :// from guid.id
|
||||
show_guid_source = 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["title"] = show.title
|
||||
show_guids["locations"] = tuple([x.split("/")[-1] for x in show.locations])
|
||||
show_guids = frozenset(show_guids.items())
|
||||
|
||||
for season in show.seasons():
|
||||
episode_guids = []
|
||||
for episode in season.episodes():
|
||||
if episode.viewCount > 0:
|
||||
episode_guids_temp = {}
|
||||
for guid in episode.guids:
|
||||
# Extract after :// from guid.id
|
||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||
episode_guids_temp[guid_source] = guid_id
|
||||
|
||||
episode_guids_temp["locations"] = tuple([x.split("/")[-1] for x in episode.locations])
|
||||
episode_guids.append(episode_guids_temp)
|
||||
|
||||
if episode_guids:
|
||||
# append show, season, episode
|
||||
if show_guids not in user_watched[user_name][library.title]:
|
||||
user_watched[user_name][library.title][show_guids] = {}
|
||||
if season.title 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
|
||||
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,
|
||||
):
|
||||
try:
|
||||
# Get all libraries
|
||||
users_watched = {}
|
||||
@@ -131,7 +312,9 @@ class Plex:
|
||||
if self.admin_user == user:
|
||||
user_plex = self.plex
|
||||
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()
|
||||
|
||||
@@ -139,13 +322,23 @@ class Plex:
|
||||
library_title = library.title
|
||||
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:
|
||||
logger(f"Plex: Skipping library {library_title} {skip_reason}", 1)
|
||||
logger(
|
||||
f"Plex: Skipping library {library_title} {skip_reason}", 1
|
||||
)
|
||||
continue
|
||||
|
||||
args.append([self.get_user_watched, user, user_plex, library])
|
||||
args.append([get_user_watched, user, user_plex, library])
|
||||
|
||||
for user_watched in future_thread_executor(args):
|
||||
for user, user_watched_temp in user_watched.items():
|
||||
@@ -158,104 +351,9 @@ class Plex:
|
||||
logger(f"Plex: Failed to get watched, Error: {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
|
||||
def update_user_watched (self, user, user_plex, library, videos, dryrun):
|
||||
try:
|
||||
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)
|
||||
|
||||
library_videos = user_plex.library.section(library)
|
||||
if videos_movies_ids:
|
||||
for movies_search in library_videos.search(unwatched=True):
|
||||
movie_found = False
|
||||
for movie_location in movies_search.locations:
|
||||
if movie_location.split("/")[-1] in videos_movies_ids["locations"]:
|
||||
movie_found = True
|
||||
break
|
||||
|
||||
if not movie_found:
|
||||
for movie_guid in movies_search.guids:
|
||||
movie_guid_source = 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_guid_source in videos_movies_ids.keys():
|
||||
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
||||
movie_found = True
|
||||
break
|
||||
|
||||
if movie_found:
|
||||
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
||||
if not dryrun:
|
||||
logger(f"Marked {msg}", 0)
|
||||
movies_search.markWatched()
|
||||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
else:
|
||||
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:
|
||||
for show_search in library_videos.search(unwatched=True):
|
||||
show_found = False
|
||||
for show_location in show_search.locations:
|
||||
if show_location.split("/")[-1] in videos_shows_ids["locations"]:
|
||||
show_found = True
|
||||
break
|
||||
|
||||
if not show_found:
|
||||
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
|
||||
break
|
||||
|
||||
if show_found:
|
||||
for episode_search in show_search.episodes():
|
||||
episode_found = False
|
||||
|
||||
for episode_location in episode_search.locations:
|
||||
if episode_location.split("/")[-1] in videos_episodes_ids["locations"]:
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
if not episode_found:
|
||||
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_episodes_ids exactly, then the episode is in the list
|
||||
if episode_guid_source in videos_episodes_ids.keys():
|
||||
if episode_guid_id in videos_episodes_ids[episode_guid_source]:
|
||||
episode_found = True
|
||||
break
|
||||
|
||||
if episode_found:
|
||||
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)
|
||||
else:
|
||||
logger(f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}", 1)
|
||||
else:
|
||||
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:
|
||||
logger(f"Jellyfin: No videos to mark as watched for {user.title} in library {library}", 1)
|
||||
|
||||
except Exception as e:
|
||||
logger(f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}", 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
|
||||
):
|
||||
try:
|
||||
args = []
|
||||
|
||||
@@ -279,7 +377,9 @@ class Plex:
|
||||
if self.admin_user == user:
|
||||
user_plex = self.plex
|
||||
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():
|
||||
library_other = None
|
||||
@@ -293,18 +393,36 @@ class Plex:
|
||||
library_list = user_plex.library.sections()
|
||||
if library.lower() not in [x.title.lower() for x in library_list]:
|
||||
if library_other:
|
||||
if library_other.lower() in [x.title.lower() for x in library_list]:
|
||||
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
|
||||
]:
|
||||
logger(
|
||||
f"Plex: Library {library} not found, but {library_other} found, using {library_other}",
|
||||
1,
|
||||
)
|
||||
library = library_other
|
||||
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
|
||||
else:
|
||||
logger(f"Plex: Library {library} not found in library list", 2)
|
||||
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(
|
||||
[
|
||||
update_user_watched,
|
||||
user,
|
||||
user_plex,
|
||||
library,
|
||||
videos,
|
||||
dryrun,
|
||||
]
|
||||
)
|
||||
|
||||
future_thread_executor(args)
|
||||
except Exception as e:
|
||||
|
||||
78
test/test_main.py
Normal file
78
test/test_main.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# getting the name of the directory
|
||||
# where the this file is present.
|
||||
current = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# Getting the parent directory name
|
||||
# where the current directory is present.
|
||||
parent = os.path.dirname(current)
|
||||
|
||||
# adding the parent directory to
|
||||
# the sys.path.
|
||||
sys.path.append(parent)
|
||||
|
||||
from src.main import setup_black_white_lists
|
||||
|
||||
|
||||
def test_setup_black_white_lists():
|
||||
# Simple
|
||||
blacklist_library = "library1, library2"
|
||||
whitelist_library = "library1, library2"
|
||||
blacklist_library_type = "library_type1, library_type2"
|
||||
whitelist_library_type = "library_type1, library_type2"
|
||||
blacklist_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,
|
||||
)
|
||||
|
||||
assert results_blacklist_library == ["library1", "library2"]
|
||||
assert return_whitelist_library == ["library1", "library2"]
|
||||
assert return_blacklist_library_type == ["library_type1", "library_type2"]
|
||||
assert return_whitelist_library_type == ["library_type1", "library_type2"]
|
||||
assert return_blacklist_users == ["user1", "user2"]
|
||||
assert return_whitelist_users == ["user1", "user2"]
|
||||
|
||||
# Library Mapping and user mapping
|
||||
library_mapping = {"library1": "library3"}
|
||||
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,
|
||||
)
|
||||
|
||||
assert results_blacklist_library == ["library1", "library2", "library3"]
|
||||
assert return_whitelist_library == ["library1", "library2", "library3"]
|
||||
assert return_blacklist_library_type == ["library_type1", "library_type2"]
|
||||
assert return_whitelist_library_type == ["library_type1", "library_type2"]
|
||||
assert return_blacklist_users == ["user1", "user2", "user3"]
|
||||
assert return_whitelist_users == ["user1", "user2", "user3"]
|
||||
@@ -1,47 +0,0 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
# getting the name of the directory
|
||||
# where the this file is present.
|
||||
current = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
# Getting the parent directory name
|
||||
# where the current directory is present.
|
||||
parent = os.path.dirname(current)
|
||||
|
||||
# adding the parent directory to
|
||||
# the sys.path.
|
||||
sys.path.append(parent)
|
||||
|
||||
from src.main import setup_black_white_lists
|
||||
|
||||
def test_setup_black_white_lists():
|
||||
# Simple
|
||||
blacklist_library = 'library1, library2'
|
||||
whitelist_library = 'library1, library2'
|
||||
blacklist_library_type = 'library_type1, library_type2'
|
||||
whitelist_library_type = 'library_type1, library_type2'
|
||||
blacklist_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)
|
||||
|
||||
assert results_blacklist_library == ['library1', 'library2']
|
||||
assert return_whitelist_library == ['library1', 'library2']
|
||||
assert return_blacklist_library_type == ['library_type1', 'library_type2']
|
||||
assert return_whitelist_library_type == ['library_type1', 'library_type2']
|
||||
assert return_blacklist_users == ['user1', 'user2']
|
||||
assert return_whitelist_users == ['user1', 'user2']
|
||||
|
||||
# Library Mapping and user mapping
|
||||
library_mapping = { "library1": "library3" }
|
||||
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)
|
||||
|
||||
assert results_blacklist_library == ['library1', 'library2', 'library3']
|
||||
assert return_whitelist_library == ['library1', 'library2', 'library3']
|
||||
assert return_blacklist_library_type == ['library_type1', 'library_type2']
|
||||
assert return_whitelist_library_type == ['library_type1', 'library_type2']
|
||||
assert return_blacklist_users == ['user1', 'user2', 'user3']
|
||||
assert return_whitelist_users == ['user1', 'user2', 'user3']
|
||||
@@ -16,86 +16,201 @@ sys.path.append(parent)
|
||||
from src.main import cleanup_watched
|
||||
|
||||
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": [
|
||||
{'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",))}): {
|
||||
"Season 1": [
|
||||
{'locations': ('Test S01E01.mkv',)},
|
||||
{'locations': ('Test S01E02.mkv',)}
|
||||
{"locations": ("Test S01E01.mkv",)},
|
||||
{"locations": ("Test S01E02.mkv",)},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
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 = {
|
||||
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": [
|
||||
{'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",))}): {
|
||||
"Season 1": [
|
||||
{'locations': ('Test S01E02.mkv',)},
|
||||
{'locations': ('Test S01E03.mkv',)}
|
||||
{"locations": ("Test S01E02.mkv",)},
|
||||
{"locations": ("Test S01E03.mkv",)},
|
||||
]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
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.
|
||||
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": [
|
||||
{'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 = {
|
||||
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": [
|
||||
{'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",))}): {
|
||||
"Season 1": [
|
||||
{'locations': ('Test S01E01.mkv',)}
|
||||
]
|
||||
}
|
||||
"Season 1": [{"locations": ("Test S01E01.mkv",)}]
|
||||
},
|
||||
}
|
||||
|
||||
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 = {
|
||||
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": [
|
||||
{'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",))}): {
|
||||
"Season 1": [
|
||||
{'locations': ('Test S01E03.mkv',)}
|
||||
]
|
||||
}
|
||||
"Season 1": [{"locations": ("Test S01E03.mkv",)}]
|
||||
},
|
||||
}
|
||||
|
||||
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": {
|
||||
"TV Shows": tv_shows_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 = {
|
||||
"user1": {
|
||||
"TV Shows": tv_shows_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 = {
|
||||
"user1": {
|
||||
"TV Shows": expected_tv_show_watched_list_1
|
||||
, "Movies": expected_movie_watched_list_1
|
||||
"TV Shows": expected_tv_show_watched_list_1,
|
||||
"Movies": expected_movie_watched_list_1,
|
||||
}
|
||||
}
|
||||
|
||||
expected_watched_list_2 = {
|
||||
"user1": {
|
||||
"TV Shows": expected_tv_show_watched_list_2
|
||||
, "Movies": expected_movie_watched_list_2
|
||||
"TV Shows": expected_tv_show_watched_list_2,
|
||||
"Movies": expected_movie_watched_list_2,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,36 +256,46 @@ def test_mapping_cleanup_watched():
|
||||
"user1": {
|
||||
"TV Shows": tv_shows_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 = {
|
||||
"user2": {
|
||||
"Shows": tv_shows_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 = {
|
||||
"user1": {
|
||||
"TV Shows": expected_tv_show_watched_list_1
|
||||
, "Movies": expected_movie_watched_list_1
|
||||
"TV Shows": expected_tv_show_watched_list_1,
|
||||
"Movies": expected_movie_watched_list_1,
|
||||
}
|
||||
}
|
||||
|
||||
expected_watched_list_2 = {
|
||||
"user2": {
|
||||
"Shows": expected_tv_show_watched_list_2
|
||||
, "Movies": expected_movie_watched_list_2
|
||||
"Shows": expected_tv_show_watched_list_2,
|
||||
"Movies": expected_movie_watched_list_2,
|
||||
}
|
||||
}
|
||||
|
||||
user_mapping = { "user1": "user2" }
|
||||
library_mapping = { "TV Shows": "Shows" }
|
||||
user_mapping = {"user1": "user2"}
|
||||
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_2 = cleanup_watched(user_watched_list_2, user_watched_list_1, user_mapping=user_mapping, library_mapping=library_mapping)
|
||||
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_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_2 == expected_watched_list_2
|
||||
|
||||
Reference in New Issue
Block a user