29 Commits

Author SHA1 Message Date
Luigi311
87b4a950f1 Merge pull request #75 from luigi311/dev
Variants, Pin versions, CI, Plex usernames
2023-05-17 13:38:25 -06:00
Luigi311
9f61c7338d Plex: Cleanup username_title 2023-05-17 13:22:00 -06:00
Luigi311
ffc81dad69 CI: Add back in dev based on alpine 2023-05-15 15:12:25 -06:00
Luigi311
7eba46b5cb plex: Fix username/title 2023-05-15 14:57:46 -06:00
Luigi311
aa177666a5 Plex: Fix username/title selection 2023-05-15 11:17:28 -06:00
Luigi311
7de7b42fd2 Users: Default to username and fall back to title 2023-05-15 11:10:03 -06:00
Luigi311
03d1fd8019 Log both servers users instead of exiting immediately 2023-05-15 10:44:30 -06:00
Luigi311
485ec5fe2d Add docker-compose file 2023-04-29 20:31:24 -06:00
Luigi311
59bfbd9811 Merge pull request #71 from luigi311/fix-docker-build/push
Do not publish on PR, fix condition check on build
2023-04-13 13:02:55 -06:00
Luigi311
1e485b37f8 Do not publish on PR, fix condition check on build 2023-04-13 12:56:52 -06:00
Luigi311
4adf94f24b Update ci.yml
Action: Use github.repository and github.actor instead
2023-04-13 10:28:01 -06:00
Luigi311
1a0fab36d3 Merge pull request #66 from Nicba1010/main
General build improvements
2023-04-13 09:50:59 -06:00
Roberto Banić
a1ef3b5a8d Add conditional to DockerHub login 2023-04-13 16:45:05 +02:00
Luigi311
0c47ee7119 Merge pull request #68 from Nicba1010/refactor-black-white
Refactor black/whitelist processing
2023-04-13 08:37:38 -06:00
Roberto Banić
e51cf6e482 Refactor black/whitelist processing 2023-04-13 12:56:28 +02:00
Roberto Banić
24d5de813d Remove DOCKER_USERNAME environment variable from docker_meta step 2023-04-13 11:23:32 +02:00
Roberto Banić
9921b2a355 Change is_default_branch to other default branch check 2023-04-13 11:21:28 +02:00
Roberto Banić
faa378c75e Add is_default_branch conditional to latest tag 2023-04-13 11:20:19 +02:00
Roberto Banić
26199100dc Update tags 2023-04-13 11:19:56 +02:00
Roberto Banić
bee854f059 Exclude DockerHub in case there is no username set 2023-04-13 10:48:03 +02:00
Roberto Banić
73c1ebf3ed Pin pytest version 2023-04-13 02:26:12 +02:00
Roberto Banić
397dd17429 Specify Python version 2023-04-13 02:26:11 +02:00
Roberto Banić
73d18dad92 Rename Dockerfile to Dockerfile.alpine 2023-04-13 02:26:10 +02:00
Roberto Banić
94d63a3fdb Add ghcr.io image name to the docker metadata action step 2023-04-13 02:26:09 +02:00
Roberto Banić
120d89e8be Add dashes to tags 2023-04-13 02:26:08 +02:00
Roberto Banić
eb5534c61c Add ghcr.io registry 2023-04-13 02:26:07 +02:00
Roberto Banić
99d217e8f1 Update ci.yml to perform a multi-variant build 2023-04-13 02:26:05 +02:00
Roberto Banić
f7e3f8ae2a Update Dockerfile to use the alpine Python 3 base image 2023-04-13 02:26:04 +02:00
Roberto Banić
2cebd2d73d Pin dependency versions to enable reproducible builds 2023-04-13 02:25:13 +02:00
11 changed files with 272 additions and 160 deletions

View File

@@ -1 +1,15 @@
.dockerignore
.env .env
.env.sample
.git
.github
.gitignore
.idea
.vscode
Dockerfile*
README.md
test
venv

View File

