longpoll-doc

User LongPoll API

Long Polling — это технология, которая позволяет получать данные о новых событиях с помощью “длинных запросов”. Сервер получает запрос, но отправляет ответ на него не сразу, а лишь тогда, когда произойдет какое-либо событие (например, придёт новое сообщение), либо истечет заданное время ожидания. После ответа клиент сразу же делает повторный запрос на сервер.
Таким образом поддерживается постоянное соединение с сервером, что позволяет получать новые события в реальном времени.

Немного подробнее о технологии можно почитать здесь.

Документация написана для 19 версии LongPoll.

План документации:

  1. Отказ от ответственности
  2. Подключение
  3. Получение истории событий
  4. Структура сообщения
  5. Описание событий
  6. Дополнительная информация

Отказ от ответственности

Автор не несет ответственности за точность, полноту или качество предоставленной информации. Используйте последующую информацию на свой страх и риск.

Правила пользования сайтом, пункт 6.7:

6.7. Создаваемые Пользователями приложения API должны использовать только опубликованные на Сайте методы API, а также ID, защищенный ключ и сервисный ключ доступа, указанные в настройках данных приложений. Использование других методов API, а также ID, защищенного ключа и сервисного ключа доступа приложений API третьих лиц, в т.ч. приложения API Администрации Сайта, строго запрещено. Пользователь обязуется регулярно проверять перечень разрешённых методов и незамедлительно вносить корректировки в функциональность своих приложений API в соответствии с изменениями перечня. За нарушение настоящего пункта Пользователь несет предусмотренную применимым законодательством, настоящими Правилами и иными документами Администрации Сайта ответственность. Администрация Сайта при этом оставляет за собой право на защиту собственных прав и законных интересов.

Подключение

Ссылка для запроса составляется следующим образом:

https://server?act=a_check&key=key&ts=ts&wait=wait&mode=mode&version=version

server, key и ts получаются методом messages.getLongPollServer

Бит Описание
1 << 1 Возвращать кладжи (секции additional и attachments в структуре сообщения)
1 << 3 Возвращать расширенную информацию в 114, 115 и 119 событиях
1 << 5 Возвращать pts
1 << 6 [неактуально] Возвращать события изменения онлайна друзей: 8 и 9
1 << 7 Возвращать random_id
1 << 9 Возвращать события о бизнес-уведомлениях
1 << 10 Возвращать тип online для marked_users при упоминании через @online

После выполнения запроса сервер вернет ответ следующего вида:

type LongPollResult =
  // pts приходит, если в mode выставлен бит (1 << 5)
  // updates - массив событий, которые описаны в разделе "Описание событий"
  | { ts: number, pts: number, updates: unknown[] }
  // ts слишком маленький (отстал более чем на 256 событий) или слишком большой (больше последнего существующего).
  // Необходимо воспользоваться переданным ts (возвращается номер последнего события).
  // Как получить пропущенные события можно узнать в разделе "Получение истории событий" чуть ниже
  | { failed: 1, ts: number }
  // Ключ инвалидировался. Необходимо получить новый key, используя метод messages.getLongPollServer
  | { failed: 2, error: string }
  // Указана неверная версия лонгполла
  | { failed: 4, min_version: 0, max_version: 19 }

После обработки ответа нужно повторить запрос, но с новым значением ts.

Получение истории событий

Если лонгполл вернул failed: 1, у вас все еще есть возможность получить пропущенные события.

1) Включите получение pts при подключении к лонгполлу, добавив флаг 1 << 5 в mode 2) Всегда сохраняйте возвращаемый лонгполлом pts 3) С использованием pts и некоторых других параметров, описанных в документации, вызовите метод messages.getLongPollHistory.

Формат ответа метода описан здесь: https://github.com/VKCOM/api-schema-typescript/blob/master/src/methods/messages.ts#L773

Поле history похоже на поле updates из лонгполла, но в нем будут находиться только персистентные события.
Так же события из этого списка придут в урезанном виде:

Как можно заметить, вместо 10000 + eventId событий приходят события 3, 4, 5 и 18. Соответственно вместо conversationMessageId приходит просто messageId. Сам conversationMessageId при необходимости можно достать из поля messages в ответе метода.

Структура этих событий выглядит так:

type LongPollHistoryMessageEvent = [
  type: 3 | 4 | 5 | 18,
  messageId: number,
  flags: number,
  peerId: number
];

Всю информацию о сообщениях и беседах можно взять из полей messages и conversations, где содержатся данные из API.

Если в ответе придет поле more, то после обработки всех событий нужно будет повторить запрос, указав в поле pts пришедший new_pts.

Структура сообщения

Подробнее про некоторые части структуры можно почитать здесь:

type LongPollMessage = [
  type: 10003 | 10004 | 10005 | 10018,
  // id сообщения относительно беседы
  conversationMessageId: number,
  flags: number,
  // Этот элемент приходит только для события 10004
  minorId?: number,
  peerId: number,
  timestamp: number,
  // Переносы строк обозначаются как <br>, а символы " & < > экранируются
  text: string,

  // Объект приходит только при указании флага 2 при подключении к LongPoll
  additional: {
    // Устаревшее поле, не следует использовать
    title?: string
    // Наличие emoji в сообщении
    emoji?: '1'
    // id автора сообщения. Приходит только в беседах
    from?: string
    // Наличие шаблона (для получения шаблона нужно получить сообщение из API)
    has_template?: '1'
    marked_users?: Array<
      // Список из упомянутых людей/сообществ в сообщении. Ответ на другое сообщение так же считается как упоминание
      | [1, number[]]
      // @online
      | [1, 'online', number[]]
      // @all
      | [1, 'all']
      // Исчезающее сообщение в обычном чате
      | [2, 'all']
    >
    // Клавиатура для ботов (для беседы или сообщения)
    keyboard?: MessageKeyboard
    // Количество секунд до исчезновения сообщения в обычном чате
    expire_ttl?: string
    // Количество секунд до исчезновения сообщения в фантомном чате
    ttl?: number
    // Сообщение исчезло, приходит в 10018 событии
    is_expired?: '1'
    // Приходит, если при отправке сообщения указать параметр payload.
    // Возвращает всегда JSON, который может содержать любую информацию.
    // Там может содержаться отладочная информация, которая может пригодиться разработчику.
    payload?: string

    // Так же здесь могут быть поля, описывающие сервисное сообщение
    // см. ссылку выше
  },

  // Объект приходит только при указании флага 2 при подключении к LongPoll
  attachments: {
    // Есть пересланное сообщение или ответ на сообщение
    fwd?: '0_0'
    // Ответ на сообщение: '{"conversation_message_id":number}'
    // Приходит только в 10004 и 10018 событиях
    reply?: string

    // Количество вложений в поле attachments
    // Число в строке
    attachments_count?: string
    // JSON с массивом вложений
    // Приходит только для некоторых типов вложений
    attachments?: string

    // Описание вложений вида { attach1_type, attach1, ... }
    // Все значения здесь имеют тип строки (и могут быть объектом в строке по типу '{"id":1}')
    // см. ссылку выше
  },

  // Возвращается, если в mode есть флаг 128
  randomId: number,
  messageId: number,
  // 0 (не редактировалось) или timestamp (время редактирования в секундах)
  updateTimestamp: number
];

Короткий кортеж сообщения

Существует возможность возвращения укороченного кортежа с сообщением. Такое может произойти, если сообщение на момент ответа лонгполла уже было удалено для всех.

Если вы отправили сообщение с определенным random_id и ждете ответа от лонгполла с этим же random_id, то с этим событием определенно возникнут проблемы, потому что в нем отсутствует random_id. Чтобы получить random_id, нужно запросить сообщение через апи: в случае 10004 события по minorId (в данном случае это синоним message_id), или в случае остальных событий по conversationMessageId и peerId. Далее, если сообщение было не нашим, апи вернет ошибку и мы ее проигнорируем, а если сообщение было нашим - вернет сообщение с полем deleted: 1 и искомым random_id.

type LongPollMessageShort = [
  type: 10004,
  conversationMessageId: number,
  flags: number,
  minorId: number
];

type LongPollMessageShort = [
  type: 10003 | 10005 | 10018,
  conversationMessageId: number,
  flags: number,
  peerId: number
];

Описание событий

Событие 10002. Установка флагов сообщения

Возможные значения флагов сообщения:

  1. Пометка важным (8 important)
  2. Пометка как спам (64 spam)
  3. Удаление сообщения (128 deleted)
  4. Удаление для всех (128 deleted и 131072 deleted_all)
  5. Прослушивание голосового сообщения (4096 audio_listened)

Событие приходит как при прослушивании собеседником вашего голосового сообщения, так и при прослушивании вами голосового сообщения собеседника.

Вручную прослушать голосовое сообщение собеседника можно с помощью метода messages.markAsListened с параметром message_id или cmid. Метод вернет 1 при первом прослушивании голосового сообщения, а 0 при последующих или при попытке прослушать свое сообщение.

type Event10002 = [
  type: 10002,
  messageId: number,
  flags: number,
  peerId: number
];

Событие 10003. Сброс флагов сообщения

Возможные значения флагов сообщения:

  1. Прочитано сообщение (1 unread). Устаревший флаг, не следует использовать
  2. Отмена пометки важным (8 important)
  3. Отмена пометки сообщения как спам (64 spam + 32768 cancel_spam)
  4. Восстановление удаленного сообщения (128 deleted)

В 3 и 4 случаях возвращается сообщение.

type Event10003 = [
  type: 10003,
  messageId: number,
  flags: number,
  peerId: number
] | LongPollMessage;

Событие 10004. Новое сообщение

Данное событие возвращает новое сообщение.

type Event10004 = LongPollMessage;

Событие 10005. Редактирование сообщения

Данное событие возвращает отредактированное сообщение.

type Event10005 = LongPollMessage;

Событие 10006. Прочтение входящих сообщений

Вы прочитали в диалоге peer_id сообщения до msg_id включительно.
count - количество непрочитанных сообщений в диалоге.

type Event10006 = [
  type: 10006,
  peerId: number,
  messageId: number,
  count: number
];

Событие 10007. Прочтение исходящих сообщений

Собеседник прочитал в диалоге peer_id сообщения до msg_id включительно.
count - количество ваших непрочитанных сообщений в диалоге.

type Event10007 = [
  type: 10007,
  peerId: number,
  messageId: number,
  count: number
];

Событие 8. Друг появился в сети

Это событие больше не приходит в LongPoll

Событие не приходит, если у друга включена невидимка хотя бы для одного пользователя.

type Event8 = [
  type: 8,
  // отрицательный id друга
  userId: number,
  // 1 - m.vk.com или неизвестное мобильное приложение
  // 2 - iPhone
  // 3 - iPad
  // 4 - Android
  // 5 - Windows Phone
  // 6 - Windows 8
  // 7 - vk.com или неизвестное десктопное приложение
  platform: 1 | 2 | 3 | 4 | 5 | 6 | 7,
  // время онлайна в секундах
  timestamp: number,
  // id приложения, с которого онлайн друг
  // 0 если онлайн был вызван не приложением (например веб)
  appId: 0 | number,
  // 1 если онлайн с мобильного, 0 если нет
  isMobile: 0 | 1,
  // Всегда приходит 0
  hasInvisibleMode: 0 | 1
];

Событие 9. Друг вышел из сети

Это событие больше не приходит в LongPoll

Событие не приходит, если у друга включена невидимка хотя бы для одного пользователя.

type Event9 = [
  type: 9,
  // отрицательный id друга
  userId: number,
  // 1 если бездействовал 5 минут, 0 если покинул сайт
  isTimeout: 0 | 1,
  // время наступления офлайна в секундах
  timestamp: number,
  // id приложения, с которого был онлайн друг
  // 0 если онлайн был вызван не приложением (например веб)
  appId: 0 | number,
  // 1 если онлайн с мобильного, 0 если нет
  isMobile: 0 | 1,
  // Всегда приходит 0
  hasInvisibleMode: 0 | 1
];

Событие 10. Сброс флагов беседы

Флаги бесед описаны здесь.

type Event10 = [
  type: 10,
  peerId: number,
  flags: number
];

Событие 12. Установка флагов беседы

Флаги бесед описаны здесь.

type Event12 = [
  type: 12,
  peerId: number,
  flags: number
];

Событие 10013. Удаление всех сообщений в диалоге

В беседе peerId были удалены все сообщения до messageId включительно.

type Event10013 = [
  type: 10013,
  peerId: number,
  messageId: number
];

Событие 10018. Обновление сообщения

Приходит при следующих событиях:

  1. Добавился сниппет (ссылка) - к вложениям добавляется link.
  2. Сообщение исчезло — удаляется текст и все вложения, добавляется ключ is_expired: true.
  3. Пришел перевод голосового сообщения.

Данное событие возвращает сообщение.

type Event10018 = LongPollMessage;

Событие 10019. Сброс кеша сообщения

По какой-либо причине изменилось сообщение (без явного редактирования). Необходимо переполучить сообщение через API.

type Event10019 = [
  type: 10019,
  messageId: number
];

Событие 20. Закрепление и открепление беседы (изменился majorId)

majorId и minorId - это дополнительные айдишники для беседы, которые используются для ее сортировки в списке всех бесед.

