From 920d55c4460292d08c2b705357488440f94858e0 Mon Sep 17 00:00:00 2001 From: fish-dd <149502546+fish-dd@users.noreply.github.com> Date: Mon, 20 Jan 2025 01:09:19 +0300 Subject: [PATCH 01/14] Delete tokens.py --- tokens.py | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tokens.py diff --git a/tokens.py b/tokens.py deleted file mode 100644 index 575694f..0000000 --- a/tokens.py +++ /dev/null @@ -1,3 +0,0 @@ -# Values from this files willn't work! Replace it with your's from my.telegram.org -api_id = 4327852 -api_hash = "yourhash" From 58a6cd6875613a2b9f47bb0068446dd8c348a42e Mon Sep 17 00:00:00 2001 From: kirill Date: Mon, 20 Jan 2025 01:11:32 +0300 Subject: [PATCH 02/14] =?UTF-8?q?=D0=B0=D1=81=D0=B8=D0=BD=D1=85=D1=80?= =?UTF-8?q?=D0=BE=D0=BD=D0=BD=D0=BE=D0=B5,=20=D0=BD=D0=BE=20=D0=BD=D0=B8?= =?UTF-8?q?=D1=85=D0=B5=D1=80=D0=B0=20=D0=BD=D0=B5=20=D0=B0=D1=81=D0=B8?= =?UTF-8?q?=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=BD=D0=BE=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 107 ++++++++++++++++++++++++---------------------------- styles.tcss | 2 +- 2 files changed, 50 insertions(+), 59 deletions(-) diff --git a/main.py b/main.py index 03e3ca6..877db53 100644 --- a/main.py +++ b/main.py @@ -1,80 +1,71 @@ +from telethon import TelegramClient, events, sync, utils 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 textual.reactive import var +from textual.widget import Widget 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): +class Chat(Widget): """Кастомный виджет чата слева""" - def __init__(self, user: T_u): - super().__init__() - self.user = user + def __init__(self, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False): + super().__init__(name=name, id=id, classes=classes, disabled=disabled) def _on_click(self): pass def compose(self) -> ComposeResult: with Horizontal(): - yield Label(f"┌───┐\n│ {self.user.name[:1]} │\n└───┘") + yield Label(f"┌───┐\n│ {self.name[:1]} │\n└───┘") with Vertical(): - yield Label(self.user.name, id="name") - yield Label(self.user.dialog[-1].text) + yield Label(self.name, id="name") + #yield Label(self.user.dialog[-1].text) class TelegramTUI(App): + CSS_PATH = "styles.tcss" + + def __init__(self): + super().__init__() + self.api_id = api_id + self.api_hash = api_hash + self.client = TelegramClient('user', api_id, api_hash) + self.chats = var([]) + + async def on_mount(self) -> None: + await self.client.start() + dialogs = [] + async for dialog in self.client.iter_dialogs(): + dialogs.append(dialog) + self.chats = dialogs + await self.update_chat_list() + + async def update_chat_list(self): + #if self.chats: + #for dialog in self.chats: + # name = utils.get_display_name(dialog.entity) + # last_msg = "" # Значение по умолчанию + + # try: + # last_messages = await self.client.get_messages(dialog.entity, limit=1) + # if last_messages: + # last_msg = last_messages[0].message # Получаем текст последнего сообщения + # except Exception as e: # Добавлена обработка ошибок + # print(f"Ошибка получения последнего сообщения: {e}") + + chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") + chat_container.query(Chat).remove() # Clear existing labels + + for dialog in self.chats: + name = utils.get_display_name(dialog.entity) + #msg = utils.get_input_peer + chat = Chat(name, id=f"chat-{dialog.id}") + chat_container.mount(chat) def compose(self) -> ComposeResult: - with Horizontal(): + with Horizontal(id="main_container"): with Horizontal(id="chats"): - with VerticalScroll(): - for i in test_chats: - yield Chat(i) + yield VerticalScroll(*[Static(id="chat_container")]) yield Rule("vertical") diff --git a/styles.tcss b/styles.tcss index fe9b9c1..544ff77 100644 --- a/styles.tcss +++ b/styles.tcss @@ -14,7 +14,7 @@ Rule { color: #FFFFFF; } -Placeholder { +.message { height: 3; padding: 1; } From 9c6ca34135327ed1208212772db5ff9a854e1d2a Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 21 Jan 2025 01:44:37 +0300 Subject: [PATCH 03/14] =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=BE=20=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=B5=D1=82=D1=81=D1=8F,=20=D0=B8=D0=BD=D1=82?= =?UTF-8?q?=D0=B5=D1=80=D1=84=D0=B5=D0=B9=D1=81=D1=8B=20=D0=BC=D1=83=D1=82?= =?UTF-8?q?=D1=8F=D1=82=D1=81=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 106 ++++++++++++++++++++++++++++++++++++---------------- styles.tcss | 17 +++++++++ 2 files changed, 91 insertions(+), 32 deletions(-) diff --git a/main.py b/main.py index 877db53..fb934a2 100644 --- a/main.py +++ b/main.py @@ -1,15 +1,17 @@ -from telethon import TelegramClient, events, sync, utils +from telethon import TelegramClient, events, utils from textual.app import App, ComposeResult -from textual.widgets import Placeholder, Label, Static, Rule -from textual.containers import Horizontal, VerticalScroll, Vertical +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, id: str | None = None, classes: str | None = None, disabled: bool = False): - super().__init__(name=name, id=id, classes=classes, disabled=disabled) + + 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 @@ -19,9 +21,58 @@ class Chat(Widget): yield Label(f"┌───┐\n│ {self.name[:1]} │\n└───┘") with Vertical(): yield Label(self.name, id="name") - #yield Label(self.user.dialog[-1].text) + 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" @@ -29,37 +80,29 @@ class TelegramTUI(App): super().__init__() self.api_id = api_id self.api_hash = api_hash + self.chats = [] self.client = TelegramClient('user', api_id, api_hash) - self.chats = var([]) + self.client.on(events.NewMessage())(self.handler) async def on_mount(self) -> None: await self.client.start() - dialogs = [] - async for dialog in self.client.iter_dialogs(): - dialogs.append(dialog) - self.chats = dialogs + await self.update_chat_list() + + async def handler(self, event): await self.update_chat_list() async def update_chat_list(self): - #if self.chats: - #for dialog in self.chats: - # name = utils.get_display_name(dialog.entity) - # last_msg = "" # Значение по умолчанию - - # try: - # last_messages = await self.client.get_messages(dialog.entity, limit=1) - # if last_messages: - # last_msg = last_messages[0].message # Получаем текст последнего сообщения - # except Exception as e: # Добавлена обработка ошибок - # print(f"Ошибка получения последнего сообщения: {e}") + 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() # Clear existing labels + chat_container.query(Chat).remove() - for dialog in self.chats: + for dialog in dialogs: name = utils.get_display_name(dialog.entity) - #msg = utils.get_input_peer - chat = Chat(name, id=f"chat-{dialog.id}") + msg = dialog.message.message + chat = Chat(name, msg, id=f"chat-{dialog.id}") chat_container.mount(chat) def compose(self) -> ComposeResult: @@ -69,12 +112,11 @@ class TelegramTUI(App): 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")) + yield Dialog() + + async def _on_exit_app(self): + await self.client.disconnect() + return super()._on_exit_app() if __name__ == "__main__": app = TelegramTUI() diff --git a/styles.tcss b/styles.tcss index 544ff77..d941030 100644 --- a/styles.tcss +++ b/styles.tcss @@ -18,3 +18,20 @@ Rule { height: 3; padding: 1; } + +Message { + height: auto; + width: auto; +} + +Message Container { + height: auto; +} + +#input_place { + height: 3; +} + +#msg_input { + width: 70%; +} From c3c85abff01855cb4f103805a339e9ad58c6ad31 Mon Sep 17 00:00:00 2001 From: avitoras Date: Tue, 21 Jan 2025 15:38:24 +0300 Subject: [PATCH 04/14] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=B3=D0=B0=D0=B2=D0=BD=D0=BE=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D0=BF=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B0=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=B2=D0=B0=D1=89=D0=B5=D0=B5=D0=B5=D0=B5=20=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=20=D0=BE=D1=82=D1=81=D1=82=D0=BE=D0=B9=20=D0=B5=D0=B1=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D0=B9=20=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/tokens.cpython-313.pyc | Bin 0 -> 195 bytes app/__init__.py | 0 app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 158 bytes app/__pycache__/app.cpython-313.pyc | Bin 0 -> 3872 bytes app/app.py | 44 +++++++ main.py | 124 +----------------- styles.tcss => tcss/style.tcss | 0 telegram/__init__.py | 0 telegram/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 163 bytes telegram/__pycache__/client.cpython-313.pyc | Bin 0 -> 2681 bytes telegram/client.py | 31 +++++ tokens.py | 2 + widgets/__init__.py | 0 widgets/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 162 bytes widgets/__pycache__/chat.cpython-313.pyc | Bin 0 -> 1930 bytes widgets/__pycache__/dialog.cpython-313.pyc | Bin 0 -> 2238 bytes widgets/__pycache__/message.cpython-313.pyc | Bin 0 -> 2141 bytes widgets/chat.py | 18 +++ widgets/dialog.py | 24 ++++ widgets/message.py | 27 ++++ 20 files changed, 149 insertions(+), 121 deletions(-) create mode 100644 __pycache__/tokens.cpython-313.pyc create mode 100644 app/__init__.py create mode 100644 app/__pycache__/__init__.cpython-313.pyc create mode 100644 app/__pycache__/app.cpython-313.pyc create mode 100644 app/app.py rename styles.tcss => tcss/style.tcss (100%) create mode 100644 telegram/__init__.py create mode 100644 telegram/__pycache__/__init__.cpython-313.pyc create mode 100644 telegram/__pycache__/client.cpython-313.pyc create mode 100644 telegram/client.py create mode 100644 tokens.py create mode 100644 widgets/__init__.py create mode 100644 widgets/__pycache__/__init__.cpython-313.pyc create mode 100644 widgets/__pycache__/chat.cpython-313.pyc create mode 100644 widgets/__pycache__/dialog.cpython-313.pyc create mode 100644 widgets/__pycache__/message.cpython-313.pyc create mode 100644 widgets/chat.py create mode 100644 widgets/dialog.py create mode 100644 widgets/message.py diff --git a/__pycache__/tokens.cpython-313.pyc b/__pycache__/tokens.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae364056d10c74c8c9110c7c917933b4bb5401e5 GIT binary patch literal 195 zcmey&%ge<81Sco=r%MCr#~=<2FhUuhd4P=jOk38%rEnSLNB?2gr6qU zEsn&3%=nDN;*48tAXa9|N`}uMy|>Ku%My$9GxBp&^%Ki7OY(~ni}eGF^0QKtON#Z= zGl4`&YEEi;QDSa>P{wB#AY&>+I)f&o-%5reCLr%KNa~iYepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`Pb?_VkB`sH%PfhH*DI*J g#bJ}1pHiBWYFESxG!0~1F^KVznURsPh#ANN0P}k$#{d8T literal 0 HcmV?d00001 diff --git a/app/__pycache__/app.cpython-313.pyc b/app/__pycache__/app.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53fddff829e3b45b48bf927f0640ca857485d6f7 GIT binary patch literal 3872 zcmb7HO>7&-6`mn?$>rZtBK1Sc`k_Tj7HyeKTK~IoP0KB8s$mVSevMW#3_QpY7Z@nws6{}jZVET!?<(?GE^WZ-xAA#ouY@n+2v9c z;k4)o`{vEOH^Z4X-}}*q&*w(a-uv0>`H&N#Puaj~e1qBeB{0{Jgd{wPQkdYBjo2b= zY@4*FIKriP!W(1zq$A}d&Xhoel#94hZsIoPxk*pTOS~x`@ufr}ru@X8>L4AdPSR<} z`N=>kNP-6MnCwb*lWv1|PWGgFNw2{Rlc7|Ygj0Q_FM>j77)h=^B)QLV`|Rd9$+o;F z=Dp7bF&h=9m7=7>WEK};cN=wN77KDAM+Mf; zXVm$uc}+eVOKe1u;mFg=I{cqLF`fV8gRsW;@2mJ&6cJ=j3fp|QjWmPqyY8Q)2oUvI9 z+gObOjy>kac_2seH8g8U&@`Sw-v{}=#!LjwTJS`(4ohuTu(%mCWdPe6o4o~<8B}bp z$)gpVjyWl>R>1I7kmW+Dpvf``!Wz=WNH-AboX?bUMbPkT4P?n#16nIK%Bf0mj=E%+ z$*8Ixylk#~btEq{60Z4N%tx`z9)3YzWc#-t8 zpX_QedfYmfA0%oN$m7dsP4K*tYx(gW~057_50>4P;fI~jzII zLY-+r`yZC!H?4xn80g1r#?>irMl%CurU}RZh-t7DF%7`>f&mbIQ}E0z>OT0LnOsPCkoJ$v$JBVkT;9B@X7rwn-R1X|BZ2x-8AJ z-;x}#vcnp0_9FVaO>!QxmmF{d2HCbY^W!0@F_0nf&JRxq^{axu5*f1bRe$vGoUsnqpiMIjgE zatU&p!IDn+DHd54%1a96N|^nq5gVkyc!y>hYnUl2X7NSbWjf+uLq7V zJMV@E-WmM)pgwk}7Cu}LpQwgUe0a7NPS?X{tKqZy_s-YCFD#39g`T?5Ulsb_o~{YQ zbs<(2Vjl~;KkXS<{`TEq`1+AIjx3+J8wg#GzY(to#;Sp_<%zXm?+xx2e{1eHg_Xjs zS$*_SHFy}bynS_Vr0R{_?D~gy^nnZY4u0-J!3q4O%5ZRHV8V;v!4q!o=JDOYZgD`C z(xUKfJTMp*sb`y!#B?wl0HI|L^8X=h%r#^8X0ntxCbfXHgsx#&+jJMH=|-jmYCQPc z*NAPP*@Us>C{NbQqwQ!Mv53W54$eDgi=ZJmX9T6A&)A319R6yJJTf&5SUb{G+=^rJ zH0((Y%&O!FD!;WU$x03g5;ekfZqj4bIjJwufhUWezqpno)Lv9WC7* zEMEsUvl3^<3U{ucHqtfO1`DYdLWQin1a%bDGh_&+8YPjLu01VM+S}^WErM~jiaqt? zX5uol_MWP^rzR%qLP8f3YyPfRFI4@}ntwz$Gxk$vM!Pz=AHR$Da=!@f$1!Z26S60J z*L2L!$~+dcWSP2Us2p-otpo3s*4urHwGRe z=pKn6+CV_a4r`-yte*-RRIl26xkMOz2zzPj(Ug}oxObdA7OUTkE6{*7-Aoi}i+8a| zK-ke(L2NF+P{={jPzlScgr&?D7i48CVZn}i8)|VD=gmAKT2qZ=!2ETy&W@XPwo#B% zhgN<;DXE)v>=@&9w0j-xT1V*`O0T2II-0zP a2JWHl_t4YpDEg4wfk%HD{Q^N}Z1^9?s9&l8 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..f890a5cc6786001b08ede4c62e8e9df92dad2018 GIT binary patch literal 163 zcmey&%ge<81PT-T(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i8epzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE0bM)imGxIV_;^XxSDsOSv dy?*Sy>EXd}L;1WGrF^vH7fK6rR~XZyd)?LVh5mi31^t)q)Wsq@)ogY7dYU3&og5f@-z3H_pPwPG>f$ zBK1(w3V}qGa)_#4+LTHa>7|z*s;U>RJz?BpYlT!*J>>>*5z4La&Du@?SL&%F?VC40 zZ)V>6-p^KAT7m@H>h0C+Z!#f&;h@%(DpNTP%o<6OB%LNxl+h`PNmDYDqhv2RK$21q zNy?*=zndvyPJubKKBs22SYQ)3#AGjg!E&rDGZ)@+Y%Aw_nzala_tYiVc6i!9#;4UY zN#z8L*T`kUXp%4~NtukdG9@VkRg-WV(~?a|wTE1xERfV-tS1AkDXYhVUgz@{zRS$T zMT>2Mx+GoG5${vs!^z60K-S153Y}<4I@>;qE6`HJsn4g#OznmKqM$Q)&I>IiU(#w* zGkT5N=?{?6>Y!$`D9MtTl<<@#Zn2ok@On>6`zC8MGZ{me&6qcH8OLG_74d=z>%(&v z=VsQ5DW1YDXU@~iMcc45o{s&z$>+Vug1@CE%TYE8%{v`mHd%Z=zhK49W!ud&lgH1q z{1q$h@_5z;;#RRU+$Fm{i2Khvvbf^uhGFMy*D$=khE_jPTgN;3VIUuqM_q&K7w=vy zcAdJR+-ffCq1EP_%_aR%K|i#f|8coAcILs@nc~>g@A`BF1W39L01VJBpTJkS0OTNv z0&oBe@GrKRDuBD^Yeqi{&OHz7%XCdns3ABdOLUS@$K*M>K@%~FHG`O^a@S<;24!Iw zaePITwc|n{ozLaKNZu=rm98!$@O(dzP0+d=ZeP86^J*#FUkLXX!viIKpr{XQS3`(;?*>ZG`^6+OlrAzNT+5WAp0M**<*xj>@bL#$L3sx;tWMwwE;?EQAM(;h~Z~ zRMdyISubo#?+zpk6@lrm!1$QPNlyCjxTa{5o}{g${RLcea!P6(o};K5v?w_T&jIYa zgCr&GAr}=c#R&)Q%pOAWr)L8GzTGsX)TGZ#=Q;^#hEuqdQwD+h+#Eqqf^d#ePj&1C z+l?u{ONDILVn)U`oqU#KtS(x)jMr=|n2Xg}h<$W7HUPv6Rd*VW%^`F8Badb{<{S*+ zszWwsEw_H^Kx344Z4v(b9vmSYvwPo-P`Nky_4Jq1rQYL(-s3-w-p79O8KF%hpAVHo zdrP7ILa2XTdl(viBE#hU-pM}?pDaekzrTE!mRh5Q*64cM!`6cvmkW{c`}+9ygUI+c ze-&=H);oESex*!Ecj&nGtvn%p*8vkh(+TMp6_|UpXR=SZw_gYTC7-|uu6zV!cXCeA zMoIV*<`>as>WQ~DL!fAZRD(2J!x2_RS=^C84tz=;#H73-dlF<+C1);JUZ9#yW11Iy zTi|#$H$3$rgGH_H9 zqrwZpEjLkLsFjev@rjVXgNCt?&n!8}hYaI`CDW;{L=0ojX54k`oR!NP27|~TtOp5Z z1vY|2JipU0AZ5CC+Av+0+3zp87B`FyA}$xHlVLmwg5YvY1=q=zB&qMvEuE?dwlt~^ z|E($N8&5;3>c3iSPs7f7s|GKr;3FFUConauKq#T5HZcW1SNYNI!nyG2Yh_-y%T7t^WWcW*}1l literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..60dd34e3c0a50fe566aa73c2073eee422a938b38 GIT binary patch literal 162 zcmey&%ge<81V$74(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iOepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`FV9R#PX!9b$7kkcmc+;F j6;$5hu*uC&Da}c>D`Ewj2(qyl#Q4a}$jDg43}gWS$xJAn literal 0 HcmV?d00001 diff --git a/widgets/__pycache__/chat.cpython-313.pyc b/widgets/__pycache__/chat.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..401b80beae9d60a33e4bec18954c5f3a2e54d178 GIT binary patch literal 1930 zcmZ`4TWl0n^xS#u%x-sETBI!t3CkmdiNZ?QswB42Vh9woF${~wu1==2)9sY$4&Ir? z$B(5SiqS@lZ9>IFKFeP*f;B#p@`Zo1^+zW)Cir3cSFHwO{P5g6yA_C>wr9@ko_n6R z>zPan5avGmuqufFzcQu;a+6peL}C_n(8&oHB9!D9Eu0XBL@EwRRN}mNLLN%cL=Jjk z7wA$Jbh(`9L!S(&QcY;eDkC)#$X=8Jx4c?lx{-R?rh#LbE*d0WbShOl zu$X-d+M2|=i^QuKPZ=oDfeJdIqApNL&*)NF*2Vn_u)AASVuMmPD7Bm@q)JdAdNK>+ z>bNvcsG_H`KvlgBaZ*oH)b|tGwgVhU%Qb!9_9M08 z_~wXfSFA)cL-xTOfYomOG@=1A3&Z%0;Z&jouV5_0t^PoX^pRNiQlTX42HiUPN~%VMWcnr!>^wPpvC^E<5vsxAuiFNoy`333AE3qfxzCQz9iQLZ=svi->0m54Hg{}3+vqMVZz|l8p=&#$_Uwi7Z=Ks_vww4Dy8MsN zUYx16!EQ3G4a(w;{m&NUrQIr$OOGZ|x>QIF3dyAb1tmA-)Zk|2W`}}u%%1P7Ttqmk zrP(sGCkU50Wf&7)rS391Z5XfBO}DA(FpN=$`hn}z?3#ya#V{(KWf-&zqllyt&vW@I z(rw7o920KAMGs7Tx(zc3s54RzY#&c%n$Lh`yRKniG^~@n!q4!fqHN4KVomVb`*!yo z_(|&jT@vK&m-H)zxtG@f$pw9}a4jZp9SKjK{(gvy!?81AjW)(8=hgs@3PZvjc7O~1 zP>GzmlaS@UH9*8Sliya<)Tkq{-v}`41I<^4Pejz!l3MIP5>|lv@w~7AqxP0IK111S y9p|u>QEc<$$3lHNF75!`k1{rVbe)Xbjt;7Id@RZI2~;x999$ z6%&(E6GDRo5^W?T5)>cct)h?`pp`!HI9o!Zn-UUz;*D5L3Gu;iX7>uIM(1+h%zX3B zH{ZPp-|=oELt_C=_)EM$#VL zwzZ<3_mA;qa3-4H4D%1*Mh6h)6oh$&!-B%&kRlDBS2#szL5JnT92S*O3&LR1aYs`y zm?RVA8m#|7CQ|NEGVP9l;xd`Yhy3`Mqz-AI^J_tD<;Kx|rlz7lG=ScRq`ccWAShf9 zJbw|T`963c-Y9rd-qdVcw>`OF*xEr;FQj=-w5t^zd$Ot;Wy4X`wCIIw-5kOR@P`|y zNP_Sps>-&js!g+=f;Qs4mKoM?=8#p=Gup7>SXi?&Z)5AQo_Fj_(E#D-re4HaNp-45 z<|Fp_cBa5yrn7PcFQcv#{!4Y%LWqFUUIF6K2%3}QW0EU3pUFlD9=$D zG#UCf_|Ff5whg5qZ~GV5y8T?BlC9YhmjriU!Epkx0U6c)do`;G#?3}sTmMoGg*DBYNpUzm|60n`nx z?4+U0!5-86VoHse79^%VFfCJfo8{2ikPDQ;O(3E0tWw3Yb+0}^dN8k}cAtZ|j{xcM z6(q=bBcM_RT-9bes0fHtd;eT6Mnb;=W0+-8tK^b3sInoeN1+-+X-VUb5p(vg7dr z?CiXgT>qGN}iU6lRZPACC-w0wH4WPYLyQJ-<7E7AKb4jhbT!7D1WDqQ6JXJj!olA1qn)8 zW%VHYq^K1PSk((&bC9ZktUL?*Y|2|B=oh)}lsqP%N{l5)bI%=lBW?!$CHPSF_3cPU zDXEsVlCG*=R8>n>p=#1`ovMCV)y!Zep{hd$wjI+b>tzd;B~>k0c~xcHh-n$%mq2(c zfO#DaK5VMyIM{%2bsNm%0Waru(**YBFeOd~zSkM}Fa;SC6sGnn5RZG-}s5p>MS-*wVmIsgCw literal 0 HcmV?d00001 diff --git a/widgets/__pycache__/message.cpython-313.pyc b/widgets/__pycache__/message.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8d066b63ef0c394c7ed126957dff943805a3ea77 GIT binary patch literal 2141 zcmah~|8EpU6rb6>y}diG^sEJrLhM0_vV1#2C>2r&L@<%o#<*6S96H(F?Hx<@Zp+Lb z3O@lqm^OeH5*yK&@C&~f6N5$+6Cv?G*t0d9laiSDQ-32?13&nKZ{~KdVA}AK`|O*U zH*em|d+&2gU0o>z^7ZvbWjKb=pJEV+7Ai|eK$%4*GVusHi7CzrT^UhMs#KM_Iubjn zQ7wnEXe%;feaO^KX@hEeKaE%7hQ25U2IktRHEFw`BwzDtzU9z_DRI5%T8!C@CrS>pCSAJ(%Nnas*_0;;acYiF2-mCa+i=Zcq-w+c zV$62SRHTw-1iT<%jexX(E&!e+M0zKLC19Lu%SC=Pf6k)$(_YoiTjw0#qZZ4*MZGh2 z(P#OJ1K``PU7=Q$_;n}$q0AJ^hcWD#I#0WW`%L)6d!jJKA*>(Z$wjn~=$g@+iR_Ki z?V%u%Z6?NA8(zAaZmsLPmRbT6RKx;95f3CE_bSZJqH*;0)2pFTq4T7J;c5+!;}tET zu-nqHRKRCEnqB^G4^`^~f?@|7afknjD@sU;sf;TtT68>v1zypjQ%a_~ijU686=#!>T7g4J>``Os_v0De{dUsDJTbYdyG6T)bz^xN=nH|$wYfX3KP~%W*YkvB8V`OGz zKHcAXdH3{*#^}uGe0rd@cWC;J2kFht^yc~W?$*`=?Umos{eL{OW7ktV4y>|+u7`M@ z=@^mc0r)L_DwP~`O`+2>gD@vVK{-?q_m)!V)bYx_5cNv*J%?toBzqwL!LavoKw(?Z zE)?}m3C=7lIn)m)iM7LOKPuxPwTulcDyNfEt}>rWsxq+55QCj4%!Xd{s#6}bd3Qu9 z;lw7eVXpuzLN*q<)?X_u^lbQ2y>a~J=;xzzS!1qe$FJ!fk5#mGW25j%;a|29Y%i}l zmcZAx45{BFLHRLtOw)hCI;c>7k|qUV8u2k9uNx^rvx1I+W>W-;qEvTO}Mhk6oD$bL>zCvxk?fw{UKg1C#YDuI@#`H+V7(YZ?9-{4l Qf5) Date: Tue, 21 Jan 2025 15:40:00 +0300 Subject: [PATCH 05/14] =?UTF-8?q?=D1=81=D0=BB=D1=83=D1=87=D0=B0=D0=B9?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BB=D0=B8=D0=BB=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=B9=D0=BA=D0=B5=D1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __pycache__/tokens.cpython-313.pyc | Bin 195 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 __pycache__/tokens.cpython-313.pyc diff --git a/__pycache__/tokens.cpython-313.pyc b/__pycache__/tokens.cpython-313.pyc deleted file mode 100644 index ae364056d10c74c8c9110c7c917933b4bb5401e5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 195 zcmey&%ge<81Sco=r%MCr#~=<2FhUuhd4P=jOk38%rEnSLNB?2gr6qU zEsn&3%=nDN;*48tAXa9|N`}uMy|>Ku%My$9GxBp&^%Ki7OY(~ni}eGF^0QKtON#Z= zGl4`&YEEi;QDSa Date: Tue, 21 Jan 2025 21:19:28 +0300 Subject: [PATCH 06/14] =?UTF-8?q?=D0=AD=D1=82=D0=BE=20=D0=BD=D0=B5=D0=B2?= =?UTF-8?q?=D1=8B=D0=BD=D0=BE=D1=81=D0=B8=D0=BC=D0=BE=20-=20=D0=9A=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8F,=20=D1=81=D0=BF=D0=B0=D1=81=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 36 +++++++++++++++++++++--------------- tcss/style.tcss | 8 +++++++- telegram/client.py | 5 ++++- widgets/chat.py | 14 +++++++++----- 4 files changed, 41 insertions(+), 22 deletions(-) diff --git a/app/app.py b/app/app.py index 41124a6..8a806b8 100644 --- a/app/app.py +++ b/app/app.py @@ -1,7 +1,7 @@ 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 textual.widgets import Placeholder, Label, Static, Input, Button from widgets.chat import Chat from widgets.dialog import Dialog from telegram.client import TelegramClientWrapper @@ -12,31 +12,37 @@ class TelegramTUI(App): def __init__(self): super().__init__() - self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.handler) + self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.update_chat_list) async def on_mount(self) -> None: - await self.telegram_client.connect() - await self.update_chat_list() + self.chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") - async def handler(self, event): - await self.update_chat_list() + self.limit = 25 + for i in range(self.limit): + chat = Chat(id=f"chat-{i + 1}") + self.chat_container.mount(chat) + + await self.telegram_client.connect() + + # TODO: скоро сюда переедет маунт чатов из функции on_mount + def mount_chats(self): + pass 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() + dialogs = await self.telegram_client.get_dialogs(limit=self.limit) - for dialog in dialogs: - name = dialog.name - msg = dialog.message.message - chat = Chat(name, msg, id=f"chat-{dialog.id}") - chat_container.mount(chat) + for i in range(len(dialogs)): + 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("Новое сообщение") #колхоз дебаг 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): diff --git a/tcss/style.tcss b/tcss/style.tcss index d941030..1a50fb4 100644 --- a/tcss/style.tcss +++ b/tcss/style.tcss @@ -30,8 +30,14 @@ Message Container { #input_place { height: 3; + width: 70%; + align-horizontal: center; } #msg_input { - width: 70%; + width: 65%; +} + +#send { + } diff --git a/telegram/client.py b/telegram/client.py index 21935a4..ad759ce 100644 --- a/telegram/client.py +++ b/telegram/client.py @@ -3,7 +3,10 @@ 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()) + #self.client.add_event_handler(message_handler, events.NewMessage()) + self.client.on(events.NewMessage())(message_handler) + + #ни то ни то не работает, костя спаси async def connect(self): await self.client.start() diff --git a/widgets/chat.py b/widgets/chat.py index 0f71c93..74380d8 100644 --- a/widgets/chat.py +++ b/widgets/chat.py @@ -1,18 +1,22 @@ from textual.widgets import Label from textual.containers import Horizontal, Vertical from textual.widget import Widget +from textual.reactive import Reactive 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): + username = Reactive(" ", recompose=True) + msg = Reactive(" ", recompose=True) + peer_id = Reactive(0) + + def __init__(self, name: 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 + self.msg = str(self.peer_id) def compose(self): with Horizontal(): - yield Label(f"┌───┐\n│ {self.name[:1]} │\n└───┘") + yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘") with Vertical(): - yield Label(self.name, id="name") + yield Label(self.username, id="name") yield Label(self.msg, id="last_msg") From d137a71653b665bb83471a785a46cdcd8af12bab Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 21 Jan 2025 21:39:54 +0300 Subject: [PATCH 07/14] =?UTF-8?q?=D0=B2=D1=81=D1=91,=20=D0=B7=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=BB=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- telegram/client.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/telegram/client.py b/telegram/client.py index ad759ce..4a3ab25 100644 --- a/telegram/client.py +++ b/telegram/client.py @@ -2,12 +2,15 @@ from telethon import TelegramClient, events, utils class TelegramClientWrapper: def __init__(self, api_id, api_hash, message_handler): + self.message_handler = message_handler self.client = TelegramClient('user', api_id, api_hash) - #self.client.add_event_handler(message_handler, events.NewMessage()) - self.client.on(events.NewMessage())(message_handler) + self.client.on(events.NewMessage())(self.local_message_handler) #ни то ни то не работает, костя спаси + async def local_message_handler(self, event): + await self.message_handler() + async def connect(self): await self.client.start() From 3472ae798dd33a29f2cc08645a59367e57bd3220 Mon Sep 17 00:00:00 2001 From: kirill Date: Tue, 21 Jan 2025 22:55:24 +0300 Subject: [PATCH 08/14] =?UTF-8?q?=D1=87=D1=82=D0=BE=20=D1=82=D0=BE=20?= =?UTF-8?q?=D1=83=D0=BD=D1=8B=D0=BB=D0=BE=20=D0=BA=D0=B0=D0=BA=20=D1=82?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 19 ++++++++++++++++--- widgets/dialog.py | 1 + 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/app.py b/app/app.py index 8a806b8..a3c1f09 100644 --- a/app/app.py +++ b/app/app.py @@ -6,6 +6,7 @@ from widgets.chat import Chat from widgets.dialog import Dialog from telegram.client import TelegramClientWrapper from tokens import api_id, api_hash +from time import sleep class TelegramTUI(App): CSS_PATH = "../tcss/style.tcss" @@ -16,17 +17,28 @@ class TelegramTUI(App): async def on_mount(self) -> None: self.chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") - self.limit = 25 for i in range(self.limit): chat = Chat(id=f"chat-{i + 1}") self.chat_container.mount(chat) + #self.mount_chats(limit=25) await self.telegram_client.connect() + await self.update_chat_list() + # TODO: скоро сюда переедет маунт чатов из функции on_mount - def mount_chats(self): - pass + # P.S. сделано, но неудачно + def mount_chats(self, limit: int): + self.limit = limit + 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 + 1 + (limit - chats_amount)}") + self.chat_container.mount(chat) + elif not (limit == chats_amount): + for i in range(chats_amount - limit): + self.chat_container.query(Chat).last().remove() async def update_chat_list(self): dialogs = await self.telegram_client.get_dialogs(limit=self.limit) @@ -42,6 +54,7 @@ class TelegramTUI(App): with Horizontal(id="main_container"): with Horizontal(id="chats"): yield VerticalScroll(Static(id="chat_container")) + #TODO: сделать кнопку чтобы прогрузить больше чатов, это оптимизация yield Dialog() diff --git a/widgets/dialog.py b/widgets/dialog.py index 6d006c3..3c5cbde 100644 --- a/widgets/dialog.py +++ b/widgets/dialog.py @@ -15,6 +15,7 @@ class Dialog(Widget): 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") From 5be6da7eeb13ec9a3fbe8c24b595bcfb881b3ad6 Mon Sep 17 00:00:00 2001 From: kirill Date: Fri, 24 Jan 2025 21:15:39 +0300 Subject: [PATCH 09/14] =?UTF-8?q?=D0=BD=D0=B8=D1=87=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D0=BA=D0=BE=D0=BC=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BC=D0=B5=D0=BD=D1=8F=D0=BB,=20=D0=BD=D0=BE=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=20=D1=84=D1=83=D0=BD=D0=BA?= =?UTF-8?q?=D1=86=D0=B8=D1=8E=20get=5Fdialogs,=20=D0=BA=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B0=D1=8F=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20?= =?UTF-8?q?=D0=B2=D0=BE=D0=B7=D0=B2=D1=80=D0=B0=D1=89=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=BD=D1=8B=D0=B9=20=D0=B4=D0=B8=D0=B0=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 13 +++++++------ telegram/client.py | 17 +++++++++-------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/app/app.py b/app/app.py index a3c1f09..6b15054 100644 --- a/app/app.py +++ b/app/app.py @@ -1,23 +1,23 @@ -from telethon import TelegramClient, events +#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, Input, Button +from textual.containers import Horizontal, VerticalScroll +from textual.widgets import Static, Footer from widgets.chat import Chat from widgets.dialog import Dialog from telegram.client import TelegramClientWrapper from tokens import api_id, api_hash -from time import sleep class TelegramTUI(App): CSS_PATH = "../tcss/style.tcss" def __init__(self): super().__init__() - self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.update_chat_list) + async def on_mount(self) -> None: + self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.update_chat_list) self.chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") - self.limit = 25 + self.limit = 100 for i in range(self.limit): chat = Chat(id=f"chat-{i + 1}") self.chat_container.mount(chat) @@ -51,6 +51,7 @@ class TelegramTUI(App): #self.notify("Новое сообщение") #колхоз дебаг def compose(self) -> ComposeResult: + yield Footer() with Horizontal(id="main_container"): with Horizontal(id="chats"): yield VerticalScroll(Static(id="chat_container")) diff --git a/telegram/client.py b/telegram/client.py index 4a3ab25..03b5fbe 100644 --- a/telegram/client.py +++ b/telegram/client.py @@ -6,8 +6,6 @@ class TelegramClientWrapper: self.client = TelegramClient('user', api_id, api_hash) self.client.on(events.NewMessage())(self.local_message_handler) - #ни то ни то не работает, костя спаси - async def local_message_handler(self, event): await self.message_handler() @@ -17,21 +15,24 @@ class TelegramClientWrapper: async def disconnect(self): await self.client.disconnect() - async def get_dialogs(self, limit=10): + async def get_dialogs(self, limit=None): + await self.client.get_dialogs(limit=limit) 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] + #return [self._map_dialog(d) for d in dialogs_list] + return dialogs_list - def _map_dialog(self, dialog): +#ого: + """def _map_dialog(self, dialog): return DialogInfo( id=dialog.id, name=utils.get_display_name(dialog.entity), message=dialog.message - ) + )""" -class DialogInfo: +"""class DialogInfo: def __init__(self, id, name, message): self.id = id self.name = name - self.message = message + self.message = message""" From c9fb658a6b50ff7fa5cb20ea363e36a5f2c18277 Mon Sep 17 00:00:00 2001 From: kirill Date: Fri, 24 Jan 2025 22:40:08 +0300 Subject: [PATCH 10/14] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=83=D1=87=D0=B8=D0=BB=D0=BE=D1=81=D1=8C=20=D1=81?= =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B0=D1=82=D1=8C=20=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=BD=D0=BE=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 55 ++++++++++++++++++++++++++++++++-------------- telegram/client.py | 5 +++++ widgets/chat.py | 6 ++++- widgets/dialog.py | 2 ++ widgets/message.py | 2 ++ 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/app/app.py b/app/app.py index 6b15054..92f58a6 100644 --- a/app/app.py +++ b/app/app.py @@ -1,34 +1,30 @@ -#from telethon import TelegramClient, events +from telethon import TelegramClient, events from textual.app import App, ComposeResult from textual.containers import Horizontal, VerticalScroll from textual.widgets import Static, Footer from widgets.chat import Chat from widgets.dialog import Dialog -from telegram.client import TelegramClientWrapper +from textual.screen import Screen +#from telegram.client import TelegramClientWrapper from tokens import api_id, api_hash -class TelegramTUI(App): - CSS_PATH = "../tcss/style.tcss" +class ChatScreen(Screen): + """Класс экрана чатов, он же основной экран приложения""" - def __init__(self): - super().__init__() - + def __init__(self, name = None, id = None, classes = None, telegram_client = None): + super().__init__(name, id, classes) + self.telegram_client = telegram_client + self.telegram_client.on(events.NewMessage())(self.update_chat_list) - async def on_mount(self) -> None: - self.telegram_client = TelegramClientWrapper(api_id, api_hash, self.update_chat_list) + def on_mount(self): self.chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") + self.limit = 100 for i in range(self.limit): - chat = Chat(id=f"chat-{i + 1}") + chat = Chat(id=f"chat-{i + 1}", notify_func=self.notify) self.chat_container.mount(chat) #self.mount_chats(limit=25) - await self.telegram_client.connect() - - await self.update_chat_list() - - # TODO: скоро сюда переедет маунт чатов из функции on_mount - # P.S. сделано, но неудачно def mount_chats(self, limit: int): self.limit = limit chats_amount = len(self.chat_container.query(Chat)) @@ -48,7 +44,7 @@ class TelegramTUI(App): chat.username = str(dialogs[i].name) chat.msg = str(dialogs[i].message.message) chat.peer_id = dialogs[i].id - #self.notify("Новое сообщение") #колхоз дебаг + self.notify("Новое сообщение") #колхоз дебаг def compose(self) -> ComposeResult: yield Footer() @@ -59,6 +55,31 @@ class TelegramTUI(App): yield Dialog() +class AuthScreen(Screen): + """Это будет классом экрана логина""" + + pass + +class TelegramTUI(App): + """Класс приложения""" + + CSS_PATH = "../tcss/style.tcss" + #SCREENS = {"chats": ChatScreen} + + def __init__(self): + super().__init__() + + async def on_mount(self) -> None: + self.telegram_client = TelegramClient("../user", api_id, api_hash) + #self.push_screen("chats") + chat_screen = ChatScreen(telegram_client=self.telegram_client) + self.install_screen(chat_screen, name="chats") + self.push_screen("chats") + self.telegram_client.on(events.NewMessage())(chat_screen.update_chat_list()) + + await self.telegram_client.start() + await self.update_chat_list() + async def on_exit_app(self): await self.telegram_client.disconnect() return super()._on_exit_app() diff --git a/telegram/client.py b/telegram/client.py index 03b5fbe..59ff039 100644 --- a/telegram/client.py +++ b/telegram/client.py @@ -1,6 +1,8 @@ from telethon import TelegramClient, events, utils class TelegramClientWrapper: + """Обёртка для метода TelegramClient из Telethon""" + def __init__(self, api_id, api_hash, message_handler): self.message_handler = message_handler self.client = TelegramClient('user', api_id, api_hash) @@ -10,6 +12,9 @@ class TelegramClientWrapper: await self.message_handler() async def connect(self): + await self.client.connect() + + async def start(self): await self.client.start() async def disconnect(self): diff --git a/widgets/chat.py b/widgets/chat.py index 74380d8..09dfdc0 100644 --- a/widgets/chat.py +++ b/widgets/chat.py @@ -4,15 +4,19 @@ from textual.widget import Widget from textual.reactive import Reactive class Chat(Widget): + """Класс виджета чата для панели чатов""" + username = Reactive(" ", recompose=True) msg = Reactive(" ", recompose=True) peer_id = Reactive(0) - def __init__(self, name: str | None = None, id: str | None = None, classes: str | None = None, disabled: bool = False): + 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(): diff --git a/widgets/dialog.py b/widgets/dialog.py index 3c5cbde..e8ed1b6 100644 --- a/widgets/dialog.py +++ b/widgets/dialog.py @@ -4,6 +4,8 @@ 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) diff --git a/widgets/message.py b/widgets/message.py index b1fc27c..edda533 100644 --- a/widgets/message.py +++ b/widgets/message.py @@ -3,6 +3,8 @@ 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 From 38625d929bdc0c454672704be8bb8b9537e8e077 Mon Sep 17 00:00:00 2001 From: kirill Date: Sat, 25 Jan 2025 23:18:36 +0300 Subject: [PATCH 11/14] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B3=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D1=81=20=D0=BD=D0=B5=D0=B8=D0=B7=D0=B1=D0=B5=D0=B6=D0=B5?= =?UTF-8?q?=D0=BD=20(=D0=BF=D0=BE=D0=BA=D0=B0=20=D1=87=D1=82=D0=BE=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82?= =?UTF-8?q?,=20=D0=BD=D0=BE=20=D1=81=D0=BA=D0=BE=D1=80=D0=BE=20=D0=B1?= =?UTF-8?q?=D1=83=D0=B4=D0=B5=D1=82=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=20?= =?UTF-8?q?=D0=B2=D1=85=D0=BE=D0=B4=D0=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 53 +++++++++++++++++++++++++++++++++++++++---------- tcss/style.tcss | 4 ++++ 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/app/app.py b/app/app.py index 92f58a6..ad2477f 100644 --- a/app/app.py +++ b/app/app.py @@ -1,7 +1,7 @@ from telethon import TelegramClient, events from textual.app import App, ComposeResult -from textual.containers import Horizontal, VerticalScroll -from textual.widgets import Static, Footer +from textual.containers import Horizontal, VerticalScroll, Vertical +from textual.widgets import Static, Footer, Label, Input, Button from widgets.chat import Chat from widgets.dialog import Dialog from textual.screen import Screen @@ -44,7 +44,7 @@ class ChatScreen(Screen): chat.username = str(dialogs[i].name) chat.msg = str(dialogs[i].message.message) chat.peer_id = dialogs[i].id - self.notify("Новое сообщение") #колхоз дебаг + #self.notify("Новое сообщение") #колхоз дебаг def compose(self) -> ComposeResult: yield Footer() @@ -58,7 +58,34 @@ class ChatScreen(Screen): class AuthScreen(Screen): """Это будет классом экрана логина""" - pass + def __init__(self, name = None, id = None, classes = None, telegram_client: TelegramClient | None = None): + super().__init__(name, id, classes) + self.client = telegram_client + 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_submit(self, event: Input.Submitted) -> None: + if event.button.id == "phone": + await self.client.send_code_request(event.value) + for i in ("#phone", "#password"): + self.ac.query_one(i).disabled = True + self.ac.query_one("#code").disabled = False + elif event.button.id == "code": + await self.client.sign_in(event.value) + for i in ("#phone", "#code"): + self.ac.query_one(i).disabled = True + self.ac.query_one("#password").disabled = False + elif event.button.id == "password": + await self.client.sign_in(event.value) + for i in ("#phone", "#password"): + self.ac.query_one(i).disabled = True + self.ac.query_one("#code").disabled = True class TelegramTUI(App): """Класс приложения""" @@ -67,18 +94,22 @@ class TelegramTUI(App): #SCREENS = {"chats": ChatScreen} def __init__(self): - super().__init__() + super().__init__() async def on_mount(self) -> None: - self.telegram_client = TelegramClient("../user", api_id, api_hash) - #self.push_screen("chats") - chat_screen = ChatScreen(telegram_client=self.telegram_client) + self.telegram_client = TelegramClient("user", api_id, api_hash) + await self.telegram_client.start() + + 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") + + """chat_screen = ChatScreen(telegram_client=self.telegram_client) self.install_screen(chat_screen, name="chats") self.push_screen("chats") - self.telegram_client.on(events.NewMessage())(chat_screen.update_chat_list()) - await self.telegram_client.start() - await self.update_chat_list() + await chat_screen.update_chat_list()""" async def on_exit_app(self): await self.telegram_client.disconnect() diff --git a/tcss/style.tcss b/tcss/style.tcss index 1a50fb4..cde8031 100644 --- a/tcss/style.tcss +++ b/tcss/style.tcss @@ -41,3 +41,7 @@ Message Container { #send { } + +#auth_container{ + align: center middle; +} From 051add865a31bd635bea09dadc57fea1d085ee69 Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 26 Jan 2025 13:42:14 +0300 Subject: [PATCH 12/14] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD,=20=D0=B0=20?= =?UTF-8?q?=D1=82=D0=B0=D0=BA=D0=B6=D0=B5=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=81=D0=B5=20=D1=81=D1=82=D1=80=D0=BE=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B2=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=D1=85=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=80=D0=B5=D0=B2=D1=8B=D1=88=D0=B0=D1=8E=D1=82?= =?UTF-8?q?=20=D0=BB=D0=B8=D0=BC=D0=B8=D1=82=20=D0=B2=2079=20=D1=81=D0=B8?= =?UTF-8?q?=D0=BC=D0=B2=D0=BE=D0=BB=D0=BE=D0=B2=20(PEP=208)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/app.py | 102 +++++------------------------------------ screens/auth_screen.py | 56 ++++++++++++++++++++++ screens/chat_screen.py | 64 ++++++++++++++++++++++++++ telegram/client.py | 5 ++ tokens.py | 6 ++- widgets/chat.py | 16 ++++++- widgets/dialog.py | 10 +++- widgets/message.py | 10 +++- 8 files changed, 171 insertions(+), 98 deletions(-) create mode 100644 screens/auth_screen.py create mode 100644 screens/chat_screen.py diff --git a/app/app.py b/app/app.py index ad2477f..3ddbf85 100644 --- a/app/app.py +++ b/app/app.py @@ -1,91 +1,15 @@ from telethon import TelegramClient, events +from telethon.errors import SessionPasswordNeededError from textual.app import App, ComposeResult from textual.containers import Horizontal, VerticalScroll, Vertical from textual.widgets import Static, Footer, Label, Input, Button +from textual.screen import Screen +from textual.events import Event from widgets.chat import Chat from widgets.dialog import Dialog -from textual.screen import Screen -#from telegram.client import TelegramClientWrapper from tokens import api_id, api_hash - -class ChatScreen(Screen): - """Класс экрана чатов, он же основной экран приложения""" - - def __init__(self, name = None, id = None, classes = None, telegram_client = None): - super().__init__(name, id, classes) - self.telegram_client = telegram_client - self.telegram_client.on(events.NewMessage())(self.update_chat_list) - - def on_mount(self): - self.chat_container = self.query_one("#main_container").query_one("#chats").query_one("#chat_container") - - self.limit = 100 - for i in range(self.limit): - chat = Chat(id=f"chat-{i + 1}", notify_func=self.notify) - self.chat_container.mount(chat) - #self.mount_chats(limit=25) - - def mount_chats(self, limit: int): - self.limit = limit - 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 + 1 + (limit - chats_amount)}") - self.chat_container.mount(chat) - elif not (limit == chats_amount): - for i in range(chats_amount - limit): - self.chat_container.query(Chat).last().remove() - - async def update_chat_list(self): - dialogs = await self.telegram_client.get_dialogs(limit=self.limit) - - for i in range(len(dialogs)): - 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("Новое сообщение") #колхоз дебаг - - def compose(self) -> ComposeResult: - yield Footer() - with Horizontal(id="main_container"): - with Horizontal(id="chats"): - yield VerticalScroll(Static(id="chat_container")) - #TODO: сделать кнопку чтобы прогрузить больше чатов, это оптимизация - - yield Dialog() - -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 - 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_submit(self, event: Input.Submitted) -> None: - if event.button.id == "phone": - await self.client.send_code_request(event.value) - for i in ("#phone", "#password"): - self.ac.query_one(i).disabled = True - self.ac.query_one("#code").disabled = False - elif event.button.id == "code": - await self.client.sign_in(event.value) - for i in ("#phone", "#code"): - self.ac.query_one(i).disabled = True - self.ac.query_one("#password").disabled = False - elif event.button.id == "password": - await self.client.sign_in(event.value) - for i in ("#phone", "#password"): - self.ac.query_one(i).disabled = True - self.ac.query_one("#code").disabled = True +from screens.auth_screen import AuthScreen +from screens.chat_screen import ChatScreen class TelegramTUI(App): """Класс приложения""" @@ -93,23 +17,19 @@ class TelegramTUI(App): CSS_PATH = "../tcss/style.tcss" #SCREENS = {"chats": ChatScreen} - def __init__(self): - super().__init__() - async def on_mount(self) -> None: self.telegram_client = TelegramClient("user", api_id, api_hash) - await self.telegram_client.start() + 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") - - """chat_screen = ChatScreen(telegram_client=self.telegram_client) - self.install_screen(chat_screen, name="chats") - self.push_screen("chats") - await self.telegram_client.start() - await chat_screen.update_chat_list()""" + else: + self.push_screen("chats") async def on_exit_app(self): await self.telegram_client.disconnect() diff --git a/screens/auth_screen.py b/screens/auth_screen.py new file mode 100644 index 0000000..4406c4c --- /dev/null +++ b/screens/auth_screen.py @@ -0,0 +1,56 @@ +from textual.screen import Screen +from textual.widgets import Label, Input +from textual.containers import Vertical +from telethon import TelegramClient +from telethon.errors import SessionPasswordNeededError + +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") diff --git a/screens/chat_screen.py b/screens/chat_screen.py new file mode 100644 index 0000000..6e58992 --- /dev/null +++ b/screens/chat_screen.py @@ -0,0 +1,64 @@ +from textual.screen import Screen +from textual.widgets import Footer, Static +from textual.containers import Horizontal, VerticalScroll +from telethon import TelegramClient, events +from widgets.dialog import Dialog +from widgets.chat import Chat + +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 + self.telegram_client.on(events.NewMessage())(self.update_chat_list) + + async def on_mount(self): + self.chat_container = self\ + .query_one("#main_container")\ + .query_one("#chats")\ + .query_one("#chat_container") + + self.limit = 100 + for i in range(self.limit): + chat = Chat(id=f"chat-{i + 1}", notify_func=self.notify) + self.chat_container.mount(chat) + #self.mount_chats(limit=25) + + await self.update_chat_list() + + def mount_chats(self, limit: int): + self.limit = limit + 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 + 1 + (limit - chats_amount)}") + self.chat_container.mount(chat) + elif not (limit == chats_amount): + for i in range(chats_amount - limit): + self.chat_container.query(Chat).last().remove() + + async def update_chat_list(self): + dialogs = await self.telegram_client.get_dialogs(limit=self.limit) + + for i in range(len(dialogs)): + 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("Новое сообщение") #колхоз дебаг + + def compose(self): + yield Footer() + with Horizontal(id="main_container"): + with Horizontal(id="chats"): + yield VerticalScroll(Static(id="chat_container")) + #TODO: сделать кнопку чтобы прогрузить больше чатов + + yield Dialog() diff --git a/telegram/client.py b/telegram/client.py index 59ff039..1010434 100644 --- a/telegram/client.py +++ b/telegram/client.py @@ -1,3 +1,8 @@ +""" +ЭТОТ ФАЙЛ БОЛЬШЕ НЕ ИСПОЛЬЗУЕТСЯ +СКОРО УДАЛИМ +""" + from telethon import TelegramClient, events, utils class TelegramClientWrapper: diff --git a/tokens.py b/tokens.py index 1c6c644..2d34b43 100644 --- a/tokens.py +++ b/tokens.py @@ -1,2 +1,4 @@ -api_hash = 111 -api_id = 11 +"""Получите свои API-ключи на https://my.telegram.org/apps""" + +api_id = 12345 +api_hash = "0123456789abcdef" \ No newline at end of file diff --git a/widgets/chat.py b/widgets/chat.py index 09dfdc0..13b9a7c 100644 --- a/widgets/chat.py +++ b/widgets/chat.py @@ -10,8 +10,20 @@ class Chat(Widget): 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) + 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): diff --git a/widgets/dialog.py b/widgets/dialog.py index e8ed1b6..05a9fe0 100644 --- a/widgets/dialog.py +++ b/widgets/dialog.py @@ -15,9 +15,15 @@ class Dialog(Widget): 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") + # должно быть примерно + # is_me = message.from_id == client.get_peer_id("me") + # но я могу ошибаться, я это фиш если что - #TODO: сделать кнопку чтобы прогрузить больше сообщений, но при этом чтобы при перезаходе в чат оставались прогруженными только 10 сообщений, а остальные декомпоузились + + #TODO: сделать кнопку чтобы прогрузить больше сообщений, + #но при этом чтобы при перезаходе в чат оставались + #прогруженными только 10 сообщений, + #а остальные декомпоузились with Horizontal(id="input_place"): yield Input(placeholder="Сообщение", id="msg_input") diff --git a/widgets/message.py b/widgets/message.py index edda533..88468e0 100644 --- a/widgets/message.py +++ b/widgets/message.py @@ -5,7 +5,15 @@ from textual.widget import Widget class Message(Widget): """Класс виджета сообщений для окна диалога""" - def __init__(self, name=None, message=None, is_me=None, id=None, classes=None, disabled=False): + 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 From ee1b6e8607e6797d769238d1de0c87614553dc2f Mon Sep 17 00:00:00 2001 From: kirill Date: Sun, 26 Jan 2025 13:51:46 +0300 Subject: [PATCH 13/14] =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=B9=D0=BA=D0=B5=D1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/__pycache__/__init__.cpython-313.pyc | Bin 158 -> 0 bytes app/__pycache__/app.cpython-313.pyc | Bin 3872 -> 0 bytes telegram/__pycache__/__init__.cpython-313.pyc | Bin 163 -> 0 bytes telegram/__pycache__/client.cpython-313.pyc | Bin 2681 -> 0 bytes widgets/__pycache__/__init__.cpython-313.pyc | Bin 162 -> 0 bytes widgets/__pycache__/chat.cpython-313.pyc | Bin 1930 -> 0 bytes widgets/__pycache__/dialog.cpython-313.pyc | Bin 2238 -> 0 bytes widgets/__pycache__/message.cpython-313.pyc | Bin 2141 -> 0 bytes 8 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 app/__pycache__/__init__.cpython-313.pyc delete mode 100644 app/__pycache__/app.cpython-313.pyc delete mode 100644 telegram/__pycache__/__init__.cpython-313.pyc delete mode 100644 telegram/__pycache__/client.cpython-313.pyc delete mode 100644 widgets/__pycache__/__init__.cpython-313.pyc delete mode 100644 widgets/__pycache__/chat.cpython-313.pyc delete mode 100644 widgets/__pycache__/dialog.cpython-313.pyc delete mode 100644 widgets/__pycache__/message.cpython-313.pyc diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 4a6205a883569d21007a2602af23c70299368b97..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 158 zcmey&%ge<81Y#5W(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iYepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`Pb?_VkB`sH%PfhH*DI*J g#bJ}1pHiBWYFESxG!0~1F^KVznURsPh#ANN0P}k$#{d8T diff --git a/app/__pycache__/app.cpython-313.pyc b/app/__pycache__/app.cpython-313.pyc deleted file mode 100644 index 53fddff829e3b45b48bf927f0640ca857485d6f7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3872 zcmb7HO>7&-6`mn?$>rZtBK1Sc`k_Tj7HyeKTK~IoP0KB8s$mVSevMW#3_QpY7Z@nws6{}jZVET!?<(?GE^WZ-xAA#ouY@n+2v9c z;k4)o`{vEOH^Z4X-}}*q&*w(a-uv0>`H&N#Puaj~e1qBeB{0{Jgd{wPQkdYBjo2b= zY@4*FIKriP!W(1zq$A}d&Xhoel#94hZsIoPxk*pTOS~x`@ufr}ru@X8>L4AdPSR<} z`N=>kNP-6MnCwb*lWv1|PWGgFNw2{Rlc7|Ygj0Q_FM>j77)h=^B)QLV`|Rd9$+o;F z=Dp7bF&h=9m7=7>WEK};cN=wN77KDAM+Mf; zXVm$uc}+eVOKe1u;mFg=I{cqLF`fV8gRsW;@2mJ&6cJ=j3fp|QjWmPqyY8Q)2oUvI9 z+gObOjy>kac_2seH8g8U&@`Sw-v{}=#!LjwTJS`(4ohuTu(%mCWdPe6o4o~<8B}bp z$)gpVjyWl>R>1I7kmW+Dpvf``!Wz=WNH-AboX?bUMbPkT4P?n#16nIK%Bf0mj=E%+ z$*8Ixylk#~btEq{60Z4N%tx`z9)3YzWc#-t8 zpX_QedfYmfA0%oN$m7dsP4K*tYx(gW~057_50>4P;fI~jzII zLY-+r`yZC!H?4xn80g1r#?>irMl%CurU}RZh-t7DF%7`>f&mbIQ}E0z>OT0LnOsPCkoJ$v$JBVkT;9B@X7rwn-R1X|BZ2x-8AJ z-;x}#vcnp0_9FVaO>!QxmmF{d2HCbY^W!0@F_0nf&JRxq^{axu5*f1bRe$vGoUsnqpiMIjgE zatU&p!IDn+DHd54%1a96N|^nq5gVkyc!y>hYnUl2X7NSbWjf+uLq7V zJMV@E-WmM)pgwk}7Cu}LpQwgUe0a7NPS?X{tKqZy_s-YCFD#39g`T?5Ulsb_o~{YQ zbs<(2Vjl~;KkXS<{`TEq`1+AIjx3+J8wg#GzY(to#;Sp_<%zXm?+xx2e{1eHg_Xjs zS$*_SHFy}bynS_Vr0R{_?D~gy^nnZY4u0-J!3q4O%5ZRHV8V;v!4q!o=JDOYZgD`C z(xUKfJTMp*sb`y!#B?wl0HI|L^8X=h%r#^8X0ntxCbfXHgsx#&+jJMH=|-jmYCQPc z*NAPP*@Us>C{NbQqwQ!Mv53W54$eDgi=ZJmX9T6A&)A319R6yJJTf&5SUb{G+=^rJ zH0((Y%&O!FD!;WU$x03g5;ekfZqj4bIjJwufhUWezqpno)Lv9WC7* zEMEsUvl3^<3U{ucHqtfO1`DYdLWQin1a%bDGh_&+8YPjLu01VM+S}^WErM~jiaqt? zX5uol_MWP^rzR%qLP8f3YyPfRFI4@}ntwz$Gxk$vM!Pz=AHR$Da=!@f$1!Z26S60J z*L2L!$~+dcWSP2Us2p-otpo3s*4urHwGRe z=pKn6+CV_a4r`-yte*-RRIl26xkMOz2zzPj(Ug}oxObdA7OUTkE6{*7-Aoi}i+8a| zK-ke(L2NF+P{={jPzlScgr&?D7i48CVZn}i8)|VD=gmAKT2qZ=!2ETy&W@XPwo#B% zhgN<;DXE)v>=@&9w0j-xT1V*`O0T2II-0zP a2JWHl_t4YpDEg4wfk%HD{Q^N}Z1^9?s9&l8 diff --git a/telegram/__pycache__/__init__.cpython-313.pyc b/telegram/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index f890a5cc6786001b08ede4c62e8e9df92dad2018..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 163 zcmey&%ge<81PT-T(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~i8epzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE0bM)imGxIV_;^XxSDsOSv dy?*Sy>EXd}L;1WGrF^vH7fK6rR~XZyd)?LVh5mi31^t)q)Wsq@)ogY7dYU3&og5f@-z3H_pPwPG>f$ zBK1(w3V}qGa)_#4+LTHa>7|z*s;U>RJz?BpYlT!*J>>>*5z4La&Du@?SL&%F?VC40 zZ)V>6-p^KAT7m@H>h0C+Z!#f&;h@%(DpNTP%o<6OB%LNxl+h`PNmDYDqhv2RK$21q zNy?*=zndvyPJubKKBs22SYQ)3#AGjg!E&rDGZ)@+Y%Aw_nzala_tYiVc6i!9#;4UY zN#z8L*T`kUXp%4~NtukdG9@VkRg-WV(~?a|wTE1xERfV-tS1AkDXYhVUgz@{zRS$T zMT>2Mx+GoG5${vs!^z60K-S153Y}<4I@>;qE6`HJsn4g#OznmKqM$Q)&I>IiU(#w* zGkT5N=?{?6>Y!$`D9MtTl<<@#Zn2ok@On>6`zC8MGZ{me&6qcH8OLG_74d=z>%(&v z=VsQ5DW1YDXU@~iMcc45o{s&z$>+Vug1@CE%TYE8%{v`mHd%Z=zhK49W!ud&lgH1q z{1q$h@_5z;;#RRU+$Fm{i2Khvvbf^uhGFMy*D$=khE_jPTgN;3VIUuqM_q&K7w=vy zcAdJR+-ffCq1EP_%_aR%K|i#f|8coAcILs@nc~>g@A`BF1W39L01VJBpTJkS0OTNv z0&oBe@GrKRDuBD^Yeqi{&OHz7%XCdns3ABdOLUS@$K*M>K@%~FHG`O^a@S<;24!Iw zaePITwc|n{ozLaKNZu=rm98!$@O(dzP0+d=ZeP86^J*#FUkLXX!viIKpr{XQS3`(;?*>ZG`^6+OlrAzNT+5WAp0M**<*xj>@bL#$L3sx;tWMwwE;?EQAM(;h~Z~ zRMdyISubo#?+zpk6@lrm!1$QPNlyCjxTa{5o}{g${RLcea!P6(o};K5v?w_T&jIYa zgCr&GAr}=c#R&)Q%pOAWr)L8GzTGsX)TGZ#=Q;^#hEuqdQwD+h+#Eqqf^d#ePj&1C z+l?u{ONDILVn)U`oqU#KtS(x)jMr=|n2Xg}h<$W7HUPv6Rd*VW%^`F8Badb{<{S*+ zszWwsEw_H^Kx344Z4v(b9vmSYvwPo-P`Nky_4Jq1rQYL(-s3-w-p79O8KF%hpAVHo zdrP7ILa2XTdl(viBE#hU-pM}?pDaekzrTE!mRh5Q*64cM!`6cvmkW{c`}+9ygUI+c ze-&=H);oESex*!Ecj&nGtvn%p*8vkh(+TMp6_|UpXR=SZw_gYTC7-|uu6zV!cXCeA zMoIV*<`>as>WQ~DL!fAZRD(2J!x2_RS=^C84tz=;#H73-dlF<+C1);JUZ9#yW11Iy zTi|#$H$3$rgGH_H9 zqrwZpEjLkLsFjev@rjVXgNCt?&n!8}hYaI`CDW;{L=0ojX54k`oR!NP27|~TtOp5Z z1vY|2JipU0AZ5CC+Av+0+3zp87B`FyA}$xHlVLmwg5YvY1=q=zB&qMvEuE?dwlt~^ z|E($N8&5;3>c3iSPs7f7s|GKr;3FFUConauKq#T5HZcW1SNYNI!nyG2Yh_-y%T7t^WWcW*}1l diff --git a/widgets/__pycache__/__init__.cpython-313.pyc b/widgets/__pycache__/__init__.cpython-313.pyc deleted file mode 100644 index 60dd34e3c0a50fe566aa73c2073eee422a938b38..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 162 zcmey&%ge<81V$74(?RrO5CH>>P{wB#AY&>+I)f&o-%5reCLr%KNa~iOepzCXenx(7 zs(xZwW=VcgVzGWeQGQlxa!Ij%dM1!4NzF-3FG|dfFDcE`FV9R#PX!9b$7kkcmc+;F j6;$5hu*uC&Da}c>D`Ewj2(qyl#Q4a}$jDg43}gWS$xJAn diff --git a/widgets/__pycache__/chat.cpython-313.pyc b/widgets/__pycache__/chat.cpython-313.pyc deleted file mode 100644 index 401b80beae9d60a33e4bec18954c5f3a2e54d178..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1930 zcmZ`4TWl0n^xS#u%x-sETBI!t3CkmdiNZ?QswB42Vh9woF${~wu1==2)9sY$4&Ir? z$B(5SiqS@lZ9>IFKFeP*f;B#p@`Zo1^+zW)Cir3cSFHwO{P5g6yA_C>wr9@ko_n6R z>zPan5avGmuqufFzcQu;a+6peL}C_n(8&oHB9!D9Eu0XBL@EwRRN}mNLLN%cL=Jjk z7wA$Jbh(`9L!S(&QcY;eDkC)#$X=8Jx4c?lx{-R?rh#LbE*d0WbShOl zu$X-d+M2|=i^QuKPZ=oDfeJdIqApNL&*)NF*2Vn_u)AASVuMmPD7Bm@q)JdAdNK>+ z>bNvcsG_H`KvlgBaZ*oH)b|tGwgVhU%Qb!9_9M08 z_~wXfSFA)cL-xTOfYomOG@=1A3&Z%0;Z&jouV5_0t^PoX^pRNiQlTX42HiUPN~%VMWcnr!>^wPpvC^E<5vsxAuiFNoy`333AE3qfxzCQz9iQLZ=svi->0m54Hg{}3+vqMVZz|l8p=&#$_Uwi7Z=Ks_vww4Dy8MsN zUYx16!EQ3G4a(w;{m&NUrQIr$OOGZ|x>QIF3dyAb1tmA-)Zk|2W`}}u%%1P7Ttqmk zrP(sGCkU50Wf&7)rS391Z5XfBO}DA(FpN=$`hn}z?3#ya#V{(KWf-&zqllyt&vW@I z(rw7o920KAMGs7Tx(zc3s54RzY#&c%n$Lh`yRKniG^~@n!q4!fqHN4KVomVb`*!yo z_(|&jT@vK&m-H)zxtG@f$pw9}a4jZp9SKjK{(gvy!?81AjW)(8=hgs@3PZvjc7O~1 zP>GzmlaS@UH9*8Sliya<)Tkq{-v}`41I<^4Pejz!l3MIP5>|lv@w~7AqxP0IK111S y9p|u>QEc<$$3lHNF75!`k1{rVbe)Xbjt;7Id@RZI2~;x999$ z6%&(E6GDRo5^W?T5)>cct)h?`pp`!HI9o!Zn-UUz;*D5L3Gu;iX7>uIM(1+h%zX3B zH{ZPp-|=oELt_C=_)EM$#VL zwzZ<3_mA;qa3-4H4D%1*Mh6h)6oh$&!-B%&kRlDBS2#szL5JnT92S*O3&LR1aYs`y zm?RVA8m#|7CQ|NEGVP9l;xd`Yhy3`Mqz-AI^J_tD<;Kx|rlz7lG=ScRq`ccWAShf9 zJbw|T`963c-Y9rd-qdVcw>`OF*xEr;FQj=-w5t^zd$Ot;Wy4X`wCIIw-5kOR@P`|y zNP_Sps>-&js!g+=f;Qs4mKoM?=8#p=Gup7>SXi?&Z)5AQo_Fj_(E#D-re4HaNp-45 z<|Fp_cBa5yrn7PcFQcv#{!4Y%LWqFUUIF6K2%3}QW0EU3pUFlD9=$D zG#UCf_|Ff5whg5qZ~GV5y8T?BlC9YhmjriU!Epkx0U6c)do`;G#?3}sTmMoGg*DBYNpUzm|60n`nx z?4+U0!5-86VoHse79^%VFfCJfo8{2ikPDQ;O(3E0tWw3Yb+0}^dN8k}cAtZ|j{xcM z6(q=bBcM_RT-9bes0fHtd;eT6Mnb;=W0+-8tK^b3sInoeN1+-+X-VUb5p(vg7dr z?CiXgT>qGN}iU6lRZPACC-w0wH4WPYLyQJ-<7E7AKb4jhbT!7D1WDqQ6JXJj!olA1qn)8 zW%VHYq^K1PSk((&bC9ZktUL?*Y|2|B=oh)}lsqP%N{l5)bI%=lBW?!$CHPSF_3cPU zDXEsVlCG*=R8>n>p=#1`ovMCV)y!Zep{hd$wjI+b>tzd;B~>k0c~xcHh-n$%mq2(c zfO#DaK5VMyIM{%2bsNm%0Waru(**YBFeOd~zSkM}Fa;SC6sGnn5RZG-}s5p>MS-*wVmIsgCw diff --git a/widgets/__pycache__/message.cpython-313.pyc b/widgets/__pycache__/message.cpython-313.pyc deleted file mode 100644 index 8d066b63ef0c394c7ed126957dff943805a3ea77..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2141 zcmah~|8EpU6rb6>y}diG^sEJrLhM0_vV1#2C>2r&L@<%o#<*6S96H(F?Hx<@Zp+Lb z3O@lqm^OeH5*yK&@C&~f6N5$+6Cv?G*t0d9laiSDQ-32?13&nKZ{~KdVA}AK`|O*U zH*em|d+&2gU0o>z^7ZvbWjKb=pJEV+7Ai|eK$%4*GVusHi7CzrT^UhMs#KM_Iubjn zQ7wnEXe%;feaO^KX@hEeKaE%7hQ25U2IktRHEFw`BwzDtzU9z_DRI5%T8!C@CrS>pCSAJ(%Nnas*_0;;acYiF2-mCa+i=Zcq-w+c zV$62SRHTw-1iT<%jexX(E&!e+M0zKLC19Lu%SC=Pf6k)$(_YoiTjw0#qZZ4*MZGh2 z(P#OJ1K``PU7=Q$_;n}$q0AJ^hcWD#I#0WW`%L)6d!jJKA*>(Z$wjn~=$g@+iR_Ki z?V%u%Z6?NA8(zAaZmsLPmRbT6RKx;95f3CE_bSZJqH*;0)2pFTq4T7J;c5+!;}tET zu-nqHRKRCEnqB^G4^`^~f?@|7afknjD@sU;sf;TtT68>v1zypjQ%a_~ijU686=#!>T7g4J>``Os_v0De{dUsDJTbYdyG6T)bz^xN=nH|$wYfX3KP~%W*YkvB8V`OGz zKHcAXdH3{*#^}uGe0rd@cWC;J2kFht^yc~W?$*`=?Umos{eL{OW7ktV4y>|+u7`M@ z=@^mc0r)L_DwP~`O`+2>gD@vVK{-?q_m)!V)bYx_5cNv*J%?toBzqwL!LavoKw(?Z zE)?}m3C=7lIn)m)iM7LOKPuxPwTulcDyNfEt}>rWsxq+55QCj4%!Xd{s#6}bd3Qu9 z;lw7eVXpuzLN*q<)?X_u^lbQ2y>a~J=;xzzS!1qe$FJ!fk5#mGW25j%;a|29Y%i}l zmcZAx45{BFLHRLtOw)hCI;c>7k|qUV8u2k9uNx^rvx1I+W>W-;qEvTO}Mhk6oD$bL>zCvxk?fw{UKg1C#YDuI@#`H+V7(YZ?9-{4l Qf5) Date: Sun, 26 Jan 2025 19:18:23 +0300 Subject: [PATCH 14/14] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=BB=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D1=83?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0=20+=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D1=8E=D1=82=D1=81=D1=8F=20+=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8C=20=D0=B2=D1=81=D1=91=20=D1=81=D1=82=D0=B0=D0=B1=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 ++ app/__init__.py | 0 main.py | 2 +- screens/auth_screen.py | 56 --------------- screens/chat_screen.py | 64 ----------------- {app => src}/app.py | 15 +--- src/screens.py | 149 +++++++++++++++++++++++++++++++++++++++ {tcss => src}/style.tcss | 0 src/widgets.py | 101 ++++++++++++++++++++++++++ telegram/__init__.py | 0 telegram/client.py | 48 ------------- widgets/__init__.py | 0 widgets/chat.py | 38 ---------- widgets/dialog.py | 33 --------- widgets/message.py | 37 ---------- 15 files changed, 259 insertions(+), 289 deletions(-) create mode 100644 .gitignore delete mode 100644 app/__init__.py delete mode 100644 screens/auth_screen.py delete mode 100644 screens/chat_screen.py rename {app => src}/app.py (61%) create mode 100644 src/screens.py rename {tcss => src}/style.tcss (100%) create mode 100644 src/widgets.py delete mode 100644 telegram/__init__.py delete mode 100644 telegram/client.py delete mode 100644 widgets/__init__.py delete mode 100644 widgets/chat.py delete mode 100644 widgets/dialog.py delete mode 100644 widgets/message.py 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/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/main.py b/main.py index d438f0f..9d0ccd7 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,4 @@ -from app.app import TelegramTUI +from src.app import TelegramTUI if __name__ == "__main__": tg = TelegramTUI() diff --git a/screens/auth_screen.py b/screens/auth_screen.py deleted file mode 100644 index 4406c4c..0000000 --- a/screens/auth_screen.py +++ /dev/null @@ -1,56 +0,0 @@ -from textual.screen import Screen -from textual.widgets import Label, Input -from textual.containers import Vertical -from telethon import TelegramClient -from telethon.errors import SessionPasswordNeededError - -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") diff --git a/screens/chat_screen.py b/screens/chat_screen.py deleted file mode 100644 index 6e58992..0000000 --- a/screens/chat_screen.py +++ /dev/null @@ -1,64 +0,0 @@ -from textual.screen import Screen -from textual.widgets import Footer, Static -from textual.containers import Horizontal, VerticalScroll -from telethon import TelegramClient, events -from widgets.dialog import Dialog -from widgets.chat import Chat - -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 - self.telegram_client.on(events.NewMessage())(self.update_chat_list) - - async def on_mount(self): - self.chat_container = self\ - .query_one("#main_container")\ - .query_one("#chats")\ - .query_one("#chat_container") - - self.limit = 100 - for i in range(self.limit): - chat = Chat(id=f"chat-{i + 1}", notify_func=self.notify) - self.chat_container.mount(chat) - #self.mount_chats(limit=25) - - await self.update_chat_list() - - def mount_chats(self, limit: int): - self.limit = limit - 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 + 1 + (limit - chats_amount)}") - self.chat_container.mount(chat) - elif not (limit == chats_amount): - for i in range(chats_amount - limit): - self.chat_container.query(Chat).last().remove() - - async def update_chat_list(self): - dialogs = await self.telegram_client.get_dialogs(limit=self.limit) - - for i in range(len(dialogs)): - 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("Новое сообщение") #колхоз дебаг - - def compose(self): - yield Footer() - with Horizontal(id="main_container"): - with Horizontal(id="chats"): - yield VerticalScroll(Static(id="chat_container")) - #TODO: сделать кнопку чтобы прогрузить больше чатов - - yield Dialog() diff --git a/app/app.py b/src/app.py similarity index 61% rename from app/app.py rename to src/app.py index 3ddbf85..1b51c91 100644 --- a/app/app.py +++ b/src/app.py @@ -1,21 +1,12 @@ from telethon import TelegramClient, events -from telethon.errors import SessionPasswordNeededError -from textual.app import App, ComposeResult -from textual.containers import Horizontal, VerticalScroll, Vertical -from textual.widgets import Static, Footer, Label, Input, Button -from textual.screen import Screen -from textual.events import Event -from widgets.chat import Chat -from widgets.dialog import Dialog +from textual.app import App from tokens import api_id, api_hash -from screens.auth_screen import AuthScreen -from screens.chat_screen import ChatScreen +from src.screens import AuthScreen, ChatScreen class TelegramTUI(App): """Класс приложения""" - CSS_PATH = "../tcss/style.tcss" - #SCREENS = {"chats": ChatScreen} + CSS_PATH = "style.tcss" async def on_mount(self) -> None: self.telegram_client = TelegramClient("user", api_id, api_hash) diff --git a/src/screens.py b/src/screens.py new file mode 100644 index 0000000..1c23140 --- /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() diff --git a/tcss/style.tcss b/src/style.tcss similarity index 100% rename from tcss/style.tcss rename to src/style.tcss diff --git a/src/widgets.py b/src/widgets.py new file mode 100644 index 0000000..8f44bc0 --- /dev/null +++ b/src/widgets.py @@ -0,0 +1,101 @@ +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 + +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): + 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") + + # но я могу ошибаться, я это фиш если что + + #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("Нажато отправить") + +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)) diff --git a/telegram/__init__.py b/telegram/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/telegram/client.py b/telegram/client.py deleted file mode 100644 index 1010434..0000000 --- a/telegram/client.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -ЭТОТ ФАЙЛ БОЛЬШЕ НЕ ИСПОЛЬЗУЕТСЯ -СКОРО УДАЛИМ -""" - -from telethon import TelegramClient, events, utils - -class TelegramClientWrapper: - """Обёртка для метода TelegramClient из Telethon""" - - def __init__(self, api_id, api_hash, message_handler): - self.message_handler = message_handler - self.client = TelegramClient('user', api_id, api_hash) - self.client.on(events.NewMessage())(self.local_message_handler) - - async def local_message_handler(self, event): - await self.message_handler() - - async def connect(self): - await self.client.connect() - - async def start(self): - await self.client.start() - - async def disconnect(self): - await self.client.disconnect() - - async def get_dialogs(self, limit=None): - await self.client.get_dialogs(limit=limit) - 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] - return 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/widgets/__init__.py b/widgets/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/widgets/chat.py b/widgets/chat.py deleted file mode 100644 index 13b9a7c..0000000 --- a/widgets/chat.py +++ /dev/null @@ -1,38 +0,0 @@ -from textual.widgets import Label -from textual.containers import Horizontal, Vertical -from textual.widget import Widget -from textual.reactive import Reactive - -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") diff --git a/widgets/dialog.py b/widgets/dialog.py deleted file mode 100644 index 05a9fe0..0000000 --- a/widgets/dialog.py +++ /dev/null @@ -1,33 +0,0 @@ -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") - - # но я могу ошибаться, я это фиш если что - - #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("Нажато отправить") diff --git a/widgets/message.py b/widgets/message.py deleted file mode 100644 index 88468e0..0000000 --- a/widgets/message.py +++ /dev/null @@ -1,37 +0,0 @@ -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))