Адаптер канала VK

Через канал vk ваши пользователи входят и подтверждают операции в диалоге с ботом сообщества VK. Ссылка входа открывает диалог с вашим сообществом, бот задаёт вопрос с двумя кнопками, и после ответа вопрос превращается в квитанцию в том же диалоге. Сообщение сообщества видно на vk.com, в приложении VK и в «VK Мессенджере»: у них одна система переписки.

Адаптер поставляется в двух формах, в VK он ведёт себя одинаково:

Форма Что это Когда выбирать
Пакет Veriqa.ChannelAdapter.Ru.Vk Подключается в .NET-процесс, где работает Veriqa, вызовом AddVk() Одно сообщество на установку
Контейнерный образ Общий хост этого репозитория с пакетом VK; Veriqa подключает его как HTTP-канал Несколько сообществ, по одному на тенант, или секреты VK вне процесса Veriqa

Как проходит вход

  1. На странице входа есть кнопка VK и QR-код. Обе ведут на https://vk.me/<короткое имя>?ref=auth_<транзакция>, а если у сообщества нет короткого имени — на https://vk.com/write-<id сообщества>?ref=auth_<транзакция>.
  2. Пользователь открывает диалог и нажимает «Начать» или пишет любое сообщение в уже существующем диалоге. VK передаёт сообщение со значением ref, и вход начинается. После первого входа под полем ввода остаётся кнопка входа, и в следующий раз пользователю достаточно нажать её: VK прикладывает ref к нажатию так же, как к набранному сообщению.
  3. Бот отправляет вопрос с кнопками «Подтвердить» и «Отклонить». Кнопки несут решение, транзакцию и язык вопроса, а отправителя адаптер берёт из полей события, которые заполняет VK.
  4. После ответа индикатор на кнопке гаснет, а вопрос правится в квитанцию без кнопок. Если правка невозможна (вопрос слишком старый, удалён или 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:

  1. Включите «Сообщения сообщества», а в разделе «Возможности ботов» — возможности ботов и кнопку «Начать».
  2. Создайте ключ доступа с правом messages («Работа с API» → «Ключи доступа»).
  3. Выберите, как сообщество доставляет события:
    • Long Poll API (публичный адрес не нужен): включите его, выберите версию API 5.103 или новее и включите события message_new и message_event;
    • Callback API: добавьте адрес сервера, задайте секретный ключ, запишите строку подтверждения, выберите версию API 5.103 или новее и включите события message_new и message_event. Адрес сервера — маршрут адаптера (см. ниже).

Сам адаптер эти настройки не меняет.

Настройки сообщества в адаптере

Ключ Секрет Обязателен Что задаёт
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, сообщения с кнопками и три действия после входа. None Veriqa добавляет в действия сама. Если 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.get VK отдаёт только Standalone-приложениям. home_phone — необязательное поле профиля: пользователь заполняет его сам и может скрыть, а платформа этот номер не подтверждает. Если приложению нужен телефон, Veriqa запрашивает его на шагах добора claims, без канала.
  • Ключ сообщества даёт до 20 запросов в секунду. Если VK требует капчу или сообщает о слишком частых запросах, отправка завершается ошибкой, и ядро повторяет её. Капчу сервер решить не может, поэтому нагрузку на сообщество планируйте заранее.

Находки живого прогона

Адаптер проверен на тестовом сообществе VK в режимах Long Poll и Callback API, в приложении VK на телефоне и на vk.com на компьютере.

  1. ref в существующем диалоге доходит целиком, но только вместе со следующим сообщением пользователя. Кнопку «Начать» VK показывает лишь в новом диалоге. Поэтому после первого входа в диалоге остаётся кнопка «Продолжить»: VK прикладывает ref к её нажатию, и повторный вход не требует ничего писать.
  2. client_info.lang_id нумеруется так же, как параметр lang запросов VK API. Клиент на английском присылает 3, и вопрос задаётся по-английски. Мобильное приложение VK применяет смену языка целиком только после перезапуска.
  3. Нажатие 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.