mirror of
https://github.com/avitoras/telegram-tui.git
synced 2025-07-27 19:26:10 +00:00
Куча изменений, вот теперь точно
This commit is contained in:
parent
359ee43615
commit
83f4cac86b
@ -2,6 +2,14 @@
|
|||||||
# Их использование крайне нежелательно!
|
# Их использование крайне нежелательно!
|
||||||
# Получите свои API-ключи приложения на https://my.telegram.org/apps и вставьте их ниже.
|
# Получите свои API-ключи приложения на https://my.telegram.org/apps и вставьте их ниже.
|
||||||
# Спасибо за понимание!
|
# Спасибо за понимание!
|
||||||
|
#
|
||||||
|
# In EN: WARNING: it's official TG API-keys, please, don't use them, get yours via link above. Thanks!
|
||||||
|
|
||||||
API_ID=2040
|
API_ID=2040
|
||||||
API_HASH=b18441a1ff607e10a989891a5462e627
|
API_HASH=b18441a1ff607e10a989891a5462e627
|
||||||
|
|
||||||
|
# Конфиг
|
||||||
|
CURRENT_USER = "user"
|
||||||
|
DO_NOTIFY = False
|
||||||
|
UTC_OFFSET = 0
|
||||||
|
LANGUAGE = "en"
|
6
main.py
6
main.py
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/python
|
#!/usr/bin/python
|
||||||
"""Файл инициализации приложения"""
|
"""Файл инициализации приложения"""
|
||||||
|
|
||||||
from src.app import TelegramTUI
|
from src.app import Talc
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
tg = TelegramTUI()
|
talc = Talc()
|
||||||
tg.run()
|
talc.run()
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
textual
|
textual
|
||||||
telethon
|
telethon
|
||||||
python-dotenv
|
python-dotenv
|
||||||
emoji
|
|
||||||
Pillow
|
|
||||||
pywhatkit
|
|
22
src/app.py
22
src/app.py
@ -1,31 +1,33 @@
|
|||||||
"""Главный файл приложения"""
|
"""Главный файл приложения"""
|
||||||
|
|
||||||
import os
|
from os import getenv
|
||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient
|
||||||
from textual.app import App
|
from textual.app import App
|
||||||
from src.screens import AuthScreen, ChatScreen
|
from src.screens import AuthScreen, ChatScreen
|
||||||
|
import src.locales
|
||||||
|
|
||||||
load_dotenv()
|
load_dotenv()
|
||||||
|
API_ID = getenv("API_ID")
|
||||||
|
API_HASH = getenv("API_HASH")
|
||||||
|
|
||||||
api_id = os.getenv("API_ID")
|
if not API_ID or not API_HASH:
|
||||||
api_hash = os.getenv("API_HASH")
|
|
||||||
|
|
||||||
if not api_id or not api_hash:
|
|
||||||
raise ValueError(
|
raise ValueError(
|
||||||
"API_ID и API_HASH не найдены в .env файле. "
|
"API_ID и API_HASH не найдены в .env файле. "
|
||||||
"Пожалуйста, скопируйте .env.example в .env и заполните свои ключи."
|
"Пожалуйста, скопируйте .env.example в .env и заполните свои ключи."
|
||||||
)
|
)
|
||||||
|
|
||||||
api_id = int(api_id)
|
API_ID = int(API_ID)
|
||||||
|
|
||||||
class TelegramTUI(App):
|
#locale = locales.
|
||||||
|
|
||||||
|
class Talc(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(getenv("CURRENT_USER"), 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)
|
||||||
@ -38,6 +40,8 @@ class TelegramTUI(App):
|
|||||||
else:
|
else:
|
||||||
self.push_screen("chats")
|
self.push_screen("chats")
|
||||||
|
|
||||||
|
self.scroll_sensitivity_y = 1.0
|
||||||
|
|
||||||
async def on_exit_app(self):
|
async def on_exit_app(self):
|
||||||
await self.telegram_client.disconnect()
|
await self.telegram_client.disconnect()
|
||||||
return super()._on_exit_app()
|
return super()._on_exit_app()
|
||||||
|
2
src/locales.py
Normal file
2
src/locales.py
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ru = {"greeting_auth": "Добро пожаловать в Тальк", "phone_number": "Номер телефона", "code": "Код", "password": "Пароль", "you": "Вы", "mention": "Вас упомянули"}
|
||||||
|
en = {"greeting_auth": "Welcome to Talc", "phone_number": "Phone number", "code": "Code", "password": "Password", "you": "You", "mention": "You got mentioned"}
|
124
src/screens.py
124
src/screens.py
@ -3,9 +3,12 @@
|
|||||||
from textual.screen import Screen
|
from textual.screen import Screen
|
||||||
from textual.widgets import Label, Input, Footer, Static, ContentSwitcher
|
from textual.widgets import Label, Input, Footer, Static, ContentSwitcher
|
||||||
from textual.containers import Vertical, Horizontal, VerticalScroll
|
from textual.containers import Vertical, Horizontal, VerticalScroll
|
||||||
|
from textual.app import ComposeResult
|
||||||
from telethon.errors import SessionPasswordNeededError
|
from telethon.errors import SessionPasswordNeededError
|
||||||
from telethon import TelegramClient, events
|
from telethon import TelegramClient, events
|
||||||
from src.widgets import Dialog, Chat
|
from src.widgets import Chat
|
||||||
|
from os import system, getenv
|
||||||
|
from telethon.utils import get_display_name
|
||||||
|
|
||||||
class AuthScreen(Screen):
|
class AuthScreen(Screen):
|
||||||
"""Класс экрана логина в аккаунт"""
|
"""Класс экрана логина в аккаунт"""
|
||||||
@ -16,16 +19,16 @@ class AuthScreen(Screen):
|
|||||||
id = None,
|
id = None,
|
||||||
classes = None,
|
classes = None,
|
||||||
telegram_client: TelegramClient | None = None
|
telegram_client: TelegramClient | None = None
|
||||||
):
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.client = telegram_client
|
self.client = telegram_client
|
||||||
|
|
||||||
def on_mount(self):
|
def on_mount(self) -> None:
|
||||||
self.ac = self.query_one("#auth_container")
|
self.ac = self.query_one("#auth_container")
|
||||||
|
|
||||||
def compose(self):
|
def compose(self) -> ComposeResult:
|
||||||
with Vertical(id="auth_container"):
|
with Vertical(id="auth_container"):
|
||||||
yield Label("Добро пожаловать в Telegram TUI")
|
yield Label("Добро пожаловать в Talc")
|
||||||
yield Input(placeholder="Номер телефона", id="phone")
|
yield Input(placeholder="Номер телефона", id="phone")
|
||||||
yield Input(placeholder="Код", id="code", disabled=True)
|
yield Input(placeholder="Код", id="code", disabled=True)
|
||||||
yield Input(
|
yield Input(
|
||||||
@ -36,27 +39,28 @@ class AuthScreen(Screen):
|
|||||||
)
|
)
|
||||||
|
|
||||||
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
async def on_input_submitted(self, event: Input.Submitted) -> None:
|
||||||
if event.input.id == "phone":
|
match event.input.id:
|
||||||
self.phone = event.value
|
case "phone":
|
||||||
self.ac.query_one("#phone").disabled = True
|
self.phone = event.value
|
||||||
self.ac.query_one("#code").disabled = False
|
self.ac.query_one("#phone").disabled = True
|
||||||
await self.client.send_code_request(phone=self.phone)
|
self.ac.query_one("#code").disabled = False
|
||||||
elif event.input.id == "code":
|
await self.client.send_code_request(phone=self.phone)
|
||||||
try:
|
case "code":
|
||||||
self.code = event.value
|
try:
|
||||||
self.ac.query_one("#code").disabled = True
|
self.code = event.value
|
||||||
await self.client.sign_in(phone=self.phone, code=self.code)
|
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
|
||||||
|
case "password":
|
||||||
|
self.password = event.value
|
||||||
|
await self.client.sign_in(password=self.password)
|
||||||
|
await self.client.start()
|
||||||
self.app.pop_screen()
|
self.app.pop_screen()
|
||||||
self.app.push_screen("chats")
|
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")
|
|
||||||
|
|
||||||
class ChatScreen(Screen):
|
class ChatScreen(Screen):
|
||||||
"""Класс экрана чатов, он же основной экран приложения"""
|
"""Класс экрана чатов, он же основной экран приложения"""
|
||||||
@ -67,13 +71,17 @@ class ChatScreen(Screen):
|
|||||||
id = None,
|
id = None,
|
||||||
classes = None,
|
classes = None,
|
||||||
telegram_client: TelegramClient | None = None
|
telegram_client: TelegramClient | None = None
|
||||||
):
|
) -> None:
|
||||||
super().__init__(name, id, classes)
|
super().__init__(name, id, classes)
|
||||||
self.telegram_client = telegram_client
|
self.telegram_client = telegram_client
|
||||||
|
self.DO_NOTIFY = getenv("DO_NOTIFY")
|
||||||
|
|
||||||
async def on_mount(self):
|
async def on_mount(self) -> None:
|
||||||
self.limit = 100
|
self.limit = 100
|
||||||
|
|
||||||
|
#Получение ID пользователя (себя)
|
||||||
|
self.me_id = await self.telegram_client.get_peer_id("me")
|
||||||
|
# Получение объекта контейнера чатов
|
||||||
self.chat_container = self\
|
self.chat_container = self\
|
||||||
.query_one("#main_container")\
|
.query_one("#main_container")\
|
||||||
.query_one("#chats")\
|
.query_one("#chats")\
|
||||||
@ -91,32 +99,39 @@ class ChatScreen(Screen):
|
|||||||
|
|
||||||
self.is_chat_update_blocked = False
|
self.is_chat_update_blocked = False
|
||||||
await self.update_chat_list()
|
await self.update_chat_list()
|
||||||
|
|
||||||
print("Первоначальная загрузка чатов завершена")
|
print("Первоначальная загрузка чатов завершена")
|
||||||
|
|
||||||
|
# Автообновление чатов при следующих событиях
|
||||||
for event in (
|
for event in (
|
||||||
events.NewMessage,
|
events.NewMessage,
|
||||||
events.MessageDeleted,
|
events.MessageDeleted,
|
||||||
events.MessageEdited
|
events.MessageEdited
|
||||||
):
|
):
|
||||||
self.telegram_client.on(event())(self.update_chat_list)
|
self.telegram_client.on(event())(self.update_chat_list)
|
||||||
|
self.telegram_client.on(events.NewMessage)(self.notify_send)
|
||||||
|
|
||||||
def mount_chats(self, limit: int):
|
def mount_chats(self, limit: int) -> None:
|
||||||
|
"""Функция маунта чатов"""
|
||||||
print("Загрузка виджетов чатов...")
|
print("Загрузка виджетов чатов...")
|
||||||
|
|
||||||
|
# Счёт текущего количества примонтированных чатов
|
||||||
chats_amount = len(self.chat_container.query(Chat))
|
chats_amount = len(self.chat_container.query(Chat))
|
||||||
|
|
||||||
if limit > chats_amount:
|
if limit > chats_amount:
|
||||||
|
# Маунт недостоющих, если чатов меньше, чем нужно
|
||||||
for i in range(limit - chats_amount):
|
for i in range(limit - chats_amount):
|
||||||
chat = Chat(id=f"chat-{i + chats_amount + 1}")
|
chat = Chat(id=f"chat-{i + chats_amount + 1}")
|
||||||
self.chat_container.mount(chat)
|
self.chat_container.mount(chat)
|
||||||
elif limit < chats_amount:
|
elif limit < chats_amount:
|
||||||
|
# Удаление лишних, если чатов больше, чем нужно
|
||||||
for i in range(chats_amount - limit):
|
for i in range(chats_amount - limit):
|
||||||
self.chat_container.query(Chat).last().remove()
|
self.chat_container.query(Chat).last().remove()
|
||||||
|
# Ничего, если их ровно столько же
|
||||||
|
|
||||||
print("Виджеты чатов загружены")
|
print("Виджеты чатов загружены")
|
||||||
|
|
||||||
async def update_chat_list(self, event = None):
|
async def update_chat_list(self, event = None) -> None:
|
||||||
|
"""Функция обновления чатов (и уведомления)"""
|
||||||
print("Запрос обновления чатов")
|
print("Запрос обновления чатов")
|
||||||
|
|
||||||
if not self.is_chat_update_blocked:
|
if not self.is_chat_update_blocked:
|
||||||
@ -127,25 +142,60 @@ class ChatScreen(Screen):
|
|||||||
)
|
)
|
||||||
print("Получены диалоги")
|
print("Получены диалоги")
|
||||||
|
|
||||||
|
# Маунт виджетов чатов в панели чатов по лимиту
|
||||||
limit = len(dialogs)
|
limit = len(dialogs)
|
||||||
self.mount_chats(limit)
|
self.mount_chats(limit)
|
||||||
|
|
||||||
|
# Изменение надписей в виджетах чатов
|
||||||
for i in range(limit):
|
for i in range(limit):
|
||||||
chat = self.chat_container.query_one(f"#chat-{i + 1}")
|
chat = self.chat_container.query_one(f"#chat-{i + 1}")
|
||||||
chat.username = str(dialogs[i].name)
|
|
||||||
chat.msg = str(dialogs[i].message.message)
|
chat.peername = str(dialogs[i].name)
|
||||||
|
chat.is_group = dialogs[i].is_group
|
||||||
chat.peer_id = dialogs[i].id
|
chat.peer_id = dialogs[i].id
|
||||||
|
|
||||||
|
try:
|
||||||
|
is_my_msg = \
|
||||||
|
dialogs[i].message.from_id.user_id == self.me_id
|
||||||
|
except:
|
||||||
|
is_my_msg = dialogs[i].id == self.me_id
|
||||||
|
|
||||||
|
if dialogs[i].is_group and is_my_msg:
|
||||||
|
chat.username = "Вы"
|
||||||
|
chat.msg = str(dialogs[i].message.message)
|
||||||
|
elif dialogs[i].is_group:
|
||||||
|
chat.username = str(
|
||||||
|
get_display_name(dialogs[i].message.sender)
|
||||||
|
)
|
||||||
|
chat.msg = str(dialogs[i].message.message)
|
||||||
|
elif is_my_msg:
|
||||||
|
chat.msg = "Вы: " * is_my_msg + str(
|
||||||
|
dialogs[i].message.message
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
chat.msg = str(dialogs[i].message.message)
|
||||||
|
|
||||||
self.is_chat_update_blocked = False
|
self.is_chat_update_blocked = False
|
||||||
print("Чаты обновлены")
|
print("Чаты обновлены")
|
||||||
else:
|
else:
|
||||||
print("Обновление чатов невозможно: уже выполняется")
|
print("Обновление чатов невозможно: уже выполняется")
|
||||||
|
|
||||||
def compose(self):
|
def notify_send(self, event) -> None:
|
||||||
yield Footer()
|
if not event:
|
||||||
with Horizontal(id="main_container"):
|
return None
|
||||||
|
if bool(self.DO_NOTIFY) and event.mentioned and not self.app.focused:
|
||||||
|
system(f"notify-send \"Вас упомянули\" Talc")
|
||||||
|
|
||||||
|
def compose(self) -> ComposeResult:
|
||||||
|
yield Footer() # Нижняя панель с подсказками
|
||||||
|
with Horizontal(id="main_container"): # Основной контейнер
|
||||||
with Horizontal(id="chats"):
|
with Horizontal(id="chats"):
|
||||||
yield VerticalScroll(id="chat_container")
|
yield VerticalScroll(id="chat_container")
|
||||||
#TODO: сделать кнопку чтобы прогрузить больше чатов
|
#TODO: сделать кнопку, чтобы прогрузить больше чатов,
|
||||||
yield ContentSwitcher(id="dialog_switcher")
|
# или ленивую прокрутку
|
||||||
#yield Dialog(telegram_client=self.telegram_client)
|
with ContentSwitcher(id="dialog_switcher"):
|
||||||
|
# ↑ Внутри него как раз крутятся диалоги
|
||||||
|
yield Label(
|
||||||
|
"Нажмите на чат в панели слева, чтобы начать общаться",
|
||||||
|
id="begin_talk_label"
|
||||||
|
) #TODO: не показывается надпись, надо будет исправить
|
||||||
|
@ -10,6 +10,10 @@ Chat {
|
|||||||
height: 3;
|
height: 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Chat Horizontal Vertical Label{
|
||||||
|
height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
Rule {
|
Rule {
|
||||||
color: #FFFFFF;
|
color: #FFFFFF;
|
||||||
}
|
}
|
||||||
@ -42,12 +46,13 @@ Message Container {
|
|||||||
align: right middle;
|
align: right middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is_me_true Static {
|
.is_me_true Container Label {
|
||||||
border: solid $primary;
|
border: solid $primary;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
text-align: right;
|
text-align: left;
|
||||||
min-width: 11;
|
min-width: 11;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is_me_false Container {
|
.is_me_false Container {
|
||||||
@ -55,10 +60,24 @@ Message Container {
|
|||||||
align: left middle;
|
align: left middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.is_me_false Static {
|
.is_me_false Container Label {
|
||||||
border: solid $foreground;
|
border: solid $foreground;
|
||||||
width: auto;
|
width: auto;
|
||||||
height: auto;
|
height: auto;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
min-width: 11;
|
min-width: 11;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContentSwitcher {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.begin_talk_label {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
text-align: center;
|
||||||
|
content-align: center middle;
|
||||||
|
color: $panel;
|
||||||
}
|
}
|
||||||
|
@ -5,14 +5,18 @@ from textual.widget import Widget
|
|||||||
from textual.reactive import Reactive
|
from textual.reactive import Reactive
|
||||||
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
from textual.widgets import Input, Button, Label, Static, ContentSwitcher
|
||||||
from textual.app import ComposeResult, RenderResult
|
from textual.app import ComposeResult, RenderResult
|
||||||
from telethon import TelegramClient, events, utils
|
from textual.content import Content
|
||||||
|
from telethon import TelegramClient, events, utils, types
|
||||||
import datetime
|
import datetime
|
||||||
|
from os import getenv
|
||||||
|
|
||||||
class Chat(Widget):
|
class Chat(Widget):
|
||||||
"""Класс виджета чата для панели чатов"""
|
"""Класс виджета чата для панели чатов"""
|
||||||
|
|
||||||
username: Reactive[str] = Reactive(" ", recompose=True)
|
username: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
|
peername: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
msg: Reactive[str] = Reactive(" ", recompose=True)
|
msg: Reactive[str] = Reactive(" ", recompose=True)
|
||||||
|
is_group: Reactive[bool] = Reactive(False, recompose=True)
|
||||||
peer_id: Reactive[int] = Reactive(0)
|
peer_id: Reactive[int] = Reactive(0)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
@ -30,30 +34,34 @@ class Chat(Widget):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def on_mount(self) -> None:
|
def on_mount(self) -> None:
|
||||||
self.switcher = self.screen.query_one(Horizontal).query_one("#dialog_switcher", ContentSwitcher)
|
self.switcher = self.screen.query_one(Horizontal)\
|
||||||
|
.query_one("#dialog_switcher", ContentSwitcher)
|
||||||
|
|
||||||
def on_click(self) -> None:
|
def on_click(self) -> None:
|
||||||
|
# Получение ID диалога и создание DOM-ID на его основе
|
||||||
dialog_id = f"dialog-{str(self.peer_id)}"
|
dialog_id = f"dialog-{str(self.peer_id)}"
|
||||||
print("click 1")
|
|
||||||
|
# Маунт диалога
|
||||||
try:
|
try:
|
||||||
self.switcher.mount(Dialog(
|
self.switcher.mount(Dialog(
|
||||||
telegram_client=self.app.telegram_client,
|
telegram_client=self.app.telegram_client,
|
||||||
chat_id=self.peer_id,
|
chat_id=self.peer_id,
|
||||||
id=dialog_id
|
id=dialog_id
|
||||||
))
|
))
|
||||||
print("click 1.1")
|
|
||||||
except:
|
except:
|
||||||
print("click 1.2")
|
# Диалог уже есть: ничего не делаем
|
||||||
print("click 2")
|
pass
|
||||||
|
|
||||||
self.switcher.current = dialog_id
|
self.switcher.current = dialog_id
|
||||||
self.switcher.recompose()
|
self.switcher.recompose()
|
||||||
print("click 3")
|
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
with Horizontal():
|
with Horizontal():
|
||||||
yield Label(f"┌───┐\n│ {self.username[:1]:1} │\n└───┘")
|
yield Label(f"┌───┐\n│ {self.peername[:1]:1} │\n└───┘")
|
||||||
with Vertical():
|
with Vertical():
|
||||||
yield Label(self.username, id="name")
|
yield Label(self.peername, id="peername")
|
||||||
|
if self.is_group:
|
||||||
|
yield Label(self.username, id="name")
|
||||||
yield Label(self.msg, id="last_msg")
|
yield Label(self.msg, id="last_msg")
|
||||||
|
|
||||||
class Dialog(Widget):
|
class Dialog(Widget):
|
||||||
@ -66,11 +74,14 @@ class Dialog(Widget):
|
|||||||
disabled=None,
|
disabled=None,
|
||||||
telegram_client: TelegramClient | None = None,
|
telegram_client: TelegramClient | None = None,
|
||||||
chat_id = None
|
chat_id = None
|
||||||
) -> 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 = chat_id
|
self.chat_id = chat_id
|
||||||
self.is_msg_update_blocked = False
|
self.is_msg_update_blocked = False
|
||||||
|
self.timezone = datetime.timezone(
|
||||||
|
datetime.timedelta(hours=int(getenv("UTC_OFFSET")))
|
||||||
|
)
|
||||||
|
|
||||||
async def on_mount(self) -> None:
|
async def on_mount(self) -> None:
|
||||||
self.limit = 10
|
self.limit = 10
|
||||||
@ -80,7 +91,6 @@ class Dialog(Widget):
|
|||||||
|
|
||||||
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 (
|
||||||
@ -92,6 +102,8 @@ class Dialog(Widget):
|
|||||||
event(chats=(self.chat_id))
|
event(chats=(self.chat_id))
|
||||||
)(self.update_dialog)
|
)(self.update_dialog)
|
||||||
|
|
||||||
|
self.dialog.scroll_down(animate=False, immediate=True)
|
||||||
|
|
||||||
def mount_messages(self, limit: int) -> None:
|
def mount_messages(self, limit: int) -> None:
|
||||||
print("Загрузка виджетов сообщений...")
|
print("Загрузка виджетов сообщений...")
|
||||||
|
|
||||||
@ -123,11 +135,46 @@ class Dialog(Widget):
|
|||||||
|
|
||||||
for i in range(limit):
|
for i in range(limit):
|
||||||
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
msg = self.dialog.query_one(f"#msg-{i + 1}")
|
||||||
msg.message = ""
|
|
||||||
if str(messages[i].message):
|
if str(messages[i].message):
|
||||||
msg.message = str(messages[i].message)
|
message = Content(
|
||||||
|
"[Медиа] " * \
|
||||||
|
bool(messages[i].media) + \
|
||||||
|
str(messages[i].message)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
message = Content("[Медиа]" * bool(messages[i].media) + "")
|
||||||
|
|
||||||
|
entities = messages[i].entities
|
||||||
|
if entities:
|
||||||
|
for entity in entities:
|
||||||
|
match type(entity):
|
||||||
|
case types.MessageEntityBold:
|
||||||
|
message = message.stylize(
|
||||||
|
"bold",
|
||||||
|
entity.offset,
|
||||||
|
entity.offset + entity.length
|
||||||
|
)
|
||||||
|
case types.MessageEntityUnderline:
|
||||||
|
message = message.stylize(
|
||||||
|
"underline",
|
||||||
|
entity.offset,
|
||||||
|
entity.offset + entity.length
|
||||||
|
)
|
||||||
|
case types.MessageEntityItalic:
|
||||||
|
message = message.stylize(
|
||||||
|
"italic",
|
||||||
|
entity.offset,
|
||||||
|
entity.offset + entity.length
|
||||||
|
)
|
||||||
|
case types.MessageEntityStrike:
|
||||||
|
message = message.stylize(
|
||||||
|
"strike",
|
||||||
|
entity.offset,
|
||||||
|
entity.offset + entity.length
|
||||||
|
)
|
||||||
|
|
||||||
|
msg.message = message
|
||||||
|
|
||||||
#TODO: завести это:
|
|
||||||
try:
|
try:
|
||||||
is_me = messages[i].from_id.user_id == self.me.id
|
is_me = messages[i].from_id.user_id == self.me.id
|
||||||
except:
|
except:
|
||||||
@ -137,7 +184,7 @@ class Dialog(Widget):
|
|||||||
msg.username = utils.get_display_name(messages[i].sender)
|
msg.username = utils.get_display_name(messages[i].sender)
|
||||||
msg.send_time = messages[i]\
|
msg.send_time = messages[i]\
|
||||||
.date\
|
.date\
|
||||||
.astimezone(datetime.timezone.utc)\
|
.astimezone(self.timezone)\
|
||||||
.strftime("%H:%M")
|
.strftime("%H:%M")
|
||||||
|
|
||||||
self.is_msg_update_blocked = False
|
self.is_msg_update_blocked = False
|
||||||
@ -172,7 +219,7 @@ class Dialog(Widget):
|
|||||||
class Message(Widget):
|
class Message(Widget):
|
||||||
"""Класс виджета сообщений для окна диалога"""
|
"""Класс виджета сообщений для окна диалога"""
|
||||||
|
|
||||||
message: Reactive[str] = Reactive("", recompose=True)
|
message: Reactive[Content] = Reactive("", recompose=True)
|
||||||
is_me: Reactive[bool] = Reactive(False, recompose=True)
|
is_me: Reactive[bool] = Reactive(False, recompose=True)
|
||||||
username: Reactive[str] = Reactive("", recompose=True)
|
username: Reactive[str] = Reactive("", recompose=True)
|
||||||
send_time: Reactive[str] = Reactive("", recompose=True)
|
send_time: Reactive[str] = Reactive("", recompose=True)
|
||||||
@ -190,12 +237,12 @@ class Message(Widget):
|
|||||||
pass
|
pass
|
||||||
|
|
||||||
def compose(self) -> ComposeResult:
|
def compose(self) -> ComposeResult:
|
||||||
static = Static(self.message)
|
label = Label(self.message, markup=False)
|
||||||
static.border_title = self.username * (not self.is_me)
|
label.border_title = self.username * (not self.is_me)
|
||||||
static.border_subtitle = self.send_time
|
label.border_subtitle = self.send_time
|
||||||
|
|
||||||
with Container():
|
with Container():
|
||||||
yield static
|
yield label
|
||||||
|
|
||||||
if self.is_me:
|
if self.is_me:
|
||||||
self.classes = "is_me_true"
|
self.classes = "is_me_true"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user