mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 11:20:31 +00:00
:Added autocomplete for user names, improved ASCII-art cache handling and message updating. Fixed errors in reading and saving cache, and improved navigation in the autocomplete menu.
This commit is contained in:
parent
4490ec3bfe
commit
6f7d2ffabf
@ -137,20 +137,22 @@ class AsciiArtCache:
|
||||
def get_cached_art(self, image_data):
|
||||
"""Получает ASCII-арт из кэша или создает новый"""
|
||||
cache_key = self.get_cache_key(image_data)
|
||||
cache_file = os.path.join(self.cache_dir, f"{cache_key}.txt")
|
||||
|
||||
# Проверяем наличие в индексе
|
||||
if cache_key in self.index:
|
||||
# Проверяем наличие в кэше
|
||||
if os.path.exists(cache_file):
|
||||
try:
|
||||
with open(os.path.join(self.cache_dir, cache_key + '.txt'), 'r') as f:
|
||||
with open(cache_file, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except Exception as e:
|
||||
print(f"Ошибка чтения кэша ASCII: {e}")
|
||||
|
||||
# Если нет в кэше, создаем новый
|
||||
ascii_art = image_to_ascii(image_data)
|
||||
|
||||
# Сохраняем в кэш
|
||||
with open(os.path.join(self.cache_dir, cache_key + '.txt'), 'w') as f:
|
||||
try:
|
||||
with open(cache_file, 'w') as f:
|
||||
f.write(ascii_art)
|
||||
|
||||
# Обновляем индекс
|
||||
@ -159,6 +161,8 @@ class AsciiArtCache:
|
||||
'size': len(image_data)
|
||||
}
|
||||
self.save_index()
|
||||
except Exception as e:
|
||||
print(f"Ошибка сохранения ASCII в кэш: {e}")
|
||||
|
||||
return ascii_art
|
||||
|
||||
@ -470,116 +474,118 @@ class SearchEdit(urwid.Edit):
|
||||
|
||||
return result
|
||||
|
||||
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
|
||||
|
||||
# Получаем диалоги
|
||||
try:
|
||||
dialogs = await self.telegram_client.get_dialogs(
|
||||
limit=50,
|
||||
folder=self.current_folder
|
||||
)
|
||||
except Exception as e:
|
||||
print(f"Ошибка получения диалогов: {e}")
|
||||
dialogs = []
|
||||
|
||||
# Фильтруем по поисковому запросу
|
||||
search_query = normalize_text(self.search_edit.get_edit_text().lower())
|
||||
if search_query:
|
||||
filtered_dialogs = []
|
||||
for dialog in dialogs:
|
||||
try:
|
||||
# Получаем имя
|
||||
name = ""
|
||||
if hasattr(dialog.entity, 'title') and dialog.entity.title:
|
||||
name = dialog.entity.title
|
||||
elif hasattr(dialog.entity, 'first_name'):
|
||||
name = dialog.entity.first_name
|
||||
if hasattr(dialog.entity, 'last_name') and dialog.entity.last_name:
|
||||
name += f" {dialog.entity.last_name}"
|
||||
name = normalize_text(name).lower()
|
||||
|
||||
# Получаем последнее сообщение
|
||||
last_message = ""
|
||||
if dialog.message and hasattr(dialog.message, 'message'):
|
||||
last_message = normalize_text(dialog.message.message).lower()
|
||||
|
||||
# Проверяем совпадение
|
||||
if search_query in name or search_query in last_message:
|
||||
filtered_dialogs.append(dialog)
|
||||
|
||||
# Ограничиваем количество результатов для производительности
|
||||
if len(filtered_dialogs) >= 20:
|
||||
break
|
||||
except Exception as e:
|
||||
print(f"Ошибка фильтрации диалога: {e}")
|
||||
|
||||
dialogs = filtered_dialogs
|
||||
|
||||
# Очищаем список
|
||||
self.chat_walker[:] = []
|
||||
|
||||
# Добавляем чаты
|
||||
restored_focus = False
|
||||
for i, dialog in enumerate(dialogs):
|
||||
try:
|
||||
# Получаем имя чата
|
||||
name = ""
|
||||
if hasattr(dialog.entity, 'title') and dialog.entity.title:
|
||||
name = dialog.entity.title
|
||||
elif hasattr(dialog.entity, 'first_name'):
|
||||
name = dialog.entity.first_name
|
||||
if hasattr(dialog.entity, 'last_name') and dialog.entity.last_name:
|
||||
name += f" {dialog.entity.last_name}"
|
||||
else:
|
||||
name = "Без названия"
|
||||
|
||||
# Получаем последнее сообщение
|
||||
message = ""
|
||||
if dialog.message and hasattr(dialog.message, 'message'):
|
||||
message = dialog.message.message
|
||||
|
||||
# Создаем виджет чата
|
||||
chat = ChatWidget(
|
||||
chat_id=dialog.id,
|
||||
name=name,
|
||||
message=message,
|
||||
is_selected=(dialog.id == current_chat_id),
|
||||
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:
|
||||
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:
|
||||
print(f"Ошибка обновления чатов: {e}")
|
||||
# В случае ошибки очищаем список
|
||||
self.chat_walker[:] = []
|
||||
|
||||
class InputEdit(urwid.Edit):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.telegram_client = kwargs.pop('telegram_client', None)
|
||||
self.chat_id = kwargs.pop('chat_id', None)
|
||||
self.users_cache = {} # username -> (full_name, user_id)
|
||||
self.completion_state = None # (start_pos, partial, matches, current_index)
|
||||
self.parent = None
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
async def update_users_cache(self):
|
||||
"""Обновляет кэш пользователей для текущего чата"""
|
||||
try:
|
||||
if not self.chat_id or not self.telegram_client:
|
||||
return
|
||||
|
||||
# Получаем участников чата
|
||||
participants = await self.telegram_client.get_participants(self.chat_id)
|
||||
|
||||
# Обновляем кэш
|
||||
for user in participants:
|
||||
if user.username:
|
||||
full_name = f"{user.first_name}"
|
||||
if user.last_name:
|
||||
full_name += f" {user.last_name}"
|
||||
self.users_cache[user.username.lower()] = (full_name, user.id)
|
||||
except Exception as e:
|
||||
print(f"Ошибка обновления кэша пользователей: {e}")
|
||||
|
||||
def find_username_matches(self, partial):
|
||||
"""Находит совпадения для частичного имени пользователя"""
|
||||
partial = partial.lower()
|
||||
matches = []
|
||||
for username in self.users_cache:
|
||||
if username.startswith(partial):
|
||||
full_name, _ = self.users_cache[username]
|
||||
matches.append((username, full_name))
|
||||
return sorted(matches, key=lambda x: len(x[0])) # Сортируем по длине юзернейма
|
||||
|
||||
def show_completion_menu(self, matches, start_pos, partial):
|
||||
"""Показывает меню автодополнения"""
|
||||
if not matches:
|
||||
self.completion_state = None
|
||||
return
|
||||
|
||||
# Сохраняем состояние автодополнения
|
||||
self.completion_state = (start_pos, partial, matches, 0)
|
||||
|
||||
# Уведомляем родителя о необходимости показать меню
|
||||
if self.parent and hasattr(self.parent, 'show_completion_overlay'):
|
||||
self.parent.show_completion_overlay(matches)
|
||||
|
||||
def hide_completion_menu(self):
|
||||
"""Скрывает меню автодополнения"""
|
||||
self.completion_state = None
|
||||
if self.parent and hasattr(self.parent, 'hide_completion_overlay'):
|
||||
self.parent.hide_completion_overlay()
|
||||
|
||||
def keypress(self, size, key):
|
||||
if key in ('esc', 'up', 'down'):
|
||||
if self.completion_state:
|
||||
if key == 'esc':
|
||||
self.hide_completion_menu()
|
||||
return None
|
||||
elif key in ('up', 'down'):
|
||||
# Передаем управление родителю для навигации по меню
|
||||
if self.parent and hasattr(self.parent, 'handle_completion_navigation'):
|
||||
return self.parent.handle_completion_navigation(key)
|
||||
return key
|
||||
return super().keypress(size, key)
|
||||
|
||||
if key == 'enter' and self.completion_state:
|
||||
# Получаем выбранный вариант из родителя
|
||||
if self.parent and hasattr(self.parent, 'get_selected_completion'):
|
||||
selected = self.parent.get_selected_completion()
|
||||
if selected is not None:
|
||||
start_pos, partial, matches, _ = self.completion_state
|
||||
username, _ = matches[selected]
|
||||
|
||||
# Обновляем текст
|
||||
text = self.get_edit_text()
|
||||
new_text = text[:start_pos] + "@" + username + " "
|
||||
if len(text) > start_pos + len(partial) + 1:
|
||||
new_text += text[start_pos + len(partial) + 1:]
|
||||
self.set_edit_text(new_text)
|
||||
self.set_edit_pos(len(new_text))
|
||||
|
||||
# Скрываем меню
|
||||
self.hide_completion_menu()
|
||||
return None
|
||||
|
||||
result = super().keypress(size, key)
|
||||
|
||||
# Проверяем, нужно ли показать меню автодополнения
|
||||
if result is None and self.users_cache:
|
||||
text = self.get_edit_text()
|
||||
pos = self.edit_pos
|
||||
|
||||
# Ищем @ перед курсором
|
||||
start_pos = text.rfind('@', 0, pos)
|
||||
if start_pos != -1 and start_pos < pos:
|
||||
partial = text[start_pos + 1:pos]
|
||||
if partial:
|
||||
matches = self.find_username_matches(partial)
|
||||
if matches:
|
||||
self.show_completion_menu(matches, start_pos, partial)
|
||||
else:
|
||||
self.hide_completion_menu()
|
||||
else:
|
||||
self.hide_completion_menu()
|
||||
else:
|
||||
self.hide_completion_menu()
|
||||
|
||||
return result
|
||||
|
||||
class TelegramTUI:
|
||||
"""Основной класс приложения"""
|
||||
@ -599,6 +605,8 @@ class TelegramTUI:
|
||||
('error', 'light red', 'black'),
|
||||
('message_selected', 'black', 'light gray'),
|
||||
('input_disabled', 'dark gray', 'black'),
|
||||
('completion_normal', 'white', 'black'),
|
||||
('completion_focus', 'black', 'light gray'),
|
||||
]
|
||||
|
||||
def __init__(self, telegram_client: TelegramClient):
|
||||
@ -624,7 +632,7 @@ class TelegramTUI:
|
||||
self.chat_list = urwid.ListBox(self.chat_walker)
|
||||
self.message_walker = urwid.SimpleFocusListWalker([])
|
||||
self.message_list = urwid.ListBox(self.message_walker)
|
||||
self.input_edit = InputEdit(('header', "Сообщение: "))
|
||||
self.input_edit = InputEdit(('header', "Сообщение: "), telegram_client=telegram_client)
|
||||
|
||||
# Создаем экраны
|
||||
self.auth_widget = urwid.Filler(
|
||||
@ -723,6 +731,10 @@ class TelegramTUI:
|
||||
self.ascii_cache = AsciiArtCache()
|
||||
self.media_cache = MediaCache(max_size_mb=1000) # 1GB по умолчанию
|
||||
|
||||
# Добавляем состояние для меню автодополнения
|
||||
self.completion_listbox = None
|
||||
self.completion_overlay = None
|
||||
|
||||
def switch_screen(self, screen_name: str):
|
||||
"""Переключение между экранами"""
|
||||
self.current_screen = screen_name
|
||||
@ -914,17 +926,29 @@ class TelegramTUI:
|
||||
media_type = 'gif'
|
||||
|
||||
if media_type:
|
||||
# Загружаем медиа
|
||||
# Для фото проверяем сначала кэш ASCII-арта
|
||||
if media_type == 'photo':
|
||||
# Получаем ID фото для кэша
|
||||
photo_id = message.photo.id
|
||||
cache_key = f"photo_{photo_id}"
|
||||
|
||||
# Проверяем кэш ASCII-арта
|
||||
try:
|
||||
with open(os.path.join('pics', f"{cache_key}.txt"), 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
# Если нет в кэше, загружаем и конвертируем
|
||||
media_data = await self.telegram_client.download_media(message.media, bytes)
|
||||
if media_data:
|
||||
ascii_art = self.ascii_cache.get_cached_art(media_data)
|
||||
return ascii_art
|
||||
|
||||
# Для остальных типов медиа
|
||||
media_data = await self.telegram_client.download_media(message.media, bytes)
|
||||
if media_data:
|
||||
# Сохраняем в кэш
|
||||
cached_path = self.media_cache.get_cached_file(media_data, media_type)
|
||||
if cached_path:
|
||||
if media_type == 'photo':
|
||||
# Для фото создаем ASCII-арт
|
||||
with open(cached_path, 'rb') as f:
|
||||
return self.ascii_cache.get_cached_art(f.read())
|
||||
else:
|
||||
# Для других типов возвращаем описание
|
||||
size_mb = len(media_data) / (1024 * 1024)
|
||||
return f"[{media_type.upper()}: {size_mb:.1f}MB - {cached_path.name}]"
|
||||
@ -934,6 +958,20 @@ class TelegramTUI:
|
||||
print(f"Ошибка обработки медиа: {e}")
|
||||
return f"[Ошибка обработки {media_type if media_type else 'медиа'}]"
|
||||
|
||||
async def message_update_loop(self):
|
||||
"""Цикл обновления сообщений"""
|
||||
while True:
|
||||
try:
|
||||
if self.current_chat_id:
|
||||
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)
|
||||
self.last_message_update_time = current_time
|
||||
await asyncio.sleep(1)
|
||||
except Exception as e:
|
||||
print(f"Ошибка в цикле обновления сообщений: {e}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def update_message_list(self, chat_id):
|
||||
"""Обновляет список сообщений"""
|
||||
try:
|
||||
@ -941,40 +979,47 @@ class TelegramTUI:
|
||||
self.message_walker[:] = []
|
||||
return
|
||||
|
||||
# Сохраняем текущий фокус
|
||||
# Сохраняем текущий фокус и ID выбранного сообщения
|
||||
current_focus = self.message_list.focus_position if self.message_walker else None
|
||||
selected_message_id = self.selected_message
|
||||
|
||||
# Получаем сообщения
|
||||
messages = await self.telegram_client.get_messages(
|
||||
entity=chat_id,
|
||||
limit=30 # Уменьшаем лимит для стабильности
|
||||
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}
|
||||
# Сохраняем старые сообщения для переиспользования
|
||||
old_messages = {
|
||||
msg.message_id: msg
|
||||
for msg in self.message_walker
|
||||
}
|
||||
|
||||
# Очищаем список
|
||||
self.message_walker[:] = []
|
||||
# Создаем новый список сообщений
|
||||
new_messages = []
|
||||
new_focus = None
|
||||
|
||||
# Добавляем сообщения
|
||||
for msg in reversed(messages):
|
||||
for i, msg in enumerate(reversed(messages)):
|
||||
try:
|
||||
# Проверяем, было ли это сообщение раньше
|
||||
old_message = old_messages.get(msg.id)
|
||||
if old_message and not msg.photo:
|
||||
# Переиспользуем существующий виджет для не-фото сообщений
|
||||
self.message_walker.append(old_message)
|
||||
# Проверяем, есть ли сообщение в старом списке
|
||||
old_widget = old_messages.get(msg.id)
|
||||
if old_widget:
|
||||
# Переиспользуем существующий виджет
|
||||
new_messages.append(old_widget)
|
||||
if msg.id == selected_message_id:
|
||||
new_focus = len(new_messages) - 1
|
||||
continue
|
||||
|
||||
# Создаем новый виджет только для новых сообщений
|
||||
is_me = False
|
||||
if hasattr(msg, 'from_id') and msg.from_id:
|
||||
if hasattr(msg.from_id, 'user_id'):
|
||||
@ -983,7 +1028,8 @@ class TelegramTUI:
|
||||
text = msg.message if hasattr(msg, 'message') else ""
|
||||
media_data = None
|
||||
|
||||
# Обрабатываем все типы медиа
|
||||
# Проверяем наличие медиа только для новых сообщений
|
||||
if msg.media:
|
||||
media_data = await self.process_media(msg)
|
||||
if media_data:
|
||||
if not text:
|
||||
@ -1017,43 +1063,37 @@ class TelegramTUI:
|
||||
is_me=is_me,
|
||||
send_time=msg.date.strftime("%H:%M"),
|
||||
status=status,
|
||||
is_selected=(msg.id == self.selected_message),
|
||||
is_selected=(msg.id == selected_message_id),
|
||||
media_data=media_data
|
||||
)
|
||||
|
||||
if msg.id in tracked_messages:
|
||||
self.pending_messages[msg.id] = message
|
||||
|
||||
self.message_walker.append(message)
|
||||
new_messages.append(message)
|
||||
if msg.id == selected_message_id:
|
||||
new_focus = len(new_messages) - 1
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка создания виджета сообщения: {e}")
|
||||
|
||||
# Проверяем, изменился ли список сообщений
|
||||
if new_messages:
|
||||
current_messages = list(self.message_walker)
|
||||
if len(current_messages) != len(new_messages) or any(a.message_id != b.message_id for a, b in zip(current_messages, new_messages)):
|
||||
# Обновляем список только если есть изменения
|
||||
self.message_walker[:] = new_messages
|
||||
|
||||
# Восстанавливаем фокус
|
||||
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)
|
||||
if new_focus is not None:
|
||||
self.message_list.set_focus(new_focus)
|
||||
elif new_messages:
|
||||
self.message_list.set_focus(len(new_messages) - 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 = "vv" # Доставлено
|
||||
else:
|
||||
widget.status = "v" # Отправлено
|
||||
widget.update_widget()
|
||||
# Если сообщение доставлено, удаляем из отслеживания
|
||||
if widget.status == "vv":
|
||||
del self.pending_messages[message.id]
|
||||
except Exception as e:
|
||||
print(f"Ошибка обновления статуса: {e}")
|
||||
# В случае ошибки НЕ очищаем список
|
||||
# self.message_walker[:] = []
|
||||
|
||||
async def check_chat_permissions(self, chat_id):
|
||||
"""Проверяет права на отправку сообщений в чате"""
|
||||
@ -1083,17 +1123,51 @@ class TelegramTUI:
|
||||
"""Обновляет видимость поля ввода"""
|
||||
if self.can_send_messages:
|
||||
if self.replying_to:
|
||||
self.input_edit = InputEdit(('header', f"Ответ на '{self.replying_to[:30]}...': "))
|
||||
self.input_edit = InputEdit(
|
||||
('header', f"Ответ на '{self.replying_to[:30]}...': "),
|
||||
telegram_client=self.telegram_client,
|
||||
chat_id=self.current_chat_id
|
||||
)
|
||||
else:
|
||||
self.input_edit = InputEdit(('header', "Сообщение: "))
|
||||
self.input_edit = InputEdit(
|
||||
('header', "Сообщение: "),
|
||||
telegram_client=self.telegram_client,
|
||||
chat_id=self.current_chat_id
|
||||
)
|
||||
# Устанавливаем ссылку на родительский виджет
|
||||
self.input_edit.parent = self
|
||||
# Обновляем кэш пользователей
|
||||
asyncio.create_task(self.input_edit.update_users_cache())
|
||||
else:
|
||||
self.input_edit = InputEdit(('input_disabled', "Отправка сообщений недоступна"))
|
||||
self.input_edit = InputEdit(
|
||||
('input_disabled', "Отправка сообщений недоступна"),
|
||||
telegram_client=self.telegram_client,
|
||||
chat_id=self.current_chat_id
|
||||
)
|
||||
self.input_edit.parent = self
|
||||
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 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 handle_chat_input(self, key):
|
||||
"""Обработка ввода в экране чатов"""
|
||||
try:
|
||||
@ -1141,11 +1215,14 @@ class TelegramTUI:
|
||||
# Проверяем права при открытии чата
|
||||
await self.check_chat_permissions(focused.chat_id)
|
||||
|
||||
# Сбрасываем таймеры обновления перед загрузкой сообщений
|
||||
self.last_message_update_time = 0
|
||||
self.last_update_time = 0
|
||||
|
||||
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}")
|
||||
|
||||
@ -1153,10 +1230,6 @@ class TelegramTUI:
|
||||
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(
|
||||
@ -1168,6 +1241,10 @@ class TelegramTUI:
|
||||
status="⋯" # Отправляется
|
||||
)
|
||||
|
||||
# Добавляем сообщение в список сразу
|
||||
self.message_walker.append(msg_widget)
|
||||
self.message_list.set_focus(len(self.message_walker) - 1)
|
||||
|
||||
# Отправляем сообщение
|
||||
sent_message = await self.telegram_client.send_message(
|
||||
self.current_chat_id,
|
||||
@ -1179,9 +1256,9 @@ class TelegramTUI:
|
||||
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)
|
||||
# Обновляем статус
|
||||
msg_widget.status = "✓"
|
||||
msg_widget.update_widget()
|
||||
|
||||
# Очищаем поле ввода и сбрасываем ответ
|
||||
self.input_edit.set_edit_text("")
|
||||
@ -1189,8 +1266,14 @@ class TelegramTUI:
|
||||
self.replying_to = None
|
||||
self.update_input_visibility()
|
||||
|
||||
# Форсируем обновление списка сообщений
|
||||
await self.update_message_list(self.current_chat_id)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Ошибка отправки сообщения: {e}")
|
||||
# Удаляем виджет сообщения в случае ошибки
|
||||
if msg_widget in self.message_walker:
|
||||
self.message_walker.remove(msg_widget)
|
||||
|
||||
elif key == 'tab':
|
||||
if self.focused_element == "search":
|
||||
@ -1267,32 +1350,17 @@ class TelegramTUI:
|
||||
async def chat_update_loop():
|
||||
while True:
|
||||
try:
|
||||
# Обновляем только если не в процессе отправки сообщения
|
||||
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)
|
||||
await asyncio.sleep(3)
|
||||
except Exception as e:
|
||||
print(f"Ошибка в цикле обновления чатов: {e}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def message_update_loop():
|
||||
while True:
|
||||
try:
|
||||
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)
|
||||
self.last_message_update_time = current_time
|
||||
await asyncio.sleep(0.5)
|
||||
except Exception as e:
|
||||
print(f"Ошибка в цикле обновления сообщений: {e}")
|
||||
await asyncio.sleep(0.5)
|
||||
await asyncio.sleep(3)
|
||||
|
||||
self.chat_update_task = asyncio.create_task(chat_update_loop())
|
||||
self.message_update_task = asyncio.create_task(message_update_loop())
|
||||
self.message_update_task = asyncio.create_task(self.message_update_loop())
|
||||
|
||||
async def stop_auto_updates(self):
|
||||
"""Останавливает автоматическое обновление"""
|
||||
@ -1339,6 +1407,60 @@ class TelegramTUI:
|
||||
await self.telegram_client.disconnect()
|
||||
print("Отключено от Telegram")
|
||||
|
||||
def show_completion_overlay(self, matches):
|
||||
"""Показывает оверлей с меню автодополнения"""
|
||||
# Создаем список вариантов
|
||||
items = []
|
||||
for username, full_name in matches:
|
||||
text = f"@{username} ({full_name})"
|
||||
items.append(urwid.AttrMap(
|
||||
urwid.Text(text),
|
||||
'completion_normal',
|
||||
'completion_focus'
|
||||
))
|
||||
|
||||
# Создаем меню
|
||||
self.completion_listbox = urwid.ListBox(urwid.SimpleFocusListWalker(items))
|
||||
box = urwid.LineBox(
|
||||
urwid.BoxAdapter(self.completion_listbox, min(len(matches), 5)),
|
||||
title="Автодополнение"
|
||||
)
|
||||
|
||||
# Создаем оверлей
|
||||
self.completion_overlay = urwid.Overlay(
|
||||
box,
|
||||
self.main_widget,
|
||||
'center', ('relative', 50),
|
||||
'middle', ('relative', 30)
|
||||
)
|
||||
|
||||
# Обновляем главный виджет
|
||||
self.main_widget = self.completion_overlay
|
||||
|
||||
def hide_completion_overlay(self):
|
||||
"""Скрывает оверлей с меню автодополнения"""
|
||||
if self.completion_overlay:
|
||||
self.main_widget = self.completion_overlay.bottom_w
|
||||
self.completion_overlay = None
|
||||
self.completion_listbox = None
|
||||
|
||||
def handle_completion_navigation(self, key):
|
||||
"""Обрабатывает навигацию по меню автодополнения"""
|
||||
if self.completion_listbox:
|
||||
if key == 'up' and self.completion_listbox.focus_position > 0:
|
||||
self.completion_listbox.focus_position -= 1
|
||||
return None
|
||||
elif key == 'down' and self.completion_listbox.focus_position < len(self.completion_listbox.body) - 1:
|
||||
self.completion_listbox.focus_position += 1
|
||||
return None
|
||||
return key
|
||||
|
||||
def get_selected_completion(self):
|
||||
"""Возвращает индекс выбранного варианта автодополнения"""
|
||||
if self.completion_listbox:
|
||||
return self.completion_listbox.focus_position
|
||||
return None
|
||||
|
||||
async def main():
|
||||
# Загружаем переменные окружения
|
||||
load_dotenv()
|
||||
|
Loading…
x
Reference in New Issue
Block a user