aboutsummaryrefslogtreecommitdiffstats
path: root/youtube/__init__.py
diff options
context:
space:
mode:
Diffstat (limited to 'youtube/__init__.py')
-rw-r--r--youtube/__init__.py137
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