diff --git a/.dockerignore b/.dockerignore index 9e5de6a..9697da3 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,3 +5,4 @@ docker-compose.yml Dockerfile.* README.md shared/tokens.json +sockets/ \ No newline at end of file diff --git a/Dockerfile.plex_authentication b/Dockerfile.plex_authentication index 5367cd0..ad142df 100644 --- a/Dockerfile.plex_authentication +++ b/Dockerfile.plex_authentication @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi # Copy the rest of the application COPY . . -# Expose port 8000 to the outside world -EXPOSE 8000 - -# Run gunicorn on port 8000, add more workers if required -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_authentication_wsgi:app"] +# Run gunicorn using Unix socket +CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_authentication.sock", "plex_authentication_wsgi:app"] \ No newline at end of file diff --git a/Dockerfile.plex_request b/Dockerfile.plex_request index 7728bb3..95ed726 100644 --- a/Dockerfile.plex_request +++ b/Dockerfile.plex_request @@ -19,8 +19,5 @@ RUN grep -E "#.*($SERVICE_NAME|all)" requirements.txt | awk '{print $0}' > servi # Copy the rest of the application COPY . . -# Expose port 8000 to the outside world -EXPOSE 8000 - -# Run gunicorn on port 8000, add more workers if required -CMD ["gunicorn", "--bind", "0.0.0.0:8000", "plex_request_wsgi:app"] +# Run gunicorn using Unix socket +CMD ["gunicorn", "--bind", "unix:/app/sockets/plex_request.sock", "plex_request_wsgi:app"] \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index ee1962b..03c8c69 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -159,8 +159,7 @@ services: pull_policy: always volumes: - ./shared/tokens.json:/app/shared/tokens.json - ports: - - 8010:8000 + - ./sockets:/app/sockets env_file: - .env restart: unless-stopped @@ -175,8 +174,7 @@ services: pull_policy: always volumes: - ./shared/tokens.json:/app/shared/tokens.json - ports: - - 8011:8000 + - ./sockets:/app/sockets env_file: - .env restart: unless-stopped @@ -191,6 +189,7 @@ services: pull_policy: always volumes: - ${PLEX_REQUEST_SSL_PATH:-/dev/null}:${PLEX_REQUEST_SSL_PATH:-/dev/null}:ro + - ./sockets:/app/sockets ports: - 8012:8000 env_file: @@ -199,6 +198,7 @@ services: profiles: [plex_request, all] depends_on: - plex_request + - plex_authentication networks: default: diff --git a/plex_authentication.py b/plex_authentication.py index 9dce503..c25cf7a 100644 --- a/plex_authentication.py +++ b/plex_authentication.py @@ -1,11 +1,12 @@ import requests import json import urllib.parse -from flask import Flask, jsonify, redirect, url_for +from flask import Flask, jsonify, redirect, url_for, request from shared.shared import watchlist, plexHeaders, tokensFilename from shared.overseerr import getUserForPlexToken from shared.plex import getServerToken from werkzeug.middleware.proxy_fix import ProxyFix +from werkzeug.middleware.dispatcher import DispatcherMiddleware # instantiate the app app = Flask(__name__) @@ -32,29 +33,44 @@ def setupComplete(pin): authToken = pinRequest.json()['authToken'] if authToken: - user = getUserForPlexToken(authToken) - serverToken = getServerToken(authToken) - userId = user['id'] + return handleToken(authToken) - with open(tokensFilename, 'r+') as tokensFile: - tokens = json.load(tokensFile) - token = tokens.get(userId, { 'etag': '' }) - token['token'] = authToken - token['serverToken'] = serverToken + return jsonify('There was an error, please try again.') - if serverToken == authToken: - token['owner'] = True +@app.route('/token', methods=['POST']) +def receiveToken(): + token = request.json.get('token') + if token: + return handleToken(token) + else: + return createResponse({'error': 'No token provided'}, 400) - tokens[userId] = token - tokensFile.seek(0) - json.dump(tokens, tokensFile) - tokensFile.truncate() +def handleToken(token): + user = getUserForPlexToken(token) + serverToken = getServerToken(token) + userId = user['id'] - return jsonify('Successfully authenticated!') + updateTokensFile(userId, token, serverToken) - return jsonify('There was an error, please try again.') + return createResponse({'message': 'Token received and stored successfully'}, 201) + +def updateTokensFile(userId, token, serverToken): + with open(tokensFilename, 'r+') as tokensFile: + tokens = json.load(tokensFile) + tokenEntry = tokens.get(userId, {'etag': ''}) + tokenEntry['token'] = token + tokenEntry['serverToken'] = serverToken + tokens[userId] = tokenEntry + tokensFile.seek(0) + json.dump(tokens, tokensFile) + tokensFile.truncate() + +def createResponse(data, statusCode): + response = jsonify(data), statusCode + return response app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_port=1, x_prefix=1) - +app.wsgi_app = DispatcherMiddleware(app.wsgi_app, {'/auth': app.wsgi_app}) + if __name__ == '__main__': app.run('127.0.0.1', 12598) \ No newline at end of file diff --git a/plex_request.py b/plex_request.py index 945d3aa..407bbd6 100644 --- a/plex_request.py +++ b/plex_request.py @@ -258,6 +258,8 @@ def all(): urlSuffix = "/children" if mediaTypeNum == mediaTypeNums['season'] else "" metadataAllRequest = requests.get(f"{plex['metadataHost']}library/metadata/{guid}{urlSuffix}", headers=metadataHeaders, params=args) if metadataAllRequest.status_code == 200: + libraryId = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId'] + additionalMetadata = metadataAllRequest.json()['MediaContainer']['Metadata'][0] if mediaTypeNum == mediaTypeNums['season'] or mediaTypeNum == mediaTypeNums['episode']: additionalMetadata['key'] = f"/library/request/{mediaType}/{mediaTypeNum}/{guid}/season/{season}" @@ -266,8 +268,8 @@ def all(): additionalMetadata['ratingKey'] = "12065" additionalMetadata['librarySectionTitle'] = "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :" - additionalMetadata['librarySectionID'] = plex['serverMovieLibraryId'] if mediaType == 'movie' else plex['serverTvShowLibraryId'] - additionalMetadata['librarySectionKey'] = f"/library/sections/{additionalMetadata['librarySectionID']}" + additionalMetadata['librarySectionID'] = libraryId + additionalMetadata['librarySectionKey'] = f"/library/sections/{libraryId}" additionalMetadata['Media'] = [{ "videoResolution": "Request Season :" if mediaTypeNum == mediaTypeNums['episode'] else "Request :" }] diff --git a/plex_request_nginx.conf b/plex_request_nginx.conf index bbea33b..9c47dda 100644 --- a/plex_request_nginx.conf +++ b/plex_request_nginx.conf @@ -98,19 +98,23 @@ server { proxy_pass ${plex_server_host}; proxy_read_timeout 86400; - # add_header 'Access-Control-Allow-Origin' '*'; - # add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; - # add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; - - # if ($request_method = 'OPTIONS') { - # add_header 'Access-Control-Allow-Origin' '*'; - # add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; - # add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; - # add_header 'Access-Control-Max-Age' 1728000; - # add_header 'Content-Type' 'text/plain; charset=utf-8'; - # add_header 'Content-Length' 0; - # return 204; - # } + proxy_hide_header 'Access-Control-Allow-Origin'; + proxy_hide_header 'Access-Control-Allow-Methods'; + proxy_hide_header 'Access-Control-Allow-Headers'; + + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; + add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + + if ($request_method = 'OPTIONS') { + add_header 'Access-Control-Allow-Origin' '*'; + add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE, PATCH'; + add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization'; + add_header 'Access-Control-Max-Age' 1728000; + add_header 'Content-Type' 'text/plain; charset=utf-8'; + add_header 'Content-Length' 0; + return 204; + } # return 302 https://$request_uri; @@ -125,21 +129,73 @@ server { # error_page 401 =200 /ldaplogin; } + location /auth { + proxy_pass http://unix:/app/sockets/plex_authentication.sock; + } + + location /web/index.html { + proxy_pass ${plex_server_host}/web/index.html; + + # access_log logs/plex.access.log; + + sub_filter '' ''; + sub_filter_once on; + } + location = /library/all { - proxy_pass http://plex_request:8000; + proxy_pass http://unix:/app/sockets/plex_request.sock; # access_log logs/plex.access.log; } location ~ ^/library/metadata/[^/]+/children$ { - proxy_pass http://plex_request:8000; + proxy_pass http://unix:/app/sockets/plex_request.sock; # access_log logs/plex.access.log; } location /library/request/ { - proxy_pass http://plex_request:8000; - + proxy_pass http://unix:/app/sockets/plex_request.sock; + # access_log logs/plex.access.log; } } diff --git a/shared/overseerr.py b/shared/overseerr.py index 3ae2cb9..32c00e0 100644 --- a/shared/overseerr.py +++ b/shared/overseerr.py @@ -18,7 +18,7 @@ def getUserForPlexToken(token): def getUserForPlexServerToken(serverToken): with open(tokensFilename, 'r') as tokensFile: tokens = json.load(tokensFile).values() - token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), next(token['token'] for token in tokens if token['owner'] == True)) + token = next((token['token'] for token in tokens if token['serverToken'] == serverToken), plex['serverApiKey']) return getUserForPlexToken(token) diff --git a/shared/shared.py b/shared/shared.py index c8c20e8..b496f47 100644 --- a/shared/shared.py +++ b/shared/shared.py @@ -7,11 +7,18 @@ default_pattern = r"<[a-z0-9_]+>" -@env.parser_for("string") -def stringEnvParser(value): +def commonEnvParser(value, convert=None): if value is not None and re.match(default_pattern, value): return None - return value + return convert(value) if convert else value + +@env.parser_for("integer") +def integerEnvParser(value): + return commonEnvParser(value, int) + +@env.parser_for("string") +def stringEnvParser(value): + return commonEnvParser(value) watchlist = { 'plexProduct': env.string('WATCHLIST_PLEX_PRODUCT', default=None), @@ -24,9 +31,9 @@ def stringEnvParser(value): 'radarrPath': env.string('BLACKHOLE_RADARR_PATH', default=None), 'sonarrPath': env.string('BLACKHOLE_SONARR_PATH', default=None), 'failIfNotCached': env.bool('BLACKHOLE_FAIL_IF_NOT_CACHED', default=None), - 'rdMountRefreshSeconds': env.int('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None), - 'waitForTorrentTimeout': env.int('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None), - 'historyPageSize': env.int('BLACKHOLE_HISTORY_PAGE_SIZE', default=None), + 'rdMountRefreshSeconds': env.integer('BLACKHOLE_RD_MOUNT_REFRESH_SECONDS', default=None), + 'waitForTorrentTimeout': env.integer('BLACKHOLE_WAIT_FOR_TORRENT_TIMEOUT', default=None), + 'historyPageSize': env.integer('BLACKHOLE_HISTORY_PAGE_SIZE', default=None), } server = { @@ -39,8 +46,8 @@ def stringEnvParser(value): 'serverHost': env.string('PLEX_SERVER_HOST', default=None), 'serverMachineId': env.string('PLEX_SERVER_MACHINE_ID', default=None), 'serverApiKey': env.string('PLEX_SERVER_API_KEY', default=None), - 'serverMovieLibraryId': env.int('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None), - 'serverTvShowLibraryId': env.int('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None) + 'serverMovieLibraryId': env.integer('PLEX_SERVER_MOVIE_LIBRARY_ID', default=None), + 'serverTvShowLibraryId': env.integer('PLEX_SERVER_TV_SHOW_LIBRARY_ID', default=None) } overseerr = { diff --git a/sockets/.gitignore b/sockets/.gitignore new file mode 100644 index 0000000..86d0cb2 --- /dev/null +++ b/sockets/.gitignore @@ -0,0 +1,4 @@ +# Ignore everything in this directory +* +# Except this file +!.gitignore \ No newline at end of file