@@ -24,25 +24,34 @@ jobs:
docker: docker:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs: pytest needs: pytest
strategy:
matrix:
include:
- dockerfile: Dockerfile.alpine
variant: alpine
- dockerfile: Dockerfile.slim
variant: slim
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Docker meta - name: Docker meta
id: docker_meta id: docker_meta
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
if: "${{ env.DOCKER_USERNAME != '' }}"
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
with: with:
images: ${{ secrets.DOCKER_USERNAME }}/jellyplex-watched # list of Docker images to use as base name for tags images: |
${{ secrets.DOCKER_USERNAME }}/jellyplex-watched,enable=${{ secrets.DOCKER_USERNAME != '' }}
# Do not push to ghcr.io on PRs due to permission issues
ghcr.io/${{ github.repository }},enable=${{ github.event_name != 'pull_request' }}
tags: | tags: |
type=raw,value=latest,enable={{is_default_branch}} type=raw,value=latest,enable=${{ matrix.variant == 'alpine' && github.ref_name == github.event.repository.default_branch }}
type=ref,event=branch type=raw,value=dev,enable=${{ matrix.variant == 'alpine' && github.ref_name == 'dev' }}
type=ref,event=pr type=raw,value=latest,suffix=-${{ matrix.variant }},enable={{ is_default_branch }}
type=semver,pattern={{version}} type=ref,event=branch,suffix=-${{ matrix.variant }}
type=semver,pattern={{major}}.{{minor}} type=ref,event=pr,suffix=-${{ matrix.variant }}
type=sha type=semver,pattern={{ version }},suffix=-${{ matrix.variant }}
type=semver,pattern={{ major }}.{{ minor }},suffix=-${{ matrix.variant }}
type=sha,suffix=-${{ matrix.variant }}
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v2 uses: docker/setup-qemu-action@v2
@@ -51,30 +60,40 @@ jobs:
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v2
- name: Login to DockerHub - name: Login to DockerHub
if: "${{ steps.docker_meta.outcome == 'success' }}" env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
if: "${{ env.DOCKER_USERNAME != '' }}"
uses: docker/login-action@v2 uses: docker/login-action@v2
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_TOKEN }} password: ${{ secrets.DOCKER_TOKEN }}
- name: Login to GitHub Container Registry
if: "${{ steps.docker_meta.outcome == 'success' }}"
uses: docker/login-action@v2
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build - name: Build
id: build id: build
if: "${{ steps.docker_meta.outcome == 'skipped' }}" if: "${{ steps.docker_meta.outputs.tags == '' }}"
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
file: ./Dockerfile file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: false push: false
tags: jellyplex-watched:action tags: jellyplex-watched:action
- name: Build Push - name: Build Push
id: build_push id: build_push
if: "${{ steps.docker_meta.outcome == 'success' }}" if: "${{ steps.docker_meta.outputs.tags != '' }}"
uses: docker/build-push-action@v3 uses: docker/build-push-action@v3
with: with:
context: . context: .
file: ./Dockerfile file: ${{ matrix.dockerfile }}
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
push: true push: true
tags: ${{ steps.docker_meta.outputs.tags }} tags: ${{ steps.docker_meta.outputs.tags }}

41
Dockerfile.alpine Normal file
View File

@@ -0,0 +1,41 @@
FROM python:3-alpine
ENV DRYRUN 'True'
ENV DEBUG 'True'
ENV DEBUG_LEVEL 'INFO'
ENV SLEEP_DURATION '3600'
ENV LOGFILE 'log.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 ''
WORKDIR /app
COPY ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
CMD ["python", "-u", "main.py"]

View File

@@ -32,12 +32,6 @@ ENV WHITELIST_USERS ''
WORKDIR /app WORKDIR /app
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
COPY ./requirements.txt ./ COPY ./requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt RUN pip install --no-cache-dir -r requirements.txt

31
docker-compose.yml Normal file
View File

@@ -0,0 +1,31 @@
version: '3'
services:
jellyplex-watched:
image: luigi311/jellyplex-watched:latest
container_name: jellyplex-watched
restart: always
environment:
- DRYRUN=True
- DEBUG=True
- DEBUG_LEVEL=info
- RUN_ONLY_ONCE=False
- SLEEP_DURATION=3600
- LOGFILE=/tmp/log.log
- USER_MAPPING=
- LIBRARY_MAPPING={"TV Shows":"Shows"}
- BLACKLIST_LIBRARY=
- WHITELIST_LIBRARY=
- BLACKLIST_LIBRARY_TYPE=
- WHITELIST_LIBRARY_TYPE=
- BLACKLIST_USERS=
- WHITELIST_USERS=
- PLEX_BASEURL=
- PLEX_TOKEN=
- JELLYFIN_BASEURL=
- JELLYFIN_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

