Merge pull request #11 from luigi311/dev
Fix plex login, Match plex marking logic to jellyfinpull/26/head v1.0.0
commit
a6f95c7b2b
31
.env.sample
31
.env.sample
|
|
@ -1,33 +1,38 @@
|
|||
# Do not mark any shows/movies as played and instead just output to log if they would of been marked.
|
||||
## Do not mark any shows/movies as played and instead just output to log if they would of been marked.
|
||||
DRYRUN = "True"
|
||||
# Additional logging information
|
||||
## Additional logging information
|
||||
DEBUG = "True"
|
||||
# How often to run the script in seconds
|
||||
## How often to run the script in seconds
|
||||
SLEEP_DURATION = "3600"
|
||||
# Log file where all output will be written to
|
||||
## Log file where all output will be written to
|
||||
LOGFILE = "log.log"
|
||||
# Map usernames between plex and jellyfin in the event that they are different, order does not matter
|
||||
## Map usernames between plex and jellyfin in the event that they are different, order does not matter
|
||||
#USER_MAPPING = { "testuser2": "testuser3" }
|
||||
# Map libraries between plex and jellyfin in the even that they are different, order does not matter
|
||||
## Map libraries between plex and jellyfin in the even that they are different, order does not matter
|
||||
#LIBRARY_MAPPING = { "Shows": "TV Shows" }
|
||||
|
||||
# URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
||||
|
||||
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
|
||||
## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
||||
PLEX_BASEURL = "http://localhost:32400"
|
||||
# Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||
PLEX_TOKEN = "SuperSecretToken"
|
||||
# If not using plex token then use username and password of the server admin
|
||||
## If not using plex token then use username and password of the server admin along with the servername
|
||||
#PLEX_USERNAME = ""
|
||||
#PLEX_PASSWORD = ""
|
||||
#PLEX_SERVERNAME = "Plex Server"
|
||||
|
||||
# Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||
|
||||
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||
JELLYFIN_BASEURL = "http://localhost:8096"
|
||||
# Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
||||
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
||||
JELLYFIN_TOKEN = "SuperSecretToken"
|
||||
|
||||
# Blacklisting/Whitelisting libraries, library types such as Movies, TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded.
|
||||
|
||||
## Blacklisting/Whitelisting libraries, library types such as Movies/TV Shows, and users. Mappings apply so if the mapping for the user or library exist then both will be excluded.
|
||||
#BLACKLIST_LIBRARY = ""
|
||||
#WHITELIST_LIBRARY = ""
|
||||
#BLACKLIST_LIBRARY_TYPE = ""
|
||||
#WHITELIST_LIBRARY_TYPE = ""
|
||||
#BLACKLIST_USERS = ""
|
||||
WHITELIST_USERS = "testuser1,testuser2"
|
||||
WHITELIST_USERS = "testuser1,testuser2"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
# JellyPlex-Watched
|
||||
|
||||
[](https://www.codacy.com/gh/luigi311/JellyPlex-Watched/dashboard?utm_source=github.com&utm_medium=referral&utm_content=luigi311/JellyPlex-Watched&utm_campaign=Badge_Grade)
|
||||
|
||||
Sync watched between jellyfin and plex
|
||||
|
||||
## Description
|
||||
|
|
|
|||
42
main.py
42
main.py
|
|
@ -10,7 +10,7 @@ load_dotenv(override=True)
|
|||
|
||||
def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_mapping=None):
|
||||
modified_watched_list_1 = copy.deepcopy(watched_list_1)
|
||||
|
||||
|
||||
# remove entries from plex_watched that are in jellyfin_watched
|
||||
for user_1 in watched_list_1:
|
||||
user_other = None
|
||||
|
|
@ -47,7 +47,7 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
|
|||
if watch_list_1_key == watch_list_2_item_key and watch_list_1_value == watch_list_2_item_value:
|
||||
if item in modified_watched_list_1[user_1][library_1]:
|
||||
modified_watched_list_1[user_1][library_1].remove(item)
|
||||
|
||||
|
||||
# TV Shows
|
||||
elif isinstance(watched_list_1[user_1][library_1], dict):
|
||||
if item in watched_list_2[user_2][library_2]:
|
||||
|
|
@ -60,22 +60,22 @@ def cleanup_watched(watched_list_1, watched_list_2, user_mapping=None, library_m
|
|||
if watch_list_1_episode_key == watch_list_2_episode_key and watch_list_1_episode_value == watch_list_2_episode_value:
|
||||
if episode in modified_watched_list_1[user_1][library_1][item][season]:
|
||||
modified_watched_list_1[user_1][library_1][item][season].remove(episode)
|
||||
|
||||
|
||||
# If season is empty, remove season
|
||||
if len(modified_watched_list_1[user_1][library_1][item][season]) == 0:
|
||||
if season in modified_watched_list_1[user_1][library_1][item]:
|
||||
del modified_watched_list_1[user_1][library_1][item][season]
|
||||
|
||||
# If the show is empty, remove the show
|
||||
# If the show is empty, remove the show
|
||||
if len(modified_watched_list_1[user_1][library_1][item]) == 0:
|
||||
if item in modified_watched_list_1[user_1][library_1]:
|
||||
del modified_watched_list_1[user_1][library_1][item]
|
||||
|
||||
|
||||
# If library is empty then remove it
|
||||
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
||||
if library_1 in modified_watched_list_1[user_1]:
|
||||
del modified_watched_list_1[user_1][library_1]
|
||||
|
||||
|
||||
# If user is empty delete user
|
||||
if len(modified_watched_list_1[user_1]) == 0:
|
||||
del modified_watched_list_1[user_1]
|
||||
|
|
@ -95,10 +95,10 @@ def setup_black_white_lists(library_mapping=None):
|
|||
if library_other:
|
||||
temp_library.append(library_other)
|
||||
|
||||
blacklist_library = blacklist_library + temp_library
|
||||
blacklist_library = blacklist_library + temp_library
|
||||
else:
|
||||
blacklist_library = []
|
||||
|
||||
|
||||
logger(f"Blacklist Library: {blacklist_library}", 1)
|
||||
|
||||
whitelist_library = os.getenv("WHITELIST_LIBRARY")
|
||||
|
|
@ -140,11 +140,11 @@ def setup_black_white_lists(library_mapping=None):
|
|||
if blacklist_users:
|
||||
if len(blacklist_users) > 0:
|
||||
blacklist_users = blacklist_users.split(",")
|
||||
blacklist_users = [x.lower().strip() for x in blacklist_users]
|
||||
blacklist_users = [x.lower().strip() for x in blacklist_users]
|
||||
else:
|
||||
blacklist_users = []
|
||||
logger(f"Blacklist Users: {blacklist_users}", 1)
|
||||
|
||||
|
||||
whitelist_users = os.getenv("WHITELIST_USERS")
|
||||
if whitelist_users:
|
||||
if len(whitelist_users) > 0:
|
||||
|
|
@ -159,11 +159,11 @@ def setup_black_white_lists(library_mapping=None):
|
|||
return blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, blacklist_users, whitelist_users
|
||||
|
||||
def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=None):
|
||||
|
||||
|
||||
# generate list of users from plex.users
|
||||
plex_users = [ x.title.lower() for x in plex.users ]
|
||||
jellyfin_users = [ key.lower() for key in jellyfin.users.keys() ]
|
||||
|
||||
|
||||
# combined list of overlapping users from plex and jellyfin
|
||||
users = {}
|
||||
|
||||
|
|
@ -173,10 +173,10 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N
|
|||
if jellyfin_plex_mapped_user:
|
||||
users[plex_user] = jellyfin_plex_mapped_user
|
||||
continue
|
||||
|
||||
|
||||
if plex_user in jellyfin_users:
|
||||
users[plex_user] = plex_user
|
||||
|
||||
|
||||
for jellyfin_user in jellyfin_users:
|
||||
if user_mapping:
|
||||
plex_jellyfin_mapped_user = search_mapping(user_mapping, jellyfin_user)
|
||||
|
|
@ -186,9 +186,9 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N
|
|||
|
||||
if jellyfin_user in plex_users:
|
||||
users[jellyfin_user] = jellyfin_user
|
||||
|
||||
|
||||
logger(f"User list that exist on both servers {users}", 1)
|
||||
|
||||
|
||||
users_filtered = {}
|
||||
for user in users:
|
||||
# whitelist_user is not empty and user lowercase is not in whitelist lowercase
|
||||
|
|
@ -196,17 +196,17 @@ def setup_users(plex, jellyfin, blacklist_users, whitelist_users, user_mapping=N
|
|||
if user not in whitelist_users and users[user] not in whitelist_users:
|
||||
logger(f"{user} or {users[user]} is not in whitelist", 1)
|
||||
continue
|
||||
|
||||
|
||||
if user not in blacklist_users and users[user] not in blacklist_users:
|
||||
users_filtered[user] = users[user]
|
||||
|
||||
logger(f"Filtered user list {users_filtered}", 1)
|
||||
|
||||
|
||||
plex_users = []
|
||||
for plex_user in plex.users:
|
||||
if plex_user.title.lower() in users_filtered.keys() or plex_user.title.lower() in users_filtered.values():
|
||||
plex_users.append(plex_user)
|
||||
|
||||
|
||||
jellyfin_users = {}
|
||||
for jellyfin_user, jellyfin_id in jellyfin.users.items():
|
||||
if jellyfin_user.lower() in users_filtered.keys() or jellyfin_user.lower() in users_filtered.values():
|
||||
|
|
@ -267,11 +267,11 @@ def main():
|
|||
# Update watched status
|
||||
plex.update_watched(jellyfin_watched, user_mapping, library_mapping, dryrun)
|
||||
jellyfin.update_watched(plex_watched, user_mapping, library_mapping, dryrun)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sleep_timer = float(os.getenv("SLEEP_TIMER", "3600"))
|
||||
|
||||
|
||||
while(True):
|
||||
try:
|
||||
main()
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ def check_skip_logic(library_title, library_type, blacklist_library, whitelist_l
|
|||
if len(whitelist_library) > 0:
|
||||
if library_title.lower() not in [x.lower() for x in whitelist_library]:
|
||||
skip_reason = "is not whitelist_library"
|
||||
|
||||
|
||||
if library_other:
|
||||
if library_other.lower() not in [x.lower() for x in whitelist_library]:
|
||||
skip_reason = "is not whitelist_library"
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Jellyfin():
|
|||
|
||||
if not self.baseurl:
|
||||
raise Exception("Jellyfin baseurl not set")
|
||||
|
||||
|
||||
if not self.token:
|
||||
raise Exception("Jellyfin token not set")
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ class Jellyfin():
|
|||
|
||||
if query_type == "get":
|
||||
response = requests.get(self.baseurl + query, headers={"accept":"application/json", "X-Emby-Token": self.token})
|
||||
|
||||
|
||||
elif query_type == "post":
|
||||
authorization = (
|
||||
'MediaBrowser , '
|
||||
|
|
@ -42,19 +42,19 @@ class Jellyfin():
|
|||
except Exception as e:
|
||||
logger(e, 2)
|
||||
logger(response, 2)
|
||||
|
||||
|
||||
def get_users(self):
|
||||
users = {}
|
||||
|
||||
query = "/Users"
|
||||
response = self.query(query, "get")
|
||||
|
||||
|
||||
# If reponse is not empty
|
||||
if response:
|
||||
for user in response:
|
||||
users[user["Name"]] = user["Id"]
|
||||
|
||||
return users
|
||||
return users
|
||||
|
||||
def get_jellyfin_watched(self, users, blacklist_library, whitelist_library, blacklist_library_type, whitelist_library_type, library_mapping=None):
|
||||
users_watched = {}
|
||||
|
|
@ -64,12 +64,12 @@ class Jellyfin():
|
|||
user_name = user_name.lower()
|
||||
|
||||
libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||
|
||||
|
||||
for library in libraries:
|
||||
library_title = library["Name"]
|
||||
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")
|
||||
|
||||
|
||||
if len(watched["Items"]) == 0:
|
||||
logger(f"Jellyfin: No watched items found in library {library_title}", 1)
|
||||
continue
|
||||
|
|
@ -123,7 +123,7 @@ class Jellyfin():
|
|||
# Lowercase episode["ProviderIds"] keys
|
||||
episode["ProviderIds"] = {k.lower(): v for k, v in episode["ProviderIds"].items()}
|
||||
users_watched[user_name][library_title][show["Name"]][season["Name"]].append(episode["ProviderIds"])
|
||||
|
||||
|
||||
return users_watched
|
||||
|
||||
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
||||
|
|
@ -135,7 +135,7 @@ class Jellyfin():
|
|||
user_other = user_mapping[user]
|
||||
elif user in user_mapping.values():
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
|
||||
|
||||
if user_other:
|
||||
logger(f"Swapping user {user} with {user_other}", 1)
|
||||
user = user_other
|
||||
|
|
@ -145,13 +145,13 @@ class Jellyfin():
|
|||
if user.lower() == key.lower():
|
||||
user_id = self.users[key]
|
||||
break
|
||||
|
||||
|
||||
if not user_id:
|
||||
logger(f"{user} not found in Jellyfin", 2)
|
||||
break
|
||||
|
||||
|
||||
jellyfin_libraries = self.query(f"/Users/{user_id}/Views", "get")["Items"]
|
||||
|
||||
|
||||
for library, videos in libraries.items():
|
||||
if library_mapping:
|
||||
library_other = None
|
||||
|
|
@ -160,7 +160,7 @@ class Jellyfin():
|
|||
library_other = library_mapping[library]
|
||||
elif library in library_mapping.values():
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
|
||||
|
||||
if library_other:
|
||||
logger(f"Swapping library {library} with {library_other}", 1)
|
||||
library = library_other
|
||||
|
|
@ -174,7 +174,7 @@ class Jellyfin():
|
|||
if jellyfin_library["Name"] == library:
|
||||
library_id = jellyfin_library["Id"]
|
||||
continue
|
||||
|
||||
|
||||
if library_id:
|
||||
logger(f"Jellyfin: Updating watched for {user} in library {library}", 1)
|
||||
library_search = self.query(f"/Users/{user_id}/Items?SortBy=SortName&SortOrder=Ascending&Recursive=true&ParentId={library_id}&limit=1", "get")
|
||||
|
|
@ -196,7 +196,7 @@ class Jellyfin():
|
|||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
break
|
||||
|
||||
|
||||
# TV Shows
|
||||
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")
|
||||
|
|
|
|||
66
src/plex.py
66
src/plex.py
|
|
@ -11,6 +11,7 @@ plex_baseurl = os.getenv("PLEX_BASEURL")
|
|||
plex_token = os.getenv("PLEX_TOKEN")
|
||||
username = os.getenv("PLEX_USERNAME")
|
||||
password = os.getenv("PLEX_PASSWORD")
|
||||
servername = os.getenv("PLEX_SERVERNAME")
|
||||
|
||||
# class plex accept base url and token and username and password but default with none
|
||||
class Plex:
|
||||
|
|
@ -19,32 +20,39 @@ class Plex:
|
|||
self.token = plex_token
|
||||
self.username = username
|
||||
self.password = password
|
||||
self.servername = servername
|
||||
self.plex = self.plex_login()
|
||||
self.admin_user = self.plex.myPlexAccount()
|
||||
self.users = self.get_plex_users()
|
||||
|
||||
def plex_login(self):
|
||||
if self.baseurl:
|
||||
if self.token:
|
||||
# Login via token
|
||||
plex = PlexServer(self.baseurl, self.token)
|
||||
elif self.username and self.password:
|
||||
try:
|
||||
if self.baseurl and self.token:
|
||||
# Login via token
|
||||
plex = PlexServer(self.baseurl, self.token)
|
||||
elif self.username and self.password and self.servername:
|
||||
# Login via plex account
|
||||
account = MyPlexAccount(self.username, self.password)
|
||||
plex = account.resource(self.baseurl).connect()
|
||||
plex = account.resource(self.servername).connect()
|
||||
else:
|
||||
raise Exception("No plex credentials provided")
|
||||
else:
|
||||
raise Exception("No plex baseurl provided")
|
||||
raise Exception("No complete plex credentials provided")
|
||||
|
||||
return plex
|
||||
except Exception as e:
|
||||
if self.username or self.password:
|
||||
msg = f"Failed to login via plex account {self.username}"
|
||||
logger(f"Plex: Failed to login, {msg}, Error: {e}", 2)
|
||||
else:
|
||||
logger(f"Plex: Failed to login, Error: {e}", 2)
|
||||
return None
|
||||
|
||||
return plex
|
||||
|
||||
def get_plex_users(self):
|
||||
users = self.plex.myPlexAccount().users()
|
||||
|
||||
|
||||
# append self to users
|
||||
users.append(self.plex.myPlexAccount())
|
||||
|
||||
|
||||
return users
|
||||
|
||||
def get_plex_user_watched(self, user, library):
|
||||
|
|
@ -52,9 +60,9 @@ class Plex:
|
|||
user_plex = self.plex
|
||||
else:
|
||||
user_plex = PlexServer(self.baseurl, user.get_token(self.plex.machineIdentifier))
|
||||
|
||||
|
||||
watched = None
|
||||
|
||||
|
||||
if library.type == "movie":
|
||||
watched = []
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
|
|
@ -73,22 +81,22 @@ class Plex:
|
|||
for season in show.seasons():
|
||||
guids = []
|
||||
for episode in season.episodes():
|
||||
if episode.viewCount > 0:
|
||||
guids_temp = {}
|
||||
if episode.viewCount > 0:
|
||||
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)
|
||||
guids_temp[guid_source] = guid_id
|
||||
|
||||
guids.append(guids_temp)
|
||||
|
||||
|
||||
guids.append(guids_temp)
|
||||
|
||||
if guids:
|
||||
# append show, season, episode
|
||||
if show.title not in watched:
|
||||
watched[show.title] = {}
|
||||
if season.title not in watched[show.title]:
|
||||
watched[show.title][season.title] = {}
|
||||
watched[show.title][season.title] = {}
|
||||
watched[show.title][season.title] = guids
|
||||
|
||||
return watched
|
||||
|
|
@ -108,7 +116,7 @@ class Plex:
|
|||
if skip_reason:
|
||||
logger(f"Plex: Skipping library {library_title} {skip_reason}", 1)
|
||||
continue
|
||||
|
||||
|
||||
for user in users:
|
||||
logger(f"Plex: Generating watched for {user.title} in library {library_title}", 0)
|
||||
user_name = user.title.lower()
|
||||
|
|
@ -119,9 +127,9 @@ class Plex:
|
|||
if library_title not in users_watched[user_name]:
|
||||
users_watched[user_name][library_title] = []
|
||||
users_watched[user_name][library_title] = watched
|
||||
|
||||
|
||||
return users_watched
|
||||
|
||||
|
||||
def update_watched(self, watched_list, user_mapping=None, library_mapping=None, dryrun=False):
|
||||
for user, libraries in watched_list.items():
|
||||
if user_mapping:
|
||||
|
|
@ -131,7 +139,7 @@ class Plex:
|
|||
user_other = user_mapping[user]
|
||||
elif user in user_mapping.values():
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
|
||||
|
||||
if user_other:
|
||||
logger(f"Swapping user {user} with {user_other}", 1)
|
||||
user = user_other
|
||||
|
|
@ -154,7 +162,7 @@ class Plex:
|
|||
library_other = library_mapping[library]
|
||||
elif library in library_mapping.values():
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
|
||||
|
||||
if library_other:
|
||||
logger(f"Swapping library {library} with {library_other}", 1)
|
||||
library = library_other
|
||||
|
|
@ -184,7 +192,7 @@ class Plex:
|
|||
else:
|
||||
logger(f"Dryrun {msg}", 0)
|
||||
break
|
||||
|
||||
|
||||
elif library_videos.type == "show":
|
||||
for show_search in library_videos.search(unmatched=False, unwatched=True):
|
||||
if show_search.title in videos:
|
||||
|
|
@ -193,9 +201,9 @@ class Plex:
|
|||
for guid in episode_search.guids:
|
||||
guid_source = re.search(r'(.*)://', guid.id).group(1).lower()
|
||||
guid_id = re.search(r'://(.*)', guid.id).group(1)
|
||||
for show, seasons in videos.items():
|
||||
for season, episodes in seasons.items():
|
||||
for episode in episodes:
|
||||
for show in videos:
|
||||
for season in videos[show]:
|
||||
for episode in videos[show][season]:
|
||||
for episode_keys, episode_id in episode.items():
|
||||
if episode_keys == guid_source and episode_id == guid_id:
|
||||
if episode_search.viewCount == 0:
|
||||
|
|
|
|||
Loading…
Reference in New Issue