mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 11:20:31 +00:00
my new shitty code😌
This commit is contained in:
parent
54704d48e3
commit
80d42fcacb
11
src/app.py
11
src/app.py
@ -38,7 +38,7 @@ class Talc(App):
|
||||
(Keys.ControlF, "notify(\"Нажата кнопка папок\")", locale["folders"]),
|
||||
(Keys.Tab, "notify(\"Нажат таб\")", locale["switch_focus"]),
|
||||
(Keys.Enter, "notify(\"Нажат энтер\")", locale["enter"]),
|
||||
(Keys.Escape, "notify(\"Нажат эскейп\")", locale["back"]),
|
||||
(Keys.Escape, "show_start_screen()", locale["back"]),
|
||||
(_character_to_key("/"), "notify(\"Нажат слэш\")", locale["search"])
|
||||
]
|
||||
|
||||
@ -51,9 +51,11 @@ class Talc(App):
|
||||
):
|
||||
super().__init__(driver_class, css_path, watch_css, ansi_color)
|
||||
self.locale = locale
|
||||
self.title = self.locale["talc"]
|
||||
self.timezone = timezone(timedelta(hours=int(UTC_OFFSET))) # type: ignore
|
||||
self.CHATS_LIMIT = CHATS_LIMIT # type: ignore
|
||||
self.MESSAGES_LIMIT = MESSAGES_LIMIT # type: ignore
|
||||
self.msg_buffer = None
|
||||
|
||||
async def on_mount(self) -> None:
|
||||
self.telegram_client = TelegramClient(
|
||||
@ -63,8 +65,8 @@ class Talc(App):
|
||||
)
|
||||
await self.telegram_client.connect()
|
||||
|
||||
chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
||||
self.install_screen(chat_screen, name="chats")
|
||||
self.chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
||||
self.install_screen(self.chat_screen, name="chats")
|
||||
|
||||
if not await self.telegram_client.is_user_authorized():
|
||||
auth_screen = AuthScreen(telegram_client=self.telegram_client)
|
||||
@ -75,6 +77,9 @@ class Talc(App):
|
||||
|
||||
self.scroll_sensitivity_y = 1.0
|
||||
|
||||
def show_start_screen(self) -> None:
|
||||
self.chat_screen.switcher.current = "start_label"
|
||||
|
||||
async def on_exit_app(self):
|
||||
await self.telegram_client.disconnect()
|
||||
return super()._on_exit_app()
|
||||
|
@ -1,3 +1,3 @@
|
||||
codes = ["auth_greeting", "phone_number", "code", "password", "you", "mention", "media", "message", "folders", "switch_focus", "enter", "back", "search", "start_converse"]
|
||||
ru = ["Добро пожаловать в Тальк", "Номер телефона", "Код", "Пароль", "Вы", "Вас упомянули", "Медиа", "Сообщение", "Папки", "Переключение фокуса", "Открыть", "Назад", "Поиск", "Нажмите на чат в панели слева, чтобы начать общаться"]
|
||||
en = ["Welcome to Talc", "Phone number", "Code", "Password", "You", "You got mentioned", "Media", "Message", "Folders", "Switch focus", "Enter", "Back", "Search", "Click on the chat to start conversation"]
|
||||
codes = ["auth_greeting", "phone_number", "code", "password", "you", "mention", "media", "message", "folders", "switch_focus", "enter", "back", "search", "start_converse", "talc"]
|
||||
ru = ["Добро пожаловать в Тальк", "Номер телефона", "Код", "Пароль", "Вы", "Вас упомянули", "Медиа", "Сообщение", "Папки", "Переключение фокуса", "Открыть", "Назад", "Поиск", "Нажмите на чат в панели слева, чтобы начать общаться", "Тальк"]
|
||||
en = ["Welcome to Talc", "Phone number", "Code", "Password", "You", "You got mentioned", "Media", "Message", "Folders", "Switch focus", "Enter", "Back", "Search", "Click on the chat to start conversation", "Talc"]
|
@ -4,11 +4,12 @@ from textual.screen import Screen
|
||||
from textual.widgets import Label, Input, Footer, Static, ContentSwitcher
|
||||
from textual.containers import Vertical, Horizontal, VerticalScroll
|
||||
from textual.app import ComposeResult
|
||||
from textual.css.query import NoMatches
|
||||
from telethon.errors import SessionPasswordNeededError
|
||||
from telethon.utils import get_display_name
|
||||
from telethon import TelegramClient, events
|
||||
from src.widgets import Chat
|
||||
from os import system, getenv
|
||||
from telethon.utils import get_display_name
|
||||
|
||||
class AuthScreen(Screen):
|
||||
"""Класс экрана логина в аккаунт"""
|
||||
@ -131,7 +132,10 @@ class ChatScreen(Screen):
|
||||
if limit > chats_amount:
|
||||
# Маунт недостающих, если чатов меньше, чем нужно
|
||||
for i in range(limit - chats_amount):
|
||||
chat = Chat(id=f"chat-{i + chats_amount + 1}")
|
||||
chat = Chat(
|
||||
id = f"chat-{i + chats_amount + 1}",
|
||||
number = i + chats_amount + 1
|
||||
)
|
||||
self.chat_container.mount(chat)
|
||||
elif limit < chats_amount:
|
||||
# Удаление лишних, если чатов больше, чем нужно
|
||||
@ -142,7 +146,7 @@ class ChatScreen(Screen):
|
||||
print("Виджеты чатов загружены")
|
||||
|
||||
async def update_chat_list(self, event = None) -> None:
|
||||
"""Функция обновления чатов (и уведомления)"""
|
||||
"""Функция обновления чатов"""
|
||||
print("Запрос обновления чатов")
|
||||
|
||||
if not self.is_chat_update_blocked:
|
||||
@ -159,13 +163,18 @@ class ChatScreen(Screen):
|
||||
|
||||
# Изменение надписей в виджетах чатов
|
||||
for i in range(limit):
|
||||
chat = self.chat_container.query_one(f"#chat-{i + 1}")
|
||||
chat = self.chat_container.query(".chat")[i]
|
||||
|
||||
chat.peername = str(dialogs[i].name)
|
||||
chat.is_group = dialogs[i].is_group
|
||||
chat.is_channel = dialogs[i].is_channel
|
||||
chat.peer_id = dialogs[i].id
|
||||
|
||||
if not (self.switcher.current in ("start_label")) \
|
||||
and chat.peer_id == int(self.switcher.current[7:]):
|
||||
self.remove_chat_lint()
|
||||
chat.add_class("selected_chat")
|
||||
|
||||
try:
|
||||
is_my_msg = \
|
||||
dialogs[i].message.from_id.user_id == self.me_id
|
||||
@ -187,20 +196,12 @@ class ChatScreen(Screen):
|
||||
else:
|
||||
chat.msg = str(dialogs[i].message.message)
|
||||
|
||||
if self.switcher.current is not None:
|
||||
current_dialog = \
|
||||
self.switcher.query_one(f"#{self.switcher.current}")
|
||||
if chat.peer_id == int(current_dialog.id[7:]):
|
||||
chat.add_class("selected_chat")
|
||||
else:
|
||||
chat.remove_class("selected_chat")
|
||||
|
||||
self.is_chat_update_blocked = False
|
||||
print("Чаты обновлены")
|
||||
else:
|
||||
print("Обновление чатов невозможно: уже выполняется")
|
||||
|
||||
if self.switcher.current is not None:
|
||||
if not (self.switcher.current in ("start_label")):
|
||||
current_dialog = \
|
||||
self.switcher.query_one(f"#{self.switcher.current}")
|
||||
await current_dialog.update_dialog()
|
||||
@ -211,16 +212,27 @@ class ChatScreen(Screen):
|
||||
if int(self.DO_NOTIFY) and not self.app.focused and event.mentioned:
|
||||
system(f"notify-send \"{self.locale["mention"]}\" Talc")
|
||||
|
||||
def remove_chat_lint(self) -> None:
|
||||
try:
|
||||
self.chat_container.query_one(".selected_chat")\
|
||||
.remove_class("selected_chat")
|
||||
except NoMatches:
|
||||
pass
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
yield Footer() # Нижняя панель с подсказками
|
||||
with Horizontal(id="main_container"): # Основной контейнер
|
||||
with Horizontal(id="chats"):
|
||||
yield VerticalScroll(id="chat_container")
|
||||
yield VerticalScroll(
|
||||
id="chat_container",
|
||||
can_focus = False,
|
||||
can_focus_children = True
|
||||
)
|
||||
#TODO: сделать кнопку, чтобы прогрузить больше чатов,
|
||||
# или ленивую прокрутку
|
||||
yield ContentSwitcher(id="dialog_switcher")
|
||||
with ContentSwitcher(id="dialog_switcher", initial="start_label"):
|
||||
# ↑ Внутри него как раз крутятся диалоги
|
||||
#yield Label(
|
||||
# self.locale["start_converse"],
|
||||
# id="start_converse_label"
|
||||
#) #TODO: не показывается надпись, надо будет исправить
|
||||
yield Label(
|
||||
self.locale["start_converse"],
|
||||
id="start_label"
|
||||
)
|
||||
|
@ -74,12 +74,13 @@ ContentSwitcher {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#start_converse_label {
|
||||
width: 100%;
|
||||
#start_label {
|
||||
width: 70%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
content-align: center middle;
|
||||
color: $panel;
|
||||
padding: 3;
|
||||
}
|
||||
|
||||
TopBar {
|
||||
|
101
src/widgets.py
101
src/widgets.py
@ -7,7 +7,7 @@ from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
||||
from textual.app import ComposeResult, RenderResult
|
||||
from textual.content import Content
|
||||
from textual.style import Style
|
||||
from telethon import TelegramClient, events, utils, types
|
||||
from telethon import TelegramClient, utils, types
|
||||
|
||||
class Chat(Widget):
|
||||
"""Класс виджета чата для панели чатов"""
|
||||
@ -21,9 +21,10 @@ class Chat(Widget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
number: int,
|
||||
name: str | None = None,
|
||||
id: str | None = None,
|
||||
classes: str | None = None,
|
||||
classes: str = "chat",
|
||||
disabled: bool = False
|
||||
) -> None:
|
||||
super().__init__(
|
||||
@ -32,24 +33,24 @@ class Chat(Widget):
|
||||
classes=classes,
|
||||
disabled=disabled
|
||||
)
|
||||
self.number = number
|
||||
|
||||
def on_mount(self) -> None:
|
||||
self.switcher = self.screen.query_one(Horizontal)\
|
||||
.query_one("#dialog_switcher", ContentSwitcher)
|
||||
|
||||
if int(self.id[5:]) % 2 != 0:
|
||||
if self.number % 2 != 0:
|
||||
self.add_class("odd")
|
||||
else:
|
||||
self.add_class("even")
|
||||
|
||||
def on_click(self) -> None:
|
||||
async def on_click(self) -> None:
|
||||
# Получение ID диалога и создание DOM-ID на его основе
|
||||
dialog_id = f"dialog-{str(self.peer_id)}"
|
||||
dialog_id = f"dialog-{self.peer_id}"
|
||||
|
||||
# Маунт диалога
|
||||
try:
|
||||
self.switcher.mount(Dialog(
|
||||
telegram_client=self.app.telegram_client,
|
||||
chat_id=self.peer_id,
|
||||
id=dialog_id,
|
||||
is_channel=self.is_channel and not self.is_group
|
||||
@ -59,6 +60,8 @@ class Chat(Widget):
|
||||
pass
|
||||
|
||||
self.switcher.current = dialog_id
|
||||
self.screen.remove_chat_lint()
|
||||
self.add_class("selected_chat")
|
||||
self.switcher.recompose()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
@ -75,15 +78,14 @@ class Dialog(Widget):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
id=None,
|
||||
classes=None,
|
||||
disabled=None,
|
||||
telegram_client: TelegramClient | None = None,
|
||||
id = None,
|
||||
classes = None,
|
||||
disabled = None,
|
||||
chat_id: int | None = None,
|
||||
is_channel: bool | None = None
|
||||
) -> None:
|
||||
super().__init__(id=id, classes=classes, disabled=disabled)
|
||||
self.telegram_client = telegram_client
|
||||
self.telegram_client: TelegramClient = self.app.telegram_client
|
||||
self.chat_id = chat_id
|
||||
self.is_msg_update_blocked = False
|
||||
self.timezone = self.app.timezone
|
||||
@ -103,8 +105,6 @@ class Dialog(Widget):
|
||||
|
||||
await self.update_dialog()
|
||||
|
||||
#self.dialog.scroll_down(animate=False, immediate=True)
|
||||
|
||||
def mount_messages(self, limit: int) -> None:
|
||||
print("Загрузка виджетов сообщений...")
|
||||
|
||||
@ -116,6 +116,7 @@ class Dialog(Widget):
|
||||
Message(id=f"msg-{i + msg_amount + 1}"),
|
||||
before=0
|
||||
)
|
||||
self.dialog.scroll_end()
|
||||
elif limit < msg_amount:
|
||||
for i in range(msg_amount - limit):
|
||||
self.dialog.query(Message).last().remove()
|
||||
@ -137,9 +138,8 @@ class Dialog(Widget):
|
||||
for i in range(limit):
|
||||
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
||||
message = Content(str(messages[i].message))
|
||||
if str(messages[i].message):
|
||||
entities = messages[i].entities
|
||||
if entities:
|
||||
if entities != None:
|
||||
for entity in entities:
|
||||
match type(entity):
|
||||
case types.MessageEntityBold:
|
||||
@ -187,6 +187,7 @@ class Dialog(Widget):
|
||||
.date\
|
||||
.astimezone(self.timezone)\
|
||||
.strftime("%H:%M")
|
||||
msg.msg_obj = messages[i]
|
||||
|
||||
self.top_bar.peername = utils.get_display_name(
|
||||
await self.telegram_client.get_entity(self.chat_id)
|
||||
@ -197,6 +198,29 @@ class Dialog(Widget):
|
||||
else:
|
||||
print("Обновление сообщений невозможно: уже выполняется")
|
||||
|
||||
async def on_button_pressed(self, event = None) -> None:
|
||||
await self.send_message()
|
||||
|
||||
async def on_input_submitted(self, event = None) -> None:
|
||||
await self.send_message()
|
||||
|
||||
async def send_message(self) -> None:
|
||||
if self.app.msg_buffer is not None:
|
||||
await self.telegram_client.forward_messages(
|
||||
self.chat_id,
|
||||
self.app.msg_buffer
|
||||
)
|
||||
self.app.msg_buffer = None
|
||||
try:
|
||||
await self.telegram_client.send_message(
|
||||
self.chat_id,
|
||||
str(self.msg_input.value)
|
||||
)
|
||||
except ValueError:
|
||||
print("Ошибка отправки")
|
||||
self.msg_input.value = ""
|
||||
await self.update_dialog()
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Vertical():
|
||||
yield TopBar()
|
||||
@ -209,23 +233,6 @@ class Dialog(Widget):
|
||||
)
|
||||
yield Button(label="➤", id="send", variant="primary")
|
||||
|
||||
async def on_button_pressed(self, event = None) -> None:
|
||||
await self.send_message()
|
||||
|
||||
async def on_input_submitted(self, event = None) -> None:
|
||||
await self.send_message()
|
||||
|
||||
async def send_message(self) -> None:
|
||||
try:
|
||||
await self.telegram_client.send_message(
|
||||
self.chat_id,
|
||||
str(self.msg_input.value)
|
||||
)
|
||||
except ValueError:
|
||||
print("Ошибка отправки")
|
||||
self.msg_input.value = ""
|
||||
await self.update_dialog()
|
||||
|
||||
class Message(Widget):
|
||||
"""Класс виджета сообщений для окна диалога"""
|
||||
|
||||
@ -234,13 +241,18 @@ class Message(Widget):
|
||||
username: reactive[str] = reactive("", recompose=True)
|
||||
info: reactive[str] = reactive("", recompose=True)
|
||||
|
||||
def __init__(self, id=None) -> None:
|
||||
def __init__(self, id = None) -> None:
|
||||
super().__init__(id=id)
|
||||
|
||||
def on_click(self, event):
|
||||
if event.chain == 2:
|
||||
self.app.msg_buffer = self.msg_obj
|
||||
self.app.notify("Сообщение в буфере.")
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
label = Label(self.message, markup=False)
|
||||
label.border_title = self.username * (not self.is_me)
|
||||
label.border_subtitle = self.info
|
||||
label.border_title = Content(self.username * (not self.is_me))
|
||||
label.border_subtitle = Content(self.info)
|
||||
|
||||
with Container():
|
||||
yield label
|
||||
@ -255,7 +267,22 @@ class TopBar(Widget):
|
||||
|
||||
peername: reactive[str] = reactive(" ", recompose=True)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.telegram_client: TelegramClient = self.app.telegram_client
|
||||
|
||||
def on_click(self, event = None) -> None:
|
||||
"""Обработка нажатия"""
|
||||
|
||||
"""pic = self.telegram_client.download_profile_photo(
|
||||
self.screen.query_one(Horizontal)\
|
||||
.query_one("#dialog_switcher", ContentSwitcher)\
|
||||
.query_one(f"#{self.screen.query_one(Horizontal)\
|
||||
.query_one("#dialog_switcher", ContentSwitcher).current}").chat_id
|
||||
)
|
||||
system(f"echo \"{pic}\" > picture.txt")""" # Debug штучка
|
||||
|
||||
def compose(self) -> ComposeResult:
|
||||
with Horizontal():
|
||||
yield Label(self.peername[:1], classes="avatar")
|
||||
yield Label(self.peername, classes="peername_top_bar")
|
||||
yield Label(self.peername[:1], classes="avatar", markup=False)
|
||||
yield Label(self.peername, classes="peername_top_bar", markup=False)
|
||||
|
Loading…
x
Reference in New Issue
Block a user