Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d1fd61f1d1 | ||
|
|
6c1ee4a7dc | ||
|
|
9a8e799e68 | ||
|
|
ffec4e2f28 | ||
|
|
00102891a5 | ||
|
|
aa76b83428 | ||
|
|
a644189ea5 | ||
|
|
c5d987a8c9 | ||
|
|
bdd68ad68d | ||
|
|
2d86bca781 | ||
|
|
1b01ff6ec2 | ||
|
|
f08ec43507 | ||
|
|
7f9424260a | ||
|
|
5f21943353 | ||
|
|
a5a795f43c | ||
|
|
fcb6d7625f | ||
|
|
fd2179998f | ||
|
|
654e7f20e1 | ||
|
|
1eb92cf7c1 | ||
|
|
111e284cc8 | ||
|
|
1a4e3f4ec4 | ||
|
|
4066228e57 | ||
|
|
59c6d278e3 | ||
|
|
39b33f3d43 | ||
|
|
e8faf52b2b | ||
|
|
370e9bac63 | ||
|
|
d0746cec5a | ||
|
|
251937431b | ||
|
|
50faf061af |
73
.env.sample
73
.env.sample
@@ -1,43 +1,68 @@
|
||||
# Global Settings
|
||||
|
||||
## 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
|
||||
DEBUG = "True"
|
||||
DEBUG = "False"
|
||||
|
||||
## Debugging level, "info" is default, "debug" is more verbose
|
||||
DEBUG_LEVEL = "info"
|
||||
|
||||
## How often to run the script in seconds
|
||||
SLEEP_DURATION = "3600"
|
||||
|
||||
## 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
|
||||
#USER_MAPPING = { "testuser2": "testuser3" }
|
||||
## Map libraries between plex and jellyfin in the even that they are different, order does not matter
|
||||
#LIBRARY_MAPPING = { "Shows": "TV Shows" }
|
||||
|
||||
## Map usernames between servers in the event that they are different, order does not matter
|
||||
## Comma seperated for multiple options
|
||||
#USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
||||
|
||||
## 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
|
||||
## Comma seperated list for multiple servers
|
||||
PLEX_BASEURL = "http://localhost:32400"
|
||||
## 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 along with the servername
|
||||
#PLEX_USERNAME = ""
|
||||
#PLEX_PASSWORD = ""
|
||||
#PLEX_SERVERNAME = "Plex Server"
|
||||
## Skip hostname validation for ssl certificates.
|
||||
SSL_BYPASS = "False"
|
||||
|
||||
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||
## Comma seperated list for multiple servers
|
||||
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_TOKEN = "SuperSecretToken"
|
||||
|
||||
## Map libraries between servers in the even that they are different, order does not matter
|
||||
## Comma seperated for multiple options
|
||||
#LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
||||
|
||||
## 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.
|
||||
## Comma seperated for multiple options
|
||||
#BLACKLIST_LIBRARY = ""
|
||||
#WHITELIST_LIBRARY = ""
|
||||
#BLACKLIST_LIBRARY_TYPE = ""
|
||||
#WHITELIST_LIBRARY_TYPE = ""
|
||||
#BLACKLIST_USERS = ""
|
||||
WHITELIST_USERS = "testuser1,testuser2"
|
||||
|
||||
|
||||
|
||||
# Plex
|
||||
|
||||
## 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
|
||||
## Comma seperated list for multiple servers
|
||||
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
||||
|
||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||
## Comma seperated list for multiple servers
|
||||
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||
|
||||
## If not using plex token then use username and password of the server admin along with the servername
|
||||
## Comma seperated for multiple options
|
||||
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
||||
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
||||
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
||||
|
||||
## Skip hostname validation for ssl certificates.
|
||||
## Set to True if running into ssl certificate errors
|
||||
SSL_BYPASS = "False"
|
||||
|
||||
|
||||
|
||||
# Jellyfin
|
||||
|
||||
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||
## Comma seperated list for multiple servers
|
||||
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
||||
|
||||
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
||||
## Comma seperated list for multiple servers
|
||||
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||
|
||||
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@@ -13,7 +13,7 @@ jobs:
|
||||
pytest:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: "Install dependencies"
|
||||
run: pip install -r requirements.txt && pip install -r test/requirements.txt
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
needs: pytest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: docker_meta
|
||||
@@ -45,14 +45,14 @@ jobs:
|
||||
type=sha
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: "${{ steps.docker_meta.outcome == 'success' }}"
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_TOKEN }}
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
- name: Build
|
||||
id: build
|
||||
if: "${{ steps.docker_meta.outcome == 'skipped' }}"
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
@@ -71,7 +71,7 @@ jobs:
|
||||
- name: Build Push
|
||||
id: build_push
|
||||
if: "${{ steps.docker_meta.outcome == 'success' }}"
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v3
|
||||
with:
|
||||
context: .
|
||||
file: ./Dockerfile
|
||||
|
||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"python.formatting.provider": "black"
|
||||
}
|
||||
@@ -6,16 +6,16 @@ ENV DEBUG_LEVEL 'INFO'
|
||||
ENV SLEEP_DURATION '3600'
|
||||
ENV LOGFILE 'log.log'
|
||||
|
||||
ENV USER_MAPPING '{ "User Test": "User Test2" }'
|
||||
ENV LIBRARY_MAPPING '{ "Shows Test": "TV Shows Test" }'
|
||||
ENV USER_MAPPING ''
|
||||
ENV LIBRARY_MAPPING ''
|
||||
|
||||
ENV PLEX_BASEURL 'http://localhost:32400'
|
||||
ENV PLEX_BASEURL ''
|
||||
ENV PLEX_TOKEN ''
|
||||
ENV PLEX_USERNAME ''
|
||||
ENV PLEX_PASSWORD ''
|
||||
ENV PLEX_SERVERNAME ''
|
||||
|
||||
ENV JELLYFIN_BASEURL 'http://localhost:8096'
|
||||
ENV JELLYFIN_BASEURL ''
|
||||
ENV JELLYFIN_TOKEN ''
|
||||
|
||||
ENV BLACKLIST_LIBRARY ''
|
||||
|
||||
74
README.md
74
README.md
@@ -2,14 +2,84 @@
|
||||
|
||||
[](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
|
||||
Sync watched between jellyfin and plex locally
|
||||
|
||||
## Description
|
||||
|
||||
Keep in sync all your users watched history between jellyfin and plex servers locally. This uses the imdb ids and any other matching id to find the correct episode/movie between the two. This is not perfect but it works for most cases. You can use this for as many servers as you want by enterying multiple options in the .env plex/jellyfin section seperated by commas.
|
||||
Keep in sync all your users watched history between jellyfin and plex servers locally. This uses file names and provider ids to find the correct episode/movie between the two. This is not perfect but it works for most cases. You can use this for as many servers as you want by enterying multiple options in the .env plex/jellyfin section seperated by commas.
|
||||
|
||||
## Configuration
|
||||
|
||||
```bash
|
||||
# Global Settings
|
||||
|
||||
## 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
|
||||
DEBUG = "False"
|
||||
|
||||
## Debugging level, "info" is default, "debug" is more verbose
|
||||
DEBUG_LEVEL = "info"
|
||||
|
||||
## How often to run the script in seconds
|
||||
SLEEP_DURATION = "3600"
|
||||
|
||||
## Log file where all output will be written to
|
||||
LOGFILE = "log.log"
|
||||
|
||||
## Map usernames between servers in the event that they are different, order does not matter
|
||||
## Comma seperated for multiple options
|
||||
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
||||
|
||||
## Map libraries between servers in the even that they are different, order does not matter
|
||||
## Comma seperated for multiple options
|
||||
LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
||||
|
||||
## 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.
|
||||
## Comma seperated for multiple options
|
||||
BLACKLIST_LIBRARY = ""
|
||||
WHITELIST_LIBRARY = ""
|
||||
BLACKLIST_LIBRARY_TYPE = ""
|
||||
WHITELIST_LIBRARY_TYPE = ""
|
||||
BLACKLIST_USERS = ""
|
||||
WHITELIST_USERS = "testuser1,testuser2"
|
||||
|
||||
|
||||
|
||||
# Plex
|
||||
|
||||
## 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
|
||||
## Comma seperated list for multiple servers
|
||||
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
||||
|
||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||
## Comma seperated list for multiple servers
|
||||
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||
|
||||
## If not using plex token then use username and password of the server admin along with the servername
|
||||
## Comma seperated for multiple options
|
||||
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
||||
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
||||
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
||||
|
||||
## Skip hostname validation for ssl certificates.
|
||||
## Set to True if running into ssl certificate errors
|
||||
SSL_BYPASS = "False"
|
||||
|
||||
|
||||
|
||||
# Jellyfin
|
||||
|
||||
## Jellyfin server URL, use hostname or IP address if the hostname is not resolving correctly
|
||||
## Comma seperated list for multiple servers
|
||||
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
||||
|
||||
## Jellyfin api token, created manually by logging in to the jellyfin server admin dashboard and creating an api key
|
||||
## Comma seperated list for multiple servers
|
||||
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||
```
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
306
src/functions.py
306
src/functions.py
@@ -1,4 +1,4 @@
|
||||
import os
|
||||
import os, copy
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
from dotenv import load_dotenv
|
||||
|
||||
@@ -8,13 +8,13 @@ logfile = os.getenv("LOGFILE", "log.log")
|
||||
|
||||
|
||||
def logger(message: str, log_type=0):
|
||||
debug = str_to_bool(os.getenv("DEBUG", "True"))
|
||||
debug = str_to_bool(os.getenv("DEBUG", "False"))
|
||||
debug_level = os.getenv("DEBUG_LEVEL", "info").lower()
|
||||
|
||||
output = str(message)
|
||||
if log_type == 0:
|
||||
pass
|
||||
elif log_type == 1 and (debug and debug_level == "info"):
|
||||
elif log_type == 1 and (debug and debug_level in ("info", "debug")):
|
||||
output = f"[INFO]: {output}"
|
||||
elif log_type == 2:
|
||||
output = f"[ERROR]: {output}"
|
||||
@@ -55,6 +55,108 @@ def search_mapping(dictionary: dict, key_value: str):
|
||||
return 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(",")
|
||||
blacklist_library = [x.strip() for x in blacklist_library]
|
||||
if library_mapping:
|
||||
temp_library = []
|
||||
for library in blacklist_library:
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
if library_other:
|
||||
temp_library.append(library_other)
|
||||
|
||||
blacklist_library = blacklist_library + temp_library
|
||||
else:
|
||||
blacklist_library = []
|
||||
logger(f"Blacklist Library: {blacklist_library}", 1)
|
||||
|
||||
if whitelist_library:
|
||||
if len(whitelist_library) > 0:
|
||||
whitelist_library = whitelist_library.split(",")
|
||||
whitelist_library = [x.strip() for x in whitelist_library]
|
||||
if library_mapping:
|
||||
temp_library = []
|
||||
for library in whitelist_library:
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
if library_other:
|
||||
temp_library.append(library_other)
|
||||
|
||||
whitelist_library = whitelist_library + temp_library
|
||||
else:
|
||||
whitelist_library = []
|
||||
logger(f"Whitelist Library: {whitelist_library}", 1)
|
||||
|
||||
if blacklist_library_type:
|
||||
if len(blacklist_library_type) > 0:
|
||||
blacklist_library_type = blacklist_library_type.split(",")
|
||||
blacklist_library_type = [x.lower().strip() for x in blacklist_library_type]
|
||||
else:
|
||||
blacklist_library_type = []
|
||||
logger(f"Blacklist Library Type: {blacklist_library_type}", 1)
|
||||
|
||||
if whitelist_library_type:
|
||||
if len(whitelist_library_type) > 0:
|
||||
whitelist_library_type = whitelist_library_type.split(",")
|
||||
whitelist_library_type = [x.lower().strip() for x in whitelist_library_type]
|
||||
else:
|
||||
whitelist_library_type = []
|
||||
logger(f"Whitelist Library Type: {whitelist_library_type}", 1)
|
||||
|
||||
if blacklist_users:
|
||||
if len(blacklist_users) > 0:
|
||||
blacklist_users = blacklist_users.split(",")
|
||||
blacklist_users = [x.lower().strip() for x in blacklist_users]
|
||||
if user_mapping:
|
||||
temp_users = []
|
||||
for user in blacklist_users:
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
if user_other:
|
||||
temp_users.append(user_other)
|
||||
|
||||
blacklist_users = blacklist_users + temp_users
|
||||
else:
|
||||
blacklist_users = []
|
||||
logger(f"Blacklist Users: {blacklist_users}", 1)
|
||||
|
||||
if whitelist_users:
|
||||
if len(whitelist_users) > 0:
|
||||
whitelist_users = whitelist_users.split(",")
|
||||
whitelist_users = [x.lower().strip() for x in whitelist_users]
|
||||
if user_mapping:
|
||||
temp_users = []
|
||||
for user in whitelist_users:
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
if user_other:
|
||||
temp_users.append(user_other)
|
||||
|
||||
whitelist_users = whitelist_users + temp_users
|
||||
else:
|
||||
whitelist_users = []
|
||||
else:
|
||||
whitelist_users = []
|
||||
logger(f"Whitelist Users: {whitelist_users}", 1)
|
||||
|
||||
return (
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
blacklist_users,
|
||||
whitelist_users,
|
||||
)
|
||||
|
||||
|
||||
def check_skip_logic(
|
||||
library_title,
|
||||
library_type,
|
||||
@@ -66,8 +168,13 @@ def check_skip_logic(
|
||||
):
|
||||
skip_reason = None
|
||||
|
||||
if library_type.lower() in blacklist_library_type:
|
||||
skip_reason = "is blacklist_library_type"
|
||||
if isinstance(library_type, (list, tuple, set)):
|
||||
for library_type_item in library_type:
|
||||
if library_type_item.lower() in blacklist_library_type:
|
||||
skip_reason = "is blacklist_library_type"
|
||||
else:
|
||||
if library_type.lower() in blacklist_library_type:
|
||||
skip_reason = "is blacklist_library_type"
|
||||
|
||||
if library_title.lower() in [x.lower() for x in blacklist_library]:
|
||||
skip_reason = "is blacklist_library"
|
||||
@@ -80,8 +187,13 @@ def check_skip_logic(
|
||||
skip_reason = "is blacklist_library"
|
||||
|
||||
if len(whitelist_library_type) > 0:
|
||||
if library_type.lower() not in whitelist_library_type:
|
||||
skip_reason = "is not whitelist_library_type"
|
||||
if isinstance(library_type, (list, tuple, set)):
|
||||
for library_type_item in library_type:
|
||||
if library_type_item.lower() not in whitelist_library_type:
|
||||
skip_reason = "is not whitelist_library_type"
|
||||
else:
|
||||
if library_type.lower() not in whitelist_library_type:
|
||||
skip_reason = "is not whitelist_library_type"
|
||||
|
||||
# if whitelist is not empty and library is not in whitelist
|
||||
if len(whitelist_library) > 0:
|
||||
@@ -100,6 +212,10 @@ def generate_library_guids_dict(user_list: dict):
|
||||
episode_output_dict = {}
|
||||
movies_output_dict = {}
|
||||
|
||||
# Handle the case where user_list is empty or does not contain the expected keys and values
|
||||
if not user_list:
|
||||
return show_output_dict, episode_output_dict, movies_output_dict
|
||||
|
||||
try:
|
||||
show_output_keys = user_list.keys()
|
||||
show_output_keys = [dict(x) for x in list(show_output_keys)]
|
||||
@@ -162,17 +278,189 @@ def combine_watched_dicts(dicts: list):
|
||||
if key not in combined_dict:
|
||||
combined_dict[key] = {}
|
||||
for subkey, subvalue in value.items():
|
||||
combined_dict[key][subkey] = subvalue
|
||||
if subkey in combined_dict[key]:
|
||||
# If the subkey already exists in the combined dictionary,
|
||||
# check if the values are different and raise an exception if they are
|
||||
if combined_dict[key][subkey] != subvalue:
|
||||
raise ValueError(
|
||||
f"Conflicting values for subkey '{subkey}' under key '{key}'"
|
||||
)
|
||||
else:
|
||||
# If the subkey does not exist in the combined dictionary, add it
|
||||
combined_dict[key][subkey] = subvalue
|
||||
|
||||
return combined_dict
|
||||
|
||||
|
||||
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 watched_list_1 that are in watched_list_2
|
||||
for user_1 in watched_list_1:
|
||||
user_other = None
|
||||
if user_mapping:
|
||||
user_other = search_mapping(user_mapping, user_1)
|
||||
user_2 = get_other(watched_list_2, user_1, user_other)
|
||||
if user_2 is None:
|
||||
continue
|
||||
|
||||
for library_1 in watched_list_1[user_1]:
|
||||
library_other = None
|
||||
if library_mapping:
|
||||
library_other = search_mapping(library_mapping, library_1)
|
||||
library_2 = get_other(watched_list_2[user_2], library_1, library_other)
|
||||
if library_2 is None:
|
||||
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):
|
||||
for movie in watched_list_1[user_1][library_1]:
|
||||
if is_movie_in_dict(movie, movies_watched_list_2_keys_dict):
|
||||
logger(f"Removing {movie} from {library_1}", 3)
|
||||
modified_watched_list_1[user_1][library_1].remove(movie)
|
||||
|
||||
# TV Shows
|
||||
elif isinstance(watched_list_1[user_1][library_1], dict):
|
||||
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
|
||||
]:
|
||||
if is_episode_in_dict(
|
||||
episode, episode_watched_list_2_keys_dict
|
||||
):
|
||||
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)
|
||||
|
||||
# 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]
|
||||
|
||||
# Remove empty shows
|
||||
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']} because it is empty",
|
||||
3,
|
||||
)
|
||||
del modified_watched_list_1[user_1][library_1][show_key_1]
|
||||
|
||||
for user_1 in watched_list_1:
|
||||
for library_1 in watched_list_1[user_1]:
|
||||
if library_1 in modified_watched_list_1[user_1]:
|
||||
# If library is empty then remove it
|
||||
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
||||
logger(f"Removing {library_1} from {user_1} because it is empty", 1)
|
||||
del modified_watched_list_1[user_1][library_1]
|
||||
|
||||
if user_1 in modified_watched_list_1:
|
||||
# If user is empty delete user
|
||||
if len(modified_watched_list_1[user_1]) == 0:
|
||||
logger(f"Removing {user_1} from watched list 1 because it is empty", 1)
|
||||
del modified_watched_list_1[user_1]
|
||||
|
||||
return modified_watched_list_1
|
||||
|
||||
|
||||
def get_other(watched_list_2, object_1, object_2):
|
||||
if object_1 in watched_list_2:
|
||||
return object_1
|
||||
elif object_2 in watched_list_2:
|
||||
return object_2
|
||||
else:
|
||||
logger(f"{object_1} and {object_2} not found in watched list 2", 1)
|
||||
return None
|
||||
|
||||
|
||||
def is_movie_in_dict(movie, movies_watched_list_2_keys_dict):
|
||||
# Iterate through the keys and values of the movie dictionary
|
||||
for movie_key, movie_value in movie.items():
|
||||
# If the key is "locations", check if the "locations" key is present in the movies_watched_list_2_keys_dict dictionary
|
||||
if movie_key == "locations":
|
||||
if "locations" in movies_watched_list_2_keys_dict.keys():
|
||||
# Iterate through the locations in the movie dictionary
|
||||
for location in movie_value:
|
||||
# If the location is in the movies_watched_list_2_keys_dict dictionary, return True
|
||||
if location in movies_watched_list_2_keys_dict["locations"]:
|
||||
return True
|
||||
# If the key is not "locations", check if the movie_key is present in the movies_watched_list_2_keys_dict dictionary
|
||||
else:
|
||||
if movie_key in movies_watched_list_2_keys_dict.keys():
|
||||
# If the movie_value is in the movies_watched_list_2_keys_dict dictionary, return True
|
||||
if movie_value in movies_watched_list_2_keys_dict[movie_key]:
|
||||
return True
|
||||
|
||||
# If the loop completes without finding a match, return False
|
||||
return False
|
||||
|
||||
|
||||
def is_episode_in_dict(episode, episode_watched_list_2_keys_dict):
|
||||
# Iterate through the keys and values of the episode dictionary
|
||||
for episode_key, episode_value in episode.items():
|
||||
# If the key is "locations", check if the "locations" key is present in the episode_watched_list_2_keys_dict dictionary
|
||||
if episode_key == "locations":
|
||||
if "locations" in episode_watched_list_2_keys_dict.keys():
|
||||
# Iterate through the locations in the episode dictionary
|
||||
for location in episode_value:
|
||||
# If the location is in the episode_watched_list_2_keys_dict dictionary, return True
|
||||
if location in episode_watched_list_2_keys_dict["locations"]:
|
||||
return True
|
||||
# If the key is not "locations", check if the episode_key is present in the episode_watched_list_2_keys_dict dictionary
|
||||
else:
|
||||
if episode_key in episode_watched_list_2_keys_dict.keys():
|
||||
# If the episode_value is in the episode_watched_list_2_keys_dict dictionary, return True
|
||||
if episode_value in episode_watched_list_2_keys_dict[episode_key]:
|
||||
return True
|
||||
|
||||
# If the loop completes without finding a match, return False
|
||||
return False
|
||||
|
||||
|
||||
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() * 2)
|
||||
|
||||
with ThreadPoolExecutor(max_workers=workers) as executor:
|
||||
for arg in args:
|
||||
|
||||
259
src/jellyfin.py
259
src/jellyfin.py
@@ -73,7 +73,7 @@ class Jellyfin:
|
||||
logger(f"Jellyfin: Get users failed {e}", 2)
|
||||
raise Exception(e)
|
||||
|
||||
async def get_user_watched(
|
||||
async def get_user_library_watched(
|
||||
self, user_name, user_id, library_type, library_id, library_title
|
||||
):
|
||||
try:
|
||||
@@ -85,26 +85,53 @@ class Jellyfin:
|
||||
f"Jellyfin: Generating watched for {user_name} in library {library_title}",
|
||||
0,
|
||||
)
|
||||
# Movies
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
# Movies
|
||||
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",
|
||||
+ f"?ParentId={library_id}&Filters=IsPlayed&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
|
||||
for movie in watched["Items"]:
|
||||
if movie["UserData"]["Played"] is True:
|
||||
movie_guids = {}
|
||||
movie_guids["title"] = movie["Name"]
|
||||
# Check if the movie has been played
|
||||
if (
|
||||
movie["UserData"]["Played"] is True
|
||||
and "MediaSources" in movie
|
||||
and movie["MediaSources"] is not {}
|
||||
):
|
||||
logger(
|
||||
f"Jellyfin: Adding {movie['Name']} to {user_name} watched list",
|
||||
3,
|
||||
)
|
||||
if "ProviderIds" in movie:
|
||||
# Lowercase movie["ProviderIds"] keys
|
||||
movie_guids = {
|
||||
k.lower(): v
|
||||
for k, v in movie["ProviderIds"].items()
|
||||
}
|
||||
logger(
|
||||
f"Jellyfin: {movie['Name']} {movie['ProviderIds']} {movie['MediaSources']}",
|
||||
3,
|
||||
)
|
||||
else:
|
||||
logger(
|
||||
f"Jellyfin: {movie['Name']} {movie['MediaSources']['Path']}",
|
||||
3,
|
||||
)
|
||||
|
||||
# Create a dictionary for the movie with its title
|
||||
movie_guids = {"title": movie["Name"]}
|
||||
|
||||
# If the movie has provider IDs, add them to the dictionary
|
||||
if "ProviderIds" in movie:
|
||||
movie_guids.update(
|
||||
{
|
||||
k.lower(): v
|
||||
for k, v in movie["ProviderIds"].items()
|
||||
}
|
||||
)
|
||||
|
||||
# If the movie has media sources, add them to the dictionary
|
||||
if "MediaSources" in movie:
|
||||
movie_guids["locations"] = tuple(
|
||||
[
|
||||
@@ -112,45 +139,67 @@ class Jellyfin:
|
||||
for x in movie["MediaSources"]
|
||||
]
|
||||
)
|
||||
|
||||
# Append the movie dictionary to the list for the given user and library
|
||||
user_watched[user_name][library_title].append(movie_guids)
|
||||
logger(
|
||||
f"Jellyfin: Added {movie_guids} to {user_name} watched list",
|
||||
3,
|
||||
)
|
||||
|
||||
# TV Shows
|
||||
if library_type == "Series":
|
||||
# Initialize an empty dictionary for the given user and library
|
||||
user_watched[user_name][library_title] = {}
|
||||
|
||||
# Retrieve a list of watched TV shows
|
||||
watched_shows = await self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?ParentId={library_id}&isPlaceHolder=false&Fields=ProviderIds,Path,RecursiveItemCount",
|
||||
+ f"?ParentId={library_id}&isPlaceHolder=false&IncludeItemTypes=Series&Recursive=True&Fields=ProviderIds,Path,RecursiveItemCount",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
|
||||
# Filter the list of shows to only include those that have been partially or fully watched
|
||||
watched_shows_filtered = []
|
||||
for show in watched_shows["Items"]:
|
||||
if "PlayedPercentage" in show["UserData"]:
|
||||
if show["UserData"]["PlayedPercentage"] > 0:
|
||||
watched_shows_filtered.append(show)
|
||||
|
||||
# Create a list of tasks to retrieve the seasons of each watched show
|
||||
seasons_tasks = []
|
||||
for show in watched_shows_filtered:
|
||||
logger(
|
||||
f"Jellyfin: Adding {show['Name']} to {user_name} watched list",
|
||||
3,
|
||||
)
|
||||
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(
|
||||
show_identifiers = {
|
||||
"show_guids": show_guids,
|
||||
"show_id": show["Id"],
|
||||
}
|
||||
season_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()),
|
||||
frozenset(show_identifiers.items()),
|
||||
)
|
||||
)
|
||||
seasons_tasks.append(task)
|
||||
seasons_tasks.append(season_task)
|
||||
|
||||
# Retrieve the seasons for each watched show
|
||||
seasons_watched = await asyncio.gather(*seasons_tasks)
|
||||
seasons_watched_filtered = []
|
||||
|
||||
# Filter the list of seasons to only include those that have been partially or fully watched
|
||||
seasons_watched_filtered = []
|
||||
for seasons in seasons_watched:
|
||||
seasons_watched_filtered_dict = {}
|
||||
seasons_watched_filtered_dict["Identifiers"] = seasons[
|
||||
@@ -169,6 +218,7 @@ class Jellyfin:
|
||||
seasons_watched_filtered_dict
|
||||
)
|
||||
|
||||
# Create a list of tasks to retrieve the episodes of each watched season
|
||||
episodes_tasks = []
|
||||
for seasons in seasons_watched_filtered:
|
||||
if len(seasons["Items"]) > 0:
|
||||
@@ -176,7 +226,7 @@ class Jellyfin:
|
||||
season_identifiers = dict(seasons["Identifiers"])
|
||||
season_identifiers["season_id"] = season["Id"]
|
||||
season_identifiers["season_name"] = season["Name"]
|
||||
task = asyncio.ensure_future(
|
||||
episode_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",
|
||||
@@ -185,60 +235,73 @@ class Jellyfin:
|
||||
frozenset(season_identifiers.items()),
|
||||
)
|
||||
)
|
||||
episodes_tasks.append(task)
|
||||
episodes_tasks.append(episode_task)
|
||||
|
||||
# Retrieve the episodes for each watched season
|
||||
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
|
||||
)
|
||||
# Iterate through the watched episodes
|
||||
for episodes in watched_episodes:
|
||||
# If the season has any watched episodes
|
||||
if len(episodes["Items"]) > 0:
|
||||
# Create a dictionary for the season with its identifier and episodes
|
||||
season_dict = {}
|
||||
season_dict["Identifiers"] = dict(episodes["Identifiers"])
|
||||
season_dict["Episodes"] = []
|
||||
for episode in episodes["Items"]:
|
||||
if (
|
||||
episode["UserData"]["Played"] is True
|
||||
and "MediaSources" in episode
|
||||
and episode["MediaSources"] is not {}
|
||||
):
|
||||
# Create a dictionary for the episode with its provider IDs and media sources
|
||||
episode_dict = {
|
||||
k.lower(): v
|
||||
for k, v in episode["ProviderIds"].items()
|
||||
}
|
||||
episode_dict["title"] = episode["Name"]
|
||||
episode_dict["locations"] = tuple(
|
||||
[
|
||||
x["Path"].split("/")[-1]
|
||||
for x in episode["MediaSources"]
|
||||
]
|
||||
)
|
||||
# Add the episode dictionary to the season's list of episodes
|
||||
season_dict["Episodes"].append(episode_dict)
|
||||
# Add the season dictionary to the show's list of seasons
|
||||
if (
|
||||
season_dict["Identifiers"]["show_guids"]
|
||||
not in user_watched[user_name][library_title]
|
||||
):
|
||||
user_watched[user_name][library_title][
|
||||
season_dict["Identifiers"]["show_guids"]
|
||||
] = {}
|
||||
|
||||
if (
|
||||
season_dict["Identifiers"]["season_name"]
|
||||
not in user_watched[user_name][library_title][
|
||||
season_dict["Identifiers"]["show_guids"]
|
||||
]
|
||||
):
|
||||
user_watched[user_name][library_title][
|
||||
season_dict["Identifiers"]["show_guids"]
|
||||
][season_dict["Identifiers"]["season_name"]] = []
|
||||
|
||||
user_watched[user_name][library_title][
|
||||
season_dict["Identifiers"]["show_guids"]
|
||||
][season_dict["Identifiers"]["season_name"]] = season_dict[
|
||||
"Episodes"
|
||||
]
|
||||
logger(
|
||||
f"Jellyfin: Added {season_dict['Episodes']} to {user_name} {season_dict['Identifiers']['show_guids']} watched list",
|
||||
1,
|
||||
)
|
||||
|
||||
logger(
|
||||
f"Jellyfin: Got watched for {user_name} in library {library_title}", 1
|
||||
)
|
||||
if library_title in user_watched[user_name]:
|
||||
logger(f"Jellyfin: {user_watched[user_name][library_title]}", 3)
|
||||
|
||||
return user_watched
|
||||
except Exception as e:
|
||||
@@ -276,7 +339,7 @@ class Jellyfin:
|
||||
task = asyncio.ensure_future(
|
||||
self.query(
|
||||
f"/Users/{user_id}/Items"
|
||||
+ f"?ParentId={library_id}&Filters=IsPlayed&limit=1",
|
||||
+ f"?ParentId={library_id}&Filters=IsPlayed&Recursive=True&excludeItemTypes=Folder&limit=100",
|
||||
"get",
|
||||
session,
|
||||
identifiers=identifiers,
|
||||
@@ -294,11 +357,18 @@ class Jellyfin:
|
||||
|
||||
library_id = watched["Identifiers"]["library_id"]
|
||||
library_title = watched["Identifiers"]["library_title"]
|
||||
library_type = watched["Items"][0]["Type"]
|
||||
# Get all library types excluding "Folder"
|
||||
types = set(
|
||||
[
|
||||
x["Type"]
|
||||
for x in watched["Items"]
|
||||
if x["Type"] in ["Movie", "Series"]
|
||||
]
|
||||
)
|
||||
|
||||
skip_reason = check_skip_logic(
|
||||
library_title,
|
||||
library_type,
|
||||
types,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
@@ -313,15 +383,29 @@ class Jellyfin:
|
||||
)
|
||||
continue
|
||||
|
||||
# Get watched for user
|
||||
task = asyncio.ensure_future(
|
||||
self.get_user_watched(
|
||||
user_name, user_id, library_type, library_id, library_title
|
||||
# If there are multiple types in library raise error
|
||||
if types is None or len(types) < 1:
|
||||
logger(
|
||||
f"Jellyfin: Skipping Library {library_title} not a single type: {types}",
|
||||
1,
|
||||
)
|
||||
)
|
||||
tasks_watched.append(task)
|
||||
continue
|
||||
|
||||
for library_type in types:
|
||||
# Get watched for user
|
||||
task = asyncio.ensure_future(
|
||||
self.get_user_library_watched(
|
||||
user_name,
|
||||
user_id,
|
||||
library_type,
|
||||
library_id,
|
||||
library_title,
|
||||
)
|
||||
)
|
||||
tasks_watched.append(task)
|
||||
|
||||
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)
|
||||
@@ -342,7 +426,7 @@ class Jellyfin:
|
||||
|
||||
for user_name, user_id in users.items():
|
||||
watched.append(
|
||||
await self.get_users_watched(
|
||||
self.get_users_watched(
|
||||
user_name,
|
||||
user_id,
|
||||
blacklist_library,
|
||||
@@ -353,6 +437,7 @@ class Jellyfin:
|
||||
)
|
||||
)
|
||||
|
||||
watched = await asyncio.gather(*watched, return_exceptions=True)
|
||||
for user_watched in watched:
|
||||
user_watched_temp = combine_watched_dicts(user_watched)
|
||||
for user, user_watched_temp in user_watched_temp.items():
|
||||
@@ -386,8 +471,8 @@ class Jellyfin:
|
||||
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",
|
||||
+ f"?SortBy=SortName&SortOrder=Ascending&Recursive=True&ParentId={library_id}"
|
||||
+ "&isPlayed=false&Fields=ItemCounts,ProviderIds,MediaSources&IncludeItemTypes=Movie",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
@@ -440,8 +525,8 @@ class Jellyfin:
|
||||
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",
|
||||
+ f"?SortBy=SortName&SortOrder=Ascending&Recursive=True&ParentId={library_id}"
|
||||
+ "&isPlayed=false&Fields=ItemCounts,ProviderIds,Path&IncludeItemTypes=Series",
|
||||
"get",
|
||||
session,
|
||||
)
|
||||
@@ -534,12 +619,12 @@ class Jellyfin:
|
||||
else:
|
||||
logger(
|
||||
f"Jellyfin: Skipping episode {jellyfin_episode['Name']} as it is not in mark list for {user_name}",
|
||||
1,
|
||||
3,
|
||||
)
|
||||
else:
|
||||
logger(
|
||||
f"Jellyfin: Skipping show {jellyfin_show['Name']} as it is not in mark list for {user_name}",
|
||||
1,
|
||||
3,
|
||||
)
|
||||
|
||||
if (
|
||||
@@ -618,13 +703,13 @@ class Jellyfin:
|
||||
else:
|
||||
logger(
|
||||
f"Jellyfin: Library {library} or {library_other} not found in library list",
|
||||
2,
|
||||
1,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
logger(
|
||||
f"Jellyfin: Library {library} not found in library list",
|
||||
2,
|
||||
1,
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
412
src/main.py
412
src/main.py
@@ -1,4 +1,4 @@
|
||||
import copy, os, traceback, json, asyncio
|
||||
import os, traceback, json, asyncio
|
||||
from dotenv import load_dotenv
|
||||
from time import sleep, perf_counter
|
||||
|
||||
@@ -6,7 +6,8 @@ from src.functions import (
|
||||
logger,
|
||||
str_to_bool,
|
||||
search_mapping,
|
||||
generate_library_guids_dict,
|
||||
cleanup_watched,
|
||||
setup_black_white_lists,
|
||||
)
|
||||
from src.plex import Plex
|
||||
from src.jellyfin import Jellyfin
|
||||
@@ -14,306 +15,6 @@ from src.jellyfin import Jellyfin
|
||||
load_dotenv(override=True)
|
||||
|
||||
|
||||
def cleanup_watched(
|
||||
watched_list_1, watched_list_2, user_mapping=None, library_mapping=None
|
||||
):
|
||||
modified_watched_list_1 = copy.deepcopy(watched_list_1)
|
||||
|
||||
# remove entries from plex_watched that are in jellyfin_watched
|
||||
for user_1 in watched_list_1:
|
||||
user_other = None
|
||||
if user_mapping:
|
||||
user_other = search_mapping(user_mapping, user_1)
|
||||
if user_1 in modified_watched_list_1:
|
||||
if user_1 in watched_list_2:
|
||||
user_2 = user_1
|
||||
elif user_other in watched_list_2:
|
||||
user_2 = user_other
|
||||
else:
|
||||
logger(f"User {user_1} and {user_other} not found in watched list 2", 1)
|
||||
continue
|
||||
|
||||
for library_1 in watched_list_1[user_1]:
|
||||
library_other = None
|
||||
if library_mapping:
|
||||
library_other = search_mapping(library_mapping, library_1)
|
||||
if library_1 in modified_watched_list_1[user_1]:
|
||||
if library_1 in watched_list_2[user_2]:
|
||||
library_2 = library_1
|
||||
elif library_other in watched_list_2[user_2]:
|
||||
library_2 = library_other
|
||||
else:
|
||||
logger(
|
||||
f"library {library_1} and {library_other} not found in watched list 2",
|
||||
1,
|
||||
)
|
||||
continue
|
||||
|
||||
(
|
||||
_,
|
||||
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):
|
||||
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":
|
||||
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
|
||||
]
|
||||
):
|
||||
movie_found = True
|
||||
|
||||
if movie_found:
|
||||
logger(f"Removing {movie} from {library_1}", 3)
|
||||
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
|
||||
|
||||
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]:
|
||||
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":
|
||||
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
|
||||
]
|
||||
):
|
||||
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)
|
||||
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 the show is empty, remove the show
|
||||
if (
|
||||
len(
|
||||
modified_watched_list_1[user_1][library_1][
|
||||
show_key_1
|
||||
]
|
||||
)
|
||||
== 0
|
||||
):
|
||||
if (
|
||||
show_key_1
|
||||
in modified_watched_list_1[user_1][library_1]
|
||||
):
|
||||
logger(
|
||||
f"Removing {show_key_dict['title']} from {library_1} because it is empty",
|
||||
1,
|
||||
)
|
||||
del modified_watched_list_1[user_1][library_1][
|
||||
show_key_1
|
||||
]
|
||||
|
||||
for user_1 in watched_list_1:
|
||||
for library_1 in watched_list_1[user_1]:
|
||||
if library_1 in modified_watched_list_1[user_1]:
|
||||
# If library is empty then remove it
|
||||
if len(modified_watched_list_1[user_1][library_1]) == 0:
|
||||
logger(f"Removing {library_1} from {user_1} because it is empty", 1)
|
||||
del modified_watched_list_1[user_1][library_1]
|
||||
|
||||
if user_1 in modified_watched_list_1:
|
||||
# If user is empty delete user
|
||||
if len(modified_watched_list_1[user_1]) == 0:
|
||||
logger(f"Removing {user_1} from watched list 1 because it is empty", 1)
|
||||
del modified_watched_list_1[user_1]
|
||||
|
||||
return modified_watched_list_1
|
||||
|
||||
|
||||
def setup_black_white_lists(
|
||||
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(",")
|
||||
blacklist_library = [x.strip() for x in blacklist_library]
|
||||
if library_mapping:
|
||||
temp_library = []
|
||||
for library in blacklist_library:
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
if library_other:
|
||||
temp_library.append(library_other)
|
||||
|
||||
blacklist_library = blacklist_library + temp_library
|
||||
else:
|
||||
blacklist_library = []
|
||||
logger(f"Blacklist Library: {blacklist_library}", 1)
|
||||
|
||||
if whitelist_library:
|
||||
if len(whitelist_library) > 0:
|
||||
whitelist_library = whitelist_library.split(",")
|
||||
whitelist_library = [x.strip() for x in whitelist_library]
|
||||
if library_mapping:
|
||||
temp_library = []
|
||||
for library in whitelist_library:
|
||||
library_other = search_mapping(library_mapping, library)
|
||||
if library_other:
|
||||
temp_library.append(library_other)
|
||||
|
||||
whitelist_library = whitelist_library + temp_library
|
||||
else:
|
||||
whitelist_library = []
|
||||
logger(f"Whitelist Library: {whitelist_library}", 1)
|
||||
|
||||
if blacklist_library_type:
|
||||
if len(blacklist_library_type) > 0:
|
||||
blacklist_library_type = blacklist_library_type.split(",")
|
||||
blacklist_library_type = [x.lower().strip() for x in blacklist_library_type]
|
||||
else:
|
||||
blacklist_library_type = []
|
||||
logger(f"Blacklist Library Type: {blacklist_library_type}", 1)
|
||||
|
||||
if whitelist_library_type:
|
||||
if len(whitelist_library_type) > 0:
|
||||
whitelist_library_type = whitelist_library_type.split(",")
|
||||
whitelist_library_type = [x.lower().strip() for x in whitelist_library_type]
|
||||
else:
|
||||
whitelist_library_type = []
|
||||
logger(f"Whitelist Library Type: {whitelist_library_type}", 1)
|
||||
|
||||
if blacklist_users:
|
||||
if len(blacklist_users) > 0:
|
||||
blacklist_users = blacklist_users.split(",")
|
||||
blacklist_users = [x.lower().strip() for x in blacklist_users]
|
||||
if user_mapping:
|
||||
temp_users = []
|
||||
for user in blacklist_users:
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
if user_other:
|
||||
temp_users.append(user_other)
|
||||
|
||||
blacklist_users = blacklist_users + temp_users
|
||||
else:
|
||||
blacklist_users = []
|
||||
logger(f"Blacklist Users: {blacklist_users}", 1)
|
||||
|
||||
if whitelist_users:
|
||||
if len(whitelist_users) > 0:
|
||||
whitelist_users = whitelist_users.split(",")
|
||||
whitelist_users = [x.lower().strip() for x in whitelist_users]
|
||||
if user_mapping:
|
||||
temp_users = []
|
||||
for user in whitelist_users:
|
||||
user_other = search_mapping(user_mapping, user)
|
||||
if user_other:
|
||||
temp_users.append(user_other)
|
||||
|
||||
whitelist_users = whitelist_users + temp_users
|
||||
else:
|
||||
whitelist_users = []
|
||||
else:
|
||||
whitelist_users = []
|
||||
logger(f"Whitelist Users: {whitelist_users}", 1)
|
||||
|
||||
return (
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
blacklist_users,
|
||||
whitelist_users,
|
||||
)
|
||||
|
||||
|
||||
def setup_users(
|
||||
server_1, server_2, blacklist_users, whitelist_users, user_mapping=None
|
||||
):
|
||||
@@ -323,8 +24,8 @@ def setup_users(
|
||||
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}")
|
||||
logger(f"Server 1: {server_1_type} {server_1_connection}", 0)
|
||||
logger(f"Server 2: {server_2_type} {server_2_connection}", 0)
|
||||
|
||||
server_1_users = []
|
||||
if server_1_type == "plex":
|
||||
@@ -412,12 +113,12 @@ def setup_users(
|
||||
|
||||
if len(output_server_1_users) == 0:
|
||||
raise Exception(
|
||||
f"No users found for server 1, users found {users} filtered users {users_filtered}"
|
||||
f"No users found for server 1 {server_1_type}, users found {users}, filtered users {users_filtered}, server 1 users {server_1_connection.users}"
|
||||
)
|
||||
|
||||
if len(output_server_2_users) == 0:
|
||||
raise Exception(
|
||||
f"No users found for server 2, users found {users} filtered users {users_filtered}"
|
||||
f"No users found for server 2 {server_2_type}, users found {users} filtered users {users_filtered}, server 2 users {server_2_connection.users}"
|
||||
)
|
||||
|
||||
logger(f"Server 1 users: {output_server_1_users}", 1)
|
||||
@@ -500,16 +201,69 @@ def generate_server_connections():
|
||||
)
|
||||
|
||||
for i, baseurl in enumerate(jellyfin_baseurl):
|
||||
baseurl = baseurl.strip()
|
||||
if baseurl[-1] == "/":
|
||||
baseurl = baseurl[:-1]
|
||||
servers.append(
|
||||
(
|
||||
"jellyfin",
|
||||
Jellyfin(baseurl=baseurl.strip(), token=jellyfin_token[i].strip()),
|
||||
Jellyfin(baseurl=baseurl, token=jellyfin_token[i].strip()),
|
||||
)
|
||||
)
|
||||
|
||||
return servers
|
||||
|
||||
|
||||
def get_server_watched(
|
||||
server_connection: list,
|
||||
users: dict,
|
||||
blacklist_library: list,
|
||||
whitelist_library: list,
|
||||
blacklist_library_type: list,
|
||||
whitelist_library_type: list,
|
||||
library_mapping: dict,
|
||||
):
|
||||
if server_connection[0] == "plex":
|
||||
return server_connection[1].get_watched(
|
||||
users,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
library_mapping,
|
||||
)
|
||||
elif server_connection[0] == "jellyfin":
|
||||
return asyncio.run(
|
||||
server_connection[1].get_watched(
|
||||
users,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
blacklist_library_type,
|
||||
whitelist_library_type,
|
||||
library_mapping,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def update_server_watched(
|
||||
server_connection: list,
|
||||
server_watched_filtered: dict,
|
||||
user_mapping: dict,
|
||||
library_mapping: dict,
|
||||
dryrun: bool,
|
||||
):
|
||||
if server_connection[0] == "plex":
|
||||
server_connection[1].update_watched(
|
||||
server_watched_filtered, user_mapping, library_mapping, dryrun
|
||||
)
|
||||
elif server_connection[0] == "jellyfin":
|
||||
asyncio.run(
|
||||
server_connection[1].update_watched(
|
||||
server_watched_filtered, user_mapping, library_mapping, dryrun
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def main_loop():
|
||||
logfile = os.getenv("LOGFILE", "log.log")
|
||||
# Delete logfile if it exists
|
||||
@@ -567,10 +321,6 @@ def main_loop():
|
||||
|
||||
# Start server_2 at the next server in the list
|
||||
for server_2 in servers[servers.index(server_1) + 1 :]:
|
||||
|
||||
server_1_connection = server_1[1]
|
||||
server_2_connection = server_2[1]
|
||||
|
||||
# Create users list
|
||||
logger("Creating users list", 1)
|
||||
server_1_users, server_2_users = setup_users(
|
||||
@@ -578,7 +328,8 @@ def main_loop():
|
||||
)
|
||||
|
||||
logger("Creating watched lists", 1)
|
||||
server_1_watched = server_1_connection.get_watched(
|
||||
server_1_watched = get_server_watched(
|
||||
server_1,
|
||||
server_1_users,
|
||||
blacklist_library,
|
||||
whitelist_library,
|
||||
@@ -587,15 +338,14 @@ def main_loop():
|
||||
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,
|
||||
)
|
||||
server_2_watched = get_server_watched(
|
||||
server_2,
|
||||
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)
|
||||
@@ -620,13 +370,20 @@ def main_loop():
|
||||
1,
|
||||
)
|
||||
|
||||
server_1_connection.update_watched(
|
||||
server_2_watched_filtered, user_mapping, library_mapping, dryrun
|
||||
update_server_watched(
|
||||
server_1,
|
||||
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
|
||||
)
|
||||
|
||||
update_server_watched(
|
||||
server_2,
|
||||
server_1_watched_filtered,
|
||||
user_mapping,
|
||||
library_mapping,
|
||||
dryrun,
|
||||
)
|
||||
|
||||
|
||||
@@ -654,6 +411,7 @@ def main():
|
||||
logger(error, log_type=2)
|
||||
|
||||
logger(traceback.format_exc(), 2)
|
||||
|
||||
logger(f"Retrying in {sleep_duration}", log_type=0)
|
||||
sleep(sleep_duration)
|
||||
|
||||
|
||||
142
src/plex.py
142
src/plex.py
@@ -15,13 +15,16 @@ from src.functions import (
|
||||
# Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
|
||||
class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter):
|
||||
def init_poolmanager(self, connections, maxsize, block=..., **pool_kwargs):
|
||||
self.poolmanager = PoolManager(num_pools=connections,
|
||||
maxsize=maxsize,
|
||||
block=block,
|
||||
assert_hostname=False,
|
||||
**pool_kwargs)
|
||||
self.poolmanager = PoolManager(
|
||||
num_pools=connections,
|
||||
maxsize=maxsize,
|
||||
block=block,
|
||||
assert_hostname=False,
|
||||
**pool_kwargs,
|
||||
)
|
||||
|
||||
def get_user_watched(user, user_plex, library):
|
||||
|
||||
def get_user_library_watched(user, user_plex, library):
|
||||
try:
|
||||
user_name = user.title.lower()
|
||||
user_watched = {}
|
||||
@@ -32,15 +35,20 @@ def get_user_watched(user, user_plex, library):
|
||||
0,
|
||||
)
|
||||
|
||||
library_videos = user_plex.library.section(library.title)
|
||||
|
||||
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):
|
||||
logger(f"Plex: Adding {video.title} to {user_name} watched list", 3)
|
||||
logger(f"Plex: {video.title} {video.guids} {video.locations}", 3)
|
||||
|
||||
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)
|
||||
# Extract source and id from guid.id
|
||||
m = re.match(r"(.*)://(.*)", guid.id)
|
||||
guid_source, guid_id = m.group(1).lower(), m.group(2)
|
||||
movie_guids[guid_source] = guid_id
|
||||
|
||||
movie_guids["title"] = video.title
|
||||
@@ -49,19 +57,18 @@ def get_user_watched(user, user_plex, library):
|
||||
)
|
||||
|
||||
user_watched[user_name][library.title].append(movie_guids)
|
||||
logger(f"Plex: Added {movie_guids} to {user_name} watched list", 3)
|
||||
|
||||
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):
|
||||
logger(f"Plex: Adding {show.title} to {user_name} watched list", 3)
|
||||
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)
|
||||
# Extract source and id from guid.id
|
||||
m = re.match(r"(.*)://(.*)", show_guid.id)
|
||||
show_guid_source, show_guid_id = m.group(1).lower(), m.group(2)
|
||||
show_guids[show_guid_source] = show_guid_id
|
||||
|
||||
show_guids["title"] = show.title
|
||||
@@ -70,38 +77,38 @@ def get_user_watched(user, user_plex, library):
|
||||
)
|
||||
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
|
||||
# Get all watched episodes for show
|
||||
episode_guids = {}
|
||||
for episode in show.watched():
|
||||
if episode.viewCount > 0:
|
||||
episode_guids_temp = {}
|
||||
for guid in episode.guids:
|
||||
# Extract after :// from guid.id
|
||||
m = re.match(r"(.*)://(.*)", guid.id)
|
||||
guid_source, guid_id = m.group(1).lower(), m.group(2)
|
||||
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)
|
||||
episode_guids_temp["locations"] = tuple(
|
||||
[x.split("/")[-1] for x in episode.locations]
|
||||
)
|
||||
if episode.parentTitle not in episode_guids:
|
||||
episode_guids[episode.parentTitle] = []
|
||||
episode_guids[episode.parentTitle].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
|
||||
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] = {}
|
||||
|
||||
user_watched[user_name][library.title][show_guids] = episode_guids
|
||||
logger(
|
||||
f"Plex: Added {episode_guids} to {user_name} {show_guids} watched list",
|
||||
3,
|
||||
)
|
||||
|
||||
logger(f"Plex: Got watched for {user_name} in library {library.title}", 1)
|
||||
if library.title in user_watched[user_name]:
|
||||
logger(f"Plex: {user_watched[user_name][library.title]}", 3)
|
||||
|
||||
return user_watched
|
||||
except Exception as e:
|
||||
@@ -223,12 +230,12 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
||||
else:
|
||||
logger(
|
||||
f"Plex: Skipping episode {episode_search.title} as it is not in mark list for {user.title}",
|
||||
1,
|
||||
3,
|
||||
)
|
||||
else:
|
||||
logger(
|
||||
f"Plex: Skipping show {show_search.title} as it is not in mark list for {user.title}",
|
||||
1,
|
||||
3,
|
||||
)
|
||||
|
||||
if not videos_movies_ids and not videos_shows_ids and not videos_episodes_ids:
|
||||
@@ -255,6 +262,7 @@ class Plex:
|
||||
password=None,
|
||||
servername=None,
|
||||
ssl_bypass=False,
|
||||
session=None,
|
||||
):
|
||||
self.baseurl = baseurl
|
||||
self.token = token
|
||||
@@ -262,21 +270,20 @@ class Plex:
|
||||
self.password = password
|
||||
self.servername = servername
|
||||
self.ssl_bypass = ssl_bypass
|
||||
self.plex = self.login(self.baseurl, self.token, ssl_bypass)
|
||||
if ssl_bypass:
|
||||
# Session for ssl bypass
|
||||
session = requests.Session()
|
||||
# By pass ssl hostname check https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
|
||||
session.mount("https://", HostNameIgnoringAdapter())
|
||||
self.session = session
|
||||
self.plex = self.login(self.baseurl, self.token)
|
||||
self.admin_user = self.plex.myPlexAccount()
|
||||
self.users = self.get_users()
|
||||
|
||||
def login(self, baseurl, token, ssl_bypass=False):
|
||||
def login(self, baseurl, token):
|
||||
try:
|
||||
if baseurl and token:
|
||||
# Login via token
|
||||
if ssl_bypass:
|
||||
session = requests.Session()
|
||||
# By pass ssl hostname check https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
|
||||
session.mount("https://", HostNameIgnoringAdapter())
|
||||
plex = PlexServer(baseurl, token, session=session)
|
||||
else:
|
||||
plex = PlexServer(baseurl, token)
|
||||
plex = PlexServer(baseurl, token, session=self.session)
|
||||
elif self.username and self.password and self.servername:
|
||||
# Login via plex account
|
||||
account = MyPlexAccount(self.username, self.password)
|
||||
@@ -324,7 +331,8 @@ class Plex:
|
||||
user_plex = self.plex
|
||||
else:
|
||||
user_plex = self.login(
|
||||
self.plex._baseurl, user.get_token(self.plex.machineIdentifier), self.ssl_bypass
|
||||
self.plex._baseurl,
|
||||
user.get_token(self.plex.machineIdentifier),
|
||||
)
|
||||
|
||||
libraries = user_plex.library.sections()
|
||||
@@ -349,7 +357,7 @@ class Plex:
|
||||
)
|
||||
continue
|
||||
|
||||
args.append([get_user_watched, user, user_plex, library])
|
||||
args.append([get_user_library_watched, user, user_plex, library])
|
||||
|
||||
for user_watched in future_thread_executor(args):
|
||||
for user, user_watched_temp in user_watched.items():
|
||||
@@ -388,8 +396,17 @@ class Plex:
|
||||
if self.admin_user == user:
|
||||
user_plex = self.plex
|
||||
else:
|
||||
if isinstance(user, str):
|
||||
logger(
|
||||
f"Plex: {user} is not a plex object, attempting to get object for user",
|
||||
4,
|
||||
)
|
||||
user = self.plex.myPlexAccount().user(user)
|
||||
|
||||
user_plex = PlexServer(
|
||||
self.plex._baseurl, user.get_token(self.plex.machineIdentifier)
|
||||
self.plex._baseurl,
|
||||
user.get_token(self.plex.machineIdentifier),
|
||||
session=self.session,
|
||||
)
|
||||
|
||||
for library, videos in libraries.items():
|
||||
@@ -415,12 +432,13 @@ class Plex:
|
||||
else:
|
||||
logger(
|
||||
f"Plex: Library {library} or {library_other} not found in library list",
|
||||
2,
|
||||
1,
|
||||
)
|
||||
continue
|
||||
else:
|
||||
logger(
|
||||
f"Plex: Library {library} not found in library list", 2
|
||||
f"Plex: Library {library} not found in library list",
|
||||
1,
|
||||
)
|
||||
continue
|
||||
|
||||
|
||||
@@ -1,78 +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"]
|
||||
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"]
|
||||
|
||||
Reference in New Issue
Block a user