diff options
Diffstat (limited to 'youtube/__init__.py')
| -rw-r--r-- | youtube/__init__.py | 137 |
1 files changed, 113 insertions, 24 deletions
diff --git a/youtube/__init__.py b/youtube/__init__.py index 3c85d47..885cadc 100644 --- a/youtube/__init__.py +++ b/youtube/__init__.py @@ -1,31 +1,68 @@ -from youtube import util -from .get_app_version import app_version +import logging +import os +import re +import traceback +from sys import exc_info + import flask +import jinja2 from flask import request +from flask_babel import Babel + +from youtube import util +from .get_app_version import app_version import settings -import traceback -import re -from sys import exc_info + yt_app = flask.Flask(__name__) yt_app.config['TEMPLATES_AUTO_RELOAD'] = True yt_app.url_map.strict_slashes = False + +# Don't log full tracebacks for handled FetchErrors +class FetchErrorFilter(logging.Filter): + def filter(self, record): + if record.exc_info and record.exc_info[0] == util.FetchError: + return False + return True + +yt_app.logger.addFilter(FetchErrorFilter()) # yt_app.jinja_env.trim_blocks = True # yt_app.jinja_env.lstrip_blocks = True +# Configure Babel for i18n +yt_app.config['BABEL_DEFAULT_LOCALE'] = 'en' +# Use absolute path for translations directory to avoid issues with package structure changes +_app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +yt_app.config['BABEL_TRANSLATION_DIRECTORIES'] = os.path.join(_app_root, 'translations') + +def get_locale(): + """Determine the best locale based on user preference or browser settings""" + # Check if user has a language preference in settings + if hasattr(settings, 'language') and settings.language: + locale = settings.language + print(f'[i18n] Using user preference: {locale}') + return locale + # Otherwise, use browser's Accept-Language header + # Only match languages with available translations + locale = request.accept_languages.best_match(['en', 'es']) + print(f'[i18n] Using browser language: {locale}') + return locale or 'en' + +babel = Babel(yt_app, locale_selector=get_locale) + yt_app.add_url_rule('/settings', 'settings_page', settings.settings_page, methods=['POST', 'GET']) @yt_app.route('/') def homepage(): - return flask.render_template('home.html', title="Youtube local") + return flask.render_template('home.html', title="YT Local") @yt_app.route('/licenses') def licensepage(): return flask.render_template( 'licenses.html', - title="Licenses - YouTube Local" + title="Licenses - YT Local" ) @@ -39,7 +76,7 @@ theme_names = { @yt_app.context_processor def inject_theme_preference(): return { - 'theme_path': '/youtube.com/static/' + theme_names[settings.theme] + '.css', + 'theme_path': f'/youtube.com/static/{theme_names[settings.theme]}.css', 'settings': settings, # Detect version 'current_version': app_version()['version'], @@ -53,7 +90,10 @@ def commatize(num): if num is None: return '' if isinstance(num, str): - num = int(num) + try: + num = int(num) + except ValueError: + return num return '{:,}'.format(num) @@ -96,25 +136,60 @@ def timestamps(text): @yt_app.errorhandler(500) def error_page(e): slim = request.args.get('slim', False) # whether it was an ajax request - if (exc_info()[0] == util.FetchError - and exc_info()[1].code == '429' - and settings.route_tor - ): - error_message = ('Error: Youtube blocked the request because the Tor' - ' exit node is overutilized. Try getting a new exit node by' - ' using the New Identity button in the Tor Browser.') - if exc_info()[1].error_message: - error_message += '\n\n' + exc_info()[1].error_message - if exc_info()[1].ip: - error_message += '\n\nExit node IP address: ' + exc_info()[1].ip - return flask.render_template('error.html', error_message=error_message, slim=slim), 502 - return flask.render_template('error.html', traceback=traceback.format_exc(), slim=slim), 500 + if exc_info()[0] == util.FetchError: + fetch_err = exc_info()[1] + error_code = fetch_err.code + + if error_code == '429' and settings.route_tor: + error_message = ('Error: YouTube blocked the request because the Tor' + ' exit node is overutilized. Try getting a new exit node by' + ' using the New Identity button in the Tor Browser.') + if fetch_err.error_message: + error_message += f'\n\n{fetch_err.error_message}' + if fetch_err.ip: + error_message += f'\n\nExit node IP address: {fetch_err.ip}' + return flask.render_template('error.html', error_message=error_message, slim=slim), 502 + + elif error_code == '429': + error_message = ('YouTube is temporarily blocking requests from your IP address (429 Too Many Requests).\n\n' + 'Try:\n' + '• Wait a few minutes and refresh\n' + '• Enable Tor routing in Settings for automatic IP rotation\n' + '• Use a VPN to change your IP address') + if fetch_err.ip: + error_message += f'\n\nYour IP: {fetch_err.ip}' + return flask.render_template('error.html', error_message=error_message, slim=slim), 429 + + elif error_code == '502' and ('Failed to resolve' in str(fetch_err) or 'Failed to establish' in str(fetch_err)): + error_message = ('Could not connect to YouTube.\n\n' + 'Check your internet connection and try again.') + return flask.render_template('error.html', error_message=error_message, slim=slim), 502 + + elif error_code == '403': + error_message = ('YouTube blocked this request (403 Forbidden).\n\n' + 'Try enabling Tor routing in Settings.') + return flask.render_template('error.html', error_message=error_message, slim=slim), 403 + + elif error_code == '404': + error_message = 'Error: The page you are looking for isn\'t here.' + return flask.render_template('error.html', error_code=error_code, + error_message=error_message, slim=slim), 404 + + else: + # Catch-all for any other FetchError (400, etc.) + error_message = f'Error communicating with YouTube ({error_code}).' + if fetch_err.error_message: + error_message += f'\n\n{fetch_err.error_message}' + return flask.render_template('error.html', error_message=error_message, slim=slim), 502 + + return flask.render_template('error.html', traceback=traceback.format_exc(), + slim=slim), 500 font_choices = { 0: 'initial', - 1: 'arial, "liberation sans", sans-serif', - 2: '"liberation serif", "times new roman", calibri, carlito, serif', + 1: '"liberation serif", "times new roman", calibri, carlito, serif', + 2: 'arial, "liberation sans", sans-serif', 3: 'verdana, sans-serif', 4: 'tahoma, sans-serif', } @@ -129,3 +204,17 @@ def get_css(): ), mimetype='text/css', ) + + +# This is okay because the flask urlize function puts the href as the first +# property +YOUTUBE_LINK_RE = re.compile(r'<a href="(' + util.YOUTUBE_URL_RE_STR + ')"') +old_urlize = jinja2.filters.urlize + + +def prefix_urlize(*args, **kwargs): + result = old_urlize(*args, **kwargs) + return YOUTUBE_LINK_RE.sub(r'<a href="/\1"', result) + + +jinja2.filters.urlize = prefix_urlize |
