mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 19:26:10 +00:00
commit
0949294da0
0
app/__init__.py
Normal file
0
app/__init__.py
Normal file
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
app/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
app/__pycache__/app.cpython-313.pyc
Normal file
BIN
app/__pycache__/app.cpython-313.pyc
Normal file
Binary file not shown.
50
app/app.py
Normal file
50
app/app.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
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 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.update_chat_list)
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
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=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:
|
||||||
|
with Horizontal(id="main_container"):
|
||||||
|
with Horizontal(id="chats"):
|
||||||
|
yield VerticalScroll(Static(id="chat_container"))
|
||||||
|
|
||||||
|
yield Dialog()
|
||||||
|
|
||||||
|
async def on_exit_app(self):
|
||||||
|
await self.telegram_client.disconnect()
|
||||||
|
return super()._on_exit_app()
|
82
main.py
82
main.py
@ -1,81 +1,5 @@
|
|||||||
from telethon import TelegramClient, events, sync, utils
|
from app.app import TelegramTUI
|
||||||
from textual.app import App, ComposeResult
|
|
||||||
from textual.widgets import Placeholder, Label, Static, Rule
|
|
||||||
from textual.containers import Horizontal, VerticalScroll, Vertical
|
|
||||||
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 _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.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(id="main_container"):
|
|
||||||
with Horizontal(id="chats"):
|
|
||||||
yield VerticalScroll(*[Static(id="chat_container")])
|
|
||||||
|
|
||||||
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"))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = TelegramTUI()
|
tg = TelegramTUI()
|
||||||
app.run()
|
tg.run()
|
||||||
|
20
styles.tcss
20
styles.tcss
@ -1,20 +0,0 @@
|
|||||||
#chats {
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#dialog {
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
Chat {
|
|
||||||
height: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
Rule {
|
|
||||||
color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message {
|
|
||||||
height: 3;
|
|
||||||
padding: 1;
|
|
||||||
}
|
|
43
tcss/style.tcss
Normal file
43
tcss/style.tcss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
#chats {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dialog {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
Chat {
|
||||||
|
height: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rule {
|
||||||
|
color: #FFFFFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message {
|
||||||
|
height: 3;
|
||||||
|
padding: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
Message Container {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#input_place {
|
||||||
|
height: 3;
|
||||||
|
width: 70%;
|
||||||
|
align-horizontal: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#msg_input {
|
||||||
|
width: 65%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#send {
|
||||||
|
|
||||||
|
}
|
0
telegram/__init__.py
Normal file
0
telegram/__init__.py
Normal file
BIN
telegram/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
telegram/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
telegram/__pycache__/client.cpython-313.pyc
Normal file
BIN
telegram/__pycache__/client.cpython-313.pyc
Normal file
Binary file not shown.
34
telegram/client.py
Normal file
34
telegram/client.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
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.on(events.NewMessage())(message_handler)
|
||||||
|
|
||||||
|
#ни то ни то не работает, костя спаси
|
||||||
|
|
||||||
|
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
|
0
widgets/__init__.py
Normal file
0
widgets/__init__.py
Normal file
BIN
widgets/__pycache__/__init__.cpython-313.pyc
Normal file
BIN
widgets/__pycache__/__init__.cpython-313.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/chat.cpython-313.pyc
Normal file
BIN
widgets/__pycache__/chat.cpython-313.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/dialog.cpython-313.pyc
Normal file
BIN
widgets/__pycache__/dialog.cpython-313.pyc
Normal file
Binary file not shown.
BIN
widgets/__pycache__/message.cpython-313.pyc
Normal file
BIN
widgets/__pycache__/message.cpython-313.pyc
Normal file
Binary file not shown.
22
widgets/chat.py
Normal file
22
widgets/chat.py
Normal file
@ -0,0 +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):
|
||||||
|
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)
|
||||||
|
|
||||||
|
def _on_click(self):
|
||||||
|
self.msg = str(self.peer_id)
|
||||||
|
|
||||||
|
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")
|
24
widgets/dialog.py
Normal file
24
widgets/dialog.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from textual.widgets import Input, Button, Label
|
||||||
|
from textual.containers import Horizontal, VerticalScroll, Vertical
|
||||||
|
from textual.widget import Widget
|
||||||
|
from widgets.message import Message
|
||||||
|
|
||||||
|
class Dialog(Widget):
|
||||||
|
def __init__(self, id=None, classes=None, disabled=False):
|
||||||
|
super().__init__(id=id, classes=classes, disabled=disabled)
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
with Vertical():
|
||||||
|
with VerticalScroll(id="dialog"):
|
||||||
|
yield Message(message="привет, я ыплыжлп", is_me=True)
|
||||||
|
yield Message(message="о, дщытрапшщцрущ", is_me=False)
|
||||||
|
yield Message(message="ДАТОУШЩАРШЩУРЩША!!!!", is_me=False)
|
||||||
|
# должно быть примерно is_me = message.from_id == client.get_peer_id("me")
|
||||||
|
# но я могу ошибаться, я это фиш если что
|
||||||
|
|
||||||
|
with Horizontal(id="input_place"):
|
||||||
|
yield Input(placeholder="Сообщение", id="msg_input")
|
||||||
|
yield Button(label="➤", id="send", variant="primary")
|
||||||
|
|
||||||
|
def on_button_pressed(self, event): # self добавил
|
||||||
|
self.app.notify("Нажато отправить")
|
27
widgets/message.py
Normal file
27
widgets/message.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
from textual.widgets import Label
|
||||||
|
from textual.containers import Container
|
||||||
|
from textual.widget import Widget
|
||||||
|
|
||||||
|
class Message(Widget):
|
||||||
|
def __init__(self, name=None, message=None, is_me=None, id=None, classes=None, disabled=False):
|
||||||
|
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||||
|
self.message = message
|
||||||
|
self.is_me = is_me
|
||||||
|
|
||||||
|
def on_mount(self):
|
||||||
|
container = self.query_one(Container)
|
||||||
|
label = container.query_one(Label)
|
||||||
|
if self.is_me:
|
||||||
|
self.styles.padding = (0, 0, 0, 15)
|
||||||
|
label.styles.text_align = "right"
|
||||||
|
container.styles.align_horizontal = "right"
|
||||||
|
label.styles.border = ("solid", "#4287f5")
|
||||||
|
else:
|
||||||
|
self.styles.padding = (0, 15, 0, 0)
|
||||||
|
label.styles.text_align = "left"
|
||||||
|
container.styles.align_horizontal = "left"
|
||||||
|
label.styles.border = ("solid", "#ffffff")
|
||||||
|
|
||||||
|
def compose(self):
|
||||||
|
with Container():
|
||||||
|
yield Label(str(self.message))
|
Loading…
x
Reference in New Issue
Block a user