Сначала список сортируется по majorId, а если есть беседы с одинаковыми majorId, то они сортируются по minorId

majorId принимает следующие значения: 0, 16, 32, 48, 64, 80. Чем больше значение, тем выше беседа в списке закрепленных. 0 означает незакрепленную беседу.

При закреплении беседы ей присваивается наибольшее доступное значение majorId, то есть она закрепляется в самый верх списка.

Если закрепленная беседа всего одна, то ей присваивается majorId = 16, если две — верхней присваивается 32 и так далее.

type Event20 = [
  type: 20,
  peerId: number,
  majorId: number,
  0
];

Событие 21. Изменился minorId

Для 10 версии LongPoll событие приходит только в методе messages.getLongPollHistory

В версиях LongPoll выше 10 событие приходит еще в некоторых сценариях, но это будет подробно расписано только при обновлении документации на новую версию LongPoll

minorId означает id последнего сообщения в беседе, но с одним отличием. В фантомном чате последнее сообщение может исчезнуть, и чтобы диалог после этого полностью не пропал из списка, minorId сохраняет в себе id последнего даже исчезнувшего сообщения.

type Event21 = [
  type: 21,
  peerId: number,
  minorId: number
];

Событие 50. Перевод сообщения

Это событие приходит, если пользователь запросил перевод сообщения.

Если для определённого сообщения перевод запрашивается в первый раз, то вместе с событием 50 приходит и событие 10018, где в объекте additional будет поле is_translated.

type Event50 = [
  type: 50,
  data: {
    peer_id: number,
    cmid: number,
    translation: string,
    language: string
  }
];

Поле language представляет собой языковую пару, который выглядит так: ru-en. Перед дефисом прописан исходный язык переводимого текста, после дефиса — язык, на который будет переведён текст.

Список поддерживаемых языковых пар можно получить, выполнив метод account.getInfo. В ответе будет поле messages_translation_language_pairs.

Чтобы запросить перевод сообщения, нужно выполнить метод messages.translate (доступен только официальным клиентам).

Параметры метода messages.translate:

interface MessagesTranslateParams {
  peer_id: number,
  cmids: string,
  language: string
}

Событие 51. Изменение данных чата

Событие означает, что в беседе chatId изменились какие-то данные. Более подробно все расписано в 52 событии, которое приходит одновременно с этим событии.

type Event51 = [
  type: 51,
  chatId: number
];

Событие 52. Изменение данных чата

Тип Описание Дополнительная информация
0 Из беседы был создан фантомный чат 0
1 Изменилось название беседы 0
2 Обновилась аватарка беседы 0
3 Назначен новый администратор id администратора
4 Изменение прав доступа в беседе mask с правами доступа
5 Закрепление или открепление сообщения conversationMsgId или 0
6 Вступление в беседу id вступившего
7 Выход из беседы id вышедшего
8 Исключение из беседы id исключенного
9 Разжалован администратор id бывшего админа
10 Изменился баннер 0
11 Появление или скрытие клавиатуры peerId
12 Отозвано / подтверждено / отклонено / пришло приглашение в чат 0 / 1 / 2 / 3
13 Контакт был сконвертирован в юзера (contactId -> userId) contactId
14 Произошло любое действие с бизнес-уведомлением 0
15 Вы отозвали приглашение контакта или юзера в чат contactId или userId
16 Контакт или юзер отклонил приглашение в чат contactId или userId
17 Контакт или юзер принял приглашение в чат contactId или userId
18 Контакт или юзера пригласили в чат contactId или userId
19 Начало или окончание группового звонка 1 в начале, 0 в конце
22 Чат больше не новый: пришло первое сообщение (только в лс) 0
23 Изменено оформление чата 0
24 Изменилось описание чата 0
25 Изменилось состояние chat_settings.short_poll_reactions 0 / 1
26 Добавление инкогнито юзера в чат incognitoId
27 Конвертация инкогнито юзера в обычного юзера incognitoId
28 Удаление инкогнито юзера из чата incognitoId

При изменении названия (1) и обновлении аватарки беседы (2) нужные данные можно взять из сервисного сообщения в 4 событии.

type Event52 = [
  type: 52,
  updateType: number,
  peerId: number,
  extra: number
];

Событие 63. Статус набора сообщения

Означает, что в беседе peerId начали писать текст totalCount людей. Их id записаны в userIds. В массиве userIds может появиться и ваш id, так что нужно фильтровать этот список.

type Event63 = [
  type: 63,
  peerId: number,
  userIds: number[],
  totalCount: number,
  timestamp: number
];

