From 364c76633ec28772a5af3c7ded21cb329ae0b1bf Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Sun, 26 Jan 2025 23:27:29 +0300 Subject: [PATCH 1/7] =?UTF-8?q?=D1=81=D1=82=D0=B8=D0=BB=D0=B8=20=D0=B2=20?= =?UTF-8?q?=D0=B4=D1=80=D1=83=D0=B3=D0=BE=D0=B9=20=D0=BF=D0=B0=D0=BF=D0=BA?= =?UTF-8?q?=D0=B5=20=D0=BD=D1=8B=D0=BD=D1=87=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- styles.tcss | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 styles.tcss diff --git a/styles.tcss b/styles.tcss deleted file mode 100644 index fe9b9c1..0000000 --- a/styles.tcss +++ /dev/null @@ -1,20 +0,0 @@ -#chats { - width: 30%; -} - -#dialog { - width: 70%; -} - -Chat { - height: 3; -} - -Rule { - color: #FFFFFF; -} - -Placeholder { - height: 3; - padding: 1; -} From 252357dc10b510959466bbed0148f56e14fb4500 Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Sun, 26 Jan 2025 23:28:12 +0300 Subject: [PATCH 2/7] =?UTF-8?q?=D0=9F=D0=9E=D0=A7=D0=98=D0=9D=D0=98=D0=A2?= =?UTF-8?q?=D0=95=20=D0=9E=D0=A2=D0=9F=D0=A0=D0=90=D0=92=D0=9A=D0=A3=20?= =?UTF-8?q?=D0=A1=D0=9E=D0=9E=20=D0=92=20WIDGETS.PY=20=D0=9F=D0=A0=D0=9E?= =?UTF-8?q?=D0=A8=D0=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ПОЖАЛУЙСТА --- .gitignore | 5 +++ main.py | 91 ++---------------------------------------------------- tokens.py | 7 +++-- 3 files changed, 12 insertions(+), 91 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe0f395 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +test.py +*.session +*.session-journal +__pycache__ +*/__pycache__ \ No newline at end of file diff --git a/main.py b/main.py index 03e3ca6..9d0ccd7 100644 --- a/main.py +++ b/main.py @@ -1,90 +1,5 @@ -from textual.app import App, ComposeResult -from textual.widgets import Placeholder, Label, Static, Rule -from textual.containers import Horizontal, VerticalScroll, Vertical -from telethon import TelegramClient, events, sync -from tokens import api_id, api_hash - -names = [] -soo = [] - -limits = 6 - -client = TelegramClient('Telegram-Cli', api_id, api_hash) -client.start() -print(client.get_me().stringify()) - -for titles in client.iter_dialogs(limit=limits): - names.append('{:<14}'.format(titles.title)) - -for messages in client.iter_dialogs(limit=limits): - soo.append('{:<14}'.format(messages.message.message)) - -class T_m(): - - def __init__(self, text: str, user: int): - """ - text - текст сообщения \n - user - айди отправителя сообщения - """ - - self.text = text - self.user = user - -class T_u(): - - def __init__(self, name: str, user_id: int, dialog: list[T_m]): - """ - name - имя пользователя \n - id - айди пользователя \n - dialog - список сообщений (T_m) диалога - """ - - self.name = name - self.user_id = user_id - self.dialog = dialog - -client = T_u("вы", 0, []) - -test_chats = [] - -for i in range(0, limits): - test_chats.append(T_u(names[i], 1, [T_m(soo[i], 0)])) - -class Chat(Horizontal): - """Кастомный виджет чата слева""" - def __init__(self, user: T_u): - super().__init__() - self.user = user - - def _on_click(self): - pass - - def compose(self) -> ComposeResult: - with Horizontal(): - yield Label(f"┌───┐\n│ {self.user.name[:1]} │\n└───┘") - with Vertical(): - yield Label(self.user.name, id="name") - yield Label(self.user.dialog[-1].text) - -class TelegramTUI(App): - CSS_PATH = "styles.tcss" - - def compose(self) -> ComposeResult: - with Horizontal(): - with Horizontal(id="chats"): - with VerticalScroll(): - for i in test_chats: - yield Chat(i) - - yield Rule("vertical") - - with VerticalScroll(id="dialog"): - yield Placeholder(label="message", classes=("message")) - yield Placeholder(label="message", classes=("message")) - yield Placeholder(label="message", classes=("message")) - yield Placeholder(label="message", classes=("message")) - yield Placeholder(label="message", classes=("message")) +from src.app import TelegramTUI if __name__ == "__main__": - app = TelegramTUI() - app.run() + tg = TelegramTUI() + tg.run() diff --git a/tokens.py b/tokens.py index 575694f..f56ccd0 100644 --- a/tokens.py +++ b/tokens.py @@ -1,3 +1,4 @@ -# Values from this files willn't work! Replace it with your's from my.telegram.org -api_id = 4327852 -api_hash = "yourhash" +"""Получите свои API-ключи на https://my.telegram.org/apps""" + +api_id = 21321 +api_hash = "yourhashherebro" From 21e2069e2f875203df466ac84936675e00485621 Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Sun, 26 Jan 2025 23:30:50 +0300 Subject: [PATCH 3/7] Create folder --- src/lan | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/lan diff --git a/src/lan b/src/lan new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/lan @@ -0,0 +1 @@ + From 3611bcde314ac8484871a95bc5d23fc6896b987a Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Sun, 26 Jan 2025 23:31:15 +0300 Subject: [PATCH 4/7] =?UTF-8?q?=D0=9F=D0=9E=D0=A4=D0=98=D0=9A=D0=A1=D0=98?= =?UTF-8?q?=D0=A2=D0=95=20=D0=9E=D0=A2=D0=9F=D0=A0=D0=90=D0=92=D0=9A=D0=A3?= =?UTF-8?q?=20=D0=92=20WIDGETS.PY?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ПРОШУ --- src/app.py | 28 ++++++++++ src/screens.py | 149 +++++++++++++++++++++++++++++++++++++++++++++++++ src/style.tcss | 47 ++++++++++++++++ src/widgets.py | 109 ++++++++++++++++++++++++++++++++++++ 4 files changed, 333 insertions(+) create mode 100644 src/app.py create mode 100644 src/screens.py create mode 100644 src/style.tcss create mode 100644 src/widgets.py diff --git a/src/app.py b/src/app.py new file mode 100644 index 0000000..4f7bc1e --- /dev/null +++ b/src/app.py @@ -0,0 +1,28 @@ +from telethon import TelegramClient, events +from textual.app import App +from tokens import api_id, api_hash +from src.screens import AuthScreen, ChatScreen + +class TelegramTUI(App): + """Класс приложения""" + + CSS_PATH = "style.tcss" + + async def on_mount(self) -> None: + self.telegram_client = TelegramClient("user", api_id, api_hash) + await self.telegram_client.connect() + + chat_screen = ChatScreen(telegram_client=self.telegram_client) + self.install_screen(chat_screen, name="chats") + + if not await self.telegram_client.is_user_authorized(): + auth_screen = AuthScreen(telegram_client=self.telegram_client) + self.install_screen(auth_screen, name="auth") + self.push_screen("auth") + else: + await self.telegram_client.start() + self.push_screen("chats") + + async def on_exit_app(self): + await self.telegram_client.disconnect() + return super()._on_exit_app() diff --git a/src/screens.py b/src/screens.py new file mode 100644 index 0000000..c94f7ba --- /dev/null +++ b/src/screens.py @@ -0,0 +1,149 @@ +from textual.screen import Screen +from textual.widgets import Label, Input, Footer, Static +from textual.containers import Vertical, Horizontal, VerticalScroll +from telethon.errors import SessionPasswordNeededError +from telethon import TelegramClient, events, utils +from src.widgets import Dialog, Chat + +class AuthScreen(Screen): + """Класс логина в аккаунт""" + + def __init__( + self, + name = None, + id = None, + classes = None, + telegram_client: TelegramClient | None = None + ): + super().__init__(name, id, classes) + self.client = telegram_client + + def on_mount(self): + self.ac = self.query_one("#auth_container") + + def compose(self): + with Vertical(id="auth_container"): + yield Label("Добро пожаловать в Telegram TUI") + yield Input(placeholder="Номер телефона", id="phone") + yield Input(placeholder="Код", id="code", disabled=True) + yield Input( + placeholder="Пароль", + id="password", + password=True, + disabled=True + ) + + async def on_input_submitted(self, event: Input.Submitted) -> None: + if event.input.id == "phone": + self.phone = event.value + self.ac.query_one("#phone").disabled = True + self.ac.query_one("#code").disabled = False + await self.client.send_code_request(phone=self.phone) + elif event.input.id == "code": + try: + self.code = event.value + self.ac.query_one("#code").disabled = True + await self.client.sign_in(phone=self.phone, code=self.code) + self.app.pop_screen() + self.app.push_screen("chats") + except SessionPasswordNeededError: + self.ac.query_one("#code").disabled = True + self.ac.query_one("#password").disabled = False + elif event.input.id == "password": + self.password = event.value + await self.client.sign_in(password=self.password) + await self.client.start() + self.app.pop_screen() + self.app.push_screen("chats") + self.app.notify("") + +class ChatScreen(Screen): + """Класс экрана чатов, он же основной экран приложения""" + + def __init__( + self, + name = None, + id = None, + classes = None, + telegram_client: TelegramClient | None = None + ): + super().__init__(name, id, classes) + self.telegram_client = telegram_client + + async def on_mount(self): + self.limit = 30 + + self.chat_container = self\ + .query_one("#main_container")\ + .query_one("#chats")\ + .query_one("#chat_container") + + print("Первоначальная загрузка виджетов чатов...") + self.mount_chats( + len( + await self.telegram_client.get_dialogs( + limit=self.limit, archived=False + ) + ) + ) + print("Первоначальная загрузка виджетов чата завершена") + + self.is_chat_update_blocked = False + await self.update_chat_list() + + print("Первоначальная загрузка чатов завершена") + + for event in ( + events.NewMessage(), + events.MessageDeleted(), + events.MessageEdited() + ): + self.telegram_client.on(event)(self.update_chat_list) + + def mount_chats(self, limit: int): + print("Загрузка виджетов чатов...") + + chats_amount = len(self.chat_container.query(Chat)) + + if limit > chats_amount: + for i in range(limit - chats_amount): + chat = Chat(id=f"chat-{i + chats_amount + 1}") + self.chat_container.mount(chat) + elif limit < chats_amount: + for i in range(chats_amount - limit): + self.chat_container.query(Chat).last().remove() + + print("Виджеты чатов загружены") + + async def update_chat_list(self, event = None): + print("Запрос обновления чатов") + if not self.is_chat_update_blocked: + self.is_chat_update_blocked = True + dialogs = await self.telegram_client.get_dialogs( + limit=self.limit, archived=False + ) + print("Получены диалоги") + limit = len(dialogs) + #limit = 30 + self.mount_chats(limit) + + for i in range(limit): + chat = self.chat_container.query_one(f"#chat-{i + 1}") + chat.username = str(dialogs[i].name) + chat.msg = str(dialogs[i].message.message) + chat.peer_id = dialogs[i].id + #self.notify("Новое сообщение") #колхоз дебаг + + self.is_chat_update_blocked = False + print("Чаты обновлены") + else: + print("Обновление чатов невозможно: уже выполняется") + + def compose(self): + yield Footer() + with Horizontal(id="main_container"): + with Horizontal(id="chats"): + yield VerticalScroll(Static(id="chat_container")) + #TODO: сделать кнопку чтобы прогрузить больше чатов + + yield Dialog(telegram_client=self.telegram_client) diff --git a/src/style.tcss b/src/style.tcss new file mode 100644 index 0000000..cde8031 --- /dev/null +++ b/src/style.tcss @@ -0,0 +1,47 @@ +#chats { + width: 30%; +} + +#dialog { + width: 70%; +} + +Chat { + height: 3; +} + +Rule { + color: #FFFFFF; +} + +.message { + height: 3; + padding: 1; +} + +Message { + height: auto; + width: auto; +} + +Message Container { + height: auto; +} + +#input_place { + height: 3; + width: 70%; + align-horizontal: center; +} + +#msg_input { + width: 65%; +} + +#send { + +} + +#auth_container{ + align: center middle; +} diff --git a/src/widgets.py b/src/widgets.py new file mode 100644 index 0000000..b150cde --- /dev/null +++ b/src/widgets.py @@ -0,0 +1,109 @@ +from textual.containers import Horizontal, Vertical, Container, VerticalScroll +from textual.widget import Widget +from textual.reactive import Reactive +from textual.widgets import Input, Button, Label +from telethon import TelegramClient, events, utils + +class Chat(Widget): + """Класс виджета чата для панели чатов""" + + username = Reactive(" ", recompose=True) + msg = Reactive(" ", recompose=True) + peer_id = Reactive(0) + + def __init__( + self, + name: str | None = None, + notify_func = None, + id: str | None = None, + classes: str | None = None, + disabled: bool = False + ): + super().__init__( + name=str(name), + id=id, + classes=classes, + disabled=disabled + ) + self.notify = notify_func + + + def _on_click(self): + self.msg = str(self.peer_id) + self.notify("нажат чат") + + def compose(self): + with Horizontal(): + yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘") + with Vertical(): + yield Label(self.username, id="name") + yield Label(self.msg, id="last_msg") + +class Dialog(Widget): + """Класс окна диалога""" + + def __init__(self, id=None, classes=None, disabled=False, telegram_client: TelegramClient | None = None): + super().__init__(id=id, classes=classes, disabled=disabled) + self.telegram_client = telegram_client + + + 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") + + # но я могу ошибаться, я это фиш если что + + #TODO: сделать кнопку чтобы прогрузить больше сообщений, + #но при этом чтобы при перезаходе в чат оставались + #прогруженными только 10 сообщений, + #а остальные декомпоузились + + 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("Нажато отправить") + self.message_text = self.query_one("#msg_input").value + self.telegram_client.send_message("ultimate_fish", self.message_text) + + + +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)) From 9f7aafafd8753155b52e7399b91f526ec3562c72 Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Sun, 26 Jan 2025 23:31:36 +0300 Subject: [PATCH 5/7] =?UTF-8?q?=D0=BD=D0=B8=D0=BA=D0=B0=D0=BA=D0=B8=D1=85?= =?UTF-8?q?=20=D0=BB=D0=B0=D0=BD=D0=BE=D0=B2=20=D0=BD=D0=B0=D1=85=D1=83?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/lan | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/lan diff --git a/src/lan b/src/lan deleted file mode 100644 index 8b13789..0000000 --- a/src/lan +++ /dev/null @@ -1 +0,0 @@ - From 82c9d12a9bb1387809ee84dd80a593e78a34521a Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Mon, 27 Jan 2025 22:43:22 +0300 Subject: [PATCH 6/7] =?UTF-8?q?=D0=B2=D0=BA=D1=80=D1=83=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BB=D0=B5=D1=82=D0=BE=D0=BD,=20=D0=BD=D0=BE?= =?UTF-8?q?=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=BE=D0=B4=D0=BD=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit он терь не грузит сообщения и крашится --- src/widgets.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/widgets.py b/src/widgets.py index b150cde..fea9371 100644 --- a/src/widgets.py +++ b/src/widgets.py @@ -25,12 +25,16 @@ class Chat(Widget): classes=classes, disabled=disabled ) + global personid + personid = 0 self.notify = notify_func def _on_click(self): + global personid self.msg = str(self.peer_id) - self.notify("нажат чат") + personid = int(self.peer_id) + self.app.notify("нажат чат") def compose(self): with Horizontal(): @@ -43,16 +47,27 @@ class Dialog(Widget): """Класс окна диалога""" def __init__(self, id=None, classes=None, disabled=False, telegram_client: TelegramClient | None = None): + global personid super().__init__(id=id, classes=classes, disabled=disabled) self.telegram_client = telegram_client + self.personid = personid + + async def load_messages(self): + self.messages = [] + for messages1 in self.telegram_client.iter_dialogs(self.personid, limit=5): + messages.append(messages1.text) def compose(self): + messages = self.messages with Vertical(): with VerticalScroll(id="dialog"): - yield Message(message="привет, я ыплыжлп", is_me=True) - yield Message(message="о, дщытрапшщцрущ", is_me=False) - yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False) + yield Message(message=messages[0], is_me=True) + yield Message(message=messages[1], is_me=False) + yield Message(message=messages[2], is_me=False) + yield Message(message=messages[3], is_me=True) + yield Message(message=messages[4], is_me=False) + # должно быть примерно # is_me = message.from_id == client.get_peer_id("me") @@ -67,10 +82,10 @@ class Dialog(Widget): yield Input(placeholder="Сообщение", id="msg_input") yield Button(label="➤", id="send", variant="primary") - def on_button_pressed(self, event): # self добавил + async def on_button_pressed(self, event): # self добавил self.app.notify("Нажато отправить") self.message_text = self.query_one("#msg_input").value - self.telegram_client.send_message("ultimate_fish", self.message_text) + await self.telegram_client.send_message(personid, str(self.message_text)) From f77f0057d8aa6b62667d9ac01e9fd0f1fd76b594 Mon Sep 17 00:00:00 2001 From: kldk-lab Date: Mon, 27 Jan 2025 22:44:14 +0300 Subject: [PATCH 7/7] =?UTF-8?q?=D0=B2=D0=BA=D1=80=D1=83=D1=82=D0=B8=D0=BB?= =?UTF-8?q?=20=D1=82=D0=B5=D0=BB=D0=B5=D1=82=D0=BE=D0=BD,=20=D0=BD=D0=BE?= =?UTF-8?q?=20=D0=B5=D1=81=D1=82=D1=8C=20=D0=BE=D0=B4=D0=BD=D0=BE=20=D0=BD?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit оно теперь не грузит сообщения и крашится