mirror of https://github.com/morpheus65535/bazarr
Fixed root url redirect not working with base_url defined. #1857
This commit is contained in:
parent
2a298860a4
commit
0f4af48be6
|
@ -1,8 +1,6 @@
|
||||||
# coding=utf-8
|
# coding=utf-8
|
||||||
|
|
||||||
import os
|
from flask import Flask, redirect
|
||||||
|
|
||||||
from flask import Flask
|
|
||||||
from flask_socketio import SocketIO
|
from flask_socketio import SocketIO
|
||||||
|
|
||||||
from .get_args import args
|
from .get_args import args
|
||||||
|
@ -13,12 +11,8 @@ socketio = SocketIO()
|
||||||
|
|
||||||
def create_app():
|
def create_app():
|
||||||
# Flask Setup
|
# Flask Setup
|
||||||
app = Flask(__name__,
|
app = Flask(__name__)
|
||||||
template_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'frontend', 'build'),
|
|
||||||
static_folder=os.path.join(os.path.dirname(os.path.dirname(__file__)), '..', 'frontend', 'build', 'static'),
|
|
||||||
static_url_path=base_url.rstrip('/') + '/static')
|
|
||||||
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
||||||
app.route = prefix_route(app.route, base_url.rstrip('/'))
|
|
||||||
|
|
||||||
app.config["SECRET_KEY"] = settings.general.flask_secret_key
|
app.config["SECRET_KEY"] = settings.general.flask_secret_key
|
||||||
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
|
app.config['JSONIFY_PRETTYPRINT_REGULAR'] = True
|
||||||
|
@ -31,19 +25,14 @@ def create_app():
|
||||||
|
|
||||||
socketio.init_app(app, path=base_url.rstrip('/')+'/api/socket.io', cors_allowed_origins='*',
|
socketio.init_app(app, path=base_url.rstrip('/')+'/api/socket.io', cors_allowed_origins='*',
|
||||||
async_mode='threading', allow_upgrades=False, transports='polling')
|
async_mode='threading', allow_upgrades=False, transports='polling')
|
||||||
|
|
||||||
|
@app.errorhandler(404)
|
||||||
|
def page_not_found(_):
|
||||||
|
return redirect(base_url, code=302)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
||||||
|
|
||||||
def prefix_route(route_function, prefix='', mask='{0}{1}'):
|
|
||||||
# Defines a new route function with a prefix.
|
|
||||||
# The mask argument is a `format string` formatted with, in that order: prefix, route
|
|
||||||
def newroute(route, *args, **kwargs):
|
|
||||||
# New function to prefix the route
|
|
||||||
return route_function(mask.format(prefix, route), *args, **kwargs)
|
|
||||||
|
|
||||||
return newroute
|
|
||||||
|
|
||||||
|
|
||||||
class ReverseProxied(object):
|
class ReverseProxied(object):
|
||||||
def __init__(self, app):
|
def __init__(self, app):
|
||||||
self.app = app
|
self.app = app
|
||||||
|
|
|
@ -8,7 +8,7 @@ import io
|
||||||
from waitress.server import create_server
|
from waitress.server import create_server
|
||||||
|
|
||||||
from api import api_bp_list
|
from api import api_bp_list
|
||||||
|
from .ui import ui_bp
|
||||||
from .get_args import args
|
from .get_args import args
|
||||||
from .config import settings, base_url
|
from .config import settings, base_url
|
||||||
from .database import database
|
from .database import database
|
||||||
|
@ -17,7 +17,9 @@ from .app import create_app
|
||||||
app = create_app()
|
app = create_app()
|
||||||
|
|
||||||
for item in api_bp_list:
|
for item in api_bp_list:
|
||||||
app.register_blueprint(item, url_prefix=base_url.rstrip('/') + '/api')
|
ui_bp.register_blueprint(item, url_prefix='/api')
|
||||||
|
|
||||||
|
app.register_blueprint(ui_bp, url_prefix=base_url.rstrip('/'))
|
||||||
|
|
||||||
|
|
||||||
class Server:
|
class Server:
|
||||||
|
|
|
@ -0,0 +1,165 @@
|
||||||
|
# coding=utf-8
|
||||||
|
|
||||||
|
import os
|
||||||
|
import requests
|
||||||
|
import mimetypes
|
||||||
|
|
||||||
|
from flask import request, redirect, abort, render_template, Response, session, send_file, stream_with_context, \
|
||||||
|
send_from_directory, Blueprint
|
||||||
|
from functools import wraps
|
||||||
|
from urllib.parse import unquote
|
||||||
|
|
||||||
|
from constants import headers
|
||||||
|
from sonarr.info import get_sonarr_info, url_sonarr
|
||||||
|
from radarr.info import get_radarr_info, url_radarr
|
||||||
|
from utilities.helper import check_credentials
|
||||||
|
|
||||||
|
from .config import settings, base_url
|
||||||
|
from .database import System
|
||||||
|
from .get_args import args
|
||||||
|
|
||||||
|
ui_bp = Blueprint('ui', __name__,
|
||||||
|
template_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
||||||
|
'frontend', 'build'),
|
||||||
|
static_folder=os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend',
|
||||||
|
'build', 'static'),
|
||||||
|
static_url_path=base_url.rstrip('/') + '/static')
|
||||||
|
|
||||||
|
|
||||||
|
def check_login(actual_method):
|
||||||
|
@wraps(actual_method)
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
if settings.auth.type == 'basic':
|
||||||
|
auth = request.authorization
|
||||||
|
if not (auth and check_credentials(request.authorization.username, request.authorization.password)):
|
||||||
|
return ('Unauthorized', 401, {
|
||||||
|
'WWW-Authenticate': 'Basic realm="Login Required"'
|
||||||
|
})
|
||||||
|
elif settings.auth.type == 'form':
|
||||||
|
if 'logged_in' not in session:
|
||||||
|
return abort(401, message="Unauthorized")
|
||||||
|
actual_method(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
@ui_bp.route('/', defaults={'path': ''})
|
||||||
|
@ui_bp.route('/<path:path>')
|
||||||
|
def catch_all(path):
|
||||||
|
auth = True
|
||||||
|
if settings.auth.type == 'basic':
|
||||||
|
auth = request.authorization
|
||||||
|
if not (auth and check_credentials(request.authorization.username, request.authorization.password)):
|
||||||
|
return ('Unauthorized', 401, {
|
||||||
|
'WWW-Authenticate': 'Basic realm="Login Required"'
|
||||||
|
})
|
||||||
|
elif settings.auth.type == 'form':
|
||||||
|
if 'logged_in' not in session:
|
||||||
|
auth = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
updated = System.get().updated
|
||||||
|
except Exception:
|
||||||
|
updated = '0'
|
||||||
|
|
||||||
|
inject = dict()
|
||||||
|
inject["baseUrl"] = base_url
|
||||||
|
inject["canUpdate"] = not args.no_update
|
||||||
|
inject["hasUpdate"] = updated != '0'
|
||||||
|
|
||||||
|
if auth:
|
||||||
|
inject["apiKey"] = settings.auth.apikey
|
||||||
|
|
||||||
|
template_url = base_url
|
||||||
|
if not template_url.endswith("/"):
|
||||||
|
template_url += "/"
|
||||||
|
|
||||||
|
return render_template("index.html", BAZARR_SERVER_INJECT=inject, baseUrl=template_url)
|
||||||
|
|
||||||
|
|
||||||
|
@ui_bp.route('/assets/<path:filename>')
|
||||||
|
def web_assets(filename):
|
||||||
|
# forcing mimetypes to prevent bad configuration in Windows registry to prevent Bazarr UI from showing
|
||||||
|
mimetypes.add_type('application/javascript', '.js')
|
||||||
|
mimetypes.add_type('text/css', '.css')
|
||||||
|
mimetypes.add_type('font/woff2', '.woff2')
|
||||||
|
mimetypes.add_type('image/svg+xml', '.svg')
|
||||||
|
mimetypes.add_type('image/png', '.png')
|
||||||
|
mimetypes.add_type('image/x-icon', '.ico')
|
||||||
|
|
||||||
|
# send_from_directory needs an absolute path then we'll use realpath() here
|
||||||
|
path = os.path.realpath(os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(__file__))), 'frontend',
|
||||||
|
'build', 'assets'))
|
||||||
|
return send_from_directory(path, filename)
|
||||||
|
|
||||||
|
|
||||||
|
@check_login
|
||||||
|
@ui_bp.route('/bazarr.log')
|
||||||
|
def download_log():
|
||||||
|
return send_file(os.path.join(args.config_dir, 'log', 'bazarr.log'), cache_timeout=0, as_attachment=True)
|
||||||
|
|
||||||
|
|
||||||
|
@check_login
|
||||||
|
@ui_bp.route('/images/series/<path:url>', methods=['GET'])
|
||||||
|
def series_images(url):
|
||||||
|
url = url.strip("/")
|
||||||
|
apikey = settings.sonarr.apikey
|
||||||
|
baseUrl = settings.sonarr.base_url
|
||||||
|
if get_sonarr_info.is_legacy():
|
||||||
|
url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' +
|
||||||
|
apikey).replace('poster-250', 'poster-500')
|
||||||
|
else:
|
||||||
|
url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' +
|
||||||
|
apikey).replace('poster-250', 'poster-500')
|
||||||
|
try:
|
||||||
|
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
|
||||||
|
except Exception:
|
||||||
|
return '', 404
|
||||||
|
else:
|
||||||
|
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
|
||||||
|
|
||||||
|
|
||||||
|
@check_login
|
||||||
|
@ui_bp.route('/images/movies/<path:url>', methods=['GET'])
|
||||||
|
def movies_images(url):
|
||||||
|
apikey = settings.radarr.apikey
|
||||||
|
baseUrl = settings.radarr.base_url
|
||||||
|
if get_radarr_info.is_legacy():
|
||||||
|
url_image = url_radarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey
|
||||||
|
else:
|
||||||
|
url_image = url_radarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey
|
||||||
|
try:
|
||||||
|
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
|
||||||
|
except Exception:
|
||||||
|
return '', 404
|
||||||
|
else:
|
||||||
|
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
|
||||||
|
|
||||||
|
|
||||||
|
def configured():
|
||||||
|
System.update({System.configured: '1'}).execute()
|
||||||
|
|
||||||
|
|
||||||
|
@check_login
|
||||||
|
@ui_bp.route('/test', methods=['GET'])
|
||||||
|
@ui_bp.route('/test/<protocol>/<path:url>', methods=['GET'])
|
||||||
|
def proxy(protocol, url):
|
||||||
|
url = protocol + '://' + unquote(url)
|
||||||
|
params = request.args
|
||||||
|
try:
|
||||||
|
result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers)
|
||||||
|
except Exception as e:
|
||||||
|
return dict(status=False, error=repr(e))
|
||||||
|
else:
|
||||||
|
if result.status_code == 200:
|
||||||
|
try:
|
||||||
|
version = result.json()['version']
|
||||||
|
return dict(status=True, version=version)
|
||||||
|
except Exception:
|
||||||
|
return dict(status=False, error='Error Occurred. Check your settings.')
|
||||||
|
elif result.status_code == 401:
|
||||||
|
return dict(status=False, error='Access Denied. Check API key.')
|
||||||
|
elif result.status_code == 404:
|
||||||
|
return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?')
|
||||||
|
elif 300 <= result.status_code <= 399:
|
||||||
|
return dict(status=False, error='Wrong URL Base.')
|
||||||
|
else:
|
||||||
|
return dict(status=False, error=result.raise_for_status())
|
158
bazarr/main.py
158
bazarr/main.py
|
@ -13,16 +13,8 @@ if os.path.isfile(version_file):
|
||||||
os.environ["BAZARR_VERSION"] = bazarr_version.lstrip('v')
|
os.environ["BAZARR_VERSION"] = bazarr_version.lstrip('v')
|
||||||
|
|
||||||
import app.libs # noqa W0611
|
import app.libs # noqa W0611
|
||||||
import requests # noqa E402
|
|
||||||
|
|
||||||
from threading import Thread # noqa E402
|
from threading import Thread # noqa E402
|
||||||
from functools import wraps # noqa E402
|
|
||||||
from urllib.parse import unquote # noqa E402
|
|
||||||
|
|
||||||
import mimetypes # noqa E402
|
|
||||||
|
|
||||||
from flask import request, redirect, abort, render_template, Response, session, send_file, stream_with_context, \
|
|
||||||
send_from_directory # noqa E402
|
|
||||||
|
|
||||||
from app.get_args import args # noqa E402
|
from app.get_args import args # noqa E402
|
||||||
from app.config import settings, configure_proxy_func, base_url # noqa E402
|
from app.config import settings, configure_proxy_func, base_url # noqa E402
|
||||||
|
@ -32,11 +24,7 @@ from app.notifier import update_notifier # noqa E402
|
||||||
from languages.get_languages import load_language_in_db # noqa E402
|
from languages.get_languages import load_language_in_db # noqa E402
|
||||||
from app.signalr_client import sonarr_signalr_client, radarr_signalr_client # noqa E402
|
from app.signalr_client import sonarr_signalr_client, radarr_signalr_client # noqa E402
|
||||||
from app.check_update import apply_update, check_releases # noqa E402
|
from app.check_update import apply_update, check_releases # noqa E402
|
||||||
from app.server import app, webserver # noqa E402
|
from app.server import webserver # noqa E402
|
||||||
from utilities.helper import check_credentials # noqa E402
|
|
||||||
from constants import headers # noqa E402
|
|
||||||
from sonarr.info import get_sonarr_info, url_sonarr # noqa E402
|
|
||||||
from radarr.info import get_radarr_info, url_radarr # noqa E402
|
|
||||||
|
|
||||||
# Install downloaded update
|
# Install downloaded update
|
||||||
if bazarr_version != '':
|
if bazarr_version != '':
|
||||||
|
@ -55,150 +43,6 @@ login_auth = settings.auth.type
|
||||||
|
|
||||||
update_notifier()
|
update_notifier()
|
||||||
|
|
||||||
|
|
||||||
def check_login(actual_method):
|
|
||||||
@wraps(actual_method)
|
|
||||||
def wrapper(*args, **kwargs):
|
|
||||||
if settings.auth.type == 'basic':
|
|
||||||
auth = request.authorization
|
|
||||||
if not (auth and check_credentials(request.authorization.username, request.authorization.password)):
|
|
||||||
return ('Unauthorized', 401, {
|
|
||||||
'WWW-Authenticate': 'Basic realm="Login Required"'
|
|
||||||
})
|
|
||||||
elif settings.auth.type == 'form':
|
|
||||||
if 'logged_in' not in session:
|
|
||||||
return abort(401, message="Unauthorized")
|
|
||||||
actual_method(*args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(404)
|
|
||||||
def page_not_found():
|
|
||||||
return redirect(base_url, code=302)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/', defaults={'path': ''})
|
|
||||||
@app.route('/<path:path>')
|
|
||||||
def catch_all(path):
|
|
||||||
auth = True
|
|
||||||
if settings.auth.type == 'basic':
|
|
||||||
auth = request.authorization
|
|
||||||
if not (auth and check_credentials(request.authorization.username, request.authorization.password)):
|
|
||||||
return ('Unauthorized', 401, {
|
|
||||||
'WWW-Authenticate': 'Basic realm="Login Required"'
|
|
||||||
})
|
|
||||||
elif settings.auth.type == 'form':
|
|
||||||
if 'logged_in' not in session:
|
|
||||||
auth = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
updated = System.get().updated
|
|
||||||
except Exception:
|
|
||||||
updated = '0'
|
|
||||||
|
|
||||||
inject = dict()
|
|
||||||
inject["baseUrl"] = base_url
|
|
||||||
inject["canUpdate"] = not args.no_update
|
|
||||||
inject["hasUpdate"] = updated != '0'
|
|
||||||
|
|
||||||
if auth:
|
|
||||||
inject["apiKey"] = settings.auth.apikey
|
|
||||||
|
|
||||||
template_url = base_url
|
|
||||||
if not template_url.endswith("/"):
|
|
||||||
template_url += "/"
|
|
||||||
|
|
||||||
return render_template("index.html", BAZARR_SERVER_INJECT=inject, baseUrl=template_url)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route('/assets/<path:filename>')
|
|
||||||
def web_assets(filename):
|
|
||||||
# forcing mimetypes to prevent bad configuration in Windows registry to prevent Bazarr UI from showing
|
|
||||||
mimetypes.add_type('application/javascript', '.js')
|
|
||||||
mimetypes.add_type('text/css', '.css')
|
|
||||||
mimetypes.add_type('font/woff2', '.woff2')
|
|
||||||
mimetypes.add_type('image/svg+xml', '.svg')
|
|
||||||
mimetypes.add_type('image/png', '.png')
|
|
||||||
mimetypes.add_type('image/x-icon', '.ico')
|
|
||||||
|
|
||||||
# send_from_directory needs an absolute path then we'll use realpath() here
|
|
||||||
path = os.path.realpath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'frontend', 'build', 'assets'))
|
|
||||||
return send_from_directory(path, filename)
|
|
||||||
|
|
||||||
|
|
||||||
@check_login
|
|
||||||
@app.route('/bazarr.log')
|
|
||||||
def download_log():
|
|
||||||
return send_file(os.path.join(args.config_dir, 'log', 'bazarr.log'), cache_timeout=0, as_attachment=True)
|
|
||||||
|
|
||||||
|
|
||||||
@check_login
|
|
||||||
@app.route('/images/series/<path:url>', methods=['GET'])
|
|
||||||
def series_images(url):
|
|
||||||
url = url.strip("/")
|
|
||||||
apikey = settings.sonarr.apikey
|
|
||||||
baseUrl = settings.sonarr.base_url
|
|
||||||
if get_sonarr_info.is_legacy():
|
|
||||||
url_image = (url_sonarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' +
|
|
||||||
apikey).replace('poster-250', 'poster-500')
|
|
||||||
else:
|
|
||||||
url_image = (url_sonarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' +
|
|
||||||
apikey).replace('poster-250', 'poster-500')
|
|
||||||
try:
|
|
||||||
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
|
|
||||||
except Exception:
|
|
||||||
return '', 404
|
|
||||||
else:
|
|
||||||
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
|
|
||||||
|
|
||||||
|
|
||||||
@check_login
|
|
||||||
@app.route('/images/movies/<path:url>', methods=['GET'])
|
|
||||||
def movies_images(url):
|
|
||||||
apikey = settings.radarr.apikey
|
|
||||||
baseUrl = settings.radarr.base_url
|
|
||||||
if get_radarr_info.is_legacy():
|
|
||||||
url_image = url_radarr() + '/api/' + url.lstrip(baseUrl) + '?apikey=' + apikey
|
|
||||||
else:
|
|
||||||
url_image = url_radarr() + '/api/v3/' + url.lstrip(baseUrl) + '?apikey=' + apikey
|
|
||||||
try:
|
|
||||||
req = requests.get(url_image, stream=True, timeout=15, verify=False, headers=headers)
|
|
||||||
except Exception:
|
|
||||||
return '', 404
|
|
||||||
else:
|
|
||||||
return Response(stream_with_context(req.iter_content(2048)), content_type=req.headers['content-type'])
|
|
||||||
|
|
||||||
|
|
||||||
def configured():
|
|
||||||
System.update({System.configured: '1'}).execute()
|
|
||||||
|
|
||||||
|
|
||||||
@check_login
|
|
||||||
@app.route('/test', methods=['GET'])
|
|
||||||
@app.route('/test/<protocol>/<path:url>', methods=['GET'])
|
|
||||||
def proxy(protocol, url):
|
|
||||||
url = protocol + '://' + unquote(url)
|
|
||||||
params = request.args
|
|
||||||
try:
|
|
||||||
result = requests.get(url, params, allow_redirects=False, verify=False, timeout=5, headers=headers)
|
|
||||||
except Exception as e:
|
|
||||||
return dict(status=False, error=repr(e))
|
|
||||||
else:
|
|
||||||
if result.status_code == 200:
|
|
||||||
try:
|
|
||||||
version = result.json()['version']
|
|
||||||
return dict(status=True, version=version)
|
|
||||||
except Exception:
|
|
||||||
return dict(status=False, error='Error Occurred. Check your settings.')
|
|
||||||
elif result.status_code == 401:
|
|
||||||
return dict(status=False, error='Access Denied. Check API key.')
|
|
||||||
elif result.status_code == 404:
|
|
||||||
return dict(status=False, error='Cannot get version. Maybe unsupported legacy API call?')
|
|
||||||
elif 300 <= result.status_code <= 399:
|
|
||||||
return dict(status=False, error='Wrong URL Base.')
|
|
||||||
else:
|
|
||||||
return dict(status=False, error=result.raise_for_status())
|
|
||||||
|
|
||||||
|
|
||||||
if settings.general.getboolean('use_sonarr'):
|
if settings.general.getboolean('use_sonarr'):
|
||||||
Thread(target=sonarr_signalr_client.start).start()
|
Thread(target=sonarr_signalr_client.start).start()
|
||||||
if settings.general.getboolean('use_radarr'):
|
if settings.general.getboolean('use_radarr'):
|
||||||
|
|
Loading…
Reference in New Issue