commit
04a8da6478
|
|
@ -2,6 +2,8 @@
|
||||||
DRYRUN = "True"
|
DRYRUN = "True"
|
||||||
## Additional logging information
|
## Additional logging information
|
||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
## Debugging level, INFO is default, DEBUG is more verbose
|
||||||
|
DEBUG_LEVEL = "INFO"
|
||||||
## How often to run the script in seconds
|
## How often to run the script in seconds
|
||||||
SLEEP_DURATION = "3600"
|
SLEEP_DURATION = "3600"
|
||||||
## Log file where all output will be written to
|
## Log file where all output will be written to
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"name": "Python",
|
||||||
|
"type": "python",
|
||||||
|
"request": "launch",
|
||||||
|
"program": "main.py",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"justMyCode": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -10,7 +10,7 @@ Keep in sync all your users watched history between jellyfin and plex locally. T
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Baremeta
|
### Baremetal
|
||||||
|
|
||||||
- Setup virtualenv of your choice
|
- Setup virtualenv of your choice
|
||||||
|
|
||||||
|
|
|
||||||
87
main.py
87
main.py
|
|
@ -2,7 +2,7 @@ import copy, os, traceback, json
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
|
||||||
from src.functions import logger, str_to_bool, search_mapping
|
from src.functions import logger, str_to_bool, search_mapping, generate_library_guids_dict
|
||||||
from src.plex import Plex
|
from src.plex import Plex
|
||||||
from src.jellyfin import Jellyfin
|
from src.jellyfin import Jellyfin
|
||||||
|
|
||||||
|
|
@ -35,49 +35,59 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
|
||||||
elif library_other in watched_list_2[user_2]:
|
elif library_other in watched_list_2[user_2]:
|
||||||
library_2 = library_other
|
library_2 = library_other
|
||||||
else:
|
else:
|
||||||
logger(f"User {library_1} and {library_other} not found in watched list 2", 1)
|
logger(f"library {library_1} and {library_other} not found in watched list 2", 1)
|
||||||
continue
|
continue
|
||||||
for item in watched_list_1[user_1][library_1]:
|
|
||||||
if item in modified_watched_list_1[user_1][library_1]:
|
|
||||||
# Movies
|
|
||||||
if isinstance(watched_list_1[user_1][library_1], list):
|
|
||||||
for watch_list_1_key, watch_list_1_value in item.items():
|
|
||||||
for watch_list_2_item in watched_list_2[user_2][library_2]:
|
|
||||||
for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items():
|
|
||||||
if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value:
|
|
||||||
if item in modified_watched_list_1[user_1][library_1]:
|
|
||||||
modified_watched_list_1[user_1][library_1].remove(item)
|
|
||||||
|
|
||||||
# TV Shows
|
# Movies
|
||||||
elif isinstance(watched_list_1[user_1][library_1], dict):
|
if isinstance(watched_list_1[user_1][library_1], list):
|
||||||
if item in watched_list_2[user_2][library_2]:
|
for item in watched_list_1[user_1][library_1]:
|
||||||
for season in watched_list_1[user_1][library_1][item]:
|
for watch_list_1_key, watch_list_1_value in item.items():
|
||||||
if season in watched_list_2[user_2][library_2][item]:
|
for watch_list_2_item in watched_list_2[user_2][library_2]:
|
||||||
for episode in watched_list_1[user_1][library_1][item][season]:
|
for watch_list_2_item_key, watch_list_2_item_value in watch_list_2_item.items():
|
||||||
for watch_list_1_episode_key, watch_list_1_episode_value in episode.items():
|
if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value:
|
||||||
for watch_list_2_episode in watched_list_2[user_2][library_2][item][season]:
|
if item in modified_watched_list_1[user_1][library_1]:
|
||||||
for watch_list_2_episode_key, watch_list_2_episode_value in watch_list_2_episode.items():
|
logger(f"Removing {item} from {library_1}", 3)
|
||||||
if watch_list_1_episode_key == watch_list_2_episode_key and watch_list_1_episode_value == watch_list_2_episode_value:
|
modified_watched_list_1[user_1][library_1].remove(item)
|
||||||
if episode in modified_watched_list_1[user_1][library_1][item][season]:
|
|
||||||
modified_watched_list_1[user_1][library_1][item][season].remove(episode)
|
|
||||||
|
|
||||||
# If season is empty, remove season
|
|
||||||
if len(modified_watched_list_1[user_1][library_1][item][season]) == 0:
|
|
||||||
if season in modified_watched_list_1[user_1][library_1][item]:
|
|
||||||
del modified_watched_list_1[user_1][library_1][item][season]
|
|
||||||
|
|
||||||
# If the show is empty, remove the show
|
# TV Shows
|
||||||
if len(modified_watched_list_1[user_1][library_1][item]) == 0:
|
elif isinstance(watched_list_1[user_1][library_1], dict):
|
||||||
if item in modified_watched_list_1[user_1][library_1]:
|
# Generate full list of provider ids for episodes in watch_list_2 to easily compare if they exist in watch_list_1
|
||||||
del modified_watched_list_1[user_1][library_1][item]
|
_, episode_watched_list_2_keys_dict, _ = generate_library_guids_dict(watched_list_2[user_2][library_2], 1)
|
||||||
|
|
||||||
# If library is empty then remove it
|
for show_key_1 in watched_list_1[user_1][library_1].keys():
|
||||||
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
show_key_dict = dict(show_key_1)
|
||||||
if library_1 in modified_watched_list_1[user_1]:
|
for season in watched_list_1[user_1][library_1][show_key_1]:
|
||||||
del modified_watched_list_1[user_1][library_1]
|
for episode in watched_list_1[user_1][library_1][show_key_1][season]:
|
||||||
|
for episode_key, episode_item in episode.items():
|
||||||
|
# If episode_key and episode_item are in episode_watched_list_2_keys_dict exactly, then remove from watch_list_1
|
||||||
|
if episode_key in episode_watched_list_2_keys_dict.keys():
|
||||||
|
if episode_item in episode_watched_list_2_keys_dict[episode_key]:
|
||||||
|
if episode in modified_watched_list_1[user_1][library_1][show_key_1][season]:
|
||||||
|
logger(f"Removing {show_key_dict['title']} {episode} from {library_1}", 3)
|
||||||
|
modified_watched_list_1[user_1][library_1][show_key_1][season].remove(episode)
|
||||||
|
|
||||||
|
# Remove empty seasons
|
||||||
|
if len(modified_watched_list_1[user_1][library_1][show_key_1][season]) == 0:
|
||||||
|
if season in modified_watched_list_1[user_1][library_1][show_key_1]:
|
||||||
|
logger(f"Removing {season} from {library_1} because it is empty", 3)
|
||||||
|
del modified_watched_list_1[user_1][library_1][show_key_1][season]
|
||||||
|
|
||||||
|
# If the show is empty, remove the show
|
||||||
|
if len(modified_watched_list_1[user_1][library_1][show_key_1]) == 0:
|
||||||
|
if show_key_1 in modified_watched_list_1[user_1][library_1]:
|
||||||
|
logger(f"Removing {show_key_dict['title']} from {library_1} because it is empty", 1)
|
||||||
|
del modified_watched_list_1[user_1][library_1][show_key_1]
|
||||||
|
|
||||||
|
# If library is empty then remove it
|
||||||
|
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
||||||
|
if library_1 in modified_watched_list_1[user_1]:
|
||||||
|
logger(f"Removing {library_1} from {user_1} because it is empty", 1)
|
||||||
|
del modified_watched_list_1[user_1][library_1]
|
||||||
|
|
||||||
# If user is empty delete user
|
# If user is empty delete user
|
||||||
if len(modified_watched_list_1[user_1]) == 0:
|
if len(modified_watched_list_1[user_1]) == 0:
|
||||||
|
logger(f"Removing {user_1} from watched list 1 because it is empty", 1)
|
||||||
del modified_watched_list_1[user_1]
|
del modified_watched_list_1[user_1]
|
||||||
|
|
||||||
return modified_watched_list_1
|
return modified_watched_list_1
|
||||||
|
|
@ -258,10 +268,13 @@ def main():
|
||||||
plex_watched_filtered = copy.deepcopy(plex_watched)
|
plex_watched_filtered = copy.deepcopy(plex_watched)
|
||||||
jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched)
|
jellyfin_watched_filtered = copy.deepcopy(jellyfin_watched)
|
||||||
|
|
||||||
|
logger("Cleaning Plex Watched", 1)
|
||||||
plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered, user_mapping, library_mapping)
|
plex_watched = cleanup_watched(plex_watched_filtered, jellyfin_watched_filtered, user_mapping, library_mapping)
|
||||||
logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1)
|
|
||||||
|
|
||||||
|
logger("Cleaning Jellyfin Watched", 1)
|
||||||
jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered, user_mapping, library_mapping)
|
jellyfin_watched = cleanup_watched(jellyfin_watched_filtered, plex_watched_filtered, user_mapping, library_mapping)
|
||||||
|
|
||||||
|
logger(f"plex_watched that needs to be synced to jellyfin:\n{plex_watched}", 1)
|
||||||
logger(f"jellyfin_watched that needs to be synced to plex:\n{jellyfin_watched}", 1)
|
logger(f"jellyfin_watched that needs to be synced to plex:\n{jellyfin_watched}", 1)
|
||||||
|
|
||||||
# Update watched status
|
# Update watched status
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,17 @@ logfile = os.getenv("LOGFILE","log.log")
|
||||||
|
|
||||||
def logger(message, log_type=0):
|
def logger(message, log_type=0):
|
||||||
debug = str_to_bool(os.getenv("DEBUG", "True"))
|
debug = str_to_bool(os.getenv("DEBUG", "True"))
|
||||||
|
debug_level = os.getenv("DEBUG_LEVEL", "INFO")
|
||||||
|
|
||||||
output = str(message)
|
output = str(message)
|
||||||
if log_type == 0:
|
if log_type == 0:
|
||||||
pass
|
pass
|
||||||
elif log_type == 1 and debug:
|
elif log_type == 1 and (debug or debug_level == "INFO"):
|
||||||
output = f"[INFO]: {output}"
|
output = f"[INFO]: {output}"
|
||||||
elif log_type == 2:
|
elif log_type == 2:
|
||||||
output = f"[ERROR]: {output}"
|
output = f"[ERROR]: {output}"
|
||||||
|
elif log_type == 3 and (debug and debug_level == "DEBUG"):
|
||||||
|
output = f"[DEBUG]: {output}"
|
||||||
else:
|
else:
|
||||||
output = None
|
output = None
|
||||||
|
|
||||||
|
|
@ -73,3 +76,41 @@ def check_skip_logic(library_title, library_type, blacklist_library, whitelist_l
|
||||||
skip_reason = "is not whitelist_library"
|
skip_reason = "is not whitelist_library"
|
||||||
|
|
||||||
return skip_reason
|
return skip_reason
|
||||||
|
|
||||||
|
|
||||||
|
def generate_library_guids_dict(user_list: dict, generate_output: int):
|
||||||
|
# if generate_output is 0 then only generate shows, if 1 then only generate episodes, if 2 then generate movies, if 3 then generate shows and episodes
|
||||||
|
show_output_dict = {}
|
||||||
|
episode_output_dict = {}
|
||||||
|
movies_output_dict = {}
|
||||||
|
|
||||||
|
if generate_output in (0, 3):
|
||||||
|
show_output_keys = user_list.keys()
|
||||||
|
show_output_keys = ([ dict(x) for x in list(show_output_keys) ])
|
||||||
|
for show_key in show_output_keys:
|
||||||
|
for provider_key, prvider_value in show_key.items():
|
||||||
|
# Skip title
|
||||||
|
if provider_key.lower() == "title":
|
||||||
|
continue
|
||||||
|
if provider_key.lower() not in show_output_dict:
|
||||||
|
show_output_dict[provider_key.lower()] = []
|
||||||
|
show_output_dict[provider_key.lower()].append(prvider_value.lower())
|
||||||
|
|
||||||
|
if generate_output in (1, 3):
|
||||||
|
for show in user_list:
|
||||||
|
for season in user_list[show]:
|
||||||
|
for episode in user_list[show][season]:
|
||||||
|
for episode_key, episode_value in episode.items():
|
||||||
|
if episode_key.lower() not in episode_output_dict:
|
||||||
|
episode_output_dict[episode_key.lower()] = []
|
||||||
|
episode_output_dict[episode_key.lower()].append(episode_value.lower())
|
||||||
|
|
||||||
|
if generate_output == 2:
|
||||||
|
for movie in user_list:
|
||||||
|
for movie_key, movie_value in movie.items():
|
||||||
|
if movie_key.lower() not in movies_output_dict:
|
||||||
|
movies_output_dict[movie_key.lower()] = []
|
||||||
|
movies_output_dict[movie_key.lower()].append(movie_value.lower())
|
||||||
|
|
||||||
|
return show_output_dict, episode_output_dict, movies_output_dict
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import requests, os
|
import requests, os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from src.functions import logger, search_mapping, str_to_bool, check_skip_logic
|
from src.functions import logger, search_mapping, str_to_bool, check_skip_logic, generate_library_guids_dict
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
@ -99,11 +99,14 @@ class Jellyfin():
|
||||||
|
|
||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type == "Episode":
|
if library_type == "Episode":
|
||||||
watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}", "get")
|
watched = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"]
|
watched_shows = [x for x in watched["Items"] if x["Type"] == "Series"]
|
||||||
|
|
||||||
for show in watched_shows:
|
for show in watched_shows:
|
||||||
seasons = self.query(f"/Shows/{show['Id']}/Seasons?userId={user_id}&Fields=ItemCounts", "get")
|
show_guids = {k.lower(): v for k, v in show["ProviderIds"].items()}
|
||||||
|
show_guids["title"] = show["Name"]
|
||||||
|
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:
|
if len(seasons["Items"]) > 0:
|
||||||
for season in seasons["Items"]:
|
for season in seasons["Items"]:
|
||||||
episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
episodes = self.query(f"/Shows/{show['Id']}/Episodes?seasonId={season['Id']}&userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
|
|
@ -115,14 +118,14 @@ class Jellyfin():
|
||||||
users_watched[user_name] = {}
|
users_watched[user_name] = {}
|
||||||
if library_title not in users_watched[user_name]:
|
if library_title not in users_watched[user_name]:
|
||||||
users_watched[user_name][library_title] = {}
|
users_watched[user_name][library_title] = {}
|
||||||
if show["Name"] not in users_watched[user_name][library_title]:
|
if show_guids not in users_watched[user_name][library_title]:
|
||||||
users_watched[user_name][library_title][show["Name"]] = {}
|
users_watched[user_name][library_title][show_guids] = {}
|
||||||
if season["Name"] not in users_watched[user_name][library_title][show["Name"]]:
|
if season["Name"] not in users_watched[user_name][library_title][show_guids]:
|
||||||
users_watched[user_name][library_title][show["Name"]][season["Name"]] = []
|
users_watched[user_name][library_title][show_guids][season["Name"]] = []
|
||||||
|
|
||||||
# Lowercase episode["ProviderIds"] keys
|
# Lowercase episode["ProviderIds"] keys
|
||||||
episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
||||||
users_watched[user_name][library_title][show["Name"]][season["Name"]].append(episode["ProviderIds"])
|
users_watched[user_name][library_title][show_guids][season["Name"]].append(episode["ProviderIds"])
|
||||||
|
|
||||||
return users_watched
|
return users_watched
|
||||||
|
|
||||||
|
|
@ -141,7 +144,7 @@ class Jellyfin():
|
||||||
user = user_other
|
user = user_other
|
||||||
|
|
||||||
user_id = None
|
user_id = None
|
||||||
for key, value in self.users.items():
|
for key in self.users.keys():
|
||||||
if user.lower() == key.lower():
|
if user.lower() == key.lower():
|
||||||
user_id = self.users[key]
|
user_id = self.users[key]
|
||||||
break
|
break
|
||||||
|
|
@ -182,13 +185,16 @@ class Jellyfin():
|
||||||
|
|
||||||
# Movies
|
# Movies
|
||||||
if library_type == "Movie":
|
if library_type == "Movie":
|
||||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
||||||
|
|
||||||
|
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
||||||
for jellyfin_video in jellyfin_search["Items"]:
|
for jellyfin_video in jellyfin_search["Items"]:
|
||||||
if str_to_bool(jellyfin_video["UserData"]["Played"]) == False:
|
if str_to_bool(jellyfin_video["UserData"]["Played"]) == False:
|
||||||
jellyfin_video_id = jellyfin_video["Id"]
|
jellyfin_video_id = jellyfin_video["Id"]
|
||||||
for video in videos:
|
|
||||||
for key, value in jellyfin_video["ProviderIds"].items():
|
for movie_provider_source, movie_provider_id in jellyfin_video["ProviderIds"].items():
|
||||||
if key.lower() in video.keys() and value.lower() == video[key.lower()].lower():
|
if movie_provider_source.lower() in videos_movies_ids:
|
||||||
|
if movie_provider_id.lower() in videos_movies_ids[movie_provider_source.lower()]:
|
||||||
msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin"
|
msg = f"{jellyfin_video['Name']} as watched for {user} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marking {msg}", 0)
|
logger(f"Marking {msg}", 0)
|
||||||
|
|
@ -199,25 +205,33 @@ class Jellyfin():
|
||||||
|
|
||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type == "Episode":
|
if library_type == "Episode":
|
||||||
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&isPlayed=false", "get")
|
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
||||||
jellyfin_shows = [x for x in jellyfin_search["Items"] if x["Type"] == "Series"]
|
|
||||||
|
jellyfin_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=false&ParentId={library_id}&isPlayed=false&Fields=ItemCounts,ProviderIds", "get")
|
||||||
|
jellyfin_shows = [x for x in jellyfin_search["Items"]]
|
||||||
|
|
||||||
for jellyfin_show in jellyfin_shows:
|
for jellyfin_show in jellyfin_shows:
|
||||||
if jellyfin_show["Name"] in videos.keys():
|
show_found = False
|
||||||
jellyfin_show_id = jellyfin_show["Id"]
|
for show_provider_source, show_provider_id in jellyfin_show["ProviderIds"].items():
|
||||||
jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
if show_provider_source.lower() in videos_shows_ids:
|
||||||
for jellyfin_episode in jellyfin_episodes["Items"]:
|
if show_provider_id.lower() in videos_shows_ids[show_provider_source.lower()]:
|
||||||
if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False:
|
show_found = True
|
||||||
jellyfin_episode_id = jellyfin_episode["Id"]
|
jellyfin_show_id = jellyfin_show["Id"]
|
||||||
for show in videos:
|
jellyfin_episodes = self.query(f"/Shows/{jellyfin_show_id}/Episodes?userId={user_id}&Fields=ItemCounts,ProviderIds", "get")
|
||||||
for season in videos[show]:
|
for jellyfin_episode in jellyfin_episodes["Items"]:
|
||||||
for episode in videos[show][season]:
|
if str_to_bool(jellyfin_episode["UserData"]["Played"]) == False:
|
||||||
for key, value in jellyfin_episode["ProviderIds"].items():
|
jellyfin_episode_id = jellyfin_episode["Id"]
|
||||||
if key.lower() in episode.keys() and value.lower() == episode[key.lower()].lower():
|
|
||||||
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin"
|
for episode_provider_source, episode_provider_id in jellyfin_episode["ProviderIds"].items():
|
||||||
|
if episode_provider_source.lower() in videos_episode_ids:
|
||||||
|
if episode_provider_id.lower() in videos_episode_ids[episode_provider_source.lower()]:
|
||||||
|
msg = f"{jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode['IndexNumber']} {jellyfin_episode['Name']} as watched for {user} in {library} for Jellyfin"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marked {msg}", 0)
|
logger(f"Marked {msg}", 0)
|
||||||
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post")
|
self.query(f"/Users/{user_id}/PlayedItems/{jellyfin_episode_id}", "post")
|
||||||
else:
|
else:
|
||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if show_found:
|
||||||
|
break
|
||||||
|
|
|
||||||
110
src/plex.py
110
src/plex.py
|
|
@ -1,7 +1,7 @@
|
||||||
import re, os
|
import re, os
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
from src.functions import logger, search_mapping, check_skip_logic
|
from src.functions import logger, search_mapping, check_skip_logic, generate_library_guids_dict
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
from plexapi.myplex import MyPlexAccount
|
from plexapi.myplex import MyPlexAccount
|
||||||
|
|
||||||
|
|
@ -78,26 +78,35 @@ class Plex:
|
||||||
watched = {}
|
watched = {}
|
||||||
library_videos = user_plex.library.section(library.title)
|
library_videos = user_plex.library.section(library.title)
|
||||||
for show in library_videos.search(unmatched=False, unwatched=False):
|
for show in library_videos.search(unmatched=False, unwatched=False):
|
||||||
|
show_guids = {}
|
||||||
|
for show_guid in show.guids:
|
||||||
|
show_guids["title"] = show.title
|
||||||
|
# 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 = frozenset(show_guids.items())
|
||||||
|
|
||||||
for season in show.seasons():
|
for season in show.seasons():
|
||||||
guids = []
|
episode_guids = []
|
||||||
for episode in season.episodes():
|
for episode in season.episodes():
|
||||||
if episode.viewCount > 0:
|
if episode.viewCount > 0:
|
||||||
guids_temp = {}
|
episode_guids_temp = {}
|
||||||
for guid in episode.guids:
|
for guid in episode.guids:
|
||||||
# Extract after :// from guid.id
|
# Extract after :// from guid.id
|
||||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||||
guids_temp[guid_source] = guid_id
|
episode_guids_temp[guid_source] = guid_id
|
||||||
|
|
||||||
guids.append(guids_temp)
|
episode_guids.append(episode_guids_temp)
|
||||||
|
|
||||||
if guids:
|
if episode_guids:
|
||||||
# append show, season, episode
|
# append show, season, episode
|
||||||
if show.title not in watched:
|
if show_guids not in watched:
|
||||||
watched[show.title] = {}
|
watched[show_guids] = {}
|
||||||
if season.title not in watched[show.title]:
|
if season.title not in watched[show_guids]:
|
||||||
watched[show.title][season.title] = {}
|
watched[show_guids][season.title] = {}
|
||||||
watched[show.title][season.title] = guids
|
watched[show_guids][season.title] = episode_guids
|
||||||
|
|
||||||
return watched
|
return watched
|
||||||
|
|
||||||
|
|
@ -177,40 +186,53 @@ class Plex:
|
||||||
library_videos = user_plex.library.section(library)
|
library_videos = user_plex.library.section(library)
|
||||||
|
|
||||||
if library_videos.type == "movie":
|
if library_videos.type == "movie":
|
||||||
|
_, _, videos_movies_ids = generate_library_guids_dict(videos, 2)
|
||||||
for movies_search in library_videos.search(unmatched=False, unwatched=True):
|
for movies_search in library_videos.search(unmatched=False, unwatched=True):
|
||||||
for guid in movies_search.guids:
|
for movie_guid in movies_search.guids:
|
||||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
movie_guid_source = re.search(r'(.*)://', movie_guid.id).group(1).lower()
|
||||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
movie_guid_id = re.search(r'://(.*)', movie_guid.id).group(1)
|
||||||
for video in videos:
|
# If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list
|
||||||
for video_keys, video_id in video.items():
|
if movie_guid_source in videos_movies_ids.keys():
|
||||||
if video_keys == guid_source and video_id == guid_id:
|
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
||||||
if movies_search.viewCount == 0:
|
if movies_search.viewCount == 0:
|
||||||
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
logger(f"Marked {msg}", 0)
|
logger(f"Marked {msg}", 0)
|
||||||
movies_search.markWatched()
|
movies_search.markWatched()
|
||||||
else:
|
else:
|
||||||
logger(f"Dryrun {msg}", 0)
|
logger(f"Dryrun {msg}", 0)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
elif library_videos.type == "show":
|
elif library_videos.type == "show":
|
||||||
|
videos_shows_ids, videos_episode_ids, _ = generate_library_guids_dict(videos, 3)
|
||||||
|
|
||||||
for show_search in library_videos.search(unmatched=False, unwatched=True):
|
for show_search in library_videos.search(unmatched=False, unwatched=True):
|
||||||
if show_search.title in videos:
|
show_found = False
|
||||||
for season_search in show_search.seasons():
|
for show_guid in show_search.guids:
|
||||||
for episode_search in season_search.episodes():
|
show_guid_source = re.search(r'(.*)://', show_guid.id).group(1).lower()
|
||||||
for guid in episode_search.guids:
|
show_guid_id = re.search(r'://(.*)', show_guid.id).group(1)
|
||||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
|
||||||
guid_id = re.search(r'://(.*)', 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
|
||||||
for show in videos:
|
if show_guid_source in videos_shows_ids.keys():
|
||||||
for season in videos[show]:
|
if show_guid_id in videos_shows_ids[show_guid_source]:
|
||||||
for episode in videos[show][season]:
|
show_found = True
|
||||||
for episode_keys, episode_id in episode.items():
|
for episode_search in show_search.episodes():
|
||||||
if episode_keys == guid_source and episode_id == guid_id:
|
for episode_guid in episode_search.guids:
|
||||||
if episode_search.viewCount == 0:
|
episode_guid_source = re.search(r'(.*)://', episode_guid.id).group(1).lower()
|
||||||
msg = f"{show_search.title} {season_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex"
|
episode_guid_id = re.search(r'://(.*)', episode_guid.id).group(1)
|
||||||
if not dryrun:
|
|
||||||
logger(f"Marked {msg}", 0)
|
# If episode provider source and episode provider id are in videos_episode_ids exactly, then the episode is in the list
|
||||||
episode_search.markWatched()
|
if episode_guid_source in videos_episode_ids.keys():
|
||||||
else:
|
if episode_guid_id in videos_episode_ids[episode_guid_source]:
|
||||||
logger(f"Dryrun {msg}", 0)
|
if episode_search.viewCount == 0:
|
||||||
break
|
msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex"
|
||||||
|
if not dryrun:
|
||||||
|
logger(f"Marked {msg}", 0)
|
||||||
|
episode_search.markWatched()
|
||||||
|
else:
|
||||||
|
logger(f"Dryrun {msg}", 0)
|
||||||
|
break
|
||||||
|
|
||||||
|
if show_found:
|
||||||
|
break
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue