use crate::app::App; use crate::tdlib::ChatAction; use crossterm::event::{KeyCode, KeyEvent, KeyModifiers}; use std::time::{Duration, Instant}; use tokio::time::timeout; pub async fn handle(app: &mut App, key: KeyEvent) { let has_ctrl = key.modifiers.contains(KeyModifiers::CONTROL); // Глобальные команды (работают всегда) match key.code { KeyCode::Char('r') if has_ctrl => { app.status_message = Some("Обновление чатов...".to_string()); let _ = timeout(Duration::from_secs(5), app.td_client.load_chats(50)).await; app.status_message = None; return; } KeyCode::Char('s') if has_ctrl => { // Ctrl+S - начать поиск (только если чат не открыт) if app.selected_chat_id.is_none() { app.start_search(); } return; } KeyCode::Char('p') if has_ctrl => { // Ctrl+P - режим просмотра закреплённых сообщений if app.selected_chat_id.is_some() && !app.is_pinned_mode() { if let Some(chat_id) = app.get_selected_chat_id() { app.status_message = Some("Загрузка закреплённых...".to_string()); match timeout( Duration::from_secs(5), app.td_client.get_pinned_messages(chat_id), ) .await { Ok(Ok(messages)) => { if messages.is_empty() { app.status_message = Some("Нет закреплённых сообщений".to_string()); } else { app.enter_pinned_mode(messages); app.status_message = None; } } Ok(Err(e)) => { app.error_message = Some(e); app.status_message = None; } Err(_) => { app.error_message = Some("Таймаут загрузки".to_string()); app.status_message = None; } } } } return; } KeyCode::Char('f') if has_ctrl => { // Ctrl+F - поиск по сообщениям в открытом чате if app.selected_chat_id.is_some() && !app.is_pinned_mode() && !app.is_message_search_mode() { app.enter_message_search_mode(); } return; } _ => {} } // Режим профиля if app.is_profile_mode() { // Обработка подтверждения выхода из группы let confirmation_step = app.get_leave_group_confirmation_step(); if confirmation_step > 0 { match key.code { KeyCode::Char('y') | KeyCode::Char('н') | KeyCode::Enter => { if confirmation_step == 1 { // Первое подтверждение - показываем второе app.show_leave_group_final_confirmation(); } else if confirmation_step == 2 { // Второе подтверждение - выходим из группы if let Some(chat_id) = app.selected_chat_id { let leave_result = app.td_client.leave_chat(chat_id).await; match leave_result { Ok(_) => { app.status_message = Some("Вы вышли из группы".to_string()); app.exit_profile_mode(); app.close_chat(); } Err(e) => { app.error_message = Some(e); app.cancel_leave_group(); } } } } } KeyCode::Char('n') | KeyCode::Char('т') | KeyCode::Esc => { // Отмена app.cancel_leave_group(); } _ => {} } return; } // Обычная навигация по профилю match key.code { KeyCode::Esc => { app.exit_profile_mode(); } KeyCode::Up => { app.select_previous_profile_action(); } KeyCode::Down => { if let Some(profile) = app.get_profile_info() { let max_actions = get_available_actions_count(profile); app.select_next_profile_action(max_actions); } } KeyCode::Enter => { // Выполнить выбранное действие if let Some(profile) = app.get_profile_info() { let actions = get_available_actions_count(profile); let action_index = app.get_selected_profile_action().unwrap_or(0); if action_index < actions { // Определяем какое действие выбрано let mut current_idx = 0; // Действие: Открыть в браузере if profile.username.is_some() { if action_index == current_idx { if let Some(username) = &profile.username { let url = format!( "https://t.me/{}", username.trim_start_matches('@') ); match open::that(&url) { Ok(_) => { app.status_message = Some(format!("Открыто: {}", url)); } Err(e) => { app.error_message = Some(format!("Ошибка открытия браузера: {}", e)); } } } return; } current_idx += 1; } // Действие: Скопировать ID if action_index == current_idx { app.status_message = Some(format!("ID скопирован: {}", profile.chat_id)); return; } current_idx += 1; // Действие: Покинуть группу if profile.is_group && action_index == current_idx { app.show_leave_group_confirmation(); } } } } _ => {} } return; } // Режим поиска по сообщениям if app.is_message_search_mode() { match key.code { KeyCode::Esc => { app.exit_message_search_mode(); } KeyCode::Up | KeyCode::Char('N') => { app.select_previous_search_result(); } KeyCode::Down | KeyCode::Char('n') => { app.select_next_search_result(); } KeyCode::Enter => { // Перейти к выбранному сообщению if let Some(msg_id) = app.get_selected_search_result_id() { let msg_index = app .td_client .current_chat_messages() .iter() .position(|m| m.id == msg_id); if let Some(idx) = msg_index { let total = app.td_client.current_chat_messages().len(); app.message_scroll_offset = total.saturating_sub(idx + 5); } app.exit_message_search_mode(); } } KeyCode::Backspace => { // Удаляем символ из запроса if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { query.pop(); app.update_search_query(query.clone()); // Выполняем поиск при изменении запроса if let Some(chat_id) = app.get_selected_chat_id() { if !query.is_empty() { if let Ok(Ok(results)) = timeout( Duration::from_secs(3), app.td_client.search_messages(chat_id, &query), ) .await { app.set_search_results(results); } } else { app.set_search_results(Vec::new()); } } } } KeyCode::Char(c) => { // Добавляем символ к запросу if let Some(mut query) = app.get_search_query().map(|s| s.to_string()) { query.push(c); app.update_search_query(query.clone()); // Выполняем поиск при изменении запроса if let Some(chat_id) = app.get_selected_chat_id() { if let Ok(Ok(results)) = timeout( Duration::from_secs(3), app.td_client.search_messages(chat_id, &query), ) .await { app.set_search_results(results); } } } } _ => {} } return; } // Режим просмотра закреплённых сообщений if app.is_pinned_mode() { match key.code { KeyCode::Esc => { app.exit_pinned_mode(); } KeyCode::Up => { app.select_previous_pinned(); } KeyCode::Down => { app.select_next_pinned(); } KeyCode::Enter => { // Перейти к сообщению в истории if let Some(msg_id) = app.get_selected_pinned_id() { // Ищем индекс сообщения в текущей истории let msg_index = app .td_client .current_chat_messages() .iter() .position(|m| m.id == msg_id); if let Some(idx) = msg_index { // Вычисляем scroll offset чтобы показать сообщение let total = app.td_client.current_chat_messages().len(); app.message_scroll_offset = total.saturating_sub(idx + 5); } app.exit_pinned_mode(); } } _ => {} } return; } // Обработка ввода в режиме выбора реакции if app.is_reaction_picker_mode() { match key.code { KeyCode::Left => { app.select_previous_reaction(); app.needs_redraw = true; } KeyCode::Right => { app.select_next_reaction(); app.needs_redraw = true; } KeyCode::Up => { // Переход на ряд выше (8 эмодзи в ряду) if let crate::app::ChatState::ReactionPicker { selected_index, .. } = &mut app.chat_state { if *selected_index >= 8 { *selected_index = selected_index.saturating_sub(8); app.needs_redraw = true; } } } KeyCode::Down => { // Переход на ряд ниже (8 эмодзи в ряду) if let crate::app::ChatState::ReactionPicker { selected_index, available_reactions, .. } = &mut app.chat_state { let new_index = *selected_index + 8; if new_index < available_reactions.len() { *selected_index = new_index; app.needs_redraw = true; } } } KeyCode::Enter => { // Добавить/убрать реакцию if let Some(emoji) = app.get_selected_reaction().cloned() { if let Some(message_id) = app.get_selected_message_for_reaction() { if let Some(chat_id) = app.selected_chat_id { app.status_message = Some("Отправка реакции...".to_string()); app.needs_redraw = true; match timeout( Duration::from_secs(5), app.td_client .toggle_reaction(chat_id, message_id, emoji.clone()), ) .await { Ok(Ok(_)) => { app.status_message = Some(format!("Реакция {} добавлена", emoji)); app.exit_reaction_picker_mode(); app.needs_redraw = true; } Ok(Err(e)) => { app.error_message = Some(format!("Ошибка: {}", e)); app.status_message = None; app.needs_redraw = true; } Err(_) => { app.error_message = Some("Таймаут отправки реакции".to_string()); app.status_message = None; app.needs_redraw = true; } } } } } } KeyCode::Esc => { app.exit_reaction_picker_mode(); app.needs_redraw = true; } _ => {} } return; } // Модалка подтверждения удаления if app.is_confirm_delete_shown() { match key.code { KeyCode::Char('y') | KeyCode::Char('н') | KeyCode::Enter => { // Подтверждение удаления if let Some(msg_id) = app.chat_state.selected_message_id() { if let Some(chat_id) = app.get_selected_chat_id() { // Находим сообщение для проверки can_be_deleted_for_all_users let can_delete_for_all = app .td_client .current_chat_messages() .iter() .find(|m| m.id == msg_id) .map(|m| m.can_be_deleted_for_all_users) .unwrap_or(false); match timeout( Duration::from_secs(5), app.td_client.delete_messages( chat_id, vec![msg_id], can_delete_for_all, ), ) .await { Ok(Ok(_)) => { // Удаляем из локального списка app.td_client .current_chat_messages_mut() .retain(|m| m.id != msg_id); // Сбрасываем состояние app.chat_state = crate::app::ChatState::Normal; } Ok(Err(e)) => { app.error_message = Some(e); } Err(_) => { app.error_message = Some("Таймаут удаления".to_string()); } } } } // Закрываем модалку app.chat_state = crate::app::ChatState::Normal; } KeyCode::Char('n') | KeyCode::Char('т') | KeyCode::Esc => { // Отмена удаления app.chat_state = crate::app::ChatState::Normal; } _ => {} } return; } // Режим выбора чата для пересылки if app.is_forwarding() { match key.code { KeyCode::Esc => { app.cancel_forward(); } KeyCode::Enter => { // Выбираем чат и пересылаем сообщение let filtered = app.get_filtered_chats(); if let Some(i) = app.chat_list_state.selected() { if let Some(chat) = filtered.get(i) { let to_chat_id = chat.id; if let Some(msg_id) = app.chat_state.selected_message_id() { if let Some(from_chat_id) = app.get_selected_chat_id() { match timeout( Duration::from_secs(5), app.td_client.forward_messages( to_chat_id, from_chat_id, vec![msg_id], ), ) .await { Ok(Ok(_)) => { app.status_message = Some("Сообщение переслано".to_string()); } Ok(Err(e)) => { app.error_message = Some(e); } Err(_) => { app.error_message = Some("Таймаут пересылки".to_string()); } } } } } } app.cancel_forward(); } KeyCode::Down => { app.next_chat(); } KeyCode::Up => { app.previous_chat(); } _ => {} } return; } // Режим поиска if app.is_searching { match key.code { KeyCode::Esc => { app.cancel_search(); } KeyCode::Enter => { // Выбрать чат из отфильтрованного списка app.select_filtered_chat(); if let Some(chat_id) = app.get_selected_chat_id() { app.status_message = Some("Загрузка сообщений...".to_string()); app.message_scroll_offset = 0; match timeout( Duration::from_secs(10), app.td_client.get_chat_history(chat_id, 100), ) .await { Ok(Ok(_)) => { // Загружаем недостающие reply info let _ = timeout( Duration::from_secs(5), app.td_client.fetch_missing_reply_info(), ) .await; // Загружаем последнее закреплённое сообщение let _ = timeout( Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id), ) .await; // Загружаем черновик app.load_draft(); app.status_message = None; } Ok(Err(e)) => { app.error_message = Some(e); app.status_message = None; } Err(_) => { app.error_message = Some("Таймаут загрузки сообщений".to_string()); app.status_message = None; } } } } KeyCode::Backspace => { app.search_query.pop(); // Сбрасываем выделение при изменении запроса app.chat_list_state.select(Some(0)); } KeyCode::Down => { app.next_filtered_chat(); } KeyCode::Up => { app.previous_filtered_chat(); } KeyCode::Char(c) => { app.search_query.push(c); // Сбрасываем выделение при изменении запроса app.chat_list_state.select(Some(0)); } _ => {} } return; } // Enter - открыть чат, отправить сообщение или редактировать if key.code == KeyCode::Enter { if app.selected_chat_id.is_some() { // Режим выбора сообщения if app.is_selecting_message() { // Начать редактирование выбранного сообщения if app.start_editing_selected() { // Редактирование начато } else { // Нельзя редактировать это сообщение app.chat_state = crate::app::ChatState::Normal; } return; } // Отправка или редактирование сообщения if !app.message_input.is_empty() { if let Some(chat_id) = app.get_selected_chat_id() { let text = app.message_input.clone(); if let Some(msg_id) = app.chat_state.selected_message_id() { if app.is_editing() { // Режим редактирования app.message_input.clear(); app.cursor_position = 0; app.chat_state = crate::app::ChatState::Normal; match timeout( Duration::from_secs(5), app.td_client.edit_message(chat_id, msg_id, text), ) .await { Ok(Ok(edited_msg)) => { // Обновляем сообщение в списке if let Some(msg) = app .td_client .current_chat_messages_mut() .iter_mut() .find(|m| m.id == msg_id) { msg.content = edited_msg.content; msg.entities = edited_msg.entities; msg.edit_date = edited_msg.edit_date; } } Ok(Err(e)) => { app.error_message = Some(e); } Err(_) => { app.error_message = Some("Таймаут редактирования".to_string()); } } } } else { // Обычная отправка (или reply) let reply_to_id = if app.is_replying() { app.chat_state.selected_message_id() } else { None }; // Создаём ReplyInfo ДО отправки, пока сообщение точно доступно let reply_info = app.get_replying_to_message().map(|m| { crate::tdlib::ReplyInfo { message_id: m.id, sender_name: m.sender_name.clone(), text: m.content.clone(), } }); app.message_input.clear(); app.cursor_position = 0; // Сбрасываем режим reply если он был активен if app.is_replying() { app.chat_state = crate::app::ChatState::Normal; } app.last_typing_sent = None; // Отменяем typing status app.td_client .send_chat_action(chat_id, ChatAction::Cancel) .await; match timeout( Duration::from_secs(5), app.td_client .send_message(chat_id, text, reply_to_id, reply_info), ) .await { Ok(Ok(sent_msg)) => { // Добавляем отправленное сообщение в список (с лимитом) app.td_client.push_message(sent_msg); // Сбрасываем скролл чтобы видеть новое сообщение app.message_scroll_offset = 0; } Ok(Err(e)) => { app.error_message = Some(e); } Err(_) => { app.error_message = Some("Таймаут отправки".to_string()); } } } } } } else { // Открываем чат let prev_selected = app.selected_chat_id; app.select_current_chat(); if app.selected_chat_id != prev_selected { if let Some(chat_id) = app.get_selected_chat_id() { app.status_message = Some("Загрузка сообщений...".to_string()); app.message_scroll_offset = 0; match timeout( Duration::from_secs(10), app.td_client.get_chat_history(chat_id, 100), ) .await { Ok(Ok(_)) => { // Загружаем недостающие reply info let _ = timeout( Duration::from_secs(5), app.td_client.fetch_missing_reply_info(), ) .await; // Загружаем последнее закреплённое сообщение let _ = timeout( Duration::from_secs(2), app.td_client.load_current_pinned_message(chat_id), ) .await; // Загружаем черновик app.load_draft(); app.status_message = None; } Ok(Err(e)) => { app.error_message = Some(e); app.status_message = None; } Err(_) => { app.error_message = Some("Таймаут загрузки сообщений".to_string()); app.status_message = None; } } } } } return; } // Esc - отменить выбор/редактирование/reply или закрыть чат if key.code == KeyCode::Esc { if app.is_selecting_message() { // Отменить выбор сообщения app.chat_state = crate::app::ChatState::Normal; } else if app.is_editing() { // Отменить редактирование app.cancel_editing(); } else if app.is_replying() { // Отменить режим ответа app.cancel_reply(); } else if app.selected_chat_id.is_some() { // Сохраняем черновик если есть текст в инпуте if let Some(chat_id) = app.selected_chat_id { if !app.message_input.is_empty() && !app.is_editing() && !app.is_replying() { let draft_text = app.message_input.clone(); let _ = app.td_client.set_draft_message(chat_id, draft_text).await; } else if app.message_input.is_empty() { // Очищаем черновик если инпут пустой let _ = app .td_client .set_draft_message(chat_id, String::new()) .await; } } app.close_chat(); } return; } // Режим открытого чата if app.selected_chat_id.is_some() { // Режим выбора сообщения для редактирования/удаления if app.is_selecting_message() { match key.code { KeyCode::Up => { app.select_previous_message(); } KeyCode::Down => { app.select_next_message(); // Если вышли из режима выбора (индекс стал None), ничего не делаем } KeyCode::Char('d') | KeyCode::Char('в') | KeyCode::Delete => { // Показать модалку подтверждения удаления if let Some(msg) = app.get_selected_message() { let can_delete = msg.can_be_deleted_only_for_self || msg.can_be_deleted_for_all_users; if can_delete { app.chat_state = crate::app::ChatState::DeleteConfirmation { message_id: msg.id, }; } } } KeyCode::Char('r') | KeyCode::Char('к') => { // Начать режим ответа на выбранное сообщение app.start_reply_to_selected(); } KeyCode::Char('f') | KeyCode::Char('а') => { // Начать режим пересылки app.start_forward_selected(); } KeyCode::Char('y') | KeyCode::Char('н') => { // Копировать сообщение if let Some(msg) = app.get_selected_message() { let text = format_message_for_clipboard(msg); match copy_to_clipboard(&text) { Ok(_) => { app.status_message = Some("Сообщение скопировано".to_string()); } Err(e) => { app.error_message = Some(format!("Ошибка копирования: {}", e)); } } } } KeyCode::Char('e') | KeyCode::Char('у') => { // Открыть emoji picker для добавления реакции if let Some(msg) = app.get_selected_message() { let chat_id = app.selected_chat_id.unwrap(); let message_id = msg.id; app.status_message = Some("Загрузка реакций...".to_string()); app.needs_redraw = true; // Запрашиваем доступные реакции match timeout( Duration::from_secs(5), app.td_client .get_message_available_reactions(chat_id, message_id), ) .await { Ok(Ok(reactions)) => { if reactions.is_empty() { app.error_message = Some("Реакции недоступны для этого сообщения".to_string()); app.status_message = None; app.needs_redraw = true; } else { app.enter_reaction_picker_mode(message_id, reactions); app.status_message = None; app.needs_redraw = true; } } Ok(Err(e)) => { app.error_message = Some(format!("Ошибка загрузки реакций: {}", e)); app.status_message = None; app.needs_redraw = true; } Err(_) => { app.error_message = Some("Таймаут загрузки реакций".to_string()); app.status_message = None; app.needs_redraw = true; } } } } _ => {} } return; } // Ctrl+U для профиля if key.code == KeyCode::Char('u') && has_ctrl { if let Some(chat_id) = app.selected_chat_id { app.status_message = Some("Загрузка профиля...".to_string()); match timeout(Duration::from_secs(5), app.td_client.get_profile_info(chat_id)).await { Ok(Ok(profile)) => { app.enter_profile_mode(profile); app.status_message = None; } Ok(Err(e)) => { app.error_message = Some(e); app.status_message = None; } Err(_) => { app.error_message = Some("Таймаут загрузки профиля".to_string()); app.status_message = None; } } } return; } match key.code { KeyCode::Backspace => { // Удаляем символ слева от курсора if app.cursor_position > 0 { let chars: Vec = app.message_input.chars().collect(); let mut new_input = String::new(); for (i, ch) in chars.iter().enumerate() { if i != app.cursor_position - 1 { new_input.push(*ch); } } app.message_input = new_input; app.cursor_position -= 1; } } KeyCode::Delete => { // Удаляем символ справа от курсора let len = app.message_input.chars().count(); if app.cursor_position < len { let chars: Vec = app.message_input.chars().collect(); let mut new_input = String::new(); for (i, ch) in chars.iter().enumerate() { if i != app.cursor_position { new_input.push(*ch); } } app.message_input = new_input; } } KeyCode::Char(c) => { // Вставляем символ в позицию курсора let chars: Vec = app.message_input.chars().collect(); let mut new_input = String::new(); for (i, ch) in chars.iter().enumerate() { if i == app.cursor_position { new_input.push(c); } new_input.push(*ch); } if app.cursor_position >= chars.len() { new_input.push(c); } app.message_input = new_input; app.cursor_position += 1; // Отправляем typing status с throttling (не чаще 1 раза в 5 сек) let should_send_typing = app .last_typing_sent .map(|t| t.elapsed().as_secs() >= 5) .unwrap_or(true); if should_send_typing { if let Some(chat_id) = app.get_selected_chat_id() { app.td_client .send_chat_action(chat_id, ChatAction::Typing) .await; app.last_typing_sent = Some(Instant::now()); } } } KeyCode::Left => { // Курсор влево if app.cursor_position > 0 { app.cursor_position -= 1; } } KeyCode::Right => { // Курсор вправо let len = app.message_input.chars().count(); if app.cursor_position < len { app.cursor_position += 1; } } KeyCode::Home => { // Курсор в начало app.cursor_position = 0; } KeyCode::End => { // Курсор в конец app.cursor_position = app.message_input.chars().count(); } // Стрелки вверх/вниз - скролл сообщений или начало выбора KeyCode::Down => { // Скролл вниз (к новым сообщениям) if app.message_scroll_offset > 0 { app.message_scroll_offset = app.message_scroll_offset.saturating_sub(3); } } KeyCode::Up => { // Если инпут пустой и не в режиме редактирования — начать выбор сообщения if app.message_input.is_empty() && !app.is_editing() { app.start_message_selection(); } else { // Скролл вверх (к старым сообщениям) app.message_scroll_offset += 3; // Проверяем, нужно ли подгрузить старые сообщения if !app.td_client.current_chat_messages().is_empty() { let oldest_msg_id = app .td_client .current_chat_messages() .first() .map(|m| m.id) .unwrap_or(0); if let Some(chat_id) = app.get_selected_chat_id() { // Подгружаем больше сообщений если скролл близко к верху if app.message_scroll_offset > app.td_client.current_chat_messages().len().saturating_sub(10) { if let Ok(Ok(older)) = timeout( Duration::from_secs(3), app.td_client .load_older_messages(chat_id, oldest_msg_id), ) .await { if !older.is_empty() { // Добавляем старые сообщения в начало let msgs = app.td_client.current_chat_messages_mut(); msgs.splice(0..0, older); } } } } } } } _ => {} } } else { // В режиме списка чатов - навигация стрелками и переключение папок match key.code { KeyCode::Down => { app.next_chat(); } KeyCode::Up => { app.previous_chat(); } // Цифры 1-9 - переключение папок KeyCode::Char(c) if c >= '1' && c <= '9' => { let folder_num = (c as usize) - ('1' as usize); // 0-based if folder_num == 0 { // 1 = All app.selected_folder_id = None; } else { // 2, 3, 4... = папки из TDLib if let Some(folder) = app.td_client.folders().get(folder_num - 1) { let folder_id = folder.id; app.selected_folder_id = Some(folder_id); // Загружаем чаты папки app.status_message = Some("Загрузка чатов папки...".to_string()); let _ = timeout( Duration::from_secs(5), app.td_client.load_folder_chats(folder_id, 50), ) .await; app.status_message = None; } } app.chat_list_state.select(Some(0)); } _ => {} } } } /// Подсчёт количества доступных действий в профиле fn get_available_actions_count(profile: &crate::tdlib::ProfileInfo) -> usize { let mut count = 0; if profile.username.is_some() { count += 1; // Открыть в браузере } count += 1; // Скопировать ID if profile.is_group { count += 1; // Покинуть группу } count } /// Копирует текст в системный буфер обмена fn copy_to_clipboard(text: &str) -> Result<(), String> { use arboard::Clipboard; let mut clipboard = Clipboard::new().map_err(|e| format!("Не удалось инициализировать буфер обмена: {}", e))?; clipboard .set_text(text) .map_err(|e| format!("Не удалось скопировать: {}", e))?; Ok(()) } /// Форматирует сообщение для копирования с контекстом fn format_message_for_clipboard(msg: &crate::tdlib::MessageInfo) -> String { let mut result = String::new(); // Добавляем forward контекст если есть if let Some(forward) = &msg.forward_from { result.push_str(&format!("↪ Переслано от {}\n", forward.sender_name)); } // Добавляем reply контекст если есть if let Some(reply) = &msg.reply_to { result.push_str(&format!("┌ {}: {}\n", reply.sender_name, reply.text)); } // Добавляем основной текст с markdown форматированием result.push_str(&convert_entities_to_markdown(&msg.content, &msg.entities)); result } /// Конвертирует текст с entities в markdown fn convert_entities_to_markdown(text: &str, entities: &[tdlib_rs::types::TextEntity]) -> String { use tdlib_rs::enums::TextEntityType; if entities.is_empty() { return text.to_string(); } // Создаём вектор символов для работы с unicode let chars: Vec = text.chars().collect(); let mut result = String::new(); let mut i = 0; while i < chars.len() { // Ищем entity, который начинается в текущей позиции let mut entity_found = false; for entity in entities { if entity.offset as usize == i { entity_found = true; let end = (entity.offset + entity.length) as usize; let entity_text: String = chars[i..end.min(chars.len())].iter().collect(); // Применяем форматирование в зависимости от типа let formatted = match &entity.r#type { TextEntityType::Bold => format!("**{}**", entity_text), TextEntityType::Italic => format!("*{}*", entity_text), TextEntityType::Underline => format!("__{}__", entity_text), TextEntityType::Strikethrough => format!("~~{}~~", entity_text), TextEntityType::Code | TextEntityType::Pre | TextEntityType::PreCode(_) => { format!("`{}`", entity_text) } TextEntityType::TextUrl(url_info) => { format!("[{}]({})", entity_text, url_info.url) } TextEntityType::Url => format!("<{}>", entity_text), TextEntityType::Mention | TextEntityType::MentionName(_) => { format!("@{}", entity_text.trim_start_matches('@')) } TextEntityType::Spoiler => format!("||{}||", entity_text), _ => entity_text, }; result.push_str(&formatted); i = end; break; } } if !entity_found { result.push(chars[i]); i += 1; } } result }