Files
site/WESP_REL/app/kiosk_page_hints.py
T
2026-07-17 12:57:18 +03:00

124 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Подсказки HTML/HTTP для киоск-Chromium: не предлагать перевод страницы."""
from __future__ import annotations
import re
from typing import Tuple
from flask import Response
# Префиксы страниц киоска (localhost / весы / загрузка).
# Киоск-страницы, где курсор мыши остаётся видимым (остальные киоск-страницы — cursor: none).
_KIOSK_CURSOR_VISIBLE_PATHS: Tuple[str, ...] = (
"/scales",
)
_KIOSK_HTML_PATH_PREFIXES: Tuple[str, ...] = (
"/scales",
"/starting",
"/setup",
"/calibration",
"/calibrate",
"/kiosk/",
"/static/startup-loading.html",
)
_HEAD_INJECT = (
'<meta name="google" content="notranslate">\n'
' <meta name="googlebot" content="notranslate">\n'
' <meta http-equiv="Content-Language" content="ru">\n'
)
_HTML_TAG_RE = re.compile(r"<html(\s[^>]*)?>", re.IGNORECASE)
_HAS_NOTRANSLATE_RE = re.compile(
r'<meta[^>]+name=["\']google["\'][^>]+content=["\']notranslate["\']',
re.IGNORECASE,
)
def is_kiosk_html_path(path: str) -> bool:
if not path:
return False
for prefix in _KIOSK_HTML_PATH_PREFIXES:
if path == prefix or path.startswith(prefix):
return True
return False
def is_kiosk_cursor_hidden_path(path: str) -> bool:
"""Скрывать курсор на киоск-страницах, кроме явных исключений (например /scales)."""
if not is_kiosk_html_path(path):
return False
normalized = path.rstrip("/") or "/"
for visible in _KIOSK_CURSOR_VISIBLE_PATHS:
if normalized == visible.rstrip("/") or normalized.startswith(visible.rstrip("/") + "/"):
return False
return True
def kiosk_translate_response_headers() -> dict[str, str]:
return {
"Content-Language": "ru",
"Google-Translate": "no",
}
def inject_no_translate_html(html: str) -> str:
"""Вставить meta notranslate и translate=no на <html> (обман встроенного переводчика)."""
if _HAS_NOTRANSLATE_RE.search(html):
patched = html
elif "<head>" in html.lower():
patched = re.sub(
r"<head>",
"<head>\n " + _HEAD_INJECT.strip().replace("\n", "\n "),
html,
count=1,
flags=re.IGNORECASE,
)
else:
patched = _HEAD_INJECT + html
def _html_repl(match: re.Match[str]) -> str:
attrs = match.group(1) or ""
if re.search(r'\btranslate\s*=', attrs, re.IGNORECASE):
return match.group(0)
if re.search(r'\bclass\s*=', attrs, re.IGNORECASE):
attrs = re.sub(
r'class\s*=\s*["\']([^"\']*)["\']',
r'class="\1 notranslate"',
attrs,
count=1,
flags=re.IGNORECASE,
)
else:
attrs = f'{attrs} class="notranslate"'
if not re.search(r'\blang\s*=', attrs, re.IGNORECASE):
attrs = f'{attrs} lang="ru"'
return f"<html{attrs} translate=\"no\">"
return _HTML_TAG_RE.sub(_html_repl, patched, count=1)
def apply_kiosk_translate_hints(response: Response, path: str) -> Response:
if not is_kiosk_html_path(path):
return response
for key, value in kiosk_translate_response_headers().items():
response.headers[key] = value
content_type = (response.content_type or "").lower()
if "text/html" not in content_type:
return response
# send_from_directory и др. — direct passthrough; get_data() падает с RuntimeError.
if getattr(response, "direct_passthrough", False):
return response
try:
html = response.get_data(as_text=True)
except (TypeError, UnicodeDecodeError, RuntimeError):
return response
if not html or "<html" not in html.lower():
return response
patched = inject_no_translate_html(html)
response.set_data(patched)
if response.content_length is not None:
response.headers.pop("Content-Length", None)
return response