From 3592da450306c130eb94e8743b60c02d3e8fde6b Mon Sep 17 00:00:00 2001 From: wheelchairy Date: Thu, 27 Mar 2025 12:24:40 +0300 Subject: [PATCH] UNSTABLE | Pictures to ascii --- .gitignore | 1 + requirements.txt | 11 +- urwid_client/telegram_tui.py | 613 +++++++++++++++++++++++++++-------- 3 files changed, 493 insertions(+), 132 deletions(-) diff --git a/.gitignore b/.gitignore index 6220c2a..e533bc4 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,7 @@ venv/ # Environment .env +pics* # IDE .vscode/ diff --git a/requirements.txt b/requirements.txt index 6a48d7c..5a35ca1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,6 @@ -urwid>=2.1.2 -telethon>=1.34.0 -python-dotenv>=1.0.0 -emoji>=2.10.1 -nest_asyncio>=1.6.0 \ No newline at end of file +telethon +python-dotenv +urwid +nest_asyncio +emoji +Pillow \ No newline at end of file diff --git a/urwid_client/telegram_tui.py b/urwid_client/telegram_tui.py index 58a7f82..97bb403 100644 --- a/urwid_client/telegram_tui.py +++ b/urwid_client/telegram_tui.py @@ -15,6 +15,10 @@ from telethon import TelegramClient, events, utils from telethon.errors import SessionPasswordNeededError from dotenv import load_dotenv import datetime +from PIL import Image +import io +import hashlib +import json # Разрешаем вложенные event loops nest_asyncio.apply() @@ -49,6 +53,113 @@ def normalize_text(text: str) -> str: print(f"Ошибка нормализации текста: {e}") return "Ошибка отображения" +def image_to_ascii(image_data, max_width=80, max_height=24): + """Конвертирует изображение в ASCII-арт""" + try: + # Открываем изображение из байтов + image = Image.open(io.BytesIO(image_data)) + + # Конвертируем в оттенки серого + image = image.convert('L') + + # Определяем новые размеры с сохранением пропорций + width, height = image.size + aspect_ratio = height/width + new_width = min(max_width, width) + new_height = int(new_width * aspect_ratio * 0.5) # * 0.5 потому что символы в терминале выше, чем шире + + if new_height > max_height: + new_height = max_height + new_width = int(new_height / aspect_ratio * 2) + + # Изменяем размер + image = image.resize((new_width, new_height)) + + # Символы от темного к светлому + ascii_chars = '@%#*+=-:. ' + + # Конвертируем пиксели в ASCII + pixels = image.getdata() + ascii_str = '' + for i, pixel in enumerate(pixels): + ascii_str += ascii_chars[pixel//32] # 256//32 = 8 уровней + if (i + 1) % new_width == 0: + ascii_str += '\n' + + # Добавляем рамку вокруг ASCII-арта для стабильности + lines = ascii_str.split('\n') + if lines and lines[-1] == '': + lines = lines[:-1] # Удаляем последнюю пустую строку + + max_line_length = max(len(line) for line in lines) + border_top = '┌' + '─' * max_line_length + '┐\n' + border_bottom = '└' + '─' * max_line_length + '┘' + + framed_ascii = border_top + for line in lines: + padding = ' ' * (max_line_length - len(line)) + framed_ascii += '│' + line + padding + '│\n' + framed_ascii += border_bottom + + return framed_ascii + except Exception as e: + print(f"Ошибка конвертации изображения: {e}") + return "[Ошибка конвертации изображения]" + +class AsciiArtCache: + """Класс для кэширования ASCII-артов""" + def __init__(self, cache_dir='pics'): + self.cache_dir = cache_dir + os.makedirs(cache_dir, exist_ok=True) + self.index_file = os.path.join(cache_dir, 'index.json') + self.load_index() + + def load_index(self): + """Загружает индекс кэшированных изображений""" + try: + with open(self.index_file, 'r') as f: + self.index = json.load(f) + except (FileNotFoundError, json.JSONDecodeError): + self.index = {} + self.save_index() + + def save_index(self): + """Сохраняет индекс кэшированных изображений""" + with open(self.index_file, 'w') as f: + json.dump(self.index, f) + + def get_cache_key(self, image_data): + """Генерирует ключ кэша для изображения""" + return hashlib.md5(image_data).hexdigest() + + def get_cached_art(self, image_data): + """Получает ASCII-арт из кэша или создает новый""" + cache_key = self.get_cache_key(image_data) + + # Проверяем наличие в индексе + if cache_key in self.index: + try: + with open(os.path.join(self.cache_dir, cache_key + '.txt'), 'r') as f: + return f.read() + except FileNotFoundError: + pass + + # Если нет в кэше, создаем новый + ascii_art = image_to_ascii(image_data) + + # Сохраняем в кэш + with open(os.path.join(self.cache_dir, cache_key + '.txt'), 'w') as f: + f.write(ascii_art) + + # Обновляем индекс + self.index[cache_key] = { + 'created_at': datetime.datetime.now().isoformat(), + 'size': len(image_data) + } + self.save_index() + + return ascii_art + class ChatWidget(urwid.WidgetWrap): """Виджет чата""" @@ -134,39 +245,77 @@ class ChatWidget(urwid.WidgetWrap): class MessageWidget(urwid.WidgetWrap): """Виджет сообщения""" - def __init__(self, text="", username="", is_me=False, send_time=""): + def __init__(self, message_id, text="", username="", is_me=False, send_time="", status="", is_selected=False, media_data=None): + self.message_id = message_id self.text = normalize_text(text) self.username = normalize_text(username) self.is_me = is_me self.send_time = send_time + self.status = status + self.is_selected = is_selected + self._media_data = None + self._cached_content = None + self.set_media_data(media_data) # Создаем содержимое виджета self.update_widget() super().__init__(self.widget) + def set_media_data(self, media_data): + """Устанавливает медиа-данные и обновляет кэш""" + if self._media_data != media_data: + self._media_data = media_data + self._cached_content = None + + def get_content(self): + """Получает содержимое сообщения с кэшированием""" + if self._cached_content is None: + text = self.text if self.text else "Пустое сообщение" + if self._media_data: + text = self._media_data + "\n" + text + self._cached_content = text + return self._cached_content + def update_widget(self): """Обновляет внешний вид виджета""" # Подготавливаем текст - text = self.text if self.text else "Пустое сообщение" username = self.username if self.username else "Неизвестный" + # Добавляем статус к времени для исходящих сообщений + time_text = self.send_time + if self.is_me and self.status: + time_text = f"{self.send_time} {self.status}" + # Создаем заголовок header = urwid.Columns([ urwid.Text(username), - ('fixed', 5, urwid.Text(self.send_time, align='right')) + ('fixed', 10 if self.is_me else 5, urwid.Text(time_text, align='right')) ]) + # Определяем стиль + style = 'message_selected' if self.is_selected else ('message_me' if self.is_me else 'message_other') + + # Создаем виджет с фиксированной шириной для ASCII-арта + content = urwid.Text(self.get_content()) + if self._media_data: + content = urwid.BoxAdapter(urwid.Filler(content), len(self._media_data.split('\n'))) + # Создаем виджет self.widget = urwid.AttrMap( urwid.Pile([ urwid.AttrMap(header, 'chat_name'), - urwid.Text(text) + content ]), - 'message_me' if self.is_me else 'message_other' + style ) def selectable(self): - return False + return True + + def keypress(self, size, key): + if key == 'ctrl r': + return 'reply' + return key class SearchEdit(urwid.Edit): def __init__(self, *args, **kwargs): @@ -205,6 +354,8 @@ class TelegramTUI: ('message_other', 'white', 'black'), ('help', 'yellow', 'black'), ('error', 'light red', 'black'), + ('message_selected', 'black', 'light gray'), + ('input_disabled', 'dark gray', 'black'), ] def __init__(self, telegram_client: TelegramClient): @@ -249,7 +400,7 @@ class TelegramTUI: # Создаем левую панель (чаты) self.left_panel = urwid.LineBox( urwid.Pile([ - ('pack', urwid.Text(('help', "Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть чат, Esc - назад, / - поиск"), align='center')), + ('pack', urwid.Text(('help', "Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть чат, Esc - назад, / - поиск, [] - папки"), align='center')), ('pack', self.search_edit), urwid.BoxAdapter(self.chat_list, 30) # Фиксированная высота для списка чатов ]) @@ -297,6 +448,36 @@ class TelegramTUI: self.update_interval = 3 # секунды для чатов self.message_update_interval = 1 # секунда для сообщений self.last_message_update_time = 0 + + # Добавляем отслеживание отправляемых сообщений + self.pending_messages = {} # message_id -> widget + + # Добавляем обработчик обновления сообщений + @telegram_client.on(events.MessageEdited()) + async def handle_edit(event): + try: + if event.message.out: + await self.update_message_status(event.message) + except Exception as e: + print(f"Ошибка обработки редактирования: {e}") + + @telegram_client.on(events.NewMessage()) + async def handle_new(event): + try: + if event.message.out: + await self.update_message_status(event.message) + elif event.message.chat_id == self.current_chat_id: + # Обновляем сообщения если это текущий чат + self.last_message_update_time = 0 + except Exception as e: + print(f"Ошибка обработки нового сообщения: {e}") + + # Добавляем состояния для ответов + self.selected_message = None + self.replying_to = None + self.can_send_messages = False + + self.ascii_cache = AsciiArtCache() def switch_screen(self, screen_name: str): """Переключение между экранами""" @@ -344,19 +525,27 @@ class TelegramTUI: async def update_chat_list(self): """Обновляет список чатов""" try: + # Сохраняем текущий фокус и ID выбранного чата + current_focus = self.chat_list.focus_position if self.chat_walker else 0 + current_chat_id = self.current_chat_id + # Получаем папки if not self.folders: try: - # Проверяем наличие архива - self.folders = [1] if await self.telegram_client.get_dialogs(limit=1, folder=1) else [] + folders = await self.telegram_client.get_dialogs(folder=1) + if folders: + self.folders = [0, 1] + else: + self.folders = [0] + print(f"Доступные папки: {self.folders}") except Exception as e: print(f"Ошибка получения папок: {e}") - self.folders = [] + self.folders = [0] # Получаем диалоги try: dialogs = await self.telegram_client.get_dialogs( - limit=100, + limit=50, # Уменьшаем лимит для стабильности folder=self.current_folder ) except Exception as e: @@ -369,7 +558,6 @@ class TelegramTUI: filtered_dialogs = [] for dialog in dialogs: try: - # Поиск по имени name = "" if hasattr(dialog.entity, 'title') and dialog.entity.title: name = dialog.entity.title @@ -378,12 +566,10 @@ class TelegramTUI: if hasattr(dialog.entity, 'last_name') and dialog.entity.last_name: name += f" {dialog.entity.last_name}" - # Поиск по последнему сообщению last_message = "" if dialog.message and hasattr(dialog.message, 'message'): last_message = dialog.message.message - # Если есть совпадение, добавляем диалог if (search_query in normalize_text(name).lower() or search_query in normalize_text(last_message).lower()): filtered_dialogs.append(dialog) @@ -392,16 +578,18 @@ class TelegramTUI: dialogs = filtered_dialogs + # Сохраняем старые чаты для сравнения + old_chats = {chat.chat_id: chat for chat in self.chat_walker} + # Очищаем список self.chat_walker[:] = [] # Добавляем чаты + restored_focus = False for i, dialog in enumerate(dialogs): try: - # Получаем имя и сообщение entity = dialog.entity - # Определяем имя чата if hasattr(entity, 'title') and entity.title: name = entity.title elif hasattr(entity, 'first_name'): @@ -411,27 +599,39 @@ class TelegramTUI: else: name = "Без названия" - # Получаем последнее сообщение if dialog.message: message = dialog.message.message if hasattr(dialog.message, 'message') else "" else: message = "" + # Проверяем, был ли этот чат раньше + old_chat = old_chats.get(dialog.id) + is_selected = (dialog.id == current_chat_id) + chat = ChatWidget( chat_id=dialog.id, name=name, message=message, - is_selected=(i == self.selected_chat_index), + is_selected=is_selected, folder=1 if self.current_folder else 0 ) + self.chat_walker.append(chat) + + # Восстанавливаем фокус если это текущий чат + if dialog.id == current_chat_id and not restored_focus: + current_focus = i + restored_focus = True + except Exception as e: print(f"Ошибка создания виджета чата: {e}") - # Обновляем фокус + # Восстанавливаем фокус if self.chat_walker: - self.selected_chat_index = min(self.selected_chat_index, len(self.chat_walker) - 1) - self.chat_list.set_focus(self.selected_chat_index) + if current_focus >= len(self.chat_walker): + current_focus = len(self.chat_walker) - 1 + self.chat_list.set_focus(max(0, current_focus)) + self.selected_chat_index = current_focus self.update_selected_chat() except Exception as e: @@ -451,31 +651,63 @@ class TelegramTUI: async def update_message_list(self, chat_id): """Обновляет список сообщений""" try: + if not chat_id: + self.message_walker[:] = [] + return + + # Сохраняем текущий фокус + current_focus = self.message_list.focus_position if self.message_walker else None + # Получаем сообщения messages = await self.telegram_client.get_messages( entity=chat_id, - limit=50 + limit=30 # Уменьшаем лимит для стабильности ) # Получаем информацию о себе me = await self.telegram_client.get_me() + # Сохраняем отслеживаемые сообщения + tracked_messages = { + msg_id: widget + for msg_id, widget in self.pending_messages.items() + } + + # Сохраняем старые сообщения для сравнения + old_messages = {msg.message_id: msg for msg in self.message_walker} + # Очищаем список self.message_walker[:] = [] # Добавляем сообщения for msg in reversed(messages): try: - # Определяем, отправлено ли сообщение нами + # Проверяем, было ли это сообщение раньше + old_message = old_messages.get(msg.id) + if old_message and not msg.photo: + # Переиспользуем существующий виджет для не-фото сообщений + self.message_walker.append(old_message) + continue + is_me = False if hasattr(msg, 'from_id') and msg.from_id: if hasattr(msg.from_id, 'user_id'): is_me = msg.from_id.user_id == me.id - # Получаем текст сообщения - text = msg.message if hasattr(msg, 'message') else "Медиа" + text = msg.message if hasattr(msg, 'message') else "" + media_data = None + + if hasattr(msg, 'photo') and msg.photo: + try: + photo_data = await self.telegram_client.download_media(msg.photo, bytes) + if photo_data: + media_data = self.ascii_cache.get_cached_art(photo_data) + if not text: + text = "[Фото]" + except Exception as e: + print(f"Ошибка загрузки фото: {e}") + text = "[Ошибка загрузки фото]" - # Получаем имя отправителя username = "" if hasattr(msg, 'sender') and msg.sender: if hasattr(msg.sender, 'first_name'): @@ -485,126 +717,251 @@ class TelegramTUI: elif hasattr(msg.sender, 'title'): username = msg.sender.title - # Если не удалось получить имя, используем Me/Другой if not username: username = "Я" if is_me else "Неизвестный" + status = "" + if is_me: + if msg.id in tracked_messages: + status = tracked_messages[msg.id].status + else: + status = "✓✓" + message = MessageWidget( + message_id=msg.id, text=text, username=username, is_me=is_me, - send_time=msg.date.strftime("%H:%M") + send_time=msg.date.strftime("%H:%M"), + status=status, + is_selected=(msg.id == self.selected_message), + media_data=media_data ) + + if msg.id in tracked_messages: + self.pending_messages[msg.id] = message + self.message_walker.append(message) except Exception as e: print(f"Ошибка создания виджета сообщения: {e}") - # Прокручиваем к последнему сообщению - if self.message_walker: + # Восстанавливаем фокус + if current_focus is not None and current_focus < len(self.message_walker): + self.message_list.set_focus(current_focus) + elif self.message_walker: self.message_list.set_focus(len(self.message_walker) - 1) except Exception as e: print(f"Ошибка обновления сообщений: {e}") + self.message_walker[:] = [] + + async def update_message_status(self, message): + """Обновляет статус сообщения""" + try: + if message.id in self.pending_messages: + widget = self.pending_messages[message.id] + # Определяем статус + if getattr(message, 'from_id', None): + widget.status = "✓✓" # Доставлено + else: + widget.status = "✓" # Отправлено + widget.update_widget() + # Если сообщение доставлено, удаляем из отслеживания + if widget.status == "✓✓": + del self.pending_messages[message.id] + except Exception as e: + print(f"Ошибка обновления статуса: {e}") + + async def check_chat_permissions(self, chat_id): + """Проверяет права на отправку сообщений в чате""" + try: + # Получаем информацию о чате + chat = await self.telegram_client.get_entity(chat_id) + + # Проверяем, является ли чат каналом + if hasattr(chat, 'broadcast') and chat.broadcast: + # Для каналов проверяем права администратора + participant = await self.telegram_client.get_permissions(chat) + self.can_send_messages = participant.is_admin + else: + # Для всех остальных чатов разрешаем отправку + self.can_send_messages = True + + # Обновляем видимость поля ввода + self.update_input_visibility() + + except Exception as e: + print(f"Ошибка проверки прав: {e}") + # В случае ошибки разрешаем отправку для не-каналов + self.can_send_messages = not (hasattr(chat, 'broadcast') and chat.broadcast) + self.update_input_visibility() + + def update_input_visibility(self): + """Обновляет видимость поля ввода""" + if self.can_send_messages: + if self.replying_to: + self.input_edit = InputEdit(('header', f"Ответ на '{self.replying_to[:30]}...': ")) + else: + self.input_edit = InputEdit(('header', "Сообщение: ")) + else: + self.input_edit = InputEdit(('input_disabled', "Отправка сообщений недоступна")) + self.input_edit.set_edit_text("") + + # Обновляем правую панель + if len(self.right_panel.original_widget.widget_list) > 1: + self.right_panel.original_widget.widget_list[-1] = self.input_edit async def handle_chat_input(self, key): """Обработка ввода в экране чатов""" - if key == 'tab': - if self.focused_element == "search": - self.focused_element = "chat_list" - self.left_panel.original_widget.focus_position = 2 - # Обновляем список при переключении на чаты - await self.update_chat_list() - elif self.focused_element == "chat_list": - if self.current_chat_id: - self.focused_element = "messages" - self.chat_widget.focus_position = 1 - self.right_panel.original_widget.focus_position = 0 - # Обновляем сообщения при переключении на них - await self.update_message_list(self.current_chat_id) - else: - self.focused_element = "search" - self.left_panel.original_widget.focus_position = 1 - elif self.focused_element == "messages": - self.focused_element = "input" - self.right_panel.original_widget.focus_position = 1 - elif self.focused_element == "input": - self.focused_element = "search" - self.chat_widget.focus_position = 0 - self.left_panel.original_widget.focus_position = 1 - # Обновляем поиск при переключении на него - await self.update_chat_list() - - elif key in ('up', 'down'): - if self.focused_element == "chat_list" and self.chat_walker: - if key == 'up': - if self.chat_list.focus_position > 0: - self.chat_list.focus_position -= 1 - else: - if self.chat_list.focus_position < len(self.chat_walker) - 1: - self.chat_list.focus_position += 1 - - # Обновляем выделение - self.selected_chat_index = self.chat_list.focus_position - self.update_selected_chat() - - # Если чат открыт, обновляем его содержимое - if self.current_chat_id: - focused = self.chat_walker[self.selected_chat_index] - self.current_chat_id = focused.chat_id - await self.update_message_list(focused.chat_id) - - elif self.focused_element == "messages" and self.message_walker: - if key == 'up': - if self.message_list.focus_position > 0: - self.message_list.focus_position -= 1 - else: - if self.message_list.focus_position < len(self.message_walker) - 1: - self.message_list.focus_position += 1 - - elif key == 'enter': - if self.focused_element == "search": - await self.update_chat_list() - self.focused_element = "chat_list" - self.left_panel.original_widget.focus_position = 2 - elif self.focused_element == "chat_list" and self.chat_walker: - try: - focused = self.chat_walker[self.chat_list.focus_position] - self.current_chat_id = focused.chat_id - self.selected_chat_index = self.chat_list.focus_position - await self.update_message_list(focused.chat_id) + try: + if key == 'reply' and self.focused_element == "messages": + # Получаем выбранное сообщение + if self.message_walker and self.message_list.focus is not None: + msg_widget = self.message_walker[self.message_list.focus_position] + self.selected_message = msg_widget.message_id + self.replying_to = msg_widget.text + self.update_input_visibility() + # Переключаемся на ввод self.focused_element = "input" - self.chat_widget.focus_position = 1 self.right_panel.original_widget.focus_position = 1 - # Сбрасываем время последнего обновления сообщений - self.last_message_update_time = 0 - except Exception as e: - print(f"Ошибка при открытии чата: {e}") - elif self.focused_element == "input" and self.current_chat_id: - message = self.input_edit.get_edit_text() - if message.strip(): + return + + elif key == 'esc': + if self.replying_to: + # Отменяем ответ + self.selected_message = None + self.replying_to = None + self.update_input_visibility() + return + if self.focused_element in ("input", "messages"): + # Закрываем текущий чат + self.current_chat_id = None + self.message_walker[:] = [] + self.input_edit.set_edit_text("") + self.focused_element = "chat_list" + self.chat_widget.focus_position = 0 + self.left_panel.original_widget.focus_position = 2 + elif self.focused_element == "search": + self.search_edit.set_edit_text("") + await self.update_chat_list() + self.focused_element = "chat_list" + self.left_panel.original_widget.focus_position = 2 + + elif key == 'enter': + if self.focused_element == "chat_list" and self.chat_walker: try: - await self.telegram_client.send_message(self.current_chat_id, message) - self.input_edit.set_edit_text("") - # Сразу обновляем сообщения после отправки - self.last_message_update_time = 0 - await self.update_message_list(self.current_chat_id) + focused = self.chat_walker[self.chat_list.focus_position] + if focused.chat_id != self.current_chat_id: + self.current_chat_id = focused.chat_id + self.selected_chat_index = self.chat_list.focus_position + + # Проверяем права при открытии чата + await self.check_chat_permissions(focused.chat_id) + + await self.update_message_list(focused.chat_id) + self.focused_element = "input" + self.chat_widget.focus_position = 1 + self.right_panel.original_widget.focus_position = 1 + self.last_message_update_time = 0 except Exception as e: - print(f"Ошибка отправки сообщения: {e}") + print(f"Ошибка при открытии чата: {e}") + + elif self.focused_element == "input" and self.current_chat_id and self.can_send_messages: + message = self.input_edit.get_edit_text() + if message.strip(): + try: + # Приостанавливаем автообновление на время отправки + self.last_update_time = datetime.datetime.now().timestamp() + self.last_message_update_time = self.last_update_time + + # Создаем виджет сообщения до отправки + now = datetime.datetime.now() + msg_widget = MessageWidget( + message_id=0, # Временный ID + text=message, + username="Я", + is_me=True, + send_time=now.strftime("%H:%M"), + status="⋯" # Отправляется + ) + + # Отправляем сообщение + sent_message = await self.telegram_client.send_message( + self.current_chat_id, + message, + reply_to=self.selected_message + ) + + # Обновляем ID сообщения и добавляем в отслеживание + msg_widget.message_id = sent_message.id + self.pending_messages[sent_message.id] = msg_widget + + # Добавляем сообщение в список + self.message_walker.append(msg_widget) + self.message_list.set_focus(len(self.message_walker) - 1) + + # Очищаем поле ввода и сбрасываем ответ + self.input_edit.set_edit_text("") + self.selected_message = None + self.replying_to = None + self.update_input_visibility() + + except Exception as e: + print(f"Ошибка отправки сообщения: {e}") + + elif key == 'tab': + if self.focused_element == "search": + self.focused_element = "chat_list" + self.left_panel.original_widget.focus_position = 2 + elif self.focused_element == "chat_list": + if self.current_chat_id: + self.focused_element = "messages" + self.chat_widget.focus_position = 1 + self.right_panel.original_widget.focus_position = 0 + else: + self.focused_element = "search" + self.left_panel.original_widget.focus_position = 1 + elif self.focused_element == "messages": + self.focused_element = "input" + self.right_panel.original_widget.focus_position = 1 + elif self.focused_element == "input": + self.focused_element = "search" + self.chat_widget.focus_position = 0 + self.left_panel.original_widget.focus_position = 1 + + elif key in ('up', 'down'): + if self.focused_element == "chat_list" and self.chat_walker: + if key == 'up': + if self.chat_list.focus_position > 0: + self.chat_list.focus_position -= 1 + else: + if self.chat_list.focus_position < len(self.chat_walker) - 1: + self.chat_list.focus_position += 1 + + # Обновляем выделение + self.selected_chat_index = self.chat_list.focus_position + self.update_selected_chat() + + # Если чат открыт, обновляем его содержимое + if self.current_chat_id: + focused = self.chat_walker[self.selected_chat_index] + if self.current_chat_id != focused.chat_id: + self.current_chat_id = focused.chat_id + await self.update_message_list(focused.chat_id) + + elif self.focused_element == "messages" and self.message_walker: + if key == 'up': + if self.message_list.focus_position > 0: + self.message_list.focus_position -= 1 + else: + if self.message_list.focus_position < len(self.message_walker) - 1: + self.message_list.focus_position += 1 - elif key == 'esc': - if self.focused_element in ("input", "messages"): - # Закрываем текущий чат - self.current_chat_id = None - self.message_walker[:] = [] - self.input_edit.set_edit_text("") - self.focused_element = "chat_list" - self.chat_widget.focus_position = 0 - self.left_panel.original_widget.focus_position = 2 - elif self.focused_element == "search": - self.search_edit.set_edit_text("") - await self.update_chat_list() - self.focused_element = "chat_list" - self.left_panel.original_widget.focus_position = 2 + except Exception as e: + print(f"Ошибка обработки ввода: {e}") + # Восстанавливаем состояние в случае ошибки + self.focused_element = "chat_list" + self.chat_widget.focus_position = 0 def unhandled_input(self, key): """Обработка необработанных нажатий клавиш""" @@ -627,10 +984,12 @@ class TelegramTUI: async def chat_update_loop(): while True: try: - current_time = datetime.datetime.now().timestamp() - if current_time - self.last_update_time >= self.update_interval: - await self.update_chat_list() - self.last_update_time = current_time + # Обновляем только если не в процессе отправки сообщения + if not self.pending_messages: + current_time = datetime.datetime.now().timestamp() + if current_time - self.last_update_time >= self.update_interval: + await self.update_chat_list() + self.last_update_time = current_time await asyncio.sleep(1) except Exception as e: print(f"Ошибка в цикле обновления чатов: {e}") @@ -639,7 +998,7 @@ class TelegramTUI: async def message_update_loop(): while True: try: - if self.current_chat_id: + if self.current_chat_id and not self.pending_messages: current_time = datetime.datetime.now().timestamp() if current_time - self.last_message_update_time >= self.message_update_interval: await self.update_message_list(self.current_chat_id)