From 1208aa5afbad04d396d42735d7788f37de01d827 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Sun, 17 May 2026 03:56:04 +0300 Subject: [PATCH] docs: clean up project markdown --- AGENT.md | 17 + CLAUDE.md | 29 -- CONTEXT.md | 359 +++------------------ DEVELOPMENT.md | 123 ++------ HOTKEYS.md | 194 ------------ PROJECT_STRUCTURE.md | 328 ------------------- README.md | 256 ++------------- REQUIREMENTS.md | 131 -------- ROADMAP.md | 168 ---------- docs/HOTKEYS.md | 74 +++++ docs/PROJECT_STRUCTURE.md | 60 ++++ docs/TDLIB_INTEGRATION.md | 647 ++------------------------------------ 12 files changed, 287 insertions(+), 2099 deletions(-) create mode 100644 AGENT.md delete mode 100644 CLAUDE.md delete mode 100644 HOTKEYS.md delete mode 100644 PROJECT_STRUCTURE.md delete mode 100644 REQUIREMENTS.md delete mode 100644 ROADMAP.md create mode 100644 docs/HOTKEYS.md create mode 100644 docs/PROJECT_STRUCTURE.md diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 0000000..302c087 --- /dev/null +++ b/AGENT.md @@ -0,0 +1,17 @@ +# tele-tui: правила для агента + +Проект: TUI-клиент Telegram на Rust. + +## Читать перед работой + +1. [DEVELOPMENT.md](DEVELOPMENT.md) - обязательные правила локальной работы. +2. [CONTEXT.md](CONTEXT.md) - текущий статус и риски. +3. [docs/PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md) - если нужна навигация по коду. +4. [docs/HOTKEYS.md](docs/HOTKEYS.md) - перед изменением ввода, режимов или keybindings. + +## Правила + +- Не запускай `cargo run`, `cargo build`, `cargo test`, `cargo check` без прямой команды пользователя. +- Не коммить изменения, пока пользователь не попросит. +- После функциональной правки дай короткий ручной сценарий проверки. +- Обновляй [CONTEXT.md](CONTEXT.md), только если изменились статус, риск, архитектурное решение или следующий шаг. diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index fb54683..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,29 +0,0 @@ -# Telegram TUI - -## Prompt - -Проект - TUI интерфейс для телеграмма - -Порядок чтения: -1) DEVELOPMENT.md - правило работы (обязательно) -2) CONTEXT.md - текущий статус -3) ROADMAP.md - план и задачи -4) REQUIREMENTS.md / ARCHITECTURE.md - по необходимости -5) E2E_TESTS.md - перед написанием тестов - -После работы обнови CONTEXT.md файл - -После прочтения скажи "Жду инструкций" ---- - -## Важные файлы - -- [DEVELOPMENT.md](DEVELOPMENT.md) — **читай первым!** Правила локальной разработки -- [CONTEXT.md](CONTEXT.md) — текущий статус, что сделано -- [ROADMAP.md](ROADMAP.md) — план разработки, задачи по фазам -- [REQUIREMENTS.md](REQUIREMENTS.md) — требования к продукту -- [ARCHITECTURE.md](ARCHITECTURE.md) — C4, sequence diagrams, API контракты, UI прототипы -- [E2E_TESTING.md](E2E_TESTING.md) — **читай перед написанием тестов!** Гайд по e2e тестированию - - - diff --git a/CONTEXT.md b/CONTEXT.md index b491f05..b3e2d49 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -1,311 +1,48 @@ -# Текущий контекст проекта - -## Статус: Фаза 14 — Мультиаккаунт (IN PROGRESS) - -### Per-Account Lock File Protection — DONE - -Защита от запуска двух экземпляров tele-tui с одним аккаунтом + логирование ошибок TDLib. - -**Проблема**: При запуске второго экземпляра с тем же аккаунтом, TDLib не может залочить свою БД. `set_tdlib_parameters` молча падает (`let _ = ...`), и приложение зависает на "Инициализация TDLib...". - -**Решение**: Advisory file locks через `fs2` (flock): -- **Lock файл**: `~/.local/share/tele-tui/accounts/{name}/tele-tui.lock` -- **Автоматическое освобождение** при crash/SIGKILL (ядро ОС закрывает file descriptors) -- **При старте**: acquire lock ДО `enable_raw_mode()` → ошибка выводится в обычный терминал -- **При переключении аккаунтов**: acquire new → release old → switch (при ошибке — остаёмся на старом) -- **Логирование**: `set_tdlib_parameters` ошибки теперь логируются через `tracing::error!` - -**Новые файлы:** -- `src/accounts/lock.rs` — `acquire_lock()`, `release_lock()`, `account_lock_path()` + 4 теста - -**Модифицированные файлы:** -- `Cargo.toml` — зависимость `fs2 = "0.4"` -- `src/accounts/mod.rs` — `pub mod lock;` + re-exports -- `src/app/mod.rs` — поле `account_lock: Option` в `App` -- `src/main.rs` — acquire lock при старте, lock при переключении аккаунтов, логирование set_tdlib_parameters -- `src/tdlib/client.rs` — логирование set_tdlib_parameters в `recreate_client()` - ---- - -### Photo Albums (Media Groups) — DONE - -Фото-альбомы (несколько фото в одном сообщении) теперь группируются в один пузырь с сеткой фото. - -**Проблема**: TDLib отправляет альбомы как отдельные `Message` с общим `media_album_id: i64`. Ранее проект это поле игнорировал — каждое фото отображалось как отдельный пузырь. - -**Решение:** - -1. **Data Model** — `media_album_id: i64` в `MessageMetadata`, `MessageBuilder`, getter `MessageInfo::media_album_id()`. Оба конвертера (async + sync) передают поле из TDLib. - -2. **Message Grouping** — новый вариант `MessageGroup::Album(Vec)`. Сообщения с одинаковым `media_album_id != 0` группируются; одиночное сообщение с album_id остаётся `Message`. - -3. **Album Grid Constants** — `ALBUM_PHOTO_WIDTH: 16`, `ALBUM_PHOTO_HEIGHT: 8`, `ALBUM_PHOTO_GAP: 1`, `ALBUM_GRID_MAX_COLS: 3` (3×16 + 2×1 = 50 = `INLINE_IMAGE_MAX_WIDTH`). - -4. **`render_album_bubble()`** — сетка фото (до 3 в ряд), `DeferredImageRender` с `x_offset` для каждого фото, общая подпись и timestamp, индикация выбора, статусы загрузки. - -5. **Integration** — `Album` arm в `render_message_list`, `x_offset` в second pass. Без feature `images` — fallback через отдельные bubble. - -**Модифицированные файлы:** -- `src/tdlib/types.rs` — `media_album_id` в `MessageMetadata`, `MessageBuilder`, getter -- `src/tdlib/messages/convert.rs` — передача `media_album_id` в builder -- `src/tdlib/message_converter.rs` — передача `media_album_id` в builder -- `src/message_grouping.rs` — `Album` variant + album detection + 4 новых теста -- `src/constants.rs` — album grid constants -- `src/ui/components/message_bubble.rs` — `x_offset` в `DeferredImageRender`, `render_album_bubble()` -- `src/ui/components/mod.rs` — export `render_album_bubble` -- `src/ui/messages.rs` — `Album` arm + `x_offset` в second pass - -6. **Навигация j/k по альбомам** — альбом обрабатывается как одно сообщение. `select_previous_message()` / `select_next_message()` перескакивают через все сообщения альбома. `start_message_selection()` встаёт на первый элемент альбома если последнее сообщение — часть альбома. - -7. **Тесты** — 4 unit-теста в `message_grouping.rs`, 5 snapshot-тестов в `tests/messages.rs`, 3 теста навигации в `tests/input_navigation.rs`. - -**Дополнительно модифицированные файлы:** -- `src/app/methods/messages.rs` — навигация перескакивает альбомы -- `tests/helpers/test_data.rs` — `TestMessageBuilder::media_album_id()` -- `tests/messages.rs` — 5 snapshot-тестов для альбомов -- `tests/input_navigation.rs` — 3 теста навигации по альбомам - -**Что НЕ меняется:** image modal (v), auto-download, одиночные фото. - ---- - -### Оптимизация: Ленивая загрузка сообщений при открытии чата (DONE) - -Чат открывается мгновенно (< 1 сек) вместо 5-30 сек для больших чатов. - -**Проблема**: `open_chat_and_load_data()` блокировал UI до полной загрузки ВСЕХ сообщений (`get_chat_history(chat_id, i32::MAX)`). Для чата с 500+ сообщениями это 10+ запросов к TDLib. - -**Решение**: -- Загрузка только 50 последних сообщений (один запрос) → чат виден сразу -- Фоновые задачи (reply info, pinned, photos) — на следующем тике main loop через `pending_chat_init` -- Старые сообщения подгружаются при скролле вверх (существующий `load_older_messages_if_needed`) - -**Модифицированные файлы:** -- `src/app/mod.rs` — поле `pending_chat_init: Option` -- `src/input/handlers/chat_list.rs` — `open_chat_and_load_data()`: 50 сообщений + `pending_chat_init` -- `src/main.rs` — обработка `pending_chat_init` в main loop (reply info, pinned, photos) -- `src/app/methods/navigation.rs` — сброс `pending_chat_init` в `close_chat()` - ---- - -### Bugfix: Авто-загрузка фото в чате (DONE) - -Фото не отображались — отсутствовал код загрузки файлов после открытия чата. - -**Проблема**: `extract_media_info()` создавал `PhotoInfo` с `PhotoDownloadState::NotDownloaded`, но никакой код не инициировал `download_file()`. Фото оставались в состоянии "📷 [Фото]" без inline превью. - -**Исправление:** -- **Авто-загрузка при открытии чата**: после загрузки истории сообщений скачиваются фото из последних 30 сообщений (если `auto_download_images = true` и `show_images = true`). Каждый файл — с таймаутом 5 сек. -- **Загрузка по `v`**: вместо "Фото не загружено" — скачивание + открытие модалки. Также повторная попытка при `Error`. -- Обновление `PhotoDownloadState` в сообщении после успешной/неуспешной загрузки. - -**Модифицированные файлы:** -- `src/input/handlers/chat_list.rs` — авто-загрузка фото в `open_chat_and_load_data()` -- `src/input/handlers/chat.rs` — `handle_view_image()`: download on NotDownloaded + retry on Error - ---- - -### Этап 2+3: Account Switcher Modal + Переключение аккаунтов (DONE) - -Реализована модалка переключения аккаунтов и механизм переключения: - -- **Модалка `Ctrl+A`**: глобальный оверлей поверх любого экрана (Loading/Auth/Main) -- **Навигация**: `j/k` по списку, `Enter` выбор, `a` добавление, `Esc` закрытие -- **Переключение**: close TDLib → `recreate_client(new_db_path)` → auth flow -- **Добавление аккаунта**: ввод имени в модалке → валидация → `add_account()` → переключение -- **Footer индикатор**: `[account_name]` если не "default" -- **`AccountSwitcherState`**: enum `SelectAccount` / `AddAccount` — глобальный оверлей в `App` -- **`recreate_client()`**: новый метод в `TdClientTrait` — close old → new TdClient → spawn set_tdlib_parameters - -**Новые файлы:** -- `src/ui/modals/account_switcher.rs` — UI рендеринг (SelectAccount + AddAccount) -- `tests/account_switcher.rs` — 12 тестов - -**Модифицированные файлы:** -- `src/app/mod.rs` — `AccountSwitcherState` enum, 3 поля (`account_switcher`, `current_account_name`, `pending_account_switch`), 8 методов -- `src/accounts/manager.rs` — `add_account()` (validate + save + ensure_dir) -- `src/accounts/mod.rs` — re-export `add_account` -- `src/tdlib/trait.rs` — `recreate_client(&mut self, db_path)` в TdClientTrait -- `src/tdlib/client.rs` — реализация `recreate_client` (close → new → set_tdlib_parameters) -- `src/tdlib/client_impl.rs` — trait impl делегирование -- `tests/helpers/fake_tdclient_impl.rs` — no-op `recreate_client` -- `src/input/main_input.rs` — account_switcher роутинг (highest priority) -- `src/input/handlers/global.rs` — `Ctrl+A` → open_account_switcher -- `src/input/handlers/modal.rs` — `handle_account_switcher()` (SelectAccount + AddAccount input) -- `src/ui/modals/mod.rs` — `pub mod account_switcher;` -- `src/ui/mod.rs` — overlay поверх любого экрана -- `src/ui/footer.rs` — `[account_name]` индикатор -- `src/main.rs` — `pending_account_switch` check в run_app, `Ctrl+A` из любого экрана - -### Этап 1: Инфраструктура профилей аккаунтов (DONE) - -Реализована инфраструктура для мультиаккаунта: - -- **Модуль `accounts/`**: `profile.rs` (типы + валидация) + `manager.rs` (загрузка/сохранение/миграция) -- **`accounts.toml`**: конфиг списка аккаунтов в `~/.config/tele-tui/accounts.toml` -- **XDG data dir**: БД TDLib хранится в `~/.local/share/tele-tui/accounts/{name}/tdlib_data/` -- **Автомиграция**: `./tdlib_data/` → XDG path при первом запуске -- **CLI флаг `--account `**: выбор аккаунта при запуске -- **Параметризация `db_path`**: `TdClient::new(db_path)`, `App::new(config, db_path)` - ---- - -## Предыдущий статус: Multiline Message Display (DONE) - -### Multiline в сообщениях - -- **Multiline в сообщениях**: `\n` корректно отображается в пузырях сообщений (split по `\n` + word wrap) -- **Маркер выделения**: ▶ показывается только на первой строке multiline-сообщения -- Перенос строки в инпуте отключён (Shift+Enter/Alt+Enter/Ctrl+J не вставляют `\n`) - -**Файлы изменены:** -- `ui/components/message_bubble.rs` — `wrap_text_with_offsets()` split по `\n` + `wrap_paragraph()` + selection marker fix - ---- - -### Vim Normal/Insert Mode (DONE) - -Реализован Vim-like режим работы с двумя состояниями: - -- **Normal mode** (по умолчанию при открытии чата): навигация j/k, команды d/r/f/y, автоматический вход в MessageSelection -- **Insert mode** (нажать `i`/`ш`): набор текста, Esc возвращает в Normal -- Автопереключение в Insert при Reply (`r`) и Edit (`Enter`) -- Визуальные индикаторы: `[NORMAL]`/`[INSERT]` в footer, зелёная/серая рамка compose bar -- В Insert mode блокируются все команды кроме текстового ввода и Esc - -**Файлы изменены:** -- `app/chat_state.rs` — enum `InputMode` -- `app/mod.rs` — поле `input_mode` в `App` -- `config/keybindings.rs` — `Command::EnterInsertMode` + keybinding `i`/`ш` -- `app/methods/navigation.rs` — `close_chat()` сбрасывает input_mode -- `input/main_input.rs` — главный роутер Insert/Normal -- `input/handlers/chat.rs` — EnterInsertMode, auto-Insert при Reply/Edit -- `input/handlers/chat_list.rs` — auto-MessageSelection при открытии чата -- `ui/footer.rs` — mode indicator -- `ui/compose_bar.rs` — visual mode differentiation -- `tests/` — обновлены для нового поведения - ---- - -## Предыдущий статус: Фаза 12 — Прослушивание голосовых сообщений (DONE) - -### Завершённые фазы (краткий итог) - -| Фаза | Описание | Статус | -|------|----------|--------| -| 1 | Базовая инфраструктура (ratatui + crossterm, vim-навигация) | DONE | -| 2 | TDLib интеграция (авторизация, чаты, сообщения) | DONE | -| 3 | Улучшение UX (отправка, поиск, скролл, realtime) | DONE | -| 4 | Папки и фильтрация (загрузка папок, переключение 1-9) | DONE | -| 5 | Расширенный функционал (онлайн-статус, медиа-заглушки, muted) | DONE | -| 6 | Полировка (60 FPS, память, graceful shutdown, динамический инпут) | DONE | -| 7 | Рефакторинг памяти (единый источник данных, LRU-кэш) | DONE | -| 8 | Дополнительные фичи (markdown, edit/delete, reply/forward, блочный курсор) | DONE | -| 9 | Расширенные возможности (typing, pinned, поиск в чате, черновики, профиль, копирование, реакции, конфиг) | DONE | -| 10 | Desktop уведомления (notify-rust, muted фильтр, mentions, медиа) | DONE (83%) | -| 11 | Inline просмотр фото (ratatui-image, кэш, загрузка) | DONE | -| 12 | Прослушивание голосовых сообщений (ffplay, play/pause, seek, ticker, cache, config) | DONE | -| 13 | Глубокий рефакторинг архитектуры (7 этапов) | DONE | - -### Фаза 11: Inline фото + оптимизации (подробности) - -Feature-gated (`images`), 2-tier архитектура: - -**Базовая реализация:** -1. **Типы**: `MediaInfo`, `PhotoInfo`, `PhotoDownloadState`, `ImageModalState`, `ImagesConfig` -2. **Зависимости**: `ratatui-image 8.1`, `image 0.25` (feature-gated) -3. **Media модуль**: `ImageCache` (LRU), dual `ImageRenderer` (inline + modal) -4. **UX**: Always-show inline preview (фикс. ширина 50 chars) + полноэкранная модалка на `v`/`м` -5. **Метаданные**: `extract_media_info()` из TDLib MessagePhoto; auto-download visible photos - -**Оптимизации производительности:** -1. **Dual protocol strategy**: - - `inline_image_renderer`: Halfblocks → быстро (Unicode блоки), для навигации - - `modal_image_renderer`: iTerm2/Sixel → медленно (high quality), для просмотра -2. **Frame throttling**: inline images 15 FPS (66ms), текст 60 FPS -3. **Lazy loading**: загрузка только видимых изображений (не все сразу) -4. **LRU кэш**: max 100 протоколов, eviction старых -5. **Loading indicator**: "⏳ Загрузка..." в модалке при первом открытии -6. **Navigation hotkeys**: `←`/`→` между фото, `Esc`/`q` закрыть модалку - -**UI рендеринг**: -- `message_bubble.rs`: photo status (Downloading/Error/placeholder), inline preview -- `messages.rs`: второй проход с `render_images()` + throttling + только видимые -- `modals/image_viewer.rs`: fullscreen modal с aspect ratio + loading state - -### Фаза 13: Рефакторинг (подробности) - -Разбиты 5 монолитных файлов (4582 строк) на модульную архитектуру: - -- **input/main_input.rs** (1199→164): чистый роутер + 5 handler модулей в `handlers/` -- **app/mod.rs** (1015→371): 5 trait модулей в `methods/` (Navigation, Message, Compose, Search, Modal) -- **ui/messages.rs** (893→365): модули `modals/` (search, pinned, delete, reactions) + `compose_bar.rs` -- **tdlib/messages.rs** (836→3 файла): `messages/` (mod, convert, operations) -- **config/mod.rs** (642→3 файла): validation.rs, loader.rs -- **Очистка дублей**: ~220 строк удалено (shared components, format_user_status, scroll_to_message) -- **Документация**: PROJECT_STRUCTURE.md переписан, 16 файлов получили `//!` docs - -### Фаза 12: Голосовые сообщения (подробности) - -**Реализовано:** -- **AudioPlayer** на ffplay (subprocess): play, pause (SIGSTOP), resume (SIGCONT), stop -- **VoiceCache**: LRU кэш OGG файлов в `~/.cache/tele-tui/voice/` (max 100 MB) -- **Типы**: `VoiceInfo`, `VoiceDownloadState`, `PlaybackState`, `PlaybackStatus` -- **TDLib интеграция**: `download_voice_note()`, конвертация `MessageVoiceNote` -- **Хоткеи**: Space (play/pause), ←/→ (seek ±5s via ffplay restart с `-ss`) -- **Автостоп**: при навигации на другое сообщение воспроизведение останавливается -- **Ticker**: `last_playback_tick` в App + обновление position в event loop (1 FPS redraw) -- **VoiceCache**: проверка кэша перед загрузкой, кэширование после download -- **AudioConfig**: `[audio]` секция в config.toml (cache_size_mb, auto_download_voice) -- **UI**: progress bar (━●─) + waveform (▁▂▃▄▅▆▇█) + иконки статуса в `message_bubble.rs` -- **Race condition fixes**: `starting` flag + pid ownership guard в потоках AudioPlayer -- **Seek**: `resume_from()` перезапускает ffplay с `-ss` offset; MoveLeft/MoveRight как alias для SeekBackward/SeekForward -- **Resume with rewind**: пауза→продолжение откатывает на 1 секунду назад -- **Graceful shutdown**: `stop_playback()` + Drop impl для AudioPlayer - -### Ключевая архитектура - -``` -main.rs → event loop (16ms poll) -├── input/ → роутер + handlers/ (chat, chat_list, compose, modal, search) -├── app/ → App + methods/ (5 traits, 67 методов) -├── ui/ → рендеринг (messages, chat_list, modals/, compose_bar, components/) -├── audio/ → player.rs (ffplay), cache.rs (VoiceCache) -├── media/ → [feature=images] cache.rs, image_renderer.rs -└── tdlib/ → TDLib wrapper (client, auth, chats, messages/, users, reactions, types) -``` - -### Тестирование - -500+ тестов (0 failures): -- Snapshot tests: 57 (UI компоненты) -- Integration tests: 93 (user flows) -- E2E tests: 12 (smoke + journey) -- Utils tests: 18 -- Performance benchmarks: 8 - -### Ключевые решения - -1. **Неблокирующий receive**: TDLib updates через `mpsc::channel` в отдельном потоке -2. **Trait-based App**: методы разбиты на traits — требуют `use` import на call site -3. **FakeTdClient**: mock для тестов без TDLib (реализует TdClientTrait) -4. **Оптимизация рендеринга**: `needs_redraw` флаг, рендеринг только при изменениях -5. **Конфиг**: TOML `~/.config/tele-tui/config.toml`, credentials с приоритетом (XDG → .env) -6. **Feature-gated images**: `images` feature flag для ratatui-image + image deps -7. **Dual renderer**: inline (Halfblocks, 15 FPS) + modal (iTerm2/Sixel, high quality) для баланса скорости/качества -8. **Audio via ffplay**: subprocess с SIGSTOP/SIGCONT для pause/resume, автостоп при навигации - -### Зависимости (основные) - -```toml -ratatui = "0.29" # TUI фреймворк -crossterm = "0.28" # Терминальный backend -tdlib-rs = "1.1" # Telegram TDLib binding -tokio = "1" # Async runtime -notify-rust = "4.11" # Desktop уведомления (feature flag) -ratatui-image = "8.1" # Inline images (feature flag) -image = "0.25" # Image decoding (feature flag) -``` - -Полная структура проекта: см. [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) -Подробный план: см. [ROADMAP.md](ROADMAP.md) +# Контекст проекта + +## Текущий статус + +Фаза 14: мультиаккаунт, в работе. + +Цель фазы: безопасные профили Telegram-аккаунтов, переключение аккаунтов и подготовка к более быстрому multi-client UX. + +## Уже сделано + +- Профили аккаунтов: `AccountProfile`, `accounts.toml`, XDG data paths. +- Миграция старого `tdlib_data/` в per-account directory. +- CLI `--account `. +- Account switcher modal по `Ctrl+A`: выбор и добавление аккаунта. +- Single-client переключение через `recreate_client()`. +- Footer показывает имя аккаунта, если он не `default`. +- Per-account lock file защищает TDLib database от двух процессов. +- TDLib receive loop передаёт `client_id`; UI применяет updates только активного клиента. +- `pending_chat_init` не должен блокировать первый redraw; reply-info и photo downloads уходят в фоновые tasks. +- Keybindings стали детерминированными; русская vim-раскладка: `h/j/k/l` -> `р/о/л/д`. +- `AudioPlayer` проверяет наличие `ffplay`. +- `message_grouping` группирует альбомы без клонирования сообщений. + +## Осталось + +- Быстрые hotkeys `Ctrl+1`..`Ctrl+9` для аккаунтов без модалки. +- Удаление/переименование аккаунта в account switcher. +- Бейджи непрочитанных с других аккаунтов. +- Решить, нужен ли переход от single-client reinit к одновременным клиентам. +- Добавить/уточнить tests для accounts + TDLib update routing. + +## Риски + +- Multi-account код должен фильтровать TDLib updates по `client_id`. +- Инициализация чата и фоновые downloads не должны блокировать первый redraw. +- Read/unread статус исходящих сообщений зависит от корректной TDLib metadata. +- Конфликтующие keybindings должны оставаться детерминированными. +- Переключение аккаунтов требует проверки lock release/acquire и auth flow. + +## Ключевые решения + +- Главный state хранится в `App`, чтобы тесты могли использовать `FakeTdClient`. +- Методы `App` разбиты на traits: navigation, messages, compose, search, modal. +- UI рендерится только при `needs_redraw`; текстовый интерфейс целится в 60 FPS. +- Фото под feature `images`: inline Halfblocks + modal iTerm2/Sixel. +- Голосовые сообщения проигрываются через `ffplay`, cache живёт в `~/.cache/tele-tui/voice/`. +- Credentials читаются из `~/.config/tele-tui/credentials`, fallback `.env`. +- TDLib data аккаунтов хранится в `~/.local/share/tele-tui/accounts/{name}/tdlib_data/`. diff --git a/DEVELOPMENT.md b/DEVELOPMENT.md index a224077..8aa3e35 100644 --- a/DEVELOPMENT.md +++ b/DEVELOPMENT.md @@ -1,110 +1,43 @@ -# Правила локальной разработки - -> **Обязательно к прочтению перед началом работы!** - ---- +# Разработка ## Инструменты -### MCP серверы -- **Serena** — для работы с кодом (символьная навигация, редактирование) -- **Context7** — для получения актуальной документации по библиотекам +- Для поиска используй `rg`. +- Для точечной навигации по Rust-коду можно использовать Serena. +- Для актуальной документации библиотек используй Context7, если это нужно для изменения. -Используй эти инструменты для эффективной работы с кодовой базой. +## Cargo-команды ---- +Агентам нельзя самостоятельно запускать: -## Правила работы - -### 1. Никогда не запускай сервисы самостоятельно - -**ЗАПРЕЩЕНО** запускать `cargo run`, `cargo build` и подобные команды. - -**Вместо этого попроси пользователя запустить:** - -``` -Запусти, пожалуйста: +```bash cargo run +cargo build +cargo test +cargo check ``` -### 2. Тестирование — только ручное +Исключение: пользователь прямо попросил запустить конкретную cargo-команду. -После завершения задачи: -1. Опиши сценарии для проверки -2. Попроси пользователя проверить вручную -3. Дождись фидбека +В финальном ответе после изменения укажи, какие cargo-команды не запускались, и дай минимальную ручную проверку. -**Формат:** -``` -Готово! Проверь, пожалуйста: +## Scope -1. Открой cargo run -2. понавигируйся в списке чатов кнопками h j k l -3. Нажми Enter для открытия чата -4. Убедись, что чат прогурзился +- Делай одну логическую правку за раз. +- Не смешивай feature work, рефакторинг и документацию без необходимости. +- Не откатывай чужие изменения в dirty worktree. +- Не используй destructive git-команды без явной просьбы. -Напиши, если что-то не работает. +## Ручная проверка + +Формат после функциональной правки: + +```text +Проверь: +1. Запусти ... +2. Открой ... +3. Выполни ... +4. Ожидаемый результат: ... ``` -### 3. Работа поэтапно - -Делай работу **небольшими итерациями**: - -1. **Один этап = одна логическая единица** - - Один endpoint - - Один компонент - - Одна фича - -2. **После каждого этапа:** - - Сообщи что сделано - - Дай сценарий проверки - - Дождись подтверждения - -3. **Не делай сразу много:** - - ❌ Весь CRUD за раз - - ✅ Сначала GET, проверили, потом POST, проверили... - ---- - -### 4. Работа с git - -НИКОГДА НЕ КОММИТЬ ИЗМЕНЕНИЯ ПОКА ТЕБЯ НЕ ПОПРОСЯТ!!! - - -## Чеклист перед началом работы - -- [ ] Прочитал CONTEXT.md -- [ ] Прочитал ROADMAP.md (понял текущую фазу) -- [ ] Понял задачу -- [ ] Готов работать поэтапно -- [ ] Помню: не запускаю сервисы сам, прошу пользователя - ---- - -## Пример правильного workflow - -``` -Пользователь: Сделай endpoint для получения списка идей - -Агент: -1. Читает документацию через Context7 (NestJS, TypeORM) -2. Использует Serena для навигации по коду -3. Создаёт endpoint GET /api/ideas -4. Сообщает: - - "Создал endpoint GET /api/ideas. - - Запусти backend: - cd backend && npm run dev - - Проверь: - curl http://localhost:4001/api/ideas - - Должен вернуться пустой массив: { data: [], meta: {...} }" - -5. Ждёт фидбек -6. Переходит к следующему этапу -``` - -## Работа с git -- никогда не добавляй себя в соавторов в тексте коммита \ No newline at end of file +Для чисто документационных задач cargo-проверки не требуются. diff --git a/HOTKEYS.md b/HOTKEYS.md deleted file mode 100644 index 1f640d3..0000000 --- a/HOTKEYS.md +++ /dev/null @@ -1,194 +0,0 @@ -# Горячие клавиши tele-tui - -## Общая навигация - -| Клавиша | Русская раскладка | Действие | -|---------|-------------------|----------| -| `↑` / `k` | `р` | Вверх по списку | -| `↓` / `j` | `о` | Вниз по списку | -| `Enter` | | Открыть чат / Отправить сообщение | -| `Esc` | | Закрыть чат / Отменить действие | -| `Ctrl+C` | | Выход из приложения | -| `Ctrl+R` | | Обновить список чатов | - -## Папки и поиск - -| Клавиша | Действие | -|---------|----------| -| `1-9` | Переключение между папками Telegram | -| `Ctrl+S` | Открыть поиск по чатам | -| `Ctrl+F` | Открыть поиск в текущем чате | -| `n` | Следующий результат поиска | -| `N` | Предыдущий результат поиска | - -## Работа с сообщениями - -### Навигация и выбор - -| Клавиша | Действие | -|---------|----------| -| `↑/↓` | Скролл сообщений (в открытом чате) | -| `↑` | Выбор сообщения (при пустом поле ввода) | -| `Esc` | Отменить выбор | - -### Действия с сообщениями - -| Клавиша | Русская раскладка | Действие | -|---------|-------------------|----------| -| `Enter` | | Редактировать выбранное сообщение | -| `r` | `к` | Ответить на сообщение (Reply) | -| `f` | `а` | Переслать сообщение (Forward) | -| `d` / `Delete` | `в` | Удалить сообщение | -| `y` | `н` | Копировать текст в буфер обмена | -| `e` | `у` | Добавить реакцию (Emoji picker) | -| `v` | `м` | Открыть изображение в полном размере | -| `Ctrl+i` | `Ctrl+ш` | Открыть профиль чата/пользователя | - -## Просмотр изображений - -### Режим просмотра изображения - -| Клавиша | Действие | -|---------|----------| -| `v` / `м` | Открыть изображение (в режиме выбора) | -| `←` | Предыдущее изображение в чате | -| `→` | Следующее изображение в чате | -| `Esc` | Закрыть просмотр изображения | - -**Примечание**: Изображения отображаются inline в чате автоматически. Используйте `v` для просмотра в полном размере. - -## Прослушивание голосовых сообщений - -### Управление воспроизведением - -| Клавиша | Русская раскладка | Действие | -|---------|-------------------|----------| -| `Space` | | Воспроизвести/Пауза (в режиме выбора голосового) | -| `s` | `ы` | Остановить воспроизведение | - -### Во время воспроизведения - -| Клавиша | Действие | -|---------|----------| -| `Space` | Пауза / Возобновить | -| `s` / `ы` | Остановить | -| `←` | Перемотка назад (по умолчанию -5 сек) | -| `→` | Перемотка вперед (по умолчанию +5 сек) | -| `↑` | Увеличить громкость (+10%) | -| `↓` | Уменьшить громкость (-10%) | - -**Примечание**: Голосовые сообщения показывают progress bar во время воспроизведения: `▶ ████████░░░░░░ 0:08 / 0:15` - -## Модалки подтверждения - -### Удаление сообщения - -| Клавиша | Русская раскладка | Действие | -|---------|-------------------|----------| -| `y` / `Enter` | `н` | Подтвердить удаление | -| `n` / `Esc` | `т` | Отменить удаление | - -## Emoji Picker (реакции) - -| Клавиша | Действие | -|---------|----------| -| `←` | Влево по сетке эмодзи | -| `→` | Вправо по сетке эмодзи | -| `↑` | Вверх по сетке эмодзи | -| `↓` | Вниз по сетке эмодзи | -| `Enter` | Добавить/удалить реакцию | -| `Esc` | Закрыть emoji picker | - -## Редактирование текста - -### Навигация по тексту - -| Клавиша | Действие | -|---------|----------| -| `←` | Курсор влево | -| `→` | Курсор вправо | -| `Home` | Курсор в начало строки | -| `End` | Курсор в конец строки | - -### Редактирование - -| Клавиша | Действие | -|---------|----------| -| `Backspace` | Удалить символ слева от курсора | -| `Delete` | Удалить символ справа от курсора | -| `Enter` | Новая строка / Отправить (зависит от контекста) | - -## Режимы работы - -### Режим списка чатов -- Навигация: `↑/↓` -- Открыть чат: `Enter` -- Поиск: `Ctrl+S` -- Папки: `1-9` - -### Режим открытого чата -- Скролл: `↑/↓` -- Выбор сообщения: `↑` (при пустом инпуте) -- Поиск в чате: `Ctrl+F` -- Закрыть чат: `Esc` - -### Режим выбора сообщения -- Редактировать: `Enter` -- Ответить: `r` / `к` -- Переслать: `f` / `а` -- Удалить: `d` / `в` / `Delete` -- Копировать: `y` / `н` -- Реакция: `e` / `у` -- Просмотр изображения: `v` / `м` (если выбрано сообщение с фото) -- Воспроизведение голосового: `Space` (если выбрано голосовое сообщение) -- Отменить: `Esc` - -### Режим редактирования -- Редактировать текст: см. "Редактирование текста" -- Отправить: `Enter` -- Отменить: `Esc` - -### Режим ответа (Reply) -- Редактировать ответ: см. "Редактирование текста" -- Отправить: `Enter` -- Отменить: `Esc` - -### Режим пересылки (Forward) -- Выбрать чат: `↑/↓` -- Переслать: `Enter` -- Отменить: `Esc` - -### Режим просмотра изображения -- Навигация: `←/→` (предыдущее/следующее изображение) -- Закрыть: `Esc` - -### Режим воспроизведения голосового -- Пауза/Возобновить: `Space` -- Остановить: `s` / `ы` -- Перемотка: `←/→` (-5с / +5с) -- Громкость: `↑/↓` (+/- 10%) - -## Поддержка русской раскладки - -Все основные vim-клавиши поддерживают русскую раскладку: - -| Английская | Русская | Действие | -|------------|---------|----------| -| `h` | `р` | Влево | -| `j` | `о` | Вниз | -| `k` | `л` | Вверх | -| `l` | `д` | Вправо | -| `r` | `к` | Reply | -| `f` | `а` | Forward | -| `d` | `в` | Delete | -| `y` | `н` | Copy (Yank) | -| `e` | `у` | Emoji reaction | -| `v` | `м` | View image | -| `s` | `ы` | Stop audio | - -## Подсказки - -- Текущие доступные команды всегда отображаются в нижней части экрана (footer) -- При открытой модалке доступны только действия этой модалки -- `Esc` всегда отменяет текущее действие и возвращает на шаг назад -- Блочный курсор █ показывает текущую позицию при редактировании текста diff --git a/PROJECT_STRUCTURE.md b/PROJECT_STRUCTURE.md deleted file mode 100644 index ec1fb6d..0000000 --- a/PROJECT_STRUCTURE.md +++ /dev/null @@ -1,328 +0,0 @@ -# Структура проекта - -## Архитектура (ASCII) - -``` - ┌─────────────┐ - │ main.rs │ Event loop (60 FPS) - └──────┬──────┘ - │ - ┌────────────┼────────────┐ - ▼ ▼ ▼ - ┌──────────┐ ┌──────────┐ ┌──────────┐ - │ input/ │ │ app/ │ │ ui/ │ - │ handlers │ │ state │ │ render │ - └────┬─────┘ └────┬─────┘ └────┬─────┘ - │ │ │ - │ ┌──────┴──────┐ │ - │ │ methods/ │ │ - │ │ (5 traits) │ │ - │ └──────┬──────┘ │ - │ │ │ - ▼ ▼ ▼ - ┌─────────────────────────────────┐ - │ tdlib/ │ - │ TdClientTrait → TdClient │ - │ messages/ | auth | chats │ - └──────────────┬──────────────────┘ - │ - ┌─────▼─────┐ - │ TDLib C │ - │ library │ - └───────────┘ -``` - -### Data Flow -``` -TDLib Updates → mpsc channel → App state → UI rendering -User Input → handlers → App methods (traits) → TdClient → TDLib API -``` - -## Обзор директорий - -``` -tele-tui/ -├── src/ -│ ├── main.rs # Точка входа, event loop -│ ├── lib.rs # Экспорт модулей для тестов -│ ├── types.rs # ChatId, MessageId (newtype wrappers) -│ ├── constants.rs # MAX_MESSAGES_IN_CHAT, etc. -│ ├── formatting.rs # Markdown entity форматирование -│ ├── message_grouping.rs # Группировка сообщений по дате/отправителю -│ ├── notifications.rs # Desktop уведомления (NotificationManager) -│ │ -│ ├── app/ # Состояние приложения -│ │ ├── mod.rs # App struct, конструкторы, getters (372 loc) -│ │ ├── state.rs # AppScreen enum -│ │ ├── chat_state.rs # ChatState enum (state machine) -│ │ ├── chat_filter.rs # ChatFilter, ChatFilterCriteria -│ │ ├── chat_list_state.rs # Состояние списка чатов -│ │ ├── auth_state.rs # Состояние авторизации -│ │ ├── compose_state.rs # Состояние compose bar -│ │ ├── ui_state.rs # UI-related state -│ │ ├── message_service.rs # Сервис сообщений -│ │ ├── message_view_state.rs # Состояние просмотра сообщений -│ │ └── methods/ # Trait-based методы App (Этап 2) -│ │ ├── mod.rs # Re-exports 5 trait модулей -│ │ ├── navigation.rs # NavigationMethods (7 методов) -│ │ ├── messages.rs # MessageMethods (8 методов) -│ │ ├── compose.rs # ComposeMethods (10 методов) -│ │ ├── search.rs # SearchMethods (15 методов) -│ │ └── modal.rs # ModalMethods (27 методов) -│ │ -│ ├── config/ # Конфигурация (Этап 5) -│ │ ├── mod.rs # Config struct, defaults (350 loc) -│ │ ├── keybindings.rs # Command enum, Keybindings -│ │ ├── validation.rs # validate(), parse_color() -│ │ └── loader.rs # load(), save(), credentials -│ │ -│ ├── input/ # Обработка пользовательского ввода -│ │ ├── mod.rs # Роутинг по экранам -│ │ ├── auth.rs # Ввод на экране авторизации -│ │ ├── main_input.rs # Роутер главного экрана (159 loc, Этап 1) -│ │ ├── key_handler.rs # Trait-based обработка клавиш -│ │ └── handlers/ # Специализированные обработчики (Этап 1) -│ │ ├── mod.rs # Exports + scroll_to_message() -│ │ ├── global.rs # Ctrl+R/S/P/F глобальные команды -│ │ ├── chat.rs # Открытый чат: ввод, скролл, selection -│ │ ├── chat_list.rs # Навигация по списку чатов, папки -│ │ ├── compose.rs # Forward mode -│ │ ├── modal.rs # Profile, reactions, pinned, delete -│ │ ├── search.rs # Поиск чатов и сообщений -│ │ ├── clipboard.rs # Копирование в буфер обмена -│ │ └── profile.rs # Хелперы профиля -│ │ -│ ├── tdlib/ # TDLib интеграция -│ │ ├── mod.rs # Экспорт публичных типов -│ │ ├── types.rs # MessageInfo, ChatInfo, ProfileInfo, etc. -│ │ ├── trait.rs # TdClientTrait (DI для тестов) -│ │ ├── client.rs # TdClient struct, конструктор -│ │ ├── client_impl.rs # impl TdClientTrait for TdClient -│ │ ├── auth.rs # Авторизация (phone, code, 2FA) -│ │ ├── chats.rs # Загрузка чатов, папок -│ │ ├── users.rs # Кеш пользователей, статусы -│ │ ├── reactions.rs # ReactionInfo, toggle_reaction -│ │ ├── chat_helpers.rs # Вспомогательные функции чатов -│ │ ├── update_handlers.rs # Обработка TDLib update events -│ │ ├── message_converter.rs # Конвертация TDLib → MessageInfo -│ │ ├── message_conversion.rs # Доп. функции конвертации -│ │ └── messages/ # Менеджер сообщений (Этап 4) -│ │ ├── mod.rs # MessageManager struct (99 loc) -│ │ ├── convert.rs # convert_message, fetch_reply_info -│ │ └── operations.rs # 11 TDLib API операций (616 loc) -│ │ -│ ├── ui/ # Рендеринг интерфейса -│ │ ├── mod.rs # render() — роутинг по экранам -│ │ ├── loading.rs # Экран загрузки -│ │ ├── auth.rs # Экран авторизации -│ │ ├── main_screen.rs # Главный экран + папки -│ │ ├── footer.rs # Футер с командами и статусом сети -│ │ ├── chat_list.rs # Список чатов + онлайн-статус -│ │ ├── messages.rs # Область сообщений (364 loc, Этап 3) -│ │ ├── compose_bar.rs # Multi-mode input box (Этап 3) -│ │ ├── profile.rs # Профиль пользователя/чата -│ │ ├── modals/ # Модальные окна (Этап 3) -│ │ │ ├── mod.rs # Re-exports -│ │ │ ├── delete_confirm.rs # Подтверждение удаления -│ │ │ ├── reaction_picker.rs # Выбор реакции -│ │ │ ├── search.rs # Поиск по сообщениям -│ │ │ └── pinned.rs # Закреплённые сообщения -│ │ └── components/ # Переиспользуемые UI компоненты (Этап 6) -│ │ ├── mod.rs # Re-exports -│ │ ├── modal.rs # render_modal(), render_delete_confirm -│ │ ├── input_field.rs # render_input_field() -│ │ ├── message_bubble.rs # render_message_bubble(), sender, date -│ │ ├── message_list.rs # render_message_item(), help_bar, scroll -│ │ ├── chat_list_item.rs # render_chat_list_item() -│ │ └── emoji_picker.rs # render_emoji_picker() -│ │ -│ └── utils/ # Утилиты -│ ├── mod.rs # Exports, with_timeout helpers -│ ├── formatting.rs # format_timestamp, format_date, etc. -│ ├── tdlib.rs # disable_tdlib_logs (FFI) -│ ├── validation.rs # is_non_empty и др. -│ ├── modal_handler.rs # handle_yes_no для Y/N модалок -│ └── retry.rs # Retry утилиты -│ -├── tests/ # Интеграционные тесты -│ ├── helpers/ # Тестовая инфраструктура -│ │ ├── mod.rs -│ │ ├── app_builder.rs # TestAppBuilder (fluent API) -│ │ ├── fake_tdclient.rs # FakeTdClient (мок TDLib) -│ │ ├── fake_tdclient_impl.rs # impl TdClientTrait for FakeTdClient -│ │ ├── test_data.rs # create_test_chat, TestMessageBuilder -│ │ └── snapshot_utils.rs # Snapshot testing хелперы -│ ├── input_navigation.rs # Тесты навигации клавиатурой -│ ├── chat_list.rs # Тесты списка чатов -│ ├── messages.rs # Тесты сообщений -│ ├── send_message.rs # Тесты отправки -│ ├── edit_message.rs # Тесты редактирования -│ ├── delete_message.rs # Тесты удаления -│ ├── reply_forward.rs # Тесты reply/forward -│ ├── reactions.rs # Тесты реакций -│ ├── search.rs # Тесты поиска -│ ├── modals.rs # Тесты модальных окон -│ ├── profile.rs # Тесты профиля -│ ├── navigation.rs # Тесты навигации -│ ├── drafts.rs # Тесты черновиков -│ ├── copy.rs # Тесты копирования -│ ├── screens.rs # Тесты экранов -│ ├── footer.rs # Тесты футера -│ ├── input_field.rs # Тесты поля ввода -│ ├── config.rs # Тесты конфигурации -│ ├── network_typing.rs # Тесты typing status -│ ├── e2e_smoke.rs # Smoke тесты -│ └── e2e_user_journey.rs # E2E user journey тесты -│ -├── .github/ # GitHub конфигурация -│ ├── ISSUE_TEMPLATE/ -│ ├── workflows/ci.yml -│ └── pull_request_template.md -│ -├── Cargo.toml # Манифест проекта -├── Cargo.lock # Точные версии зависимостей -├── build.rs # Build script (TDLib) -├── rustfmt.toml # cargo fmt конфигурация -├── .editorconfig # Настройки IDE -├── .gitignore # Git ignore -│ -├── config.toml.example # Пример конфигурации -├── credentials.example # Пример credentials -│ -├── CLAUDE.md # Инструкции для AI -├── CONTEXT.md # Текущий статус -├── ROADMAP.md # План развития -├── DEVELOPMENT.md # Правила разработки -├── REQUIREMENTS.md # Требования -├── ARCHITECTURE.md # C4, sequence diagrams -├── PROJECT_STRUCTURE.md # Этот файл -├── E2E_TESTING.md # Гайд по тестированию -├── HOTKEYS.md # Горячие клавиши -├── CHANGELOG.md # История изменений -├── README.md # Главная документация -├── INSTALL.md # Установка -├── FAQ.md # FAQ -├── CONTRIBUTING.md # Гайд по контрибуции -├── SECURITY.md # Безопасность -└── LICENSE # MIT лицензия -``` - -## Ключевые модули - -### app/ — Состояние приложения - -`App` — главная структура, параметризована trait'ом для DI. - -**State machine** (`ChatState` enum): -``` -Normal → MessageSelection → Editing - → Reply - → Forward - → DeleteConfirmation - → ReactionPicker - → Profile - → SearchInChat - → PinnedMessages -``` - -**Trait-based methods** (5 traits на `App`): -| Trait | Методы | Описание | -|-------|--------|----------| -| NavigationMethods | 7 | next/previous_chat, close_chat, select_current_chat | -| MessageMethods | 8 | is_editing, is_replying, get_selected_message, etc. | -| ComposeMethods | 10 | start_reply, cancel_editing, load_draft, etc. | -| SearchMethods | 15 | start_search, enter_message_search_mode, etc. | -| ModalMethods | 27 | enter_profile_mode, exit_pinned_mode, etc. | - -### input/ — Обработка ввода - -**Маршрутизация** (порядок приоритетов в `main_input.rs`): -1. Global commands (Ctrl+R/S/P/F) -2. Profile mode -3. Message search mode -4. Pinned messages mode -5. Reaction picker mode -6. Delete confirmation -7. Forward mode -8. Chat search mode -9. Enter/Esc commands -10. Open chat input / Chat list navigation - -### tdlib/ — Telegram интеграция - -**Dependency Injection**: `TdClientTrait` позволяет подменять TdClient на `FakeTdClient` в тестах. - -**MessageManager** — управление сообщениями: -- `convert.rs` — конвертация TDLib JSON → MessageInfo -- `operations.rs` — 11 API операций (get_history, send, edit, delete, forward, search, etc.) - -### ui/ — Рендеринг - -**Компоненты** (`ui/components/`): -| Компонент | Описание | -|-----------|----------| -| message_bubble | Рендеринг пузыря сообщения с реакциями | -| message_list | Элемент списка сообщений (search/pinned) | -| chat_list_item | Элемент списка чатов | -| input_field | Поле ввода с курсором | -| emoji_picker | Сетка выбора реакций | -| modal | Центрированная модалка | - -### config/ — Конфигурация - -- **mod.rs** — struct Config, GeneralConfig, ColorsConfig, NotificationsConfig -- **keybindings.rs** — Command enum (30+ команд), кастомные горячие клавиши -- **validation.rs** — валидация timezone, цветов -- **loader.rs** — загрузка из `~/.config/tele-tui/config.toml`, credentials - -## Тестирование - -**500+ тестов** через `cargo test` (без TDLib). - -**Инфраструктура**: -- `TestAppBuilder` — fluent API для создания App с нужным состоянием -- `FakeTdClient` — мок TDLib, реализует TdClientTrait -- `TestMessageBuilder` — создание тестовых сообщений - -**Типы тестов**: -- Unit-тесты — в `#[cfg(test)]` секциях модулей -- Integration-тесты — в `tests/` (навигация, отправка, UI рендеринг) -- Doc-тесты — примеры в документации -- E2E — smoke и user journey тесты - -## Потоки выполнения - -``` -Main thread TDLib thread - │ │ - │ ◄── mpsc ─────── │ td_client.receive() в Tokio task - │ │ - ├── poll events │ - ├── handle input │ - ├── update state │ - ├── render UI │ - └── sleep 16ms ──► │ -``` - -## Рантайм файлы - -| Путь | Описание | -|------|----------| -| `~/.config/tele-tui/config.toml` | Пользовательская конфигурация | -| `~/.config/tele-tui/credentials` | API_ID и API_HASH | -| `tdlib_data/` | TDLib сессия (НЕ коммитится) | - -## Зависимости - -| Категория | Крейт | Назначение | -|-----------|-------|------------| -| UI | ratatui 0.29 | TUI framework | -| UI | crossterm 0.28 | Terminal control | -| Telegram | tdlib-rs 1.1 | TDLib bindings | -| Async | tokio 1.x | Async runtime | -| Config | serde + toml | Serialization | -| Time | chrono 0.4 | Date/time | -| System | dirs 5.0 | XDG directories | -| System | arboard 3.4 | Clipboard | -| Notify | notify-rust 4.11 | Desktop уведомления (feature) | -| URL | open 5.0 | Открытие URL (feature) | diff --git a/README.md b/README.md index 9f7975e..6578fa9 100644 --- a/README.md +++ b/README.md @@ -1,261 +1,69 @@ # tele-tui -[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -[![Rust](https://img.shields.io/badge/rust-1.70%2B-orange.svg)](https://www.rust-lang.org/) - -Консольный Telegram клиент с Vim-style навигацией. - -![tele-tui screenshot](docs/screenshot.png) +Консольный Telegram-клиент на Rust с Vim-style навигацией, Normal/Insert режимами, TDLib и поддержкой русской раскладки. ## Возможности -- **Полная интеграция с Telegram**: отправка/получение сообщений, редактирование, удаление, пересылка -- **Vim-style навигация**: hjkl + поддержка русской раскладки (ролд) -- **Markdown форматирование**: жирный, курсив, подчёркивание, зачёркивание, код, спойлеры, ссылки -- **Реакции на сообщения**: emoji picker с навигацией стрелками -- **Папки Telegram**: переключение между папками (1-9) -- **Поиск**: по чатам (Ctrl+S) и внутри чата (Ctrl+F) -- **Черновики**: автосохранение набранного текста при переключении чатов -- **Typing indicator**: показывает когда собеседник печатает -- **Закреплённые сообщения**: отображение и переход к закреплённому сообщению -- **Копирование в буфер**: copy сообщений в системный буфер обмена -- **Профиль**: просмотр информации о пользователе/чате -- **Конфигурация**: настройка цветов и часового пояса через TOML -- **Оптимизация**: 60 FPS, умное кеширование, graceful shutdown +- Авторизация через TDLib, список чатов, история и realtime updates. +- Отправка, редактирование, удаление, reply, forward, copy и реакции. +- Поиск по чатам и внутри открытого чата. +- Telegram-папки, pinned/muted/mentions/unread indicators. +- Markdown entities, inline фото, fullscreen image modal и фото-альбомы. +- Голосовые сообщения через `ffplay`. +- Desktop notifications, clipboard и открытие URL через feature flags. +- Multi-account profiles, account switcher и per-account lock files. ## Установка -### Требования +Требования: -- Rust 1.70+ -- TDLib (скачивается автоматически через tdlib-rs) - -### Сборка +- Rust stable, рекомендовано 1.85+. +- TDLib скачивается автоматически через `tdlib-rs` feature `download-tdlib`. +- Для голосовых сообщений нужен `ffplay` из ffmpeg. ```bash -git clone https://github.com/your-username/tele-tui.git -cd tele-tui cargo build --release ``` -### API Credentials +## Credentials -Получите API credentials на https://my.telegram.org/apps +Получите Telegram API credentials на и сохраните их в: -Создайте файл `~/.config/tele-tui/credentials`: +```text +~/.config/tele-tui/credentials ``` + +Формат: + +```text API_ID=your_api_id API_HASH=your_api_hash ``` -Или используйте `.env` файл в директории проекта: -``` -API_ID=your_api_id -API_HASH=your_api_hash -``` +Для локальной разработки можно использовать `.env` в корне проекта с теми же ключами. -## Использование +## Запуск ```bash cargo run --release ``` -При первом запуске нужно пройти авторизацию (телефон + код + опционально 2FA пароль). - -## Конфигурация - -Конфигурационный файл создаётся автоматически в `~/.config/tele-tui/config.toml`: - -```toml -[general] -# Часовой пояс в формате "+03:00" или "-05:00" -timezone = "+03:00" - -[colors] -# Поддерживаемые цвета: black, red, green, yellow, blue, magenta, cyan, gray, white, -# darkgray, lightred, lightgreen, lightyellow, lightblue, lightmagenta, lightcyan -incoming_message = "white" -outgoing_message = "green" -selected_message = "yellow" -reaction_chosen = "yellow" -reaction_other = "gray" -``` - -## Горячие клавиши - -### Навигация -- `↑/↓` или `k/j` (р/о) — навигация по списку чатов -- `Enter` — открыть чат / отправить сообщение -- `Esc` — закрыть чат / отменить действие -- `1-9` — переключение между папками -- `Ctrl+S` — поиск по чатам -- `Ctrl+R` — обновить список чатов -- `Ctrl+C` — выход - -### В открытом чате -- `↑/↓` — скролл сообщений -- `Ctrl+F` — поиск в чате -- `n/N` — следующий/предыдущий результат поиска -- `i` — информация о чате/пользователе - -### Работа с сообщениями -- `↑` при пустом инпуте — выбор сообщения -- `Enter` в режиме выбора — редактировать -- `r` / `к` — ответить (reply) -- `f` / `а` — переслать (forward) -- `d` / `в` / `Delete` — удалить -- `y` / `н` — скопировать в буфер -- `e` / `у` — добавить реакцию - -### Emoji Picker (реакции) -- `←/→/↑/↓` — навигация по сетке -- `Enter` — добавить/удалить реакцию -- `Esc` — закрыть picker - -### Редактирование текста -- `←/→` — перемещение курсора -- `Home` — в начало строки -- `End` — в конец строки -- `Backspace` — удалить символ слева -- `Delete` — удалить символ справа - -## Структура проекта - -``` -src/ -├── main.rs # Точка входа, event loop -├── config.rs # Конфигурация (TOML), credentials -├── app/ # Состояние приложения -├── ui/ # Отрисовка интерфейса -├── input/ # Обработка ввода -├── utils.rs # Утилиты (форматирование времени, логи) -└── tdlib/ # TDLib интеграция -``` - -## Зависимости - -- `ratatui` 0.29 — TUI framework -- `crossterm` 0.28 — terminal handling -- `tdlib-rs` 1.1 — Telegram API -- `tokio` 1.x — async runtime -- `serde` + `serde_json` — serialization -- `toml` 0.8 — config parsing -- `dirs` 5.0 — XDG directories -- `clipboard` 0.5 — clipboard access -- `chrono` 0.4 — date/time formatting - -## Тестирование - -tele-tui использует **snapshot тестирование** для UI и интеграционные тесты для логики. - -### Запуск всех тестов +Для выбора аккаунта: ```bash -cargo test +cargo run --release -- --account work ``` -### Snapshot тесты - -Snapshot тесты проверяют отображение UI компонентов через виртуальный терминал: - -```bash -# Прогнать snapshot тесты -cargo test --test chat_list -cargo test --test messages - -# Посмотреть изменения в snapshots -cargo insta review - -# Принять все новые snapshots -cargo insta accept - -# Отклонить все изменения -cargo insta reject -``` - -### Установка cargo-insta - -Для работы со snapshot тестами нужен `cargo-insta`: - -```bash -cargo install cargo-insta -``` - -### Структура тестов - -``` -tests/ -├── helpers/ # Тестовые утилиты -│ ├── app_builder.rs # TestAppBuilder для создания тестовых App -│ ├── test_data.rs # Builders для чатов и сообщений -│ ├── snapshot_utils.rs # Утилиты для snapshot тестов -│ └── fake_tdclient.rs # Mock TDLib клиент (для будущих integration тестов) -├── chat_list.rs # Snapshot тесты для списка чатов (9 тестов) -├── messages.rs # Snapshot тесты для сообщений (18 тестов) -├── modals.rs # Snapshot тесты для модалок (8 тестов) -└── input_field.rs # Snapshot тесты для поля ввода (7 тестов) -``` - -### Создание snapshot теста - -```rust -use helpers::test_data::TestChatBuilder; -use helpers::app_builder::TestAppBuilder; -use helpers::snapshot_utils::{render_to_buffer, buffer_to_string}; -use insta::assert_snapshot; - -#[test] -fn snapshot_my_feature() { - let chat = TestChatBuilder::new("Test Chat", 123) - .unread_count(5) - .build(); - - let mut app = TestAppBuilder::new() - .with_chat(chat) - .selected_chat(123) - .build(); - - let buffer = render_to_buffer(80, 24, |f| { - tele_tui::ui::chat_list::render(f, f.area(), &mut app); - }); - - let output = buffer_to_string(&buffer); - assert_snapshot!("my_feature", output); -} -``` - -### Покрытие тестами - -**Текущий прогресс**: 81/151 тестов (54%) - -- ✅ Фаза 0: Инфраструктура (100%) -- ✅ Фаза 1: UI Snapshot Tests (100%) - - Chat List, Messages, Modals, Input Field, Footer, Screens -- 🔄 Фаза 2: Integration Tests (35%) - - ✅ Send Message Flow (6 тестов) - - ✅ Edit Message Flow (6 тестов) - - ✅ Delete Message Flow (6 тестов) - - ✅ Reply & Forward Flow (8 тестов) - - 📋 Reactions, Search, Drafts, Navigation, Profile, Network (0/48) - -Подробный план: [TESTING_ROADMAP.md](TESTING_ROADMAP.md) +Runtime-конфиг создаётся в `~/.config/tele-tui/config.toml`; пример лежит в [config.toml.example](config.toml.example). ## Документация -- [INSTALL.md](INSTALL.md) — подробная инструкция по установке -- [HOTKEYS.md](HOTKEYS.md) — все горячие клавиши -- [FAQ.md](FAQ.md) — часто задаваемые вопросы -- [CONTRIBUTING.md](CONTRIBUTING.md) — как внести вклад -- [PROJECT_STRUCTURE.md](PROJECT_STRUCTURE.md) — структура проекта -- [SECURITY.md](SECURITY.md) — политика безопасности -- [CHANGELOG.md](CHANGELOG.md) — история изменений -- [REQUIREMENTS.md](REQUIREMENTS.md) — функциональные требования -- [DEVELOPMENT.md](DEVELOPMENT.md) — правила разработки -- [ROADMAP.md](ROADMAP.md) — план развития проекта -- [REFACTORING_ROADMAP.md](REFACTORING_ROADMAP.md) — план рефакторинга кода -- [TESTING_ROADMAP.md](TESTING_ROADMAP.md) — план покрытия тестами -- [TESTING_PROGRESS.md](TESTING_PROGRESS.md) — прогресс тестирования -- [CONTEXT.md](CONTEXT.md) — текущий статус разработки +- [AGENT.md](AGENT.md) - краткие правила для агента. +- [DEVELOPMENT.md](DEVELOPMENT.md) - локальные правила разработки и проверок. +- [CONTEXT.md](CONTEXT.md) - текущий статус, риски и следующие шаги. +- [docs/HOTKEYS.md](docs/HOTKEYS.md) - горячие клавиши. +- [docs/PROJECT_STRUCTURE.md](docs/PROJECT_STRUCTURE.md) - карта подсистем. +- [docs/TDLIB_INTEGRATION.md](docs/TDLIB_INTEGRATION.md) - проектные заметки по TDLib. ## Лицензия diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md deleted file mode 100644 index d423c71..0000000 --- a/REQUIREMENTS.md +++ /dev/null @@ -1,131 +0,0 @@ -# TTUI - Требование к приложению - -## Описание приложения - -Терминальный интерфейс для telegram - -## Функциональные требования - -### Интерфейс - -┌─ TTUI ───────────────────────────────────────────────────────────────────────┐ -│ 1:All │ 2:Personal │ 3:Work │ 4:Bots │ -├──────────────────────┬───────────────────────────────────────────────────────┤ -│ 🔍 Search... │ 👤 Mom (online) │ -├──────────────────────┼───────────────────────────────────────────────────────┤ -│ 📌 Saved Messages │ Today, Dec 21│ -│ ▌ Mom (2)│ │ -│ Boss │ Mom ────────────────────────────────────────── 14:20 │ -│ Rust Community │ Привет! Ты покормил кота? │ -│ Durov │ │ -│ News Channel │ You ─────────────────────────────────────── 14:22 ✓✓ │ -│ Spam Bot │ Да, конечно. Купил ему корм. │ -│ Wife │ Скоро буду дома. │ -│ Team Lead │ │ -│ DevOps Chat (9)│ Mom ────────────────────────────────────────── 14:23 │ -│ Server Alerts │ Отлично, захвати хлеба. │ -│ Gym Bro │ │ -│ Design Team │ You ─────────────────────────────────────── 14:25 ✓ │ -│ Project X │ Ок. │ -│ HR │ │ -│ Mom's Friend │ │ -│ Taxi Bot │ │ -│ │ │ -│ │ │ -│ │ │ -│ │ │ -│ │ │ -├──────────────────────┼───────────────────────────────────────────────────────┤ -│ [User: Online] │ > **message** │ -└──────────────────────┴───────────────────────────────────────────────────────┘ - **commands** - - -### Список желаемого -1) футер - список папок в телеграме -2) список с чатами - лички и группы, сверху инпут для поиска чата -3) основной контент - открытый чат с сообщениями из чата, если никакой чат не открыт, то контент пустой, ничего не показываем. Снизу - инпут для ввода сообщения в чат, который открыт -4) снизу списка чата статус онлайн или нет сам пользователь приложения -5) при открытии чата должна загружаться история чата, а так же подгружаться новые сообщения от собеседника. -6) выделяем сообщения собеседника его никнеймом, группируем его сообщения и разделяем наши сообщения и сообщения собеседника, как на интерфейсе сверху -7) отображаем наше сообщение символом `✓`, если телеграм подтвердил, что сообщение отправлено, и выделяем `✓✓` если собеседник прочитал его -8) при навигации в чате выделяем строку курсивом, при выборе чата (то есть его открытии) ставим в начало символ ▌ -9) `(2)` — счетчик непрочитанных (можно красить в красный/зеленый). -10) `muted` — статус чата (серый цвет). -11) `@` — пинг/меншн. -12) с видео/картинками/голосовые пока ничего не делаем, ренденим заглушку (с упоминанием что это картинка или видео и тд) - -### Дополнительно реализованные возможности - -13) **Markdown форматирование**: жирный, курсив, подчёркивание, зачёркивание, код, спойлеры, ссылки, упоминания -14) **Редактирование сообщений**: ↑ при пустом инпуте → выбор → Enter → редактирование -15) **Удаление сообщений**: d/в/Delete в режиме выбора → подтверждение → удаление -16) **Reply на сообщения**: r/к в режиме выбора → превью → отправка ответа -17) **Forward сообщений**: f/а в режиме выбора → выбор чата → пересылка -18) **Typing indicator**: отображение "печатает..." когда собеседник набирает текст -19) **Закреплённые сообщения**: отображение pinned message вверху чата с переходом -20) **Поиск по сообщениям**: Ctrl+F для поиска внутри чата, n/N для навигации -21) **Черновики**: автосохранение текста при переключении между чатами -22) **Профиль**: i для просмотра информации о пользователе/группе -23) **Копирование**: y/н для копирования сообщения в системный буфер -24) **Реакции**: e/у для добавления реакций, emoji picker с навигацией стрелками -25) **Конфигурация**: ~/.config/tele-tui/config.toml для настройки цветов и timezone -26) **Credentials**: приоритетная загрузка из ~/.config/tele-tui/credentials или .env -27) **Блочный курсор**: Vim-style курсор █ с навигацией ←/→/Home/End -28) **Динамический инпут**: автоматическое расширение до 10 строк -29) **Онлайн-статус**: зелёная точка ● для онлайн пользователей -30) **Индикаторы**: 📌 закреплённые чаты, 🔇 замьюченные, @ упоминания -31) **Состояние сети**: индикатор в футере (⚠ Нет сети, ⏳ Подключение...) -32) **Graceful shutdown**: корректное закрытие при Ctrl+C - -### Управление -1) ctrl+c или command+c - выход из программы -2) "h j k l" - влево, вниз, вверх, вправо (навигация в левом столбце) vim-style управление -3) стрелки - управление, так же как и "h j k l" -4) "command + 1", "command + 2" и так далее - переключение между папками, которые созданы в телеграме -5) из интерфейса "**message**" - это инпут для ввода сообщения в открытый чат -6) ctrl + s - фокус в инпут поиска чата -7) Esc - закрытие открытого чата -8) command + стрелка вверх (или ctrl + k) - выделяем самый верхний чат (без открытия) -9) поддержка русской раскладки: "р о л д" соответствует "h j k l" -10) Ctrl+R - обновить список чатов - -### Реализованные команды - -#### Навигация -``` -↑/↓ или k/j (р/о): Navigate | Enter: Open/Send | Esc: Close/Cancel | 1-9: Folders -Ctrl+S: Search Chats | Ctrl+R: Refresh | Ctrl+F: Search in Chat | Ctrl+C: Quit -``` - -#### Работа с сообщениями -``` -↑ (пустой инпут): Select message | Enter: Edit | r/к: Reply | f/а: Forward -d/в/Delete: Delete | y/н: Copy | e/у: React | i: Profile -``` - -#### Emoji Picker (реакции) -``` -←/→/↑/↓: Navigate | Enter: Toggle reaction | Esc: Close -``` - -## Технологии -Пишем на rust-е - -1) ratatui 0.29 - для tui интерфейса -2) tdlib-rs 1.1 - для подключения апи телеграма (обёртка над TDLib) -3) tokio 1.x - async runtime -4) crossterm 0.28 - кроссплатформенный терминал -5) serde + serde_json 1.0 - сериализация/десериализация -6) toml 0.8 - парсинг конфигурации -7) dirs 5.0 - XDG директории (config, data) -8) clipboard 0.5 - работа с системным буфером обмена -9) chrono 0.4 - форматирование даты/времени -10) dotenvy 0.15 - загрузка .env файлов - -## Нефункциональные требования - -### Производительность -1) программа должна выдавать 60 фпс -2) интерфейс не должен мерцать -3) минимальное разрешение - 600 символов, максимального нет, не ограничиваем diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 518ea81..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,168 +0,0 @@ -# Roadmap - -## Завершённые фазы - -| Фаза | Описание | Ключевые результаты | -|------|----------|---------------------| -| 1 | Базовая инфраструктура | ratatui + crossterm, vim-навигация, русская раскладка | -| 2 | TDLib интеграция | tdlib-rs, авторизация, загрузка чатов и сообщений | -| 3 | Улучшение UX | Отправка, поиск, скролл, realtime обновления | -| 4 | Папки и фильтрация | Загрузка папок из Telegram, переключение 1-9 | -| 5 | Расширенный функционал | Онлайн-статус, галочки прочтения, медиа-заглушки, muted | -| 6 | Полировка | 60 FPS, оптимизация памяти, graceful shutdown, динамический инпут | -| 7 | Рефакторинг памяти | Единый источник данных, LRU-кэш (500 users), lazy loading | -| 8 | Дополнительные фичи | Markdown, edit/delete, reply/forward, блочный курсор | -| 9 | Расширенные возможности | Typing, pinned, поиск в чате, черновики, профиль, копирование, реакции, конфиг | -| 10 | Desktop уведомления (83%) | notify-rust, muted фильтр, mentions, медиа. TODO: кастомные звуки | -| 11 | Inline просмотр фото | Dual renderer (Halfblocks + iTerm2/Sixel), throttling 15 FPS, modal viewer, lazy loading, auto-download | -| 12 | Голосовые сообщения | ffplay player, pause/resume with seek, VoiceCache, AudioConfig, progress bar + waveform UI | -| 13 | Глубокий рефакторинг | 5 файлов (4582->модули), 5 traits, shared components, docs | - ---- - -## Фаза 11: Inline просмотр фото в чате [DONE] - -**UX**: Always-show inline preview (50 chars, Halfblocks) -> `v`/`м` открывает fullscreen modal (iTerm2/Sixel) -> `←`/`→` навигация между фото. - -### Реализовано: -- [x] **Dual renderer архитектура**: - - `inline_image_renderer`: Halfblocks (быстро, Unicode блоки) для навигации - - `modal_image_renderer`: iTerm2/Sixel (медленно, высокое качество) для просмотра -- [x] **Performance optimizations**: - - Frame throttling: inline 15 FPS, текст 60 FPS - - Lazy loading: только видимые изображения - - LRU cache: max 100 протоколов - - Skip partial rendering (no flickering) -- [x] **UX улучшения**: - - Always-show inline preview (фикс. ширина 50 chars) - - Fullscreen modal на `v`/`м` с aspect ratio - - Loading indicator в модалке - - Navigation hotkeys: `←`/`→` между фото, `Esc`/`q` закрыть -- [x] **Типы и API**: - - `MediaInfo`, `PhotoInfo`, `PhotoDownloadState`, `ImageModalState` - - `ImagesConfig` в config.toml - - Feature flag `images` для зависимостей -- [x] **Media модуль**: - - `cache.rs`: ImageCache (LRU) - - `image_renderer.rs`: new() + new_fast() -- [x] **UI модули**: - - `modals/image_viewer.rs`: fullscreen modal - - `messages.rs`: throttled second-pass rendering -- [x] **Авто-загрузка фото** (bugfix): - - Auto-download последних 30 фото при открытии чата (`open_chat_and_load_data`) - - Download on demand по `v` (вместо "Фото не загружено") - - Retry при ошибке загрузки - - Конфиг: `auto_download_images` + `show_images` в `[images]` - ---- - -## Фаза 12: Прослушивание голосовых сообщений [DONE] - -### Этап 1: Инфраструктура аудио [DONE] -- [x] Модуль `src/audio/` - - `player.rs` — AudioPlayer на ffplay (subprocess) - - `cache.rs` — VoiceCache (LRU, configurable size, `~/.cache/tele-tui/voice/`) -- [x] AudioPlayer API: play(), play_from(ss), pause() (SIGSTOP), resume(), resume_from(ss), stop() -- [x] Race condition fix: `starting` flag + pid ownership guard в потоках -- [x] Drop impl для AudioPlayer (убивает ffplay при выходе) - -### Этап 2: Интеграция с TDLib [DONE] -- [x] Типы: `VoiceInfo`, `VoiceDownloadState`, `PlaybackState`, `PlaybackStatus` -- [x] Конвертация `MessageVoiceNote` в `message_conversion.rs` -- [x] `download_voice_note()` в TdClientTrait + client_impl + fake -- [x] Методы `has_voice()`, `voice_info()`, `voice_info_mut()` на `MessageInfo` - -### Этап 3: UI для воспроизведения [DONE] -- [x] Progress bar (━●─) с позицией и длительностью -- [x] Waveform визуализация (▁▂▃▄▅▆▇█) из base64-encoded TDLib данных -- [x] Иконки статуса: ▶ Playing, ⏸ Paused, ⏹ Stopped -- [x] Throttled redraw: обновление UI только при смене секунды (не 60 FPS) - -### Этап 4: Хоткеи [DONE] -- [x] Space — play/pause toggle (запуск + пауза/возобновление с откатом 1s) -- [x] ←/→ — seek ±5 сек (через `resume_from()` — перезапуск ffplay с `-ss`) -- [x] Seek работает и при воспроизведении, и на паузе (на паузе двигает позицию, при resume стартует с неё) -- [x] MoveLeft/MoveRight как alias для SeekBackward/SeekForward (HashMap non-deterministic order fix) -- [x] Автоматическая остановка при навигации на другое сообщение -- [x] Остановка ffplay при выходе из приложения (Ctrl+C) - -### Этап 5: Конфигурация и кэш [DONE] -- [x] `AudioConfig` в config.toml (`cache_size_mb`, `auto_download_voice`) -- [x] `DEFAULT_AUDIO_CACHE_SIZE_MB` константа (100 MB) -- [x] Ticker для progress bar в event loop (delta-based position tracking) -- [x] VoiceCache интеграция: проверка кэша перед загрузкой, кэширование после download - -### Технические детали -- **Аудио:** ffplay (subprocess), resume/seek через перезапуск с `-ss` offset -- **Race conditions:** `starting` flag предотвращает false `is_stopped()` при старте ffplay; pid ownership guard в потоках предотвращает затирание pid нового процесса старым -- **Keybinding conflict:** Left/Right привязаны к MoveLeft/MoveRight и SeekBackward/SeekForward; HashMap iteration order не гарантирован → оба варианта обрабатываются как seek в режиме выбора сообщения -- **Платформы:** macOS, Linux (везде где есть ffmpeg) -- **Хоткеи:** Space (play/pause), ←/→ (seek ±5s) - ---- - -## Фаза 14: Мультиаккаунт - -**Цель**: поддержка нескольких Telegram-аккаунтов с мгновенным переключением внутри приложения. - -### UI: Индикатор в footer + хоткеи - -``` -┌──────────────┬───────────────────────────┐ -│ Saved Msgs │ Привет! │ -│ Иван Петров │ Как дела? │ -│ Работа чат │ │ -├──────────────┴───────────────────────────┤ -│ [NORMAL] Михаил ⟨1/2⟩ Work(3) │ Ctrl+A │ -└──────────────────────────────────────────┘ -``` - -- **Footer**: текущий аккаунт + номер `⟨1/2⟩` + бейджи непрочитанных с других аккаунтов -- **Быстрое переключение**: `Ctrl+1`..`Ctrl+9` — мгновенный switch без модалки -- **Модалка управления** (`Ctrl+A`): список аккаунтов, добавление/удаление, выбор активного - -### Модалка переключения аккаунтов - -``` -┌──────────────────────────────────┐ -│ Аккаунты │ -│ │ -│ 1. Михаил (+7 900 ...) ● │ ← активный -│ 2. Work (+7 911 ...) (3) │ ← 3 непрочитанных -│ 3. + Добавить аккаунт │ -│ │ -│ [j/k навигация, Enter выбор] │ -│ [d — удалить аккаунт] │ -└──────────────────────────────────┘ -``` - -### Техническая реализация: все клиенты одновременно - -- **Несколько TdClient**: каждый аккаунт — отдельный `TdClient` со своим `database_directory` - - Аккаунт 1: `~/.local/share/tele-tui/accounts/1/tdlib_data/` - - Аккаунт 2: `~/.local/share/tele-tui/accounts/2/tdlib_data/` -- **Все клиенты активны**: polling updates со всех аккаунтов одновременно (уведомления, непрочитанные) -- **Мгновенное переключение**: swap активного `App.td_client` — чаты и сообщения уже загружены -- **Общий конфиг**: `~/.config/tele-tui/config.toml` (один для всех аккаунтов) -- **Профили аккаунтов**: `~/.config/tele-tui/accounts.toml` — список аккаунтов (имя, путь к БД) - -### Этапы - -- [x] **Этап 1: Инфраструктура профилей** (DONE) - - Структура `AccountProfile` (name, display_name, db_path) - - `accounts.toml` — хранение списка аккаунтов - - Миграция `tdlib_data/` → `accounts/default/tdlib_data/` (обратная совместимость) - - CLI: `--account ` для запуска конкретного аккаунта - -- [x] **Этап 2+3: Account Switcher Modal + Переключение** (DONE) - - Подход: single-client reinit (close TDLib → new TdClient → auth) - - Модалка `Ctrl+A` — глобальный оверлей с навигацией j/k - - Footer индикатор `[account_name]` если не "default" - - `AccountSwitcherState` enum (SelectAccount / AddAccount) - - `recreate_client()` метод в TdClientTrait (close → new → set_tdlib_parameters) - - `add_account()` — создание нового аккаунта из модалки - - `pending_account_switch` флаг → обработка в main loop - -- [ ] **Этап 4: Расширенные возможности мультиаккаунта** - - Хоткеи `Ctrl+1`..`Ctrl+9` — быстрое переключение - - Бейджи непрочитанных с других аккаунтов (требует множественных TdClient) diff --git a/docs/HOTKEYS.md b/docs/HOTKEYS.md new file mode 100644 index 0000000..fae5309 --- /dev/null +++ b/docs/HOTKEYS.md @@ -0,0 +1,74 @@ +# Горячие клавиши + +Краткий справочник актуальных команд tele-tui. Русская раскладка указана там, где команда имеет отдельный буквенный alias. + +## Глобальные команды + +| Клавиша | Действие | +|---------|----------| +| `Ctrl+C` | Выход | +| `Ctrl+R` | Обновить список чатов | +| `Ctrl+A` | Открыть переключатель аккаунтов | +| `Esc` | Закрыть модалку, отменить режим или вернуться назад | +| `1`-`9` | Переключить Telegram-папку | + +## Навигация + +| Клавиша | Русская | Действие | +|---------|---------|----------| +| `j` / `↓` | `о` | Вниз | +| `k` / `↑` | `л` | Вверх | +| `h` / `←` | `р` | Влево / seek назад в голосовом | +| `l` / `→` | `д` | Вправо / seek вперёд в голосовом | +| `Enter` | | Открыть чат, подтвердить выбор или отправить сообщение | + +## Режимы ввода + +| Клавиша | Русская | Действие | +|---------|---------|----------| +| `i` | `ш` | Перейти в Insert mode | +| `Esc` | | Вернуться из Insert в Normal mode | +| `Home` / `End` | | В начало / конец строки | +| `Backspace` / `Delete` | | Удалить символ слева / справа | + +В Normal mode буквенные команды управляют чатом. В Insert mode вводится текст; команды чата блокируются до `Esc`. + +## Поиск + +| Клавиша | Действие | +|---------|----------| +| `Ctrl+S` | Поиск по чатам | +| `Ctrl+F` | Поиск в текущем чате | +| `n` | Следующий результат | +| `N` | Предыдущий результат | + +## Сообщения + +| Клавиша | Русская | Действие | +|---------|---------|----------| +| `↑` при пустом вводе | | Выбрать сообщение | +| `Enter` на выбранном | | Редактировать сообщение | +| `r` | `к` | Ответить | +| `f` | `а` | Переслать | +| `d` / `Delete` | `в` | Удалить | +| `y` | `н` | Скопировать текст | +| `e` | `у` | Открыть emoji picker | +| `v` | `м` | Открыть фото в модалке | +| `Space` | | Play / pause голосового сообщения | +| `Ctrl+I` | `Ctrl+Ш` | Профиль чата или пользователя | + +## Модалки + +| Контекст | Клавиши | +|----------|---------| +| Emoji picker | `←` `→` `↑` `↓`, `Enter`, `Esc` | +| Удаление | `y` / `н` / `Enter` подтвердить, `n` / `т` / `Esc` отменить | +| Пересылка | `j`/`k` или стрелки для выбора чата, `Enter`, `Esc` | +| Просмотр фото | `←`/`→` между фото, `Esc` закрыть | +| Аккаунты | `j`/`k`, `Enter`, `a` добавить, `Esc` закрыть | + +## Подсказки + +- Footer показывает доступные команды для текущего состояния. +- При открытой модалке обрабатываются только команды этой модалки. +- Русская vim-раскладка: `h/j/k/l` соответствует `р/о/л/д`. diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md new file mode 100644 index 0000000..be5376e --- /dev/null +++ b/docs/PROJECT_STRUCTURE.md @@ -0,0 +1,60 @@ +# Структура проекта + +Карта подсистем без полного дерева файлов. + +## Поток данных + +```text +TDLib updates -> mpsc channel -> App state -> UI render +User input -> input handlers -> App methods -> TdClientTrait -> TDLib API +``` + +`main.rs` держит event loop: клавиатура, TDLib updates, фоновые задачи и отрисовка по `needs_redraw`. + +## Подсистемы + +| Путь | Назначение | +|------|------------| +| `src/app/` | Центральное состояние `App`, экраны, режимы и modal state | +| `src/app/methods/` | Trait-based методы navigation/messages/compose/search/modal | +| `src/input/` | Роутинг клавиатуры и обработчики global/chat/compose/modal/search | +| `src/tdlib/` | TDLib wrapper: auth, chats, users, reactions, updates, conversion, operations | +| `src/ui/` | Отрисовка экранов, списка чатов, сообщений, footer, compose bar и модалок | +| `src/config/` | TOML config, credentials, keybindings, validation | +| `src/accounts/` | Профили, `accounts.toml`, migration, per-account lock files | +| `src/audio/` | Голосовые сообщения через `ffplay` и `VoiceCache` | +| `src/media/` | Inline images и modal viewer под feature `images` | +| `src/utils/` | Форматирование, validation, retry, TDLib log helpers | + +## State + +`ChatState` описывает режим открытого чата: normal selection, editing, reply, forward, delete confirmation, reaction picker, profile, search, pinned messages. + +Отдельно есть `InputMode`: в Normal mode работают команды, в Insert mode вводится текст. + +## TDLib слой + +- `TdClientTrait` задаёт API для приложения и тестов. +- Реальный клиент живёт в `TdClient`, тесты используют `FakeTdClient`. +- Updates должны фильтроваться по активному `client_id`, особенно для мультиаккаунта. + +## Тесты + +| Область | Где искать | +|---------|------------| +| Test builders и fake TDLib | `tests/helpers/` | +| Snapshot UI | `tests/*` + `tests/snapshots/` | +| Навигация и ввод | `tests/input_navigation.rs`, `tests/vim_mode.rs`, `tests/navigation.rs` | +| Аккаунты | `tests/accounts.rs`, `tests/account_switcher.rs` | +| User journeys | `tests/e2e_smoke.rs`, `tests/e2e_user_journey.rs` | + +## Runtime-файлы + +| Путь | Назначение | +|------|------------| +| `~/.config/tele-tui/config.toml` | Пользовательская конфигурация | +| `~/.config/tele-tui/credentials` | `API_ID` и `API_HASH` | +| `~/.config/tele-tui/accounts.toml` | Список аккаунтов | +| `~/.local/share/tele-tui/accounts/{name}/tdlib_data/` | TDLib database аккаунта | +| `~/.local/share/tele-tui/accounts/{name}/tele-tui.lock` | Lock активного аккаунта | +| `~/.cache/tele-tui/voice/` | Кэш голосовых сообщений | diff --git a/docs/TDLIB_INTEGRATION.md b/docs/TDLIB_INTEGRATION.md index b50c90d..7d0e395 100644 --- a/docs/TDLIB_INTEGRATION.md +++ b/docs/TDLIB_INTEGRATION.md @@ -1,636 +1,45 @@ -# Интеграция TDLib в Telegram TUI +# TDLib integration notes -## Обзор +Проект использует `tdlib-rs` с feature `download-tdlib`. Актуальные версии смотри в `Cargo.toml`. -TDLib (Telegram Database Library) — это официальная кроссплатформенная библиотека для создания Telegram клиентов. Она предоставляет полный функционал Telegram API с автоматическим управлением сессиями, кэшированием и синхронизацией. +## Runtime -## Выбор библиотеки для Rust +- Credentials: `~/.config/tele-tui/credentials`, fallback `.env`. +- TDLib data: `~/.local/share/tele-tui/accounts/{name}/tdlib_data/`. +- Каждый аккаунт защищён lock file: `~/.local/share/tele-tui/accounts/{name}/tele-tui.lock`. -Существует несколько Rust-оберток для TDLib: +## Auth flow -### 1. rust-tdlib -- **GitHub**: [antonio-antuan/rust-tdlib](https://github.com/antonio-antuan/rust-tdlib) -- **docs.rs**: https://docs.rs/rust-tdlib -- **Особенности**: - - Async/await с tokio - - Client/Worker архитектура - - Требует предварительной сборки TDLib +TDLib авторизация идёт через `updateAuthorizationState`: -### 2. tdlib-rs (Рекомендуется) -- **GitHub**: [FedericoBruzzone/tdlib-rs](https://github.com/FedericoBruzzone/tdlib-rs) -- **crates.io**: https://crates.io/crates/tdlib-rs -- **docs.rs**: https://docs.rs/tdlib/latest/tdlib/ -- **Преимущества**: - - ✅ Не требует предварительной установки TDLib - - ✅ Кроссплатформенность (Windows, Linux, macOS) - - ✅ Автоматическая загрузка прекомпилированных бинарников - - ✅ Поддержка TDLib v1.8.29 - - ✅ Автогенерация типов из TL схемы - -## Установка tdlib-rs - -### Вариант 1: Автоматическая загрузка (Рекомендуется) - -**Cargo.toml:** -```toml -[dependencies] -tdlib-rs = { version = "0.3", features = ["download-tdlib"] } -tokio = { version = "1", features = ["full"] } - -[build-dependencies] -tdlib-rs = { version = "0.3", features = ["download-tdlib"] } +```text +WaitTdlibParameters -> WaitPhoneNumber -> WaitCode -> WaitPassword? -> Ready ``` -**build.rs:** -```rust -fn main() { - tdlib_rs::build::build(None); -} -``` +UI должен показывать понятное состояние и не блокировать event loop дольше необходимого. -### Вариант 2: Локальная установка TDLib +## Updates -Если TDLib уже установлен (версия 1.8.29): +- Receive loop передаёт в UI пару `(client_id, Update)`. +- UI применяет update только если `client_id == active_client_id`. +- При переключении аккаунта pending updates старого клиента должны игнорироваться. -```bash -export LOCAL_TDLIB_PATH=$HOME/lib/tdlib -``` +Это критично для multi-account: без фильтрации старый клиент может испортить state нового аккаунта. -```toml -[dependencies] -tdlib-rs = { version = "0.3", features = ["local-tdlib"] } -``` +## Messages -### Вариант 3: Через pkg-config +- История грузится порциями, открытие чата не должно блокировать первый redraw. +- Reply-info и downloads уходят в фоновые задачи. +- Read/unread для исходящих сообщений должен опираться на `last_read_outbox_message_id`. +- Send/edit/delete/forward/search проходят через `TdClientTrait`, чтобы тесты могли использовать `FakeTdClient`. -```bash -export PKG_CONFIG_PATH=$HOME/lib/tdlib/lib/pkgconfig/:$PKG_CONFIG_PATH -export LD_LIBRARY_PATH=$HOME/lib/tdlib/lib/:$LD_LIBRARY_PATH -``` +## Folders -```toml -[dependencies] -tdlib-rs = { version = "0.3", features = ["pkg-config"] } -``` +Telegram-папки доступны через TDLib chat folders и переключаются клавишами `1`-`9`. -## Архитектура TDLib +## Практические правила -### Основные компоненты - -``` -┌─────────────────────────────────────────────────────────┐ -│ Your Application │ -├─────────────────────────────────────────────────────────┤ -│ ┌────────────┐ ┌──────────────┐ ┌────────────────┐ │ -│ │ Client │ │ Update Stream │ │ API Requests │ │ -│ └────────────┘ └──────────────┘ └────────────────┘ │ -├─────────────────────────────────────────────────────────┤ -│ TDLib Client │ -│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ -│ │ Auth State │ │ Local Cache │ │ API Handler │ │ -│ └──────────────┘ └──────────────┘ └──────────────┘ │ -├─────────────────────────────────────────────────────────┤ -│ Telegram Servers │ -└─────────────────────────────────────────────────────────┘ -``` - -### Поток работы - -1. **Инициализация** → TDLib запускается с параметрами -2. **Авторизация** → Проход через стейт-машину авторизации -3. **Синхронизация** → Загрузка базовых данных (чаты, контакты) -4. **Updates Stream** → Постоянный поток обновлений от сервера -5. **API Requests** → Запросы на получение данных / отправку сообщений - -## Процесс авторизации - -### Стейт-машина авторизации - -TDLib работает через систему состояний. Приложение получает обновления `updateAuthorizationState` и реагирует на них: - -``` -authorizationStateWaitTdlibParameters - ↓ (вызываем setTdlibParameters) -authorizationStateWaitPhoneNumber - ↓ (вызываем setAuthenticationPhoneNumber) -authorizationStateWaitCode - ↓ (вызываем checkAuthenticationCode) -authorizationStateWaitPassword (опционально, если 2FA) - ↓ (вызываем checkAuthenticationPassword) -authorizationStateReady ✓ -``` - -### Шаг 1: Получение API ключей - -Перед началом работы нужно: -1. Зайти на https://my.telegram.org -2. Войти с номером телефона -3. Перейти в "API development tools" -4. Создать приложение и получить `api_id` и `api_hash` - -### Шаг 2: Инициализация TDLib - -```rust -use tdlib::{functions, types}; - -async fn init_tdlib() { - // Параметры инициализации - let params = types::TdlibParameters { - database_directory: "./tdlib_db".to_string(), - use_message_database: true, - use_secret_chats: true, - api_id: env::var("API_ID").unwrap().parse().unwrap(), - api_hash: env::var("API_HASH").unwrap(), - system_language_code: "en".to_string(), - device_model: "Desktop".to_string(), - system_version: "Unknown".to_string(), - application_version: "0.1.0".to_string(), - enable_storage_optimizer: true, - ignore_file_names: false, - }; - - // Отправляем параметры - functions::set_tdlib_parameters(params, &client).await?; -} -``` - -### Шаг 3: Ввод номера телефона - -```rust -async fn authenticate_with_phone(phone: String, client: &Client) { - let phone_number = types::SetAuthenticationPhoneNumber { - phone_number: phone, - settings: None, - }; - - functions::set_authentication_phone_number(phone_number, client).await?; -} -``` - -### Шаг 4: Ввод кода подтверждения - -```rust -async fn verify_code(code: String, client: &Client) { - let check_code = types::CheckAuthenticationCode { - code, - }; - - functions::check_authentication_code(check_code, client).await?; -} -``` - -### Шаг 5: Ввод пароля 2FA (если включен) - -```rust -async fn verify_password(password: String, client: &Client) { - let check_password = types::CheckAuthenticationPassword { - password, - }; - - functions::check_authentication_password(check_password, client).await?; -} -``` - -## Получение списка чатов - -### Концепция чатов в TDLib - -TDLib автоматически кэширует чаты локально. Приложение должно: -1. Подписаться на обновления `updateNewChat` -2. Вызвать `loadChats()` для загрузки чатов -3. Поддерживать локальный кэш с сортировкой - -### Типы списков чатов - -- **Main** — основные чаты -- **Archive** — архивные чаты -- **Folder** — пользовательские папки - -### Загрузка чатов - -```rust -use tdlib::{functions, types}; - -async fn load_chats(client: &Client) -> Result> { - // Указываем тип списка (Main, Archive, или конкретная папка) - let chat_list = types::ChatList::Main; - - // Загружаем чаты - // limit - количество чатов для загрузки - functions::load_chats( - types::LoadChats { - chat_list: Some(chat_list), - limit: 50, - }, - client - ).await?; - - // После вызова loadChats, чаты будут приходить через updateNewChat - Ok(vec![]) -} -``` - -### Получение информации о чате - -```rust -async fn get_chat_info(chat_id: i64, client: &Client) -> Result { - let chat = functions::get_chat( - types::GetChat { chat_id }, - client - ).await?; - - Ok(chat) -} -``` - -### Сортировка чатов - -Чаты нужно сортировать по паре `(position.order, chat.id)` в порядке убывания: - -```rust -chats.sort_by(|a, b| { - let order_a = a.positions.get(0).map(|p| p.order).unwrap_or(0); - let order_b = b.positions.get(0).map(|p| p.order).unwrap_or(0); - - order_b.cmp(&order_a) - .then_with(|| b.id.cmp(&a.id)) -}); -``` - -## Получение истории сообщений - -### Загрузка сообщений из чата - -```rust -async fn get_chat_history( - chat_id: i64, - from_message_id: i64, - limit: i32, - client: &Client -) -> Result> { - let history = functions::get_chat_history( - types::GetChatHistory { - chat_id, - from_message_id, // 0 для последних сообщений - offset: 0, - limit, - only_local: false, - }, - client - ).await?; - - Ok(history.messages.unwrap_or_default()) -} -``` - -### Пагинация сообщений - -Сообщения возвращаются в обратном хронологическом порядке (новые → старые). - -Для загрузки следующей страницы: -```rust -// Первая загрузка (последние сообщения) -let messages = get_chat_history(chat_id, 0, 50, &client).await?; - -// Загрузка более старых сообщений -if let Some(oldest_msg) = messages.last() { - let older_messages = get_chat_history( - chat_id, - oldest_msg.id, - 50, - &client - ).await?; -} -``` - -## Обработка обновлений (Updates Stream) - -### Типы обновлений - -TDLib отправляет обновления через `Update` enum: - -- `UpdateNewMessage` — новое сообщение -- `UpdateMessageContent` — изменение контента сообщения -- `UpdateMessageSendSucceeded` — сообщение успешно отправлено -- `UpdateMessageSendFailed` — ошибка отправки -- `UpdateChatLastMessage` — изменилось последнее сообщение чата -- `UpdateChatPosition` — изменилась позиция чата в списке -- `UpdateNewChat` — новый чат добавлен -- `UpdateUser` — обновилась информация о пользователе -- `UpdateUserStatus` — изменился статус пользователя (онлайн/оффлайн) -- `UpdateChatReadInbox` — прочитаны входящие сообщения -- `UpdateChatReadOutbox` — прочитаны исходящие сообщения - -### Слушатель обновлений - -```rust -use tdlib::types::Update; - -async fn handle_updates(client: Client) { - loop { - match client.receive() { - Some(Update::NewMessage(update)) => { - println!("New message in chat {}: {}", - update.message.chat_id, - update.message.content - ); - } - Some(Update::MessageSendSucceeded(update)) => { - println!("Message sent successfully: {}", update.message.id); - } - Some(Update::UserStatus(update)) => { - println!("User {} is now {:?}", - update.user_id, - update.status - ); - } - Some(Update::NewChat(update)) => { - println!("New chat added: {}", update.chat.title); - } - _ => {} - } - } -} -``` - -## Отправка сообщений - -### Отправка текстового сообщения - -```rust -async fn send_message( - chat_id: i64, - text: String, - client: &Client -) -> Result { - let input_content = types::InputMessageContent::InputMessageText( - types::InputMessageText { - text: types::FormattedText { - text, - entities: vec![], - }, - disable_web_page_preview: false, - clear_draft: true, - } - ); - - let message = functions::send_message( - types::SendMessage { - chat_id, - message_thread_id: 0, - reply_to: None, - options: None, - reply_markup: None, - input_message_content: input_content, - }, - client - ).await?; - - Ok(message) -} -``` - -### Статусы доставки и прочтения - -Для отображения ✓ и ✓✓: - -```rust -fn get_message_status(message: &types::Message) -> &str { - if message.is_outgoing { - match &message.sending_state { - Some(types::MessageSendingState::Pending) => "", // отправляется - Some(types::MessageSendingState::Failed(_)) => "✗", // ошибка - None => { - // Отправлено успешно - if message.chat_id > 0 { // личный чат - // Проверяем, прочитано ли - // (нужно следить за UpdateChatReadOutbox) - "✓✓" // или "✓" если не прочитано - } else { - "✓" // групповой чат - } - } - } - } else { - "" // входящее сообщение - } -} -``` - -## Работа с папками (Folders) - -### Получение списка папок - -```rust -async fn get_chat_folders(client: &Client) -> Result> { - let folders = functions::get_chat_folders( - types::GetChatFolders {}, - client - ).await?; - - Ok(folders.chat_folders) -} -``` - -### Фильтрация чатов по папке - -```rust -async fn get_chats_in_folder(folder_id: i32, client: &Client) { - let chat_list = types::ChatList::Folder { - chat_folder_id: folder_id - }; - - functions::load_chats( - types::LoadChats { - chat_list: Some(chat_list), - limit: 50, - }, - client - ).await?; -} -``` - -## Архитектура приложения - -### Рекомендуемая структура - -``` -src/ -├── main.rs # Entry point, UI loop -├── tdlib/ -│ ├── mod.rs # TDLib module -│ ├── client.rs # Client wrapper -│ ├── auth.rs # Authentication logic -│ └── updates.rs # Update handlers -├── ui/ -│ ├── mod.rs -│ ├── app.rs # App state -│ ├── layout.rs # UI layout -│ └── components/ # UI components -└── models/ - ├── chat.rs # Chat models - └── message.rs # Message models -``` - -### Разделение ответственности - -1. **TDLib Client** — управление клиентом, запросы к API -2. **Update Handler** — обработка обновлений в фоне -3. **App State** — состояние приложения (чаты, сообщения, UI) -4. **UI Layer** — отрисовка интерфейса (ratatui) - -### Коммуникация между слоями - -```rust -// Используем каналы для коммуникации -use tokio::sync::mpsc; - -#[derive(Debug)] -enum AppEvent { - NewMessage(Message), - ChatUpdated(Chat), - UserStatusChanged(i64, UserStatus), -} - -#[tokio::main] -async fn main() { - // Канал для событий от TDLib - let (tx, mut rx) = mpsc::channel::(100); - - // Запускаем TDLib в отдельной задаче - tokio::spawn(async move { - run_tdlib_client(tx).await; - }); - - // Основной UI loop - loop { - // Проверяем события - while let Ok(event) = rx.try_recv() { - match event { - AppEvent::NewMessage(msg) => { - // Обновляем UI - } - _ => {} - } - } - - // Отрисовываем UI - terminal.draw(|f| ui(f, &app))?; - - // Обрабатываем ввод пользователя - handle_input()?; - } -} -``` - -## Пример: Минимальный клиент - -```rust -use tdlib::{Client, ClientState, functions, types}; -use tokio::sync::mpsc; - -#[tokio::main] -async fn main() -> Result<(), Box> { - // 1. Создаем клиент - let (sender, mut receiver) = mpsc::channel(100); - let client = Client::new(sender); - - // 2. Запускаем клиент - tokio::spawn(async move { - client.start().await; - }); - - // 3. Ждем авторизации - let mut authorized = false; - - while let Some(update) = receiver.recv().await { - match update { - types::Update::AuthorizationState(state) => { - match state.authorization_state { - types::AuthorizationState::WaitTdlibParameters => { - // Отправляем параметры - init_tdlib(&client).await?; - } - types::AuthorizationState::WaitPhoneNumber => { - // Запрашиваем номер у пользователя - let phone = read_phone_from_user(); - authenticate_with_phone(phone, &client).await?; - } - types::AuthorizationState::WaitCode(_) => { - // Запрашиваем код - let code = read_code_from_user(); - verify_code(code, &client).await?; - } - types::AuthorizationState::Ready => { - authorized = true; - break; - } - _ => {} - } - } - _ => {} - } - } - - // 4. Загружаем чаты - if authorized { - load_chats(&client).await?; - - // 5. Слушаем обновления - while let Some(update) = receiver.recv().await { - handle_update(update); - } - } - - Ok(()) -} -``` - -## Best Practices - -### 1. Кэширование -- Всегда включай `use_message_database: true` -- Храни кэш чатов и сообщений в памяти -- Используй `only_local: true` для быстрого доступа - -### 2. Обработка ошибок -- Все TDLib функции возвращают `Result` -- Обрабатывай потерю соединения -- Переподключайся при ошибках сети - -### 3. Производительность -- Не загружай все чаты сразу (используй пагинацию) -- Лимитируй количество сообщений в истории -- Используй `offset` для ленивой загрузки - -### 4. UI/UX -- Показывай индикаторы загрузки -- Кэшируй отрисованные элементы -- Обновляй UI только при изменениях - -## Полезные ссылки - -### Официальная документация -- [TDLib Getting Started](https://core.telegram.org/tdlib/getting-started) -- [TDLib Documentation](https://core.telegram.org/tdlib/docs/) - -### Rust библиотеки -- [rust-tdlib GitHub](https://github.com/antonio-antuan/rust-tdlib) -- [rust-tdlib docs.rs](https://docs.rs/rust-tdlib) -- [tdlib-rs GitHub](https://github.com/FedericoBruzzone/tdlib-rs) -- [tdlib-rs docs.rs](https://docs.rs/tdlib/latest/tdlib/) - -### API Reference -- [tdlib::functions](https://docs.rs/tdlib/latest/tdlib/functions/index.html) -- [tdlib::types](https://docs.rs/tdlib-types/latest/tdlib_types/types/index.html) - -## Следующие шаги - -1. ✅ Изучить документацию TDLib -2. ⬜ Добавить зависимость tdlib-rs в проект -3. ⬜ Реализовать модуль авторизации -4. ⬜ Реализовать загрузку чатов -5. ⬜ Реализовать загрузку сообщений -6. ⬜ Интегрировать с существующим UI -7. ⬜ Добавить отправку сообщений -8. ⬜ Реализовать обработку обновлений в реальном времени +- Не добавлять долгие `await` в UI loop без явной причины. +- Не смешивать updates разных `client_id`. +- Не обходить `TdClientTrait` в коде, который должен тестироваться. +- Не хранить TDLib database в корне проекта; использовать XDG account paths. -- 2.49.1