refactor: implement newtype pattern for IDs (P2.4)
Добавлены типобезопасные обёртки ChatId, MessageId, UserId для предотвращения смешивания разных типов идентификаторов на этапе компиляции. Изменения: - Создан src/types.rs с тремя newtype структурами - Реализованы методы: new(), as_i64(), From<i64>, Display - Добавлены traits: Hash, Eq, Serialize, Deserialize - Обновлены 15+ модулей для использования новых типов: * tdlib: types.rs, chats.rs, messages.rs, users.rs, reactions.rs, client.rs * app: mod.rs, chat_state.rs * input: main_input.rs * tests: app_builder.rs, test_data.rs - Исправлены 53 ошибки компиляции связанные с type conversions Преимущества: - Компилятор предотвращает смешивание разных типов ID - Улучшенная читаемость кода (явные типы вместо i64) - Самодокументирующиеся типы Статус: Priority 2 теперь 60% (3/5 задач) - ✅ Error enum - ✅ Config validation - ✅ Newtype для ID - ⏳ MessageInfo реструктуризация - ⏳ MessageBuilder pattern Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
72
CONTEXT.md
72
CONTEXT.md
@@ -128,6 +128,7 @@
|
|||||||
src/
|
src/
|
||||||
├── main.rs # Точка входа, event loop, TDLib инициализация, graceful shutdown
|
├── main.rs # Точка входа, event loop, TDLib инициализация, graceful shutdown
|
||||||
├── lib.rs # Библиотечный интерфейс (для тестов)
|
├── lib.rs # Библиотечный интерфейс (для тестов)
|
||||||
|
├── types.rs # Типобезопасные обёртки (ChatId, MessageId, UserId)
|
||||||
├── config.rs # Конфигурация (TOML), загрузка credentials
|
├── config.rs # Конфигурация (TOML), загрузка credentials
|
||||||
├── app/
|
├── app/
|
||||||
│ ├── mod.rs # App структура и состояние (needs_redraw флаг)
|
│ ├── mod.rs # App структура и состояние (needs_redraw флаг)
|
||||||
@@ -147,7 +148,13 @@ src/
|
|||||||
├── utils.rs # Утилиты (disable_tdlib_logs, format_timestamp_with_tz, format_date, get_day)
|
├── utils.rs # Утилиты (disable_tdlib_logs, format_timestamp_with_tz, format_date, get_day)
|
||||||
└── tdlib/
|
└── tdlib/
|
||||||
├── mod.rs # Модуль экспорта (TdClient, UserOnlineStatus, NetworkState)
|
├── mod.rs # Модуль экспорта (TdClient, UserOnlineStatus, NetworkState)
|
||||||
└── client.rs # TdClient: авторизация, чаты, сообщения, кеш, NetworkState, ReactionInfo
|
├── client.rs # TdClient: авторизация, chats, messages, users, reactions
|
||||||
|
├── auth.rs # AuthManager + AuthState enum
|
||||||
|
├── chats.rs # ChatManager для операций с чатами
|
||||||
|
├── messages.rs # MessageManager для сообщений
|
||||||
|
├── users.rs # UserCache с LRU кэшем
|
||||||
|
├── reactions.rs # ReactionManager
|
||||||
|
└── types.rs # Общие типы данных (ChatInfo, MessageInfo, etc.)
|
||||||
|
|
||||||
tests/
|
tests/
|
||||||
├── helpers/
|
├── helpers/
|
||||||
@@ -290,9 +297,48 @@ reaction_chosen = "yellow"
|
|||||||
reaction_other = "gray"
|
reaction_other = "gray"
|
||||||
```
|
```
|
||||||
|
|
||||||
## Последние обновления (2026-01-30)
|
## Последние обновления (2026-01-31)
|
||||||
|
|
||||||
### Тестирование — ЗАВЕРШЕНО! 🎉🎊🚀
|
### Рефакторинг — Priority 2 продолжается! 🏗️✨
|
||||||
|
|
||||||
|
**P2.4 — Newtype pattern для ID** ✅ ЗАВЕРШЕНО!
|
||||||
|
|
||||||
|
**Что сделано**:
|
||||||
|
- ✅ Создан `src/types.rs` с типобезопасными обёртками для идентификаторов
|
||||||
|
- ✅ Реализованы три типа: `ChatId(i64)`, `MessageId(i64)`, `UserId(i64)`
|
||||||
|
- ✅ Добавлены методы: `new()`, `as_i64()`, `From<i64>`, `Display`, `Hash`, `Eq`, `Serialize/Deserialize`
|
||||||
|
- ✅ Обновлены 15+ модулей для использования новых типов
|
||||||
|
- ✅ Исправлены 53 ошибки компиляции связанные с type conversions
|
||||||
|
- ✅ Компилятор теперь предотвращает смешивание разных типов ID на этапе компиляции
|
||||||
|
|
||||||
|
**Модули обновлены**:
|
||||||
|
- `tdlib/types.rs` — ChatInfo, MessageInfo, ReplyInfo, ProfileInfo
|
||||||
|
- `tdlib/chats.rs` — все методы с chat_id параметрами
|
||||||
|
- `tdlib/messages.rs` — MessageManager, pending_view_messages
|
||||||
|
- `tdlib/users.rs` — LruCache<UserId>, UserCache mappings
|
||||||
|
- `tdlib/reactions.rs` — reaction methods
|
||||||
|
- `tdlib/client.rs` — все публичные методы и Update handlers
|
||||||
|
- `app/mod.rs` — selected_chat_id
|
||||||
|
- `app/chat_state.rs` — все варианты ChatState
|
||||||
|
- `input/main_input.rs` — обработка ввода с преобразованием типов
|
||||||
|
- Test helpers — TestAppBuilder, TestChatBuilder, TestMessageBuilder
|
||||||
|
|
||||||
|
**Преимущества**:
|
||||||
|
- 🛡️ Type safety на уровне компиляции — невозможно перепутать ChatId, MessageId, UserId
|
||||||
|
- 🔍 Улучшенная читаемость кода — явные типы вместо i64
|
||||||
|
- 🐛 Меньше ошибок — компилятор ловит проблемы до запуска
|
||||||
|
- 📚 Лучшая документация — типы самодокументируются
|
||||||
|
|
||||||
|
**Статус Priority 2**: 60% (3/5 задач) ✅
|
||||||
|
- ✅ Error enum
|
||||||
|
- ✅ Config validation
|
||||||
|
- ✅ Newtype для ID
|
||||||
|
- ⏳ MessageInfo реструктуризация
|
||||||
|
- ⏳ MessageBuilder pattern
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Тестирование — ЗАВЕРШЕНО! 🎉🎊🚀 (2026-01-30)
|
||||||
|
|
||||||
**Добавлено**:
|
**Добавлено**:
|
||||||
- 📝 93 integration теста (12 файлов): send_message, edit_message, delete_message, reply_forward, reactions, search, drafts, navigation, profile, network_typing, **copy**, **config**
|
- 📝 93 integration теста (12 файлов): send_message, edit_message, delete_message, reply_forward, reactions, search, drafts, navigation, profile, network_typing, **copy**, **config**
|
||||||
@@ -357,7 +403,7 @@ reaction_other = "gray"
|
|||||||
- Проще добавлять новые фичи
|
- Проще добавлять новые фичи
|
||||||
- Лучше читаемость
|
- Лучше читаемость
|
||||||
|
|
||||||
**Priority 2 (40% завершено - 2/5)**:
|
**Priority 2 (60% завершено - 3/5)**:
|
||||||
- ✅ **P2.5 — Error enum** (завершено 2026-01-31)
|
- ✅ **P2.5 — Error enum** (завершено 2026-01-31)
|
||||||
- Создан `src/error.rs` с типобезопасным enum `TeletuiError`
|
- Создан `src/error.rs` с типобезопасным enum `TeletuiError`
|
||||||
- Добавлены варианты: TdLib, Config, Network, Auth, Chat, Message, User, InvalidTimezone, InvalidColor, Clipboard, Io, Toml, Json, Other
|
- Добавлены варианты: TdLib, Config, Network, Auth, Chat, Message, User, InvalidTimezone, InvalidColor, Clipboard, Io, Toml, Json, Other
|
||||||
@@ -373,7 +419,20 @@ reaction_other = "gray"
|
|||||||
- При загрузке невалидного конфига автоматически используется дефолтный
|
- При загрузке невалидного конфига автоматически используется дефолтный
|
||||||
- Все 350 тестов проходят ✅
|
- Все 350 тестов проходят ✅
|
||||||
|
|
||||||
**Следующие шаги**: Priority 2 (Newtype для ID, MessageBuilder, реструктуризация MessageInfo)
|
- ✅ **P2.4 — Newtype pattern для ID** (завершено 2026-01-31)
|
||||||
|
- Создан `src/types.rs` с типобезопасными обёртками: `ChatId`, `MessageId`, `UserId`
|
||||||
|
- Реализованы методы: `new()`, `as_i64()`, `From<i64>`, `Display`, `Hash`, `Eq`, `Serialize/Deserialize`
|
||||||
|
- Обновлены 15+ модулей для использования новых типов:
|
||||||
|
- `tdlib/types.rs`: ChatInfo, MessageInfo, ReplyInfo, ProfileInfo
|
||||||
|
- `tdlib/chats.rs`, `tdlib/messages.rs`, `tdlib/users.rs`, `tdlib/reactions.rs`
|
||||||
|
- `tdlib/client.rs`: все методы и Update handlers
|
||||||
|
- `app/mod.rs`, `app/chat_state.rs`
|
||||||
|
- `input/main_input.rs`
|
||||||
|
- Test helpers (app_builder, test_data)
|
||||||
|
- Компилятор теперь предотвращает смешивание разных типов ID
|
||||||
|
- Все тесты компилируются успешно ✅
|
||||||
|
|
||||||
|
**Следующие шаги**: Priority 2 (MessageBuilder, реструктуризация MessageInfo)
|
||||||
|
|
||||||
Подробности: [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md)
|
Подробности: [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md)
|
||||||
|
|
||||||
@@ -393,9 +452,10 @@ reaction_other = "gray"
|
|||||||
**Завершено** (Priority 2):
|
**Завершено** (Priority 2):
|
||||||
1. ~~**Error enum**~~ ✅ — типобезопасная обработка ошибок (2026-01-31)
|
1. ~~**Error enum**~~ ✅ — типобезопасная обработка ошибок (2026-01-31)
|
||||||
2. ~~**Config validation**~~ ✅ — валидация конфигурации при загрузке (2026-01-31)
|
2. ~~**Config validation**~~ ✅ — валидация конфигурации при загрузке (2026-01-31)
|
||||||
|
3. ~~**Newtype pattern для ID**~~ ✅ — типобезопасные обёртки ChatId, MessageId, UserId (2026-01-31)
|
||||||
|
|
||||||
**В работе** (Priority 2-5):
|
**В работе** (Priority 2-5):
|
||||||
1. **Типобезопасность** — newtype pattern для ID
|
1. **MessageInfo реструктуризация** — упрощение структуры сообщений
|
||||||
2. **MessageBuilder** — упрощение создания сообщений
|
2. **MessageBuilder** — упрощение создания сообщений
|
||||||
3. **UI компоненты** — выделить переиспользуемые компоненты
|
3. **UI компоненты** — выделить переиспользуемые компоненты
|
||||||
4. **Форматирование** — вынести markdown форматирование в отдельный модуль
|
4. **Форматирование** — вынести markdown форматирование в отдельный модуль
|
||||||
|
|||||||
@@ -145,34 +145,52 @@ pub const TDLIB_MESSAGE_LIMIT: i32 = 50;
|
|||||||
|
|
||||||
## Приоритет 2: Улучшение типобезопасности
|
## Приоритет 2: Улучшение типобезопасности
|
||||||
|
|
||||||
### 4. Newtype pattern для ID
|
### 4. Newtype pattern для ID ✅ ЗАВЕРШЕНО!
|
||||||
|
|
||||||
|
**Статус**: ЗАВЕРШЕНО (2026-01-31)
|
||||||
|
|
||||||
**Проблема**: Везде используется `i64` для `chat_id`, `message_id`, `user_id` — легко перепутать.
|
**Проблема**: Везде используется `i64` для `chat_id`, `message_id`, `user_id` — легко перепутать.
|
||||||
|
|
||||||
**Решение**: Создать `src/types.rs`:
|
**Решение**: ✅ Реализовано в `src/types.rs`:
|
||||||
```rust
|
```rust
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct ChatId(pub i64);
|
pub struct ChatId(pub i64);
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
impl ChatId {
|
||||||
pub struct MessageId(pub i64);
|
pub fn new(id: i64) -> Self { Self(id) }
|
||||||
|
pub fn as_i64(&self) -> i64 { self.0 }
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
}
|
||||||
pub struct UserId(pub i64);
|
|
||||||
|
|
||||||
impl From<i64> for ChatId {
|
impl From<i64> for ChatId {
|
||||||
fn from(id: i64) -> Self {
|
fn from(id: i64) -> Self { ChatId(id) }
|
||||||
ChatId(id)
|
}
|
||||||
|
|
||||||
|
impl Display for ChatId {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Аналогично для MessageId и UserId
|
// Аналогично для MessageId и UserId
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Что сделано**:
|
||||||
|
- ✅ Создан `src/types.rs` с тремя типами: `ChatId`, `MessageId`, `UserId`
|
||||||
|
- ✅ Добавлены методы `new()`, `as_i64()`, `From<i64>`, `Display`
|
||||||
|
- ✅ Реализованы traits: `Hash`, `Eq`, `Serialize`, `Deserialize`
|
||||||
|
- ✅ Обновлены 15+ модулей:
|
||||||
|
- `tdlib/types.rs`, `tdlib/chats.rs`, `tdlib/messages.rs`, `tdlib/users.rs`
|
||||||
|
- `tdlib/reactions.rs`, `tdlib/client.rs`
|
||||||
|
- `app/mod.rs`, `app/chat_state.rs`, `input/main_input.rs`
|
||||||
|
- Test helpers: `app_builder.rs`, `test_data.rs`
|
||||||
|
- ✅ Исправлены 53 ошибки компиляции
|
||||||
|
- ✅ Код компилируется успешно
|
||||||
|
|
||||||
**Преимущества**:
|
**Преимущества**:
|
||||||
- Невозможно случайно передать message_id вместо chat_id
|
- ✅ Невозможно случайно передать message_id вместо chat_id
|
||||||
- Компилятор поймает ошибки
|
- ✅ Компилятор ловит ошибки на этапе компиляции
|
||||||
- Улучшенная читаемость
|
- ✅ Улучшенная читаемость кода
|
||||||
|
- ✅ Самодокументирующиеся типы
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -608,12 +626,17 @@ tracing-subscriber = "0.3"
|
|||||||
- [x] P1.1 — ChatState enum
|
- [x] P1.1 — ChatState enum
|
||||||
- [x] P1.2 — Разделить TdClient
|
- [x] P1.2 — Разделить TdClient
|
||||||
- [x] P1.3 — Константы
|
- [x] P1.3 — Константы
|
||||||
- [x] Priority 2: 2/5 задач (40%)
|
- [x] Priority 2: 3/5 задач (60%)
|
||||||
|
- [x] P2.5 — Error enum
|
||||||
|
- [x] P2.3 — Config validation
|
||||||
|
- [x] P2.4 — Newtype для ID
|
||||||
|
- [ ] P2.6 — MessageInfo реструктуризация
|
||||||
|
- [ ] P2.7 — MessageBuilder pattern
|
||||||
- [ ] Priority 3: 0/4 задач
|
- [ ] Priority 3: 0/4 задач
|
||||||
- [ ] Priority 4: 0/4 задач
|
- [ ] Priority 4: 0/4 задач
|
||||||
- [ ] Priority 5: 0/3 задач
|
- [ ] Priority 5: 0/3 задач
|
||||||
|
|
||||||
**Всего**: 5/17 задач (29%)
|
**Всего**: 6/17 задач (35%)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Chat state management - type-safe state machine for chat modes
|
// Chat state management - type-safe state machine for chat modes
|
||||||
|
|
||||||
use crate::tdlib::{MessageInfo, ProfileInfo};
|
use crate::tdlib::{MessageInfo, ProfileInfo};
|
||||||
|
use crate::types::MessageId;
|
||||||
|
|
||||||
/// Состояния чата - взаимоисключающие режимы работы с чатом
|
/// Состояния чата - взаимоисключающие режимы работы с чатом
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@@ -17,7 +18,7 @@ pub enum ChatState {
|
|||||||
/// Редактирование сообщения
|
/// Редактирование сообщения
|
||||||
Editing {
|
Editing {
|
||||||
/// ID редактируемого сообщения
|
/// ID редактируемого сообщения
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
/// Индекс сообщения в списке
|
/// Индекс сообщения в списке
|
||||||
selected_index: usize,
|
selected_index: usize,
|
||||||
},
|
},
|
||||||
@@ -25,13 +26,13 @@ pub enum ChatState {
|
|||||||
/// Ответ на сообщение (reply)
|
/// Ответ на сообщение (reply)
|
||||||
Reply {
|
Reply {
|
||||||
/// ID сообщения, на которое отвечаем
|
/// ID сообщения, на которое отвечаем
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Пересылка сообщения (forward)
|
/// Пересылка сообщения (forward)
|
||||||
Forward {
|
Forward {
|
||||||
/// ID сообщения для пересылки
|
/// ID сообщения для пересылки
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
/// Находимся в режиме выбора чата для пересылки
|
/// Находимся в режиме выбора чата для пересылки
|
||||||
selecting_chat: bool,
|
selecting_chat: bool,
|
||||||
},
|
},
|
||||||
@@ -39,13 +40,13 @@ pub enum ChatState {
|
|||||||
/// Подтверждение удаления сообщения
|
/// Подтверждение удаления сообщения
|
||||||
DeleteConfirmation {
|
DeleteConfirmation {
|
||||||
/// ID сообщения для удаления
|
/// ID сообщения для удаления
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Выбор реакции на сообщение
|
/// Выбор реакции на сообщение
|
||||||
ReactionPicker {
|
ReactionPicker {
|
||||||
/// ID сообщения для реакции
|
/// ID сообщения для реакции
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
/// Список доступных реакций
|
/// Список доступных реакций
|
||||||
available_reactions: Vec<String>,
|
available_reactions: Vec<String>,
|
||||||
/// Индекс выбранной реакции в picker
|
/// Индекс выбранной реакции в picker
|
||||||
@@ -139,7 +140,7 @@ impl ChatState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Возвращает ID выбранного сообщения (если есть)
|
/// Возвращает ID выбранного сообщения (если есть)
|
||||||
pub fn selected_message_id(&self) -> Option<i64> {
|
pub fn selected_message_id(&self) -> Option<MessageId> {
|
||||||
match self {
|
match self {
|
||||||
ChatState::Editing { message_id, .. } => Some(*message_id),
|
ChatState::Editing { message_id, .. } => Some(*message_id),
|
||||||
ChatState::Reply { message_id } => Some(*message_id),
|
ChatState::Reply { message_id } => Some(*message_id),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ pub use chat_state::ChatState;
|
|||||||
pub use state::AppScreen;
|
pub use state::AppScreen;
|
||||||
|
|
||||||
use crate::tdlib::{ChatInfo, TdClient};
|
use crate::tdlib::{ChatInfo, TdClient};
|
||||||
|
use crate::types::ChatId;
|
||||||
use ratatui::widgets::ListState;
|
use ratatui::widgets::ListState;
|
||||||
|
|
||||||
pub struct App {
|
pub struct App {
|
||||||
@@ -22,7 +23,7 @@ pub struct App {
|
|||||||
// Main app state
|
// Main app state
|
||||||
pub chats: Vec<ChatInfo>,
|
pub chats: Vec<ChatInfo>,
|
||||||
pub chat_list_state: ListState,
|
pub chat_list_state: ListState,
|
||||||
pub selected_chat_id: Option<i64>,
|
pub selected_chat_id: Option<ChatId>,
|
||||||
pub message_input: String,
|
pub message_input: String,
|
||||||
/// Позиция курсора в message_input (в символах)
|
/// Позиция курсора в message_input (в символах)
|
||||||
pub cursor_position: usize,
|
pub cursor_position: usize,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::app::App;
|
use crate::app::App;
|
||||||
use crate::tdlib::ChatAction;
|
use crate::tdlib::ChatAction;
|
||||||
|
use crate::types::{ChatId, MessageId};
|
||||||
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
use tokio::time::timeout;
|
use tokio::time::timeout;
|
||||||
@@ -187,6 +188,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Перейти к выбранному сообщению
|
// Перейти к выбранному сообщению
|
||||||
if let Some(msg_id) = app.get_selected_search_result_id() {
|
if let Some(msg_id) = app.get_selected_search_result_id() {
|
||||||
|
let msg_id = MessageId::new(msg_id);
|
||||||
let msg_index = app
|
let msg_index = app
|
||||||
.td_client
|
.td_client
|
||||||
.current_chat_messages()
|
.current_chat_messages()
|
||||||
@@ -260,6 +262,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
KeyCode::Enter => {
|
KeyCode::Enter => {
|
||||||
// Перейти к сообщению в истории
|
// Перейти к сообщению в истории
|
||||||
if let Some(msg_id) = app.get_selected_pinned_id() {
|
if let Some(msg_id) = app.get_selected_pinned_id() {
|
||||||
|
let msg_id = MessageId::new(msg_id);
|
||||||
// Ищем индекс сообщения в текущей истории
|
// Ищем индекс сообщения в текущей истории
|
||||||
let msg_index = app
|
let msg_index = app
|
||||||
.td_client
|
.td_client
|
||||||
@@ -324,6 +327,7 @@ pub async fn handle(app: &mut App, key: KeyEvent) {
|
|||||||
if let Some(emoji) = app.get_selected_reaction().cloned() {
|
if let Some(emoji) = app.get_selected_reaction().cloned() {
|
||||||
if let Some(message_id) = app.get_selected_message_for_reaction() {
|
if let Some(message_id) = app.get_selected_message_for_reaction() {
|
||||||
if let Some(chat_id) = app.selected_chat_id {
|
if let Some(chat_id) = app.selected_chat_id {
|
||||||
|
let message_id = MessageId::new(message_id);
|
||||||
app.status_message = Some("Отправка реакции...".to_string());
|
app.status_message = Some("Отправка реакции...".to_string());
|
||||||
app.needs_redraw = true;
|
app.needs_redraw = true;
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,9 @@
|
|||||||
pub mod app;
|
pub mod app;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
|
pub mod error;
|
||||||
pub mod input;
|
pub mod input;
|
||||||
pub mod tdlib;
|
pub mod tdlib;
|
||||||
|
pub mod types;
|
||||||
pub mod ui;
|
pub mod ui;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::constants::TDLIB_CHAT_LIMIT;
|
use crate::constants::TDLIB_CHAT_LIMIT;
|
||||||
|
use crate::types::{ChatId, UserId};
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tdlib_rs::enums::{ChatAction, ChatList, ChatType};
|
use tdlib_rs::enums::{ChatAction, ChatList, ChatType};
|
||||||
use tdlib_rs::functions;
|
use tdlib_rs::functions;
|
||||||
@@ -11,7 +12,7 @@ pub struct ChatManager {
|
|||||||
pub folders: Vec<FolderInfo>,
|
pub folders: Vec<FolderInfo>,
|
||||||
pub main_chat_list_position: i32,
|
pub main_chat_list_position: i32,
|
||||||
/// Typing status для текущего чата: (user_id, action_text, timestamp)
|
/// Typing status для текущего чата: (user_id, action_text, timestamp)
|
||||||
pub typing_status: Option<(i64, String, Instant)>,
|
pub typing_status: Option<(UserId, String, Instant)>,
|
||||||
client_id: i32,
|
client_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,8 +51,8 @@ impl ChatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Покинуть чат/группу
|
/// Покинуть чат/группу
|
||||||
pub async fn leave_chat(&self, chat_id: i64) -> Result<(), String> {
|
pub async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String> {
|
||||||
let result = functions::leave_chat(chat_id, self.client_id).await;
|
let result = functions::leave_chat(chat_id.as_i64(), self.client_id).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => Err(format!("Ошибка выхода из чата: {:?}", e)),
|
Err(e) => Err(format!("Ошибка выхода из чата: {:?}", e)),
|
||||||
@@ -59,9 +60,9 @@ impl ChatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Получить информацию профиля чата
|
/// Получить информацию профиля чата
|
||||||
pub async fn get_profile_info(&self, chat_id: i64) -> Result<ProfileInfo, String> {
|
pub async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String> {
|
||||||
// Получаем основную информацию о чате
|
// Получаем основную информацию о чате
|
||||||
let chat_result = functions::get_chat(chat_id, self.client_id).await;
|
let chat_result = functions::get_chat(chat_id.as_i64(), self.client_id).await;
|
||||||
let chat_enum = match chat_result {
|
let chat_enum = match chat_result {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => return Err(format!("Ошибка получения чата: {:?}", e)),
|
Err(e) => return Err(format!("Ошибка получения чата: {:?}", e)),
|
||||||
@@ -187,8 +188,8 @@ impl ChatManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Отправить typing action
|
/// Отправить typing action
|
||||||
pub async fn send_chat_action(&self, chat_id: i64, action: ChatAction) {
|
pub async fn send_chat_action(&self, chat_id: ChatId, action: ChatAction) {
|
||||||
let _ = functions::send_chat_action(chat_id, 0, Some(action), self.client_id).await;
|
let _ = functions::send_chat_action(chat_id.as_i64(), 0, Some(action), self.client_id).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Очистить устаревший typing status (вызывать периодически)
|
/// Очистить устаревший typing status (вызывать периодически)
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::{ChatId, MessageId, UserId};
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
use tdlib_rs::enums::{
|
use tdlib_rs::enums::{
|
||||||
@@ -82,15 +83,15 @@ impl TdClient {
|
|||||||
self.chat_manager.load_folder_chats(folder_id, limit).await
|
self.chat_manager.load_folder_chats(folder_id, limit).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn leave_chat(&self, chat_id: i64) -> Result<(), String> {
|
pub async fn leave_chat(&self, chat_id: ChatId) -> Result<(), String> {
|
||||||
self.chat_manager.leave_chat(chat_id).await
|
self.chat_manager.leave_chat(chat_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_profile_info(&self, chat_id: i64) -> Result<ProfileInfo, String> {
|
pub async fn get_profile_info(&self, chat_id: ChatId) -> Result<ProfileInfo, String> {
|
||||||
self.chat_manager.get_profile_info(chat_id).await
|
self.chat_manager.get_profile_info(chat_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_chat_action(&self, chat_id: i64, action: tdlib_rs::enums::ChatAction) {
|
pub async fn send_chat_action(&self, chat_id: ChatId, action: tdlib_rs::enums::ChatAction) {
|
||||||
self.chat_manager.send_chat_action(chat_id, action).await
|
self.chat_manager.send_chat_action(chat_id, action).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,7 +106,7 @@ impl TdClient {
|
|||||||
// Делегирование к message_manager
|
// Делегирование к message_manager
|
||||||
pub async fn get_chat_history(
|
pub async fn get_chat_history(
|
||||||
&mut self,
|
&mut self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
limit: i32,
|
limit: i32,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
self.message_manager.get_chat_history(chat_id, limit).await
|
self.message_manager.get_chat_history(chat_id, limit).await
|
||||||
@@ -113,25 +114,25 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn load_older_messages(
|
pub async fn load_older_messages(
|
||||||
&mut self,
|
&mut self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
from_message_id: i64,
|
from_message_id: MessageId,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
self.message_manager
|
self.message_manager
|
||||||
.load_older_messages(chat_id, from_message_id)
|
.load_older_messages(chat_id, from_message_id)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result<Vec<MessageInfo>, String> {
|
pub async fn get_pinned_messages(&mut self, chat_id: ChatId) -> Result<Vec<MessageInfo>, String> {
|
||||||
self.message_manager.get_pinned_messages(chat_id).await
|
self.message_manager.get_pinned_messages(chat_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn load_current_pinned_message(&mut self, chat_id: i64) {
|
pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) {
|
||||||
self.message_manager.load_current_pinned_message(chat_id).await
|
self.message_manager.load_current_pinned_message(chat_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn search_messages(
|
pub async fn search_messages(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
query: &str,
|
query: &str,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
self.message_manager.search_messages(chat_id, query).await
|
self.message_manager.search_messages(chat_id, query).await
|
||||||
@@ -139,9 +140,9 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn send_message(
|
pub async fn send_message(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
text: String,
|
text: String,
|
||||||
reply_to_message_id: Option<i64>,
|
reply_to_message_id: Option<MessageId>,
|
||||||
reply_info: Option<super::types::ReplyInfo>,
|
reply_info: Option<super::types::ReplyInfo>,
|
||||||
) -> Result<MessageInfo, String> {
|
) -> Result<MessageInfo, String> {
|
||||||
self.message_manager
|
self.message_manager
|
||||||
@@ -151,8 +152,8 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn edit_message(
|
pub async fn edit_message(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
text: String,
|
text: String,
|
||||||
) -> Result<MessageInfo, String> {
|
) -> Result<MessageInfo, String> {
|
||||||
self.message_manager
|
self.message_manager
|
||||||
@@ -162,8 +163,8 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn delete_messages(
|
pub async fn delete_messages(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_ids: Vec<i64>,
|
message_ids: Vec<MessageId>,
|
||||||
revoke: bool,
|
revoke: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.message_manager
|
self.message_manager
|
||||||
@@ -173,16 +174,16 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn forward_messages(
|
pub async fn forward_messages(
|
||||||
&self,
|
&self,
|
||||||
to_chat_id: i64,
|
to_chat_id: ChatId,
|
||||||
from_chat_id: i64,
|
from_chat_id: ChatId,
|
||||||
message_ids: Vec<i64>,
|
message_ids: Vec<MessageId>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.message_manager
|
self.message_manager
|
||||||
.forward_messages(to_chat_id, from_chat_id, message_ids)
|
.forward_messages(to_chat_id, from_chat_id, message_ids)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
|
pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> {
|
||||||
self.message_manager.set_draft_message(chat_id, text).await
|
self.message_manager.set_draft_message(chat_id, text).await
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,11 +200,11 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Делегирование к user_cache
|
// Делегирование к user_cache
|
||||||
pub async fn get_user_name(&self, user_id: i64) -> String {
|
pub async fn get_user_name(&self, user_id: UserId) -> String {
|
||||||
self.user_cache.get_user_name(user_id).await
|
self.user_cache.get_user_name(user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_user_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> {
|
pub fn get_user_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus> {
|
||||||
self.user_cache.get_status_by_chat_id(chat_id)
|
self.user_cache.get_status_by_chat_id(chat_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,8 +215,8 @@ impl TdClient {
|
|||||||
// Делегирование к reaction_manager
|
// Делегирование к reaction_manager
|
||||||
pub async fn get_message_available_reactions(
|
pub async fn get_message_available_reactions(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
) -> Result<Vec<String>, String> {
|
) -> Result<Vec<String>, String> {
|
||||||
self.reaction_manager
|
self.reaction_manager
|
||||||
.get_message_available_reactions(chat_id, message_id)
|
.get_message_available_reactions(chat_id, message_id)
|
||||||
@@ -224,8 +225,8 @@ impl TdClient {
|
|||||||
|
|
||||||
pub async fn toggle_reaction(
|
pub async fn toggle_reaction(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
emoji: String,
|
emoji: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
self.reaction_manager
|
self.reaction_manager
|
||||||
@@ -275,11 +276,11 @@ impl TdClient {
|
|||||||
&mut self.message_manager.current_chat_messages
|
&mut self.message_manager.current_chat_messages
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_chat_id(&self) -> Option<i64> {
|
pub fn current_chat_id(&self) -> Option<ChatId> {
|
||||||
self.message_manager.current_chat_id
|
self.message_manager.current_chat_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_current_chat_id(&mut self, chat_id: Option<i64>) {
|
pub fn set_current_chat_id(&mut self, chat_id: Option<ChatId>) {
|
||||||
self.message_manager.current_chat_id = chat_id;
|
self.message_manager.current_chat_id = chat_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -371,7 +372,7 @@ impl TdClient {
|
|||||||
self.add_or_update_chat(&td_chat);
|
self.add_or_update_chat(&td_chat);
|
||||||
}
|
}
|
||||||
Update::ChatLastMessage(update) => {
|
Update::ChatLastMessage(update) => {
|
||||||
let chat_id = update.chat_id;
|
let chat_id = ChatId::new(update.chat_id);
|
||||||
let (last_message_text, last_message_date) = update
|
let (last_message_text, last_message_date) = update
|
||||||
.last_message
|
.last_message
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@@ -397,30 +398,31 @@ impl TdClient {
|
|||||||
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
||||||
}
|
}
|
||||||
Update::ChatReadInbox(update) => {
|
Update::ChatReadInbox(update) => {
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||||
chat.unread_count = update.unread_count;
|
chat.unread_count = update.unread_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Update::ChatUnreadMentionCount(update) => {
|
Update::ChatUnreadMentionCount(update) => {
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||||
chat.unread_mention_count = update.unread_mention_count;
|
chat.unread_mention_count = update.unread_mention_count;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Update::ChatNotificationSettings(update) => {
|
Update::ChatNotificationSettings(update) => {
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||||
// mute_for > 0 означает что чат замьючен
|
// mute_for > 0 означает что чат замьючен
|
||||||
chat.is_muted = update.notification_settings.mute_for > 0;
|
chat.is_muted = update.notification_settings.mute_for > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Update::ChatReadOutbox(update) => {
|
Update::ChatReadOutbox(update) => {
|
||||||
// Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения
|
// Обновляем last_read_outbox_message_id когда собеседник прочитал сообщения
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
let last_read_msg_id = MessageId::new(update.last_read_outbox_message_id);
|
||||||
chat.last_read_outbox_message_id = update.last_read_outbox_message_id;
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||||
|
chat.last_read_outbox_message_id = last_read_msg_id;
|
||||||
}
|
}
|
||||||
// Если это текущий открытый чат — обновляем is_read у сообщений
|
// Если это текущий открытый чат — обновляем is_read у сообщений
|
||||||
if Some(update.chat_id) == self.current_chat_id() {
|
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||||
for msg in self.current_chat_messages_mut().iter_mut() {
|
for msg in self.current_chat_messages_mut().iter_mut() {
|
||||||
if msg.is_outgoing && msg.id <= update.last_read_outbox_message_id {
|
if msg.is_outgoing && msg.id <= last_read_msg_id {
|
||||||
msg.is_read = true;
|
msg.is_read = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -428,13 +430,14 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
Update::ChatPosition(update) => {
|
Update::ChatPosition(update) => {
|
||||||
// Обновляем позицию чата или удаляем его из списка
|
// Обновляем позицию чата или удаляем его из списка
|
||||||
|
let chat_id = ChatId::new(update.chat_id);
|
||||||
match &update.position.list {
|
match &update.position.list {
|
||||||
ChatList::Main => {
|
ChatList::Main => {
|
||||||
if update.position.order == 0 {
|
if update.position.order == 0 {
|
||||||
// Чат больше не в Main (перемещён в архив и т.д.)
|
// Чат больше не в Main (перемещён в архив и т.д.)
|
||||||
self.chats_mut().retain(|c| c.id != update.chat_id);
|
self.chats_mut().retain(|c| c.id != chat_id);
|
||||||
} else if let Some(chat) =
|
} else if let Some(chat) =
|
||||||
self.chats_mut().iter_mut().find(|c| c.id == update.chat_id)
|
self.chats_mut().iter_mut().find(|c| c.id == chat_id)
|
||||||
{
|
{
|
||||||
// Обновляем позицию существующего чата
|
// Обновляем позицию существующего чата
|
||||||
chat.order = update.position.order;
|
chat.order = update.position.order;
|
||||||
@@ -445,7 +448,7 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
ChatList::Folder(folder) => {
|
ChatList::Folder(folder) => {
|
||||||
// Обновляем folder_ids для чата
|
// Обновляем folder_ids для чата
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == chat_id) {
|
||||||
if update.position.order == 0 {
|
if update.position.order == 0 {
|
||||||
// Чат удалён из папки
|
// Чат удалён из папки
|
||||||
chat.folder_ids.retain(|&id| id != folder.chat_folder_id);
|
chat.folder_ids.retain(|&id| id != folder.chat_folder_id);
|
||||||
@@ -464,7 +467,7 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
Update::NewMessage(new_msg) => {
|
Update::NewMessage(new_msg) => {
|
||||||
// Добавляем новое сообщение если это текущий открытый чат
|
// Добавляем новое сообщение если это текущий открытый чат
|
||||||
let chat_id = new_msg.message.chat_id;
|
let chat_id = ChatId::new(new_msg.message.chat_id);
|
||||||
if Some(chat_id) == self.current_chat_id() {
|
if Some(chat_id) == self.current_chat_id() {
|
||||||
let msg_info = self.convert_message(&new_msg.message, chat_id);
|
let msg_info = self.convert_message(&new_msg.message, chat_id);
|
||||||
let msg_id = msg_info.id;
|
let msg_id = msg_info.id;
|
||||||
@@ -563,7 +566,7 @@ impl TdClient {
|
|||||||
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
|
UserStatus::LastMonth(_) => UserOnlineStatus::LastMonth,
|
||||||
UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
|
UserStatus::Empty => UserOnlineStatus::LongTimeAgo,
|
||||||
};
|
};
|
||||||
self.user_cache.user_statuses.insert(update.user_id, status);
|
self.user_cache.user_statuses.insert(UserId::new(update.user_id), status);
|
||||||
}
|
}
|
||||||
Update::ConnectionState(update) => {
|
Update::ConnectionState(update) => {
|
||||||
// Обновляем состояние сетевого соединения
|
// Обновляем состояние сетевого соединения
|
||||||
@@ -577,10 +580,10 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
Update::ChatAction(update) => {
|
Update::ChatAction(update) => {
|
||||||
// Обрабатываем только для текущего открытого чата
|
// Обрабатываем только для текущего открытого чата
|
||||||
if Some(update.chat_id) == self.current_chat_id() {
|
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||||
// Извлекаем user_id из sender_id
|
// Извлекаем user_id из sender_id
|
||||||
let user_id = match update.sender_id {
|
let user_id = match update.sender_id {
|
||||||
MessageSender::User(user) => Some(user.user_id),
|
MessageSender::User(user) => Some(UserId::new(user.user_id)),
|
||||||
MessageSender::Chat(_) => None, // Игнорируем действия от имени чата
|
MessageSender::Chat(_) => None, // Игнорируем действия от имени чата
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -624,7 +627,7 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
Update::ChatDraftMessage(update) => {
|
Update::ChatDraftMessage(update) => {
|
||||||
// Обновляем черновик в списке чатов
|
// Обновляем черновик в списке чатов
|
||||||
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == update.chat_id) {
|
if let Some(chat) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(update.chat_id)) {
|
||||||
chat.draft_text = update.draft_message.as_ref().and_then(|draft| {
|
chat.draft_text = update.draft_message.as_ref().and_then(|draft| {
|
||||||
// Извлекаем текст из InputMessageText
|
// Извлекаем текст из InputMessageText
|
||||||
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) =
|
if let tdlib_rs::enums::InputMessageContent::InputMessageText(text_msg) =
|
||||||
@@ -639,11 +642,11 @@ impl TdClient {
|
|||||||
}
|
}
|
||||||
Update::MessageInteractionInfo(update) => {
|
Update::MessageInteractionInfo(update) => {
|
||||||
// Обновляем реакции в текущем открытом чате
|
// Обновляем реакции в текущем открытом чате
|
||||||
if Some(update.chat_id) == self.current_chat_id() {
|
if Some(ChatId::new(update.chat_id)) == self.current_chat_id() {
|
||||||
if let Some(msg) = self
|
if let Some(msg) = self
|
||||||
.current_chat_messages_mut()
|
.current_chat_messages_mut()
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.find(|m| m.id == update.message_id)
|
.find(|m| m.id == MessageId::new(update.message_id))
|
||||||
{
|
{
|
||||||
// Извлекаем реакции из interaction_info
|
// Извлекаем реакции из interaction_info
|
||||||
msg.reactions = update
|
msg.reactions = update
|
||||||
@@ -702,7 +705,7 @@ impl TdClient {
|
|||||||
// Пропускаем удалённые аккаунты
|
// Пропускаем удалённые аккаунты
|
||||||
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
|
if td_chat.title == "Deleted Account" || td_chat.title.is_empty() {
|
||||||
// Удаляем из списка если уже был добавлен
|
// Удаляем из списка если уже был добавлен
|
||||||
self.chats_mut().retain(|c| c.id != td_chat.id);
|
self.chats_mut().retain(|c| c.id != ChatId::new(td_chat.id));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -727,18 +730,20 @@ impl TdClient {
|
|||||||
let username = match &td_chat.r#type {
|
let username = match &td_chat.r#type {
|
||||||
ChatType::Private(private) => {
|
ChatType::Private(private) => {
|
||||||
// Ограничиваем размер chat_user_ids
|
// Ограничиваем размер chat_user_ids
|
||||||
|
let chat_id = ChatId::new(td_chat.id);
|
||||||
if self.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS
|
if self.user_cache.chat_user_ids.len() >= MAX_CHAT_USER_IDS
|
||||||
&& !self.user_cache.chat_user_ids.contains_key(&td_chat.id)
|
&& !self.user_cache.chat_user_ids.contains_key(&chat_id)
|
||||||
{
|
{
|
||||||
// Удаляем случайную запись (первую найденную)
|
// Удаляем случайную запись (первую найденную)
|
||||||
if let Some(&key) = self.user_cache.chat_user_ids.keys().next() {
|
if let Some(&key) = self.user_cache.chat_user_ids.keys().next() {
|
||||||
self.user_cache.chat_user_ids.remove(&key);
|
self.user_cache.chat_user_ids.remove(&key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.user_cache.chat_user_ids.insert(td_chat.id, private.user_id);
|
let user_id = UserId::new(private.user_id);
|
||||||
|
self.user_cache.chat_user_ids.insert(chat_id, user_id);
|
||||||
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
|
// Проверяем, есть ли уже username в кэше (peek не обновляет LRU)
|
||||||
self.user_cache.user_usernames
|
self.user_cache.user_usernames
|
||||||
.peek(&private.user_id)
|
.peek(&user_id)
|
||||||
.map(|u| format!("@{}", u))
|
.map(|u| format!("@{}", u))
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -761,7 +766,7 @@ impl TdClient {
|
|||||||
let is_muted = td_chat.notification_settings.mute_for > 0;
|
let is_muted = td_chat.notification_settings.mute_for > 0;
|
||||||
|
|
||||||
let chat_info = ChatInfo {
|
let chat_info = ChatInfo {
|
||||||
id: td_chat.id,
|
id: ChatId::new(td_chat.id),
|
||||||
title: td_chat.title.clone(),
|
title: td_chat.title.clone(),
|
||||||
username,
|
username,
|
||||||
last_message,
|
last_message,
|
||||||
@@ -770,13 +775,13 @@ impl TdClient {
|
|||||||
unread_mention_count: td_chat.unread_mention_count,
|
unread_mention_count: td_chat.unread_mention_count,
|
||||||
is_pinned,
|
is_pinned,
|
||||||
order,
|
order,
|
||||||
last_read_outbox_message_id: td_chat.last_read_outbox_message_id,
|
last_read_outbox_message_id: MessageId::new(td_chat.last_read_outbox_message_id),
|
||||||
folder_ids,
|
folder_ids,
|
||||||
is_muted,
|
is_muted,
|
||||||
draft_text: None,
|
draft_text: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(existing) = self.chats_mut().iter_mut().find(|c| c.id == td_chat.id) {
|
if let Some(existing) = self.chats_mut().iter_mut().find(|c| c.id == ChatId::new(td_chat.id)) {
|
||||||
existing.title = chat_info.title;
|
existing.title = chat_info.title;
|
||||||
existing.last_message = chat_info.last_message;
|
existing.last_message = chat_info.last_message;
|
||||||
existing.last_message_date = chat_info.last_message_date;
|
existing.last_message_date = chat_info.last_message_date;
|
||||||
@@ -815,37 +820,40 @@ impl TdClient {
|
|||||||
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
self.chats_mut().sort_by(|a, b| b.order.cmp(&a.order));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn convert_message(&mut self, message: &TdMessage, chat_id: i64) -> MessageInfo {
|
fn convert_message(&mut self, message: &TdMessage, chat_id: ChatId) -> MessageInfo {
|
||||||
let sender_name = match &message.sender_id {
|
let sender_name = match &message.sender_id {
|
||||||
tdlib_rs::enums::MessageSender::User(user) => {
|
tdlib_rs::enums::MessageSender::User(user) => {
|
||||||
// Пробуем получить имя из кеша (get обновляет LRU порядок)
|
// Пробуем получить имя из кеша (get обновляет LRU порядок)
|
||||||
if let Some(name) = self.user_cache.user_names.get(&user.user_id).cloned() {
|
let user_id = UserId::new(user.user_id);
|
||||||
|
if let Some(name) = self.user_cache.user_names.get(&user_id).cloned() {
|
||||||
name
|
name
|
||||||
} else {
|
} else {
|
||||||
// Добавляем в очередь для загрузки
|
// Добавляем в очередь для загрузки
|
||||||
if !self.pending_user_ids().contains(&user.user_id) {
|
if !self.pending_user_ids().contains(&user_id) {
|
||||||
self.pending_user_ids_mut().push(user.user_id);
|
self.pending_user_ids_mut().push(user_id);
|
||||||
}
|
}
|
||||||
format!("User_{}", user.user_id)
|
format!("User_{}", user_id.as_i64())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tdlib_rs::enums::MessageSender::Chat(chat) => {
|
tdlib_rs::enums::MessageSender::Chat(chat) => {
|
||||||
// Для чатов используем название чата
|
// Для чатов используем название чата
|
||||||
|
let sender_chat_id = ChatId::new(chat.chat_id);
|
||||||
self.chats()
|
self.chats()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|c| c.id == chat.chat_id)
|
.find(|c| c.id == sender_chat_id)
|
||||||
.map(|c| c.title.clone())
|
.map(|c| c.title.clone())
|
||||||
.unwrap_or_else(|| format!("Chat_{}", chat.chat_id))
|
.unwrap_or_else(|| format!("Chat_{}", sender_chat_id.as_i64()))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Определяем, прочитано ли исходящее сообщение
|
// Определяем, прочитано ли исходящее сообщение
|
||||||
|
let message_id = MessageId::new(message.id);
|
||||||
let is_read = if message.is_outgoing {
|
let is_read = if message.is_outgoing {
|
||||||
// Сообщение прочитано, если его ID <= last_read_outbox_message_id чата
|
// Сообщение прочитано, если его ID <= last_read_outbox_message_id чата
|
||||||
self.chats()
|
self.chats()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|c| c.id == chat_id)
|
.find(|c| c.id == chat_id)
|
||||||
.map(|c| message.id <= c.last_read_outbox_message_id)
|
.map(|c| message_id <= c.last_read_outbox_message_id)
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
} else {
|
} else {
|
||||||
true // Входящие сообщения не показывают галочки
|
true // Входящие сообщения не показывают галочки
|
||||||
@@ -863,7 +871,7 @@ impl TdClient {
|
|||||||
let reactions = self.extract_reactions(message);
|
let reactions = self.extract_reactions(message);
|
||||||
|
|
||||||
MessageInfo {
|
MessageInfo {
|
||||||
id: message.id,
|
id: message_id,
|
||||||
sender_name,
|
sender_name,
|
||||||
is_outgoing: message.is_outgoing,
|
is_outgoing: message.is_outgoing,
|
||||||
content,
|
content,
|
||||||
@@ -891,14 +899,16 @@ impl TdClient {
|
|||||||
self.get_origin_sender_name(origin)
|
self.get_origin_sender_name(origin)
|
||||||
} else {
|
} else {
|
||||||
// Пробуем найти оригинальное сообщение в текущем списке
|
// Пробуем найти оригинальное сообщение в текущем списке
|
||||||
|
let reply_msg_id = MessageId::new(reply.message_id);
|
||||||
self.current_chat_messages()
|
self.current_chat_messages()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.id == reply.message_id)
|
.find(|m| m.id == reply_msg_id)
|
||||||
.map(|m| m.sender_name.clone())
|
.map(|m| m.sender_name.clone())
|
||||||
.unwrap_or_else(|| "...".to_string())
|
.unwrap_or_else(|| "...".to_string())
|
||||||
};
|
};
|
||||||
|
|
||||||
// Получаем текст из content или quote
|
// Получаем текст из content или quote
|
||||||
|
let reply_msg_id = MessageId::new(reply.message_id);
|
||||||
let text = if let Some(quote) = &reply.quote {
|
let text = if let Some(quote) = &reply.quote {
|
||||||
quote.text.text.clone()
|
quote.text.text.clone()
|
||||||
} else if let Some(content) = &reply.content {
|
} else if let Some(content) = &reply.content {
|
||||||
@@ -907,12 +917,12 @@ impl TdClient {
|
|||||||
// Пробуем найти в текущих сообщениях
|
// Пробуем найти в текущих сообщениях
|
||||||
self.current_chat_messages()
|
self.current_chat_messages()
|
||||||
.iter()
|
.iter()
|
||||||
.find(|m| m.id == reply.message_id)
|
.find(|m| m.id == reply_msg_id)
|
||||||
.map(|m| m.content.clone())
|
.map(|m| m.content.clone())
|
||||||
.unwrap_or_default()
|
.unwrap_or_default()
|
||||||
};
|
};
|
||||||
|
|
||||||
Some(ReplyInfo { message_id: reply.message_id, sender_name, text })
|
Some(ReplyInfo { message_id: reply_msg_id, sender_name, text })
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::constants::{MAX_MESSAGES_IN_CHAT, TDLIB_MESSAGE_LIMIT};
|
use crate::constants::{MAX_MESSAGES_IN_CHAT, TDLIB_MESSAGE_LIMIT};
|
||||||
|
use crate::types::{ChatId, MessageId};
|
||||||
use tdlib_rs::enums::{ChatAction, InputMessageContent, InputMessageReplyTo, MessageContent, MessageSender, SearchMessagesFilter, TextParseMode};
|
use tdlib_rs::enums::{ChatAction, InputMessageContent, InputMessageReplyTo, MessageContent, MessageSender, SearchMessagesFilter, TextParseMode};
|
||||||
use tdlib_rs::functions;
|
use tdlib_rs::functions;
|
||||||
use tdlib_rs::types::{Chat as TdChat, FormattedText, InputMessageReplyToMessage, InputMessageText, Message as TdMessage, TextEntity, TextParseModeMarkdown};
|
use tdlib_rs::types::{Chat as TdChat, FormattedText, InputMessageReplyToMessage, InputMessageText, Message as TdMessage, TextEntity, TextParseModeMarkdown};
|
||||||
@@ -8,10 +9,10 @@ use super::types::{ForwardInfo, MessageInfo, ReactionInfo, ReplyInfo};
|
|||||||
/// Менеджер сообщений
|
/// Менеджер сообщений
|
||||||
pub struct MessageManager {
|
pub struct MessageManager {
|
||||||
pub current_chat_messages: Vec<MessageInfo>,
|
pub current_chat_messages: Vec<MessageInfo>,
|
||||||
pub current_chat_id: Option<i64>,
|
pub current_chat_id: Option<ChatId>,
|
||||||
pub current_pinned_message: Option<MessageInfo>,
|
pub current_pinned_message: Option<MessageInfo>,
|
||||||
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
|
/// Очередь сообщений для отметки как прочитанных: (chat_id, message_ids)
|
||||||
pub pending_view_messages: Vec<(i64, Vec<i64>)>,
|
pub pending_view_messages: Vec<(ChatId, Vec<MessageId>)>,
|
||||||
client_id: i32,
|
client_id: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,14 +40,14 @@ impl MessageManager {
|
|||||||
/// Получить историю чата
|
/// Получить историю чата
|
||||||
pub async fn get_chat_history(
|
pub async fn get_chat_history(
|
||||||
&mut self,
|
&mut self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
limit: i32,
|
limit: i32,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
// Устанавливаем текущий чат для получения новых сообщений
|
// Устанавливаем текущий чат для получения новых сообщений
|
||||||
self.current_chat_id = Some(chat_id);
|
self.current_chat_id = Some(chat_id);
|
||||||
|
|
||||||
let result = functions::get_chat_history(
|
let result = functions::get_chat_history(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
0, // from_message_id
|
0, // from_message_id
|
||||||
0, // offset
|
0, // offset
|
||||||
limit,
|
limit,
|
||||||
@@ -75,12 +76,12 @@ impl MessageManager {
|
|||||||
/// Загрузить более старые сообщения
|
/// Загрузить более старые сообщения
|
||||||
pub async fn load_older_messages(
|
pub async fn load_older_messages(
|
||||||
&mut self,
|
&mut self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
from_message_id: i64,
|
from_message_id: MessageId,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
let result = functions::get_chat_history(
|
let result = functions::get_chat_history(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
from_message_id,
|
from_message_id.as_i64(),
|
||||||
0, // offset
|
0, // offset
|
||||||
TDLIB_MESSAGE_LIMIT,
|
TDLIB_MESSAGE_LIMIT,
|
||||||
false,
|
false,
|
||||||
@@ -106,9 +107,9 @@ impl MessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Получить закреплённые сообщения
|
/// Получить закреплённые сообщения
|
||||||
pub async fn get_pinned_messages(&mut self, chat_id: i64) -> Result<Vec<MessageInfo>, String> {
|
pub async fn get_pinned_messages(&mut self, chat_id: ChatId) -> Result<Vec<MessageInfo>, String> {
|
||||||
let result = functions::search_chat_messages(
|
let result = functions::search_chat_messages(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
String::new(),
|
String::new(),
|
||||||
None,
|
None,
|
||||||
0, // from_message_id
|
0, // from_message_id
|
||||||
@@ -137,7 +138,7 @@ impl MessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Загрузить текущее закреплённое сообщение
|
/// Загрузить текущее закреплённое сообщение
|
||||||
pub async fn load_current_pinned_message(&mut self, chat_id: i64) {
|
pub async fn load_current_pinned_message(&mut self, chat_id: ChatId) {
|
||||||
// TODO: В tdlib-rs 1.8.29 поле pinned_message_id было удалено из Chat.
|
// TODO: В tdlib-rs 1.8.29 поле pinned_message_id было удалено из Chat.
|
||||||
// Нужно использовать getChatPinnedMessage или альтернативный способ.
|
// Нужно использовать getChatPinnedMessage или альтернативный способ.
|
||||||
// Временно отключено.
|
// Временно отключено.
|
||||||
@@ -155,11 +156,11 @@ impl MessageManager {
|
|||||||
/// Поиск сообщений в чате
|
/// Поиск сообщений в чате
|
||||||
pub async fn search_messages(
|
pub async fn search_messages(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
query: &str,
|
query: &str,
|
||||||
) -> Result<Vec<MessageInfo>, String> {
|
) -> Result<Vec<MessageInfo>, String> {
|
||||||
let result = functions::search_chat_messages(
|
let result = functions::search_chat_messages(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
query.to_string(),
|
query.to_string(),
|
||||||
None,
|
None,
|
||||||
0, // from_message_id
|
0, // from_message_id
|
||||||
@@ -190,9 +191,9 @@ impl MessageManager {
|
|||||||
/// Отправить сообщение
|
/// Отправить сообщение
|
||||||
pub async fn send_message(
|
pub async fn send_message(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
text: String,
|
text: String,
|
||||||
reply_to_message_id: Option<i64>,
|
reply_to_message_id: Option<MessageId>,
|
||||||
_reply_info: Option<ReplyInfo>,
|
_reply_info: Option<ReplyInfo>,
|
||||||
) -> Result<MessageInfo, String> {
|
) -> Result<MessageInfo, String> {
|
||||||
// Парсим markdown в тексте
|
// Парсим markdown в тексте
|
||||||
@@ -224,13 +225,13 @@ impl MessageManager {
|
|||||||
let reply_to = reply_to_message_id.map(|msg_id| {
|
let reply_to = reply_to_message_id.map(|msg_id| {
|
||||||
InputMessageReplyTo::Message(InputMessageReplyToMessage {
|
InputMessageReplyTo::Message(InputMessageReplyToMessage {
|
||||||
chat_id: 0,
|
chat_id: 0,
|
||||||
message_id: msg_id,
|
message_id: msg_id.as_i64(),
|
||||||
quote: None,
|
quote: None,
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let result = functions::send_message(
|
let result = functions::send_message(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
0, // message_thread_id
|
0, // message_thread_id
|
||||||
reply_to,
|
reply_to,
|
||||||
None, // options
|
None, // options
|
||||||
@@ -252,8 +253,8 @@ impl MessageManager {
|
|||||||
/// Редактировать сообщение
|
/// Редактировать сообщение
|
||||||
pub async fn edit_message(
|
pub async fn edit_message(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
text: String,
|
text: String,
|
||||||
) -> Result<MessageInfo, String> {
|
) -> Result<MessageInfo, String> {
|
||||||
let formatted_text = match functions::parse_text_entities(
|
let formatted_text = match functions::parse_text_entities(
|
||||||
@@ -282,7 +283,7 @@ impl MessageManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let result =
|
let result =
|
||||||
functions::edit_message_text(chat_id, message_id, content, self.client_id).await;
|
functions::edit_message_text(chat_id.as_i64(), message_id.as_i64(), content, self.client_id).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(tdlib_rs::enums::Message::Message(msg)) => self
|
Ok(tdlib_rs::enums::Message::Message(msg)) => self
|
||||||
@@ -297,12 +298,13 @@ impl MessageManager {
|
|||||||
/// Удалить сообщения
|
/// Удалить сообщения
|
||||||
pub async fn delete_messages(
|
pub async fn delete_messages(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_ids: Vec<i64>,
|
message_ids: Vec<MessageId>,
|
||||||
revoke: bool,
|
revoke: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let message_ids_i64: Vec<i64> = message_ids.into_iter().map(|id| id.as_i64()).collect();
|
||||||
let result =
|
let result =
|
||||||
functions::delete_messages(chat_id, message_ids, revoke, self.client_id).await;
|
functions::delete_messages(chat_id.as_i64(), message_ids_i64, revoke, self.client_id).await;
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(e) => Err(format!("Ошибка удаления: {:?}", e)),
|
Err(e) => Err(format!("Ошибка удаления: {:?}", e)),
|
||||||
@@ -312,15 +314,16 @@ impl MessageManager {
|
|||||||
/// Переслать сообщения
|
/// Переслать сообщения
|
||||||
pub async fn forward_messages(
|
pub async fn forward_messages(
|
||||||
&self,
|
&self,
|
||||||
to_chat_id: i64,
|
to_chat_id: ChatId,
|
||||||
from_chat_id: i64,
|
from_chat_id: ChatId,
|
||||||
message_ids: Vec<i64>,
|
message_ids: Vec<MessageId>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
|
let message_ids_i64: Vec<i64> = message_ids.into_iter().map(|id| id.as_i64()).collect();
|
||||||
let result = functions::forward_messages(
|
let result = functions::forward_messages(
|
||||||
to_chat_id,
|
to_chat_id.as_i64(),
|
||||||
0, // message_thread_id
|
0, // message_thread_id
|
||||||
from_chat_id,
|
from_chat_id.as_i64(),
|
||||||
message_ids,
|
message_ids_i64,
|
||||||
None, // options
|
None, // options
|
||||||
false, // send_copy
|
false, // send_copy
|
||||||
false, // remove_caption
|
false, // remove_caption
|
||||||
@@ -335,7 +338,7 @@ impl MessageManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Установить черновик
|
/// Установить черновик
|
||||||
pub async fn set_draft_message(&self, chat_id: i64, text: String) -> Result<(), String> {
|
pub async fn set_draft_message(&self, chat_id: ChatId, text: String) -> Result<(), String> {
|
||||||
use tdlib_rs::types::DraftMessage;
|
use tdlib_rs::types::DraftMessage;
|
||||||
|
|
||||||
let draft = if text.is_empty() {
|
let draft = if text.is_empty() {
|
||||||
@@ -355,7 +358,7 @@ impl MessageManager {
|
|||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
let result = functions::set_chat_draft_message(chat_id, 0, draft, self.client_id).await;
|
let result = functions::set_chat_draft_message(chat_id.as_i64(), 0, draft, self.client_id).await;
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::types::{ChatId, MessageId};
|
||||||
use tdlib_rs::enums::ReactionType;
|
use tdlib_rs::enums::ReactionType;
|
||||||
use tdlib_rs::functions;
|
use tdlib_rs::functions;
|
||||||
use tdlib_rs::types::ReactionTypeEmoji;
|
use tdlib_rs::types::ReactionTypeEmoji;
|
||||||
@@ -15,11 +16,11 @@ impl ReactionManager {
|
|||||||
/// Получить доступные реакции для сообщения
|
/// Получить доступные реакции для сообщения
|
||||||
pub async fn get_message_available_reactions(
|
pub async fn get_message_available_reactions(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
) -> Result<Vec<String>, String> {
|
) -> Result<Vec<String>, String> {
|
||||||
// Получаем сообщение
|
// Получаем сообщение
|
||||||
let msg_result = functions::get_message(chat_id, message_id, self.client_id).await;
|
let msg_result = functions::get_message(chat_id.as_i64(), message_id.as_i64(), self.client_id).await;
|
||||||
let msg = match msg_result {
|
let msg = match msg_result {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => return Err(format!("Ошибка получения сообщения: {:?}", e)),
|
Err(e) => return Err(format!("Ошибка получения сообщения: {:?}", e)),
|
||||||
@@ -27,8 +28,8 @@ impl ReactionManager {
|
|||||||
|
|
||||||
// Получаем доступные реакции для чата
|
// Получаем доступные реакции для чата
|
||||||
let reactions_result = functions::get_message_available_reactions(
|
let reactions_result = functions::get_message_available_reactions(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
message_id,
|
message_id.as_i64(),
|
||||||
10, // row_size
|
10, // row_size
|
||||||
self.client_id,
|
self.client_id,
|
||||||
)
|
)
|
||||||
@@ -89,15 +90,15 @@ impl ReactionManager {
|
|||||||
/// Переключить реакцию на сообщение
|
/// Переключить реакцию на сообщение
|
||||||
pub async fn toggle_reaction(
|
pub async fn toggle_reaction(
|
||||||
&self,
|
&self,
|
||||||
chat_id: i64,
|
chat_id: ChatId,
|
||||||
message_id: i64,
|
message_id: MessageId,
|
||||||
emoji: String,
|
emoji: String,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let reaction = ReactionType::Emoji(ReactionTypeEmoji { emoji });
|
let reaction = ReactionType::Emoji(ReactionTypeEmoji { emoji });
|
||||||
|
|
||||||
let result = functions::add_message_reaction(
|
let result = functions::add_message_reaction(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
message_id,
|
message_id.as_i64(),
|
||||||
reaction.clone(),
|
reaction.clone(),
|
||||||
false, // is_big
|
false, // is_big
|
||||||
false, // update_recent_reactions
|
false, // update_recent_reactions
|
||||||
@@ -110,8 +111,8 @@ impl ReactionManager {
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Если добавление не удалось, пытаемся удалить
|
// Если добавление не удалось, пытаемся удалить
|
||||||
let remove_result = functions::remove_message_reaction(
|
let remove_result = functions::remove_message_reaction(
|
||||||
chat_id,
|
chat_id.as_i64(),
|
||||||
message_id,
|
message_id.as_i64(),
|
||||||
reaction,
|
reaction,
|
||||||
self.client_id,
|
self.client_id,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use tdlib_rs::types::TextEntity;
|
use tdlib_rs::types::TextEntity;
|
||||||
|
|
||||||
|
use crate::types::{ChatId, MessageId};
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub struct ChatInfo {
|
pub struct ChatInfo {
|
||||||
pub id: i64,
|
pub id: ChatId,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub last_message: String,
|
pub last_message: String,
|
||||||
@@ -14,7 +16,7 @@ pub struct ChatInfo {
|
|||||||
pub is_pinned: bool,
|
pub is_pinned: bool,
|
||||||
pub order: i64,
|
pub order: i64,
|
||||||
/// ID последнего прочитанного исходящего сообщения (для галочек)
|
/// ID последнего прочитанного исходящего сообщения (для галочек)
|
||||||
pub last_read_outbox_message_id: i64,
|
pub last_read_outbox_message_id: MessageId,
|
||||||
/// ID папок, в которых находится чат
|
/// ID папок, в которых находится чат
|
||||||
pub folder_ids: Vec<i32>,
|
pub folder_ids: Vec<i32>,
|
||||||
/// Чат замьючен (уведомления отключены)
|
/// Чат замьючен (уведомления отключены)
|
||||||
@@ -27,7 +29,7 @@ pub struct ChatInfo {
|
|||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ReplyInfo {
|
pub struct ReplyInfo {
|
||||||
/// ID сообщения, на которое отвечают
|
/// ID сообщения, на которое отвечают
|
||||||
pub message_id: i64,
|
pub message_id: MessageId,
|
||||||
/// Имя отправителя оригинального сообщения
|
/// Имя отправителя оригинального сообщения
|
||||||
pub sender_name: String,
|
pub sender_name: String,
|
||||||
/// Текст оригинального сообщения (превью)
|
/// Текст оригинального сообщения (превью)
|
||||||
@@ -57,7 +59,7 @@ pub struct ReactionInfo {
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MessageInfo {
|
pub struct MessageInfo {
|
||||||
pub id: i64,
|
pub id: MessageId,
|
||||||
pub sender_name: String,
|
pub sender_name: String,
|
||||||
pub is_outgoing: bool,
|
pub is_outgoing: bool,
|
||||||
pub content: String,
|
pub content: String,
|
||||||
@@ -90,7 +92,7 @@ pub struct FolderInfo {
|
|||||||
/// Информация о профиле чата/пользователя
|
/// Информация о профиле чата/пользователя
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct ProfileInfo {
|
pub struct ProfileInfo {
|
||||||
pub chat_id: i64,
|
pub chat_id: ChatId,
|
||||||
pub title: String,
|
pub title: String,
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
pub bio: Option<String>,
|
pub bio: Option<String>,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::constants::{LAZY_LOAD_USERS_PER_TICK, MAX_CHAT_USER_IDS, MAX_USER_CACHE_SIZE};
|
use crate::constants::{LAZY_LOAD_USERS_PER_TICK, MAX_CHAT_USER_IDS, MAX_USER_CACHE_SIZE};
|
||||||
|
use crate::types::{ChatId, UserId};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use tdlib_rs::enums::{User, UserStatus};
|
use tdlib_rs::enums::{User, UserStatus};
|
||||||
use tdlib_rs::functions;
|
use tdlib_rs::functions;
|
||||||
@@ -7,9 +8,9 @@ use super::types::UserOnlineStatus;
|
|||||||
|
|
||||||
/// Простой LRU-кэш на основе HashMap + Vec для отслеживания порядка
|
/// Простой LRU-кэш на основе HashMap + Vec для отслеживания порядка
|
||||||
pub struct LruCache<V> {
|
pub struct LruCache<V> {
|
||||||
map: HashMap<i64, V>,
|
map: HashMap<UserId, V>,
|
||||||
/// Порядок доступа: последний элемент — самый недавно использованный
|
/// Порядок доступа: последний элемент — самый недавно использованный
|
||||||
order: Vec<i64>,
|
order: Vec<UserId>,
|
||||||
capacity: usize,
|
capacity: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +24,7 @@ impl<V: Clone> LruCache<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Получить значение и обновить порядок доступа
|
/// Получить значение и обновить порядок доступа
|
||||||
pub fn get(&mut self, key: &i64) -> Option<&V> {
|
pub fn get(&mut self, key: &UserId) -> Option<&V> {
|
||||||
if self.map.contains_key(key) {
|
if self.map.contains_key(key) {
|
||||||
// Перемещаем ключ в конец (самый недавно использованный)
|
// Перемещаем ключ в конец (самый недавно использованный)
|
||||||
self.order.retain(|k| k != key);
|
self.order.retain(|k| k != key);
|
||||||
@@ -35,12 +36,12 @@ impl<V: Clone> LruCache<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Получить значение без обновления порядка (для read-only доступа)
|
/// Получить значение без обновления порядка (для read-only доступа)
|
||||||
pub fn peek(&self, key: &i64) -> Option<&V> {
|
pub fn peek(&self, key: &UserId) -> Option<&V> {
|
||||||
self.map.get(key)
|
self.map.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Вставить значение
|
/// Вставить значение
|
||||||
pub fn insert(&mut self, key: i64, value: V) {
|
pub fn insert(&mut self, key: UserId, value: V) {
|
||||||
if self.map.contains_key(&key) {
|
if self.map.contains_key(&key) {
|
||||||
// Обновляем существующее значение
|
// Обновляем существующее значение
|
||||||
self.map.insert(key, value);
|
self.map.insert(key, value);
|
||||||
@@ -78,9 +79,9 @@ pub struct UserCache {
|
|||||||
/// LRU-кэш имён: user_id -> display_name (first_name + last_name)
|
/// LRU-кэш имён: user_id -> display_name (first_name + last_name)
|
||||||
pub user_names: LruCache<String>,
|
pub user_names: LruCache<String>,
|
||||||
/// Связь chat_id -> user_id для приватных чатов
|
/// Связь chat_id -> user_id для приватных чатов
|
||||||
pub chat_user_ids: HashMap<i64, i64>,
|
pub chat_user_ids: HashMap<ChatId, UserId>,
|
||||||
/// Очередь user_id для загрузки имён
|
/// Очередь user_id для загрузки имён
|
||||||
pub pending_user_ids: Vec<i64>,
|
pub pending_user_ids: Vec<UserId>,
|
||||||
/// LRU-кэш онлайн-статусов пользователей: user_id -> status
|
/// LRU-кэш онлайн-статусов пользователей: user_id -> status
|
||||||
pub user_statuses: LruCache<UserOnlineStatus>,
|
pub user_statuses: LruCache<UserOnlineStatus>,
|
||||||
client_id: i32,
|
client_id: i32,
|
||||||
@@ -99,22 +100,22 @@ impl UserCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Получить username пользователя
|
/// Получить username пользователя
|
||||||
pub fn get_username(&mut self, user_id: &i64) -> Option<&String> {
|
pub fn get_username(&mut self, user_id: &UserId) -> Option<&String> {
|
||||||
self.user_usernames.get(user_id)
|
self.user_usernames.get(user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получить имя пользователя
|
/// Получить имя пользователя
|
||||||
pub fn get_name(&mut self, user_id: &i64) -> Option<&String> {
|
pub fn get_name(&mut self, user_id: &UserId) -> Option<&String> {
|
||||||
self.user_names.get(user_id)
|
self.user_names.get(user_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получить user_id по chat_id
|
/// Получить user_id по chat_id
|
||||||
pub fn get_user_id_by_chat(&self, chat_id: i64) -> Option<i64> {
|
pub fn get_user_id_by_chat(&self, chat_id: ChatId) -> Option<UserId> {
|
||||||
self.chat_user_ids.get(&chat_id).copied()
|
self.chat_user_ids.get(&chat_id).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получить статус пользователя по chat_id
|
/// Получить статус пользователя по chat_id
|
||||||
pub fn get_status_by_chat_id(&self, chat_id: i64) -> Option<&UserOnlineStatus> {
|
pub fn get_status_by_chat_id(&self, chat_id: ChatId) -> Option<&UserOnlineStatus> {
|
||||||
let user_id = self.chat_user_ids.get(&chat_id)?;
|
let user_id = self.chat_user_ids.get(&chat_id)?;
|
||||||
self.user_statuses.peek(user_id)
|
self.user_statuses.peek(user_id)
|
||||||
}
|
}
|
||||||
@@ -126,20 +127,20 @@ impl UserCache {
|
|||||||
|
|
||||||
// Сохраняем username
|
// Сохраняем username
|
||||||
if let Some(username) = user.usernames.as_ref().map(|u| u.editable_username.clone()) {
|
if let Some(username) = user.usernames.as_ref().map(|u| u.editable_username.clone()) {
|
||||||
self.user_usernames.insert(user_id, username);
|
self.user_usernames.insert(UserId::new(user_id), username);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сохраняем имя
|
// Сохраняем имя
|
||||||
let display_name = format!("{} {}", user.first_name, user.last_name).trim().to_string();
|
let display_name = format!("{} {}", user.first_name, user.last_name).trim().to_string();
|
||||||
self.user_names.insert(user_id, display_name);
|
self.user_names.insert(UserId::new(user_id), display_name);
|
||||||
|
|
||||||
// Обновляем статус
|
// Обновляем статус
|
||||||
self.update_status(user_id, &user.status);
|
self.update_status(UserId::new(user_id), &user.status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Обработать обновление статуса пользователя
|
/// Обработать обновление статуса пользователя
|
||||||
pub fn update_status(&mut self, user_id: i64, status: &UserStatus) {
|
pub fn update_status(&mut self, user_id: UserId, status: &UserStatus) {
|
||||||
let online_status = match status {
|
let online_status = match status {
|
||||||
UserStatus::Online(_) => UserOnlineStatus::Online,
|
UserStatus::Online(_) => UserOnlineStatus::Online,
|
||||||
UserStatus::Recently(_) => UserOnlineStatus::Recently,
|
UserStatus::Recently(_) => UserOnlineStatus::Recently,
|
||||||
@@ -152,24 +153,24 @@ impl UserCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Сохранить связь chat_id -> user_id
|
/// Сохранить связь chat_id -> user_id
|
||||||
pub fn register_private_chat(&mut self, chat_id: i64, user_id: i64) {
|
pub fn register_private_chat(&mut self, chat_id: ChatId, user_id: UserId) {
|
||||||
self.chat_user_ids.insert(chat_id, user_id);
|
self.chat_user_ids.insert(chat_id, user_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Получить имя пользователя (асинхронно с загрузкой если нужно)
|
/// Получить имя пользователя (асинхронно с загрузкой если нужно)
|
||||||
pub async fn get_user_name(&self, user_id: i64) -> String {
|
pub async fn get_user_name(&self, user_id: UserId) -> String {
|
||||||
// Сначала пытаемся получить из кэша
|
// Сначала пытаемся получить из кэша
|
||||||
if let Some(name) = self.user_names.peek(&user_id) {
|
if let Some(name) = self.user_names.peek(&user_id) {
|
||||||
return name.clone();
|
return name.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загружаем пользователя
|
// Загружаем пользователя
|
||||||
match functions::get_user(user_id, self.client_id).await {
|
match functions::get_user(user_id.as_i64(), self.client_id).await {
|
||||||
Ok(User::User(user)) => {
|
Ok(User::User(user)) => {
|
||||||
let name = format!("{} {}", user.first_name, user.last_name).trim().to_string();
|
let name = format!("{} {}", user.first_name, user.last_name).trim().to_string();
|
||||||
name
|
name
|
||||||
}
|
}
|
||||||
_ => format!("User {}", user_id),
|
_ => format!("User {}", user_id.as_i64()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
170
src/types.rs
Normal file
170
src/types.rs
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
/// Type-safe ID wrappers to prevent mixing up different ID types
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// Chat identifier
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct ChatId(pub i64);
|
||||||
|
|
||||||
|
impl ChatId {
|
||||||
|
pub fn new(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_i64(&self) -> i64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for ChatId {
|
||||||
|
fn from(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ChatId> for i64 {
|
||||||
|
fn from(id: ChatId) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ChatId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Message identifier
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct MessageId(pub i64);
|
||||||
|
|
||||||
|
impl MessageId {
|
||||||
|
pub fn new(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_i64(&self) -> i64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for MessageId {
|
||||||
|
fn from(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<MessageId> for i64 {
|
||||||
|
fn from(id: MessageId) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for MessageId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// User identifier
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct UserId(pub i64);
|
||||||
|
|
||||||
|
impl UserId {
|
||||||
|
pub fn new(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_i64(&self) -> i64 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<i64> for UserId {
|
||||||
|
fn from(id: i64) -> Self {
|
||||||
|
Self(id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<UserId> for i64 {
|
||||||
|
fn from(id: UserId) -> Self {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for UserId {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_chat_id() {
|
||||||
|
let id = ChatId::new(123);
|
||||||
|
assert_eq!(id.as_i64(), 123);
|
||||||
|
assert_eq!(i64::from(id), 123);
|
||||||
|
|
||||||
|
let id2: ChatId = 456.into();
|
||||||
|
assert_eq!(id2.0, 456);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_message_id() {
|
||||||
|
let id = MessageId::new(789);
|
||||||
|
assert_eq!(id.as_i64(), 789);
|
||||||
|
assert_eq!(i64::from(id), 789);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_user_id() {
|
||||||
|
let id = UserId::new(111);
|
||||||
|
assert_eq!(id.as_i64(), 111);
|
||||||
|
assert_eq!(i64::from(id), 111);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_safety() {
|
||||||
|
// Type safety is enforced at compile time
|
||||||
|
// The following would not compile:
|
||||||
|
// let chat_id = ChatId::new(1);
|
||||||
|
// let message_id = MessageId::new(1);
|
||||||
|
// if chat_id == message_id { } // ERROR: mismatched types
|
||||||
|
|
||||||
|
// Runtime values can be the same, but types are different
|
||||||
|
let chat_id = ChatId::new(1);
|
||||||
|
let message_id = MessageId::new(1);
|
||||||
|
assert_eq!(chat_id.as_i64(), 1);
|
||||||
|
assert_eq!(message_id.as_i64(), 1);
|
||||||
|
// But they cannot be compared directly due to type safety
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_display() {
|
||||||
|
let chat_id = ChatId::new(123);
|
||||||
|
assert_eq!(format!("{}", chat_id), "123");
|
||||||
|
|
||||||
|
let message_id = MessageId::new(456);
|
||||||
|
assert_eq!(format!("{}", message_id), "456");
|
||||||
|
|
||||||
|
let user_id = UserId::new(789);
|
||||||
|
assert_eq!(format!("{}", user_id), "789");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_hash_map() {
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
let mut map = HashMap::new();
|
||||||
|
map.insert(ChatId::new(1), "chat1");
|
||||||
|
map.insert(ChatId::new(2), "chat2");
|
||||||
|
|
||||||
|
assert_eq!(map.get(&ChatId::new(1)), Some(&"chat1"));
|
||||||
|
assert_eq!(map.get(&ChatId::new(2)), Some(&"chat2"));
|
||||||
|
assert_eq!(map.get(&ChatId::new(3)), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ use tele_tui::app::{App, AppScreen, ChatState};
|
|||||||
use tele_tui::config::Config;
|
use tele_tui::config::Config;
|
||||||
use tele_tui::tdlib::AuthState;
|
use tele_tui::tdlib::AuthState;
|
||||||
use tele_tui::tdlib::{ChatInfo, MessageInfo};
|
use tele_tui::tdlib::{ChatInfo, MessageInfo};
|
||||||
|
use tele_tui::types::{ChatId, MessageId};
|
||||||
|
|
||||||
/// Builder для создания тестового App
|
/// Builder для создания тестового App
|
||||||
///
|
///
|
||||||
@@ -102,7 +103,7 @@ impl TestAppBuilder {
|
|||||||
/// Режим редактирования сообщения
|
/// Режим редактирования сообщения
|
||||||
pub fn editing_message(mut self, message_id: i64, selected_index: usize) -> Self {
|
pub fn editing_message(mut self, message_id: i64, selected_index: usize) -> Self {
|
||||||
self.chat_state = Some(ChatState::Editing {
|
self.chat_state = Some(ChatState::Editing {
|
||||||
message_id,
|
message_id: MessageId::new(message_id),
|
||||||
selected_index,
|
selected_index,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
@@ -110,14 +111,14 @@ impl TestAppBuilder {
|
|||||||
|
|
||||||
/// Режим ответа на сообщение
|
/// Режим ответа на сообщение
|
||||||
pub fn replying_to(mut self, message_id: i64) -> Self {
|
pub fn replying_to(mut self, message_id: i64) -> Self {
|
||||||
self.chat_state = Some(ChatState::Reply { message_id });
|
self.chat_state = Some(ChatState::Reply { message_id: MessageId::new(message_id) });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Режим выбора реакции
|
/// Режим выбора реакции
|
||||||
pub fn reaction_picker(mut self, message_id: i64, available_reactions: Vec<String>) -> Self {
|
pub fn reaction_picker(mut self, message_id: i64, available_reactions: Vec<String>) -> Self {
|
||||||
self.chat_state = Some(ChatState::ReactionPicker {
|
self.chat_state = Some(ChatState::ReactionPicker {
|
||||||
message_id,
|
message_id: MessageId::new(message_id),
|
||||||
available_reactions,
|
available_reactions,
|
||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
});
|
});
|
||||||
@@ -136,7 +137,7 @@ impl TestAppBuilder {
|
|||||||
|
|
||||||
/// Подтверждение удаления
|
/// Подтверждение удаления
|
||||||
pub fn delete_confirmation(mut self, message_id: i64) -> Self {
|
pub fn delete_confirmation(mut self, message_id: i64) -> Self {
|
||||||
self.chat_state = Some(ChatState::DeleteConfirmation { message_id });
|
self.chat_state = Some(ChatState::DeleteConfirmation { message_id: MessageId::new(message_id) });
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,7 +178,7 @@ impl TestAppBuilder {
|
|||||||
/// Режим пересылки сообщения
|
/// Режим пересылки сообщения
|
||||||
pub fn forward_mode(mut self, message_id: i64) -> Self {
|
pub fn forward_mode(mut self, message_id: i64) -> Self {
|
||||||
self.chat_state = Some(ChatState::Forward {
|
self.chat_state = Some(ChatState::Forward {
|
||||||
message_id,
|
message_id: MessageId::new(message_id),
|
||||||
selecting_chat: true,
|
selecting_chat: true,
|
||||||
});
|
});
|
||||||
self
|
self
|
||||||
@@ -223,7 +224,7 @@ impl TestAppBuilder {
|
|||||||
|
|
||||||
app.screen = self.screen;
|
app.screen = self.screen;
|
||||||
app.chats = self.chats;
|
app.chats = self.chats;
|
||||||
app.selected_chat_id = self.selected_chat_id;
|
app.selected_chat_id = self.selected_chat_id.map(ChatId::new);
|
||||||
app.message_input = self.message_input;
|
app.message_input = self.message_input;
|
||||||
app.is_searching = self.is_searching;
|
app.is_searching = self.is_searching;
|
||||||
app.search_query = self.search_query;
|
app.search_query = self.search_query;
|
||||||
@@ -264,7 +265,7 @@ impl TestAppBuilder {
|
|||||||
if let Some(chat_id) = self.selected_chat_id {
|
if let Some(chat_id) = self.selected_chat_id {
|
||||||
if let Some(messages) = self.messages.get(&chat_id) {
|
if let Some(messages) = self.messages.get(&chat_id) {
|
||||||
app.td_client.message_manager.current_chat_messages = messages.clone();
|
app.td_client.message_manager.current_chat_messages = messages.clone();
|
||||||
app.td_client.set_current_chat_id(Some(chat_id));
|
app.td_client.set_current_chat_id(Some(ChatId::new(chat_id)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Test data builders and fixtures
|
// Test data builders and fixtures
|
||||||
|
|
||||||
use tele_tui::tdlib::{ChatInfo, ForwardInfo, MessageInfo, ProfileInfo, ReactionInfo, ReplyInfo};
|
use tele_tui::tdlib::{ChatInfo, ForwardInfo, MessageInfo, ProfileInfo, ReactionInfo, ReplyInfo};
|
||||||
|
use tele_tui::types::{ChatId, MessageId};
|
||||||
|
|
||||||
/// Builder для создания тестового чата
|
/// Builder для создания тестового чата
|
||||||
pub struct TestChatBuilder {
|
pub struct TestChatBuilder {
|
||||||
@@ -80,7 +81,7 @@ impl TestChatBuilder {
|
|||||||
|
|
||||||
pub fn build(self) -> ChatInfo {
|
pub fn build(self) -> ChatInfo {
|
||||||
ChatInfo {
|
ChatInfo {
|
||||||
id: self.id,
|
id: ChatId::new(self.id),
|
||||||
title: self.title,
|
title: self.title,
|
||||||
username: self.username,
|
username: self.username,
|
||||||
last_message: self.last_message,
|
last_message: self.last_message,
|
||||||
@@ -89,7 +90,7 @@ impl TestChatBuilder {
|
|||||||
unread_mention_count: self.unread_mention_count,
|
unread_mention_count: self.unread_mention_count,
|
||||||
is_pinned: self.is_pinned,
|
is_pinned: self.is_pinned,
|
||||||
order: self.order,
|
order: self.order,
|
||||||
last_read_outbox_message_id: self.last_read_outbox_message_id,
|
last_read_outbox_message_id: MessageId::new(self.last_read_outbox_message_id),
|
||||||
folder_ids: self.folder_ids,
|
folder_ids: self.folder_ids,
|
||||||
is_muted: self.is_muted,
|
is_muted: self.is_muted,
|
||||||
draft_text: self.draft_text,
|
draft_text: self.draft_text,
|
||||||
@@ -165,7 +166,7 @@ impl TestMessageBuilder {
|
|||||||
|
|
||||||
pub fn reply_to(mut self, message_id: i64, sender: &str, text: &str) -> Self {
|
pub fn reply_to(mut self, message_id: i64, sender: &str, text: &str) -> Self {
|
||||||
self.reply_to = Some(ReplyInfo {
|
self.reply_to = Some(ReplyInfo {
|
||||||
message_id,
|
message_id: MessageId::new(message_id),
|
||||||
sender_name: sender.to_string(),
|
sender_name: sender.to_string(),
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
});
|
});
|
||||||
@@ -188,7 +189,7 @@ impl TestMessageBuilder {
|
|||||||
|
|
||||||
pub fn build(self) -> MessageInfo {
|
pub fn build(self) -> MessageInfo {
|
||||||
MessageInfo {
|
MessageInfo {
|
||||||
id: self.id,
|
id: MessageId::new(self.id),
|
||||||
sender_name: self.sender_name,
|
sender_name: self.sender_name,
|
||||||
is_outgoing: self.is_outgoing,
|
is_outgoing: self.is_outgoing,
|
||||||
content: self.content,
|
content: self.content,
|
||||||
@@ -223,7 +224,7 @@ pub fn create_test_user(name: &str, id: i64) -> (i64, String) {
|
|||||||
/// Хелпер для создания профиля
|
/// Хелпер для создания профиля
|
||||||
pub fn create_test_profile(title: &str, chat_id: i64) -> ProfileInfo {
|
pub fn create_test_profile(title: &str, chat_id: i64) -> ProfileInfo {
|
||||||
ProfileInfo {
|
ProfileInfo {
|
||||||
chat_id,
|
chat_id: ChatId::new(chat_id),
|
||||||
title: title.to_string(),
|
title: title.to_string(),
|
||||||
username: None,
|
username: None,
|
||||||
bio: None,
|
bio: None,
|
||||||
|
|||||||
Reference in New Issue
Block a user