Событие 64. Статус записи голосового сообщения

Вызывается при записи голосового сообщения.
Идентичен событию 63.

Событие 65. Статус загрузки фотографии

Вызывается при загрузке фотографии.
Идентичен событию 63.

Событие 66. Статус загрузки видеозаписи

Вызывается при загрузке видеозаписи.
Идентичен событию 63.

Событие 67. Статус загрузки файла

Вызывается при загрузке файла.
Идентичен событию 63.

Событие 80. Изменение количества непрочитанных диалогов

Все счетчики возвращаются без учета сообщений в архиве

type Event80 = [
  type: 80,
  // Количество непрочитанных бесед
  unreadCount: number,
  // Количество непрочитанных незамьюченных бесед
  unreadUnmutedCount: number,
  // 0 - показывать количество всех бесед, 1 - только незамьюченные
  showOnlyUnmuted: 0 | 1,
  // Количество непрочитанных бизнес-уведомлений
  businessNotifyUnreadCount: number,
  // Количество непрочитанных бесед в мессенджере в шапке
  // Эти два счетчика можно сбросить, просто открыв мессенджер в шапке на вебе
  headerUnreadCount: number,
  // Количество непрочитанных незамьюченных бесед в мессенджере в шапке
  headerUnreadUnmutedCount: number,
  // Количество непрочитанных бесед в архиве
  archiveUnreadCount: number,
  // Количество непрочитанных незамьюченных бесед в архиве
  archiveUnreadUnmutedCount: number,
  // Количество бесед с упоминаниями в архиве
  archiveMentionsCount: number
];

Событие 81. Изменение состояния невидимки друга

Если невидимка будет выключена не для всех, то метод так же вернет state: 1, хотя для нас онлайн может быть так же виден

type Event81 = [
  type: 81,
  // Отрицательный id друга
  userId: number,
  // 0 - невидимка выключена, 1 - включена
  state: 0 | 1,
  // время последнего онлайна друга
  timestamp: number,
  -1,
  // При настройке невидимки через VK Me приходит 0, в других случаях не проверял
  appId: 0 | number
];

Событие 90. Добавление или удаление из друзей

Событие возвращается только для действий с вашей стороны.

Значения actionType:

type Event90 = [
  type: 90,
  actionType: 2 | 3,
  userId: number
];

Событие 114. Изменение настроек пуш-уведомлений в беседе

Для работы необходимо использовать в mode флаг 8 при выполнении запроса.

type Event114 = [
  type: 114,
  data: {
    peer_id: number
    sound: 0 | 1
    disabled_until: 0 | -1 | number
  }
];

Событие 115. Звонок

Для работы необходимо использовать в mode флаг 8 при выполнении запроса.

¯\_(ツ)_/¯

Событие 119. Ответ callback-кнопки

Для работы необходимо использовать в mode флаг 8 при выполнении запроса.

Callback-кнопки работают следующим образом:

  1. Бот отправляет клавиатуру (обычную или инлайн), где находится callback-кнопка;
  2. Пользователь нажимает на эту кнопку и клиент вызывает метод messages.sendMessageEvent (параметры см. ниже). Метод возвращает строку - event_id;
  3. Бот получает событие message_event и вызывает метод messages.sendMessageEventAnswer;
  4. Этот метод вызывает 119 событие LongPoll у пользователя, где в action прописано действие, которое необходимо выполнить клиенту.

Параметры метода messages.sendMessageEvent:

interface MessagesSendMessageEventParams {
  peer_id: number
  // Находится в кнопке клавиатуры
  payload: string

  // Нужно передать один из двух параметров,
  // если это инлайн клавиатура в сообщении или карусели
  message_id?: number
  conversation_message_id?: number

  // Находится в объекте клавиатуры беседы
  // Нужно передать, если это клавиатура беседы
  author_id?: number
}

После получения event_id на 2 стадии нужно начать ждать 119 событие LongPoll с нужным event_id. Если за минуту бот так и не пришлет событие, то ожидание ответа следует прекратить.

