Encapsulate TDLib state mutations

This commit is contained in:
Mikhail Kilin
2026-05-17 18:25:18 +03:00
parent d8af6a76a1
commit 6b27cbece9
6 changed files with 237 additions and 147 deletions

View File

@@ -104,11 +104,11 @@ rg -n "current_chat_messages_mut|chats_mut|folders_mut|pending_user_ids_mut|user
Steps: Steps:
- [ ] Add focused methods on `TdClient` for common mutations: update chat, update message by id, queue pending user, update user cache, update folders. - [x] Add focused methods on `TdClient` for common mutations: update chat, update message by id, queue pending user, update user cache, update folders.
- [ ] Replace raw `*_mut()` usage in helper/update modules with those methods. - [x] Replace raw `*_mut()` usage in helper/update modules with those methods.
- [ ] Keep raw mutable access private to `TdClient` implementation where it is still needed. - [x] Keep raw mutable access private to `TdClient` implementation where it is still needed.
- [ ] Add or update tests around message updates, user-cache updates, and chat-list updates. - [x] Add or update tests around message updates, user-cache updates, and chat-list updates.
- [ ] Run `cargo test --all-features`. - [x] Run `cargo test --all-features`.
Acceptance criteria: Acceptance criteria:

View File

@@ -10,19 +10,12 @@ use tdlib_rs::enums::{Chat as TdChat, ChatList, ChatType};
use super::client::TdClient; use super::client::TdClient;
use super::types::ChatInfo; use super::types::ChatInfo;
/// Находит мутабельную ссылку на чат по ID.
pub fn find_chat_mut(client: &mut TdClient, chat_id: ChatId) -> Option<&mut ChatInfo> {
client.chats_mut().iter_mut().find(|c| c.id == chat_id)
}
/// Обновляет поле чата, если чат найден. /// Обновляет поле чата, если чат найден.
pub fn update_chat<F>(client: &mut TdClient, chat_id: ChatId, updater: F) pub fn update_chat<F>(client: &mut TdClient, chat_id: ChatId, updater: F)
where where
F: FnOnce(&mut ChatInfo), F: FnOnce(&mut ChatInfo),
{ {
if let Some(chat) = find_chat_mut(client, chat_id) { client.update_chat(chat_id, updater);
updater(chat);
}
} }
/// Добавляет новый чат или обновляет существующий /// Добавляет новый чат или обновляет существующий
@@ -33,9 +26,7 @@ pub fn add_or_update_chat(client: &mut TdClient, td_chat_enum: &TdChat) {
// Пропускаем удалённые аккаунты // Пропускаем удалённые аккаунты
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() { if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
// Удаляем из списка если уже был добавлен // Удаляем из списка если уже был добавлен
client client.remove_chat(ChatId::new(td_chat.id));
.chats_mut()
.retain(|c| c.id != ChatId::new(td_chat.id));
return; return;
} }
@@ -61,22 +52,23 @@ pub fn add_or_update_chat(client: &mut TdClient, td_chat_enum: &TdChat) {
ChatType::Private(private) => { ChatType::Private(private) => {
// Ограничиваем размер chat_user_ids // Ограничиваем размер chat_user_ids
let chat_id = ChatId::new(td_chat.id); let chat_id = ChatId::new(td_chat.id);
if client.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS let user_id = UserId::new(private.user_id);
&& !client.user_cache.chat_user_ids.contains_key(&chat_id) client.update_user_cache(|cache| {
if cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS
&& !cache.chat_user_ids.contains_key(&chat_id)
{ {
// Удаляем случайную запись (первую найденную) // Удаляем случайную запись (первую найденную)
if let Some(&key) = client.user_cache.chat_user_ids.keys().next() { if let Some(&key) = cache.chat_user_ids.keys().next() {
client.user_cache.chat_user_ids.remove(&key); cache.chat_user_ids.remove(&key);
} }
} }
let user_id = UserId::new(private.user_id); cache.chat_user_ids.insert(chat_id, user_id);
client.user_cache.chat_user_ids.insert(chat_id, user_id);
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU) // Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
client cache
.user_cache
.user_usernames .user_usernames
.peek(&user_id) .peek(&user_id)
.map(|u| format!("@{}", u)) .map(|u| format!("@{}", u))
})
} }
_ => None, _ => None,
}; };
@@ -110,44 +102,35 @@ pub fn add_or_update_chat(client: &mut TdClient, td_chat_enum: &TdChat) {
draft_text: None, draft_text: None,
}; };
if let Some(existing) = find_chat_mut(client, ChatId::new(td_chat.id)) { let chat_info_for_update = chat_info.clone();
existing.title = chat_info.title; let updated_existing = client.update_chat(ChatId::new(td_chat.id), |existing| {
existing.last_message = chat_info.last_message; existing.title = chat_info_for_update.title;
existing.last_message_date = chat_info.last_message_date; existing.last_message = chat_info_for_update.last_message;
existing.unread_count = chat_info.unread_count; existing.last_message_date = chat_info_for_update.last_message_date;
existing.unread_mention_count = chat_info.unread_mention_count; existing.unread_count = chat_info_for_update.unread_count;
existing.last_read_outbox_message_id = chat_info.last_read_outbox_message_id; existing.unread_mention_count = chat_info_for_update.unread_mention_count;
existing.folder_ids = chat_info.folder_ids; existing.last_read_outbox_message_id = chat_info_for_update.last_read_outbox_message_id;
existing.is_muted = chat_info.is_muted; existing.folder_ids = chat_info_for_update.folder_ids;
existing.is_muted = chat_info_for_update.is_muted;
// Обновляем username если он появился // Обновляем username если он появился
if let Some(username) = chat_info.username { if let Some(username) = chat_info_for_update.username {
existing.username = Some(username); existing.username = Some(username);
} }
// Обновляем позицию только если она пришла // Обновляем позицию только если она пришла
if main_position.is_some() { if main_position.is_some() {
existing.is_pinned = chat_info.is_pinned; existing.is_pinned = chat_info_for_update.is_pinned;
existing.order = chat_info.order; existing.order = chat_info_for_update.order;
} }
} else { });
client.chats_mut().push(chat_info);
if !updated_existing {
client.push_chat(chat_info);
// Ограничиваем количество чатов // Ограничиваем количество чатов
if client.chats_mut().len() > MAX_CHATS { client.trim_chats_to_max_by_order(MAX_CHATS);
// Удаляем чат с наименьшим order (наименее активный)
let Some(min_idx) = client
.chats()
.iter()
.enumerate()
.min_by_key(|(_, c)| c.order)
.map(|(i, _)| i)
else {
return; // Нет чатов для удаления (не должно произойти)
};
client.chats_mut().remove(min_idx);
}
} }
// Сортируем чаты по order (TDLib order учитывает pinned и время) // Сортируем чаты по order (TDLib order учитывает pinned и время)
client.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); client.sort_chats_by_order();
} }

