This commit is contained in:
Mikhail Kilin
2026-01-18 14:49:31 +03:00
parent d701464fde
commit b6d9291864
29 changed files with 3920 additions and 833 deletions

423
src/tdlib/client.rs Normal file
View File

@@ -0,0 +1,423 @@
use std::env;
use tdlib_rs::enums::{AuthorizationState, ChatList, MessageContent, Update, User};
use tdlib_rs::functions;
use tdlib_rs::types::{Chat as TdChat, Message as TdMessage};
#[derive(Debug, Clone, PartialEq)]
#[allow(dead_code)]
pub enum AuthState {
WaitTdlibParameters,
WaitPhoneNumber,
WaitCode,
WaitPassword,
Ready,
Closed,
Error(String),
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct ChatInfo {
pub id: i64,
pub title: String,
pub last_message: String,
pub last_message_date: i32,
pub unread_count: i32,
pub is_pinned: bool,
pub order: i64,
}
#[derive(Debug, Clone)]
pub struct MessageInfo {
pub id: i64,
pub sender_name: String,
pub is_outgoing: bool,
pub content: String,
pub date: i32,
pub is_read: bool,
}
pub struct TdClient {
pub auth_state: AuthState,
pub api_id: i32,
pub api_hash: String,
client_id: i32,
pub chats: Vec<ChatInfo>,
pub current_chat_messages: Vec<MessageInfo>,
}
#[allow(dead_code)]
impl TdClient {
pub fn new() -> Self {
let api_id: i32 = env::var("API_ID")
.unwrap_or_else(|_| "0".to_string())
.parse()
.unwrap_or(0);
let api_hash = env::var("API_HASH").unwrap_or_default();
let client_id = tdlib_rs::create_client();
TdClient {
auth_state: AuthState::WaitTdlibParameters,
api_id,
api_hash,
client_id,
chats: Vec::new(),
current_chat_messages: Vec::new(),
}
}
pub fn is_authenticated(&self) -> bool {
matches!(self.auth_state, AuthState::Ready)
}
pub fn client_id(&self) -> i32 {
self.client_id
}
/// Инициализация TDLib с параметрами
pub async fn init(&mut self) -> Result<(), String> {
let result = 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
self.api_id, // api_id
self.api_hash.clone(), // 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
self.client_id,
)
.await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Failed to set TDLib parameters: {:?}", e)),
}
}
/// Обрабатываем одно обновление от TDLib
pub fn handle_update(&mut self, update: Update) {
match update {
Update::AuthorizationState(state) => {
self.handle_auth_state(state.authorization_state);
}
Update::NewChat(new_chat) => {
self.add_or_update_chat(&new_chat.chat);
}
Update::ChatLastMessage(update) => {
let chat_id = update.chat_id;
let (last_message_text, last_message_date) = update
.last_message
.as_ref()
.map(|msg| (extract_message_text_static(msg), msg.date))
.unwrap_or_default();
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == chat_id) {
chat.last_message = last_message_text;
chat.last_message_date = last_message_date;
}
// Пересортируем после обновления
self.chats.sort_by(|a, b| b.last_message_date.cmp(&a.last_message_date));
}
Update::ChatReadInbox(update) => {
if let Some(chat) = self.chats.iter_mut().find(|c| c.id == update.chat_id) {
chat.unread_count = update.unread_count;
}
}
Update::NewMessage(_new_msg) => {
// Новые сообщения обрабатываются при обновлении UI
}
_ => {}
}
}
fn handle_auth_state(&mut self, state: AuthorizationState) {
self.auth_state = match state {
AuthorizationState::WaitTdlibParameters => AuthState::WaitTdlibParameters,
AuthorizationState::WaitPhoneNumber => AuthState::WaitPhoneNumber,
AuthorizationState::WaitCode(_) => AuthState::WaitCode,
AuthorizationState::WaitPassword(_) => AuthState::WaitPassword,
AuthorizationState::Ready => AuthState::Ready,
AuthorizationState::Closed => AuthState::Closed,
_ => self.auth_state.clone(),
};
}
fn add_or_update_chat(&mut self, td_chat: &TdChat) {
let (last_message, last_message_date) = td_chat
.last_message
.as_ref()
.map(|m| (extract_message_text_static(m), m.date))
.unwrap_or_default();
let chat_info = ChatInfo {
id: td_chat.id,
title: td_chat.title.clone(),
last_message,
last_message_date,
unread_count: td_chat.unread_count,
is_pinned: false,
order: 0,
};
if let Some(existing) = self.chats.iter_mut().find(|c| c.id == td_chat.id) {
existing.title = chat_info.title;
existing.last_message = chat_info.last_message;
existing.last_message_date = chat_info.last_message_date;
existing.unread_count = chat_info.unread_count;
} else {
self.chats.push(chat_info);
}
// Сортируем чаты по дате последнего сообщения (новые сверху)
self.chats.sort_by(|a, b| b.last_message_date.cmp(&a.last_message_date));
}
fn convert_message(&self, message: &TdMessage) -> MessageInfo {
let sender_name = match &message.sender_id {
tdlib_rs::enums::MessageSender::User(user) => format!("User_{}", user.user_id),
tdlib_rs::enums::MessageSender::Chat(chat) => format!("Chat_{}", chat.chat_id),
};
MessageInfo {
id: message.id,
sender_name,
is_outgoing: message.is_outgoing,
content: extract_message_text_static(message),
date: message.date,
is_read: !message.is_outgoing || message.id <= 0,
}
}
/// Отправка номера телефона
pub async fn send_phone_number(&mut self, phone: String) -> Result<(), String> {
let result = functions::set_authentication_phone_number(
phone,
None,
self.client_id,
)
.await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка отправки номера: {:?}", e)),
}
}
/// Отправка кода подтверждения
pub async fn send_code(&mut self, code: String) -> Result<(), String> {
let result = functions::check_authentication_code(code, self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Неверный код: {:?}", e)),
}
}
/// Отправка пароля 2FA
pub async fn send_password(&mut self, password: String) -> Result<(), String> {
let result = functions::check_authentication_password(password, self.client_id).await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Неверный пароль: {:?}", e)),
}
}
/// Загрузка списка чатов
pub async fn load_chats(&mut self, limit: i32) -> Result<(), String> {
let result = functions::load_chats(
Some(ChatList::Main),
limit,
self.client_id,
)
.await;
match result {
Ok(_) => Ok(()),
Err(e) => Err(format!("Ошибка загрузки чатов: {:?}", e)),
}
}
/// Загрузка истории сообщений чата
pub async fn get_chat_history(
&mut self,
chat_id: i64,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
let _ = functions::open_chat(chat_id, self.client_id).await;
// Загружаем историю с сервера (only_local=false)
let result = functions::get_chat_history(
chat_id,
0, // from_message_id (0 = с последнего сообщения)
0, // offset
limit,
false, // only_local - загружаем с сервера!
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
let mut result_messages: Vec<MessageInfo> = messages
.messages
.into_iter()
.filter_map(|m| m.map(|msg| self.convert_message(&msg)))
.collect();
// Сообщения приходят от новых к старым, переворачиваем
result_messages.reverse();
self.current_chat_messages = result_messages.clone();
Ok(result_messages)
}
Err(e) => Err(format!("Ошибка загрузки сообщений: {:?}", e)),
}
}
/// Загрузка старых сообщений (для скролла вверх)
pub async fn load_older_messages(
&mut self,
chat_id: i64,
from_message_id: i64,
limit: i32,
) -> Result<Vec<MessageInfo>, String> {
let result = functions::get_chat_history(
chat_id,
from_message_id,
0, // offset
limit,
false, // only_local
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::Messages::Messages(messages)) => {
let mut result_messages: Vec<MessageInfo> = messages
.messages
.into_iter()
.filter_map(|m| m.map(|msg| self.convert_message(&msg)))
.collect();
// Сообщения приходят от новых к старым, переворачиваем
result_messages.reverse();
Ok(result_messages)
}
Err(e) => Err(format!("Ошибка загрузки сообщений: {:?}", e)),
}
}
/// Получение информации о пользователе по ID
pub async fn get_user_name(&self, user_id: i64) -> String {
match functions::get_user(user_id, self.client_id).await {
Ok(user) => {
// User is an enum, need to match it
match user {
User::User(u) => {
let first = u.first_name;
let last = u.last_name;
if last.is_empty() {
first
} else {
format!("{} {}", first, last)
}
}
}
}
Err(_) => format!("User_{}", user_id),
}
}
/// Получение моего user_id
pub async fn get_me(&self) -> Result<i64, String> {
match functions::get_me(self.client_id).await {
Ok(user) => {
match user {
User::User(u) => Ok(u.id),
}
}
Err(e) => Err(format!("Ошибка получения профиля: {:?}", e)),
}
}
/// Отправка текстового сообщения
pub async fn send_message(&self, chat_id: i64, text: String) -> Result<MessageInfo, String> {
use tdlib_rs::types::{FormattedText, InputMessageText};
use tdlib_rs::enums::InputMessageContent;
let content = InputMessageContent::InputMessageText(InputMessageText {
text: FormattedText {
text: text.clone(),
entities: vec![],
},
link_preview_options: None,
clear_draft: true,
});
let result = functions::send_message(
chat_id,
0, // message_thread_id
None, // reply_to
None, // options
content,
self.client_id,
)
.await;
match result {
Ok(tdlib_rs::enums::Message::Message(msg)) => {
// Конвертируем отправленное сообщение в MessageInfo
Ok(MessageInfo {
id: msg.id,
sender_name: "You".to_string(),
is_outgoing: true,
content: text,
date: msg.date,
is_read: false,
})
}
Err(e) => Err(format!("Ошибка отправки сообщения: {:?}", e)),
}
}
}
/// Статическая функция для извлечения текста сообщения (без &self)
fn extract_message_text_static(message: &TdMessage) -> String {
match &message.content {
MessageContent::MessageText(text) => text.text.text.clone(),
MessageContent::MessagePhoto(photo) => {
if photo.caption.text.is_empty() {
"[Фото]".to_string()
} else {
format!("[Фото] {}", photo.caption.text)
}
}
MessageContent::MessageVideo(_) => "[Видео]".to_string(),
MessageContent::MessageDocument(doc) => {
format!("[Файл: {}]", doc.document.file_name)
}
MessageContent::MessageVoiceNote(_) => "[Голосовое сообщение]".to_string(),
MessageContent::MessageVideoNote(_) => "[Видеосообщение]".to_string(),
MessageContent::MessageSticker(sticker) => {
format!("[Стикер: {}]", sticker.sticker.emoji)
}
MessageContent::MessageAnimation(_) => "[GIF]".to_string(),
MessageContent::MessageAudio(audio) => {
format!("[Аудио: {}]", audio.audio.title)
}
MessageContent::MessageCall(_) => "[Звонок]".to_string(),
MessageContent::MessagePoll(poll) => {
format!("[Опрос: {}]", poll.poll.question.text)
}
_ => "[Сообщение]".to_string(),
}
}