Some checks failed
Windows project source guards / test (push) Has been cancelled
Пользователь вписал прокси 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>
418 lines
27 KiB
Python
418 lines
27 KiB
Python
"""Шаги обучающей экскурсии: что подсветить и что рассказать.
|
||
|
||
Шаг не хранит виджет — только то, как его найти (``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"),
|
||
),
|
||
)
|