type Event119 = [
  type: 119,
  data: {
    // Отрицательный ID бота, который ответил на клик по кнопке
    owner_id: number
    // ID беседы, в которой находится сообщение
    peer_id: number
    // Уникальный ID события, действующий 1 минуту.
    // Нужен для идентификации кликнутой кнопки
    event_id: string
    // Не приходит, если не нужно выполнять никакое действие
    // т.е. бот отправил пустой payload или неизвестный тип действия
    action?:
      // Показать снекбар с текстом `text`
      | { type: 'show_snackbar', text: string }
      // Открыть ссылку `link`
      | { type: 'open_link', link: string }
      // Открыть приложение по ссылке
      // https://vk.com/app${app_id}_${owner_id}#${hash}
      // https://vk.com/app${app_id}_${owner_id} (если hash = '')
      // https://vk.com/app${app_id} (если owner_id = undefined и hash = '')
      | { type: 'open_app', app_id: number, owner_id?: number, hash: string }
  }
];

Событие 501. Создание папки

type Event501 = [
  type: 501,
  folderId: number,
  folderName: string,
  randomId: number
];

Событие 502. Удаление папки

type Event502 = [
  type: 502,
  folderId: number
];

Событие 503. Переименование папки

type Event503 = [
  type: 503,
  folderId: number,
  newFolderName: string
];

Событие 504. Добавление бесед в папку

type Event504 = [
  type: 504,
  folderId: number,
  ...addedFolderIds: number[]
];

Например, если в папку с id = 5 добавить три беседы: 88262293, 172894294 и 2000000346, то событие будет выглядеть вот так:

[504, 5, 88262293, 172894294, 2000000346]

Событие 505. Удаление бесед из папки

type Event505 = [
  type: 505,
  folderId: number,
  ...deletedFolderIds: number[]
];

Например, если из папки с id = 5 удалить три беседы: 88262293, 172894294 и 2000000346, то событие будет выглядеть вот так:

[505, 5, 88262293, 172894294, 2000000346]

Событие 506. Изменение порядка папок

type Event506 = [
  type: 506,
  ...folderIds: number[]
];

Например, если у нас есть три папки с id 1, 2 и 4, то нам может прийти такое событие:

[506, 1, 4, 2]

Событие 507. Изменение количества непрочитанных диалогов в папках

type Event507 = [
  type: 507,
  ...foldersCounters: FolderCountersTuple[]
];

type FolderCountersTuple = [
  folderId: number,
  unreadCount: number,
  unreadUnmutedCount: number
];

Событие возвращает данные только для тех папок, в которых изменились каунтеры.

Например, если в папке с id = 2 стало 3 непрочитанных замьюченных чата, а в папке с id = 3 стало 2 непрочитанных чата: один замьючен, а другой нет, то событие будет выглядеть так:

[507, [2, 3, 0], [3, 2, 1]]

Дополнительная информация

Флаги сообщений

Маской называют сумму некоторых флагов (степеней двойки), которую можно использовать как хорошую замену для объектов или массивов.

// Обычно маску записывают подобным образом:
const mask = 1 | 2 | 8 | 64 | 1024;
// Что эквивалентно сложению чисел:
const mask = 1 + 2 + 8 + 64 + 1024;
// А еще можно не писать магические цифры, а записывать степени двойки в таком формате:
const mask = (1 << 0) | (1 << 1) | (1 << 3) | (1 << 6) | (1 << 10);
Бит Значение Описание
1 << 0 1 Непрочитанное сообщение
1 << 1 2 Исходящее сообщение
1 << 3 8 Важное сообщение
1 << 4 16 [deprecated] Сообщение из группового чата
1 << 5 32 [deprecated] Сообщение от друга
1 << 6 64 Сообщение помечено как спам
1 << 7 128 Сообщение удалено локально
1 << 12 4096 Прослушано голосовое сообщение
1 << 13 8192 Сообщение из группового чата
1 << 15 32768 🤔 Сообщение перемещено в список спама
1 << 16 65536 Не обновлять minor_id, т.е. не поднимать чат в списке
1 << 17 131072 Сообщение удалено для всех
1 << 18 262144 [internal]
1 << 19 524288 [internal]
1 << 20 1048576 Не показывать пуш/уведомление для этого сообщения
1 << 21 2097152 Сообщение с ответом на другое сообщение
1 << 22 4194304 Сообщение будет удалено при окончании TTL
1 << 23 8388608 Сообщение придет автоматически прочитанным, если ранее не было непрочитанных сообщений
1 << 24 16777216 Наличие реакции у сообщения
1 << 26 67108864 [internal]

Бесшумное сообщение можно отправить, добавив к параметрам метода messages.send ключ silent: true:

Пример определения наличия флага в маске:

const mask = 1 | 2 | 32; // = 35

8 & mask // вернет 0 (false)
2 & mask // вернет 2 (true)

Флаги бесед

