# Реакции на сообщения

136й слой. Общая схема изменений слоя:

```
updateMessageReactions peer:Peer msg_id:int reactions:MessageReactions = Update;

reactionCount flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;

messageReactions flags:# min:flags.0?true results:Vector<ReactionCount> recent_reactons:flags.1?Vector<MessageUserReaction> = MessageReactions;

messageUserReaction user_id:long reaction:string = MessageUserReaction;
messages.messageReactionsList flags:# count:int reactions:Vector<MessageUserReaction> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;

message flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;

availableReaction reaction:string title:string static_icon:Document select_animation:Document activate_animation:Document effect_animation:Document = AvailableReaction;

messages.availableReactionsNotModified = messages.AvailableReactions;
messages.availableReactions hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;

---functions---

messages.sendReaction flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
messages.getMessagesReactions peer:InputPeer id:Vector<int> = Updates;
messages.getMessageReactionsList flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;

messages.getAvailableReactions hash:int = messages.AvailableReactions;
```

## Возможные реакции

    messages.availableReactionsNotModified = messages.AvailableReactions;
    messages.availableReactions hash:int reactions:Vector<AvailableReaction> = messages.AvailableReactions;
    ---functions---
    messages.getAvailableReactions hash:int = messages.AvailableReactions;

Метод `messages.getAvailableReactions` позволяет получить все возможные реакции, они же являются реакциями для личных сообщений.
Для каждой реакции возвращаются следующие поля конструктора `availableReaction`:
`reaction` - строка с эмодзи реакции, например `👍`, `❤️`.
`title` - строка с текстовым описанием реакции, например `Thumbs Up`, `Red Heart`
`static_icon` - статичная иконка в формате WebP для отображения прямо внутри блока сообщения
`select_animation` - короткая анимация подрагивания в формате TGS, которая воспроизводится в списке реакций
`activate_animation` - анимация самого действия эмодзи в формате TGS (примеры: t.me/addstickers/animatedemojies)
`effect_animation` - анимация эффектов вокруг эмодзи в формате TGS (примеры: t.me/addstickers/EmojiAnimations).

Поле `hash` конструктора `messages.availableReactions` позволяет не выкачивать повторно весь ответ на этот метод при отсутствии изменений. Если повторно вызвать `messages.getAvailableReactions` с указанием этого поля, то метод может вернуть `messages.availableReactionsNotModified`, если содержимое не изменилось. При отсутствии на клиенте локального кеша можно передавать `hash = 0`.

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

## Получение списка реакций, доступных в группе/канале

    channelFull flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.20?true can_set_location:flags.16?true has_scheduled:flags.19?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?Vector<string> = ChatFull;

    chatFull flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?Vector<string> = ChatFull;

Новое поле `available_reactions` в конструкторах `channelFull` и `chatFull` задаёт список доступных в конкретной группе или канале реакций. Строки внутри вектора соответствуют значениям `availableReaction.reaction` разрешённых реакций.
Если поля нету по флагу, значит реакции были выключены администратором.

## Изменение списка доступных реакций

    messages.setChatAvailableReactions peer:InputPeer available_reactions:Vector<string> = Updates;

Метод `messages.setChatAvailableReactions` позволяет изменить набор включенных в канале или группе реакций.
Параметр `peer` задаёт канал или группу, в которой происходит редактирование. Метод доступен только участникам группы или админам канала с правом `change_info`, остальным вернётся ошибка `400 CHAT_ADMIN_REQUIRED`.
Параметр `available_reactions` должен содержать список включенных реакций (а именно строки `reaction`, полученные ранее с помощью `messages.getAvailableReactions`). Для выключения всех реакций необходимо передать пустой вектор.

Обновления на тему изменения доступных в чате реакций пока не предусмотрены, поэтому при изменении клиент сам обновляет поле `available_reactions` в  `channelFull` / `chatFull` локального кеша. Остальные участники получат новый список доступных реакций вместе с очередным вызовом `messages.getFullChat` / `channels.getFullChannel` (сейчас он почти всегда вызывается при очередном входе в чат).

Если были переданы эмодзи, которые не встречаются в актуальном ответе на `messages.getAvailableReactions`, то вернётся ошибка `400 REACTION_INVALID`. Для её обработки можно вызвать метод `messages.getAvailableReactions` для актуализации возможных реакций.

## Счётчики реакций сообщения

    reactionCount flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;

    messageReactions flags:# min:flags.0?true results:Vector<ReactionCount> recent_reactons:flags.1?Vector<MessageUserReaction> = MessageReactions;

    messageUserReaction user_id:long reaction:string = MessageUserReaction;

    message flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int = Message;

В конструкторе `message` появляется новое опциональное поле `reactions:flags.20?MessageReactions`. Оно содержит информацию, которая требуется для отображения секции реакций прямо в блоке сообщения в интерфейсе. 

#### messageReactions

Если присутствует флаг `min`, значит конструктор содержит информацию о реакциях без учёта текущего пользователя. В таком случае не стоит перезаписывать существующее локальное значение выбранной пользователем реакции (флаг `chosen`). Обработка флага должна быть похожа на обработку таких флагов в опросах (`pollResults.min` и `pollAnswerVoters.chosen`)

Поле `results` содержит список установленных реакций со счётчиками, а также информацию о том, какая реакция была выбрана текущим пользователем (флаг `reactionCount.chosen` при отсутствии флага `min`).
,
Поле `recent_reactons` может содержать до трех последних оставленных реакций в обратном хронологическом порядке вместе с их авторами. Поле может отсутствовать по флагу, в случае если список пользователей недоступен, например в каналах.

#### messageUserReaction

