Files
telegram-tui/src/main.rs
Mikhail Kilin e1bceada6d fixes
2026-01-20 00:57:28 +03:00

177 lines
6.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

mod app;
mod input;
mod tdlib;
mod ui;
mod utils;
use crossterm::{
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io;
use std::time::Duration;
use tdlib_rs::enums::Update;
use app::{App, AppScreen};
use input::{handle_auth_input, handle_main_input};
use tdlib::client::AuthState;
use utils::disable_tdlib_logs;
#[tokio::main]
async fn main() -> Result<(), io::Error> {
// Загружаем переменные окружения из .env
let _ = dotenvy::dotenv();
// Отключаем логи TDLib ДО создания клиента
disable_tdlib_logs();
// Setup terminal
enable_raw_mode()?;
let mut stdout = io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
// Create app state
let mut app = App::new();
let res = run_app(&mut terminal, &mut app).await;
// Restore terminal
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
if let Err(err) = res {
println!("Error: {:?}", err);
}
Ok(())
}
async fn run_app<B: ratatui::backend::Backend>(
terminal: &mut Terminal<B>,
app: &mut App,
) -> io::Result<()> {
// Канал для передачи updates из polling задачи в main loop
let (update_tx, mut update_rx) = tokio::sync::mpsc::unbounded_channel::<Update>();
// Запускаем polling TDLib receive() в отдельной задаче
tokio::spawn(async move {
loop {
// receive() блокирующий, поэтому запускаем в blocking thread
let result = tokio::task::spawn_blocking(|| tdlib_rs::receive()).await;
if let Ok(Some((update, _client_id))) = result {
let _ = update_tx.send(update);
}
}
});
// Запускаем инициализацию TDLib в фоне
let client_id = app.td_client.client_id();
let api_id = app.td_client.api_id;
let api_hash = app.td_client.api_hash.clone();
tokio::spawn(async move {
let _ = tdlib_rs::functions::set_tdlib_parameters(
false, // use_test_dc
"tdlib_data".to_string(), // database_directory
"".to_string(), // files_directory
"".to_string(), // database_encryption_key
true, // use_file_database
true, // use_chat_info_database
true, // use_message_database
false, // use_secret_chats
api_id,
api_hash,
"en".to_string(), // system_language_code
"Desktop".to_string(), // device_model
"".to_string(), // system_version
env!("CARGO_PKG_VERSION").to_string(), // application_version
client_id,
)
.await;
});
loop {
// Обрабатываем updates от TDLib из канала (неблокирующе)
while let Ok(update) = update_rx.try_recv() {
app.td_client.handle_update(update);
}
// Обновляем состояние экрана на основе auth_state
update_screen_state(app).await;
terminal.draw(|f| ui::render(f, app))?;
// Используем poll для неблокирующего чтения событий
if event::poll(Duration::from_millis(100))? {
if let Event::Key(key) = event::read()? {
// Global quit command
if key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
return Ok(());
}
match app.screen {
AppScreen::Loading => {
// В состоянии загрузки игнорируем ввод
}
AppScreen::Auth => handle_auth_input(app, key.code).await,
AppScreen::Main => handle_main_input(app, key).await,
}
}
}
}
}
async fn update_screen_state(app: &mut App) {
use tokio::time::timeout;
let prev_screen = app.screen.clone();
match &app.td_client.auth_state {
AuthState::WaitTdlibParameters => {
app.screen = AppScreen::Loading;
app.status_message = Some("Инициализация TDLib...".to_string());
}
AuthState::WaitPhoneNumber | AuthState::WaitCode | AuthState::WaitPassword => {
app.screen = AppScreen::Auth;
app.is_loading = false;
}
AuthState::Ready => {
if prev_screen != AppScreen::Main {
app.screen = AppScreen::Main;
app.is_loading = true;
app.status_message = Some("Загрузка чатов...".to_string());
// Запрашиваем загрузку чатов с таймаутом
let _ = timeout(Duration::from_secs(5), app.td_client.load_chats(50)).await;
}
// Синхронизируем чаты из td_client в app
if !app.td_client.chats.is_empty() {
app.chats = app.td_client.chats.clone();
if app.chat_list_state.selected().is_none() && !app.chats.is_empty() {
app.chat_list_state.select(Some(0));
}
// Убираем статус загрузки когда чаты появились
if app.is_loading {
app.is_loading = false;
app.status_message = None;
}
}
}
AuthState::Closed => {
app.status_message = Some("Соединение закрыто".to_string());
}
AuthState::Error(e) => {
app.error_message = Some(e.clone());
}
}
}