zapret-kvn/xray_fluent/ui/tour/steps.py
loop-uh bfb8214a60
Some checks failed
Windows project source guards / test (push) Has been cancelled
Режим TUN держит локальный прокси на 127.0.0.1:1390 — Telegram с прокси не обрывается
Пользователь вписал прокси ZapretKVN в Telegram, переключился в «VPN (TUN)»,
и Telegram перестал подключаться вовсе (16.10.2026): TUN-план не открывал
SOCKS/HTTP, а сессия при этом записывала порты по умолчанию, на которых никто
не слушал. Решение владельца — прокси в обоих режимах.

plan_singbox_runtime добавляет тот же mixed/http, что режим «Прокси», через
_ensure_singbox_proxy_runtime_contract, но с listen=127.0.0.1: раздачу прокси в
сеть включают режимом «Прокси», в TUN её никто не просил. Порты своей сессии
передаются как разрешённые — переподключение их сохраняет. Панель показывает
адрес и в TUN (подсказка: только для программ этого компьютера), кнопка
«Добавить в Telegram» — только в режиме «Прокси». Тест «TUN не объявляет
порты» закреплял прежний контракт и заменён на обратный.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 13:00:07 +03:00

418 lines
27 KiB
Python
Raw Permalink 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.

"""Шаги обучающей экскурсии: что подсветить и что рассказать.
Шаг не хранит виджет — только то, как его найти (``target``). Страницы
создаются лениво (``DeferredPage``), поэтому искать элемент можно лишь после
того, как страница шага открыта; оверлей вызывает ``target`` заново на каждом
кадре и сам следит за прокруткой и размером окна.
Тексты описывают только то, что приложение действительно делает. Меняете
поведение (режимы, подписки, проверки серверов) — поправьте и шаг.
"""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from PyQt6.QtWidgets import QWidget
TourTargets = Callable[[QWidget], list[QWidget]]
@dataclass(frozen=True, slots=True)
class TourStep:
key: str
title: str
body: str
#: Атрибут главного окна со страницей, которую нужно открыть (``nodes_page``).
#: ``None`` — остаться на текущей.
page: str | None = None
#: Раздел «Маршрутизации» (ключ из ``configs_page.SECTIONS``).
section: str | None = None
target: TourTargets | None = None
#: Крупная карточка по центру без подсветки — для вступления.
hero: bool = False
def nav_item(*routes: str) -> TourTargets:
"""Пункты бокового меню по ключу маршрута (``objectName`` страницы)."""
def resolve(window: QWidget) -> list[QWidget]:
panel = window.navigationInterface.panel
found: list[QWidget] = []
for route in routes:
item = panel.items.get(route)
if item is None:
continue
widget = item.widget
# У пункта с подпунктами («Маршрутизация») строка — itemWidget.
found.append(getattr(widget, "itemWidget", widget))
return found
return resolve
def on_page(page: str, *paths: str, first_visible: bool = False) -> TourTargets:
"""Элементы страницы по пути из атрибутов: ``"overview.reset_btn"``.
Звено пути может быть и ключом словаря (``"kind_rows.tcp"``).
``first_visible`` — подсветить только первый видимый из путей: следующие
пути — запасная подсветка, пока главный элемент скрыт (строка адреса
прокси видна только при подключении в режиме «Прокси»).
"""
def resolve(window: QWidget) -> list[QWidget]:
host = getattr(window, page)
ensure = getattr(host, "ensure_page", None)
root = ensure() if ensure is not None else host
found: list[QWidget] = []
for path in paths:
current = root
for part in path.split("."):
current = current.get(part) if isinstance(current, dict) else getattr(current, part, None)
if current is None:
break
if isinstance(current, QWidget):
if first_visible:
if current.isVisible():
return [current]
continue
found.append(current)
return found
return resolve
TOUR_STEPS: tuple[TourStep, ...] = (
TourStep(
"welcome",
"Добро пожаловать в Zapret KVN",
"Zapret KVN открывает сайты и программы, которые у вас блокируются или "
"замедляются. Для этого в нём два инструмента: подключение через сервер "
"(VPN или прокси) и Zapret — обход без сервера.\n\n"
"За несколько минут покажем, где что находится и с чего начать. Листайте "
"кнопкой «Далее» или стрелками на клавиатуре, закрыть экскурсию можно "
"клавишей Esc.",
hero=True,
),
TourStep(
"how_it_works",
"Как это работает",
"Приложение подключается к серверу в другой стране и отправляет трафик "
"через него. Блокировки провайдера на такой трафик не действуют, а сайты "
"видят адрес сервера, а не ваш.\n\n"
"Самих серверов в приложении нет — их выдаёт ваш VPN-провайдер: "
"ссылкой-подпиской или ссылкой на отдельный сервер. Поэтому порядок такой:\n\n"
"1. Добавить серверы.\n"
"2. Выбрать режим — «Прокси» или «VPN (TUN)».\n"
"3. Нажать кнопку подключения.",
hero=True,
),
TourStep(
"subscription_what",
"Подписка — список серверов по ссылке",
"Подписка — это ссылка от вашего VPN-провайдера. По ней приложение "
"скачивает список серверов и потом само его обновляет: провайдер добавил "
"или заменил сервер — у вас он появится без ручной настройки.\n\n"
"В ссылке зашит ваш личный ключ, поэтому относитесь к ней как к паролю и "
"не пересылайте посторонним. Приложение её тоже прячет: в окне и в логах "
"она скрыта.",
page="subscriptions_page",
target=nav_item("subscriptions"),
),
TourStep(
"subscription_add",
"Как добавить подписку",
"Нажмите «+», вставьте ссылку в поле «URL подписки» и нажмите «Проверить "
"и добавить». Вместо ссылки можно считать QR-код: из файла, из буфера "
"обмена или прямо с экрана.\n\n"
"Приложение сразу скачает список. Если ссылка не сработала, подписка не "
"сохранится, а в сообщении будет причина. Некоторые провайдеры отвечают "
"только определённым программам — тогда в этой же форме поменяйте "
"«Профиль клиента».",
page="subscriptions_page",
target=on_page("subscriptions_page", "add_btn"),
),
TourStep(
"subscription_update",
"Что происходит при обновлении",
"В таблице видно, сколько в подписке серверов, сколько трафика "
"израсходовано и когда она истекает — если провайдер это сообщает.\n\n"
"При обновлении список заменяется целиком: серверы, которые провайдер "
"убрал, исчезнут и у вас. Ваши названия, группы и избранное сохраняются. "
"Обновление идёт само — так часто, как задал провайдер, а если он не "
"задал, раз в сутки. Переключатель «Авто» отключает его для отдельной "
"подписки.\n\n"
"Сервер подписки при запросе видит ваш IP-адрес и название программы. "
"Сведения об устройстве (HWID) отправляются, только если вы сами "
"включили это в настройках подписки.",
page="subscriptions_page",
target=on_page("subscriptions_page", "table"),
),
TourStep(
"nodes_import",
"Отдельный сервер по ссылке",
"Если у вас не подписка, а ссылка на один сервер (vless://, trojan://, "
"ss://, hy2:// и другие), скопируйте её и нажмите эту кнопку или Ctrl+V. "
"Так же добавляется WireGuard или AmneziaWG: скопируйте текст файла "
".conf целиком.\n\n"
"Учтите: после импорта приложение само выберет новый сервер и начнёт "
"подключаться.",
page="nodes_page",
target=on_page("nodes_page", "import_btn"),
),
TourStep(
"nodes_list",
"Список серверов",
"Здесь все ваши серверы. Одиночный щелчок только выделяет строку. Чтобы "
"подключиться к серверу, щёлкните по нему дважды или нажмите Enter. "
"Сервер, который используется сейчас, подсвечен.\n\n"
"Если подключение уже работает, приложение само перейдёт на новый сервер.\n\n"
"Адреса серверов скрыты. Чтобы их увидеть, удерживайте кнопку с глазом "
"на панели сверху.",
page="nodes_page",
target=on_page("nodes_page", "table"),
),
TourStep(
"nodes_checks",
"Пинг и тест скорости",
"Пинг показывает, за сколько миллисекунд сервер отвечает на соединение. "
"Меньше — лучше, красный цвет значит «недоступен». У серверов WireGuard "
"и AmneziaWG пинг не измеряется — это нормально.\n\n"
"Пинг не проверяет, что через сервер действительно открываются сайты. "
"Для этого есть тест скорости: он скачивает файл через выбранный сервер "
"и показывает скорость в мегабайтах в секунду. Текущее подключение он "
"не трогает.\n\n"
"Обе кнопки работают с выделенными серверами. «Пинг всех» и «Тест "
"скорости всех» — в меню «Ещё действия» справа.",
page="nodes_page",
target=on_page("nodes_page", "ping_btn", "speed_test_btn"),
),
TourStep(
"connect",
"Кнопка подключения",
"Это главный выключатель. Нажмите на него, чтобы подключиться, и ещё "
"раз, чтобы отключиться. Рядом написано состояние: «Не подключено», "
"«Подключение…», «Защищено» или «Подключено».\n\n"
"Если подключиться не удалось, здесь появится причина и кнопка "
"«Открыть логи».",
page="dashboard_page",
target=on_page("dashboard_page", "connection_orb"),
),
TourStep(
"current_server",
"Текущий сервер",
"Под кнопкой — сервер, через который пойдёт трафик, и его пинг. "
"«Следующий» переключает на следующий сервер из списка, «Все серверы» "
"открывает полный список. В узком окне от этих кнопок остаются только "
"значки.\n\n"
"Пока сервер не выбран, подключаться не к чему — сначала добавьте "
"подписку или сервер.",
page="dashboard_page",
target=on_page("dashboard_page", "server_row"),
),
TourStep(
"modes",
"Два режима: «Прокси» и «VPN (TUN)»",
"Режим определяет, какие программы на компьютере пойдут через сервер. "
"Сам сервер и правила в обоих режимах одни и те же — различается только "
"то, как трафик попадает в приложение.\n\n"
"Дальше — про каждый режим отдельно.",
page="dashboard_page",
target=on_page("dashboard_page", "mode_card"),
),
TourStep(
"mode_proxy",
"Режим «Прокси»",
"Приложение открывает на компьютере локальный прокси, и через сервер "
"идут только те программы, которые им пользуются. Пока включён "
"переключатель «Системный прокси Windows», браузеры и большинство "
"обычных программ подхватывают его сами.\n\n"
"Программы, которые системный прокси не замечают, — многие игры, часть "
"мессенджеров, — в этом режиме идут напрямую, мимо сервера. Некоторым из "
"них прокси можно указать вручную — об этом следующий шаг.\n\n"
"Тем же прокси могут пользоваться другие устройства вашей сети: укажите "
"в них адрес этого компьютера и те же порты. Пароля у прокси нет, "
"поэтому в чужой сети — в кафе или гостинице — лучше выбрать «VPN (TUN)».",
page="dashboard_page",
target=on_page("dashboard_page", "proxy_tile", "proxy_switch"),
),
TourStep(
"proxy_address",
"Адрес прокси и Telegram",
"После подключения здесь появляется адрес локального прокси: «SOCKS5: "
"127.0.0.1:1390 · HTTP: 127.0.0.1:1391». Если порт занят, строка "
"покажет другой. Прокси работает в обоих режимах.\n\n"
"Программы, которые системный прокси не подхватили, идут мимо сервера. "
"Telegram на компьютере подключается кнопкой «Добавить в Telegram»: он "
"сам спросит, подключить ли прокси. Без этого сообщения приходят, а "
"картинки грузятся медленно. Другим программам адрес вписывают вручную.\n\n"
"Для Telegram удобнее наш ZaStoGram: он обходит блокировку сам, без VPN. "
"В режиме «VPN (TUN)» Telegram идёт через сервер сам.",
page="dashboard_page",
target=on_page(
"dashboard_page", "proxy_address_row", "proxy_options", "mode_card", first_visible=True
),
),
TourStep(
"mode_vpn",
"Режим «VPN (TUN)»",
"Приложение создаёт виртуальный сетевой адаптер, и через него идёт весь "
"трафик компьютера: любые программы, игры, голосовые звонки. Настраивать "
"прокси нигде не нужно.\n\n"
"Режиму нужны права администратора — приложение запрашивает их при "
"запуске.",
page="dashboard_page",
target=on_page("dashboard_page", "vpn_tile"),
),
TourStep(
"mode_choice",
"Какой режим выбрать",
"• «Прокси» — если нужны в основном браузер и обычные программы, а "
"остальное трогать не хочется.\n"
"• «VPN (TUN)» — если нужная программа или игра в режиме «Прокси» не "
"заработала или через сервер должно идти всё.\n\n"
"Режим можно менять в любой момент, в том числе при работающем "
"подключении: приложение переподключится само, это занимает около "
"десяти секунд.",
page="dashboard_page",
target=on_page("dashboard_page", "mode_card"),
),
TourStep(
"traffic",
"Скорость и трафик",
"Здесь текущая скорость загрузки и отдачи, пинг до сервера и объём "
"трафика, который прошёл через сервер с момента подключения. Нажмите на "
"график, чтобы открыть его крупно.\n\n"
"Ниже — какие программы сейчас пользуются сетью и по каким правилам "
"распределяется трафик.",
page="dashboard_page",
target=on_page("dashboard_page", "traffic_card"),
),
TourStep(
"routing",
"Маршрутизация: что идёт через сервер",
"Не весь трафик стоит отправлять через сервер. Готовые правила уже "
"настроены так: российские сайты, банки и госуслуги открываются "
"напрямую, заблокированные и все остальные — через сервер, устройства "
"домашней сети — напрямую.\n\n"
"Если всё открывается как надо, в этом разделе ничего менять не нужно.",
page="configs_page",
section="overview",
target=nav_item("configs"),
),
TourStep(
"routing_rule",
"Как добавить исключение",
"Сайт или программа идут не так, как вам нужно? Нажмите «Добавить "
"правило» и выберите «Сайты, IP, программы (просто)». Укажите, куда "
"отправлять — «Через VPN», «Напрямую» или «Блокировать», — и впишите "
"сайты или имена программ, по одному в строке.\n\n"
"Новое правило встанет выше готовых и сработает первым.",
page="configs_page",
section="rules",
target=on_page("configs_page", "_sections.rules.rules.add_button"),
),
TourStep(
"routing_check",
"Проверить, куда пойдёт сайт",
"Впишите адрес сайта, и приложение покажет, каким путём он пойдёт и "
"какое правило это решило. Удобно, когда непонятно, почему сайт "
"открывается не так.",
page="configs_page",
section="rules",
target=on_page("configs_page", "_sections.rules.check_row"),
),
TourStep(
"routing_apply",
"Не забудьте «Применить»",
"Изменения в этом разделе начинают действовать после кнопки "
"«Применить»: она сохраняет настройки и переподключает VPN, если он "
"включён. «Отменить изменения» возвращает последнее сохранённое "
"состояние.\n\n"
"Если что-то сломалось, на странице «Обзор» есть «Сбросить всё к "
"стоку» — возврат к заводским правилам. Подробная справка по каждому "
"разделу открывается ссылкой справа от его заголовка.",
page="configs_page",
section="rules",
target=on_page("configs_page", "apply_btn", "revert_btn"),
),
TourStep(
"zapret_what",
"Zapret: обход без сервера",
"Zapret работает иначе, чем VPN. Он никуда не перенаправляет трафик, а "
"слегка меняет первые пакеты соединения прямо на вашем компьютере — "
"так, что фильтр провайдера перестаёт узнавать сайт. Сервер для этого "
"не нужен.\n\n"
"Так обычно возвращают замедленные YouTube и Discord. Сайты, которые "
"сами закрыты для вашей страны, так не открыть — для них нужен сервер.",
page="zapret_page",
target=nav_item("zapret"),
),
TourStep(
"zapret_start",
"Запуск и пресеты",
"«Запустить» включает обход по выбранному пресету. Пресет — готовый "
"набор правил: какие сайты обрабатывать и каким способом. Zapret "
"работает независимо от подключения к серверу.\n\n"
"У разных провайдеров фильтры разные, а автоматического подбора нет. "
"Если с текущим пресетом не заработало, нажмите на его название, "
"откройте список и попробуйте другой.",
page="zapret_page",
target=on_page("zapret_page", "toggle_btn", "preset_link"),
),
TourStep(
"zapret_server",
"Обход для самого VPN-сервера",
"Бывает, что провайдер мешает подключиться к самому серверу. На этот "
"случай Zapret может прикрывать соединение с выбранным сервером: он "
"включается сам при подключении, отдельно запускать ничего не нужно.\n\n"
"Для обычных серверов (VLESS, Trojan и подобных) это уже включено. Если "
"сервер не подключается, нажмите на строку и попробуйте другую "
"стратегию — начните с помеченных «Рекомендуется» и «Стабильная».",
page="zapret_page",
target=on_page("zapret_page", "kind_rows.tcp", "kind_rows.quic", "kind_rows.wireguard"),
),
TourStep(
"settings",
"Запуск вместе с Windows",
"«Запускать вместе с Windows» стартует приложение при входе в систему и "
"сразу прячет его в трей — к значку возле часов. «Подключаться при "
"запуске» включает подключение к серверу, выбранному в прошлый раз.\n\n"
"Кнопка закрытия окна тоже не завершает приложение, а сворачивает его в "
"трей. Выйти совсем можно из меню значка в трее.\n\n"
"В настройках также: тема и цвет оформления, автоматическая смена "
"сервера, если он перестал отвечать, обновление подписок и "
"мастер-пароль, который блокирует приложение от посторонних.",
page="settings_page",
target=on_page("settings_page", "launch_card", "auto_connect_card"),
),
TourStep(
"diagnostics",
"Если что-то не работает",
"В разделе «Логи» видно, что приложение делало и на чём споткнулось. "
"Кнопка «Экспорт диагностики» собирает ZIP-архив для поддержки: пароли, "
"ссылки и адреса серверов в нём заменены заглушками.",
page="logs_page",
target=on_page("logs_page", "export_btn"),
),
TourStep(
"updates",
"Обновления",
"Приложение само проверяет обновления и по умолчанию само их "
"устанавливает: перезапускается и возвращает подключение. Ваши серверы, "
"подписки и настройки при этом сохраняются. Автоматическую установку "
"можно отключить в настройках.",
page="updates_page",
target=nav_item("updates"),
),
TourStep(
"finish",
"Готово!",
"Теперь можно начинать: добавьте подписку или сервер, выберите режим и "
"нажмите кнопку подключения на странице «Панель».\n\n"
"Экскурсию можно пройти ещё раз в любой момент — пунктом «Обучение» "
"внизу бокового меню.",
target=nav_item("tour"),
),
)