Поле `user_id` содержит идентификатор пользователя, который оставил эту реакцию. Стоит ожидать, что соответствующий пользователь вернётся в списке `users:Vector<User>` c [флагом min](/api/min.md).

Поле `reaction` содержит строку с эмодзи реакции.

## Отправка реакции

    ---functions---
    messages.sendReaction flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;

Метод `messages.sendReaction` позволяет добавить/изменить/удалить свою реакцию к определённому сообщению.

Параметр `peer` задаёт чат, в который происходит отправка реакции. Вызов метода доступен всем участникам, которые могут просматривать историю чата.

Параметр `msg_id` задаёт идентификатор сообщения, к которому необходимо добавить реакцию. Реакции к `messageService` добавлять нельзя. Также нельзя добавлять реакции к отложенным и спонсорским сообщениям.

Параметр `reaction` задаёт реакцию, которую необходимо добавить. Значение должно совпадать с одним из элементов `available_reactions` в группах/каналах или `availableReaction.reaction` в случае личных сообщений. При установке реакции необходимо также установить нулевой бит поля flags. Для удаления реакции необходимо не передавать нулевой бит поля flags и соответствующее поле `reaction`.

При добавлении / изменении реакции в группах и каналах можно также передавать `reaction`, который совпадает с одной из уже оставленных реакций под этим сообщением. То есть ограничение `available_reactions` накладывается только на добавление первой реакции с соответствующим эмодзи.

В случае успеха вернётся Updates, который будет содержать новое обновление `updateMessageReactions`, которое будет описано ниже.

### Ошибки

При передаче недопустимого чата в `peer` вернётся ошибка `400 PEER_ID_INVALID`.
При передаче недопустимого `msg_id` (например, сообщение было удалено или является сервисным) вернётся ошибка `400 MESSAGE_ID_INVALID`.
При передаче недопустимой реакции вернётся ошибка `400 REACTION_INVALID`.

## Обновления об изменении счётчиков реакций

    updateMessageReactions peer:Peer msg_id:int reactions:MessageReactions = Update;
    ---functions---
    messages.getMessagesReactions peer:InputPeer id:Vector<int> = Updates;

Новое обновление `updateMessageReactions`  соответствует изменению счетчиков реакций на сообщение. Поля `peer` и `msg_id` содержат чат и идентификатор сообщения, соответственно, в котором изменились счётчики. Поле `reactions` содержит новое значение в соответствующем объекте `message`. При получении такого обновления необходимо обновить локальный кеш соответствующего сообщения в БД и памяти, а также обновить состояние UI.

Также при добавлении первой реакции на сообщение будет приходить обновление о редактировании сообщения `updateEditMessage` / `updateEditChannelMessage`. При наличии поля `reactions` в новом объекте сообщения необходимо применять его аналогично, как если бы он пришёл из обновления `updateMessageReactions`.

Метод `messages.getMessagesReactions` позволяет обновить счетчики реакций у сообщения. Следует использовать этот метод для получения актуального состояния сообщений, отображаемых на экране, но не чаще, чем раз в 15 секунд для каждого из сообщений. Не следует запрашивать информацию о сообщениях, у которых нету поля `reactions:flags.20?MessageReactions` по флагу.

В ответе метода будут содержаться обновления `updateMessageReactions`, которые необходимо применить.

С помощью такого периодического обновления состояния отображаемых сообщений реализуется механизм шорт-поллинга. При реализации имеет смысл оглядываться на уже существующую реализацию `messages.getPollResults` + `updateMessagePoll`.

## Получение списка реакций

    messages.messageReactionsList flags:# count:int reactions:Vector<MessageUserReaction> users:Vector<User> next_offset:flags.0?string = messages.MessageReactionsList;
    ---functions---

    messages.getMessageReactionsList flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;

Метод `messages.getMessageReactionsList` позволяет получить список реакций на то или иное сообщение в обратном хронологическом порядке.

Параметры `peer` и `id` задают чат и идентификатор сообщения, список реакций к которому необходимо получить.
Параметр `reaction` позволяет опционально отфильтровать список по определённой реакции. Для этого необходимо установить соответствующий бит в `flags`, а также передать одну из возможных реакций в значение поля.
Параметр `limit` позволяет ограничить число получаемых элементов списка. Допустимые значения 1-100. Рекомендуется использовать значение `100` для подгрузки общего списка без ограничения по `reaction` и `50` в случае фильтра по конкретным реакциям.
Параметр `offset` позволяет продолжить подгрузку списка с предыдущей позиции. Необходимо передавать значение `next_offset` из последнего результата.

В ответ на метод вернётся конструктор `messages.messageReactionsList` со следующими полями:

Поле `count` содержит общее число элементов в списке.
Поле `reactions` содержит вектор самих реакций: пользователь + соответствующий emoji. В случае переданного параметра `reaction` его значение должно совпадать со всеми `messageUserReaction.reaction` в ответе.
Поле `users` содержит вектор пользователей, упомянутых в `reactions`.
Поле `next_offset` содержит значение параметра `offset` для подгрузки следующей порции списка. В случае отсутствия поля по флагу можно считать, что подгрузка всего списка завершена.

  Обратите внимание, что при подгрузке сплошного списка реакций (без фильтра `reaction`) можно предзаполнять остальные вкладки, вручную отфильтровав полученный список по эмодзи. Далее при открытии новой вкладки с фильтром по реакции можно сразу показать отфильтрованный общий список. Если список ещё не был загружен до конца, то необходимо отобразить недостающие мерцающие заглушки и выполнить запрос с `reaction` и без `offset`, после чего полученным результатом перезаписать весь список этой вкладки. При этом перестановок не ожидается, поскольку порядок во всех случаях обратный хронологический.

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