Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd64088bde | ||
|
|
7832e41a3b | ||
|
|
cadd65d69b | ||
|
|
9f004797fc | ||
|
|
9041fee7ad | ||
|
|
9af6c9057c | ||
|
|
757ce91138 | ||
|
|
98f96ed5c7 | ||
|
|
3e15120e2a | ||
|
|
5824e6c0cc | ||
|
|
7087d75efb | ||
|
|
b2a06b8fd3 | ||
|
|
1ee055faf5 | ||
|
|
404089dfca | ||
|
|
ed24948dee | ||
|
|
1f16fcb8eb | ||
|
|
03de3affd7 | ||
|
|
2bad887659 | ||
|
|
796be47a63 | ||
|
|
dc1fe11590 | ||
|
|
13b4ff3215 | ||
|
|
dca54cf4fb | ||
|
|
a4365e59f3 | ||
|
|
b960bccb86 | ||
|
|
218037200c | ||
|
|
4ac670e837 | ||
|
|
96eff65c3e | ||
|
|
45471607c8 | ||
|
|
14885744b1 |
22
.env.sample
22
.env.sample
@@ -16,15 +16,15 @@ SLEEP_DURATION = "3600"
|
|||||||
LOGFILE = "log.log"
|
LOGFILE = "log.log"
|
||||||
|
|
||||||
## Map usernames between servers in the event that they are different, order does not matter
|
## Map usernames between servers in the event that they are different, order does not matter
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
#USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
#USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
||||||
|
|
||||||
## Map libraries between servers in the even that they are different, order does not matter
|
## Map libraries between servers in the even that they are different, order does not matter
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
#LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
#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.
|
## 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
|
## Comma separated for multiple options
|
||||||
#BLACKLIST_LIBRARY = ""
|
#BLACKLIST_LIBRARY = ""
|
||||||
#WHITELIST_LIBRARY = ""
|
#WHITELIST_LIBRARY = ""
|
||||||
#BLACKLIST_LIBRARY_TYPE = ""
|
#BLACKLIST_LIBRARY_TYPE = ""
|
||||||
@@ -38,15 +38,15 @@ WHITELIST_USERS = "testuser1,testuser2"
|
|||||||
|
|
||||||
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
|
## 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
|
## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
PLEX_BASEURL = "http://localhost:32400, https://nas: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/
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
|
|
||||||
## If not using plex token then use username and password of the server admin along with the servername
|
## If not using plex token then use username and password of the server admin along with the servername
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
||||||
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
||||||
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
||||||
@@ -55,14 +55,20 @@ PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
|||||||
## Set to True if running into ssl certificate errors
|
## Set to True if running into ssl certificate errors
|
||||||
SSL_BYPASS = "False"
|
SSL_BYPASS = "False"
|
||||||
|
|
||||||
|
## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex
|
||||||
|
## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers
|
||||||
|
SYNC_FROM_PLEX_TO_JELLYFIN = "True"
|
||||||
|
SYNC_FROM_JELLYFIN_TO_PLEX = "True"
|
||||||
|
SYNC_FROM_PLEX_TO_PLEX = "True"
|
||||||
|
SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True"
|
||||||
|
|
||||||
|
|
||||||
# Jellyfin
|
# Jellyfin
|
||||||
|
|
||||||
## 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
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
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
|
## 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
|
## Comma separated list for multiple servers
|
||||||
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
|
|||||||
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
31
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
---
|
||||||
|
name: Bug report
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: "[BUG]"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the bug**
|
||||||
|
A clear and concise description of what the bug is.
|
||||||
|
|
||||||
|
**To Reproduce**
|
||||||
|
Steps to reproduce the behavior:
|
||||||
|
1. Go to '...'
|
||||||
|
2. Click on '....'
|
||||||
|
3. Scroll down to '....'
|
||||||
|
4. See error
|
||||||
|
|
||||||
|
**Expected behavior**
|
||||||
|
A clear and concise description of what you expected to happen.
|
||||||
|
|
||||||
|
**Logs**
|
||||||
|
If applicable, add logs to help explain your problem ideally with DEBUG set to true, be sure to remove sensitive information
|
||||||
|
|
||||||
|
**Type:**
|
||||||
|
- [ ] Docker
|
||||||
|
- [ ] Native
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
name: Feature request
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: "[Feature Request]"
|
||||||
|
labels: ''
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
6
.vscode/settings.json
vendored
6
.vscode/settings.json
vendored
@@ -1,3 +1,7 @@
|
|||||||
{
|
{
|
||||||
"python.formatting.provider": "black"
|
"[python]" : {
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
},
|
||||||
|
"python.formatting.provider": "black",
|
||||||
|
|
||||||
}
|
}
|
||||||
28
README.md
28
README.md
@@ -6,7 +6,7 @@ Sync watched between jellyfin and plex locally
|
|||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
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.
|
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 entering multiple options in the .env plex/jellyfin section separated by commas.
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
@@ -29,15 +29,15 @@ SLEEP_DURATION = "3600"
|
|||||||
LOGFILE = "log.log"
|
LOGFILE = "log.log"
|
||||||
|
|
||||||
## Map usernames between servers in the event that they are different, order does not matter
|
## Map usernames between servers in the event that they are different, order does not matter
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
USER_MAPPING = { "testuser2": "testuser3", "testuser1":"testuser4" }
|
||||||
|
|
||||||
## Map libraries between servers in the even that they are different, order does not matter
|
## Map libraries between servers in the even that they are different, order does not matter
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
LIBRARY_MAPPING = { "Shows": "TV Shows", "Movie": "Movies" }
|
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.
|
## 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
|
## Comma separated for multiple options
|
||||||
BLACKLIST_LIBRARY = ""
|
BLACKLIST_LIBRARY = ""
|
||||||
WHITELIST_LIBRARY = ""
|
WHITELIST_LIBRARY = ""
|
||||||
BLACKLIST_LIBRARY_TYPE = ""
|
BLACKLIST_LIBRARY_TYPE = ""
|
||||||
@@ -51,15 +51,15 @@ WHITELIST_USERS = "testuser1,testuser2"
|
|||||||
|
|
||||||
## Recommended to use token as it is faster to connect as it is direct to the server instead of going through the plex servers
|
## 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
|
## URL of the plex server, use hostname or IP address if the hostname is not resolving correctly
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
PLEX_BASEURL = "http://localhost:32400, https://nas:32400"
|
PLEX_BASEURL = "http://localhost:32400, https://nas: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/
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
|
|
||||||
## If not using plex token then use username and password of the server admin along with the servername
|
## If not using plex token then use username and password of the server admin along with the servername
|
||||||
## Comma seperated for multiple options
|
## Comma separated for multiple options
|
||||||
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
#PLEX_USERNAME = "PlexUser, PlexUser2"
|
||||||
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
#PLEX_PASSWORD = "SuperSecret, SuperSecret2"
|
||||||
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
#PLEX_SERVERNAME = "Plex Server1, Plex Server2"
|
||||||
@@ -69,19 +69,25 @@ PLEX_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
|||||||
SSL_BYPASS = "False"
|
SSL_BYPASS = "False"
|
||||||
|
|
||||||
|
|
||||||
|
## control the direction of syncing. e.g. SYNC_FROM_PLEX_TO_JELLYFIN set to true will cause the updates from plex
|
||||||
|
## to be updated in jellyfin. SYNC_FROM_PLEX_TO_PLEX set to true will sync updates between multiple plex servers
|
||||||
|
SYNC_FROM_PLEX_TO_JELLYFIN = "True"
|
||||||
|
SYNC_FROM_JELLYFIN_TO_PLEX = "True"
|
||||||
|
SYNC_FROM_PLEX_TO_PLEX = "True"
|
||||||
|
SYNC_FROM_JELLYFIN_TO_JELLYFIN = "True"
|
||||||
|
|
||||||
|
|
||||||
# Jellyfin
|
# Jellyfin
|
||||||
|
|
||||||
## 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
|
||||||
## Comma seperated list for multiple servers
|
## Comma separated list for multiple servers
|
||||||
JELLYFIN_BASEURL = "http://localhost:8096, http://nas:8096"
|
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
|
## 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
|
## Comma separated list for multiple servers
|
||||||
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### Baremetal
|
### Baremetal
|
||||||
@@ -136,7 +142,7 @@ JELLYFIN_TOKEN = "SuperSecretToken, SuperSecretToken2"
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
I am open to recieving pull requests. If you are submitting a pull request, please make sure run it locally for a day or two to make sure it is working as expected and stable. Make all pull requests against the dev branch and nothing will be merged into the main without going through the lower branches.
|
I am open to receiving pull requests. If you are submitting a pull request, please make sure run it locally for a day or two to make sure it is working as expected and stable. Make all pull requests against the dev branch and nothing will be merged into the main without going through the lower branches.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
139
src/black_white.py
Normal file
139
src/black_white.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
from src.functions import logger, search_mapping
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
):
|
||||||
|
blacklist_library, blacklist_library_type, blacklist_users = setup_black_lists(
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
blacklist_users,
|
||||||
|
library_mapping,
|
||||||
|
user_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
whitelist_library, whitelist_library_type, whitelist_users = setup_white_lists(
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
whitelist_users,
|
||||||
|
library_mapping,
|
||||||
|
user_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
blacklist_users,
|
||||||
|
whitelist_users,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def setup_black_lists(
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
blacklist_users,
|
||||||
|
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 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 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)
|
||||||
|
|
||||||
|
return blacklist_library, blacklist_library_type, blacklist_users
|
||||||
|
|
||||||
|
|
||||||
|
def setup_white_lists(
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
whitelist_users,
|
||||||
|
library_mapping=None,
|
||||||
|
user_mapping=None,
|
||||||
|
):
|
||||||
|
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 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 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 whitelist_library, whitelist_library_type, whitelist_users
|
||||||
402
src/functions.py
402
src/functions.py
@@ -1,4 +1,4 @@
|
|||||||
import os, copy
|
import os
|
||||||
from concurrent.futures import ThreadPoolExecutor
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
@@ -55,406 +55,6 @@ def search_mapping(dictionary: dict, key_value: str):
|
|||||||
return None
|
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,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
):
|
|
||||||
skip_reason = None
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
library_other = None
|
|
||||||
if library_mapping:
|
|
||||||
library_other = search_mapping(library_mapping, library_title)
|
|
||||||
if library_other:
|
|
||||||
if library_other.lower() in [x.lower() for x in blacklist_library]:
|
|
||||||
skip_reason = "is blacklist_library"
|
|
||||||
|
|
||||||
if len(whitelist_library_type) > 0:
|
|
||||||
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:
|
|
||||||
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"
|
|
||||||
|
|
||||||
return skip_reason
|
|
||||||
|
|
||||||
|
|
||||||
def generate_library_guids_dict(user_list: dict):
|
|
||||||
show_output_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)]
|
|
||||||
for show_key in show_output_keys:
|
|
||||||
for provider_key, provider_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()] = []
|
|
||||||
if provider_key.lower() == "locations":
|
|
||||||
for show_location in provider_value:
|
|
||||||
show_output_dict[provider_key.lower()].append(show_location)
|
|
||||||
else:
|
|
||||||
show_output_dict[provider_key.lower()].append(
|
|
||||||
provider_value.lower()
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
logger("Generating show_output_dict failed, skipping", 1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
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()] = []
|
|
||||||
if episode_key == "locations":
|
|
||||||
for episode_location in episode_value:
|
|
||||||
episode_output_dict[episode_key.lower()].append(
|
|
||||||
episode_location
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
episode_output_dict[episode_key.lower()].append(
|
|
||||||
episode_value.lower()
|
|
||||||
)
|
|
||||||
except Exception:
|
|
||||||
logger("Generating episode_output_dict failed, skipping", 1)
|
|
||||||
|
|
||||||
try:
|
|
||||||
for movie in user_list:
|
|
||||||
for movie_key, movie_value in movie.items():
|
|
||||||
if movie_key.lower() not in movies_output_dict:
|
|
||||||
movies_output_dict[movie_key.lower()] = []
|
|
||||||
if movie_key == "locations":
|
|
||||||
for movie_location in movie_value:
|
|
||||||
movies_output_dict[movie_key.lower()].append(movie_location)
|
|
||||||
else:
|
|
||||||
movies_output_dict[movie_key.lower()].append(movie_value.lower())
|
|
||||||
except Exception:
|
|
||||||
logger("Generating movies_output_dict failed, skipping", 1)
|
|
||||||
|
|
||||||
return show_output_dict, episode_output_dict, movies_output_dict
|
|
||||||
|
|
||||||
|
|
||||||
def combine_watched_dicts(dicts: list):
|
|
||||||
combined_dict = {}
|
|
||||||
for single_dict in dicts:
|
|
||||||
for key, value in single_dict.items():
|
|
||||||
if key not in combined_dict:
|
|
||||||
combined_dict[key] = {}
|
|
||||||
for subkey, subvalue in value.items():
|
|
||||||
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):
|
def future_thread_executor(args: list, workers: int = -1):
|
||||||
futures_list = []
|
futures_list = []
|
||||||
results = []
|
results = []
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
import asyncio, aiohttp
|
import asyncio, aiohttp, traceback
|
||||||
|
|
||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
search_mapping,
|
search_mapping,
|
||||||
|
)
|
||||||
|
from src.library import (
|
||||||
check_skip_logic,
|
check_skip_logic,
|
||||||
generate_library_guids_dict,
|
generate_library_guids_dict,
|
||||||
|
)
|
||||||
|
from src.watched import (
|
||||||
combine_watched_dicts,
|
combine_watched_dicts,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,21 +43,33 @@ class Jellyfin:
|
|||||||
async with session.get(
|
async with session.get(
|
||||||
self.baseurl + query, headers=headers
|
self.baseurl + query, headers=headers
|
||||||
) as response:
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"Query failed with status {response.status} {response.reason}"
|
||||||
|
)
|
||||||
results = await response.json()
|
results = await response.json()
|
||||||
|
|
||||||
elif query_type == "post":
|
elif query_type == "post":
|
||||||
async with session.post(
|
async with session.post(
|
||||||
self.baseurl + query, headers=headers
|
self.baseurl + query, headers=headers
|
||||||
) as response:
|
) as response:
|
||||||
|
if response.status != 200:
|
||||||
|
raise Exception(
|
||||||
|
f"Query failed with status {response.status} {response.reason}"
|
||||||
|
)
|
||||||
results = await response.json()
|
results = await response.json()
|
||||||
|
|
||||||
|
if not isinstance(results, list) and not isinstance(results, dict):
|
||||||
|
raise Exception("Query result is not of type list or dict")
|
||||||
|
|
||||||
# append identifiers to results
|
# append identifiers to results
|
||||||
if identifiers:
|
if identifiers:
|
||||||
results["Identifiers"] = identifiers
|
results["Identifiers"] = identifiers
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger(f"Jellyfin: Query failed {e}", 2)
|
logger(f"Jellyfin: Query {query_type} {query}\nResults {results}\n{e}", 2)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
async def get_users(self):
|
async def get_users(self):
|
||||||
@@ -63,7 +80,7 @@ class Jellyfin:
|
|||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
response = await self.query(query_string, "get", session)
|
response = await self.query(query_string, "get", session)
|
||||||
|
|
||||||
# If reponse is not empty
|
# If response is not empty
|
||||||
if response:
|
if response:
|
||||||
for user in response:
|
for user in response:
|
||||||
users[user["Name"]] = user["Id"]
|
users[user["Name"]] = user["Id"]
|
||||||
@@ -148,7 +165,7 @@ class Jellyfin:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type == "Series":
|
if library_type in ["Series", "Episode"]:
|
||||||
# Initialize an empty dictionary for the given user and library
|
# Initialize an empty dictionary for the given user and library
|
||||||
user_watched[user_name][library_title] = {}
|
user_watched[user_name][library_title] = {}
|
||||||
|
|
||||||
@@ -184,6 +201,7 @@ class Jellyfin:
|
|||||||
"show_guids": show_guids,
|
"show_guids": show_guids,
|
||||||
"show_id": show["Id"],
|
"show_id": show["Id"],
|
||||||
}
|
}
|
||||||
|
|
||||||
season_task = asyncio.ensure_future(
|
season_task = asyncio.ensure_future(
|
||||||
self.query(
|
self.query(
|
||||||
f"/Shows/{show['Id']}/Seasons"
|
f"/Shows/{show['Id']}/Seasons"
|
||||||
@@ -309,7 +327,9 @@ class Jellyfin:
|
|||||||
f"Jellyfin: Failed to get watched for {user_name} in library {library_title}, Error: {e}",
|
f"Jellyfin: Failed to get watched for {user_name} in library {library_title}, Error: {e}",
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
raise Exception(e)
|
|
||||||
|
logger(traceback.format_exc(), 2)
|
||||||
|
return {}
|
||||||
|
|
||||||
async def get_users_watched(
|
async def get_users_watched(
|
||||||
self,
|
self,
|
||||||
@@ -362,7 +382,7 @@ class Jellyfin:
|
|||||||
[
|
[
|
||||||
x["Type"]
|
x["Type"]
|
||||||
for x in watched["Items"]
|
for x in watched["Items"]
|
||||||
if x["Type"] in ["Movie", "Series"]
|
if x["Type"] in ["Movie", "Series", "Episode"]
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -378,15 +398,16 @@ class Jellyfin:
|
|||||||
|
|
||||||
if skip_reason:
|
if skip_reason:
|
||||||
logger(
|
logger(
|
||||||
f"Jellyfin: Skipping library {library_title} {skip_reason}",
|
f"Jellyfin: Skipping library {library_title}: {skip_reason}",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# If there are multiple types in library raise error
|
# If there are multiple types in library raise error
|
||||||
if types is None or len(types) < 1:
|
if types is None or len(types) < 1:
|
||||||
|
all_types = set([x["Type"] for x in watched["Items"]])
|
||||||
logger(
|
logger(
|
||||||
f"Jellyfin: Skipping Library {library_title} not a single type: {types}",
|
f"Jellyfin: Skipping Library {library_title} found types: {types}, all types: {all_types}",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|||||||
212
src/library.py
Normal file
212
src/library.py
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
from src.functions import (
|
||||||
|
logger,
|
||||||
|
search_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def check_skip_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping=None,
|
||||||
|
):
|
||||||
|
skip_reason = None
|
||||||
|
library_other = None
|
||||||
|
if library_mapping:
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
|
||||||
|
skip_reason_black = check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
skip_reason_white = check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Combine skip reasons
|
||||||
|
if skip_reason_black:
|
||||||
|
skip_reason = skip_reason_black
|
||||||
|
|
||||||
|
if skip_reason_white:
|
||||||
|
if skip_reason:
|
||||||
|
skip_reason = skip_reason + " and " + skip_reason_white
|
||||||
|
else:
|
||||||
|
skip_reason = skip_reason_white
|
||||||
|
|
||||||
|
return skip_reason
|
||||||
|
|
||||||
|
|
||||||
|
def check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other=None,
|
||||||
|
):
|
||||||
|
skip_reason = None
|
||||||
|
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 = f"{library_type_item} is in blacklist_library_type"
|
||||||
|
else:
|
||||||
|
if library_type.lower() in blacklist_library_type:
|
||||||
|
skip_reason = f"{library_type} is in blacklist_library_type"
|
||||||
|
|
||||||
|
if library_title.lower() in [x.lower() for x in blacklist_library]:
|
||||||
|
if skip_reason:
|
||||||
|
skip_reason = (
|
||||||
|
skip_reason + " and " + f"{library_title} is in blacklist_library"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
skip_reason = f"{library_title} is in blacklist_library"
|
||||||
|
|
||||||
|
if library_other:
|
||||||
|
if library_other.lower() in [x.lower() for x in blacklist_library]:
|
||||||
|
if skip_reason:
|
||||||
|
skip_reason = (
|
||||||
|
skip_reason + " and " + f"{library_other} is in blacklist_library"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
skip_reason = f"{library_other} is in blacklist_library"
|
||||||
|
|
||||||
|
return skip_reason
|
||||||
|
|
||||||
|
|
||||||
|
def check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other=None,
|
||||||
|
):
|
||||||
|
skip_reason = None
|
||||||
|
if len(whitelist_library_type) > 0:
|
||||||
|
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 = (
|
||||||
|
f"{library_type_item} is not in whitelist_library_type"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if library_type.lower() not in whitelist_library_type:
|
||||||
|
skip_reason = f"{library_type} is not in whitelist_library_type"
|
||||||
|
|
||||||
|
# if whitelist is not empty and library is not in whitelist
|
||||||
|
if len(whitelist_library) > 0:
|
||||||
|
if library_other:
|
||||||
|
if library_title.lower() not in [
|
||||||
|
x.lower() for x in whitelist_library
|
||||||
|
] and library_other.lower() not in [x.lower() for x in whitelist_library]:
|
||||||
|
if skip_reason:
|
||||||
|
skip_reason = (
|
||||||
|
skip_reason
|
||||||
|
+ " and "
|
||||||
|
+ f"{library_title} is not in whitelist_library"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
skip_reason = f"{library_title} is not in whitelist_library"
|
||||||
|
else:
|
||||||
|
if library_title.lower() not in [x.lower() for x in whitelist_library]:
|
||||||
|
if skip_reason:
|
||||||
|
skip_reason = (
|
||||||
|
skip_reason
|
||||||
|
+ " and "
|
||||||
|
+ f"{library_title} is not in whitelist_library"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
skip_reason = f"{library_title} is not in whitelist_library"
|
||||||
|
|
||||||
|
return skip_reason
|
||||||
|
|
||||||
|
|
||||||
|
def show_title_dict(user_list: dict):
|
||||||
|
try:
|
||||||
|
show_output_dict = {}
|
||||||
|
|
||||||
|
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, provider_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()] = []
|
||||||
|
if provider_key.lower() == "locations":
|
||||||
|
for show_location in provider_value:
|
||||||
|
show_output_dict[provider_key.lower()].append(show_location)
|
||||||
|
else:
|
||||||
|
show_output_dict[provider_key.lower()].append(
|
||||||
|
provider_value.lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
return show_output_dict
|
||||||
|
except Exception:
|
||||||
|
logger("Generating show_output_dict failed, skipping", 1)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def episode_title_dict(user_list: dict):
|
||||||
|
try:
|
||||||
|
episode_output_dict = {}
|
||||||
|
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()] = []
|
||||||
|
if episode_key == "locations":
|
||||||
|
for episode_location in episode_value:
|
||||||
|
episode_output_dict[episode_key.lower()].append(
|
||||||
|
episode_location
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
episode_output_dict[episode_key.lower()].append(
|
||||||
|
episode_value.lower()
|
||||||
|
)
|
||||||
|
|
||||||
|
return episode_output_dict
|
||||||
|
except Exception:
|
||||||
|
logger("Generating episode_output_dict failed, skipping", 1)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def movies_title_dict(user_list: dict):
|
||||||
|
try:
|
||||||
|
movies_output_dict = {}
|
||||||
|
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()] = []
|
||||||
|
if movie_key == "locations":
|
||||||
|
for movie_location in movie_value:
|
||||||
|
movies_output_dict[movie_key.lower()].append(movie_location)
|
||||||
|
else:
|
||||||
|
movies_output_dict[movie_key.lower()].append(movie_value.lower())
|
||||||
|
|
||||||
|
return movies_output_dict
|
||||||
|
except Exception:
|
||||||
|
logger("Generating movies_output_dict failed, skipping", 1)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def generate_library_guids_dict(user_list: 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 = show_title_dict(user_list)
|
||||||
|
episode_output_dict = episode_title_dict(user_list)
|
||||||
|
movies_output_dict = movies_title_dict(user_list)
|
||||||
|
|
||||||
|
return show_output_dict, episode_output_dict, movies_output_dict
|
||||||
193
src/main.py
193
src/main.py
@@ -5,10 +5,18 @@ from time import sleep, perf_counter
|
|||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
str_to_bool,
|
str_to_bool,
|
||||||
search_mapping,
|
|
||||||
cleanup_watched,
|
|
||||||
setup_black_white_lists,
|
|
||||||
)
|
)
|
||||||
|
from src.users import (
|
||||||
|
generate_user_list,
|
||||||
|
combine_user_lists,
|
||||||
|
filter_user_lists,
|
||||||
|
generate_server_users,
|
||||||
|
)
|
||||||
|
from src.watched import (
|
||||||
|
cleanup_watched,
|
||||||
|
)
|
||||||
|
from src.black_white import setup_black_white_lists
|
||||||
|
|
||||||
from src.plex import Plex
|
from src.plex import Plex
|
||||||
from src.jellyfin import Jellyfin
|
from src.jellyfin import Jellyfin
|
||||||
|
|
||||||
@@ -18,107 +26,27 @@ load_dotenv(override=True)
|
|||||||
def setup_users(
|
def setup_users(
|
||||||
server_1, server_2, blacklist_users, whitelist_users, user_mapping=None
|
server_1, server_2, blacklist_users, whitelist_users, user_mapping=None
|
||||||
):
|
):
|
||||||
|
server_1_users = generate_user_list(server_1)
|
||||||
|
server_2_users = generate_user_list(server_2)
|
||||||
|
|
||||||
# generate list of users from server 1 and server 2
|
users = combine_user_lists(server_1_users, server_2_users, user_mapping)
|
||||||
server_1_type = server_1[0]
|
|
||||||
server_1_connection = server_1[1]
|
|
||||||
server_2_type = server_2[0]
|
|
||||||
server_2_connection = server_2[1]
|
|
||||||
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":
|
|
||||||
server_1_users = [x.title.lower() for x in server_1_connection.users]
|
|
||||||
elif server_1_type == "jellyfin":
|
|
||||||
server_1_users = [key.lower() for key in server_1_connection.users.keys()]
|
|
||||||
|
|
||||||
server_2_users = []
|
|
||||||
if server_2_type == "plex":
|
|
||||||
server_2_users = [x.title.lower() for x in server_2_connection.users]
|
|
||||||
elif server_2_type == "jellyfin":
|
|
||||||
server_2_users = [key.lower() for key in server_2_connection.users.keys()]
|
|
||||||
|
|
||||||
# combined list of overlapping users from plex and jellyfin
|
|
||||||
users = {}
|
|
||||||
|
|
||||||
for server_1_user in server_1_users:
|
|
||||||
if user_mapping:
|
|
||||||
jellyfin_plex_mapped_user = search_mapping(user_mapping, server_1_user)
|
|
||||||
if jellyfin_plex_mapped_user:
|
|
||||||
users[server_1_user] = jellyfin_plex_mapped_user
|
|
||||||
continue
|
|
||||||
|
|
||||||
if server_1_user in server_2_users:
|
|
||||||
users[server_1_user] = server_1_user
|
|
||||||
|
|
||||||
for server_2_user in server_2_users:
|
|
||||||
if user_mapping:
|
|
||||||
plex_jellyfin_mapped_user = search_mapping(user_mapping, server_2_user)
|
|
||||||
if plex_jellyfin_mapped_user:
|
|
||||||
users[plex_jellyfin_mapped_user] = server_2_user
|
|
||||||
continue
|
|
||||||
|
|
||||||
if server_2_user in server_1_users:
|
|
||||||
users[server_2_user] = server_2_user
|
|
||||||
|
|
||||||
logger(f"User list that exist on both servers {users}", 1)
|
logger(f"User list that exist on both servers {users}", 1)
|
||||||
|
|
||||||
users_filtered = {}
|
users_filtered = filter_user_lists(users, blacklist_users, whitelist_users)
|
||||||
for user in users:
|
|
||||||
# whitelist_user is not empty and user lowercase is not in whitelist lowercase
|
|
||||||
if len(whitelist_users) > 0:
|
|
||||||
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)
|
logger(f"Filtered user list {users_filtered}", 1)
|
||||||
|
|
||||||
if server_1_type == "plex":
|
output_server_1_users = generate_server_users(server_1, users_filtered)
|
||||||
output_server_1_users = []
|
output_server_2_users = generate_server_users(server_2, users_filtered)
|
||||||
for plex_user in server_1_connection.users:
|
|
||||||
if (
|
|
||||||
plex_user.title.lower() in users_filtered.keys()
|
|
||||||
or plex_user.title.lower() in users_filtered.values()
|
|
||||||
):
|
|
||||||
output_server_1_users.append(plex_user)
|
|
||||||
elif server_1_type == "jellyfin":
|
|
||||||
output_server_1_users = {}
|
|
||||||
for jellyfin_user, jellyfin_id in server_1_connection.users.items():
|
|
||||||
if (
|
|
||||||
jellyfin_user.lower() in users_filtered.keys()
|
|
||||||
or jellyfin_user.lower() in users_filtered.values()
|
|
||||||
):
|
|
||||||
output_server_1_users[jellyfin_user] = jellyfin_id
|
|
||||||
|
|
||||||
if server_2_type == "plex":
|
# Check if users is none or empty
|
||||||
output_server_2_users = []
|
if output_server_1_users is None or len(output_server_1_users) == 0:
|
||||||
for plex_user in server_2_connection.users:
|
|
||||||
if (
|
|
||||||
plex_user.title.lower() in users_filtered.keys()
|
|
||||||
or plex_user.title.lower() in users_filtered.values()
|
|
||||||
):
|
|
||||||
output_server_2_users.append(plex_user)
|
|
||||||
elif server_2_type == "jellyfin":
|
|
||||||
output_server_2_users = {}
|
|
||||||
for jellyfin_user, jellyfin_id in server_2_connection.users.items():
|
|
||||||
if (
|
|
||||||
jellyfin_user.lower() in users_filtered.keys()
|
|
||||||
or jellyfin_user.lower() in users_filtered.values()
|
|
||||||
):
|
|
||||||
output_server_2_users[jellyfin_user] = jellyfin_id
|
|
||||||
|
|
||||||
if len(output_server_1_users) == 0:
|
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"No users found for server 1 {server_1_type}, users found {users}, filtered users {users_filtered}, server 1 users {server_1_connection.users}"
|
f"No users found for server 1 {server_1[0]}, users found {users}, filtered users {users_filtered}, server 1 users {server_1[1].users}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(output_server_2_users) == 0:
|
if output_server_2_users is None or len(output_server_2_users) == 0:
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"No users found for server 2 {server_2_type}, users found {users} filtered users {users_filtered}, server 2 users {server_2_connection.users}"
|
f"No users found for server 2 {server_2[0]}, users found {users} filtered users {users_filtered}, server 2 users {server_2[1].users}"
|
||||||
)
|
)
|
||||||
|
|
||||||
logger(f"Server 1 users: {output_server_1_users}", 1)
|
logger(f"Server 1 users: {output_server_1_users}", 1)
|
||||||
@@ -264,6 +192,53 @@ def update_server_watched(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def should_sync_server(server_1_type, server_2_type):
|
||||||
|
sync_from_plex_to_jellyfin = str_to_bool(
|
||||||
|
os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True")
|
||||||
|
)
|
||||||
|
sync_from_jelly_to_plex = str_to_bool(
|
||||||
|
os.getenv("SYNC_FROM_JELLYFIN_TO_PLEX", "True")
|
||||||
|
)
|
||||||
|
sync_from_plex_to_plex = str_to_bool(os.getenv("SYNC_FROM_PLEX_TO_PLEX", "True"))
|
||||||
|
sync_from_jelly_to_jellyfin = str_to_bool(
|
||||||
|
os.getenv("SYNC_FROM_JELLYFIN_TO_JELLYFIN", "True")
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
server_1_type == "plex"
|
||||||
|
and server_2_type == "plex"
|
||||||
|
and not sync_from_plex_to_plex
|
||||||
|
):
|
||||||
|
logger("Sync between plex and plex is disabled", 1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
server_1_type == "plex"
|
||||||
|
and server_2_type == "jellyfin"
|
||||||
|
and not sync_from_jelly_to_plex
|
||||||
|
):
|
||||||
|
logger("Sync from jellyfin to plex disabled", 1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
server_1_type == "jellyfin"
|
||||||
|
and server_2_type == "jellyfin"
|
||||||
|
and not sync_from_jelly_to_jellyfin
|
||||||
|
):
|
||||||
|
logger("Sync between jellyfin and jellyfin is disabled", 1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
if (
|
||||||
|
server_1_type == "jellyfin"
|
||||||
|
and server_2_type == "plex"
|
||||||
|
and not sync_from_plex_to_jellyfin
|
||||||
|
):
|
||||||
|
logger("Sync from plex to jellyfin is disabled", 1)
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main_loop():
|
def main_loop():
|
||||||
logfile = os.getenv("LOGFILE", "log.log")
|
logfile = os.getenv("LOGFILE", "log.log")
|
||||||
# Delete logfile if it exists
|
# Delete logfile if it exists
|
||||||
@@ -370,21 +345,23 @@ def main_loop():
|
|||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
|
|
||||||
update_server_watched(
|
if should_sync_server(server_1[0], server_2[0]):
|
||||||
server_1,
|
update_server_watched(
|
||||||
server_2_watched_filtered,
|
server_1,
|
||||||
user_mapping,
|
server_2_watched_filtered,
|
||||||
library_mapping,
|
user_mapping,
|
||||||
dryrun,
|
library_mapping,
|
||||||
)
|
dryrun,
|
||||||
|
)
|
||||||
|
|
||||||
update_server_watched(
|
if should_sync_server(server_2[0], server_1[0]):
|
||||||
server_2,
|
update_server_watched(
|
||||||
server_1_watched_filtered,
|
server_2,
|
||||||
user_mapping,
|
server_1_watched_filtered,
|
||||||
library_mapping,
|
user_mapping,
|
||||||
dryrun,
|
library_mapping,
|
||||||
)
|
dryrun,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
197
src/plex.py
197
src/plex.py
@@ -1,4 +1,4 @@
|
|||||||
import re, requests
|
import re, requests, os, traceback
|
||||||
from urllib3.poolmanager import PoolManager
|
from urllib3.poolmanager import PoolManager
|
||||||
|
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
@@ -7,10 +7,13 @@ from plexapi.myplex import MyPlexAccount
|
|||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
search_mapping,
|
search_mapping,
|
||||||
check_skip_logic,
|
|
||||||
generate_library_guids_dict,
|
|
||||||
future_thread_executor,
|
future_thread_executor,
|
||||||
)
|
)
|
||||||
|
from src.library import (
|
||||||
|
check_skip_logic,
|
||||||
|
generate_library_guids_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
|
# Bypass hostname validation for ssl. Taken from https://github.com/pkkid/python-plexapi/issues/143#issuecomment-775485186
|
||||||
class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter):
|
class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter):
|
||||||
@@ -24,6 +27,52 @@ class HostNameIgnoringAdapter(requests.adapters.HTTPAdapter):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_user_library_watched_show(show):
|
||||||
|
try:
|
||||||
|
show_guids = {}
|
||||||
|
for show_guid in show.guids:
|
||||||
|
# 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
|
||||||
|
show_guids["locations"] = tuple([x.split("/")[-1] for x in show.locations])
|
||||||
|
show_guids = frozenset(show_guids.items())
|
||||||
|
|
||||||
|
# Get all watched episodes for show
|
||||||
|
episode_guids = {}
|
||||||
|
watched_episodes = show.watched()
|
||||||
|
for episode in watched_episodes:
|
||||||
|
episode_guids_temp = {}
|
||||||
|
try:
|
||||||
|
if len(episode.guids) > 0:
|
||||||
|
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
|
||||||
|
except Exception:
|
||||||
|
logger(
|
||||||
|
f"Plex: Failed to get guids for {episode.title} in {show.title}, Using location only",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
return show_guids, episode_guids
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
return {}, {}
|
||||||
|
|
||||||
|
|
||||||
def get_user_library_watched(user, user_plex, library):
|
def get_user_library_watched(user, user_plex, library):
|
||||||
try:
|
try:
|
||||||
user_name = user.title.lower()
|
user_name = user.title.lower()
|
||||||
@@ -61,41 +110,17 @@ def get_user_library_watched(user, user_plex, library):
|
|||||||
|
|
||||||
elif library.type == "show":
|
elif library.type == "show":
|
||||||
user_watched[user_name][library.title] = {}
|
user_watched[user_name][library.title] = {}
|
||||||
|
shows = library_videos.search(unwatched=False)
|
||||||
|
|
||||||
for show in library_videos.search(unwatched=False):
|
# Parallelize show processing
|
||||||
logger(f"Plex: Adding {show.title} to {user_name} watched list", 3)
|
args = []
|
||||||
show_guids = {}
|
for show in shows:
|
||||||
for show_guid in show.guids:
|
args.append([get_user_library_watched_show, show])
|
||||||
# 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
|
for show_guids, episode_guids in future_thread_executor(
|
||||||
show_guids["locations"] = tuple(
|
args, workers=min(os.cpu_count(), 4)
|
||||||
[x.split("/")[-1] for x in show.locations]
|
):
|
||||||
)
|
if show_guids and episode_guids:
|
||||||
show_guids = frozenset(show_guids.items())
|
|
||||||
|
|
||||||
# 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]
|
|
||||||
)
|
|
||||||
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
|
# append show, season, episode
|
||||||
if show_guids not in user_watched[user_name][library.title]:
|
if show_guids not in user_watched[user_name][library.title]:
|
||||||
user_watched[user_name][library.title][show_guids] = {}
|
user_watched[user_name][library.title][show_guids] = {}
|
||||||
@@ -116,7 +141,27 @@ def get_user_library_watched(user, user_plex, library):
|
|||||||
f"Plex: Failed to get watched for {user_name} in library {library.title}, Error: {e}",
|
f"Plex: Failed to get watched for {user_name} in library {library.title}, Error: {e}",
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
raise Exception(e)
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def find_video(plex_search, video_ids):
|
||||||
|
try:
|
||||||
|
for location in plex_search.locations:
|
||||||
|
if location.split("/")[-1] in video_ids["locations"]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
for guid in plex_search.guids:
|
||||||
|
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
|
||||||
|
if guid_source in video_ids.keys():
|
||||||
|
if guid_id in video_ids[guid_source]:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def update_user_watched(user, user_plex, library, videos, dryrun):
|
def update_user_watched(user, user_plex, library, videos, dryrun):
|
||||||
@@ -135,26 +180,7 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
library_videos = user_plex.library.section(library)
|
library_videos = user_plex.library.section(library)
|
||||||
if videos_movies_ids:
|
if videos_movies_ids:
|
||||||
for movies_search in library_videos.search(unwatched=True):
|
for movies_search in library_videos.search(unwatched=True):
|
||||||
movie_found = False
|
if find_video(movies_search, videos_movies_ids):
|
||||||
for movie_location in movies_search.locations:
|
|
||||||
if movie_location.split("/")[-1] in videos_movies_ids["locations"]:
|
|
||||||
movie_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not movie_found:
|
|
||||||
for movie_guid in movies_search.guids:
|
|
||||||
movie_guid_source = (
|
|
||||||
re.search(r"(.*)://", movie_guid.id).group(1).lower()
|
|
||||||
)
|
|
||||||
movie_guid_id = re.search(r"://(.*)", movie_guid.id).group(1)
|
|
||||||
|
|
||||||
# If movie provider source and movie provider id are in videos_movie_ids exactly, then the movie is in the list
|
|
||||||
if movie_guid_source in videos_movies_ids.keys():
|
|
||||||
if movie_guid_id in videos_movies_ids[movie_guid_source]:
|
|
||||||
movie_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if movie_found:
|
|
||||||
msg = f"{movies_search.title} as watched for {user.title} in {library} for Plex"
|
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)
|
||||||
@@ -169,58 +195,9 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
|
|
||||||
if videos_shows_ids and videos_episodes_ids:
|
if videos_shows_ids and videos_episodes_ids:
|
||||||
for show_search in library_videos.search(unwatched=True):
|
for show_search in library_videos.search(unwatched=True):
|
||||||
show_found = False
|
if find_video(show_search, videos_shows_ids):
|
||||||
for show_location in show_search.locations:
|
|
||||||
if show_location.split("/")[-1] in videos_shows_ids["locations"]:
|
|
||||||
show_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not show_found:
|
|
||||||
for show_guid in show_search.guids:
|
|
||||||
show_guid_source = (
|
|
||||||
re.search(r"(.*)://", show_guid.id).group(1).lower()
|
|
||||||
)
|
|
||||||
show_guid_id = re.search(r"://(.*)", show_guid.id).group(1)
|
|
||||||
|
|
||||||
# If show provider source and show provider id are in videos_shows_ids exactly, then the show is in the list
|
|
||||||
if show_guid_source in videos_shows_ids.keys():
|
|
||||||
if show_guid_id in videos_shows_ids[show_guid_source]:
|
|
||||||
show_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if show_found:
|
|
||||||
for episode_search in show_search.episodes():
|
for episode_search in show_search.episodes():
|
||||||
episode_found = False
|
if find_video(episode_search, videos_episodes_ids):
|
||||||
|
|
||||||
for episode_location in episode_search.locations:
|
|
||||||
if (
|
|
||||||
episode_location.split("/")[-1]
|
|
||||||
in videos_episodes_ids["locations"]
|
|
||||||
):
|
|
||||||
episode_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not episode_found:
|
|
||||||
for episode_guid in episode_search.guids:
|
|
||||||
episode_guid_source = (
|
|
||||||
re.search(r"(.*)://", episode_guid.id)
|
|
||||||
.group(1)
|
|
||||||
.lower()
|
|
||||||
)
|
|
||||||
episode_guid_id = re.search(
|
|
||||||
r"://(.*)", episode_guid.id
|
|
||||||
).group(1)
|
|
||||||
|
|
||||||
# If episode provider source and episode provider id are in videos_episodes_ids exactly, then the episode is in the list
|
|
||||||
if episode_guid_source in videos_episodes_ids.keys():
|
|
||||||
if (
|
|
||||||
episode_guid_id
|
|
||||||
in videos_episodes_ids[episode_guid_source]
|
|
||||||
):
|
|
||||||
episode_found = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if episode_found:
|
|
||||||
msg = f"{show_search.title} {episode_search.title} as watched for {user.title} in {library} for Plex"
|
msg = f"{show_search.title} {episode_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)
|
||||||
@@ -249,7 +226,7 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}",
|
f"Plex: Failed to update watched for {user.title} in library {library}, Error: {e}",
|
||||||
2,
|
2,
|
||||||
)
|
)
|
||||||
raise Exception(e)
|
logger(traceback.format_exc(), 2)
|
||||||
|
|
||||||
|
|
||||||
# class plex accept base url and token and username and password but default with none
|
# class plex accept base url and token and username and password but default with none
|
||||||
@@ -353,7 +330,7 @@ class Plex:
|
|||||||
|
|
||||||
if skip_reason:
|
if skip_reason:
|
||||||
logger(
|
logger(
|
||||||
f"Plex: Skipping library {library_title} {skip_reason}", 1
|
f"Plex: Skipping library {library_title}: {skip_reason}", 1
|
||||||
)
|
)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
83
src/users.py
Normal file
83
src/users.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
from src.functions import (
|
||||||
|
logger,
|
||||||
|
search_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_user_list(server):
|
||||||
|
# generate list of users from server 1 and server 2
|
||||||
|
server_type = server[0]
|
||||||
|
server_connection = server[1]
|
||||||
|
|
||||||
|
server_users = []
|
||||||
|
if server_type == "plex":
|
||||||
|
server_users = [x.title.lower() for x in server_connection.users]
|
||||||
|
elif server_type == "jellyfin":
|
||||||
|
server_users = [key.lower() for key in server_connection.users.keys()]
|
||||||
|
|
||||||
|
return server_users
|
||||||
|
|
||||||
|
|
||||||
|
def combine_user_lists(server_1_users, server_2_users, user_mapping):
|
||||||
|
# combined list of overlapping users from plex and jellyfin
|
||||||
|
users = {}
|
||||||
|
|
||||||
|
for server_1_user in server_1_users:
|
||||||
|
if user_mapping:
|
||||||
|
mapped_user = search_mapping(user_mapping, server_1_user)
|
||||||
|
if mapped_user in server_2_users:
|
||||||
|
users[server_1_user] = mapped_user
|
||||||
|
continue
|
||||||
|
|
||||||
|
if server_1_user in server_2_users:
|
||||||
|
users[server_1_user] = server_1_user
|
||||||
|
|
||||||
|
for server_2_user in server_2_users:
|
||||||
|
if user_mapping:
|
||||||
|
mapped_user = search_mapping(user_mapping, server_2_user)
|
||||||
|
if mapped_user in server_1_users:
|
||||||
|
users[mapped_user] = server_2_user
|
||||||
|
continue
|
||||||
|
|
||||||
|
if server_2_user in server_1_users:
|
||||||
|
users[server_2_user] = server_2_user
|
||||||
|
|
||||||
|
return users
|
||||||
|
|
||||||
|
|
||||||
|
def filter_user_lists(users, blacklist_users, whitelist_users):
|
||||||
|
users_filtered = {}
|
||||||
|
for user in users:
|
||||||
|
# whitelist_user is not empty and user lowercase is not in whitelist lowercase
|
||||||
|
if len(whitelist_users) > 0:
|
||||||
|
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]
|
||||||
|
|
||||||
|
return users_filtered
|
||||||
|
|
||||||
|
|
||||||
|
def generate_server_users(server, users):
|
||||||
|
server_users = None
|
||||||
|
|
||||||
|
if server[0] == "plex":
|
||||||
|
server_users = []
|
||||||
|
for plex_user in server[1].users:
|
||||||
|
if (
|
||||||
|
plex_user.title.lower() in users.keys()
|
||||||
|
or plex_user.title.lower() in users.values()
|
||||||
|
):
|
||||||
|
server_users.append(plex_user)
|
||||||
|
elif server[0] == "jellyfin":
|
||||||
|
server_users = {}
|
||||||
|
for jellyfin_user, jellyfin_id in server[1].users.items():
|
||||||
|
if (
|
||||||
|
jellyfin_user.lower() in users.keys()
|
||||||
|
or jellyfin_user.lower() in users.values()
|
||||||
|
):
|
||||||
|
server_users[jellyfin_user] = jellyfin_id
|
||||||
|
|
||||||
|
return server_users
|
||||||
192
src/watched.py
Normal file
192
src/watched.py
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
import copy
|
||||||
|
|
||||||
|
from src.functions import (
|
||||||
|
logger,
|
||||||
|
search_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
from src.library import generate_library_guids_dict
|
||||||
|
|
||||||
|
|
||||||
|
def combine_watched_dicts(dicts: list):
|
||||||
|
combined_dict = {}
|
||||||
|
for single_dict in dicts:
|
||||||
|
for key, value in single_dict.items():
|
||||||
|
if key not in combined_dict:
|
||||||
|
combined_dict[key] = {}
|
||||||
|
for subkey, subvalue in value.items():
|
||||||
|
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, object_1, object_2):
|
||||||
|
if object_1 in watched_list:
|
||||||
|
return object_1
|
||||||
|
elif object_2 in watched_list:
|
||||||
|
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
|
||||||
78
test/test_black_white.py
Normal file
78
test/test_black_white.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# getting the name of the directory
|
||||||
|
# where the this file is present.
|
||||||
|
current = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
# Getting the parent directory name
|
||||||
|
# where the current directory is present.
|
||||||
|
parent = os.path.dirname(current)
|
||||||
|
|
||||||
|
# adding the parent directory to
|
||||||
|
# the sys.path.
|
||||||
|
sys.path.append(parent)
|
||||||
|
|
||||||
|
from src.black_white 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"]
|
||||||
302
test/test_library.py
Normal file
302
test/test_library.py
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
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.functions import (
|
||||||
|
search_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
from src.library import (
|
||||||
|
check_skip_logic,
|
||||||
|
check_blacklist_logic,
|
||||||
|
check_whitelist_logic,
|
||||||
|
show_title_dict,
|
||||||
|
episode_title_dict,
|
||||||
|
movies_title_dict,
|
||||||
|
generate_library_guids_dict,
|
||||||
|
)
|
||||||
|
|
||||||
|
blacklist_library = ["TV Shows"]
|
||||||
|
whitelist_library = ["Movies"]
|
||||||
|
blacklist_library_type = ["episodes"]
|
||||||
|
whitelist_library_type = ["movies"]
|
||||||
|
library_mapping = {"Shows": "TV Shows", "Movie": "Movies"}
|
||||||
|
|
||||||
|
show_list = {
|
||||||
|
frozenset(
|
||||||
|
{
|
||||||
|
("locations", ("The Last of Us",)),
|
||||||
|
("tmdb", "100088"),
|
||||||
|
("imdb", "tt3581920"),
|
||||||
|
("tvdb", "392256"),
|
||||||
|
("title", "The Last of Us"),
|
||||||
|
}
|
||||||
|
): {
|
||||||
|
"Season 1": [
|
||||||
|
{
|
||||||
|
"imdb": "tt11957006",
|
||||||
|
"tmdb": "2181581",
|
||||||
|
"tvdb": "8444132",
|
||||||
|
"locations": (
|
||||||
|
"The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
movie_list = [
|
||||||
|
{
|
||||||
|
"title": "Coco",
|
||||||
|
"imdb": "tt2380307",
|
||||||
|
"tmdb": "354912",
|
||||||
|
"locations": ("Coco (2017) Remux-2160p.mkv", "Coco (2017) Remux-1080p.mkv"),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
show_titles = {
|
||||||
|
"imdb": ["tt3581920"],
|
||||||
|
"locations": ["The Last of Us"],
|
||||||
|
"tmdb": ["100088"],
|
||||||
|
"tvdb": ["392256"],
|
||||||
|
}
|
||||||
|
episode_titles = {
|
||||||
|
"imdb": ["tt11957006"],
|
||||||
|
"locations": [
|
||||||
|
"The Last of Us - S01E01 - When You're Lost in the Darkness WEBDL-1080p.mkv"
|
||||||
|
],
|
||||||
|
"tmdb": ["2181581"],
|
||||||
|
"tvdb": ["8444132"],
|
||||||
|
}
|
||||||
|
movie_titles = {
|
||||||
|
"imdb": ["tt2380307"],
|
||||||
|
"locations": ["Coco (2017) Remux-2160p.mkv", "Coco (2017) Remux-1080p.mkv"],
|
||||||
|
"title": ["coco"],
|
||||||
|
"tmdb": ["354912"],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_skip_logic():
|
||||||
|
# Failes
|
||||||
|
library_title = "Test"
|
||||||
|
library_type = "movies"
|
||||||
|
skip_reason = check_skip_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == "Test is not in whitelist_library"
|
||||||
|
|
||||||
|
library_title = "Shows"
|
||||||
|
library_type = "episodes"
|
||||||
|
skip_reason = check_skip_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
skip_reason
|
||||||
|
== "episodes is in blacklist_library_type and TV Shows is in blacklist_library and "
|
||||||
|
+ "episodes is not in whitelist_library_type and Shows is not in whitelist_library"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Passes
|
||||||
|
library_title = "Movie"
|
||||||
|
library_type = "movies"
|
||||||
|
skip_reason = check_skip_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == None
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_blacklist_logic():
|
||||||
|
# Fails
|
||||||
|
library_title = "Shows"
|
||||||
|
library_type = "episodes"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
skip_reason
|
||||||
|
== "episodes is in blacklist_library_type and TV Shows is in blacklist_library"
|
||||||
|
)
|
||||||
|
|
||||||
|
library_title = "TV Shows"
|
||||||
|
library_type = "episodes"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
skip_reason
|
||||||
|
== "episodes is in blacklist_library_type and TV Shows is in blacklist_library"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Passes
|
||||||
|
library_title = "Movie"
|
||||||
|
library_type = "movies"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == None
|
||||||
|
|
||||||
|
library_title = "Movies"
|
||||||
|
library_type = "movies"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_blacklist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == None
|
||||||
|
|
||||||
|
|
||||||
|
def test_check_whitelist_logic():
|
||||||
|
# Fails
|
||||||
|
library_title = "Shows"
|
||||||
|
library_type = "episodes"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
skip_reason
|
||||||
|
== "episodes is not in whitelist_library_type and Shows is not in whitelist_library"
|
||||||
|
)
|
||||||
|
|
||||||
|
library_title = "TV Shows"
|
||||||
|
library_type = "episodes"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
skip_reason
|
||||||
|
== "episodes is not in whitelist_library_type and TV Shows is not in whitelist_library"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Passes
|
||||||
|
library_title = "Movie"
|
||||||
|
library_type = "movies"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == None
|
||||||
|
|
||||||
|
library_title = "Movies"
|
||||||
|
library_type = "movies"
|
||||||
|
library_other = search_mapping(library_mapping, library_title)
|
||||||
|
skip_reason = check_whitelist_logic(
|
||||||
|
library_title,
|
||||||
|
library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_other,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert skip_reason == None
|
||||||
|
|
||||||
|
|
||||||
|
def test_show_title_dict():
|
||||||
|
show_titles_dict = show_title_dict(show_list)
|
||||||
|
|
||||||
|
assert show_titles_dict == show_titles
|
||||||
|
|
||||||
|
|
||||||
|
def test_episode_title_dict():
|
||||||
|
episode_titles_dict = episode_title_dict(show_list)
|
||||||
|
|
||||||
|
assert episode_titles_dict == episode_titles
|
||||||
|
|
||||||
|
|
||||||
|
def test_movies_title_dict():
|
||||||
|
movies_titles_dict = movies_title_dict(movie_list)
|
||||||
|
|
||||||
|
assert movies_titles_dict == movie_titles
|
||||||
|
|
||||||
|
|
||||||
|
def test_generate_library_guids_dict():
|
||||||
|
# Test with shows
|
||||||
|
(
|
||||||
|
show_titles_dict,
|
||||||
|
episode_titles_dict,
|
||||||
|
movies_titles_dict,
|
||||||
|
) = generate_library_guids_dict(show_list)
|
||||||
|
|
||||||
|
assert show_titles_dict == show_titles
|
||||||
|
assert episode_titles_dict == episode_titles
|
||||||
|
assert movies_titles_dict == {}
|
||||||
|
|
||||||
|
# Test with movies
|
||||||
|
(
|
||||||
|
show_titles_dict,
|
||||||
|
episode_titles_dict,
|
||||||
|
movies_titles_dict,
|
||||||
|
) = generate_library_guids_dict(movie_list)
|
||||||
|
|
||||||
|
assert show_titles_dict == {}
|
||||||
|
assert episode_titles_dict == {}
|
||||||
|
assert movies_titles_dict == movie_titles
|
||||||
@@ -13,7 +13,7 @@ parent = os.path.dirname(current)
|
|||||||
# the sys.path.
|
# the sys.path.
|
||||||
sys.path.append(parent)
|
sys.path.append(parent)
|
||||||
|
|
||||||
from src.main import setup_black_white_lists
|
from src.black_white import setup_black_white_lists
|
||||||
|
|
||||||
|
|
||||||
def test_setup_black_white_lists():
|
def test_setup_black_white_lists():
|
||||||
|
|||||||
39
test/test_users.py
Normal file
39
test/test_users.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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.users import (
|
||||||
|
combine_user_lists,
|
||||||
|
filter_user_lists,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_user_lists():
|
||||||
|
server_1_users = ["test", "test3", "luigi311"]
|
||||||
|
server_2_users = ["luigi311", "test2", "test3"]
|
||||||
|
user_mapping = {"test2": "test"}
|
||||||
|
|
||||||
|
combined = combine_user_lists(server_1_users, server_2_users, user_mapping)
|
||||||
|
|
||||||
|
assert combined == {"luigi311": "luigi311", "test": "test2", "test3": "test3"}
|
||||||
|
|
||||||
|
|
||||||
|
def test_filter_user_lists():
|
||||||
|
users = {"luigi311": "luigi311", "test": "test2", "test3": "test3"}
|
||||||
|
blacklist_users = ["test3"]
|
||||||
|
whitelist_users = ["test", "luigi311"]
|
||||||
|
|
||||||
|
filtered = filter_user_lists(users, blacklist_users, whitelist_users)
|
||||||
|
|
||||||
|
assert filtered == {"test": "test2", "luigi311": "luigi311"}
|
||||||
@@ -1,301 +1,410 @@
|
|||||||
import sys
|
import sys
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# getting the name of the directory
|
# getting the name of the directory
|
||||||
# where the this file is present.
|
# where the this file is present.
|
||||||
current = os.path.dirname(os.path.realpath(__file__))
|
current = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
# Getting the parent directory name
|
# Getting the parent directory name
|
||||||
# where the current directory is present.
|
# where the current directory is present.
|
||||||
parent = os.path.dirname(current)
|
parent = os.path.dirname(current)
|
||||||
|
|
||||||
# adding the parent directory to
|
# adding the parent directory to
|
||||||
# the sys.path.
|
# the sys.path.
|
||||||
sys.path.append(parent)
|
sys.path.append(parent)
|
||||||
|
|
||||||
from src.main import cleanup_watched
|
from src.watched import cleanup_watched, combine_watched_dicts
|
||||||
|
|
||||||
tv_shows_watched_list_1 = {
|
tv_shows_watched_list_1 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
("tvdb", "75710"),
|
("tvdb", "75710"),
|
||||||
("title", "Criminal Minds"),
|
("title", "Criminal Minds"),
|
||||||
("imdb", "tt0452046"),
|
("imdb", "tt0452046"),
|
||||||
("locations", ("Criminal Minds",)),
|
("locations", ("Criminal Minds",)),
|
||||||
("tmdb", "4057"),
|
("tmdb", "4057"),
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{
|
{
|
||||||
"imdb": "tt0550489",
|
"imdb": "tt0550489",
|
||||||
"tmdb": "282843",
|
"tmdb": "282843",
|
||||||
"tvdb": "176357",
|
"tvdb": "176357",
|
||||||
"locations": (
|
"locations": (
|
||||||
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"imdb": "tt0550487",
|
"imdb": "tt0550487",
|
||||||
"tmdb": "282861",
|
"tmdb": "282861",
|
||||||
"tvdb": "300385",
|
"tvdb": "300385",
|
||||||
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
|
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{"locations": ("Test S01E01.mkv",)},
|
{"locations": ("Test S01E01.mkv",)},
|
||||||
{"locations": ("Test S01E02.mkv",)},
|
{"locations": ("Test S01E02.mkv",)},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
movies_watched_list_1 = [
|
movies_watched_list_1 = [
|
||||||
{
|
{
|
||||||
"imdb": "tt2380307",
|
"imdb": "tt2380307",
|
||||||
"tmdb": "354912",
|
"tmdb": "354912",
|
||||||
"title": "Coco",
|
"title": "Coco",
|
||||||
"locations": ("Coco (2017) Remux-1080p.mkv",),
|
"locations": ("Coco (2017) Remux-1080p.mkv",),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"tmdbcollection": "448150",
|
"tmdbcollection": "448150",
|
||||||
"imdb": "tt1431045",
|
"imdb": "tt1431045",
|
||||||
"tmdb": "293660",
|
"tmdb": "293660",
|
||||||
"title": "Deadpool",
|
"title": "Deadpool",
|
||||||
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
|
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
tv_shows_watched_list_2 = {
|
tv_shows_watched_list_2 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
("tvdb", "75710"),
|
("tvdb", "75710"),
|
||||||
("title", "Criminal Minds"),
|
("title", "Criminal Minds"),
|
||||||
("imdb", "tt0452046"),
|
("imdb", "tt0452046"),
|
||||||
("locations", ("Criminal Minds",)),
|
("locations", ("Criminal Minds",)),
|
||||||
("tmdb", "4057"),
|
("tmdb", "4057"),
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{
|
{
|
||||||
"imdb": "tt0550487",
|
"imdb": "tt0550487",
|
||||||
"tmdb": "282861",
|
"tmdb": "282861",
|
||||||
"tvdb": "300385",
|
"tvdb": "300385",
|
||||||
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
|
"locations": ("Criminal Minds S01E02 Compulsion WEBDL-720p.mkv",),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"imdb": "tt0550498",
|
"imdb": "tt0550498",
|
||||||
"tmdb": "282865",
|
"tmdb": "282865",
|
||||||
"tvdb": "300474",
|
"tvdb": "300474",
|
||||||
"locations": (
|
"locations": (
|
||||||
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
|
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{"locations": ("Test S01E02.mkv",)},
|
{"locations": ("Test S01E02.mkv",)},
|
||||||
{"locations": ("Test S01E03.mkv",)},
|
{"locations": ("Test S01E03.mkv",)},
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
movies_watched_list_2 = [
|
movies_watched_list_2 = [
|
||||||
{
|
{
|
||||||
"imdb": "tt2380307",
|
"imdb": "tt2380307",
|
||||||
"tmdb": "354912",
|
"tmdb": "354912",
|
||||||
"title": "Coco",
|
"title": "Coco",
|
||||||
"locations": ("Coco (2017) Remux-1080p.mkv",),
|
"locations": ("Coco (2017) Remux-1080p.mkv",),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"imdb": "tt0384793",
|
"imdb": "tt0384793",
|
||||||
"tmdb": "9788",
|
"tmdb": "9788",
|
||||||
"tvdb": "9103",
|
"tvdb": "9103",
|
||||||
"title": "Accepted",
|
"title": "Accepted",
|
||||||
"locations": ("Accepted (2006) Remux-1080p.mkv",),
|
"locations": ("Accepted (2006) Remux-1080p.mkv",),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
# Test to see if objects get deleted all the way up to the root.
|
# Test to see if objects get deleted all the way up to the root.
|
||||||
tv_shows_2_watched_list_1 = {
|
tv_shows_2_watched_list_1 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
("tvdb", "75710"),
|
("tvdb", "75710"),
|
||||||
("title", "Criminal Minds"),
|
("title", "Criminal Minds"),
|
||||||
("imdb", "tt0452046"),
|
("imdb", "tt0452046"),
|
||||||
("locations", ("Criminal Minds",)),
|
("locations", ("Criminal Minds",)),
|
||||||
("tmdb", "4057"),
|
("tmdb", "4057"),
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{
|
{
|
||||||
"imdb": "tt0550489",
|
"imdb": "tt0550489",
|
||||||
"tmdb": "282843",
|
"tmdb": "282843",
|
||||||
"tvdb": "176357",
|
"tvdb": "176357",
|
||||||
"locations": (
|
"locations": (
|
||||||
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_tv_show_watched_list_1 = {
|
expected_tv_show_watched_list_1 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
("tvdb", "75710"),
|
("tvdb", "75710"),
|
||||||
("title", "Criminal Minds"),
|
("title", "Criminal Minds"),
|
||||||
("imdb", "tt0452046"),
|
("imdb", "tt0452046"),
|
||||||
("locations", ("Criminal Minds",)),
|
("locations", ("Criminal Minds",)),
|
||||||
("tmdb", "4057"),
|
("tmdb", "4057"),
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{
|
{
|
||||||
"imdb": "tt0550489",
|
"imdb": "tt0550489",
|
||||||
"tmdb": "282843",
|
"tmdb": "282843",
|
||||||
"tvdb": "176357",
|
"tvdb": "176357",
|
||||||
"locations": (
|
"locations": (
|
||||||
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
"Criminal Minds S01E01 Extreme Aggressor WEBDL-720p.mkv",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
||||||
"Season 1": [{"locations": ("Test S01E01.mkv",)}]
|
"Season 1": [{"locations": ("Test S01E01.mkv",)}]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_movie_watched_list_1 = [
|
expected_movie_watched_list_1 = [
|
||||||
{
|
{
|
||||||
"tmdbcollection": "448150",
|
"tmdbcollection": "448150",
|
||||||
"imdb": "tt1431045",
|
"imdb": "tt1431045",
|
||||||
"tmdb": "293660",
|
"tmdb": "293660",
|
||||||
"title": "Deadpool",
|
"title": "Deadpool",
|
||||||
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
|
"locations": ("Deadpool (2016) Remux-1080p.mkv",),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_tv_show_watched_list_2 = {
|
expected_tv_show_watched_list_2 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
{
|
{
|
||||||
("tvdb", "75710"),
|
("tvdb", "75710"),
|
||||||
("title", "Criminal Minds"),
|
("title", "Criminal Minds"),
|
||||||
("imdb", "tt0452046"),
|
("imdb", "tt0452046"),
|
||||||
("locations", ("Criminal Minds",)),
|
("locations", ("Criminal Minds",)),
|
||||||
("tmdb", "4057"),
|
("tmdb", "4057"),
|
||||||
}
|
}
|
||||||
): {
|
): {
|
||||||
"Season 1": [
|
"Season 1": [
|
||||||
{
|
{
|
||||||
"imdb": "tt0550498",
|
"imdb": "tt0550498",
|
||||||
"tmdb": "282865",
|
"tmdb": "282865",
|
||||||
"tvdb": "300474",
|
"tvdb": "300474",
|
||||||
"locations": (
|
"locations": (
|
||||||
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
|
"Criminal Minds S01E03 Won't Get Fooled Again WEBDL-720p.mkv",
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
frozenset({("title", "Test"), ("locations", ("Test",))}): {
|
||||||
"Season 1": [{"locations": ("Test S01E03.mkv",)}]
|
"Season 1": [{"locations": ("Test S01E03.mkv",)}]
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_movie_watched_list_2 = [
|
expected_movie_watched_list_2 = [
|
||||||
{
|
{
|
||||||
"imdb": "tt0384793",
|
"imdb": "tt0384793",
|
||||||
"tmdb": "9788",
|
"tmdb": "9788",
|
||||||
"tvdb": "9103",
|
"tvdb": "9103",
|
||||||
"title": "Accepted",
|
"title": "Accepted",
|
||||||
"locations": ("Accepted (2006) Remux-1080p.mkv",),
|
"locations": ("Accepted (2006) Remux-1080p.mkv",),
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_simple_cleanup_watched():
|
def test_simple_cleanup_watched():
|
||||||
user_watched_list_1 = {
|
user_watched_list_1 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": tv_shows_watched_list_1,
|
"TV Shows": tv_shows_watched_list_1,
|
||||||
"Movies": movies_watched_list_1,
|
"Movies": movies_watched_list_1,
|
||||||
"Other Shows": tv_shows_2_watched_list_1,
|
"Other Shows": tv_shows_2_watched_list_1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
user_watched_list_2 = {
|
user_watched_list_2 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": tv_shows_watched_list_2,
|
"TV Shows": tv_shows_watched_list_2,
|
||||||
"Movies": movies_watched_list_2,
|
"Movies": movies_watched_list_2,
|
||||||
"Other Shows": tv_shows_2_watched_list_1,
|
"Other Shows": tv_shows_2_watched_list_1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_watched_list_1 = {
|
expected_watched_list_1 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": expected_tv_show_watched_list_1,
|
"TV Shows": expected_tv_show_watched_list_1,
|
||||||
"Movies": expected_movie_watched_list_1,
|
"Movies": expected_movie_watched_list_1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_watched_list_2 = {
|
expected_watched_list_2 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": expected_tv_show_watched_list_2,
|
"TV Shows": expected_tv_show_watched_list_2,
|
||||||
"Movies": expected_movie_watched_list_2,
|
"Movies": expected_movie_watched_list_2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return_watched_list_1 = cleanup_watched(user_watched_list_1, user_watched_list_2)
|
return_watched_list_1 = cleanup_watched(user_watched_list_1, user_watched_list_2)
|
||||||
return_watched_list_2 = cleanup_watched(user_watched_list_2, user_watched_list_1)
|
return_watched_list_2 = cleanup_watched(user_watched_list_2, user_watched_list_1)
|
||||||
|
|
||||||
assert return_watched_list_1 == expected_watched_list_1
|
assert return_watched_list_1 == expected_watched_list_1
|
||||||
assert return_watched_list_2 == expected_watched_list_2
|
assert return_watched_list_2 == expected_watched_list_2
|
||||||
|
|
||||||
|
|
||||||
def test_mapping_cleanup_watched():
|
def test_mapping_cleanup_watched():
|
||||||
user_watched_list_1 = {
|
user_watched_list_1 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": tv_shows_watched_list_1,
|
"TV Shows": tv_shows_watched_list_1,
|
||||||
"Movies": movies_watched_list_1,
|
"Movies": movies_watched_list_1,
|
||||||
"Other Shows": tv_shows_2_watched_list_1,
|
"Other Shows": tv_shows_2_watched_list_1,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
user_watched_list_2 = {
|
user_watched_list_2 = {
|
||||||
"user2": {
|
"user2": {
|
||||||
"Shows": tv_shows_watched_list_2,
|
"Shows": tv_shows_watched_list_2,
|
||||||
"Movies": movies_watched_list_2,
|
"Movies": movies_watched_list_2,
|
||||||
"Other Shows": tv_shows_2_watched_list_1,
|
"Other Shows": tv_shows_2_watched_list_1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_watched_list_1 = {
|
expected_watched_list_1 = {
|
||||||
"user1": {
|
"user1": {
|
||||||
"TV Shows": expected_tv_show_watched_list_1,
|
"TV Shows": expected_tv_show_watched_list_1,
|
||||||
"Movies": expected_movie_watched_list_1,
|
"Movies": expected_movie_watched_list_1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected_watched_list_2 = {
|
expected_watched_list_2 = {
|
||||||
"user2": {
|
"user2": {
|
||||||
"Shows": expected_tv_show_watched_list_2,
|
"Shows": expected_tv_show_watched_list_2,
|
||||||
"Movies": expected_movie_watched_list_2,
|
"Movies": expected_movie_watched_list_2,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user_mapping = {"user1": "user2"}
|
user_mapping = {"user1": "user2"}
|
||||||
library_mapping = {"TV Shows": "Shows"}
|
library_mapping = {"TV Shows": "Shows"}
|
||||||
|
|
||||||
return_watched_list_1 = cleanup_watched(
|
return_watched_list_1 = cleanup_watched(
|
||||||
user_watched_list_1,
|
user_watched_list_1,
|
||||||
user_watched_list_2,
|
user_watched_list_2,
|
||||||
user_mapping=user_mapping,
|
user_mapping=user_mapping,
|
||||||
library_mapping=library_mapping,
|
library_mapping=library_mapping,
|
||||||
)
|
)
|
||||||
return_watched_list_2 = cleanup_watched(
|
return_watched_list_2 = cleanup_watched(
|
||||||
user_watched_list_2,
|
user_watched_list_2,
|
||||||
user_watched_list_1,
|
user_watched_list_1,
|
||||||
user_mapping=user_mapping,
|
user_mapping=user_mapping,
|
||||||
library_mapping=library_mapping,
|
library_mapping=library_mapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
assert return_watched_list_1 == expected_watched_list_1
|
assert return_watched_list_1 == expected_watched_list_1
|
||||||
assert return_watched_list_2 == expected_watched_list_2
|
assert return_watched_list_2 == expected_watched_list_2
|
||||||
|
|
||||||
|
|
||||||
|
def test_combine_watched_dicts():
|
||||||
|
input_watched = [
|
||||||
|
{
|
||||||
|
"test3": {
|
||||||
|
"Anime Movies": [
|
||||||
|
{
|
||||||
|
"title": "Ponyo",
|
||||||
|
"tmdb": "12429",
|
||||||
|
"imdb": "tt0876563",
|
||||||
|
"locations": ("Ponyo (2008) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Spirited Away",
|
||||||
|
"tmdb": "129",
|
||||||
|
"imdb": "tt0245429",
|
||||||
|
"locations": ("Spirited Away (2001) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Castle in the Sky",
|
||||||
|
"tmdb": "10515",
|
||||||
|
"imdb": "tt0092067",
|
||||||
|
"locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"test3": {"Anime Shows": {}}},
|
||||||
|
{"test3": {"Cartoon Shows": {}}},
|
||||||
|
{
|
||||||
|
"test3": {
|
||||||
|
"Shows": {
|
||||||
|
frozenset(
|
||||||
|
{
|
||||||
|
("tmdb", "64464"),
|
||||||
|
("tvdb", "301824"),
|
||||||
|
("tvrage", "45210"),
|
||||||
|
("title", "11.22.63"),
|
||||||
|
("locations", ("11.22.63",)),
|
||||||
|
("imdb", "tt2879552"),
|
||||||
|
}
|
||||||
|
): {
|
||||||
|
"Season 1": [
|
||||||
|
{
|
||||||
|
"imdb": "tt4460418",
|
||||||
|
"title": "The Rabbit Hole",
|
||||||
|
"locations": (
|
||||||
|
"11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{"test3": {"Subbed Anime": {}}},
|
||||||
|
]
|
||||||
|
expected = {
|
||||||
|
"test3": {
|
||||||
|
"Anime Movies": [
|
||||||
|
{
|
||||||
|
"title": "Ponyo",
|
||||||
|
"tmdb": "12429",
|
||||||
|
"imdb": "tt0876563",
|
||||||
|
"locations": ("Ponyo (2008) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Spirited Away",
|
||||||
|
"tmdb": "129",
|
||||||
|
"imdb": "tt0245429",
|
||||||
|
"locations": ("Spirited Away (2001) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Castle in the Sky",
|
||||||
|
"tmdb": "10515",
|
||||||
|
"imdb": "tt0092067",
|
||||||
|
"locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"Anime Shows": {},
|
||||||
|
"Cartoon Shows": {},
|
||||||
|
"Shows": {
|
||||||
|
frozenset(
|
||||||
|
{
|
||||||
|
("tmdb", "64464"),
|
||||||
|
("tvdb", "301824"),
|
||||||
|
("tvrage", "45210"),
|
||||||
|
("title", "11.22.63"),
|
||||||
|
("locations", ("11.22.63",)),
|
||||||
|
("imdb", "tt2879552"),
|
||||||
|
}
|
||||||
|
): {
|
||||||
|
"Season 1": [
|
||||||
|
{
|
||||||
|
"imdb": "tt4460418",
|
||||||
|
"title": "The Rabbit Hole",
|
||||||
|
"locations": (
|
||||||
|
"11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv",
|
||||||
|
),
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Subbed Anime": {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert combine_watched_dicts(input_watched) == expected
|
||||||
Reference in New Issue
Block a user