mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 11:20:31 +00:00
Что-то поменял обратно, тем не менее, спасибо контрибьюторам
This commit is contained in:
parent
c01d5954dc
commit
e523fce7bd
2
.gitignore
vendored
2
.gitignore
vendored
@ -5,3 +5,5 @@ __pycache__
|
|||||||
*/__pycache__
|
*/__pycache__
|
||||||
tokens.py
|
tokens.py
|
||||||
.env
|
.env
|
||||||
|
.venv
|
||||||
|
.python-version
|
||||||
|
@ -38,5 +38,5 @@ cp .env.example .env
|
|||||||
## Запуск
|
## Запуск
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python src/app.py
|
./main.py
|
||||||
```
|
```
|
||||||
|
@ -9,13 +9,16 @@ from rich.console import Console
|
|||||||
from src.screens import AuthScreen, ChatScreen
|
from src.screens import AuthScreen, ChatScreen
|
||||||
|
|
||||||
# Настройка консоли для корректной работы с Unicode
|
# Настройка консоли для корректной работы с Unicode
|
||||||
|
"""
|
||||||
console = Console(force_terminal=True, color_system="auto")
|
console = Console(force_terminal=True, color_system="auto")
|
||||||
sys.stdout = console
|
sys.stdout = console
|
||||||
|
"""
|
||||||
|
# спойлер: не помогло
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
|
||||||
api_id = os.getenv("API_ID")
|
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:
|
if not api_id or not api_hash:
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
@ -33,7 +36,6 @@ class TelegramTUI(App):
|
|||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.console = console
|
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.telegram_client = TelegramClient("user", api_id, api_hash)
|
self.telegram_client = TelegramClient("user", api_id, api_hash)
|
||||||
@ -52,3 +54,6 @@ class TelegramTUI(App):
|
|||||||
async def on_exit_app(self):
|
async def on_exit_app(self):
|
||||||
await self.telegram_client.disconnect()
|
await self.telegram_client.disconnect()
|
||||||
return super()._on_exit_app()
|
return super()._on_exit_app()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise Exception("Запущен не тот файл. Запустите main.py.")
|
||||||
|
@ -8,6 +8,7 @@ from telethon.errors import SessionPasswordNeededError
|
|||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
from src.widgets import Dialog, Chat, normalize_text
|
from src.widgets import Dialog, Chat, normalize_text
|
||||||
from textual import log
|
from textual import log
|
||||||
|
from textual.keys import Keys, _character_to_key
|
||||||
|
|
||||||
class AuthScreen(Screen):
|
class AuthScreen(Screen):
|
||||||
"""Класс экрана логина в аккаунт"""
|
"""Класс экрана логина в аккаунт"""
|
||||||
@ -62,6 +63,13 @@ class AuthScreen(Screen):
|
|||||||
|
|
||||||
class ChatScreen(Screen):
|
class ChatScreen(Screen):
|
||||||
"""Класс экрана чатов, он же основной экран приложения"""
|
"""Класс экрана чатов, он же основной экран приложения"""
|
||||||
|
|
||||||
|
BINDINGS = [
|
||||||
|
(Keys.Tab, "log(\"Нажат таб\")", "Переключение фокуса"),
|
||||||
|
(Keys.Enter, "log(\"Нажат энтер\")", "Открыть"),
|
||||||
|
(Keys.Escape, "log(\"Нажат эскейп\")", "Назад"),
|
||||||
|
(_character_to_key("/"), "log(\"Нажат слэш\")", "Поиск")
|
||||||
|
]
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -86,7 +94,6 @@ class ChatScreen(Screen):
|
|||||||
.query_one("#chat_container")
|
.query_one("#chat_container")
|
||||||
|
|
||||||
self.search_input = self.query_one("#search_input")
|
self.search_input = self.query_one("#search_input")
|
||||||
self.help_label = self.query_one("#help_label")
|
|
||||||
|
|
||||||
log("Первоначальная загрузка виджетов чатов...")
|
log("Первоначальная загрузка виджетов чатов...")
|
||||||
self.mount_chats(
|
self.mount_chats(
|
||||||
@ -140,7 +147,8 @@ class ChatScreen(Screen):
|
|||||||
if self.search_query:
|
if self.search_query:
|
||||||
dialogs = [
|
dialogs = [
|
||||||
d for d in 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)
|
limit = len(dialogs)
|
||||||
@ -152,7 +160,8 @@ class ChatScreen(Screen):
|
|||||||
chat.msg = normalize_text(str(dialogs[i].message.message))
|
chat.msg = normalize_text(str(dialogs[i].message.message))
|
||||||
chat.peer_id = dialogs[i].id
|
chat.peer_id = dialogs[i].id
|
||||||
chat.is_selected = (i == self.selected_chat_index)
|
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
|
self.is_chat_update_blocked = False
|
||||||
log("Чаты обновлены")
|
log("Чаты обновлены")
|
||||||
@ -166,7 +175,7 @@ class ChatScreen(Screen):
|
|||||||
self.update_chat_list()
|
self.update_chat_list()
|
||||||
|
|
||||||
def on_key(self, event: Key) -> None:
|
def on_key(self, event: Key) -> None:
|
||||||
if event.key == "tab":
|
if event.key == Keys.Tab:
|
||||||
# Переключаем фокус между элементами
|
# Переключаем фокус между элементами
|
||||||
if self.focused_element == "search":
|
if self.focused_element == "search":
|
||||||
self.focused_element = "chat_list"
|
self.focused_element = "chat_list"
|
||||||
@ -185,44 +194,43 @@ class ChatScreen(Screen):
|
|||||||
if not chats:
|
if not chats:
|
||||||
return
|
return
|
||||||
|
|
||||||
if event.key == "up":
|
match event.key:
|
||||||
self.selected_chat_index = max(0, self.selected_chat_index - 1)
|
case Keys.Up:
|
||||||
for i, chat in enumerate(chats):
|
self.selected_chat_index = max(0, self.selected_chat_index - 1)
|
||||||
chat.is_selected = (i == self.selected_chat_index)
|
for i, chat in enumerate(chats):
|
||||||
chat.is_focused = (i == self.selected_chat_index)
|
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)
|
selected_chat = chats[self.selected_chat_index]
|
||||||
elif event.key == "down":
|
self.chat_container.scroll_to(selected_chat, animate=False)
|
||||||
self.selected_chat_index = min(len(chats) - 1, self.selected_chat_index + 1)
|
case Keys.Down:
|
||||||
for i, chat in enumerate(chats):
|
self.selected_chat_index = min(len(chats) - 1, self.selected_chat_index + 1)
|
||||||
chat.is_selected = (i == self.selected_chat_index)
|
for i, chat in enumerate(chats):
|
||||||
chat.is_focused = (i == self.selected_chat_index)
|
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)
|
selected_chat = chats[self.selected_chat_index]
|
||||||
elif event.key == "enter":
|
self.chat_container.scroll_to(selected_chat, animate=False)
|
||||||
chats[self.selected_chat_index].on_click()
|
case Keys.Enter:
|
||||||
elif event.key == "escape":
|
chats[self.selected_chat_index].on_click()
|
||||||
# Возвращаемся к списку чатов
|
case Keys.Escape:
|
||||||
self.app.pop_screen()
|
# Возвращаемся к списку чатов
|
||||||
self.app.push_screen("chats")
|
self.app.pop_screen()
|
||||||
elif event.key == "/":
|
self.app.push_screen("chats")
|
||||||
# Фокус на поиск
|
case "/": #Не работает: нужен кейкод слэша
|
||||||
self.focused_element = "search"
|
# Фокус на поиск
|
||||||
self.search_input.focus()
|
self.focused_element = "search"
|
||||||
self.update_chat_list()
|
self.search_input.focus()
|
||||||
|
self.update_chat_list()
|
||||||
|
|
||||||
def compose(self):
|
def compose(self):
|
||||||
yield Footer()
|
yield Footer()
|
||||||
with Horizontal(id="main_container"):
|
with Horizontal(id="main_container"):
|
||||||
with Vertical(id="chats"):
|
with Vertical(id="chats"):
|
||||||
yield Label(
|
|
||||||
"Навигация: Tab - переключение фокуса, ↑↓ - выбор чата, Enter - открыть, Esc - назад, / - поиск",
|
|
||||||
id="help_label",
|
|
||||||
classes="help-text"
|
|
||||||
)
|
|
||||||
yield Input(placeholder=normalize_text("Поиск чатов..."), id="search_input")
|
yield Input(placeholder=normalize_text("Поиск чатов..."), id="search_input")
|
||||||
yield VerticalScroll(id="chat_container")
|
yield VerticalScroll(id="chat_container")
|
||||||
yield ContentSwitcher(id="dialog_switcher")
|
yield ContentSwitcher(id="dialog_switcher")
|
||||||
#yield Dialog(telegram_client=self.telegram_client)
|
#yield Dialog(telegram_client=self.telegram_client)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise Exception("Запущен не тот файл. Запустите main.py.")
|
||||||
|
@ -13,8 +13,9 @@ import emoji
|
|||||||
import os
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
import pywhatkit as kit
|
#import pywhatkit as kit
|
||||||
from textual import log
|
from textual import log
|
||||||
|
from warnings import deprecated
|
||||||
|
|
||||||
def remove_emoji(text: str) -> str:
|
def remove_emoji(text: str) -> str:
|
||||||
"""Удаляет эмодзи из текста"""
|
"""Удаляет эмодзи из текста"""
|
||||||
@ -29,11 +30,12 @@ def normalize_text(text: str) -> str:
|
|||||||
# Удаляем эмодзи
|
# Удаляем эмодзи
|
||||||
text = remove_emoji(text)
|
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
|
# Нормализуем Unicode
|
||||||
text = unicodedata.normalize('NFKC', text)
|
text = unicodedata.normalize('NFKC', text)
|
||||||
# Заменяем специальные символы на их ASCII-эквиваленты
|
# Заменяем специальные символы на их ASCII-эквиваленты
|
||||||
text = text.replace('—', '-').replace('–', '-').replace('…', '...')
|
text = text.replace('—', '-').replace('–', '-')
|
||||||
# Удаляем все непечатаемые символы
|
# Удаляем все непечатаемые символы
|
||||||
text = ''.join(char for char in text if char.isprintable())
|
text = ''.join(char for char in text if char.isprintable())
|
||||||
return text
|
return text
|
||||||
@ -47,6 +49,7 @@ def safe_ascii(text: str) -> str:
|
|||||||
# Оставляем только ASCII символы и пробелы
|
# Оставляем только ASCII символы и пробелы
|
||||||
return ''.join(char for char in text if ord(char) < 128 or char.isspace())
|
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:
|
def convert_image_to_ascii(image_path: str, width: int = 50) -> str:
|
||||||
"""Конвертирует изображение в ASCII-арт"""
|
"""Конвертирует изображение в ASCII-арт"""
|
||||||
try:
|
try:
|
||||||
@ -123,11 +126,21 @@ class Chat(Widget):
|
|||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Horizontal(classes="chat-item"):
|
with Horizontal(classes="chat-item"):
|
||||||
|
"""
|
||||||
# Используем ASCII-символы для рамки
|
# Используем 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():
|
with Vertical():
|
||||||
yield Label(normalize_text(self.username), id="name")
|
yield Label(normalize_text(self.username), id="name")
|
||||||
yield Label(normalize_text(self.msg), id="last_msg")
|
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:
|
def on_mouse_enter(self) -> None:
|
||||||
self.add_class("hover")
|
self.add_class("hover")
|
||||||
@ -324,3 +337,6 @@ class Message(Widget):
|
|||||||
self.classes = "is_me_true"
|
self.classes = "is_me_true"
|
||||||
else:
|
else:
|
||||||
self.classes = "is_me_false"
|
self.classes = "is_me_false"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise Exception("Запущен не тот файл. Запустите main.py.")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user