View File

@@ -1,4 +1,4 @@
plexapi PlexAPI==4.13.4
requests requests==2.28.2
python-dotenv python-dotenv==1.0.0
aiohttp aiohttp==3.8.4

View File

@@ -11,18 +11,20 @@ def setup_black_white_lists(
library_mapping=None, library_mapping=None,
user_mapping=None, user_mapping=None,
): ):
blacklist_library, blacklist_library_type, blacklist_users = setup_black_lists( blacklist_library, blacklist_library_type, blacklist_users = setup_x_lists(
blacklist_library, blacklist_library,
blacklist_library_type, blacklist_library_type,
blacklist_users, blacklist_users,
"White",
library_mapping, library_mapping,
user_mapping, user_mapping,
) )
whitelist_library, whitelist_library_type, whitelist_users = setup_white_lists( whitelist_library, whitelist_library_type, whitelist_users = setup_x_lists(
whitelist_library, whitelist_library,
whitelist_library_type, whitelist_library_type,
whitelist_users, whitelist_users,
"Black",
library_mapping, library_mapping,
user_mapping, user_mapping,
) )
@@ -36,104 +38,93 @@ def setup_black_white_lists(
whitelist_users, whitelist_users,
) )
def setup_x_lists(
def setup_black_lists( xlist_library,
blacklist_library, xlist_library_type,
blacklist_library_type, xlist_users,
blacklist_users, xlist_type,
library_mapping=None, library_mapping=None,
user_mapping=None, user_mapping=None,
): ):
if blacklist_library: if xlist_library:
if len(blacklist_library) > 0: if len(xlist_library) > 0:
blacklist_library = blacklist_library.split(",") xlist_library = xlist_library.split(",")
blacklist_library = [x.strip() for x in blacklist_library] xlist_library = [x.strip() for x in xlist_library]
if library_mapping: if library_mapping:
temp_library = [] temp_library = []
for library in blacklist_library: for library in xlist_library:
library_other = search_mapping(library_mapping, library) library_other = search_mapping(library_mapping, library)
if library_other: if library_other:
temp_library.append(library_other) temp_library.append(library_other)
blacklist_library = blacklist_library + temp_library xlist_library = xlist_library + temp_library
else: else:
blacklist_library = [] xlist_library = []
logger(f"Blacklist Library: {blacklist_library}", 1) logger(f"{xlist_type}list Library: {xlist_library}", 1)
if blacklist_library_type: if xlist_library_type:
if len(blacklist_library_type) > 0: if len(xlist_library_type) > 0:
blacklist_library_type = blacklist_library_type.split(",") xlist_library_type = xlist_library_type.split(",")
blacklist_library_type = [x.lower().strip() for x in blacklist_library_type] xlist_library_type = [x.lower().strip() for x in xlist_library_type]
else: else:
blacklist_library_type = [] xlist_library_type = []
logger(f"Blacklist Library Type: {blacklist_library_type}", 1) logger(f"{xlist_type}list Library Type: {xlist_library_type}", 1)
if blacklist_users: if xlist_users:
if len(blacklist_users) > 0: if len(xlist_users) > 0:
blacklist_users = blacklist_users.split(",") xlist_users = xlist_users.split(",")
blacklist_users = [x.lower().strip() for x in blacklist_users] xlist_users = [x.lower().strip() for x in xlist_users]
if user_mapping: if user_mapping:
temp_users = [] temp_users = []
for user in blacklist_users: for user in xlist_users:
user_other = search_mapping(user_mapping, user) user_other = search_mapping(user_mapping, user)
if user_other: if user_other:
temp_users.append(user_other) temp_users.append(user_other)
blacklist_users = blacklist_users + temp_users xlist_users = xlist_users + temp_users
else:
blacklist_users = []
logger(f"Blacklist Users: {blacklist_users}", 1)
return blacklist_library, blacklist_library_type, blacklist_users
def setup_white_lists(
whitelist_library,
whitelist_library_type,
whitelist_users,
library_mapping=None,
user_mapping=None,
):
if whitelist_library:
if len(whitelist_library) > 0:
whitelist_library = whitelist_library.split(",")
whitelist_library = [x.strip() for x in whitelist_library]
if library_mapping:
temp_library = []
for library in whitelist_library:
library_other = search_mapping(library_mapping, library)
if library_other:
temp_library.append(library_other)
whitelist_library = whitelist_library + temp_library
else:
whitelist_library = []
logger(f"Whitelist Library: {whitelist_library}", 1)
if whitelist_library_type:
if len(whitelist_library_type) > 0:
whitelist_library_type = whitelist_library_type.split(",")
whitelist_library_type = [x.lower().strip() for x in whitelist_library_type]
else:
whitelist_library_type = []
logger(f"Whitelist Library Type: {whitelist_library_type}", 1)
if whitelist_users:
if len(whitelist_users) > 0:
whitelist_users = whitelist_users.split(",")
whitelist_users = [x.lower().strip() for x in whitelist_users]
if user_mapping:
temp_users = []
for user in whitelist_users:
user_other = search_mapping(user_mapping, user)
if user_other:
temp_users.append(user_other)
whitelist_users = whitelist_users + temp_users
else: else:
whitelist_users = [] xlist_users = []
else: else:
whitelist_users = [] xlist_users = []
logger(f"Whitelist Users: {whitelist_users}", 1) logger(f"{xlist_type}list Users: {xlist_users}", 1)
return xlist_library, xlist_library_type, xlist_users
return whitelist_library, whitelist_library_type, whitelist_users

