UNSTABLE | Pictures to ascii

This commit is contained in:
wheelchairy 2025-03-27 12:24:40 +03:00
parent cca1249526
commit 3592da4503
3 changed files with 493 additions and 132 deletions

1
.gitignore vendored
View File

@ -12,6 +12,7 @@ venv/
# Environment # Environment
.env .env
pics*
# IDE # IDE
.vscode/ .vscode/

View File

@ -1,5 +1,6 @@
urwid>=2.1.2 telethon
telethon>=1.34.0 python-dotenv
python-dotenv>=1.0.0 urwid
emoji>=2.10.1 nest_asyncio
nest_asyncio>=1.6.0 emoji
Pillow

View File

@ -15,6 +15,10 @@ from telethon import TelegramClient, events, utils
from telethon.errors import SessionPasswordNeededError from telethon.errors import SessionPasswordNeededError
from dotenv import load_dotenv from dotenv import load_dotenv
import datetime import datetime
from PIL import Image
import io
import hashlib
import json
# Разрешаем вложенные event loops # Разрешаем вложенные event loops
nest_asyncio.apply() nest_asyncio.apply()
@ -49,6 +53,113 @@ def normalize_text(text: str) -> str:
print(f"Ошибка нормализации текста: {e}") print(f"Ошибка нормализации текста: {e}")
return "Ошибка отображения" 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): class ChatWidget(urwid.WidgetWrap):
"""Виджет чата""" """Виджет чата"""
@ -134,39 +245,77 @@ class ChatWidget(urwid.WidgetWrap):
class MessageWidget(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.text = normalize_text(text)
self.username = normalize_text(username) self.username = normalize_text(username)
self.is_me = is_me self.is_me = is_me
self.send_time = send_time 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() self.update_widget()
super().__init__(self.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): def update_widget(self):
"""Обновляет внешний вид виджета""" """Обновляет внешний вид виджета"""
# Подготавливаем текст # Подготавливаем текст
text = self.text if self.text else "Пустое сообщение"
username = self.username if self.username 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([ header = urwid.Columns([
urwid.Text(username), 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( self.widget = urwid.AttrMap(
urwid.Pile([ urwid.Pile([
urwid.AttrMap(header, 'chat_name'), urwid.AttrMap(header, 'chat_name'),
urwid.Text(text) content
]), ]),
'message_me' if self.is_me else 'message_other' style
) )
def selectable(self): def selectable(self):
return False return True
def keypress(self, size, key):
if key == 'ctrl r':
return 'reply'
return key
class SearchEdit(urwid.Edit): class SearchEdit(urwid.Edit):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -205,6 +354,8 @@ class TelegramTUI:
('message_other', 'white', 'black'), ('message_other', 'white', 'black'),
('help', 'yellow', 'black'), ('help', 'yellow', 'black'),
('error', 'light red', 'black'), ('error', 'light red', 'black'),
('message_selected', 'black', 'light gray'),
('input_disabled', 'dark gray', 'black'),
] ]
def __init__(self, telegram_client: TelegramClient): def __init__(self, telegram_client: TelegramClient):
@ -249,7 +400,7 @@ class TelegramTUI:
# Создаем левую панель (чаты) # Создаем левую панель (чаты)
self.left_panel = urwid.LineBox( self.left_panel = urwid.LineBox(
urwid.Pile([ urwid.Pile([
('pack', urwid.Text(('help', "Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть чат, Esc - назад, / - поиск"), align='center')), ('pack', urwid.Text(('help', "Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть чат, Esc - назад, / - поиск, [] - папки"), align='center')),
('pack', self.search_edit), ('pack', self.search_edit),
urwid.BoxAdapter(self.chat_list, 30) # Фиксированная высота для списка чатов urwid.BoxAdapter(self.chat_list, 30) # Фиксированная высота для списка чатов
]) ])
@ -297,6 +448,36 @@ class TelegramTUI:
self.update_interval = 3 # секунды для чатов self.update_interval = 3 # секунды для чатов
self.message_update_interval = 1 # секунда для сообщений self.message_update_interval = 1 # секунда для сообщений
self.last_message_update_time = 0 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): def switch_screen(self, screen_name: str):
"""Переключение между экранами""" """Переключение между экранами"""
@ -344,19 +525,27 @@ class TelegramTUI:
async def update_chat_list(self): async def update_chat_list(self):
"""Обновляет список чатов""" """Обновляет список чатов"""
try: 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: if not self.folders:
try: try:
# Проверяем наличие архива folders = await self.telegram_client.get_dialogs(folder=1)
self.folders = [1] if await self.telegram_client.get_dialogs(limit=1, folder=1) else [] if folders:
self.folders = [0, 1]
else:
self.folders = [0]
print(f"Доступные папки: {self.folders}")
except Exception as e: except Exception as e:
print(f"Ошибка получения папок: {e}") print(f"Ошибка получения папок: {e}")
self.folders = [] self.folders = [0]
# Получаем диалоги # Получаем диалоги
try: try:
dialogs = await self.telegram_client.get_dialogs( dialogs = await self.telegram_client.get_dialogs(
limit=100, limit=50, # Уменьшаем лимит для стабильности
folder=self.current_folder folder=self.current_folder
) )
except Exception as e: except Exception as e:
@ -369,7 +558,6 @@ class TelegramTUI:
filtered_dialogs = [] filtered_dialogs = []
for dialog in dialogs: for dialog in dialogs:
try: try:
# Поиск по имени
name = "" name = ""
if hasattr(dialog.entity, 'title') and dialog.entity.title: if hasattr(dialog.entity, 'title') and dialog.entity.title:
name = dialog.entity.title name = dialog.entity.title
@ -378,12 +566,10 @@ class TelegramTUI:
if hasattr(dialog.entity, 'last_name') and dialog.entity.last_name: if hasattr(dialog.entity, 'last_name') and dialog.entity.last_name:
name += f" {dialog.entity.last_name}" name += f" {dialog.entity.last_name}"
# Поиск по последнему сообщению
last_message = "" last_message = ""
if dialog.message and hasattr(dialog.message, 'message'): if dialog.message and hasattr(dialog.message, 'message'):
last_message = dialog.message.message last_message = dialog.message.message
# Если есть совпадение, добавляем диалог
if (search_query in normalize_text(name).lower() or if (search_query in normalize_text(name).lower() or
search_query in normalize_text(last_message).lower()): search_query in normalize_text(last_message).lower()):
filtered_dialogs.append(dialog) filtered_dialogs.append(dialog)
@ -392,16 +578,18 @@ class TelegramTUI:
dialogs = filtered_dialogs dialogs = filtered_dialogs
# Сохраняем старые чаты для сравнения
old_chats = {chat.chat_id: chat for chat in self.chat_walker}
# Очищаем список # Очищаем список
self.chat_walker[:] = [] self.chat_walker[:] = []
# Добавляем чаты # Добавляем чаты
restored_focus = False
for i, dialog in enumerate(dialogs): for i, dialog in enumerate(dialogs):
try: try:
# Получаем имя и сообщение
entity = dialog.entity entity = dialog.entity
# Определяем имя чата
if hasattr(entity, 'title') and entity.title: if hasattr(entity, 'title') and entity.title:
name = entity.title name = entity.title
elif hasattr(entity, 'first_name'): elif hasattr(entity, 'first_name'):
@ -411,27 +599,39 @@ class TelegramTUI:
else: else:
name = "Без названия" name = "Без названия"
# Получаем последнее сообщение
if dialog.message: if dialog.message:
message = dialog.message.message if hasattr(dialog.message, 'message') else "" message = dialog.message.message if hasattr(dialog.message, 'message') else ""
else: else:
message = "" message = ""
# Проверяем, был ли этот чат раньше
old_chat = old_chats.get(dialog.id)
is_selected = (dialog.id == current_chat_id)
chat = ChatWidget( chat = ChatWidget(
chat_id=dialog.id, chat_id=dialog.id,
name=name, name=name,
message=message, message=message,
is_selected=(i == self.selected_chat_index), is_selected=is_selected,
folder=1 if self.current_folder else 0 folder=1 if self.current_folder else 0
) )
self.chat_walker.append(chat) 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: except Exception as e:
print(f"Ошибка создания виджета чата: {e}") print(f"Ошибка создания виджета чата: {e}")
# Обновляем фокус # Восстанавливаем фокус
if self.chat_walker: if self.chat_walker:
self.selected_chat_index = min(self.selected_chat_index, len(self.chat_walker) - 1) if current_focus >= len(self.chat_walker):
self.chat_list.set_focus(self.selected_chat_index) 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() self.update_selected_chat()
except Exception as e: except Exception as e:
@ -451,31 +651,63 @@ class TelegramTUI:
async def update_message_list(self, chat_id): async def update_message_list(self, chat_id):
"""Обновляет список сообщений""" """Обновляет список сообщений"""
try: 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( messages = await self.telegram_client.get_messages(
entity=chat_id, entity=chat_id,
limit=50 limit=30 # Уменьшаем лимит для стабильности
) )
# Получаем информацию о себе # Получаем информацию о себе
me = await self.telegram_client.get_me() 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[:] = [] self.message_walker[:] = []
# Добавляем сообщения # Добавляем сообщения
for msg in reversed(messages): for msg in reversed(messages):
try: 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 is_me = False
if hasattr(msg, 'from_id') and msg.from_id: if hasattr(msg, 'from_id') and msg.from_id:
if hasattr(msg.from_id, 'user_id'): if hasattr(msg.from_id, 'user_id'):
is_me = msg.from_id.user_id == me.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 = "" username = ""
if hasattr(msg, 'sender') and msg.sender: if hasattr(msg, 'sender') and msg.sender:
if hasattr(msg.sender, 'first_name'): if hasattr(msg.sender, 'first_name'):
@ -485,126 +717,251 @@ class TelegramTUI:
elif hasattr(msg.sender, 'title'): elif hasattr(msg.sender, 'title'):
username = msg.sender.title username = msg.sender.title
# Если не удалось получить имя, используем Me/Другой
if not username: if not username:
username = "Я" if is_me else "Неизвестный" 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 = MessageWidget(
message_id=msg.id,
text=text, text=text,
username=username, username=username,
is_me=is_me, 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) self.message_walker.append(message)
except Exception as e: except Exception as e:
print(f"Ошибка создания виджета сообщения: {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) self.message_list.set_focus(len(self.message_walker) - 1)
except Exception as e: except Exception as e:
print(f"Ошибка обновления сообщений: {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): async def handle_chat_input(self, key):
"""Обработка ввода в экране чатов""" """Обработка ввода в экране чатов"""
if key == 'tab': try:
if self.focused_element == "search": if key == 'reply' and self.focused_element == "messages":
self.focused_element = "chat_list" # Получаем выбранное сообщение
self.left_panel.original_widget.focus_position = 2 if self.message_walker and self.message_list.focus is not None:
# Обновляем список при переключении на чаты msg_widget = self.message_walker[self.message_list.focus_position]
await self.update_chat_list() self.selected_message = msg_widget.message_id
elif self.focused_element == "chat_list": self.replying_to = msg_widget.text
if self.current_chat_id: self.update_input_visibility()
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)
self.focused_element = "input" self.focused_element = "input"
self.chat_widget.focus_position = 1
self.right_panel.original_widget.focus_position = 1 self.right_panel.original_widget.focus_position = 1
# Сбрасываем время последнего обновления сообщений return
self.last_message_update_time = 0
except Exception as e: elif key == 'esc':
print(f"Ошибка при открытии чата: {e}") if self.replying_to:
elif self.focused_element == "input" and self.current_chat_id: # Отменяем ответ
message = self.input_edit.get_edit_text() self.selected_message = None
if message.strip(): 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: try:
await self.telegram_client.send_message(self.current_chat_id, message) focused = self.chat_walker[self.chat_list.focus_position]
self.input_edit.set_edit_text("") if focused.chat_id != self.current_chat_id:
# Сразу обновляем сообщения после отправки self.current_chat_id = focused.chat_id
self.last_message_update_time = 0 self.selected_chat_index = self.chat_list.focus_position
await self.update_message_list(self.current_chat_id)
# Проверяем права при открытии чата
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: 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': except Exception as e:
if self.focused_element in ("input", "messages"): print(f"Ошибка обработки ввода: {e}")
# Закрываем текущий чат # Восстанавливаем состояние в случае ошибки
self.current_chat_id = None self.focused_element = "chat_list"
self.message_walker[:] = [] self.chat_widget.focus_position = 0
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
def unhandled_input(self, key): def unhandled_input(self, key):
"""Обработка необработанных нажатий клавиш""" """Обработка необработанных нажатий клавиш"""
@ -627,10 +984,12 @@ class TelegramTUI:
async def chat_update_loop(): async def chat_update_loop():
while True: while True:
try: try:
current_time = datetime.datetime.now().timestamp() # Обновляем только если не в процессе отправки сообщения
if current_time - self.last_update_time >= self.update_interval: if not self.pending_messages:
await self.update_chat_list() current_time = datetime.datetime.now().timestamp()
self.last_update_time = current_time 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) await asyncio.sleep(1)
except Exception as e: except Exception as e:
print(f"Ошибка в цикле обновления чатов: {e}") print(f"Ошибка в цикле обновления чатов: {e}")
@ -639,7 +998,7 @@ class TelegramTUI:
async def message_update_loop(): async def message_update_loop():
while True: while True:
try: try:
if self.current_chat_id: if self.current_chat_id and not self.pending_messages:
current_time = datetime.datetime.now().timestamp() current_time = datetime.datetime.now().timestamp()
if current_time - self.last_message_update_time >= self.message_update_interval: if current_time - self.last_message_update_time >= self.message_update_interval:
await self.update_message_list(self.current_chat_id) await self.update_message_list(self.current_chat_id)