diff --git a/anonstream/locale.py b/anonstream/locale.py index 078935a..a4f248e 100644 --- a/anonstream/locale.py +++ b/anonstream/locale.py @@ -2,17 +2,21 @@ from quart import current_app LOCALES = current_app.locales -def get_lang_and_locale_from(context): +def get_lang_and_locale_from(context, burrow=(), validate=True): lang = context.args.get('lang') locale = LOCALES.get(lang) if locale is None: - lang, locale = None, LOCALES[None] + if validate: + lang = None + locale = LOCALES[None] + for key in burrow: + locale = locale[key] return lang, locale -def get_lang_from(context): - lang, locale = get_lang_and_locale_from(context) +def get_lang_from(context, validate=True): + lang, locale = get_lang_and_locale_from(context, validate=validate) return lang -def get_locale_from(context): +def get_locale_from(context, burrow=()): lang, locale = get_lang_and_locale_from(context) return locale diff --git a/anonstream/routes/core.py b/anonstream/routes/core.py index 6a1c36e..e2cbaa3 100644 --- a/anonstream/routes/core.py +++ b/anonstream/routes/core.py @@ -26,7 +26,7 @@ LANG = current_app.lang @current_app.route('/') @with_user_from(request, fallback_to_token=True, ignore_allowedness=True) async def home(timestamp, user_or_token): - lang, locale = get_lang_and_locale_from(request) + lang, locale = get_lang_and_locale_from(request, burrow=('anonstream',)) match user_or_token: case str() | None as token: failure_id = request.args.get('failure', type=int) @@ -35,25 +35,27 @@ async def home(timestamp, user_or_token): 'captcha.html', csp=generate_csp(), token=token, - locale=locale['anonstream']['captcha'], + request_lang=get_lang_from(request, validate=False), + locale=locale['captcha'], digest=get_random_captcha_digest(), - failure=locale['anonstream']['internal'].get(failure), + failure=locale['internal'].get(failure), ) case dict() as user: try: ensure_allowedness(user, timestamp=timestamp) except Blacklisted: - raise Forbidden(locale['anonstream']['error']['blacklisted']) + raise Forbidden(locale['error']['blacklisted']) except SecretClub: # TODO allow changing tripcode - raise Forbidden(locale['anonstream']['error']['not_whitelisted']) + raise Forbidden(locale['error']['not_whitelisted']) else: response = await render_template( 'home.html', csp=generate_csp(), user=user, - lang=lang or LANG, - locale=locale['anonstream']['home'], + lang=lang, + default_lang=LANG, + locale=locale['home'], version=current_app.version, ) return response @@ -99,7 +101,7 @@ async def stream(timestamp, user): @current_app.route('/login') @auth_required async def login(): - return redirect(url_for('home'), 303) + return redirect(url_for('home', lang=get_lang_from(request)), 303) @current_app.route('/captcha.jpg') @with_user_from(request, fallback_to_token=True) @@ -114,7 +116,6 @@ async def captcha(timestamp, user_or_token): @current_app.post('/access') @with_user_from(request, fallback_to_token=True, ignore_allowedness=True) async def access(timestamp, user_or_token): - lang = get_lang_from(request) match user_or_token: case str() | None as token: form = await request.form @@ -130,12 +131,11 @@ async def access(timestamp, user_or_token): case Answer.OK: failure_id = None user = generate_and_add_user(timestamp, token, verified=True) - if failure_id is not None: - url = url_for('home', token=token, lang=lang, failure=failure_id) - raise abort(redirect(url, 303)) case dict() as user: - pass - url = url_for('home', token=user['token']) + token = user['token'] + failure_id = None + lang = get_lang_from(request, validate=failure_id is None) + url = url_for('home', token=token, lang=lang, failure=failure_id) return redirect(url, 303) @current_app.route('/static/') diff --git a/anonstream/routes/error.py b/anonstream/routes/error.py index 02f55a2..6b8093f 100644 --- a/anonstream/routes/error.py +++ b/anonstream/routes/error.py @@ -6,12 +6,11 @@ from anonstream.locale import get_locale_from for error in default_exceptions: async def handle(error): locale = get_locale_from(request)['http'] - error.description = locale.get(error.description) + if localized_name := locale.get(str(error.code)): + error.name = localized_name + if error.description == error.__class__.description: + error.description = None return ( - await render_template( - 'error.html', - error=error, - locale=locale, - ), error.code + await render_template('error.html', error=error), error.code ) current_app.register_error_handler(error, handle) diff --git a/anonstream/routes/nojs.py b/anonstream/routes/nojs.py index ad963fb..a8212f7 100644 --- a/anonstream/routes/nojs.py +++ b/anonstream/routes/nojs.py @@ -5,7 +5,7 @@ from quart import current_app, request, render_template, redirect, url_for, esca from anonstream.captcha import get_random_captcha_digest_for from anonstream.chat import add_chat_message, Rejected -from anonstream.locale import get_locale_from +from anonstream.locale import get_lang_and_locale_from, get_lang_from, get_locale_from from anonstream.stream import is_online, get_stream_title, get_stream_uptime_and_viewership from anonstream.user import add_state, pop_state, try_change_appearance, update_presence, get_users_by_presence, Presence, verify, deverify, BadCaptcha, reading from anonstream.routes.wrappers import with_user_from, render_template_with_etag @@ -64,7 +64,12 @@ async def nojs_chat_messages(timestamp, user): @current_app.route('/chat/messages') @with_user_from(request) async def nojs_chat_messages_redirect(timestamp, user): - url = url_for('nojs_chat_messages', token=user['token'], _anchor='end') + url = url_for( + 'nojs_chat_messages', + token=user['token'], + lang=get_lang_from(request), + _anchor='end', + ) return redirect(url, 303) @current_app.route('/chat/users.html') @@ -86,17 +91,18 @@ async def nojs_chat_users(timestamp, user): @current_app.route('/chat/form.html') @with_user_from(request) async def nojs_chat_form(timestamp, user): + lang, locale = get_lang_and_locale_from(request) state_id = request.args.get('state', type=int) state = pop_state(user, state_id) prefer_chat_form = request.args.get('landing') != 'appearance' - print(state) return await render_template( 'nojs_chat_form.html', csp=generate_csp(), user=user, prefer_chat_form=prefer_chat_form, state=state, - locale=get_locale_from(request)['anonstream'], + lang=lang, + locale=locale['anonstream'], nonce=generate_nonce(), digest=get_random_captcha_digest_for(user), default_name=get_default_name(user), @@ -116,7 +122,12 @@ async def nojs_chat_form_redirect(timestamp, user): ) else: state_id = None - url = url_for('nojs_chat_form', token=user['token'], state=state_id) + url = url_for( + 'nojs_chat_form', + token=user['token'], + lang=get_lang_from(request), + state=state_id, + ) return redirect(url, 303) @current_app.post('/chat/message') @@ -162,6 +173,7 @@ async def nojs_submit_message(timestamp, user): url = url_for( 'nojs_chat_form', token=user['token'], + lang=get_lang_from(request), landing='chat', state=state_id, ) @@ -200,6 +212,7 @@ async def nojs_submit_appearance(timestamp, user): url = url_for( 'nojs_chat_form', token=user['token'], + lang=get_lang_from(request), landing='appearance' if errors else 'chat', state=state_id, ) diff --git a/anonstream/routes/wrappers.py b/anonstream/routes/wrappers.py index fcc1a77..8ed6e55 100644 --- a/anonstream/routes/wrappers.py +++ b/anonstream/routes/wrappers.py @@ -8,11 +8,12 @@ import string from functools import wraps from urllib.parse import quote, unquote -from quart import current_app, request, make_response, render_template, request, url_for, Markup +from quart import current_app, request, make_response, render_template, request, url_for, escape, Markup from werkzeug.exceptions import BadRequest, Unauthorized, Forbidden from werkzeug.security import check_password_hash from anonstream.broadcast import broadcast +from anonstream.locale import get_lang_and_locale_from, get_locale_from from anonstream.user import ensure_allowedness, Blacklisted, SecretClub from anonstream.helpers.user import generate_user from anonstream.utils.user import generate_token, Presence @@ -53,18 +54,11 @@ def auth_required(f): async def wrapper(*args, **kwargs): if check_auth(request): return await f(*args, **kwargs) - hint = ( - 'The broadcaster should log in with the credentials printed in ' - 'their terminal.' - ) + locale = get_locale_from(request)['anonstream']['error'] if request.authorization is None: - description = hint + description = locale['broadcaster_should_log_in'] else: - description = Markup( - f'Wrong username or password. Refresh the page to try again. ' - f'
' - f'{hint}' - ) + description = locale['wrong_username_or_password'] error = Unauthorized(description) response = await current_app.handle_http_exception(error) response = await make_response(response) @@ -107,11 +101,11 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False): # Reject invalid tokens if isinstance(token, str) and not RE_TOKEN.fullmatch(token): - raise BadRequest(Markup( - f'Your token contains disallowed characters or is too ' - f'long. Tokens must match this regular expression:
' - f'{RE_TOKEN.pattern}' - )) + locale = get_locale_from(context) + args = ( + Markup(f'
{RE_TOKEN.pattern}'), + ) + raise BadRequest(escape(locale['invalid_token']) % args) # Only logged in broadcaster may have the broadcaster's token if ( @@ -119,15 +113,16 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False): and isinstance(token, str) and hmac.compare_digest(token, CONFIG['AUTH_TOKEN']) ): - raise Unauthorized(Markup( - f"You are using the broadcaster's token but you are " - f"not logged in. The broadcaster should " - f"" - f"click here" - f" " - f"and log in with the credentials printed in their " - f"terminal when they started anonstream." - )) + lang, locale = get_lang_and_locale_from( + context, burrow=('anonstream', 'error'), + ) + args = ( + Markup(f''''''), + Markup(f''''''), + ) + raise Unauthorized( + escape(locale['impostor']) % args + ) # Create response user = USERS_BY_TOKEN.get(token) @@ -136,19 +131,25 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False): user['last']['seen'] = timestamp user['headers'] = tuple(context.headers) if not ignore_allowedness: - assert_allowedness(timestamp, user) + assert_allowedness(context, timestamp, user) if user is not None and user['verified'] is not None: response = await f(timestamp, user, *args, **kwargs) elif fallback_to_token: #assert not broadcaster response = await f(timestamp, token, *args, **kwargs) else: - raise Forbidden(Markup( - f"You have not solved the access captcha. " - f"" - f"Click here." - f"" - )) + lang, locale = get_lang_and_locale_from( + context, burrow=('anonstream', 'error'), + ) + args = ( + Markup(f''''''), + Markup(f''''''), + ) + if user is None: + string = locale['captcha'] + else: + string = locale['captcha_again'] + raise Forbidden(escape(string) % args) else: if user is not None: user['last']['seen'] = timestamp @@ -161,7 +162,7 @@ def with_user_from(context, fallback_to_token=False, ignore_allowedness=False): headers=tuple(context.headers), ) if not ignore_allowedness: - assert_allowedness(timestamp, user) + assert_allowedness(context, timestamp, user) response = await f(timestamp, user, *args, **kwargs) # Set cookie @@ -229,10 +230,12 @@ def etag_conditional(f): return wrapper -def assert_allowedness(timestamp, user): +def assert_allowedness(context, timestamp, user): try: ensure_allowedness(user, timestamp=timestamp) except Blacklisted as e: - raise Forbidden('You have been blacklisted.') + locale = get_locale_from(context)['anonstream']['error'] + raise Forbidden(locale['blacklisted']) except SecretClub as e: - raise Forbidden('You have not been whitelisted.') + locale = get_locale_from(context)['anonstream']['error'] + raise Forbidden(locale['whitelisted']) diff --git a/anonstream/templates/captcha.html b/anonstream/templates/captcha.html index 780601f..0be0e33 100644 --- a/anonstream/templates/captcha.html +++ b/anonstream/templates/captcha.html @@ -55,7 +55,7 @@ -
+ diff --git a/anonstream/templates/error.html b/anonstream/templates/error.html index 404c99d..57dc177 100644 --- a/anonstream/templates/error.html +++ b/anonstream/templates/error.html @@ -7,7 +7,7 @@ - {{ error.code }} {{ locale[error.code | string] or error.name }} + {{ error.code }} {{ error.name }}