From e523fce7bd4d4647c194003072b8996faae90f5b Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 29 Mar 2025 01:58:39 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A7=D1=82=D0=BE-=D1=82=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB=20=D0=BE=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D0=BD=D0=BE,=20=D1=82=D0=B5=D0=BC=20=D0=BD=D0=B5=20=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=B5=D0=B5,=20=D1=81=D0=BF=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=B1=D0=BE=20=D0=BA=D0=BE=D0=BD=D1=82=D1=80=D0=B8=D0=B1=D1=8C?= =?UTF-8?q?=D1=8E=D1=82=D0=BE=D1=80=D0=B0=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ README.md | 2 +- src/app.py | 9 ++++-- src/screens.py | 80 +++++++++++++++++++++++++++----------------------- src/widgets.py | 24 ++++++++++++--- 5 files changed, 74 insertions(+), 43 deletions(-) diff --git a/.gitignore b/.gitignore index b87114d..d195cda 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ __pycache__ */__pycache__ tokens.py .env +.venv +.python-version diff --git a/README.md b/README.md index 5f26c32..9b2b307 100644 --- a/README.md +++ b/README.md @@ -38,5 +38,5 @@ cp .env.example .env ## Запуск ```bash -python src/app.py +./main.py ``` diff --git a/src/app.py b/src/app.py index be900d3..50585dd 100644 --- a/src/app.py +++ b/src/app.py @@ -9,13 +9,16 @@ from rich.console import Console from src.screens import AuthScreen, ChatScreen # Настройка консоли для корректной работы с Unicode +""" console = Console(force_terminal=True, color_system="auto") sys.stdout = console +""" +# спойлер: не помогло load_dotenv() api_id = os.getenv("API_ID") -api_hash = os.getenv("API_HASH") +api_hash = os.getenv("API_HASH") if not api_id or not api_hash: raise ValueError( @@ -33,7 +36,6 @@ class TelegramTUI(App): def __init__(self): super().__init__() - self.console = console async def on_mount(self) -> None: self.telegram_client = TelegramClient("user", api_id, api_hash) @@ -52,3 +54,6 @@ class TelegramTUI(App): async def on_exit_app(self): await self.telegram_client.disconnect() return super()._on_exit_app() + +if __name__ == "__main__": + raise Exception("Запущен не тот файл. Запустите main.py.") diff --git a/src/screens.py b/src/screens.py index 5d7fc9d..27dd204 100644 --- a/src/screens.py +++ b/src/screens.py @@ -8,6 +8,7 @@ from telethon.errors import SessionPasswordNeededError from telethon import TelegramClient, events from src.widgets import Dialog, Chat, normalize_text from textual import log +from textual.keys import Keys, _character_to_key class AuthScreen(Screen): """Класс экрана логина в аккаунт""" @@ -62,6 +63,13 @@ class AuthScreen(Screen): class ChatScreen(Screen): """Класс экрана чатов, он же основной экран приложения""" + + BINDINGS = [ + (Keys.Tab, "log(\"Нажат таб\")", "Переключение фокуса"), + (Keys.Enter, "log(\"Нажат энтер\")", "Открыть"), + (Keys.Escape, "log(\"Нажат эскейп\")", "Назад"), + (_character_to_key("/"), "log(\"Нажат слэш\")", "Поиск") + ] def __init__( self, @@ -86,7 +94,6 @@ class ChatScreen(Screen): .query_one("#chat_container") self.search_input = self.query_one("#search_input") - self.help_label = self.query_one("#help_label") log("Первоначальная загрузка виджетов чатов...") self.mount_chats( @@ -140,7 +147,8 @@ class ChatScreen(Screen): if self.search_query: dialogs = [ d for d in dialogs - if self.search_query.lower() in normalize_text(d.name).lower() + if self.search_query.lower() in \ + normalize_text(d.name).lower() ] limit = len(dialogs) @@ -152,7 +160,8 @@ class ChatScreen(Screen): chat.msg = normalize_text(str(dialogs[i].message.message)) chat.peer_id = dialogs[i].id chat.is_selected = (i == self.selected_chat_index) - chat.is_focused = (self.focused_element == "chat_list" and i == self.selected_chat_index) + chat.is_focused = (self.focused_element == "chat_list" and \ + i == self.selected_chat_index) self.is_chat_update_blocked = False log("Чаты обновлены") @@ -166,7 +175,7 @@ class ChatScreen(Screen): self.update_chat_list() def on_key(self, event: Key) -> None: - if event.key == "tab": + if event.key == Keys.Tab: # Переключаем фокус между элементами if self.focused_element == "search": self.focused_element = "chat_list" @@ -185,44 +194,43 @@ class ChatScreen(Screen): if not chats: return - if event.key == "up": - self.selected_chat_index = max(0, self.selected_chat_index - 1) - for i, chat in enumerate(chats): - chat.is_selected = (i == self.selected_chat_index) - chat.is_focused = (i == self.selected_chat_index) - # Прокручиваем к выбранному чату - selected_chat = chats[self.selected_chat_index] - self.chat_container.scroll_to(selected_chat, animate=False) - elif event.key == "down": - self.selected_chat_index = min(len(chats) - 1, self.selected_chat_index + 1) - for i, chat in enumerate(chats): - chat.is_selected = (i == self.selected_chat_index) - chat.is_focused = (i == self.selected_chat_index) - # Прокручиваем к выбранному чату - selected_chat = chats[self.selected_chat_index] - self.chat_container.scroll_to(selected_chat, animate=False) - elif event.key == "enter": - chats[self.selected_chat_index].on_click() - elif event.key == "escape": - # Возвращаемся к списку чатов - self.app.pop_screen() - self.app.push_screen("chats") - elif event.key == "/": - # Фокус на поиск - self.focused_element = "search" - self.search_input.focus() - self.update_chat_list() + match event.key: + case Keys.Up: + self.selected_chat_index = max(0, self.selected_chat_index - 1) + for i, chat in enumerate(chats): + chat.is_selected = (i == self.selected_chat_index) + chat.is_focused = (i == self.selected_chat_index) + # Прокручиваем к выбранному чату + selected_chat = chats[self.selected_chat_index] + self.chat_container.scroll_to(selected_chat, animate=False) + case Keys.Down: + self.selected_chat_index = min(len(chats) - 1, self.selected_chat_index + 1) + for i, chat in enumerate(chats): + chat.is_selected = (i == self.selected_chat_index) + chat.is_focused = (i == self.selected_chat_index) + # Прокручиваем к выбранному чату + selected_chat = chats[self.selected_chat_index] + self.chat_container.scroll_to(selected_chat, animate=False) + case Keys.Enter: + chats[self.selected_chat_index].on_click() + case Keys.Escape: + # Возвращаемся к списку чатов + self.app.pop_screen() + self.app.push_screen("chats") + case "/": #Не работает: нужен кейкод слэша + # Фокус на поиск + self.focused_element = "search" + self.search_input.focus() + self.update_chat_list() def compose(self): yield Footer() with Horizontal(id="main_container"): with Vertical(id="chats"): - yield Label( - "Навигация: Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть, Esc - назад, / - поиск", - id="help_label", - classes="help-text" - ) yield Input(placeholder=normalize_text("Поиск чатов..."), id="search_input") yield VerticalScroll(id="chat_container") yield ContentSwitcher(id="dialog_switcher") #yield Dialog(telegram_client=self.telegram_client) + +if __name__ == "__main__": + raise Exception("Запущен не тот файл. Запустите main.py.") diff --git a/src/widgets.py b/src/widgets.py index 613e7d8..034652a 100644 --- a/src/widgets.py +++ b/src/widgets.py @@ -13,8 +13,9 @@ import emoji import os import tempfile from PIL import Image -import pywhatkit as kit +#import pywhatkit as kit from textual import log +from warnings import deprecated def remove_emoji(text: str) -> str: """Удаляет эмодзи из текста""" @@ -29,11 +30,12 @@ def normalize_text(text: str) -> str: # Удаляем эмодзи text = remove_emoji(text) # Удаляем все управляющие символы - text = ''.join(char for char in text if unicodedata.category(char)[0] != 'C') + text = ''\ + .join(char for char in text if unicodedata.category(char)[0] != 'C') # Нормализуем Unicode text = unicodedata.normalize('NFKC', text) # Заменяем специальные символы на их ASCII-эквиваленты - text = text.replace('—', '-').replace('–', '-').replace('…', '...') + text = text.replace('—', '-').replace('–', '-') # Удаляем все непечатаемые символы text = ''.join(char for char in text if char.isprintable()) return text @@ -47,6 +49,7 @@ def safe_ascii(text: str) -> str: # Оставляем только ASCII символы и пробелы return ''.join(char for char in text if ord(char) < 128 or char.isspace()) +@deprecated("Не работает на моём компьютере.") def convert_image_to_ascii(image_path: str, width: int = 50) -> str: """Конвертирует изображение в ASCII-арт""" try: @@ -123,11 +126,21 @@ class Chat(Widget): def compose(self) -> ComposeResult: with Horizontal(classes="chat-item"): + """ # Используем ASCII-символы для рамки - yield Label(f"+---+\n| {safe_ascii(self.username[:1].upper()):1} |\n+---+") + yield Label( + f"┌───┐\n│ {normalize_text( + self.username[:1].upper() + ):1} │\n└───┘" + ) with Vertical(): yield Label(normalize_text(self.username), id="name") yield Label(normalize_text(self.msg), id="last_msg") + """ + yield Label(f"┌───┐\n│ {self.username[:1].upper():1} │\n└───┘") + with Vertical(): + yield Label(self.username, id="name") + yield Label(self.msg, id="last_msg") def on_mouse_enter(self) -> None: self.add_class("hover") @@ -324,3 +337,6 @@ class Message(Widget): self.classes = "is_me_true" else: self.classes = "is_me_false" + +if __name__ == "__main__": + raise Exception("Запущен не тот файл. Запустите main.py.")