View File

@@ -105,7 +105,8 @@ impl TdClient {
self.notification_manager.set_enabled(config.enabled); self.notification_manager.set_enabled(config.enabled);
self.notification_manager self.notification_manager
.set_only_mentions(config.only_mentions); .set_only_mentions(config.only_mentions);
self.notification_manager.set_show_preview(config.show_preview); self.notification_manager
.set_show_preview(config.show_preview);
self.notification_manager.set_timeout(config.timeout_ms); self.notification_manager.set_timeout(config.timeout_ms);
self.notification_manager self.notification_manager
.set_urgency(config.urgency.clone()); .set_urgency(config.urgency.clone());
@@ -433,24 +434,117 @@ impl TdClient {
&self.chat_manager.chats &self.chat_manager.chats
} }
pub fn chats_mut(&mut self) -> &mut Vec<ChatInfo> { pub fn update_chats<F, R>(&mut self, updater: F) -> R
&mut self.chat_manager.chats where
F: FnOnce(&mut Vec<ChatInfo>) -> R,
{
updater(&mut self.chat_manager.chats)
}
pub fn update_chat<F>(&mut self, chat_id: ChatId, updater: F) -> bool
where
F: FnOnce(&mut ChatInfo),
{
let Some(chat) = self.chat_manager.chats.iter_mut().find(|c| c.id == chat_id) else {
return false;
};
updater(chat);
true
}
pub fn remove_chat(&mut self, chat_id: ChatId) {
self.chat_manager.chats.retain(|c| c.id != chat_id);
}
pub fn push_chat(&mut self, chat: ChatInfo) {
self.chat_manager.chats.push(chat);
}
pub fn trim_chats_to_max_by_order(&mut self, max_chats: usize) {
if self.chat_manager.chats.len() <= max_chats {
return;
}
let Some(min_idx) = self
.chat_manager
.chats
.iter()
.enumerate()
.min_by_key(|(_, chat)| chat.order)
.map(|(idx, _)| idx)
else {
return;
};
self.chat_manager.chats.remove(min_idx);
}
pub fn sort_chats_by_order(&mut self) {
self.chat_manager
.chats
.sort_by(|a, b| b.order.cmp(&a.order));
} }
pub fn folders(&self) -> &[FolderInfo] { pub fn folders(&self) -> &[FolderInfo] {
&self.chat_manager.folders &self.chat_manager.folders
} }
pub fn folders_mut(&mut self) -> &mut Vec<FolderInfo> { pub fn update_folders<F, R>(&mut self, updater: F) -> R
&mut self.chat_manager.folders where
F: FnOnce(&mut Vec<FolderInfo>) -> R,
{
updater(&mut self.chat_manager.folders)
}
pub fn set_folders(&mut self, folders: Vec<FolderInfo>) {
self.chat_manager.folders = folders;
} }
pub fn current_chat_messages(&self) -> &[MessageInfo] { pub fn current_chat_messages(&self) -> &[MessageInfo] {
&self.message_manager.current_chat_messages &self.message_manager.current_chat_messages
} }
pub fn current_chat_messages_mut(&mut self) -> &mut Vec<MessageInfo> { pub fn clear_current_chat_messages(&mut self) {
&mut self.message_manager.current_chat_messages self.message_manager.current_chat_messages.clear();
}
pub fn set_current_chat_messages(&mut self, messages: Vec<MessageInfo>) {
self.message_manager.current_chat_messages = messages;
}
pub fn update_current_chat_messages<F, R>(&mut self, updater: F) -> R
where
F: FnOnce(&mut Vec<MessageInfo>) -> R,
{
updater(&mut self.message_manager.current_chat_messages)
}
pub fn update_current_chat_message<F>(&mut self, message_id: MessageId, updater: F) -> bool
where
F: FnOnce(&mut MessageInfo),
{
let Some(message) = self
.message_manager
.current_chat_messages
.iter_mut()
.find(|message| message.id() == message_id)
else {
return false;
};
updater(message);
true
}
pub fn replace_current_chat_message(
&mut self,
message_id: MessageId,
new_message: MessageInfo,
) -> bool {
self.update_current_chat_message(message_id, |message| {
*message = new_message;
})
} }
pub fn current_chat_id(&self) -> Option<ChatId> { pub fn current_chat_id(&self) -> Option<ChatId> {
@@ -498,8 +592,10 @@ impl TdClient {
&self.user_cache.pending_user_ids &self.user_cache.pending_user_ids
} }
pub fn pending_user_ids_mut(&mut self) -> &mut Vec<crate::types::UserId> { pub fn queue_pending_user_id(&mut self, user_id: crate::types::UserId) {
&mut self.user_cache.pending_user_ids if !self.user_cache.pending_user_ids.contains(&user_id) {
self.user_cache.pending_user_ids.push(user_id);
}
} }
pub fn main_chat_list_position(&self) -> i32 { pub fn main_chat_list_position(&self) -> i32 {
@@ -515,8 +611,11 @@ impl TdClient {
&self.user_cache &self.user_cache
} }
pub fn user_cache_mut(&mut self) -> &mut UserCache { pub fn update_user_cache<F, R>(&mut self, updater: F) -> R
&mut self.user_cache where
F: FnOnce(&mut UserCache) -> R,
{
updater(&mut self.user_cache)
} }
// ==================== Helper методы для упрощения обработки updates ==================== // ==================== Helper методы для упрощения обработки updates ====================
@@ -558,7 +657,7 @@ impl TdClient {
} }
// Пересортируем по order // Пересортируем по order
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); self.sort_chats_by_order();
} }
Update::ChatReadInbox(update) => { Update::ChatReadInbox(update) => {
crate::tdlib::chat_helpers::update_chat( crate::tdlib::chat_helpers::update_chat(
@@ -600,11 +699,13 @@ impl TdClient {
); );
// Если это текущий открытый чат — обновляем is_read у сообщений // Если это текущий открытый чат — обновляем is_read у сообщений
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() { if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
for msg in self.current_chat_messages_mut().iter_mut() { self.update_current_chat_messages(|messages| {
for msg in messages {
if msg.is_outgoing() && msg.id() <= last_read_msg_id { if msg.is_outgoing() && msg.id() <= last_read_msg_id {
msg.state.is_read = true; msg.state.is_read = true;
} }
} }
});
} }
} }
Update::ChatPosition(update) => { Update::ChatPosition(update) => {
@@ -618,11 +719,13 @@ impl TdClient {
} }
Update::ChatFolders(update) => { Update::ChatFolders(update) => {
// Обновляем список папок // Обновляем список папок
*self.folders_mut() = update self.set_folders(
update
.chat_folders .chat_folders
.into_iter() .into_iter()
.map(|f| FolderInfo { id: f.id, name: f.title }) .map(|f| FolderInfo { id: f.id, name: f.title })
.collect(); .collect(),
);
self.set_main_chat_list_position(update.main_chat_list_position); self.set_main_chat_list_position(update.main_chat_list_position);
} }
Update::UserStatus(update) => { Update::UserStatus(update) => {
@@ -635,9 +738,11 @@ impl TdClient {
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth, UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
UserStatus::Empty => UserOnlineStatus::LongTimeAgo, UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
}; };
self.user_cache self.update_user_cache(|cache| {
cache
.user_statuses .user_statuses
.insert(UserId::new(update.user_id), status); .insert(UserId::new(update.user_id), status);
});
} }
Update::ConnectionState(update) => { Update::ConnectionState(update) => {
// Обновляем состояние сетевого соединения // Обновляем состояние сетевого соединения

View File

@@ -70,14 +70,14 @@ impl ChatClient for TdClient {
where where
F: FnOnce(&mut Vec<ChatInfo>), F: FnOnce(&mut Vec<ChatInfo>),
{ {
updater(self.chats_mut()); TdClient::update_chats(self, updater);
} }
fn update_folders<F>(&mut self, updater: F) fn update_folders<F>(&mut self, updater: F)
where where
F: FnOnce(&mut Vec<FolderInfo>), F: FnOnce(&mut Vec<FolderInfo>),
{ {
updater(self.folders_mut()); TdClient::update_folders(self, updater);
} }
} }
@@ -204,18 +204,18 @@ impl MessageClient for TdClient {
} }
fn clear_current_chat_messages(&mut self) { fn clear_current_chat_messages(&mut self) {
self.current_chat_messages_mut().clear() TdClient::clear_current_chat_messages(self)
} }
fn set_current_chat_messages(&mut self, messages: Vec<MessageInfo>) { fn set_current_chat_messages(&mut self, messages: Vec<MessageInfo>) {
*self.current_chat_messages_mut() = messages; TdClient::set_current_chat_messages(self, messages);
} }
fn update_current_chat_messages<F>(&mut self, updater: F) fn update_current_chat_messages<F>(&mut self, updater: F)
where where
F: FnOnce(&mut Vec<MessageInfo>), F: FnOnce(&mut Vec<MessageInfo>),
{ {
updater(self.current_chat_messages_mut()); TdClient::update_current_chat_messages(self, updater);
} }
fn set_current_chat_id(&mut self, chat_id: Option<ChatId>) { fn set_current_chat_id(&mut self, chat_id: Option<ChatId>) {
@@ -253,7 +253,7 @@ impl UserClient for TdClient {
where where
F: FnOnce(&mut UserCache), F: FnOnce(&mut UserCache),
{ {
updater(self.user_cache_mut()); TdClient::update_user_cache(self, updater);
} }
async fn process_pending_user_ids(&mut self) { async fn process_pending_user_ids(&mut self) {

View File

@@ -23,9 +23,7 @@ pub fn convert_message(client: &mut TdClient, message: &TdMessage, chat_id: Chat
.cloned() .cloned()
.unwrap_or_else(|| { .unwrap_or_else(|| {
// Добавляем в очередь для загрузки // Добавляем в очередь для загрузки
if !client.pending_user_ids().contains(&user_id) { client.queue_pending_user_id(user_id);
client.pending_user_ids_mut().push(user_id);
}
format!("User_{}", user_id.as_i64()) format!("User_{}", user_id.as_i64())
}) })
} }
@@ -210,7 +208,8 @@ pub fn update_reply_info_from_loaded_messages(client: &mut TdClient) {
.collect(); .collect();
// Обновляем reply_to для сообщений с неполными данными // Обновляем reply_to для сообщений с неполными данными
for msg in client.current_chat_messages_mut().iter_mut() { client.update_current_chat_messages(|messages| {
for msg in messages {
let Some(ref mut reply) = msg.interactions.reply_to else { let Some(ref mut reply) = msg.interactions.reply_to else {
continue; continue;
}; };
@@ -231,4 +230,5 @@ pub fn update_reply_info_from_loaded_messages(client: &mut TdClient) {
reply.text = content.clone(); reply.text = content.clone();
} }
} }
});
} }

View File

@@ -54,17 +54,19 @@ pub fn handle_new_message_update(client: &mut TdClient, new_msg: UpdateNewMessag
Some(idx) => { Some(idx) => {
// Сообщение уже есть - обновляем // Сообщение уже есть - обновляем
if is_incoming { if is_incoming {
client.current_chat_messages_mut()[idx] = msg_info; client.replace_current_chat_message(msg_id, msg_info);
} else { } else {
// Для исходящих: обновляем can_be_edited и другие поля, // Для исходящих: обновляем can_be_edited и другие поля,
// но сохраняем reply_to (добавленный при отправке) // но сохраняем reply_to (добавленный при отправке)
let existing = &mut client.current_chat_messages_mut()[idx]; client.update_current_chat_messages(|messages| {
let existing = &mut messages[idx];
existing.state.can_be_edited = msg_info.state.can_be_edited; existing.state.can_be_edited = msg_info.state.can_be_edited;
existing.state.can_be_deleted_only_for_self = existing.state.can_be_deleted_only_for_self =
msg_info.state.can_be_deleted_only_for_self; msg_info.state.can_be_deleted_only_for_self;
existing.state.can_be_deleted_for_all_users = existing.state.can_be_deleted_for_all_users =
msg_info.state.can_be_deleted_for_all_users; msg_info.state.can_be_deleted_for_all_users;
existing.state.is_read = msg_info.state.is_read; existing.state.is_read = msg_info.state.is_read;
});
} }
} }
None => { None => {
@@ -122,7 +124,7 @@ pub fn handle_chat_position_update(client: &mut TdClient, update: UpdateChatPosi
ChatList::Main => { ChatList::Main => {
if update.position.order == 0 { if update.position.order == 0 {
// Чат больше не в Main (перемещён в архив и т.д.) // Чат больше не в Main (перемещён в архив и т.д.)
client.chats_mut().retain(|c| c.id != chat_id); client.remove_chat(chat_id);
} else { } else {
// Обновляем позицию существующего чата // Обновляем позицию существующего чата
crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| { crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| {
@@ -131,7 +133,7 @@ pub fn handle_chat_position_update(client: &mut TdClient, update: UpdateChatPosi
}); });
} }
// Пересортируем по order // Пересортируем по order
client.chats_mut().sort_by(|a, b| b.order.cmp(&a.order)); client.sort_chats_by_order();
} }
ChatList::Folder(folder) => { ChatList::Folder(folder) => {
// Обновляем folder_ids для чата // Обновляем folder_ids для чата
@@ -166,10 +168,10 @@ pub fn handle_user_update(client: &mut TdClient, update: UpdateUser) {
// Удаляем чаты с этим пользователем из списка // Удаляем чаты с этим пользователем из списка
let user_id = user.id; let user_id = user.id;
// Clone chat_user_ids to avoid borrow conflict // Clone chat_user_ids to avoid borrow conflict
let chat_user_ids = client.user_cache.chat_user_ids.clone(); let chat_user_ids = client.user_cache().chat_user_ids.clone();
client client.update_chats(|chats| {
.chats_mut() chats.retain(|c| chat_user_ids.get(&c.id) != Some(&UserId::new(user_id)));
.retain(|c| chat_user_ids.get(&c.id) != Some(&UserId::new(user_id))); });
return; return;
} }
@@ -179,10 +181,9 @@ pub fn handle_user_update(client: &mut TdClient, update: UpdateUser) {
} else { } else {
format!("{} {}", user.first_name, user.last_name) format!("{} {}", user.first_name, user.last_name)
}; };
client client.update_user_cache(|cache| {
.user_cache cache.user_names.insert(UserId::new(user.id), display_name);
.user_names });
.insert(UserId::new(user.id), display_name);
// Сохраняем username если есть (с упрощённым извлечением через and_then) // Сохраняем username если есть (с упрощённым извлечением через and_then)
if let Some(username) = user if let Some(username) = user
@@ -190,19 +191,25 @@ pub fn handle_user_update(client: &mut TdClient, update: UpdateUser) {
.as_ref() .as_ref()
.and_then(|u| u.active_usernames.first()) .and_then(|u| u.active_usernames.first())
{ {
client let affected_chat_ids = client.update_user_cache(|cache| {
.user_cache cache
.user_usernames .user_usernames
.insert(UserId::new(user.id), username.to_string()); .insert(UserId::new(user.id), username.to_string());
cache
.chat_user_ids
.iter()
.filter_map(|(&chat_id, &user_id)| {
(user_id == UserId::new(user.id)).then_some(chat_id)
})
.collect::<Vec<_>>()
});
// Обновляем username в чатах, связанных с этим пользователем // Обновляем username в чатах, связанных с этим пользователем
for (&chat_id, &user_id) in &client.user_cache.chat_user_ids.clone() { for chat_id in affected_chat_ids {
if user_id == UserId::new(user.id) {
crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| { crate::tdlib::chat_helpers::update_chat(client, chat_id, |chat| {
chat.username = Some(format!("@{}", username)); chat.username = Some(format!("@{}", username));
}); });
} }
} }
}
// LRU-кэш автоматически удаляет старые записи при вставке // LRU-кэш автоматически удаляет старые записи при вставке
} }
@@ -218,16 +225,8 @@ pub fn handle_message_interaction_info_update(
return; return;
} }
let Some(msg) = client
.current_chat_messages_mut()
.iter_mut()
.find(|m| m.id() == MessageId::new(update.message_id))
else {
return;
};
// Извлекаем реакции из interaction_info // Извлекаем реакции из interaction_info
msg.interactions.reactions = update let reactions = update
.interaction_info .interaction_info
.as_ref() .as_ref()
.and_then(|info| info.reactions.as_ref()) .and_then(|info| info.reactions.as_ref())
@@ -250,6 +249,9 @@ pub fn handle_message_interaction_info_update(
.collect() .collect()
}) })
.unwrap_or_default(); .unwrap_or_default();
client.update_current_chat_message(MessageId::new(update.message_id), |msg| {
msg.interactions.reactions = reactions;
});
} }
/// Обрабатывает Update::MessageSendSucceeded - успешная отправка сообщения. /// Обрабатывает Update::MessageSendSucceeded - успешная отправка сообщения.
@@ -291,7 +293,7 @@ pub fn handle_message_send_succeeded_update(
} }
// Заменяем старое сообщение на новое // Заменяем старое сообщение на новое
client.current_chat_messages_mut()[idx] = new_msg; client.replace_current_chat_message(old_id, new_msg);
} }
/// Обрабатывает Update::ChatDraftMessage - обновление черновика сообщения в чате. /// Обрабатывает Update::ChatDraftMessage - обновление черновика сообщения в чате.