Бит Значение Описание
1 << 4 16 Отключение пуш уведомлений
1 << 5 32 Отключение звука уведомлений
1 << 8 256 Входящий запрос на переписку / вступление в беседу
1 << 9 512 Отклоненный запрос на переписку / вступление в беседу
1 << 10 1024 Наличие упоминания
1 << 11 2048 Не отображать беседу при поиске
1 << 12 4096 [internal]
1 << 13 8192 Беседа с бизнес-уведомлениями
1 << 14 16384 Наличие маркированного сообщения: упоминание или исчезающее сообщение
1 << 16 65536 Беседа, которая не пропадает из списка чатов (не сбрасывается minor_id)
1 << 18 262144 Не присылать уведомления об @all и @online
1 << 19 524288 Не присылать уведомления о всех упоминаниях
1 << 20 1048576 Беседа помечена как непрочитанная
1 << 22 4194304 [internal]
1 << 23 8388608 Беседа помещена в архив
1 << 24 16777216 Беседа, в которой идет звонок
1 << 26 67108864 Признак того, что это чат: при создании старого канала снимается этот флаг

Сервисные сообщения

Сервисное сообщение описывается ключом source_act и ключами с дополнительными данными в объекте additional из структуры сообщения.

У некоторых сервисных сообщений в поле text приходит сообщение вида Сообщение не поддерживается Вашим приложением., поэтому при обработке сервисных сообщений не нужно обрабатывать текст сообщения.

Вместе с сервисными сообщениями chat_title_update и chat_photo_remove в каналах приходит поле source_is_channel: "1". Сервисное сообщение chat_photo_update для каналов не приходит (только 52 событие).

Возможные значения source_act:

Тип Дополнительные ключи
chat_create
Создание беседы
source_text - название беседы
chat_photo_update
Обновление фотографии беседы
Фото можно получить во вложении сообщения
chat_photo_remove
Удаление фотографии беседы
chat_title_update
Обновление названия беседы
source_old_text - старое название беседы
source_text - новое название беседы
chat_pin_message
Закрепление сообщения
source_mid - id закрепившего сообщение
source_message - обрезанное закрепленное сообщение
source_chat_local_id - локальный id сообщения
chat_unpin_message
Открепление сообщения
source_mid - id открепившего сообщение
source_chat_local_id - локальный id сообщения
chat_invite_user
Вступление в беседу
source_mid - id вступившего в беседу
chat_invite_user_by_link
Вступление в беседу по ссылке
chat_kick_user
Выход или исключение из беседы
source_mid - id вышедшего или исключенного
chat_kick_don
Исключение дона из беседы
Приходит, начиная с версии API 5.154
chat_screenshot
Создание скриншота с фантомным сообщением
source_mid - id создавшего скриншот
chat_group_call_started
Начало группового звонка в беседе
Больше не приходит в LongPoll
Вместо него создается сообщение с вложением group_call_in_progress
Приходит для старых сообщений через API при получении через токен VK для Android
chat_invite_user_by_call
Приглашение пользователя в звонок
source_mid - id приглашенного юзера
chat_invite_user_by_call_join_link
Присоединение пользователя к звонку по ссылке
chat_invite_user_by_message_request
Запрос на добавление в беседу
source_mid - id пригласившего в беседу
conversation_style_update
Обновление стиля беседы
source_style - название стиля
Поле не приходит в случае сброса стиля
Возможные варианты можно посмотреть здесь

Пример описания сервисного сообщения:

const longpollServiceMessage = {
  source_act: 'chat_pin_message',
  source_mid: '88262293',
  source_message: 'Сообщение, которое будет в закрепе',
  source_chat_local_id: '5517'
}

Клавиатура для ботов

Клавиатура для ботов представляет собой объект с описанием ее типа и кнопок. Основная структура представлена ниже, остальную информацию можно узнать в документации.

// TODO: новые тайпинги
// Типы у объекта клавиатуры:
// https://github.com/danyadev/vk-types/blob/master/src/objects/VKKeyboard.ts
interface LongPollKeyboard {
  // Приходит, если это клавиатура сообщения или карусели
  inline?: true
  // Скрывать ли клавиатуру при клике на кнопку (не работает для inline)
  one_time: boolean
  buttons?: VKKeyboardButton[][]
}

Вложения

Список существующих вложений

Список известных на данный момент вложений: geo, doc, link, poll, wall, call, gift, story, photo, audio, video, event, market, artist, widget, sticker, article, podcast, curator, graffiti, mini_app, narrative, wall_reply, audio_message, money_request, audio_playlist, group_call_in_progress.