View File

@@ -40,15 +40,23 @@ def setup_users(
# Check if users is none or empty # Check if users is none or empty
if output_server_1_users is None or len(output_server_1_users) == 0: if output_server_1_users is None or len(output_server_1_users) == 0:
raise Exception( logger(
f"No users found for server 1 {server_1[0]}, users found {users}, filtered users {users_filtered}, server 1 users {server_1[1].users}" 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: if output_server_2_users is None or len(output_server_2_users) == 0:
raise Exception( logger(
f"No users found for server 2 {server_2[0]}, users found {users} filtered users {users_filtered}, server 2 users {server_2[1].users}" 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 1 users: {output_server_1_users}", 1)
logger(f"Server 2 users: {output_server_2_users}", 1) logger(f"Server 2 users: {output_server_2_users}", 1)

View File

@@ -126,7 +126,7 @@ def get_user_library_watched_show(show):
def get_user_library_watched(user, user_plex, library): def get_user_library_watched(user, user_plex, library):
try: try:
user_name = user.title.lower() user_name = user.username.lower() if user.username else user.title.lower()
user_watched = {} user_watched = {}
user_watched[user_name] = {} user_watched[user_name] = {}
@@ -509,10 +509,16 @@ class Plex:
user_other = search_mapping(user_mapping, user) user_other = search_mapping(user_mapping, user)
for index, value in enumerate(self.users): for index, value in enumerate(self.users):
if user.lower() == value.title.lower(): username_title = (
value.username.lower()
if value.username
else value.title.lower()
)
if user.lower() == username_title:
user = self.users[index] user = self.users[index]
break break
elif user_other and user_other.lower() == value.title.lower(): elif user_other and user_other.lower() == username_title:
user = self.users[index] user = self.users[index]
break break

View File

@@ -11,7 +11,11 @@ def generate_user_list(server):
server_users = [] server_users = []
if server_type == "plex": if server_type == "plex":
server_users = [x.title.lower() for x in server_connection.users] for user in server_connection.users:
server_users.append(
user.username.lower() if user.username else user.title.lower()
)
elif server_type == "jellyfin": elif server_type == "jellyfin":
server_users = [key.lower() for key in server_connection.users.keys()] server_users = [key.lower() for key in server_connection.users.keys()]
@@ -66,9 +70,13 @@ def generate_server_users(server, users):
if server[0] == "plex": if server[0] == "plex":
server_users = [] server_users = []
for plex_user in server[1].users: for plex_user in server[1].users:
username_title = (
plex_user.username if plex_user.username else plex_user.title
)
if ( if (
plex_user.title.lower() in users.keys() username_title.lower() in users.keys()
or plex_user.title.lower() in users.values() or username_title.lower() in users.values()
): ):
server_users.append(plex_user) server_users.append(plex_user)
elif server[0] == "jellyfin": elif server[0] == "jellyfin":

View File

@@ -1 +1 @@
pytest pytest==7.3.0