mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 19:26:10 +00:00
Compare commits
2 Commits
2602fd2680
...
058edf3fc8
Author | SHA1 | Date | |
---|---|---|---|
![]() |
058edf3fc8 | ||
![]() |
8c69a666bb |
@ -11,7 +11,7 @@ class TelegramTUI(App):
|
|||||||
CSS_PATH = "style.tcss"
|
CSS_PATH = "style.tcss"
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.telegram_client = TelegramClient("user", api_id, api_hash)
|
self.telegram_client = TelegramClient("user2", api_id, api_hash)
|
||||||
await self.telegram_client.connect()
|
await self.telegram_client.connect()
|
||||||
|
|
||||||
chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
chat_screen = ChatScreen(telegram_client=self.telegram_client)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Файл с кастомными экранами приложения"""
|
"""Файл с кастомными экранами приложения"""
|
||||||
|
|
||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Label, Input, Footer, Static
|
from textual.widgets import Label, Input, Footer, Static, ContentSwitcher
|
||||||
from textual.containers import Vertical, Horizontal, VerticalScroll
|
from textual.containers import Vertical, Horizontal, VerticalScroll
|
||||||
from telethon.errors import SessionPasswordNeededError
|
from telethon.errors import SessionPasswordNeededError
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
@ -72,7 +72,7 @@ class ChatScreen(Screen):
|
|||||||
self.telegram_client = telegram_client
|
self.telegram_client = telegram_client
|
||||||
|
|
||||||
async def on_mount(self):
|
async def on_mount(self):
|
||||||
self.limit = 30
|
self.limit = 100
|
||||||
|
|
||||||
self.chat_container = self\
|
self.chat_container = self\
|
||||||
.query_one("#main_container")\
|
.query_one("#main_container")\
|
||||||
@ -147,4 +147,5 @@ class ChatScreen(Screen):
|
|||||||
with Horizontal(id="chats"):
|
with Horizontal(id="chats"):
|
||||||
yield VerticalScroll(id="chat_container")
|
yield VerticalScroll(id="chat_container")
|
||||||
#TODO: сделать кнопку чтобы прогрузить больше чатов
|
#TODO: сделать кнопку чтобы прогрузить больше чатов
|
||||||
yield Dialog(telegram_client=self.telegram_client)
|
yield ContentSwitcher(id="dialog_switcher")
|
||||||
|
#yield Dialog(telegram_client=self.telegram_client)
|
||||||
|
@ -14,11 +14,6 @@ Rule {
|
|||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
|
||||||
height: 3;
|
|
||||||
padding: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Message {
|
Message {
|
||||||
height: auto;
|
height: auto;
|
||||||
width: auto;
|
width: auto;
|
||||||
@ -28,11 +23,6 @@ Message Container {
|
|||||||
height: auto;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
Message Static {
|
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#input_place {
|
#input_place {
|
||||||
height: 3;
|
height: 3;
|
||||||
width: 70%;
|
width: 70%;
|
||||||
@ -46,3 +36,29 @@ Message Static {
|
|||||||
#auth_container{
|
#auth_container{
|
||||||
align: center middle;
|
align: center middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.is_me_true Container {
|
||||||
|
padding: 0 0 0 15;
|
||||||
|
align: right middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is_me_true Static {
|
||||||
|
border: solid $primary;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
text-align: right;
|
||||||
|
min-width: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is_me_false Container {
|
||||||
|
padding: 0 15 0 0;
|
||||||
|
align: left middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is_me_false Static {
|
||||||
|
border: solid $foreground;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
text-align: left;
|
||||||
|
min-width: 11;
|
||||||
|
}
|
||||||
|
141
src/widgets.py
141
src/widgets.py
@ -3,15 +3,17 @@
|
|||||||
from textual.containers import Horizontal, Vertical, Container, VerticalScroll
|
from textual.containers import Horizontal, Vertical, Container, VerticalScroll
|
||||||
from textual.widget import Widget
|
from textual.widget import Widget
|
||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
from textual.widgets import Input, Button, Label, Static
|
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
||||||
from telethon import TelegramClient, events
|
from textual.app import ComposeResult, RenderResult
|
||||||
|
from telethon import TelegramClient, events, utils
|
||||||
|
import datetime
|
||||||
|
|
||||||
class Chat(Widget):
|
class Chat(Widget):
|
||||||
"""Класс виджета чата для панели чатов"""
|
"""Класс виджета чата для панели чатов"""
|
||||||
|
|
||||||
username = Reactive(" ", recompose=True)
|
username: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
msg = Reactive(" ", recompose=True)
|
msg: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
peer_id = Reactive(0)
|
peer_id: Reactive[int] = Reactive(0)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -19,19 +21,35 @@ class Chat(Widget):
|
|||||||
id: str | None = None,
|
id: str | None = None,
|
||||||
classes: str | None = None,
|
classes: str | None = None,
|
||||||
disabled: bool = False
|
disabled: bool = False
|
||||||
):
|
) -> None:
|
||||||
super().__init__(
|
super().__init__(
|
||||||
name=str(name),
|
name=str(name),
|
||||||
id=id,
|
id=id,
|
||||||
classes=classes,
|
classes=classes,
|
||||||
disabled=disabled
|
disabled=disabled
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def on_mount(self) -> None:
|
||||||
|
self.switcher = self.screen.query_one(Horizontal).query_one("#dialog_switcher", ContentSwitcher)
|
||||||
|
|
||||||
def _on_click(self):
|
def on_click(self) -> None:
|
||||||
self.msg = str(self.peer_id)
|
dialog_id = f"dialog-{str(self.peer_id)}"
|
||||||
self.app.notify("нажат чат")
|
print("click 1")
|
||||||
|
try:
|
||||||
|
self.switcher.mount(Dialog(
|
||||||
|
telegram_client=self.app.telegram_client,
|
||||||
|
chat_id=self.peer_id,
|
||||||
|
id=dialog_id
|
||||||
|
))
|
||||||
|
print("click 1.1")
|
||||||
|
except:
|
||||||
|
print("click 1.2")
|
||||||
|
print("click 2")
|
||||||
|
self.switcher.current = dialog_id
|
||||||
|
self.switcher.recompose()
|
||||||
|
print("click 3")
|
||||||
|
|
||||||
def compose(self):
|
def compose(self) -> ComposeResult:
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘")
|
yield Label(f"┌───┐\n│ {self.username[:1]} │\n└───┘")
|
||||||
with Vertical():
|
with Vertical():
|
||||||
@ -46,21 +64,23 @@ class Dialog(Widget):
|
|||||||
id=None,
|
id=None,
|
||||||
classes=None,
|
classes=None,
|
||||||
disabled=None,
|
disabled=None,
|
||||||
telegram_client: TelegramClient | None = None
|
telegram_client: TelegramClient | None = None,
|
||||||
):
|
chat_id = None
|
||||||
|
) -> None:
|
||||||
super().__init__(id=id, classes=classes, disabled=disabled)
|
super().__init__(id=id, classes=classes, disabled=disabled)
|
||||||
self.telegram_client = telegram_client
|
self.telegram_client = telegram_client
|
||||||
self.chat_id = -1002299818671
|
self.chat_id = chat_id
|
||||||
self.is_msg_update_blocked = False
|
self.is_msg_update_blocked = False
|
||||||
|
|
||||||
async def on_mount(self):
|
async def on_mount(self) -> None:
|
||||||
self.limit = 30
|
self.limit = 10
|
||||||
|
|
||||||
self.msg_input = self.query_one("#msg_input")
|
self.msg_input = self.query_one("#msg_input")
|
||||||
self.dialog = self.query_one(Vertical).query_one("#dialog")
|
self.dialog = self.query_one(Vertical).query_one("#dialog")
|
||||||
|
|
||||||
self.me = await self.telegram_client.get_me()
|
self.me = await self.telegram_client.get_me()
|
||||||
|
|
||||||
|
self.dialog.scroll_end(animate=False)
|
||||||
await self.update_dialog()
|
await self.update_dialog()
|
||||||
|
|
||||||
for event in (
|
for event in (
|
||||||
@ -68,22 +88,26 @@ class Dialog(Widget):
|
|||||||
events.MessageDeleted,
|
events.MessageDeleted,
|
||||||
events.MessageEdited
|
events.MessageEdited
|
||||||
):
|
):
|
||||||
self.telegram_client.on(event(chats=(self.chat_id)))\
|
self.telegram_client.on(
|
||||||
(self.update_dialog)
|
event(chats=(self.chat_id))
|
||||||
|
)(self.update_dialog)
|
||||||
|
|
||||||
def mount_messages(self, limit: int):
|
def mount_messages(self, limit: int) -> None:
|
||||||
print("Загрузка виджетов сообщений...")
|
print("Загрузка виджетов сообщений...")
|
||||||
|
|
||||||
msg_amount = len(self.dialog.query(Message))
|
msg_amount = len(self.dialog.query(Message))
|
||||||
|
|
||||||
if limit > msg_amount:
|
if limit > msg_amount:
|
||||||
for i in range(limit - msg_amount):
|
for i in range(limit - msg_amount):
|
||||||
self.dialog.mount(Message(id=f"msg-{i + msg_amount + 1}"))
|
self.dialog.mount(
|
||||||
|
Message(id=f"msg-{i + msg_amount + 1}"),
|
||||||
|
before=0
|
||||||
|
)
|
||||||
elif limit < msg_amount:
|
elif limit < msg_amount:
|
||||||
for i in range(msg_amount - limit):
|
for i in range(msg_amount - limit):
|
||||||
self.dialog.query(Message).last().remove()
|
self.dialog.query(Message).last().remove()
|
||||||
|
|
||||||
async def update_dialog(self, event = None):
|
async def update_dialog(self, event = None) -> None:
|
||||||
print("Запрос обновления сообщений")
|
print("Запрос обновления сообщений")
|
||||||
|
|
||||||
if not self.is_msg_update_blocked:
|
if not self.is_msg_update_blocked:
|
||||||
@ -98,70 +122,79 @@ class Dialog(Widget):
|
|||||||
self.mount_messages(limit)
|
self.mount_messages(limit)
|
||||||
|
|
||||||
for i in range(limit):
|
for i in range(limit):
|
||||||
chat = self.dialog.query_one(f"#msg-{i + 1}")
|
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
||||||
chat.message = str(messages[i].message) + \
|
msg.message = ""
|
||||||
(not str(messages[i].message)) * " "
|
if str(messages[i].message):
|
||||||
chat.is_me = messages[i].from_id == self.me.id
|
msg.message = str(messages[i].message)
|
||||||
chat._update_styles()
|
|
||||||
|
#TODO: завести это:
|
||||||
|
is_me = messages[i].from_id.user_id == self.me.id
|
||||||
|
|
||||||
|
msg.is_me = is_me
|
||||||
|
msg.username = utils.get_display_name(messages[i].sender)
|
||||||
|
msg.send_time = messages[i]\
|
||||||
|
.date\
|
||||||
|
.astimezone(datetime.timezone.utc)\
|
||||||
|
.strftime("%H:%M")
|
||||||
|
|
||||||
self.is_msg_update_blocked = False
|
self.is_msg_update_blocked = False
|
||||||
print("Сообщения обновлены")
|
print("Сообщения обновлены")
|
||||||
else:
|
else:
|
||||||
print("Обновление сообщений невозможно: уже выполняется")
|
print("Обновление сообщений невозможно: уже выполняется")
|
||||||
|
|
||||||
def compose(self):
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical():
|
with Vertical():
|
||||||
yield VerticalScroll(id="dialog")
|
yield VerticalScroll(id="dialog")
|
||||||
with Horizontal(id="input_place"):
|
with Horizontal(id="input_place"):
|
||||||
yield Input(placeholder="Сообщение", id="msg_input")
|
yield Input(placeholder="Сообщение", id="msg_input")
|
||||||
yield Button(label="➤", id="send", variant="primary")
|
yield Button(label="➤", id="send", variant="primary")
|
||||||
|
|
||||||
async def on_button_pressed(self, event = None):
|
async def on_button_pressed(self, event = None) -> None:
|
||||||
await self.send_message()
|
await self.send_message()
|
||||||
|
|
||||||
async def on_input_submitted(self, event = None):
|
async def on_input_submitted(self, event = None) -> None:
|
||||||
await self.send_message()
|
await self.send_message()
|
||||||
|
|
||||||
async def send_message(self):
|
async def send_message(self) -> None:
|
||||||
await self.telegram_client.send_message(
|
try:
|
||||||
self.chat_id,
|
await self.telegram_client.send_message(
|
||||||
str(self.msg_input.value)
|
self.chat_id,
|
||||||
)
|
str(self.msg_input.value)
|
||||||
|
)
|
||||||
|
except ValueError:
|
||||||
|
self.app.notify("Ошибка отправки")
|
||||||
self.msg_input.value = ""
|
self.msg_input.value = ""
|
||||||
await self.update_dialog()
|
await self.update_dialog()
|
||||||
|
|
||||||
class Message(Widget):
|
class Message(Widget):
|
||||||
"""Класс виджета сообщений для окна диалога"""
|
"""Класс виджета сообщений для окна диалога"""
|
||||||
|
|
||||||
message = Reactive("", recompose=True)
|
message: Reactive[str] = Reactive("", recompose=True)
|
||||||
is_me = Reactive(False, recompose=True)
|
is_me: Reactive[bool] = Reactive(False, recompose=True)
|
||||||
|
username: Reactive[str] = Reactive("", recompose=True)
|
||||||
|
send_time: Reactive[str] = Reactive("", recompose=True)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
name=None,
|
name=None,
|
||||||
message=None,
|
|
||||||
is_me=None,
|
|
||||||
id=None,
|
id=None,
|
||||||
classes=None,
|
classes=None,
|
||||||
disabled=False
|
disabled=False
|
||||||
):
|
) -> None:
|
||||||
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
super().__init__(name=name, id=id, classes=classes, disabled=disabled)
|
||||||
self.message = message
|
|
||||||
self.is_me = is_me
|
|
||||||
|
|
||||||
def on_mount(self):
|
def on_mount(self) -> None:
|
||||||
container = self.query_one(Container)
|
pass
|
||||||
label_border = container.query_one(".border")
|
|
||||||
if self.is_me:
|
|
||||||
self.styles.padding = (0, 0, 0, 15)
|
|
||||||
container.styles.align_horizontal = "right"
|
|
||||||
label_border.styles.border = ("solid", "#4287f5")
|
|
||||||
else:
|
|
||||||
self.styles.padding = (0, 15, 0, 0)
|
|
||||||
container.styles.align_horizontal = "left"
|
|
||||||
label_border.styles.border = ("solid", "#ffffff")
|
|
||||||
|
|
||||||
def compose(self):
|
def compose(self) -> ComposeResult:
|
||||||
|
static = Static(self.message)
|
||||||
|
static.border_title = self.username * (not self.is_me)
|
||||||
|
static.border_subtitle = self.send_time
|
||||||
|
|
||||||
with Container():
|
with Container():
|
||||||
with Static(classes="border"):
|
yield static
|
||||||
yield Static(str(self.message))
|
|
||||||
|
if self.is_me:
|
||||||
|
self.classes = "is_me_true"
|
||||||
|
else:
|
||||||
|
self.classes = "is_me_false"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user