Управление приложением
Полная спецификация управления приложением IMSEL VPN через подписку — по схеме, совместимой с документацией Incy/Happ. Клиент читает HTTP-заголовки ответа подписки и fallback-строки #param в теле и применяет их к настройкам и поведению.
Реализация: приём метаданных — lib/config/header_receive_config.dart; команды управления — lib/config/subscription_commands.dart; применение — Subscription.applyMeta/applyCommands + SubscriptionService; фоновое применение — WorkManager-воркер.
Как отправляет запросы клиент
| Заголовок запроса | Значение |
|---|---|
User-Agent | IMSEL/<версия>/<ОС>/<hwid> — например, IMSEL/1.0.0/Android/17839452147361875676 (может быть подменён командой change-user-agent; до инициализации — fallback v2rayNG/1.8.5 для совместимости с панелями) |
X-HWID | идентификатор устройства — отправляется всегда, отключения нет |
Cookie | тот же идентификатор устройства (HWID), что и в X-HWID — дублирование, как у Happ (cookie: <hwid>) |
x-device-locale | язык устройства (ru, en, …) |
x-device-os | платформа (Android, Windows) |
x-ver-os | версия ОС (14, 10.0.22631, …) |
x-device-model | модель устройства (23117RA68G, имя ПК на Windows; пустая — заголовок не отправляется) |
Accept / Connection | */* / close |
change-user-agent заменяет только User-Agent — заголовки x-device-* и X-HWID продолжают отправляться. При включённом VPN запросы идут через локальный HTTP-прокси ядра; Domain Fronting подписочных URL — см. Подписка через домен-фронтинг.
Способы передачи параметров
1. HTTP-заголовок ответа (приоритетный):
HTTP/1.1 200 OK
profile-title: Мой VPN
profile-update-interval: 6
2. Строка в теле подписки (fallback):
#profile-title: Мой VPN
#profile-update-interval: 6
vless://...
Правила:
- Приоритет: заголовок >
#param-строка. Немаркированные строки тела игнорируются. - Boolean (тринарная семантика):
true/1→ включить; любое другое непустое значение (0,false) → выключить; отсутствие параметра → сервер не управляет, локальное значение не трогается. - Кодирование: текстовые значения поддерживают префикс
base64:(UTF-8). - Имена телесных строк принимаются в вариантах
with-dashиwithoutdash(#profile-title:и#profiletitle:). - Применение — молчаливое, без подтверждения пользователя, при каждом успешном обновлении подписки (в UI и в фоне).
- URL-значения принимаются только со схемой
http:///https://(включая фронтинг-URL); невалидные значения молча отбрасываются.
Данные подписки
Имя подписки
profile-title: Мой VPN
profile-title: base64:0JzQvtC5INCy0YHQsNC5
До 25 символов (обрезается). В base64-варианте первая строка — имя, остальные — описание. Альтернативные заголовки: subscription-name, content-disposition (также распознаётся из заголовка-строки).
Описание подписки
profile-description: Быстрые серверы в Европе
profile-description: base64:0JHRgtC40L/QsNGG0LjRjw==
Показывается мелким текстом в шапке карточки. Имеет приоритет над описанием из base64-profile-title.
Статус (трафик и срок)
subscription-userinfo: upload=1073741824;download=10737418240;total=107374182400;expire=1700000000
Трафик в байтах. expire — unix-время в секундах; значения больше 32 000 000 000 трактуются как миллисекунды и конвертируются. total=0 — безлимит (∞). Прогресс-бар: зелёный → оранжевый (≥70%) → красный (≥90%).
Интервал автообновления
profile-update-interval: 6
Часы. > 0 — включить автообновление с этим интервалом (ограничивается диапазоном 1–168 ч); 0 — выключить; отсутствие — не управлять. Управляет и фоновыми WorkManager-задачами.
Ссылки
support-url: https://t.me/your_support_bot
profile-web-page-url: https://your-site.com
premium-url: https://example.com/pricing
Кнопки в карточке подписки: поддержка (✈), веб-страница (ℹ), «Премиум» (★, только при наличии premium-url).
Объявление
announce: [текст | base64:...]
announce-url: https://example.com/news.txt
Текст объявления (до 200 символов, обрезается с «…») в рамке в карточке. announce (текст напрямую) имеет приоритет над announce-url: клиент скачивает текст по ссылке (тело — plain или base64:..., тот же таймаут и User-Agent, ошибки молча игнорируются). Устаревший sub-info (текст) работает как alias announce.
Сортировка серверов
subscriptions-sort-type: [without | ping | alphabet]
Собственное расширение IMSEL: сортировка серверов внутри подписки.
Зеркала подписки
Первый URL — без метки, каждый следующий — с меткой url:N=, в конце может стоять fallback-url=:
https://gmail.com/sub/token#m1?resolve-address=gmail.com&host=storage.googleapis.com
|url:1=https://www.google.com/sub/token#m2?resolve-address=www.google.com&host=storage.googleapis.com
|url:2=https://fcm.googleapis.com/sub/token#m3?resolve-address=fcm.googleapis.com&host=storage.googleapis.com
|fallback-url=https://backup.example.com/token
- Позиции: первый URL = позиция
0,url:1= второй URL (позиция1),url:N= позицияN. Диапазон меток: 0–99. - Разделители:
|или перевод строки. - Каждый URL — обычный или фронтинг-URL.
- Немаркированный список (просто URL через
|/\n) тоже принимается — зеркала по порядку. - Зеркала пробуются по порядку до первого успешного ответа; во время загрузки top-баннер показывает позицию (
2/4) и секунды до таймаута.
Управление подпиской
Смена URL (new-url)
Три режима работы по значению:
new-url: https://new-first.com/token
Одиночный URL — замена только первого зеркала (позиция 0), остальные не трогаются.
new-url: https://a.com/token|https://b.com/token|https://c.com/token
Несколько plain URL без меток — полная замена списка (легаси-режим).
new-url: https://first.com/t|url:1=https://second.com/t|fallback-url=https://fb.com/t
Помеченная структура — точечно по позициям: упомянутые позиции заменяются, fallback-url= записывается в поле запасного адреса, неупомянутые зеркала не меняются. Все URL проходят валидацию схемы; невалидные части отбрасываются.
Применяется начиная со следующего обновления; фоновая задача WorkManager автоматически привязывается к новому URL.
Точечные операции с зеркалами (new-url-N)
Заголовок new-url-N (N — позиция: 0 — первый URL, 1 — url:1, …) или телесная строка #new-url:N=значение (колон-форма; при нескольких командах используйте каноническую форму с дефисом — одноимённые #new-url: строки перекрывают друг друга):
| Команда | Действие |
|---|---|
new-url-1: https://drive.google.com/tok#m?resolve-address=drive.google.com&host=storage.googleapis.com | заменить зеркало url:1 целиком |
new-url-3: https://append.com/token | N = количество зеркал → добавить в конец |
new-url-1: 0 (или false) | удалить зеркало url:1 |
new-url-1: resolve-address=142.251.41.165 | точечно заменить только resolve-address — домен или IP-адрес |
new-url-0: host=storage.googleapis.com | точечно заменить только host (Host-заголовок фронтинга) |
Правила:
- Патч параметра: существующее значение заменяется, отсутствующее — дописывается в фрагмент URL.
- N > количества зеркал («дыра») игнорируется.
- Операция, после которой список опустел бы полностью, отклоняется целиком.
- Порядок применения в одном ответе:
new-url→ точечные замены/удаления → патчи параметров → домены.
Смена домена (new-domain)
new-domain: new-domain.com
new-domain-1: drive.google.com
new-domain: 1=drive.google.com
new-domain — домен (host) во всех зеркалах; new-domain-N / колон-форма — только у зеркала на позиции N. Путь, query и фрагмент сохраняются.
Синхронизация фронтинга: если у зеркала resolve-address совпадает со старым доменом — заменяется вместе с host (фронт и резолв согласованы). host= не трогается никогда; resolve-address, не равный старому домену (например, IP), — тоже.
Запасные адреса (fallback-url)
fallback-url: https://backup-domain.com/token
fallback-url: https://f1.com/token#m?resolve-address=f1.com&host=storage.googleapis.com|https://f2.com/token
Пробуются по очереди после отказа всех зеркал (HTTP 300–599 или таймаут; таймаут — персональный настройки подписки либо глобальная, 5–15 с). Несколько URL через |/\n; фронтинг-URL разрешены. Встроенный fallback-url= в конце помеченного списка имеет тот же эффект и пробуется первым. Fallback не заменяет список зеркал.
User-Agent (change-user-agent)
change-user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Подменяет User-Agent для всех запросов этой подписки: зеркала, fallback и скачивание announce-url. Применяется начиная со следующего обновления.
Блокировка настроек (hide-settings)
hide-settings: 1
hide-settings: 0
1 — настройки подписки становятся только для чтения (аналог локальной блокировки); 0 — снимает блокировку (в т. ч. выставленную пользователем вручную).
Уведомления о загрузке
Клиент детально сообщает о попытках:
- Всё упало — красное уведомление с полной сводкой:
Ошибка: не удалось загрузить подписку. Попытки: зеркало 1 (gmail.com): HTTP 502; зеркало 2 (www.google.com): таймаут 15 с; запасной URL (backup.com): HTTP 404.Для тотального таймаута добавляется подсказка увеличить таймаут. - Успех через отказоустойчивость — предупреждение:
Подписка обновлена через запасной URL. Недоступны: зеркало 1 (gmail.com): таймаут 15 силиПодписка обновлена (не все зеркала доступны: …). - Чистый успех — без уведомлений (только top-баннер прогресса).
Расширенные объявления
Инфо-блок sub-info
sub-info-color: red
sub-info-text: Продлите подписку со скидкой
sub-info-button-text: Купить
sub-info-button-link: https://example.com/buy
Цветной баннер в карточке. Цвет: red / blue / green (default blue; неизвестные значения → blue). Текст — до 200 символов (обрезается с «…»), кнопка — до 25. Кнопка не показывается без ссылки. Отключение: sub-info-text: 0. Блок скрывается, пока активен баннер истечения (см. ниже).
Баннер истечения sub-expire
sub-expire: 1
sub-expire-button-link: https://example.com/renew
Системный баннер по expireDate из subscription-userinfo: за ≤ 3 дней — «Ваша подписка заканчивается через N д.» (или «…сегодня»), после истечения — «Подписка закончилась!». Кнопка «Продлить» открывает ссылку. Имеет приоритет над sub-info-блоком.
Локальные уведомления об истечении (notification-subs-expire)
notification-subs-expire: 1
Локальное уведомление раз в день в течение 3 дней до истечения: «У вашей подписки [имя] скоро истечёт срок действия (через N д.), не забудьте продлить её».
Настройки подключения (серверные)
Перезаписывают соответствующие настройки карточки подписки; действуют на серверы этой подписки при генерации конфига Xray. Область действия — только эта подписка (см. ниже «Эко-система подписок»).
Фрагментация TLS
fragmentation-enable: 1
fragmentation-packets: tlshello
fragmentation-length: 10-30
fragmentation-interval: 10-30
packets: tlshello / 1 / 1-3 / all; length/interval: диапазон min-max (положительные целые, min ≤ max). Некорректные значения молча отбрасываются (параметр не применяется).
Шумовые пакеты
noises-enable: 1
noises-type: rand
noises-packet: 10-20
noises-delay: 10-50
Тип шума: rand / str / hex / base64. Принимаются оба написания — noises-type (Incy) и noises-packet-type (Happ); значение array трактуется как rand. delay — диапазон min-max мс.
DoH-резолв адреса сервера
server-address-resolve-enable: 1
server-address-resolve-dns-domain: https://common.dot.dns.yandex.net/dns-query
server-address-resolve-dns-ip: 77.88.8.8
Перед подключением домен сервера резолвится через DNS-over-HTTPS (JSON API, A-записи):
- при нескольких A-записях выбирается IP с минимальным временем TCP-подключения (до 5 кандидатов);
- домен в адресе outbound заменяется на IP, оригинальный домен сохраняется в SNI (
sni=/host=; для vmess — поляsni/host); dns-ip— bootstrap: прямое подключение к IP DoH-сервера с подменённым Host, если домен DoH не резолвится;- по умолчанию (без
dns-domain) используетсяhttps://common.dot.dns.yandex.net/dns-query; - результат кэшируется на 5 минут;
- работает для share-ссылок (включая vmess-base64); готовые JSON-конфиги не переписываются;
- ошибка резолва не блокирует подключение — используется исходный домен.
Эко-система подписок: область действия команд
Каждая подписка в IMSEL — изолированный контейнер: свои зеркала URL + fallback, свои настройки подключения (фрагментация/шум/MUX/allowInsecure/DoH), свои метаданные (трафик/срок/объявление/баннеры), своё состояние команд (locked, autoconnect, кастомный User-Agent). Серверы каждой подписки помечены именем группы (subName) и живут в своём списке.
Цепочка применения при подключении
Подключение идёт к конкретному серверу → по его subName находится его подписка → в конфиг Xray идут настройки этой подписки:
сервер (subName: "Провайдер A")
→ findRealSubscription("Провайдер A")
→ fragment/noise/mux/allowInsecure/DoH из карточки «Провайдер A»
→ конфиг Xray этого сервера
Команда fragmentation-*/noises-*/server-address-resolve-* от панели A перезаписывает поля только подписки A — серверы подписок B и C подключаются со своими (локальными или серверными) значениями.
Цепочка эффективных настроек (ЭКО)
При генерации конфига значения выбираются по убыванию специфичности:
- JSON-конфиг подписки (не share-ссылки) — настройки карточки не применяются вовсе (JSON не модифицируется; кнопка «Ещё» заблокирована).
- Сервер без карточки подписки (группы «ручные серверы»/«импорт из буфера» без записи) — берутся глобальные настройки приложения, а не чужой подписки.
- Share-ссылка + подписка — настройки карточки этой подписки (именно их перезаписывают серверные команды) с учётом:
overrideLinkFragment(«перекрывать параметры из ссылки») — форсировать fragment-параметры подписки поверх per-link значений;allowInsecure— объединение флага подписки и флага самого сервера.
- DoH-резолв — срабатывает при подключении к серверу только если у его подписки включён
server-address-resolve-enable.
Что per-subscription, а что глобально
| Область | Параметры |
|---|---|
| Только эта подписка | fragmentation-*, noises-*, server-address-resolve-*, change-user-agent, new-url(-N), new-domain(-N), fallback-url, hide-settings, sub-info-*, sub-expire(-button-link), notification-subs-expire, subscription-autoconnect(-type), subscription-ping-onopen-enabled, subscription-always-hwid-enable, no-limit-enabled, sniffing-enable, subscription-auto-update-enable, subscription-auto-update-open-enable, а также метаданные (title/описание/трафик/срок/ссылки/объявление/интервал) |
| Всё приложение | ping-type, check-url-via-proxy |
Глобальных команд всего две — обе про пинг; UI-настроек для них нет, управляются только заголовками (последняя приславшая панель задаёт значение). Всё остальное, включая sniffing и автообновление, живёт в контейнере подписки: команда панели и кнопки UI («Ещё», кнопка Sniffing на карточке) пишут одни и те же поля. Тринарность (0 для снятия) работает одинаково в обеих областях.
Поведение приложения
Автоподключение
subscription-autoconnect: 1
subscription-autoconnect-type: lastused
Автоматическое подключение при запуске приложения (один раз за холодный старт, если VPN не активен). Критерий выбора сервера: lastused (по умолчанию; ранее выбранный сервер), lowestdelay (после завершения автопинга — минимальная задержка), random.
Автопинг при открытии
subscription-ping-onopen-enabled: 1
Автоматический пинг серверов подписки при открытии приложения. Способ — ping-type.
Способ пинга
ping-type: proxy
check-url-via-proxy: https://cp.cloudflare.com/generate_204
ping-type: proxy (GET через ядро; default) / proxy-head / tcp / icmp (без root недоступен — выполняется как TCP-пинг). check-url-via-proxy — URL проверки для proxy-режимов (по умолчанию — стандартный URL проверки приложения, тот же что в диалоге пинга).
Обе команды применяются глобально и UI-настроек в приложении не имеют — единственный способ управления: заголовок ответа подписки (снятие — ping-type с другим значением; check-url-via-proxy перезаписывает пользовательский URL пинга).
Sniffing (per-subscription)
sniffing-enable: 1
Анализ трафика (sniffing) для серверов этой подписки — перезаписывает значение профиля маршрутизации только для неё (эко-система). В UI есть кнопка на карточке подписки (иконка «подзорная труба», рядом с пингом и меню): цикл «наследовать профиль → выключен → включён»; команда пишет то же поле. Тринарность: 0 — выключить, 1 — включить, отсутствие — не менять.
Автообновление (per-subscription)
subscription-auto-update-enable: 0
subscription-auto-update-open-enable: 1
Управляют тумблерами карточки этой подписки (эко-система):
subscription-auto-update-enable— «Автообновление» (интервальное, 1–168 ч; интервал сохраняется,profile-update-intervalможет задать свой).0— выключить,1— включить;subscription-auto-update-open-enable— «Обновление при открытии»: обновлять эту подписку при каждом открытии приложения.
Оба тумблера доступны пользователю в окне управления подпиской («Ещё»); команда и UI пишут одни и те же поля.
Неотключаемый HWID
subscription-always-hwid-enable: 1
Принимается и сохраняется. В IMSEL X-HWID отправляется всегда (выключателя в приложении нет), поэтому команда фактически закрепляет существующее поведение.
Лимиты памяти ядра
no-limit-enabled: 1
Принимается и сохраняется, но пока не применяется: требуется нативная поддержка memory-limit в Go-ядре (GOMEMLIMIT).
Описание сервера (serverDescription)
Подпись под именем сервера (вместо технического тега «VLESS / WS / TLS»). Добавляется в фрагмент share-ссылки после имени:
vless://uuid@server:443#Сервер1?serverDescription=0J/RgNC40LzQtdGA
Значение — base64 (при невозможности декодирования принимается как plain text). Рекомендуемая длина — до 30 символов. Поддерживается во всех форматах ссылок (vless/vmess/trojan/ss/socks/wireguard/hysteria2) и в JSON-подписках через поле meta.serverDescription.
Приложение: порядок применения
Обработка ответа подписки (UI-обновление и фоновый воркер одинаковы):
- Разбор метаданных (
SubscriptionMeta) и команд (SubscriptionCommands) — заголовки поверх#param. applyMeta— данные (трафик/срок/ссылки/объявление/интервал).applyCommands— порядок для URL: вынос встроенногоfallback-url=→new-url→ точечные замены/удаления → патчиresolve-address/host→new-domain(глобальный) →new-domain-N.- Глобальные команды (ping-type, check-url-via-proxy) — в настройках приложения. Sniffing и автообновление применяются per-subscription вместе с остальными полями подписки.
- Планирование уведомлений об истечении.
- Сохранение; пересинхронизация фоновой задачи при смене URL/интервала.
Защита от регресса: подписка без новых заголовков ведёт себя идентично — все команды тринарны, отсутствующий параметр ничего не меняет.
Сводная таблица параметров
| Параметр | Тип | Валидация/лимит |
|---|---|---|
profile-title | текст / base64: | ≤ 25 симв. (обрезается) |
profile-description | текст / base64: | — |
profile-web-page-url, support-url, premium-url | URL | http/https |
subscription-userinfo | k=v;… | байты; expire сек (или мс > 32e9) |
profile-update-interval | int | часы; clamped 1–168; 0 = выкл |
announce | текст / base64: | ≤ 200 симв. |
announce-url | URL | тело ответа ≤ 200 симв. |
subscriptions-sort-type | enum | without / ping / alphabet |
new-url | URL / список / структура | http/https; метки url:0–99 |
new-url-N | URL / 0 / патч | N = 0–99; патч: resolve-address=<домен|IP>, host=<домен> |
new-domain, new-domain-N | домен | N = 0–99 |
fallback-url | URL / список | http/https |
change-user-agent | текст | — |
hide-settings | tri-bool | true/1, 0/false |
sub-info-color | enum | red / blue / green |
sub-info-text | текст | ≤ 200 симв.; 0 = отключить |
sub-info-button-text | текст | ≤ 25 симв. |
sub-info-button-link, sub-expire-button-link | URL | http/https |
sub-expire | tri-bool | — |
notification-subs-expire | tri-bool | — |
subscription-always-hwid-enable | tri-bool | — |
no-limit-enabled | tri-bool | сохраняется, не применяется |
fragmentation-enable | tri-bool | — |
fragmentation-packets | enum/диапазон | tlshello / 1 / min-max / all |
fragmentation-length, fragmentation-interval | диапазон | min-max, min ≥ 1 |
noises-enable | tri-bool | — |
noises-type / noises-packet-type | enum | rand / str / hex / base64 (array→rand) |
noises-packet | текст/диапазон | — |
noises-delay | диапазон | min-max |
server-address-resolve-enable | tri-bool | — |
server-address-resolve-dns-domain | URL | http/https |
server-address-resolve-dns-ip | IPv4 | 4 октета 0–255 |
subscription-autoconnect | tri-bool | — |
subscription-autoconnect-type | enum | lastused / lowestdelay / random |
subscription-ping-onopen-enabled | tri-bool | — |
ping-type | enum | proxy / proxy-head / tcp / icmp (→tcp) |
check-url-via-proxy | URL | http/https |
sniffing-enable | tri-bool | per-sub; null = наследовать профиль |
subscription-auto-update-enable, subscription-auto-update-open-enable | tri-bool | per-sub (тумблеры карточки) |
Не поддерживается
routing,autorouting,routing-enable— управление маршрутизацией из подписки (профили Happ применяются отдельным импортомimsel://routing/...с подтверждением пользователя);per-app-proxy-enable/-mode/-list,app-auto-start— платформо-зависимые (Android) параметры;socks-auth-*,http-auth-*,tun-type,custom-tunnel-config— desktop-параметры;no-limit-enabled— сохраняется, применение ожидает нативной поддержки ядра;- Provider ID / Premium API (Lite Mode, кастомные темы, баннеры, push) — не реализовано.