Однако названия вложений, полученных через LongPoll, могут не совпадать с теми, что приходят через API:

Вложения artist, article, mini_app и audio_playlist приходят в API только через токен VK для Android.

Вложение geo (прикрепленное местоположение) приходит в виде ключей geo и geo_provider вместо attach* (см. структуру). Также при получении сообщения через messages.getById ключ geo будет находиться не во вложениях, а в “корне” сообщения.

Сообщение с вложением group_call_in_progress создается, когда пользователь начинает групповой звонок. При окончании группового звонка сообщение с этим вложением удаляется и создается новое сообщение с вложением call.

Работа с вложениями в LongPoll

Пример вложений, состоящих из фотографии, документа и аудиозаписи:

const longpollAttachments = {
  attach1: '88262293_457290160',
  attach1_type: 'photo',
  attach2: '88262293_532324610',
  attach2_type: 'doc',
  attach3: '88262293_535133534',
  attach3_kind: 'audiomsg',
  attach3_type: 'doc'
}

Обычно дополнительная информация о вложении не нужна, поэтому можно ограничиться получением названий вложений для предварительного отображения сообщения и анализа списка вложений, чтобы в случае необходимости получить сообщение через API.

Пример кода для получения массива с названиями вложений
function getAttachments(data) {
  const attachments = [];

  if (data.geo) {
    attachments.push('geo');
  }

  for (const key in data) {
    const match = key.match(/attach(\d+)$/);

    if (match) {
      const id = match[1];
      const kind = data[`attach${id}_kind`];
      let type = data[`attach${id}_type`];

      if (kind === 'audiomsg') type = 'audio_message';
      if (kind === 'graffiti') type = 'graffiti';
      if (type === 'group') type = 'event';

      attachments.push(type);
    }
  }

  return attachments;
}

Ответ на сообщение

Определить наличие ответа на сообщение можно с помощью проверки наличия attachments.reply (структура) или флага reply_msg.

В некоторых случаях получать сообщение через API для получения ответа на сообщение не нужно: если сообщение, на которое пришло ответ, пришло недавно, то оно наверняка уже должно быть в локальном списке сообщений. Найти сообщение можно с помощью conversation_message_id из attachments.reply.

Пересланные сообщения

Определить наличие пересланных сообщений можно с помощью проверки на отсутствие ответа на сообщение и на наличие attachments.fwd. ID пересланных сообщений, как и их количество, пока что не приходит через LongPoll.

Оптимизация получения вложений

Для получения некоторых вложений необязательно получать сообщение через API - в объект attachments (структура) приходят поля attachments_count и attachments, где и содержатся объекты вложений из API. К примеру, в списке вложений можно найти стикер и голосовое сообщение.

При работе с вложениями можно попробовать найти необходимый элемент в документации, однако у некоторых вложений документация не обновлена или вовсе отсутствует.

Права доступа в беседах

В списке представлены все возможные параметры прав доступа с их флагом из 4 действия 52 события.

Дополнительные флаги:

Зачем нужен random_id

random_id начиная с версии API 5.90 стал обязательным параметром. Но это не означает, что всем нужно генерировать уникальные значения для этого параметра. Если вы не собираетесь его использовать, то можете указать в его значении число 0.

Значение random_id должно быть уникальным в течение одного часа в рамках app_id, id пользователя и peer_id диалога.

Значение random_id может принимать числа от -2147483648 (-(2^31)) до 2147483647 (2^31 - 1). Если число будет больше или меньше данного порога, то из переданного числа отнимется этот лимит.

Основная задача этого параметра заключается в гарантировании идемпотентности API для отправки сообщений. Это означает, что при отправке запросов с одинаковым random_id и, чаще всего, одинаковыми параметрами, сервер не будет создавать новое сообщение и вернет ID уже созданного. Про необходимость данной технологии с убедительными примерами рассказывается в данной статье.

Наверняка вы уже видели, что во всех мессенджерах ВКонтакте при отправке сообщения само сообщение отображается сразу, но около сообщения некоторое время видно иконку часов. Эта иконка означает, что запрос на сервер с новым сообщением отправлен, но сообщение еще не пришло обратно через LongPoll.

Чтобы реализовать подобную фичу, нужно определить, какое именно сообщение, приходящее из LongPoll, было только что отправлено. Для этого нужно: 1) Создать random_id и сохранить его в списке с отправляемыми сообщениями 2) Отправить сообщение 3) Дождаться прихода из LongPoll сообщения с нашим random_id 4) Удалить random_id из списка, тем самым пометив сообщение как отправленное.