Адаптер канала VK
Через канал vk ваши пользователи входят и подтверждают операции в диалоге с ботом сообщества VK. Ссылка входа открывает диалог с вашим сообществом, бот задаёт вопрос с двумя кнопками, и после ответа вопрос превращается в квитанцию в том же диалоге. Сообщение сообщества видно на vk.com, в приложении VK и в «VK Мессенджере»: у них одна система переписки.
Адаптер поставляется в двух формах, в VK он ведёт себя одинаково:
| Форма | Что это | Когда выбирать |
|---|---|---|
Пакет Veriqa.ChannelAdapter.Ru.Vk
|
Подключается в .NET-процесс, где работает Veriqa, вызовом AddVk()
|
Одно сообщество на установку |
| Контейнерный образ | Общий хост этого репозитория с пакетом VK; Veriqa подключает его как HTTP-канал | Несколько сообществ, по одному на тенант, или секреты VK вне процесса Veriqa |
Как проходит вход
- На странице входа есть кнопка VK и QR-код. Обе ведут на
https://vk.me/<короткое имя>?ref=auth_<транзакция>, а если у сообщества нет короткого имени — наhttps://vk.com/write-<id сообщества>?ref=auth_<транзакция>. - Пользователь открывает диалог и нажимает «Начать» или пишет любое сообщение в уже существующем диалоге. VK передаёт сообщение со значением
ref, и вход начинается. После первого входа под полем ввода остаётся кнопка входа, и в следующий раз пользователю достаточно нажать её: VK прикладываетrefк нажатию так же, как к набранному сообщению. - Бот отправляет вопрос с кнопками «Подтвердить» и «Отклонить». Кнопки несут решение, транзакцию и язык вопроса, а отправителя адаптер берёт из полей события, которые заполняет VK.
- После ответа индикатор на кнопке гаснет, а вопрос правится в квитанцию без кнопок. Если правка невозможна (вопрос слишком старый, удалён или Veriqa не сохранила ссылку на него), квитанция приходит новым сообщением. Если установка показывает квитанции новым сообщением, вопрос сохраняет текст, но теряет кнопки, а квитанция приходит под ним.
Если ref не дошёл, страница входа показывает запасной код. Пользователь отправляет сообществу код одним сообщением, только цифры, и вход продолжается так же. Пробелы и дефисы между цифрами допустимы. Код сработает и в диалоге, открытом другой ссылкой.
Подсказка на странице входа. VK показывает «Начать» только в новом диалоге, а после первого входа — «Продолжить», поэтому страница говорит, что нажать. В compose.yaml текст задан в двух местах: Veriqa:ChannelDisplay:Hints:vk для панели канала и Veriqa:MessageTemplates:hop-channel-hint:BySurface:core-page:ByChannel:vk:Templates для режима hop. В пакете те же ключи задаются в конфигурации приложения.
Подтверждение операции, которое создаёт ваш бэкенд, проходит так же: вопрос уходит пользователю, которого канал уже знает.
Согласие пользователя. Сообщество может писать пользователю, только если он написал сообществу первым или разрешил сообщения от него. Вход по ссылке или по коду даёт такое согласие. Если VK отказывается отправить вопрос, потому что пользователь не разрешил сообщения (ошибка 901), заблокировал сообщество (900) или запретил сообщения в настройках приватности (902), транзакция сразу завершается с этой причиной и не ждёт истечения срока.
Идентичность и claims. sub пользователя — vk:<id пользователя VK>. Профиль адаптер читает методом users.get только в начале входа, и из него получаются name, given_name, family_name, preferred_username и picture. Если профиль прочитать не удалось, вход продолжается, а вместо имени стоит id пользователя. При нажатии «Подтвердить» или «Отклонить» профиль заново не читается: Veriqa берёт тот, что прочитан в начале входа. Язык клиента VK передаёт вместе с сообщением, и по нему выбирается язык вопроса. В claim locale канал передаёт язык клиента со старта входа, а если на старте его не было, — язык вопроса из кнопок, если это один из языков ru, uk, be, en, es, fi, de и it. Почту и номер телефона, подтверждённый платформой, бот сообщества не получает, а RawMetadata адаптер не заполняет, так что персональных данных сверх перечисленных claims канал не передаёт.
После входа
После подтверждённого входа канал может отправить пользователю приветствие, меню или ссылку для продолжения: это действия WelcomeMessage, Menu и Redirect. Их выполняют обе формы адаптера. Какое действие следует за входом, задаёт секция Veriqa:CompletionBehavior в настройках Veriqa.
-
WelcomeMessage— приветствие обычным текстом. -
MenuиRedirect— сообщение с inline-клавиатурой под текстом. УRedirectодна кнопка-ссылка, уMenu— пункты меню. Пункт-ссылка открывает свой адрес, а callback-пункт передаёт своё значение обработчикуIMenuItemPressHandler, зарегистрированному в приложении, где работает Veriqa. Без такого обработчика Veriqa callback-пункты не рисует, а ссылки из того же меню отправляет. - На нажатие callback-пункта адаптер отвечает VK сам, и индикатор на кнопке гаснет.
Лимиты VK адаптер проверяет до отправки. В клавиатуре до 10 кнопок, остальные отбрасываются; до 6 кнопок стоят по одной в ряд, от 7 до 10 — по две. Подпись длиннее 40 символов укорачивается и заканчивается «…». Callback-пункт рисуется, только если его payload помещается в 255 символов. Значению пункта из них остаётся 238 символов, если это латинские буквы, цифры, - и _. Некоторые другие символы занимают больше места: буква вне ASCII — шесть, большинство эмодзи — двенадцать. Поэтому значения callback-пунктов задавайте короткими и латиницей. Сообщение, у которого не осталось ни одной кнопки, не отправляется. Каждый отброшенный пункт и каждая укороченная подпись записываются в лог предупреждением.
Настройка сообщества
В управлении сообществом VK:
- Включите «Сообщения сообщества», а в разделе «Возможности ботов» — возможности ботов и кнопку «Начать».
- Создайте ключ доступа с правом
messages(«Работа с API» → «Ключи доступа»). - Выберите, как сообщество доставляет события:
-
Long Poll API (публичный адрес не нужен): включите его, выберите версию API 5.103 или новее и включите события
message_newиmessage_event; -
Callback API: добавьте адрес сервера, задайте секретный ключ, запишите строку подтверждения, выберите версию API 5.103 или новее и включите события
message_newиmessage_event. Адрес сервера — маршрут адаптера (см. ниже).
-
Long Poll API (публичный адрес не нужен): включите его, выберите версию API 5.103 или новее и включите события
Сам адаптер эти настройки не меняет.
Настройки сообщества в адаптере
| Ключ | Секрет | Обязателен | Что задаёт |
|---|---|---|---|
AccessToken |
да | да | Ключ доступа сообщества с правом messages
|
GroupId |
нет | да | Числовой id сообщества |
ScreenName |
нет | нет | Короткое имя сообщества: ссылка vk.me и имя чата на странице входа (@<короткое имя>) |
ApiVersion |
нет | нет, 5.131
|
Версия VK API в вызовах методов, 5.103 или новее |
ApiBaseUrl |
нет | нет, https://api.vk.ru/method/
|
Адрес VK API; меняйте его, только чтобы направить вызовы на тестовый двойник |
UpdateMode |
нет | нет, LongPoll
|
LongPoll или CallbackApi
|
CallbackConfirmationCode |
нет | при CallbackApi
|
Строка подтверждения из настроек Callback API |
CallbackSecret |
да | при CallbackApi
|
Секретный ключ из настроек Callback API |
SignInButton |
нет | нет, true
|
Прикладывать ли к квитанции и простым ответам бота постоянную клавиатуру с кнопкой входа. Выключите, если в сообществе работает ваш собственный бот: клавиатура у диалога одна, и наша заменит вашу. Подпись кнопки — Continue («Продолжить») на языке, который адаптер последним узнал о пользователе: из сообщения, нажатия кнопки или отправленного ему вопроса. Адаптер хранит язык в памяти, поэтому после перезапуска или суток без событий от пользователя подпись английская, пока он снова не напишет |
Если обязательный ключ не задан или значение неверного формата, хост не стартует. Сообщение называет канал, тенант и ключ, но не значение. AccessToken и CallbackSecret держите в переменных окружения или в хранилище секретов, а не в appsettings.json.
Пакет
dotnet add package Veriqa.ChannelAdapter.Ru.Vk
using Veriqa.ChannelAdapter.Ru.Vk;
authServer.AddChannelAdapters(adapters => adapters.AddVk());
Настройки задаются в секции Veriqa:Channels:vk вместе с двумя ключами, которые есть у любого канала:
Veriqa__Channels__vk__Enabled=true
Veriqa__Channels__vk__InboundVerification=outbound_fetch
Veriqa__Channels__vk__AccessToken=<ключ доступа>
Veriqa__Channels__vk__GroupId=<id сообщества>
Veriqa__Channels__vk__ScreenName=<короткое имя>
-
Enabledпо умолчаниюfalse. Пока онfalse, цикл Long Poll не запускается, маршрут Callback API отвечает 404, а настройки сообщества не проверяются. -
InboundVerificationзависит от режима событий:
UpdateMode |
InboundVerification |
Почему |
|---|---|---|
CallbackApi |
shared_secret |
Поле secret каждого события сравнивается с CallbackSecret за постоянное время |
LongPoll |
outbound_fetch |
Входящих запросов нет: события адаптер забирает сам |
В режиме CallbackApi укажите в VK адрес сервера https://<адрес вашей Veriqa>/api/channels/vk/webhook. Маршрут отвечает на проверку адреса строкой подтверждения, а на каждое подлинное событие — ok, как ждёт VK.
Канал отвечает в диалоге, когда ссылка входа устарела и когда код не подходит ни к одному входу. На сообщение, которое не относится ко входу, канал не отвечает: в том же сообществе может работать ваш собственный бот, и такие сообщения — для него.
Контейнерный образ
В образе работает общий хост с пакетом VK. Veriqa подключает его как HTTP-канал с именем vk, а секреты VK хранит хост. Хост слушает два порта: на один приходят вызовы Veriqa, на другой — трафик VK (Callback API). Наружу публикуйте только порт платформы, порт вызовов оставьте в частной сети.
Настройки хоста
| Ключ | Обязателен | По умолчанию | Что задаёт |
|---|---|---|---|
ChannelHost:VeriqaBaseUrl |
да | — | Абсолютный адрес Veriqa, куда хост отправляет события |
ChannelHost:Channels:vk:TransportSecret |
да | — | Секрет транспорта, равный Http:TransportSecret регистрации в Veriqa |
ChannelHost:Channels:vk:PreviousTransportSecret |
нет | — | Предыдущий секрет на время ротации |
ChannelHost:CallsPort / ChannelHost:PlatformPort
|
нет |
8080 / 8081
|
Порт вызовов Veriqa / порт трафика VK |
ChannelHost:IdempotencyRetention |
нет | 01:00:00 |
Сколько хранится ответ на вызов для повторного вызова |
ChannelHost:TransactionRetention |
нет | 01:00:00 |
Сколько хост помнит, какому тенанту принадлежит транзакция |
ChannelHost:EventDeliveryBudget |
нет | 00:00:10 |
Время на повторы при отправке события в Veriqa |
ChannelHost:PlatformRateLimit:PermitLimit / :Window
|
нет |
100 / 00:00:01
|
Лимит запросов к маршрутам платформы |
Channels:vk:* |
да | — | Настройки сообщества тенанта по умолчанию (таблица выше) |
Tenants:{tenant}:Channels:vk:* |
нет | — | Настройки сообщества тенанта; заменяют секцию по умолчанию целиком |
Ключа Enabled для VK у хоста нет: канал работает, пока работает хост. Чтобы выключить канал, задайте Veriqa__Channels__vk__Enabled=false на стороне Veriqa или остановите контейнер.
Тенант без своей секции работает с сообществом по умолчанию. В режиме CallbackApi адрес сервера в VK — https://<публичный адрес порта платформы>/api/channels/vk/webhook для секции по умолчанию и …/api/channels/vk/webhook/<тенант> для тенанта со своей секцией.
Настройки Veriqa
Veriqa__Channels__vk__Enabled=true
Veriqa__Channels__vk__InboundVerification=outbound_fetch
Veriqa__Channels__vk__Http__BaseUrl=http://vk-adapter:8080/
Veriqa__Channels__vk__Http__AllowInsecureHttp=true
Veriqa__Channels__vk__Http__TransportSecret=whsec_<base64 от 24–64 случайных байт>
Veriqa__Channels__vk__Http__DisplayName=VK
Veriqa__Channels__vk__Http__Capabilities__SupportsInChannelConfirmation=true
Veriqa__Channels__vk__Http__Capabilities__SupportedMessageKinds__0=plain_text
Veriqa__Channels__vk__Http__Capabilities__SupportedMessageKinds__1=claim_form_link
Veriqa__Channels__vk__Http__Capabilities__SupportedMessageKinds__2=action_buttons
Veriqa__Channels__vk__Http__Capabilities__SupportsMessageUpdate=true
Veriqa__Channels__vk__Http__Capabilities__DeliversOutcomeNotice=true
Veriqa__Channels__vk__Http__Capabilities__ProvidesRecipientLocale=true
Veriqa__Channels__vk__Http__Capabilities__RendersEmoji=true
Veriqa__Channels__vk__Http__Capabilities__SupportedCompletionBehaviorActions__0=WelcomeMessage
Veriqa__Channels__vk__Http__Capabilities__SupportedCompletionBehaviorActions__1=Menu
Veriqa__Channels__vk__Http__Capabilities__SupportedCompletionBehaviorActions__2=Redirect
Veriqa__Channels__vk__Http__Capabilities__CodeStart__LinkCarriesTransaction=true
Veriqa__Channels__vk__Http__StaleLinkReplyText="This sign-in link has expired or is no longer valid. Return to the application and start the sign-in again."
Veriqa__Channels__vk__Http__UnmatchedCodeReplyText="This code did not work. Check the code shown on the sign-in page and send it again, or open the sign-in page again to get a new one."
Veriqa__Channels__vk__Http__UnaddressedReplyText="To keep sign-ins secure, this chat answers only a sign-in started from the sign-in page. Send the code shown on that page here, or scan its QR code again."
-
InboundVerificationзависит отUpdateModeхоста, как и в пакете:shared_secretдляCallbackApi,outbound_fetchдляLongPoll. -
SupportedMessageKindsиSupportedCompletionBehaviorActionsповторяют то, что пакет объявляет в коде: кнопку формы добора claims, сообщения с кнопками и три действия после входа.NoneVeriqa добавляет в действия сама. ЕслиMenuилиRedirectобъявлены безaction_buttons, Veriqa не стартует. - Все три текста ответа — Natural Keys ядра, поэтому Veriqa переводит их на язык пользователя.
UnaddressedReplyTextотвечает только на нажатие кнопки входа без ссылки. Остальные сообщения, не относящиеся ко входу, остаются без ответа: на них отвечает ваш собственный бот. - Подпись кнопки входа хост переводит по locale-файлам образа
locales/<язык>.json. В образе есть файл для русского языка, на остальных языках подпись остаётся английской. -
Http:BaseUrlуказывает на порт вызовов хоста. Адрес должен бытьhttps, если не заданHttp:AllowInsecureHttp=true; этот ключ рассчитан на частную сеть, например сеть Compose, как в примере. -
Http:Tenantне задавайте: запасной код работает только у регистрации без тенанта-владельца.
Ограничения
- Пакет обслуживает одно сообщество — только тенант по умолчанию. Чтобы у каждого тенанта было своё сообщество, используйте контейнерный образ.
-
Контейнерный образ не передаёт имя сообщества. Имя чата на странице входа не показывается, а подпись кнопки канала задаёт
Http:DisplayName. - Запасной код в контейнерном образе работает только у регистрации без тенанта-владельца.
-
Номер телефона канал не запрашивает. У бота сообщества нет кнопки «поделиться контактом». Поле
mobile_phoneметодаusers.getVK отдаёт только Standalone-приложениям.home_phone— необязательное поле профиля: пользователь заполняет его сам и может скрыть, а платформа этот номер не подтверждает. Если приложению нужен телефон, Veriqa запрашивает его на шагах добора claims, без канала. - Ключ сообщества даёт до 20 запросов в секунду. Если VK требует капчу или сообщает о слишком частых запросах, отправка завершается ошибкой, и ядро повторяет её. Капчу сервер решить не может, поэтому нагрузку на сообщество планируйте заранее.
Находки живого прогона
Адаптер проверен на тестовом сообществе VK в режимах Long Poll и Callback API, в приложении VK на телефоне и на vk.com на компьютере.
-
refв существующем диалоге доходит целиком, но только вместе со следующим сообщением пользователя. Кнопку «Начать» VK показывает лишь в новом диалоге. Поэтому после первого входа в диалоге остаётся кнопка «Продолжить»: VK прикладываетrefк её нажатию, и повторный вход не требует ничего писать. -
client_info.lang_idнумеруется так же, как параметрlangзапросов VK API. Клиент на английском присылает3, и вопрос задаётся по-английски. Мобильное приложение VK применяет смену языка целиком только после перезапуска. -
Нажатие callback-кнопки приходит событием
message_eventиз приложения VK и с vk.com на компьютере. Клиенты m.vk.com и «VK Мессенджер» не проверялись.
Что учесть при настройке:
-
Диалог уже открыт. Если открыть ссылку входа при открытом диалоге с ботом, кнопки «Продолжить» может не быть. Адаптер на это не влияет. Любое сообщение пользователя придёт с
refи начнёт вход. - Кнопка на vk.com. vk.com может сворачивать клавиатуру: кнопка «Продолжить» тогда открывается значком рядом с кнопкой смайлов в поле сообщения. Вход начинается и с любого сообщения пользователя.
-
Callback API без типов событий. VK подтверждает адрес сервера и показывает его рабочим, даже если не включено ни одного типа событий. События тогда не приходят, и вход истекает. Проверьте, что включены
message_newиmessage_event. -
httpsс недоверенным сертификатом вне Compose. Если хост отправляет события в Veriqa поhttpsс сертификатом, которому процесс хоста не доверяет (например, dev-сертификатlocalhost), доставка завершается ошибкойUntrustedRoot. После четырёх попыток событие теряется, а страница входа ждёт до истечения срока. Укажите адрес Veriqa с доверенным сертификатом илиhttpв частной сети, как вcompose.yaml.