diff --git a/__pycache__/tokens.cpython-313.pyc b/__pycache__/tokens.cpython-313.pyc new file mode 100644 index 0000000..ae36405 Binary files /dev/null and b/__pycache__/tokens.cpython-313.pyc differ diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..4a6205a Binary files /dev/null and b/app/__pycache__/__init__.cpython-313.pyc differ diff --git a/app/__pycache__/app.cpython-313.pyc b/app/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000..53fddff Binary files /dev/null and b/app/__pycache__/app.cpython-313.pyc differ diff --git a/app/app.py b/app/app.py new file mode 100644 index 0000000..41124a6 --- /dev/null +++ b/app/app.py @@ -0,0 +1,44 @@ +from telethon import TelegramClient, events +from textual.app import App, ComposeResult +from textual.containers import Horizontal, VerticalScroll, Vertical, Container +from textual.widgets import Placeholder, Label, Static, Rule, Input, Button +from widgets.chat import Chat +from widgets.dialog import Dialog +from telegram.client import TelegramClientWrapper +from tokens import api_id, api_hash + +class TelegramTUI(App): + CSS_PATH = "../tcss/style.tcss" + + def __init__(self): + super().__init__() + self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.handler) + + async def on_mount(self) -> None: + await self.telegram_client.connect() + await self.update_chat_list() + + async def handler(self, event): + await self.update_chat_list() + + async def update_chat_list(self): + dialogs = await self.telegram_client.get_dialogs(limit=10) + chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") + chat_container.query(Chat).remove() + + for dialog in dialogs: + name = dialog.name + msg = dialog.message.message + chat = Chat(name, msg, id=f"chat-{dialog.id}") + chat_container.mount(chat) + + def compose(self) -> ComposeResult: + with Horizontal(id="main_container"): + with Horizontal(id="chats"): + yield VerticalScroll(Static(id="chat_container")) + yield Rule("vertical") + yield Dialog() + + async def on_exit_app(self): + await self.telegram_client.disconnect() + return super()._on_exit_app() diff --git a/main.py b/main.py index fb934a2..d438f0f 100644 --- a/main.py +++ b/main.py @@ -1,123 +1,5 @@ -from telethon import TelegramClient, events, utils -from textual.app import App, ComposeResult -from textual.widgets import Placeholder, Label, Static, Rule, Input, Button -from textual.containers import Horizontal, VerticalScroll, Vertical, Container -from textual.reactive import var -from textual.widget import Widget -from tokens import api_id, api_hash - -class Chat(Widget): - """Кастомный виджет чата слева""" - - def __init__(self, name: str | None = None, msg: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False): - super().__init__(name=str(name), id=id, classes=classes, disabled=disabled) - self.msg = str(msg) - - def _on_click(self): - pass - - def compose(self) -> ComposeResult: - with Horizontal(): - yield Label(f"┌───┐\n│ {self.name[:1]} │\n└───┘") - with Vertical(): - yield Label(self.name, id="name") - yield Label(self.msg, id="last_msg") - -class Dialog(Widget): - """Кастомный виджет диалога справа""" - - def __init__(self, name = None, id = None, classes = None, disabled = False): - super().__init__(name=name, id=id, classes=classes, disabled=disabled) - - def compose(self): - with Vertical(): - with VerticalScroll(id="dialog"): - yield Message(message="привет, я ыплыжлп", is_me=True) - yield Message(message="о, дщытрапшщцрущ", is_me=False) - yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False) - #должно быть примерно is_me = message.from_id == client.get_peer_id("me") - #но я могу ошибаться, я это фиш если что - - with Horizontal(id="input_place"): - yield Input(placeholder="Сообщение", id="msg_input") - yield Button(label="➤", id="send", variant="primary") - - def on_button_pressed(event): - app.notify("Нажато отправить") - - -class Message(Widget): - """Кастомный виджет сообщения""" - - def __init__(self, name = None, message = None, is_me = None, id = None, classes = None, disabled = False): - super().__init__(name=name, id=id, classes=classes, disabled=disabled) - self.message = message - self.is_me = is_me - - def _on_mount(self): - if self.is_me: - self.styles.padding = (0, 0, 0, 15) - self.query_one(Container).query_one(Label).styles.text_align = "right" - self.query_one(Container).styles.align_horizontal = "right" - self.query_one(Container).query_one(Label).styles.border = ("solid", "#4287f5") - else: - self.styles.padding = (0, 15, 0, 0) - self.query_one(Container).query_one(Label).styles.text_align = "left" - self.query_one(Container).styles.align_horizontal = "left" - self.query_one(Container).query_one(Label).styles.border = ("solid", "#ffffff") - - def compose(self): - with Container(): - #yield Label(self.message.message) #это нормальный вариант - yield Label(str(self.message)) #это тестовый вариант - -class TelegramTUI(App): - """Главный класс приложения""" - - CSS_PATH = "styles.tcss" - - def __init__(self): - super().__init__() - self.api_id = api_id - self.api_hash = api_hash - self.chats = [] - self.client = TelegramClient('user', api_id, api_hash) - self.client.on(events.NewMessage())(self.handler) - - async def on_mount(self) -> None: - await self.client.start() - await self.update_chat_list() - - async def handler(self, event): - await self.update_chat_list() - - async def update_chat_list(self): - dialogs = [] - async for dialog in self.client.iter_dialogs(limit=10): - dialogs.append(dialog) - - chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") - chat_container.query(Chat).remove() - - for dialog in dialogs: - name = utils.get_display_name(dialog.entity) - msg = dialog.message.message - chat = Chat(name, msg, id=f"chat-{dialog.id}") - chat_container.mount(chat) - - def compose(self) -> ComposeResult: - with Horizontal(id="main_container"): - with Horizontal(id="chats"): - yield VerticalScroll(*[Static(id="chat_container")]) - - yield Rule("vertical") - - yield Dialog() - - async def _on_exit_app(self): - await self.client.disconnect() - return super()._on_exit_app() +from app.app import TelegramTUI if __name__ == "__main__": - app = TelegramTUI() - app.run() + tg = TelegramTUI() + tg.run() diff --git a/styles.tcss b/tcss/style.tcss similarity index 100% rename from styles.tcss rename to tcss/style.tcss diff --git a/telegram/__init__.py b/telegram/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/telegram/__pycache__/__init__.cpython-313.pyc b/telegram/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..f890a5c Binary files /dev/null and b/telegram/__pycache__/__init__.cpython-313.pyc differ diff --git a/telegram/__pycache__/client.cpython-313.pyc b/telegram/__pycache__/client.cpython-313.pyc new file mode 100644 index 0000000..67dba28 Binary files /dev/null and b/telegram/__pycache__/client.cpython-313.pyc differ diff --git a/telegram/client.py b/telegram/client.py new file mode 100644 index 0000000..21935a4 --- /dev/null +++ b/telegram/client.py @@ -0,0 +1,31 @@ +from telethon import TelegramClient, events, utils + +class TelegramClientWrapper: + def __init__(self, api_id, api_hash, message_handler): + self.client = TelegramClient('user', api_id, api_hash) + self.client.add_event_handler(message_handler, events.NewMessage()) + + async def connect(self): + await self.client.start() + + async def disconnect(self): + await self.client.disconnect() + + async def get_dialogs(self, limit=10): + dialogs_list = [] + async for dialog in self.client.iter_dialogs(limit=limit): + dialogs_list.append(dialog) + return [self._map_dialog(d) for d in dialogs_list] + + def _map_dialog(self, dialog): + return DialogInfo( + id=dialog.id, + name=utils.get_display_name(dialog.entity), + message=dialog.message + ) + +class DialogInfo: + def __init__(self, id, name, message): + self.id = id + self.name = name + self.message = message diff --git a/tokens.py b/tokens.py new file mode 100644 index 0000000..1c6c644 --- /dev/null +++ b/tokens.py @@ -0,0 +1,2 @@ +api_hash = 111 +api_id = 11 diff --git a/widgets/__init__.py b/widgets/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/widgets/__pycache__/__init__.cpython-313.pyc b/widgets/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000..60dd34e Binary files /dev/null and b/widgets/__pycache__/__init__.cpython-313.pyc differ diff --git a/widgets/__pycache__/chat.cpython-313.pyc b/widgets/__pycache__/chat.cpython-313.pyc new file mode 100644 index 0000000..401b80b Binary files /dev/null and b/widgets/__pycache__/chat.cpython-313.pyc differ diff --git a/widgets/__pycache__/dialog.cpython-313.pyc b/widgets/__pycache__/dialog.cpython-313.pyc new file mode 100644 index 0000000..f1496b8 Binary files /dev/null and b/widgets/__pycache__/dialog.cpython-313.pyc differ diff --git a/widgets/__pycache__/message.cpython-313.pyc b/widgets/__pycache__/message.cpython-313.pyc new file mode 100644 index 0000000..8d066b6 Binary files /dev/null and b/widgets/__pycache__/message.cpython-313.pyc differ diff --git a/widgets/chat.py b/widgets/chat.py new file mode 100644 index 0000000..0f71c93 --- /dev/null +++ b/widgets/chat.py @@ -0,0 +1,18 @@ +from textual.widgets import Label +from textual.containers import Horizontal, Vertical +from textual.widget import Widget + +class Chat(Widget): + def __init__(self, name: str | None = None, msg: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False): + super().__init__(name=str(name), id=id, classes=classes, disabled=disabled) + self.msg = str(msg) + + def _on_click(self): + pass + + def compose(self): + with Horizontal(): + yield Label(f"┌───┐\n│ {self.name[:1]} │\n└───┘") + with Vertical(): + yield Label(self.name, id="name") + yield Label(self.msg, id="last_msg") diff --git a/widgets/dialog.py b/widgets/dialog.py new file mode 100644 index 0000000..6d006c3 --- /dev/null +++ b/widgets/dialog.py @@ -0,0 +1,24 @@ +from textual.widgets import Input, Button, Label +from textual.containers import Horizontal, VerticalScroll, Vertical +from textual.widget import Widget +from widgets.message import Message + +class Dialog(Widget): + def __init__(self, id=None, classes=None, disabled=False): + super().__init__(id=id, classes=classes, disabled=disabled) + + def compose(self): + with Vertical(): + with VerticalScroll(id="dialog"): + yield Message(message="привет, я ыплыжлп", is_me=True) + yield Message(message="о, дщытрапшщцрущ", is_me=False) + yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False) + # должно быть примерно is_me = message.from_id == client.get_peer_id("me") + # но я могу ошибаться, я это фиш если что + + with Horizontal(id="input_place"): + yield Input(placeholder="Сообщение", id="msg_input") + yield Button(label="➤", id="send", variant="primary") + + def on_button_pressed(self, event): # self добавил + self.app.notify("Нажато отправить") diff --git a/widgets/message.py b/widgets/message.py new file mode 100644 index 0000000..b1fc27c --- /dev/null +++ b/widgets/message.py @@ -0,0 +1,27 @@ +from textual.widgets import Label +from textual.containers import Container +from textual.widget import Widget + +class Message(Widget): + def __init__(self, name=None, message=None, is_me=None, id=None, classes=None, disabled=False): + super().__init__(name=name, id=id, classes=classes, disabled=disabled) + self.message = message + self.is_me = is_me + + def on_mount(self): + container = self.query_one(Container) + label = container.query_one(Label) + if self.is_me: + self.styles.padding = (0, 0, 0, 15) + label.styles.text_align = "right" + container.styles.align_horizontal = "right" + label.styles.border = ("solid", "#4287f5") + else: + self.styles.padding = (0, 15, 0, 0) + label.styles.text_align = "left" + container.styles.align_horizontal = "left" + label.styles.border = ("solid", "#ffffff") + + def compose(self): + with Container(): + yield Label(str(self.message))