fixes
This commit is contained in:
423
src/tdlib/client.rs
Normal file
423
src/tdlib/client.rs
Normal 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(),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user