Compare commits
43 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4771f736b0 | ||
|
|
8d7436579e | ||
|
|
43e1df98b1 | ||
|
|
3017030f52 | ||
|
|
348a0b8226 | ||
|
|
4e60c08120 | ||
|
|
10b58379cd | ||
|
|
fa9201b20f | ||
|
|
86f72997b4 | ||
|
|
62d0319aad | ||
|
|
a096a09eb7 | ||
|
|
7294241fed | ||
|
|
a5995d3999 | ||
|
|
30f31b2f3f | ||
|
|
bc09c873e9 | ||
|
|
8428be9dda | ||
|
|
6a45ad18f9 | ||
|
|
023b638729 | ||
|
|
7e13c14636 | ||
|
|
0c218fa9dd | ||
|
|
b3b0ccac73 | ||
|
|
fa0134551f | ||
|
|
34d62c9021 | ||
|
|
920bbbb3be | ||
|
|
762e5f10da | ||
|
|
27797cb361 | ||
|
|
066f9d1f66 | ||
|
|
acf7c2cdf2 | ||
|
|
469857a31a | ||
|
|
405e5decf2 | ||
|
|
da9abf8a24 | ||
|
|
128c6a1c76 | ||
|
|
99f32c10ef | ||
|
|
44e42f99db | ||
|
|
b1639eab0f | ||
|
|
679d3535b1 | ||
|
|
a795d4bba5 | ||
|
|
0a025cf5fa | ||
|
|
6a1ceb4db3 | ||
|
|
99c339c405 | ||
|
|
bd75d865ba | ||
|
|
d30e03b702 | ||
|
|
4a4c9f9ccf |
@@ -35,7 +35,7 @@ GENERATE_GUIDS = "True"
|
|||||||
GENERATE_LOCATIONS = "True"
|
GENERATE_LOCATIONS = "True"
|
||||||
|
|
||||||
## Max threads for processing
|
## Max threads for processing
|
||||||
MAX_THREADS = 32
|
MAX_THREADS = 2
|
||||||
|
|
||||||
## 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 separated for multiple options
|
## Comma separated for multiple options
|
||||||
|
|||||||
15
.github/FUNDING.yml
vendored
Normal file
15
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: [Luigi311]
|
||||||
|
patreon: # Replace with a single Patreon username
|
||||||
|
open_collective: # Replace with a single Open Collective username
|
||||||
|
ko_fi: # Replace with a single Ko-fi username
|
||||||
|
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||||
|
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
|
||||||
|
liberapay: # Replace with a single Liberapay username
|
||||||
|
issuehunt: # Replace with a single IssueHunt username
|
||||||
|
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||||
|
polar: # Replace with a single Polar username
|
||||||
|
buy_me_a_coffee: # Replace with a single Buy Me a Coffee username
|
||||||
|
thanks_dev: # Replace with a single thanks.dev username
|
||||||
|
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
|
||||||
28
.github/workflows/ci.yml
vendored
28
.github/workflows/ci.yml
vendored
@@ -10,12 +10,19 @@ on:
|
|||||||
- .gitignore
|
- .gitignore
|
||||||
- "*.md"
|
- "*.md"
|
||||||
|
|
||||||
|
env:
|
||||||
|
PYTHON_VERSION: '3.13'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
pytest:
|
pytest:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: pip install -r requirements.txt && pip install -r test/requirements.txt
|
run: pip install -r requirements.txt && pip install -r test/requirements.txt
|
||||||
|
|
||||||
@@ -27,6 +34,10 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: ${{ env.PYTHON_VERSION }}
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: |
|
run: |
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
@@ -46,7 +57,7 @@ jobs:
|
|||||||
sleep 10
|
sleep 10
|
||||||
|
|
||||||
for FOLDER in $(find "JellyPlex-Watched-CI" -type f -name "docker-compose.yml" -exec dirname {} \;); do
|
for FOLDER in $(find "JellyPlex-Watched-CI" -type f -name "docker-compose.yml" -exec dirname {} \;); do
|
||||||
docker-compose -f "${FOLDER}/docker-compose.yml" logs
|
docker compose -f "${FOLDER}/docker-compose.yml" logs
|
||||||
done
|
done
|
||||||
|
|
||||||
- name: "Test Plex"
|
- name: "Test Plex"
|
||||||
@@ -77,7 +88,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv test/ci_guids.env .env
|
mv test/ci_guids.env .env
|
||||||
python main.py
|
python main.py
|
||||||
python test/validate_ci_marklog.py --dry
|
python test/validate_ci_marklog.py --guids
|
||||||
|
|
||||||
rm mark.log
|
rm mark.log
|
||||||
|
|
||||||
@@ -85,7 +96,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mv test/ci_locations.env .env
|
mv test/ci_locations.env .env
|
||||||
python main.py
|
python main.py
|
||||||
python test/validate_ci_marklog.py --dry
|
python test/validate_ci_marklog.py --locations
|
||||||
|
|
||||||
rm mark.log
|
rm mark.log
|
||||||
|
|
||||||
@@ -129,18 +140,23 @@ jobs:
|
|||||||
${{ secrets.DOCKER_USERNAME }}/jellyplex-watched,enable=${{ secrets.DOCKER_USERNAME != '' }}
|
${{ secrets.DOCKER_USERNAME }}/jellyplex-watched,enable=${{ secrets.DOCKER_USERNAME != '' }}
|
||||||
# Do not push to ghcr.io on PRs due to permission issues, only push if the owner is luigi311 so it doesnt fail on forks
|
# Do not push to ghcr.io on PRs due to permission issues, only push if the owner is luigi311 so it doesnt fail on forks
|
||||||
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' && github.repository_owner == 'luigi311'}}
|
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' && github.repository_owner == 'luigi311'}}
|
||||||
|
flavor: latest=false
|
||||||
tags: |
|
tags: |
|
||||||
type=raw,value=latest,enable=${{ matrix.variant == env.DEFAULT_VARIANT && github.ref_name == github.event.repository.default_branch }}
|
type=raw,value=latest,enable=${{ matrix.variant == env.DEFAULT_VARIANT && startsWith(github.ref, 'refs/tags/') }}
|
||||||
type=raw,value=dev,enable=${{ matrix.variant == env.DEFAULT_VARIANT && github.ref_name == 'dev' }}
|
type=raw,value=latest,suffix=-${{ matrix.variant }},enable=${{ startsWith(github.ref, 'refs/tags/') }}
|
||||||
type=raw,value=latest,suffix=-${{ matrix.variant }},enable={{ is_default_branch }}
|
|
||||||
type=ref,event=branch,suffix=-${{ matrix.variant }}
|
type=ref,event=branch,suffix=-${{ matrix.variant }}
|
||||||
type=ref,event=branch,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
type=ref,event=branch,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
||||||
|
|
||||||
type=ref,event=pr,suffix=-${{ matrix.variant }}
|
type=ref,event=pr,suffix=-${{ matrix.variant }}
|
||||||
type=ref,event=pr,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
type=ref,event=pr,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
||||||
|
|
||||||
type=semver,pattern={{ version }},suffix=-${{ matrix.variant }}
|
type=semver,pattern={{ version }},suffix=-${{ matrix.variant }}
|
||||||
type=semver,pattern={{ version }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
type=semver,pattern={{ version }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
||||||
|
|
||||||
type=semver,pattern={{ major }}.{{ minor }},suffix=-${{ matrix.variant }}
|
type=semver,pattern={{ major }}.{{ minor }},suffix=-${{ matrix.variant }}
|
||||||
type=semver,pattern={{ major }}.{{ minor }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
type=semver,pattern={{ major }}.{{ minor }},enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
||||||
|
|
||||||
type=sha,suffix=-${{ matrix.variant }}
|
type=sha,suffix=-${{ matrix.variant }}
|
||||||
type=sha,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
type=sha,enable=${{ matrix.variant == env.DEFAULT_VARIANT }}
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,49 @@
|
|||||||
FROM python:3.11-alpine
|
FROM python:3.13-alpine
|
||||||
|
|
||||||
ENV DRYRUN 'True'
|
ENV PUID=1000
|
||||||
ENV DEBUG 'True'
|
ENV PGID=1000
|
||||||
ENV DEBUG_LEVEL 'INFO'
|
ENV GOSU_VERSION=1.17
|
||||||
ENV RUN_ONLY_ONCE 'False'
|
|
||||||
ENV SLEEP_DURATION '3600'
|
|
||||||
ENV LOGFILE 'log.log'
|
|
||||||
ENV MARKFILE 'mark.log'
|
|
||||||
|
|
||||||
ENV USER_MAPPING ''
|
RUN apk add --no-cache tini dos2unix
|
||||||
ENV LIBRARY_MAPPING ''
|
|
||||||
|
|
||||||
ENV PLEX_BASEURL ''
|
# Install gosu
|
||||||
ENV PLEX_TOKEN ''
|
RUN set -eux; \
|
||||||
ENV PLEX_USERNAME ''
|
\
|
||||||
ENV PLEX_PASSWORD ''
|
apk add --no-cache --virtual .gosu-deps \
|
||||||
ENV PLEX_SERVERNAME ''
|
ca-certificates \
|
||||||
|
dpkg \
|
||||||
ENV JELLYFIN_BASEURL ''
|
gnupg \
|
||||||
ENV JELLYFIN_TOKEN ''
|
; \
|
||||||
|
\
|
||||||
ENV SYNC_FROM_PLEX_TO_JELLYFIN 'True'
|
dpkgArch="$(dpkg --print-architecture | awk -F- '{ print $NF }')"; \
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_PLEX 'True'
|
wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch"; \
|
||||||
ENV SYNC_FROM_PLEX_TO_PLEX 'True'
|
wget -O /usr/local/bin/gosu.asc "https://github.com/tianon/gosu/releases/download/$GOSU_VERSION/gosu-$dpkgArch.asc"; \
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_JELLYFIN 'True'
|
\
|
||||||
|
# verify the signature
|
||||||
ENV BLACKLIST_LIBRARY ''
|
export GNUPGHOME="$(mktemp -d)"; \
|
||||||
ENV WHITELIST_LIBRARY ''
|
gpg --batch --keyserver hkps://keys.openpgp.org --recv-keys B42F6819007F00F88E364FD4036A9C25BF357DD4; \
|
||||||
ENV BLACKLIST_LIBRARY_TYPE ''
|
gpg --batch --verify /usr/local/bin/gosu.asc /usr/local/bin/gosu; \
|
||||||
ENV WHITELIST_LIBRARY_TYPE ''
|
gpgconf --kill all; \
|
||||||
ENV BLACKLIST_USERS ''
|
rm -rf "$GNUPGHOME" /usr/local/bin/gosu.asc; \
|
||||||
ENV WHITELIST_USERS ''
|
\
|
||||||
|
# clean up fetch dependencies
|
||||||
|
apk del --no-network .gosu-deps; \
|
||||||
RUN apk add --no-cache tini && \
|
\
|
||||||
addgroup --system jellyplex_user && \
|
chmod +x /usr/local/bin/gosu; \
|
||||||
adduser --system --no-create-home jellyplex_user --ingroup jellyplex_user && \
|
# verify that the binary works
|
||||||
mkdir -p /app && \
|
gosu --version; \
|
||||||
chown -R jellyplex_user:jellyplex_user /app
|
gosu nobody true
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
|
COPY ./requirements.txt ./
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user . .
|
COPY . .
|
||||||
|
|
||||||
USER jellyplex_user
|
RUN chmod +x *.sh && \
|
||||||
|
dos2unix *.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/sbin/tini", "--"]
|
ENTRYPOINT ["tini", "--", "/app/entrypoint.sh"]
|
||||||
CMD ["python", "-u", "main.py"]
|
CMD ["python", "-u", "main.py"]
|
||||||
|
|||||||
@@ -1,56 +1,23 @@
|
|||||||
FROM python:3.11-slim
|
FROM python:3.13-slim
|
||||||
|
|
||||||
ENV DRYRUN 'True'
|
|
||||||
ENV DEBUG 'True'
|
|
||||||
ENV DEBUG_LEVEL 'INFO'
|
|
||||||
ENV RUN_ONLY_ONCE 'False'
|
|
||||||
ENV SLEEP_DURATION '3600'
|
|
||||||
ENV LOGFILE 'log.log'
|
|
||||||
ENV MARKFILE 'mark.log'
|
|
||||||
|
|
||||||
ENV USER_MAPPING ''
|
|
||||||
ENV LIBRARY_MAPPING ''
|
|
||||||
|
|
||||||
ENV PLEX_BASEURL ''
|
|
||||||
ENV PLEX_TOKEN ''
|
|
||||||
ENV PLEX_USERNAME ''
|
|
||||||
ENV PLEX_PASSWORD ''
|
|
||||||
ENV PLEX_SERVERNAME ''
|
|
||||||
|
|
||||||
ENV JELLYFIN_BASEURL ''
|
|
||||||
ENV JELLYFIN_TOKEN ''
|
|
||||||
|
|
||||||
ENV SYNC_FROM_PLEX_TO_JELLYFIN 'True'
|
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_PLEX 'True'
|
|
||||||
ENV SYNC_FROM_PLEX_TO_PLEX 'True'
|
|
||||||
ENV SYNC_FROM_JELLYFIN_TO_JELLYFIN 'True'
|
|
||||||
|
|
||||||
ENV BLACKLIST_LIBRARY ''
|
|
||||||
ENV WHITELIST_LIBRARY ''
|
|
||||||
ENV BLACKLIST_LIBRARY_TYPE ''
|
|
||||||
ENV WHITELIST_LIBRARY_TYPE ''
|
|
||||||
ENV BLACKLIST_USERS ''
|
|
||||||
ENV WHITELIST_USERS ''
|
|
||||||
|
|
||||||
|
ENV PUID=1000
|
||||||
|
ENV PGID=1000
|
||||||
|
|
||||||
RUN apt-get update && \
|
RUN apt-get update && \
|
||||||
apt-get install tini --yes --no-install-recommends && \
|
apt-get install tini gosu dos2unix --yes --no-install-recommends && \
|
||||||
apt-get clean && \
|
apt-get clean && \
|
||||||
rm -rf /var/lib/apt/lists/* && \
|
rm -rf /var/lib/apt/lists/*
|
||||||
addgroup --system jellyplex_user && \
|
|
||||||
adduser --system --no-create-home jellyplex_user --ingroup jellyplex_user && \
|
|
||||||
mkdir -p /app && \
|
|
||||||
chown -R jellyplex_user:jellyplex_user /app
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user ./requirements.txt ./
|
COPY ./requirements.txt ./
|
||||||
|
|
||||||
RUN pip install --no-cache-dir -r requirements.txt
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
COPY --chown=jellyplex_user:jellyplex_user . .
|
COPY . .
|
||||||
|
|
||||||
USER jellyplex_user
|
RUN chmod +x *.sh && \
|
||||||
|
dos2unix *.sh
|
||||||
|
|
||||||
ENTRYPOINT ["/bin/tini", "--"]
|
ENTRYPOINT ["/bin/tini", "--", "/app/entrypoint.sh"]
|
||||||
CMD ["python", "-u", "main.py"]
|
CMD ["python", "-u", "main.py"]
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ Full list of configuration options can be found in the [.env.sample](.env.sample
|
|||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,11 @@
|
|||||||
version: '3'
|
# Sync watched status between media servers locally
|
||||||
|
|
||||||
services:
|
services:
|
||||||
jellyplex-watched:
|
jellyplex-watched:
|
||||||
image: luigi311/jellyplex-watched:latest
|
image: luigi311/jellyplex-watched:latest
|
||||||
container_name: jellyplex-watched
|
container_name: jellyplex-watched
|
||||||
restart: always
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
- DRYRUN=True
|
- PUID=1000
|
||||||
- DEBUG=True
|
- PGID=1000
|
||||||
- DEBUG_LEVEL=info
|
env_file: "./.env"
|
||||||
- RUN_ONLY_ONCE=False
|
|
||||||
- SLEEP_DURATION=3600
|
|
||||||
- LOGFILE=/tmp/log.log
|
|
||||||
- MARKFILE=/tmp/mark.log
|
|
||||||
- USER_MAPPING={"user1":"user2"}
|
|
||||||
- LIBRARY_MAPPING={"TV Shows":"Shows"}
|
|
||||||
- BLACKLIST_LIBRARY=
|
|
||||||
- WHITELIST_LIBRARY=
|
|
||||||
- BLACKLIST_LIBRARY_TYPE=
|
|
||||||
- WHITELIST_LIBRARY_TYPE=
|
|
||||||
- BLACKLIST_USERS=
|
|
||||||
- WHITELIST_USERS=
|
|
||||||
- PLEX_BASEURL=https://localhost:32400
|
|
||||||
- PLEX_TOKEN=plex_token
|
|
||||||
- JELLYFIN_BASEURL=http://localhost:8096
|
|
||||||
- JELLYFIN_TOKEN=jelly_token
|
|
||||||
- SSL_BYPASS=True
|
|
||||||
- SYNC_FROM_PLEX_TO_JELLYFIN=True
|
|
||||||
- SYNC_FROM_JELLYFIN_TO_PLEX=True
|
|
||||||
- SYNC_FROM_PLEX_TO_PLEX=True
|
|
||||||
- SYNC_FROM_JELLYFIN_TO_JELLYFIN=True
|
|
||||||
|
|||||||
61
entrypoint.sh
Normal file
61
entrypoint.sh
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Check if user is root
|
||||||
|
if [ "$(id -u)" = '0' ]; then
|
||||||
|
echo "User is root, checking if we need to create a user and group based on environment variables"
|
||||||
|
# Create group and user based on environment variables
|
||||||
|
if [ ! "$(getent group "$PGID")" ]; then
|
||||||
|
# If groupadd exists, use it
|
||||||
|
if command -v groupadd > /dev/null; then
|
||||||
|
groupadd -g "$PGID" jellyplex_watched
|
||||||
|
elif command -v addgroup > /dev/null; then
|
||||||
|
addgroup -g "$PGID" jellyplex_watched
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If user id does not exist, create the user
|
||||||
|
if [ ! "$(getent passwd "$PUID")" ]; then
|
||||||
|
if command -v useradd > /dev/null; then
|
||||||
|
useradd --no-create-home -u "$PUID" -g "$PGID" jellyplex_watched
|
||||||
|
elif command -v adduser > /dev/null; then
|
||||||
|
# Get the group name based on the PGID since adduser does not have a flag to specify the group id
|
||||||
|
# and if the group id already exists the group name will be sommething unexpected
|
||||||
|
GROUPNAME=$(getent group "$PGID" | cut -d: -f1)
|
||||||
|
|
||||||
|
# Use alpine busybox adduser syntax
|
||||||
|
adduser -D -H -u "$PUID" -G "$GROUPNAME" jellyplex_watched
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
# If user is not root, set the PUID and PGID to the current user
|
||||||
|
PUID=$(id -u)
|
||||||
|
PGID=$(id -g)
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Get directory of log and mark file to create base folder if it doesnt exist
|
||||||
|
LOG_DIR=$(dirname "$LOG_FILE")
|
||||||
|
# If LOG_DIR is set, create the directory
|
||||||
|
if [ -n "$LOG_DIR" ]; then
|
||||||
|
mkdir -p "$LOG_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
MARK_DIR=$(dirname "$MARK_FILE")
|
||||||
|
if [ -n "$MARK_DIR" ]; then
|
||||||
|
mkdir -p "$MARK_DIR"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Starting JellyPlex-Watched with UID: $PUID and GID: $PGID"
|
||||||
|
|
||||||
|
# If root run as the created user
|
||||||
|
if [ "$(id -u)" = '0' ]; then
|
||||||
|
chown -R "$PUID:$PGID" "$LOG_DIR"
|
||||||
|
chown -R "$PUID:$PGID" "$MARK_DIR"
|
||||||
|
|
||||||
|
# Run the application as the created user
|
||||||
|
exec gosu "$PUID:$PGID" "$@"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Run the application as the current user
|
||||||
|
exec "$@"
|
||||||
BIN
requirements.txt
BIN
requirements.txt
Binary file not shown.
139
src/connection.py
Normal file
139
src/connection.py
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import os
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
from src.functions import logger, str_to_bool
|
||||||
|
from src.plex import Plex
|
||||||
|
from src.jellyfin import Jellyfin
|
||||||
|
from src.emby import Emby
|
||||||
|
|
||||||
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
|
def jellyfin_emby_server_connection(server_baseurl, server_token, server_type):
|
||||||
|
servers = []
|
||||||
|
|
||||||
|
server_baseurl = server_baseurl.split(",")
|
||||||
|
server_token = server_token.split(",")
|
||||||
|
|
||||||
|
if len(server_baseurl) != len(server_token):
|
||||||
|
raise Exception(
|
||||||
|
f"{server_type.upper()}_BASEURL and {server_type.upper()}_TOKEN must have the same number of entries"
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, baseurl in enumerate(server_baseurl):
|
||||||
|
baseurl = baseurl.strip()
|
||||||
|
if baseurl[-1] == "/":
|
||||||
|
baseurl = baseurl[:-1]
|
||||||
|
|
||||||
|
if server_type == "jellyfin":
|
||||||
|
server = Jellyfin(baseurl=baseurl, token=server_token[i].strip())
|
||||||
|
servers.append(
|
||||||
|
(
|
||||||
|
"jellyfin",
|
||||||
|
server,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
elif server_type == "emby":
|
||||||
|
server = Emby(baseurl=baseurl, token=server_token[i].strip())
|
||||||
|
servers.append(
|
||||||
|
(
|
||||||
|
"emby",
|
||||||
|
server,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise Exception("Unknown server type")
|
||||||
|
|
||||||
|
logger(f"{server_type} Server {i} info: {server.info()}", 3)
|
||||||
|
|
||||||
|
return servers
|
||||||
|
|
||||||
|
|
||||||
|
def generate_server_connections():
|
||||||
|
servers = []
|
||||||
|
|
||||||
|
plex_baseurl = os.getenv("PLEX_BASEURL", None)
|
||||||
|
plex_token = os.getenv("PLEX_TOKEN", None)
|
||||||
|
plex_username = os.getenv("PLEX_USERNAME", None)
|
||||||
|
plex_password = os.getenv("PLEX_PASSWORD", None)
|
||||||
|
plex_servername = os.getenv("PLEX_SERVERNAME", None)
|
||||||
|
ssl_bypass = str_to_bool(os.getenv("SSL_BYPASS", "False"))
|
||||||
|
|
||||||
|
if plex_baseurl and plex_token:
|
||||||
|
plex_baseurl = plex_baseurl.split(",")
|
||||||
|
plex_token = plex_token.split(",")
|
||||||
|
|
||||||
|
if len(plex_baseurl) != len(plex_token):
|
||||||
|
raise Exception(
|
||||||
|
"PLEX_BASEURL and PLEX_TOKEN must have the same number of entries"
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, url in enumerate(plex_baseurl):
|
||||||
|
server = Plex(
|
||||||
|
baseurl=url.strip(),
|
||||||
|
token=plex_token[i].strip(),
|
||||||
|
username=None,
|
||||||
|
password=None,
|
||||||
|
servername=None,
|
||||||
|
ssl_bypass=ssl_bypass,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger(f"Plex Server {i} info: {server.info()}", 3)
|
||||||
|
|
||||||
|
servers.append(
|
||||||
|
(
|
||||||
|
"plex",
|
||||||
|
server,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
if plex_username and plex_password and plex_servername:
|
||||||
|
plex_username = plex_username.split(",")
|
||||||
|
plex_password = plex_password.split(",")
|
||||||
|
plex_servername = plex_servername.split(",")
|
||||||
|
|
||||||
|
if len(plex_username) != len(plex_password) or len(plex_username) != len(
|
||||||
|
plex_servername
|
||||||
|
):
|
||||||
|
raise Exception(
|
||||||
|
"PLEX_USERNAME, PLEX_PASSWORD and PLEX_SERVERNAME must have the same number of entries"
|
||||||
|
)
|
||||||
|
|
||||||
|
for i, username in enumerate(plex_username):
|
||||||
|
server = Plex(
|
||||||
|
baseurl=None,
|
||||||
|
token=None,
|
||||||
|
username=username.strip(),
|
||||||
|
password=plex_password[i].strip(),
|
||||||
|
servername=plex_servername[i].strip(),
|
||||||
|
ssl_bypass=ssl_bypass,
|
||||||
|
)
|
||||||
|
|
||||||
|
logger(f"Plex Server {i} info: {server.info()}", 3)
|
||||||
|
servers.append(
|
||||||
|
(
|
||||||
|
"plex",
|
||||||
|
server,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None)
|
||||||
|
jellyfin_token = os.getenv("JELLYFIN_TOKEN", None)
|
||||||
|
|
||||||
|
if jellyfin_baseurl and jellyfin_token:
|
||||||
|
servers.extend(
|
||||||
|
jellyfin_emby_server_connection(
|
||||||
|
jellyfin_baseurl, jellyfin_token, "jellyfin"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
emby_baseurl = os.getenv("EMBY_BASEURL", None)
|
||||||
|
emby_token = os.getenv("EMBY_TOKEN", None)
|
||||||
|
|
||||||
|
if emby_baseurl and emby_token:
|
||||||
|
servers.extend(
|
||||||
|
jellyfin_emby_server_connection(emby_baseurl, emby_token, "emby")
|
||||||
|
)
|
||||||
|
|
||||||
|
return servers
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
from src.jellyfin_emby import JellyfinEmby
|
from src.jellyfin_emby import JellyfinEmby
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
|
||||||
class Emby(JellyfinEmby):
|
class Emby(JellyfinEmby):
|
||||||
@@ -8,7 +9,7 @@ class Emby(JellyfinEmby):
|
|||||||
'Client="JellyPlex-Watched", '
|
'Client="JellyPlex-Watched", '
|
||||||
'Device="script", '
|
'Device="script", '
|
||||||
'DeviceId="script", '
|
'DeviceId="script", '
|
||||||
'Version="0.0.0"'
|
'Version="6.0.2"'
|
||||||
)
|
)
|
||||||
headers = {
|
headers = {
|
||||||
"Accept": "application/json",
|
"Accept": "application/json",
|
||||||
@@ -19,3 +20,6 @@ class Emby(JellyfinEmby):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
server_type="Emby", baseurl=baseurl, token=token, headers=headers
|
server_type="Emby", baseurl=baseurl, token=token, headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_partial_update_supported(self, server_version):
|
||||||
|
return server_version > version.parse("4.4")
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from dotenv import load_dotenv
|
|||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
logfile = os.getenv("LOGFILE", "log.log")
|
log_file = os.getenv("LOG_FILE", os.getenv("LOGFILE", "log.log"))
|
||||||
markfile = os.getenv("MARKFILE", "mark.log")
|
mark_file = os.getenv("MARK_FILE", os.getenv("MARKFILE", "mark.log"))
|
||||||
|
|
||||||
|
|
||||||
def logger(message: str, log_type=0):
|
def logger(message: str, log_type=0):
|
||||||
@@ -32,17 +32,23 @@ def logger(message: str, log_type=0):
|
|||||||
|
|
||||||
if output is not None:
|
if output is not None:
|
||||||
print(output)
|
print(output)
|
||||||
file = open(logfile, "a", encoding="utf-8")
|
with open(f"{log_file}", "a", encoding="utf-8") as file:
|
||||||
file.write(output + "\n")
|
file.write(output + "\n")
|
||||||
|
|
||||||
|
|
||||||
def log_marked(
|
def log_marked(
|
||||||
username: str, library: str, movie_show: str, episode: str = None, duration=None
|
server_type: str,
|
||||||
|
server_name: str,
|
||||||
|
username: str,
|
||||||
|
library: str,
|
||||||
|
movie_show: str,
|
||||||
|
episode: str = None,
|
||||||
|
duration=None,
|
||||||
):
|
):
|
||||||
if markfile is None:
|
if mark_file is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
output = f"{username}/{library}/{movie_show}"
|
output = f"{server_type}/{server_name}/{username}/{library}/{movie_show}"
|
||||||
|
|
||||||
if episode:
|
if episode:
|
||||||
output += f"/{episode}"
|
output += f"/{episode}"
|
||||||
@@ -50,7 +56,7 @@ def log_marked(
|
|||||||
if duration:
|
if duration:
|
||||||
output += f"/{duration}"
|
output += f"/{duration}"
|
||||||
|
|
||||||
file = open(f"{markfile}", "a", encoding="utf-8")
|
with open(f"{mark_file}", "a", encoding="utf-8") as file:
|
||||||
file.write(output + "\n")
|
file.write(output + "\n")
|
||||||
|
|
||||||
|
|
||||||
@@ -93,6 +99,20 @@ def search_mapping(dictionary: dict, key_value: str):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Return list of objects that exist in both lists including mappings
|
||||||
|
def match_list(list1, list2, list_mapping=None):
|
||||||
|
output = []
|
||||||
|
for element in list1:
|
||||||
|
if element in list2:
|
||||||
|
output.append(element)
|
||||||
|
elif list_mapping:
|
||||||
|
element_other = search_mapping(list_mapping, element)
|
||||||
|
if element_other in list2:
|
||||||
|
output.append(element)
|
||||||
|
|
||||||
|
return output
|
||||||
|
|
||||||
|
|
||||||
def future_thread_executor(
|
def future_thread_executor(
|
||||||
args: list, threads: int = None, override_threads: bool = False
|
args: list, threads: int = None, override_threads: bool = False
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from src.jellyfin_emby import JellyfinEmby
|
from src.jellyfin_emby import JellyfinEmby
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
|
|
||||||
class Jellyfin(JellyfinEmby):
|
class Jellyfin(JellyfinEmby):
|
||||||
@@ -8,7 +9,7 @@ class Jellyfin(JellyfinEmby):
|
|||||||
'Client="JellyPlex-Watched", '
|
'Client="JellyPlex-Watched", '
|
||||||
'Device="script", '
|
'Device="script", '
|
||||||
'DeviceId="script", '
|
'DeviceId="script", '
|
||||||
'Version="5.2.0", '
|
'Version="6.0.2", '
|
||||||
f'Token="{token}"'
|
f'Token="{token}"'
|
||||||
)
|
)
|
||||||
headers = {
|
headers = {
|
||||||
@@ -19,3 +20,6 @@ class Jellyfin(JellyfinEmby):
|
|||||||
super().__init__(
|
super().__init__(
|
||||||
server_type="Jellyfin", baseurl=baseurl, token=token, headers=headers
|
server_type="Jellyfin", baseurl=baseurl, token=token, headers=headers
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def is_partial_update_supported(self, server_version):
|
||||||
|
return server_version >= version.parse("10.9.0")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import traceback, os
|
|||||||
from math import floor
|
from math import floor
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
import requests
|
import requests
|
||||||
|
from packaging import version
|
||||||
|
|
||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
@@ -12,13 +13,7 @@ from src.functions import (
|
|||||||
log_marked,
|
log_marked,
|
||||||
str_to_bool,
|
str_to_bool,
|
||||||
)
|
)
|
||||||
from src.library import (
|
from src.library import generate_library_guids_dict
|
||||||
check_skip_logic,
|
|
||||||
generate_library_guids_dict,
|
|
||||||
)
|
|
||||||
from src.watched import (
|
|
||||||
combine_watched_dicts,
|
|
||||||
)
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -111,7 +106,6 @@ class JellyfinEmby:
|
|||||||
def __init__(self, server_type, baseurl, token, headers):
|
def __init__(self, server_type, baseurl, token, headers):
|
||||||
if server_type not in ["Jellyfin", "Emby"]:
|
if server_type not in ["Jellyfin", "Emby"]:
|
||||||
raise Exception(f"Server type {server_type} not supported")
|
raise Exception(f"Server type {server_type} not supported")
|
||||||
|
|
||||||
self.server_type = server_type
|
self.server_type = server_type
|
||||||
self.baseurl = baseurl
|
self.baseurl = baseurl
|
||||||
self.token = token
|
self.token = token
|
||||||
@@ -126,6 +120,7 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
self.session = requests.Session()
|
self.session = requests.Session()
|
||||||
self.users = self.get_users()
|
self.users = self.get_users()
|
||||||
|
self.server_name = self.info(name_only=True)
|
||||||
|
|
||||||
def query(self, query, query_type, identifiers=None, json=None):
|
def query(self, query, query_type, identifiers=None, json=None):
|
||||||
try:
|
try:
|
||||||
@@ -177,13 +172,15 @@ class JellyfinEmby:
|
|||||||
)
|
)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def info(self) -> str:
|
def info(self, name_only: bool = False) -> str:
|
||||||
try:
|
try:
|
||||||
query_string = "/System/Info/Public"
|
query_string = "/System/Info/Public"
|
||||||
|
|
||||||
response = self.query(query_string, "get")
|
response = self.query(query_string, "get")
|
||||||
|
|
||||||
if response:
|
if response:
|
||||||
|
if name_only:
|
||||||
|
return f"{response['ServerName']}"
|
||||||
return f"{self.server_type} {response['ServerName']}: {response['Version']}"
|
return f"{self.server_type} {response['ServerName']}: {response['Version']}"
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
@@ -192,6 +189,19 @@ class JellyfinEmby:
|
|||||||
logger(f"{self.server_type}: Get server name failed {e}", 2)
|
logger(f"{self.server_type}: Get server name failed {e}", 2)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
|
def get_server_version(self):
|
||||||
|
try:
|
||||||
|
response = self.query("/System/Info/Public", "get")
|
||||||
|
|
||||||
|
if response:
|
||||||
|
return version.parse(response["Version"])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"{self.server_type}: Get server version failed: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def get_users(self):
|
def get_users(self):
|
||||||
try:
|
try:
|
||||||
users = {}
|
users = {}
|
||||||
@@ -209,13 +219,54 @@ class JellyfinEmby:
|
|||||||
logger(f"{self.server_type}: Get users failed {e}", 2)
|
logger(f"{self.server_type}: Get users failed {e}", 2)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
|
def get_libraries(self):
|
||||||
|
try:
|
||||||
|
libraries = {}
|
||||||
|
|
||||||
|
# Theres no way to get all libraries so individually get list of libraries from all users
|
||||||
|
users = self.get_users()
|
||||||
|
|
||||||
|
for _, user_id in users.items():
|
||||||
|
user_libraries = self.query(f"/Users/{user_id}/Views", "get")
|
||||||
|
for library in user_libraries["Items"]:
|
||||||
|
library_id = library["Id"]
|
||||||
|
library_title = library["Name"]
|
||||||
|
|
||||||
|
# Get library items to check the type
|
||||||
|
media_info = self.query(
|
||||||
|
f"/Users/{user_id}/Items"
|
||||||
|
+ f"?ParentId={library_id}&Filters=IsPlayed&Recursive=True&excludeItemTypes=Folder&limit=100",
|
||||||
|
"get",
|
||||||
|
)
|
||||||
|
|
||||||
|
types = set(
|
||||||
|
[
|
||||||
|
x["Type"]
|
||||||
|
for x in media_info["Items"]
|
||||||
|
if x["Type"] in ["Movie", "Series", "Episode"]
|
||||||
|
]
|
||||||
|
)
|
||||||
|
all_types = set([x["Type"] for x in media_info["Items"]])
|
||||||
|
|
||||||
|
if not types:
|
||||||
|
logger(
|
||||||
|
f"{self.server_type}: Skipping Library {library_title} found wanted types: {all_types}",
|
||||||
|
1,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
libraries[library_title] = str(types)
|
||||||
|
|
||||||
|
return libraries
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"{self.server_type}: Get libraries failed {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
def get_user_library_watched(
|
def get_user_library_watched(
|
||||||
self, user_name, user_id, library_type, library_id, library_title
|
self, user_name, user_id, library_type, library_id, library_title
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
user_name = user_name.lower()
|
user_name = user_name.lower()
|
||||||
user_watched = {}
|
user_watched = {}
|
||||||
user_watched[user_name] = {}
|
|
||||||
|
|
||||||
logger(
|
logger(
|
||||||
f"{self.server_type}: Generating watched for {user_name} in library {library_title}",
|
f"{self.server_type}: Generating watched for {user_name} in library {library_title}",
|
||||||
@@ -224,7 +275,7 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
# Movies
|
# Movies
|
||||||
if library_type == "Movie":
|
if library_type == "Movie":
|
||||||
user_watched[user_name][library_title] = []
|
user_watched[library_title] = []
|
||||||
watched = self.query(
|
watched = self.query(
|
||||||
f"/Users/{user_id}/Items"
|
f"/Users/{user_id}/Items"
|
||||||
+ f"?ParentId={library_id}&Filters=IsPlayed&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources",
|
+ f"?ParentId={library_id}&Filters=IsPlayed&IncludeItemTypes=Movie&Recursive=True&Fields=ItemCounts,ProviderIds,MediaSources",
|
||||||
@@ -260,7 +311,7 @@ class JellyfinEmby:
|
|||||||
movie_guids = get_guids(self.server_type, movie)
|
movie_guids = get_guids(self.server_type, movie)
|
||||||
|
|
||||||
# Append the movie dictionary to the list for the given user and library
|
# Append the movie dictionary to the list for the given user and library
|
||||||
user_watched[user_name][library_title].append(movie_guids)
|
user_watched[library_title].append(movie_guids)
|
||||||
logger(
|
logger(
|
||||||
f"{self.server_type}: Added {movie_guids} to {user_name} watched list",
|
f"{self.server_type}: Added {movie_guids} to {user_name} watched list",
|
||||||
3,
|
3,
|
||||||
@@ -269,7 +320,7 @@ class JellyfinEmby:
|
|||||||
# TV Shows
|
# TV Shows
|
||||||
if library_type in ["Series", "Episode"]:
|
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[library_title] = {}
|
||||||
|
|
||||||
# Retrieve a list of watched TV shows
|
# Retrieve a list of watched TV shows
|
||||||
watched_shows = self.query(
|
watched_shows = self.query(
|
||||||
@@ -301,11 +352,6 @@ class JellyfinEmby:
|
|||||||
if "Path" in show
|
if "Path" in show
|
||||||
else tuple()
|
else tuple()
|
||||||
)
|
)
|
||||||
show_display_name = (
|
|
||||||
show_guids["title"]
|
|
||||||
if show_guids["title"]
|
|
||||||
else show_guids["locations"]
|
|
||||||
)
|
|
||||||
|
|
||||||
show_guids = frozenset(show_guids.items())
|
show_guids = frozenset(show_guids.items())
|
||||||
|
|
||||||
@@ -338,26 +384,22 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
if mark_episodes_list:
|
if mark_episodes_list:
|
||||||
# Add the show dictionary to the user's watched list
|
# Add the show dictionary to the user's watched list
|
||||||
if show_guids not in user_watched[user_name][library_title]:
|
if show_guids not in user_watched[library_title]:
|
||||||
user_watched[user_name][library_title][show_guids] = []
|
user_watched[library_title][show_guids] = []
|
||||||
|
|
||||||
user_watched[user_name][library_title][
|
user_watched[library_title][show_guids] = mark_episodes_list
|
||||||
show_guids
|
|
||||||
] = mark_episodes_list
|
|
||||||
for episode in mark_episodes_list:
|
for episode in mark_episodes_list:
|
||||||
logger(
|
logger(
|
||||||
f"{self.server_type}: Added {episode} to {user_name} {show_display_name} watched list",
|
f"{self.server_type}: Added {episode} to {user_name} watched list",
|
||||||
1,
|
3,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger(
|
logger(
|
||||||
f"{self.server_type}: Got watched for {user_name} in library {library_title}",
|
f"{self.server_type}: Got watched for {user_name} in library {library_title}",
|
||||||
1,
|
1,
|
||||||
)
|
)
|
||||||
if library_title in user_watched[user_name]:
|
if library_title in user_watched:
|
||||||
logger(
|
logger(f"{self.server_type}: {user_watched[library_title]}", 3)
|
||||||
f"{self.server_type}: {user_watched[user_name][library_title]}", 3
|
|
||||||
)
|
|
||||||
|
|
||||||
return user_watched
|
return user_watched
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -369,27 +411,22 @@ class JellyfinEmby:
|
|||||||
logger(traceback.format_exc(), 2)
|
logger(traceback.format_exc(), 2)
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
def get_users_watched(
|
def get_watched(self, users, sync_libraries):
|
||||||
self,
|
|
||||||
user_name,
|
|
||||||
user_id,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
):
|
|
||||||
try:
|
try:
|
||||||
# Get all libraries
|
users_watched = {}
|
||||||
user_name = user_name.lower()
|
|
||||||
watched = []
|
watched = []
|
||||||
|
|
||||||
|
for user_name, user_id in users.items():
|
||||||
libraries = []
|
libraries = []
|
||||||
|
|
||||||
all_libraries = self.query(f"/Users/{user_id}/Views", "get")
|
all_libraries = self.query(f"/Users/{user_id}/Views", "get")
|
||||||
for library in all_libraries["Items"]:
|
for library in all_libraries["Items"]:
|
||||||
library_id = library["Id"]
|
library_id = library["Id"]
|
||||||
library_title = library["Name"]
|
library_title = library["Name"]
|
||||||
|
|
||||||
|
if library_title not in sync_libraries:
|
||||||
|
continue
|
||||||
|
|
||||||
identifiers = {
|
identifiers = {
|
||||||
"library_id": library_id,
|
"library_id": library_id,
|
||||||
"library_title": library_title,
|
"library_title": library_title,
|
||||||
@@ -409,6 +446,7 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
library_id = library["Identifiers"]["library_id"]
|
library_id = library["Identifiers"]["library_id"]
|
||||||
library_title = library["Identifiers"]["library_title"]
|
library_title = library["Identifiers"]["library_title"]
|
||||||
|
|
||||||
# Get all library types excluding "Folder"
|
# Get all library types excluding "Folder"
|
||||||
types = set(
|
types = set(
|
||||||
[
|
[
|
||||||
@@ -418,81 +456,19 @@ class JellyfinEmby:
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
skip_reason = check_skip_logic(
|
|
||||||
library_title,
|
|
||||||
types,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
)
|
|
||||||
|
|
||||||
if skip_reason:
|
|
||||||
logger(
|
|
||||||
f"{self.server_type}: Skipping library {library_title}: {skip_reason}",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If there are multiple types in library raise error
|
|
||||||
if types is None or len(types) < 1:
|
|
||||||
all_types = set([x["Type"] for x in library["Items"]])
|
|
||||||
logger(
|
|
||||||
f"{self.server_type}: Skipping Library {library_title} found types: {types}, all types: {all_types}",
|
|
||||||
1,
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
for library_type in types:
|
for library_type in types:
|
||||||
# Get watched for user
|
# Get watched for user
|
||||||
watched.append(
|
watched = self.get_user_library_watched(
|
||||||
self.get_user_library_watched(
|
|
||||||
user_name,
|
user_name,
|
||||||
user_id,
|
user_id,
|
||||||
library_type,
|
library_type,
|
||||||
library_id,
|
library_id,
|
||||||
library_title,
|
library_title,
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
return watched
|
if user_name.lower() not in users_watched:
|
||||||
except Exception as e:
|
users_watched[user_name.lower()] = {}
|
||||||
logger(f"{self.server_type}: Failed to get users watched, Error: {e}", 2)
|
users_watched[user_name.lower()].update(watched)
|
||||||
raise Exception(e)
|
|
||||||
|
|
||||||
def get_watched(
|
|
||||||
self,
|
|
||||||
users,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping=None,
|
|
||||||
):
|
|
||||||
try:
|
|
||||||
users_watched = {}
|
|
||||||
watched = []
|
|
||||||
|
|
||||||
for user_name, user_id in users.items():
|
|
||||||
watched.append(
|
|
||||||
self.get_users_watched(
|
|
||||||
user_name,
|
|
||||||
user_id,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for user_watched in watched:
|
|
||||||
user_watched_combine = combine_watched_dicts(user_watched)
|
|
||||||
for user, user_watched_temp in user_watched_combine.items():
|
|
||||||
if user not in users_watched:
|
|
||||||
users_watched[user] = {}
|
|
||||||
users_watched[user].update(user_watched_temp)
|
|
||||||
|
|
||||||
return users_watched
|
return users_watched
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -500,7 +476,7 @@ class JellyfinEmby:
|
|||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def update_user_watched(
|
def update_user_watched(
|
||||||
self, user_name, user_id, library, library_id, videos, dryrun
|
self, user_name, user_id, library, library_id, videos, update_partial, dryrun
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
logger(
|
logger(
|
||||||
@@ -556,11 +532,13 @@ class JellyfinEmby:
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
self.server_type,
|
||||||
|
self.server_name,
|
||||||
user_name,
|
user_name,
|
||||||
library,
|
library,
|
||||||
jellyfin_video.get("Name"),
|
jellyfin_video.get("Name"),
|
||||||
)
|
)
|
||||||
else:
|
elif update_partial:
|
||||||
msg = f"{self.server_type}: {jellyfin_video.get('Name')} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
msg = f"{self.server_type}: {jellyfin_video.get('Name')} as partially watched for {floor(movie_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
||||||
|
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
@@ -578,6 +556,8 @@ class JellyfinEmby:
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
self.server_type,
|
||||||
|
self.server_name,
|
||||||
user_name,
|
user_name,
|
||||||
library,
|
library,
|
||||||
jellyfin_video.get("Name"),
|
jellyfin_video.get("Name"),
|
||||||
@@ -684,12 +664,14 @@ class JellyfinEmby:
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
self.server_type,
|
||||||
|
self.server_name,
|
||||||
user_name,
|
user_name,
|
||||||
library,
|
library,
|
||||||
jellyfin_episode.get("SeriesName"),
|
jellyfin_episode.get("SeriesName"),
|
||||||
jellyfin_episode.get("Name"),
|
jellyfin_episode.get("Name"),
|
||||||
)
|
)
|
||||||
else:
|
elif update_partial:
|
||||||
msg = (
|
msg = (
|
||||||
f"{self.server_type}: {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}"
|
f"{self.server_type}: {jellyfin_episode['SeriesName']} {jellyfin_episode['SeasonName']} Episode {jellyfin_episode.get('IndexNumber')} {jellyfin_episode.get('Name')}"
|
||||||
+ f" as partially watched for {floor(episode_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
+ f" as partially watched for {floor(episode_status['time'] / 60_000)} minutes for {user_name} in {library}"
|
||||||
@@ -712,6 +694,8 @@ class JellyfinEmby:
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
self.server_type,
|
||||||
|
self.server_name,
|
||||||
user_name,
|
user_name,
|
||||||
library,
|
library,
|
||||||
jellyfin_episode.get("SeriesName"),
|
jellyfin_episode.get("SeriesName"),
|
||||||
@@ -741,6 +725,15 @@ class JellyfinEmby:
|
|||||||
self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
|
self, watched_list, user_mapping=None, library_mapping=None, dryrun=False
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
|
server_version = self.get_server_version()
|
||||||
|
update_partial = self.is_partial_update_supported(server_version)
|
||||||
|
|
||||||
|
if not update_partial:
|
||||||
|
logger(
|
||||||
|
f"{self.server_type}: Server version {server_version} does not support updating playback position.",
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
for user, libraries in watched_list.items():
|
for user, libraries in watched_list.items():
|
||||||
logger(f"{self.server_type}: Updating for entry {user}, {libraries}", 1)
|
logger(f"{self.server_type}: Updating for entry {user}, {libraries}", 1)
|
||||||
user_other = None
|
user_other = None
|
||||||
@@ -813,7 +806,13 @@ class JellyfinEmby:
|
|||||||
|
|
||||||
if library_id:
|
if library_id:
|
||||||
self.update_user_watched(
|
self.update_user_watched(
|
||||||
user_name, user_id, library, library_id, videos, dryrun
|
user_name,
|
||||||
|
user_id,
|
||||||
|
library,
|
||||||
|
library_id,
|
||||||
|
videos,
|
||||||
|
update_partial,
|
||||||
|
dryrun,
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
|
match_list,
|
||||||
search_mapping,
|
search_mapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -129,6 +130,77 @@ def check_whitelist_logic(
|
|||||||
return skip_reason
|
return skip_reason
|
||||||
|
|
||||||
|
|
||||||
|
def filter_libaries(
|
||||||
|
server_libraries,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping=None,
|
||||||
|
):
|
||||||
|
filtered_libaries = []
|
||||||
|
for library in server_libraries:
|
||||||
|
skip_reason = check_skip_logic(
|
||||||
|
library,
|
||||||
|
server_libraries[library],
|
||||||
|
blacklist_library,
|
||||||
|
whitelist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
if skip_reason:
|
||||||
|
logger(f"Skipping library {library}: {skip_reason}", 1)
|
||||||
|
continue
|
||||||
|
|
||||||
|
filtered_libaries.append(library)
|
||||||
|
|
||||||
|
return filtered_libaries
|
||||||
|
|
||||||
|
|
||||||
|
def setup_libraries(
|
||||||
|
server_1,
|
||||||
|
server_2,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping=None,
|
||||||
|
):
|
||||||
|
server_1_libraries = server_1.get_libraries()
|
||||||
|
server_2_libraries = server_2.get_libraries()
|
||||||
|
logger(f"Server 1 libraries: {server_1_libraries}", 1)
|
||||||
|
logger(f"Server 2 libraries: {server_2_libraries}", 1)
|
||||||
|
|
||||||
|
# Filter out all blacklist, whitelist libaries
|
||||||
|
filtered_server_1_libraries = filter_libaries(
|
||||||
|
server_1_libraries,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
filtered_server_2_libraries = filter_libaries(
|
||||||
|
server_2_libraries,
|
||||||
|
blacklist_library,
|
||||||
|
blacklist_library_type,
|
||||||
|
whitelist_library,
|
||||||
|
whitelist_library_type,
|
||||||
|
library_mapping,
|
||||||
|
)
|
||||||
|
|
||||||
|
output_server_1_libaries = match_list(
|
||||||
|
filtered_server_1_libraries, filtered_server_2_libraries, library_mapping
|
||||||
|
)
|
||||||
|
output_server_2_libaries = match_list(
|
||||||
|
filtered_server_2_libraries, filtered_server_1_libraries, library_mapping
|
||||||
|
)
|
||||||
|
|
||||||
|
return output_server_1_libaries, output_server_2_libaries
|
||||||
|
|
||||||
|
|
||||||
def show_title_dict(user_list: dict):
|
def show_title_dict(user_list: dict):
|
||||||
try:
|
try:
|
||||||
show_output_dict = {}
|
show_output_dict = {}
|
||||||
@@ -158,7 +230,6 @@ def show_title_dict(user_list: dict):
|
|||||||
|
|
||||||
return show_output_dict
|
return show_output_dict
|
||||||
except Exception:
|
except Exception:
|
||||||
logger("Skipping show_output_dict ", 1)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -219,7 +290,6 @@ def episode_title_dict(user_list: dict):
|
|||||||
|
|
||||||
return episode_output_dict
|
return episode_output_dict
|
||||||
except Exception:
|
except Exception:
|
||||||
logger("Skipping episode_output_dict", 1)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
@@ -252,7 +322,6 @@ def movies_title_dict(user_list: dict):
|
|||||||
|
|
||||||
return movies_output_dict
|
return movies_output_dict
|
||||||
except Exception:
|
except Exception:
|
||||||
logger("Skipping movies_output_dict failed", 1)
|
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
213
src/main.py
213
src/main.py
@@ -2,200 +2,21 @@ import os, traceback, json
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from time import sleep, perf_counter
|
from time import sleep, perf_counter
|
||||||
|
|
||||||
|
from src.library import setup_libraries
|
||||||
from src.functions import (
|
from src.functions import (
|
||||||
logger,
|
logger,
|
||||||
str_to_bool,
|
str_to_bool,
|
||||||
)
|
)
|
||||||
from src.users import (
|
from src.users import setup_users
|
||||||
generate_user_list,
|
|
||||||
combine_user_lists,
|
|
||||||
filter_user_lists,
|
|
||||||
generate_server_users,
|
|
||||||
)
|
|
||||||
from src.watched import (
|
from src.watched import (
|
||||||
cleanup_watched,
|
cleanup_watched,
|
||||||
)
|
)
|
||||||
from src.black_white import setup_black_white_lists
|
from src.black_white import setup_black_white_lists
|
||||||
|
from src.connection import generate_server_connections
|
||||||
from src.plex import Plex
|
|
||||||
from src.jellyfin import Jellyfin
|
|
||||||
from src.emby import Emby
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
|
|
||||||
def setup_users(
|
|
||||||
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)
|
|
||||||
logger(f"Server 1 users: {server_1_users}", 1)
|
|
||||||
logger(f"Server 2 users: {server_2_users}", 1)
|
|
||||||
|
|
||||||
users = combine_user_lists(server_1_users, server_2_users, user_mapping)
|
|
||||||
logger(f"User list that exist on both servers {users}", 1)
|
|
||||||
|
|
||||||
users_filtered = filter_user_lists(users, blacklist_users, whitelist_users)
|
|
||||||
logger(f"Filtered user list {users_filtered}", 1)
|
|
||||||
|
|
||||||
output_server_1_users = generate_server_users(server_1, users_filtered)
|
|
||||||
output_server_2_users = generate_server_users(server_2, users_filtered)
|
|
||||||
|
|
||||||
# Check if users is none or empty
|
|
||||||
if output_server_1_users is None or len(output_server_1_users) == 0:
|
|
||||||
logger(
|
|
||||||
f"No users found for server 1 {server_1[0]}, users: {server_1_users}, overlapping users {users}, filtered users {users_filtered}, server 1 users {server_1[1].users}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if output_server_2_users is None or len(output_server_2_users) == 0:
|
|
||||||
logger(
|
|
||||||
f"No users found for server 2 {server_2[0]}, users: {server_2_users}, overlapping users {users} filtered users {users_filtered}, server 2 users {server_2[1].users}"
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
output_server_1_users is None
|
|
||||||
or len(output_server_1_users) == 0
|
|
||||||
or output_server_2_users is None
|
|
||||||
or len(output_server_2_users) == 0
|
|
||||||
):
|
|
||||||
raise Exception("No users found for one or both servers")
|
|
||||||
|
|
||||||
logger(f"Server 1 users: {output_server_1_users}", 1)
|
|
||||||
logger(f"Server 2 users: {output_server_2_users}", 1)
|
|
||||||
|
|
||||||
return output_server_1_users, output_server_2_users
|
|
||||||
|
|
||||||
|
|
||||||
def jellyfin_emby_server_connection(server_baseurl, server_token, server_type):
|
|
||||||
servers = []
|
|
||||||
|
|
||||||
server_baseurl = server_baseurl.split(",")
|
|
||||||
server_token = server_token.split(",")
|
|
||||||
|
|
||||||
if len(server_baseurl) != len(server_token):
|
|
||||||
raise Exception(
|
|
||||||
f"{server_type.upper()}_BASEURL and {server_type.upper()}_TOKEN must have the same number of entries"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, baseurl in enumerate(server_baseurl):
|
|
||||||
baseurl = baseurl.strip()
|
|
||||||
if baseurl[-1] == "/":
|
|
||||||
baseurl = baseurl[:-1]
|
|
||||||
|
|
||||||
if server_type == "jellyfin":
|
|
||||||
server = Jellyfin(baseurl=baseurl, token=server_token[i].strip())
|
|
||||||
servers.append(
|
|
||||||
(
|
|
||||||
"jellyfin",
|
|
||||||
server,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
elif server_type == "emby":
|
|
||||||
server = Emby(baseurl=baseurl, token=server_token[i].strip())
|
|
||||||
servers.append(
|
|
||||||
(
|
|
||||||
"emby",
|
|
||||||
server,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
raise Exception("Unknown server type")
|
|
||||||
|
|
||||||
logger(f"{server_type} Server {i} info: {server.info()}", 3)
|
|
||||||
|
|
||||||
return servers
|
|
||||||
|
|
||||||
|
|
||||||
def generate_server_connections():
|
|
||||||
servers = []
|
|
||||||
|
|
||||||
plex_baseurl = os.getenv("PLEX_BASEURL", None)
|
|
||||||
plex_token = os.getenv("PLEX_TOKEN", None)
|
|
||||||
plex_username = os.getenv("PLEX_USERNAME", None)
|
|
||||||
plex_password = os.getenv("PLEX_PASSWORD", None)
|
|
||||||
plex_servername = os.getenv("PLEX_SERVERNAME", None)
|
|
||||||
ssl_bypass = str_to_bool(os.getenv("SSL_BYPASS", "False"))
|
|
||||||
|
|
||||||
if plex_baseurl and plex_token:
|
|
||||||
plex_baseurl = plex_baseurl.split(",")
|
|
||||||
plex_token = plex_token.split(",")
|
|
||||||
|
|
||||||
if len(plex_baseurl) != len(plex_token):
|
|
||||||
raise Exception(
|
|
||||||
"PLEX_BASEURL and PLEX_TOKEN must have the same number of entries"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, url in enumerate(plex_baseurl):
|
|
||||||
server = Plex(
|
|
||||||
baseurl=url.strip(),
|
|
||||||
token=plex_token[i].strip(),
|
|
||||||
username=None,
|
|
||||||
password=None,
|
|
||||||
servername=None,
|
|
||||||
ssl_bypass=ssl_bypass,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger(f"Plex Server {i} info: {server.info()}", 3)
|
|
||||||
|
|
||||||
servers.append(
|
|
||||||
(
|
|
||||||
"plex",
|
|
||||||
server,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if plex_username and plex_password and plex_servername:
|
|
||||||
plex_username = plex_username.split(",")
|
|
||||||
plex_password = plex_password.split(",")
|
|
||||||
plex_servername = plex_servername.split(",")
|
|
||||||
|
|
||||||
if len(plex_username) != len(plex_password) or len(plex_username) != len(
|
|
||||||
plex_servername
|
|
||||||
):
|
|
||||||
raise Exception(
|
|
||||||
"PLEX_USERNAME, PLEX_PASSWORD and PLEX_SERVERNAME must have the same number of entries"
|
|
||||||
)
|
|
||||||
|
|
||||||
for i, username in enumerate(plex_username):
|
|
||||||
server = Plex(
|
|
||||||
baseurl=None,
|
|
||||||
token=None,
|
|
||||||
username=username.strip(),
|
|
||||||
password=plex_password[i].strip(),
|
|
||||||
servername=plex_servername[i].strip(),
|
|
||||||
ssl_bypass=ssl_bypass,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger(f"Plex Server {i} info: {server.info()}", 3)
|
|
||||||
servers.append(
|
|
||||||
(
|
|
||||||
"plex",
|
|
||||||
server,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
jellyfin_baseurl = os.getenv("JELLYFIN_BASEURL", None)
|
|
||||||
jellyfin_token = os.getenv("JELLYFIN_TOKEN", None)
|
|
||||||
|
|
||||||
if jellyfin_baseurl and jellyfin_token:
|
|
||||||
servers.extend(
|
|
||||||
jellyfin_emby_server_connection(
|
|
||||||
jellyfin_baseurl, jellyfin_token, "jellyfin"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
emby_baseurl = os.getenv("EMBY_BASEURL", None)
|
|
||||||
emby_token = os.getenv("EMBY_TOKEN", None)
|
|
||||||
|
|
||||||
if emby_baseurl and emby_token:
|
|
||||||
servers.extend(
|
|
||||||
jellyfin_emby_server_connection(emby_baseurl, emby_token, "emby")
|
|
||||||
)
|
|
||||||
|
|
||||||
return servers
|
|
||||||
|
|
||||||
|
|
||||||
def should_sync_server(server_1_type, server_2_type):
|
def should_sync_server(server_1_type, server_2_type):
|
||||||
sync_from_plex_to_jellyfin = str_to_bool(
|
sync_from_plex_to_jellyfin = str_to_bool(
|
||||||
os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True")
|
os.getenv("SYNC_FROM_PLEX_TO_JELLYFIN", "True")
|
||||||
@@ -262,10 +83,10 @@ def should_sync_server(server_1_type, server_2_type):
|
|||||||
|
|
||||||
|
|
||||||
def main_loop():
|
def main_loop():
|
||||||
logfile = os.getenv("LOGFILE", "log.log")
|
log_file = os.getenv("LOG_FILE", os.getenv("LOGFILE", "log.log"))
|
||||||
# Delete logfile if it exists
|
# Delete log_file if it exists
|
||||||
if os.path.exists(logfile):
|
if os.path.exists(log_file):
|
||||||
os.remove(logfile)
|
os.remove(log_file)
|
||||||
|
|
||||||
dryrun = str_to_bool(os.getenv("DRYRUN", "False"))
|
dryrun = str_to_bool(os.getenv("DRYRUN", "False"))
|
||||||
logger(f"Dryrun: {dryrun}", 1)
|
logger(f"Dryrun: {dryrun}", 1)
|
||||||
@@ -333,24 +154,24 @@ def main_loop():
|
|||||||
server_1, server_2, blacklist_users, whitelist_users, user_mapping
|
server_1, server_2, blacklist_users, whitelist_users, user_mapping
|
||||||
)
|
)
|
||||||
|
|
||||||
logger("Creating watched lists", 1)
|
server_1_libraries, server_2_libraries = setup_libraries(
|
||||||
server_1_watched = server_1[1].get_watched(
|
server_1[1],
|
||||||
server_1_users,
|
server_2[1],
|
||||||
blacklist_library,
|
blacklist_library,
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
blacklist_library_type,
|
||||||
|
whitelist_library,
|
||||||
whitelist_library_type,
|
whitelist_library_type,
|
||||||
library_mapping,
|
library_mapping,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logger("Creating watched lists", 1)
|
||||||
|
server_1_watched = server_1[1].get_watched(
|
||||||
|
server_1_users, server_1_libraries
|
||||||
|
)
|
||||||
logger("Finished creating watched list server 1", 1)
|
logger("Finished creating watched list server 1", 1)
|
||||||
|
|
||||||
server_2_watched = server_2[1].get_watched(
|
server_2_watched = server_2[1].get_watched(
|
||||||
server_2_users,
|
server_2_users, server_2_libraries
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
)
|
)
|
||||||
logger("Finished creating watched list server 2", 1)
|
logger("Finished creating watched list server 2", 1)
|
||||||
|
|
||||||
|
|||||||
68
src/plex.py
68
src/plex.py
@@ -19,10 +19,7 @@ from src.functions import (
|
|||||||
log_marked,
|
log_marked,
|
||||||
str_to_bool,
|
str_to_bool,
|
||||||
)
|
)
|
||||||
from src.library import (
|
from src.library import generate_library_guids_dict
|
||||||
check_skip_logic,
|
|
||||||
generate_library_guids_dict,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
@@ -186,7 +183,7 @@ def get_user_library_watched(user, user_plex, library):
|
|||||||
if show_guids and episode_guids:
|
if show_guids and episode_guids:
|
||||||
watched[show_guids] = episode_guids
|
watched[show_guids] = episode_guids
|
||||||
logger(
|
logger(
|
||||||
f"Plex: Added {episode_guids} to {user_name} {show_guids} watched list",
|
f"Plex: Added {episode_guids} to {user_name} watched list",
|
||||||
3,
|
3,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -317,7 +314,15 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
else:
|
else:
|
||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(user.title, library, movies_search.title, None, None)
|
log_marked(
|
||||||
|
"Plex",
|
||||||
|
user_plex.friendlyName,
|
||||||
|
user.title,
|
||||||
|
library,
|
||||||
|
movies_search.title,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
elif video_status["time"] > 60_000:
|
elif video_status["time"] > 60_000:
|
||||||
msg = f"Plex: {movies_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library}"
|
msg = f"Plex: {movies_search.title} as partially watched for {floor(video_status['time'] / 60_000)} minutes for {user.title} in {library}"
|
||||||
if not dryrun:
|
if not dryrun:
|
||||||
@@ -327,6 +332,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
"Plex",
|
||||||
|
user_plex.friendlyName,
|
||||||
user.title,
|
user.title,
|
||||||
library,
|
library,
|
||||||
movies_search.title,
|
movies_search.title,
|
||||||
@@ -358,6 +365,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
"Plex",
|
||||||
|
user_plex.friendlyName,
|
||||||
user.title,
|
user.title,
|
||||||
library,
|
library,
|
||||||
show_search.title,
|
show_search.title,
|
||||||
@@ -372,6 +381,8 @@ def update_user_watched(user, user_plex, library, videos, dryrun):
|
|||||||
logger(msg, 6)
|
logger(msg, 6)
|
||||||
|
|
||||||
log_marked(
|
log_marked(
|
||||||
|
"Plex",
|
||||||
|
user_plex.friendlyName,
|
||||||
user.title,
|
user.title,
|
||||||
library,
|
library,
|
||||||
show_search.title,
|
show_search.title,
|
||||||
@@ -466,15 +477,24 @@ class Plex:
|
|||||||
logger(f"Plex: Failed to get users, Error: {e}", 2)
|
logger(f"Plex: Failed to get users, Error: {e}", 2)
|
||||||
raise Exception(e)
|
raise Exception(e)
|
||||||
|
|
||||||
def get_watched(
|
def get_libraries(self):
|
||||||
self,
|
try:
|
||||||
users,
|
output = {}
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
libraries = self.plex.library.sections()
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
for library in libraries:
|
||||||
library_mapping,
|
library_title = library.title
|
||||||
):
|
library_type = library.type
|
||||||
|
|
||||||
|
output[library_title] = library_type
|
||||||
|
|
||||||
|
return output
|
||||||
|
except Exception as e:
|
||||||
|
logger(f"Plex: Failed to get libraries, Error: {e}", 2)
|
||||||
|
raise Exception(e)
|
||||||
|
|
||||||
|
def get_watched(self, users, sync_libraries):
|
||||||
try:
|
try:
|
||||||
# Get all libraries
|
# Get all libraries
|
||||||
users_watched = {}
|
users_watched = {}
|
||||||
@@ -500,23 +520,7 @@ class Plex:
|
|||||||
libraries = user_plex.library.sections()
|
libraries = user_plex.library.sections()
|
||||||
|
|
||||||
for library in libraries:
|
for library in libraries:
|
||||||
library_title = library.title
|
if library.title not in sync_libraries:
|
||||||
library_type = library.type
|
|
||||||
|
|
||||||
skip_reason = check_skip_logic(
|
|
||||||
library_title,
|
|
||||||
library_type,
|
|
||||||
blacklist_library,
|
|
||||||
whitelist_library,
|
|
||||||
blacklist_library_type,
|
|
||||||
whitelist_library_type,
|
|
||||||
library_mapping,
|
|
||||||
)
|
|
||||||
|
|
||||||
if skip_reason:
|
|
||||||
logger(
|
|
||||||
f"Plex: Skipping library {library_title}: {skip_reason}", 1
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
user_watched = get_user_library_watched(user, user_plex, library)
|
user_watched = get_user_library_watched(user, user_plex, library)
|
||||||
|
|||||||
42
src/users.py
42
src/users.py
@@ -89,3 +89,45 @@ def generate_server_users(server, users):
|
|||||||
server_users[jellyfin_user] = jellyfin_id
|
server_users[jellyfin_user] = jellyfin_id
|
||||||
|
|
||||||
return server_users
|
return server_users
|
||||||
|
|
||||||
|
|
||||||
|
def setup_users(
|
||||||
|
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)
|
||||||
|
logger(f"Server 1 users: {server_1_users}", 1)
|
||||||
|
logger(f"Server 2 users: {server_2_users}", 1)
|
||||||
|
|
||||||
|
users = combine_user_lists(server_1_users, server_2_users, user_mapping)
|
||||||
|
logger(f"User list that exist on both servers {users}", 1)
|
||||||
|
|
||||||
|
users_filtered = filter_user_lists(users, blacklist_users, whitelist_users)
|
||||||
|
logger(f"Filtered user list {users_filtered}", 1)
|
||||||
|
|
||||||
|
output_server_1_users = generate_server_users(server_1, users_filtered)
|
||||||
|
output_server_2_users = generate_server_users(server_2, users_filtered)
|
||||||
|
|
||||||
|
# Check if users is none or empty
|
||||||
|
if output_server_1_users is None or len(output_server_1_users) == 0:
|
||||||
|
logger(
|
||||||
|
f"No users found for server 1 {server_1[0]}, users: {server_1_users}, overlapping users {users}, filtered users {users_filtered}, server 1 users {server_1[1].users}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if output_server_2_users is None or len(output_server_2_users) == 0:
|
||||||
|
logger(
|
||||||
|
f"No users found for server 2 {server_2[0]}, users: {server_2_users}, overlapping users {users} filtered users {users_filtered}, server 2 users {server_2[1].users}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
output_server_1_users is None
|
||||||
|
or len(output_server_1_users) == 0
|
||||||
|
or output_server_2_users is None
|
||||||
|
or len(output_server_2_users) == 0
|
||||||
|
):
|
||||||
|
raise Exception("No users found for one or both servers")
|
||||||
|
|
||||||
|
logger(f"Server 1 users: {output_server_1_users}", 1)
|
||||||
|
logger(f"Server 2 users: {output_server_2_users}", 1)
|
||||||
|
|
||||||
|
return output_server_1_users, output_server_2_users
|
||||||
|
|||||||
@@ -5,33 +5,6 @@ from src.functions import logger, search_mapping, contains_nested
|
|||||||
from src.library import generate_library_guids_dict
|
from src.library import generate_library_guids_dict
|
||||||
|
|
||||||
|
|
||||||
def combine_watched_dicts(dicts: list):
|
|
||||||
# Ensure that the input is a list of dictionaries
|
|
||||||
if not all(isinstance(d, dict) for d in dicts):
|
|
||||||
raise ValueError("Input must be a list of dictionaries")
|
|
||||||
|
|
||||||
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 check_remove_entry(video, library, video_index, library_watched_list_2):
|
def check_remove_entry(video, library, video_index, library_watched_list_2):
|
||||||
if video_index is not None:
|
if video_index is not None:
|
||||||
if (
|
if (
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "True"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "True"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "True"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "True"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "True"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ DRYRUN = "False"
|
|||||||
DEBUG = "True"
|
DEBUG = "True"
|
||||||
|
|
||||||
## Debugging level, "info" is default, "debug" is more verbose
|
## Debugging level, "info" is default, "debug" is more verbose
|
||||||
DEBUG_LEVEL = "info"
|
DEBUG_LEVEL = "debug"
|
||||||
|
|
||||||
## If set to true then the script will only run once and then exit
|
## If set to true then the script will only run once and then exit
|
||||||
RUN_ONLY_ONCE = "True"
|
RUN_ONLY_ONCE = "True"
|
||||||
@@ -62,11 +62,11 @@ WHITELIST_USERS = "jellyplex_watched"
|
|||||||
## 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 seperated list for multiple servers
|
||||||
PLEX_BASEURL = "https://localhost:32400"
|
PLEX_BASEURL = "http://localhost:32400"
|
||||||
|
|
||||||
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
## Plex token https://support.plex.tv/articles/204059436-finding-an-authentication-token-x-plex-token/
|
||||||
## Comma seperated list for multiple servers
|
## Comma seperated list for multiple servers
|
||||||
PLEX_TOKEN = "mVaCzSyd78uoWkCBzZ_Y"
|
PLEX_TOKEN = "6S28yhwKg4y-vAXYMi1c"
|
||||||
|
|
||||||
## 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 seperated for multiple options
|
||||||
|
|||||||
@@ -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.watched import cleanup_watched, combine_watched_dicts
|
from src.watched import cleanup_watched
|
||||||
|
|
||||||
tv_shows_watched_list_1 = {
|
tv_shows_watched_list_1 = {
|
||||||
frozenset(
|
frozenset(
|
||||||
@@ -541,116 +541,3 @@ def test_mapping_cleanup_watched():
|
|||||||
|
|
||||||
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",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Spirited Away",
|
|
||||||
"tmdb": "129",
|
|
||||||
"imdb": "tt0245429",
|
|
||||||
"locations": ("Spirited Away (2001) Bluray-1080p.mkv",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Castle in the Sky",
|
|
||||||
"tmdb": "10515",
|
|
||||||
"imdb": "tt0092067",
|
|
||||||
"locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{"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"),
|
|
||||||
}
|
|
||||||
): [
|
|
||||||
{
|
|
||||||
"imdb": "tt4460418",
|
|
||||||
"title": "The Rabbit Hole",
|
|
||||||
"locations": (
|
|
||||||
"11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv",
|
|
||||||
),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{"test3": {"Subbed Anime": {}}},
|
|
||||||
]
|
|
||||||
expected = {
|
|
||||||
"test3": {
|
|
||||||
"Anime Movies": [
|
|
||||||
{
|
|
||||||
"title": "Ponyo",
|
|
||||||
"tmdb": "12429",
|
|
||||||
"imdb": "tt0876563",
|
|
||||||
"locations": ("Ponyo (2008) Bluray-1080p.mkv",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Spirited Away",
|
|
||||||
"tmdb": "129",
|
|
||||||
"imdb": "tt0245429",
|
|
||||||
"locations": ("Spirited Away (2001) Bluray-1080p.mkv",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": "Castle in the Sky",
|
|
||||||
"tmdb": "10515",
|
|
||||||
"imdb": "tt0092067",
|
|
||||||
"locations": ("Castle in the Sky (1986) Bluray-1080p.mkv",),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"Anime Shows": {},
|
|
||||||
"Cartoon Shows": {},
|
|
||||||
"Shows": {
|
|
||||||
frozenset(
|
|
||||||
{
|
|
||||||
("tmdb", "64464"),
|
|
||||||
("tvdb", "301824"),
|
|
||||||
("tvrage", "45210"),
|
|
||||||
("title", "11.22.63"),
|
|
||||||
("locations", ("11.22.63",)),
|
|
||||||
("imdb", "tt2879552"),
|
|
||||||
}
|
|
||||||
): [
|
|
||||||
{
|
|
||||||
"imdb": "tt4460418",
|
|
||||||
"title": "The Rabbit Hole",
|
|
||||||
"locations": (
|
|
||||||
"11.22.63 S01E01 The Rabbit Hole Bluray-1080p.mkv",
|
|
||||||
),
|
|
||||||
"status": {"completed": True, "time": 0},
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Subbed Anime": {},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assert combine_watched_dicts(input_watched) == expected
|
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ def parse_args():
|
|||||||
description="Check the mark.log file that is generated by the CI to make sure it contains the expected values"
|
description="Check the mark.log file that is generated by the CI to make sure it contains the expected values"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--dry", action="store_true", help="Check the mark.log file for dry-run"
|
"--guids", action="store_true", help="Check the mark.log file for guids"
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--locations", action="store_true", help="Check the mark.log file for locations"
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--write", action="store_true", help="Check the mark.log file for write-run"
|
"--write", action="store_true", help="Check the mark.log file for write-run"
|
||||||
@@ -74,81 +77,110 @@ def check_marklog(lines, expected_values):
|
|||||||
def main():
|
def main():
|
||||||
args = parse_args()
|
args = parse_args()
|
||||||
expected_jellyfin = [
|
expected_jellyfin = [
|
||||||
"jellyplex_watched/Movies/Five Nights at Freddy's",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom Movies/Movie Two (2021)",
|
||||||
"jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom TV Shows/Greatest Show Ever 3000/Episode 2",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/Five Nights at Freddy's",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670",
|
||||||
"jellyplex_watched/Movies/The Family Plan",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
||||||
"jellyplex_watched/Movies/Five Nights at Freddy's",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741",
|
||||||
"jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5",
|
"Emby/Emby-Server/jellyplex_watched/Custom Movies/Movie Two",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
"Emby/Emby-Server/jellyplex_watched/Custom TV Shows/Greatest Show Ever (3000)/S01E02",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5",
|
"Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Five Nights at Freddy's",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
"Emby/Emby-Server/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
||||||
]
|
]
|
||||||
expected_emby = [
|
expected_emby = [
|
||||||
"jellyplex_watched/Movies/Tears of Steel",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom Movies/Movie Three (2022)",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom TV Shows/Greatest Show Ever 3000/Episode 3",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/Tears of Steel",
|
||||||
"JellyUser/Movies/Tears of Steel",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom Movies/Movie Three (2022)",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom TV Shows/Greatest Show Ever (3000)/S01E03",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Tears of Steel",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
||||||
]
|
]
|
||||||
expected_plex = [
|
expected_plex = [
|
||||||
"JellyUser/Movies/Big Buck Bunny",
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Big Buck Bunny",
|
||||||
"JellyUser/Movies/Killers of the Flower Moon/4",
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Killers of the Flower Moon/4",
|
||||||
"JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom TV Shows/Greatest Show Ever (3000)/S01E01",
|
||||||
"JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
"jellyplex_watched/Movies/Big Buck Bunny",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
||||||
"jellyplex_watched/Movies/The Family Plan",
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom Movies/Movie One (2020)",
|
||||||
"jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Big Buck Bunny",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
"Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"Emby/Emby-Server/jellyplex_watched/Custom TV Shows/Greatest Show Ever (3000)/S01E01",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/Custom Movies/Movie One",
|
||||||
]
|
]
|
||||||
|
|
||||||
expected_dry = expected_emby + expected_plex + expected_jellyfin
|
expected_locations = expected_emby + expected_plex + expected_jellyfin
|
||||||
|
# Remove Custom Movies/TV Shows as they should not have guids
|
||||||
|
expected_guids = [item for item in expected_locations if "Custom" not in item ]
|
||||||
|
|
||||||
|
|
||||||
expected_write = [
|
expected_write = [
|
||||||
"jellyplex_watched/Movies/Five Nights at Freddy's",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom Movies/Movie Two (2021)",
|
||||||
"jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom TV Shows/Greatest Show Ever 3000/Episode 2",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/Five Nights at Freddy's",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/301215",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/300670",
|
||||||
"JellyUser/Movies/Big Buck Bunny",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Aftermath",
|
||||||
"JellyUser/Movies/Killers of the Flower Moon/4",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/300741",
|
||||||
"JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Big Buck Bunny",
|
||||||
"JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Killers of the Flower Moon/4",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom TV Shows/Greatest Show Ever (3000)/S01E01",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/The Unquiet Dead",
|
||||||
"jellyplex_watched/Movies/Tears of Steel",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Doctor Who/Aliens of London (1)/4",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
"jellyplex_watched/Movies/Big Buck Bunny",
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
||||||
"jellyplex_watched/Movies/The Family Plan",
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom Movies/Movie One (2020)",
|
||||||
"jellyplex_watched/Movies/Five Nights at Freddy's",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom Movies/Movie Three (2022)",
|
||||||
"jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5",
|
"Plex/JellyPlex-CI/jellyplex_watched/Custom TV Shows/Greatest Show Ever 3000/Episode 3",
|
||||||
"jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
"Plex/JellyPlex-CI/jellyplex_watched/Movies/Tears of Steel",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
"Plex/JellyPlex-CI/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Parallels and Interiors/240429",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Big Buck Bunny",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
"Emby/Emby-Server/jellyplex_watched/Movies/The Family Plan",
|
||||||
"jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Five Nights at Freddy's",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5",
|
"Emby/Emby-Server/jellyplex_watched/Movies/The Hunger Games: The Ballad of Songbirds & Snakes/5",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
"Emby/Emby-Server/jellyplex_watched/Movies/Killers of the Flower Moon/4",
|
||||||
"jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
"Emby/Emby-Server/jellyplex_watched/Custom TV Shows/Greatest Show Ever (3000)/S01E01",
|
||||||
"JellyUser/Movies/Tears of Steel",
|
"Emby/Emby-Server/jellyplex_watched/Custom TV Shows/Greatest Show Ever (3000)/S01E02",
|
||||||
"JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4",
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Rose",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The End of the World/5",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/The Unquiet Dead",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Doctor Who (2005)/Aliens of London (1)/4",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Departure/5",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/Secrets and Lies",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/TV Shows/Monarch: Legacy of Monsters/The Way Out",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/Custom Movies/Movie One",
|
||||||
|
"Emby/Emby-Server/jellyplex_watched/Custom Movies/Movie Two",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom Movies/Movie Three (2022)",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Custom TV Shows/Greatest Show Ever (3000)/S01E03",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Movies/Tears of Steel",
|
||||||
|
"Jellyfin/Jellyfin-Server/JellyUser/Shows/Monarch: Legacy of Monsters/Parallels and Interiors/4"
|
||||||
]
|
]
|
||||||
|
|
||||||
# Expected values for the mark.log file, dry-run is slightly different than write-run
|
# Expected values for the mark.log file, dry-run is slightly different than write-run
|
||||||
# due to some of the items being copied over from one server to another and now being there
|
# due to some of the items being copied over from one server to another and now being there
|
||||||
# for the next server run.
|
# for the next server run.
|
||||||
if args.dry:
|
if args.guids:
|
||||||
expected_values = expected_dry
|
expected_values = expected_guids
|
||||||
|
elif args.locations:
|
||||||
|
expected_values = expected_locations
|
||||||
elif args.write:
|
elif args.write:
|
||||||
expected_values = expected_write
|
expected_values = expected_write
|
||||||
elif args.plex:
|
elif args.plex:
|
||||||
@@ -164,6 +196,12 @@ def main():
|
|||||||
lines = read_marklog()
|
lines = read_marklog()
|
||||||
if not check_marklog(lines, expected_values):
|
if not check_marklog(lines, expected_values):
|
||||||
print("Failed to validate marklog")
|
print("Failed to validate marklog")
|
||||||
|
for line in lines:
|
||||||
|
# Remove the newline character
|
||||||
|
line = line.strip()
|
||||||
|
|
||||||
|
print(line)
|
||||||
|
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
print("Successfully validated marklog")
|
print("Successfully validated marklog")
|
||||||
|
|||||||
Reference in New Issue
Block a user