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

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.

Поле 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, после чего полученным результатом перезаписать весь список этой вкладки. При этом перестановок не ожидается, поскольку порядок во всех случаях обратный хронологический.

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