@emaxe/tuigram 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -13,6 +13,47 @@ and this project adheres to [Semantic Versioning](https://semver.org/).
13
13
 
14
14
  - English localization of the TUI and CLI interface strings.
15
15
 
16
+ ## [1.4.0] — 2026-09-01
17
+
18
+ ### Added
19
+
20
+ - Message selection in the chat feed: a left click marks a message with a colored `▌` bar
21
+ along all of its lines. `↑` / `↓` (and `k` / `j`) move the selection between messages and
22
+ scroll it into view; reaching the topmost message loads the previous page of history.
23
+ - The message action menu now opens with the right mouse button, and with `[Enter]` on the
24
+ selected message. Previously any click opened it, so a message could not simply be picked.
25
+ - The selected message became the target for actions: `[Ctrl+R]` replies to it, `[Ctrl+E]`
26
+ edits it (when it is yours), `[Ctrl+A]` and the status bar `[Ctrl+A]` button open the menu
27
+ for it. With nothing selected the previous behaviour applies — the last message in the chat.
28
+ - Full-screen image viewer opened by clicking an image preview: the thumbnail that arrived
29
+ with the message is shown instantly, then replaced by the full-size version. For videos and
30
+ documents the largest thumbnail is used and the file itself is never downloaded. Close with
31
+ `[Esc]`, `[Q]`, `[Enter]` or a click; the image is re-rendered when the terminal is resized.
32
+ - The `[Ctrl+A]` shortcut (action menu) now works from every panel, including the input box.
33
+ - `[F12]` temporarily hands the mouse back to the terminal so text can be selected and
34
+ copied, and restores the capture when pressed again.
35
+
36
+ ### Fixed
37
+
38
+ - A modal window was closed by the very click that opened it: blessed delivers a click to the
39
+ element first and to the screen immediately after. As a result the status bar buttons
40
+ `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`, the header logo and the chat title did not work
41
+ with the mouse at all, and the action menu only stayed open when the click happened to land
42
+ inside the future modal rectangle.
43
+ - Clicking a message hit the wrong message in a scrolled feed: the position was read from
44
+ `getScroll()`, which adds a blessed-internal offset to the actual content shift.
45
+ - The mouse wheel scrolled the feed twice per notch (a custom handler on top of the built-in
46
+ blessed one), and scrolling up from the bottom of the feed did nothing for the first notches.
47
+ - `PageUp` / `PageDown` scrolled the feed twice as far while it had focus.
48
+ - Clicks on the input hints were off by about 10 cells: clicking `[Ctrl+E] Правка` triggered
49
+ commands, and clicking the "Введите сообщение…" text switched to reply mode.
50
+ - A right click behaved like a left one: it opened chats in the list and pressed status bar
51
+ buttons and filter tabs.
52
+ - The feed layout map drifted by one line per message, so click coordinates diverged from the
53
+ actual text the longer the history grew.
54
+ - A full-screen image render could evict and replace the same image's thumbnail in the feed:
55
+ the render size is now part of the pseudo-graphics cache key.
56
+
16
57
  ## [1.3.0] — 2026-09-01
17
58
 
18
59
  ### Added
@@ -135,7 +176,8 @@ First public release.
135
176
  - A WCAG contrast test (3:1 threshold) applied to every theme **after** conversion
136
177
  to xterm-256 — the colors are checked exactly as the user sees them.
137
178
 
138
- [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.3.0...HEAD
179
+ [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.4.0...HEAD
180
+ [1.4.0]: https://github.com/emaxe/tuigram/compare/v1.3.0...v1.4.0
139
181
  [1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
140
182
  [1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
141
183
  [1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
package/CHANGELOG.ru.md CHANGED
@@ -13,6 +13,51 @@
13
13
 
14
14
  - Английская локализация строк интерфейса TUI и CLI.
15
15
 
16
+ ## [1.4.0] — 2026-09-01
17
+
18
+ ### Добавлено
19
+
20
+ - Выделение сообщений в ленте чата: левый клик мышью помечает сообщение цветной полосой `▌`
21
+ слева от всех его строк. Стрелки `↑` / `↓` (и `k` / `j`) перемещают выделение между
22
+ сообщениями с автоподкруткой ленты; на самом верхнем сообщении подгружается предыдущая
23
+ страница истории.
24
+ - Меню действий над сообщением открывается правой кнопкой мыши, а также `[Enter]` над
25
+ выделенным сообщением. Прежде меню открывалось любым кликом, из-за чего сообщение нельзя
26
+ было просто выбрать.
27
+ - Выделенное сообщение стало целью действий: `[Ctrl+R]` отвечает именно на него, `[Ctrl+E]`
28
+ правит его (если оно ваше), `[Ctrl+A]` и кнопка `[Ctrl+A]` строки состояния открывают меню
29
+ для него. Без выделения действует прежнее поведение — последнее сообщение чата.
30
+ - Полноэкранный просмотр изображений по клику на превью в сообщении: сначала мгновенно
31
+ показывается пришедшая с сообщением миниатюра, затем она заменяется полноразмерной
32
+ версией. У видео и документов берётся самая крупная миниатюра, сам файл не качается.
33
+ Закрытие — `[Esc]`, `[Q]`, `[Enter]` или клик; при изменении размера терминала
34
+ изображение перерисовывается.
35
+ - Глобальное сочетание `[Ctrl+A]` (меню действий) теперь работает из любой панели, включая
36
+ поле ввода.
37
+ - Клавиша `[F12]` временно отдаёт мышь терминалу, чтобы можно было выделить и скопировать
38
+ текст, и возвращает захват повторным нажатием.
39
+
40
+ ### Исправлено
41
+
42
+ - Модальное окно закрывалось тем же кликом, которым его открыли: blessed рассылает клик
43
+ сначала элементу, а сразу за ним — экрану. Из-за этого мышью вовсе не работали кнопки
44
+ строки состояния `[F1]`, `[Ctrl+A]`, `[Ctrl+P]`, `[Ctrl+Q]`, логотип и заголовок чата в
45
+ шапке, а меню действий открывалось лишь при попадании клика в будущий прямоугольник окна.
46
+ - Клик по сообщению попадал не в то сообщение при прокрученной ленте: позиция бралась из
47
+ `getScroll()`, которая складывает сдвиг содержимого со служебным смещением blessed.
48
+ - Колесо мыши прокручивало ленту дважды за щелчок (собственный обработчик поверх
49
+ встроенного в blessed), а прокрутка вверх от нижнего края ленты первые несколько щелчков
50
+ не работала вовсе.
51
+ - `PageUp` / `PageDown` прокручивали ленту на удвоенное расстояние, когда она была в фокусе.
52
+ - Клики по подсказкам поля ввода были смещены примерно на 10 ячеек: клик по `[Ctrl+E] Правка`
53
+ вызывал команды, а клик по тексту «Введите сообщение…» включал режим ответа.
54
+ - Правый клик срабатывал как левый: открывал чат в списке, нажимал кнопки строки состояния
55
+ и вкладки фильтров.
56
+ - Разметка ленты «съезжала» на одну строку с каждым сообщением, из-за чего карта координат
57
+ расходилась с реальным текстом тем сильнее, чем длиннее история.
58
+ - Полноэкранный рендер картинки мог вытеснить и подменить её же миниатюру в ленте: размер
59
+ теперь входит в ключ кэша псевдографики.
60
+
16
61
  ## [1.3.0] — 2026-09-01
17
62
 
18
63
  ### Добавлено
@@ -137,7 +182,8 @@
137
182
  - Тест контраста по WCAG (порог 3:1) для каждой темы — **после** конверсии в xterm-256,
138
183
  то есть ровно в том виде, в каком цвет увидит пользователь.
139
184
 
140
- [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.3.0...HEAD
185
+ [Unreleased]: https://github.com/emaxe/tuigram/compare/v1.4.0...HEAD
186
+ [1.4.0]: https://github.com/emaxe/tuigram/compare/v1.3.0...v1.4.0
141
187
  [1.3.0]: https://github.com/emaxe/tuigram/compare/v1.2.0...v1.3.0
142
188
  [1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
143
189
  [1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
package/README.md CHANGED
@@ -71,7 +71,8 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
71
71
 
72
72
  - **A complete interactive TUI**:
73
73
  - Two-pane adaptive layout (dialog list on the left, history and input box on the right);
74
- - Keyboard and mouse control (clicks, wheel scrolling);
74
+ - Keyboard and mouse control (left click selects a message, right click opens the action
75
+ menu, clicking an image opens it full screen, wheel scrolling);
75
76
  - Chat categories: All, Direct messages, Groups, Channels, Bots, Unread;
76
77
  - Instant search and filtering of chats by title and `@username` (`/`);
77
78
  - Live typing indicator ("… is typing");
@@ -433,22 +434,26 @@ node scripts/check-package.js # verifies that .env and the session do not leak
433
434
  |---|---|---|
434
435
  | `Tab` / `Shift+Tab` | Global | Cycle focus: dialog list → message feed → input box |
435
436
  | `↑` / `↓` | Dialog list | Select a chat |
437
+ | `↑` / `↓`, `k` / `j` | Message feed | Move the selection between messages |
436
438
  | `Enter` | Dialog list | Open the selected chat and load its history |
439
+ | `Enter` | Message feed | Action menu for the selected message |
437
440
  | `1` .. `6` | Dialog list | Switch category: `1:All`, `2:DM`, `3:Groups`, `4:Channels`, `5:Bots`, `6:Unread` |
438
441
  | `/` | Dialog list | Search / filter chats |
439
442
  | `Enter` | Input box | Send the typed message |
440
443
  | `Ctrl+J` | Input box | Insert a line break without sending |
441
- | `Ctrl+R` | Chat / Input | Reply to the last message |
442
- | `Ctrl+E` | Chat / Input | Edit your own last message |
443
- | `Ctrl+A` | Messages | Context action menu (reactions, delete, download, reply) |
444
+ | `Ctrl+R` | Chat / Input | Reply to the selected message, or the last one |
445
+ | `Ctrl+E` | Chat / Input | Edit the selected message of yours, or your last one |
446
+ | `Ctrl+A` | Global | Context action menu (reactions, delete, download, reply) |
444
447
  | `Ctrl+O` | Global | Send a file / photo / document |
445
448
  | `Ctrl+F` | Send dialog | File browser (navigate folders) |
446
449
  | `Ctrl+D` | Send dialog | Send uncompressed, as a document |
447
450
  | `Ctrl+P` | Global | Info about the current chat (ID, members, links) |
448
451
  | `PageUp` / `Ctrl+U` | History | Scroll up / load older history |
449
452
  | `PageDown` / `Ctrl+D` | History | Scroll down |
453
+ | `Home` / `End` | History | Jump to the top (loads history) / to the last message |
450
454
  | `Esc` | Modals | Close the modal / cancel reply or edit |
451
455
  | `F1` or `?` | Global | Help window with every shortcut |
456
+ | `F12` | Global | Hand the mouse to the terminal for text selection (press again to return) |
452
457
  | `Ctrl+Q` / `Ctrl+C` | Global | Safely exit the client |
453
458
 
454
459
  ---
@@ -459,20 +464,51 @@ TuiGram fully supports mouse interaction in terminals that support mouse reporti
459
464
 
460
465
  | Action | Target | Result |
461
466
  |---|---|---|
462
- | Click | Left / Middle / Bottom panel | Focus the clicked panel |
463
- | Click | Chat item in dialog list | Immediately select and open the chat |
464
- | Click | Filter tabs `1:Все` .. `6:Непроч` | Switch chat category filter |
465
- | Click | Search bar `[/]` | Focus search box and clear placeholder |
467
+ | Left click | Left / Middle / Bottom panel | Focus the clicked panel |
468
+ | Left click | Chat item in dialog list | Immediately select and open the chat |
469
+ | Left click | Filter tabs `1:Все` .. `6:Непроч` | Switch chat category filter |
470
+ | Left click | Search bar `[/]` | Focus search box and clear placeholder |
466
471
  | Wheel scroll | Dialog list / Message feed | Smooth scroll (scrolling up to the top loads older messages) |
467
- | Click | Any message line | Open message action menu (reactions, reply, edit, download, delete) |
468
- | Click | Header logo 🚀 | Open Help window |
469
- | Click | Header active chat title | Open Chat Info window |
470
- | Click | Header connection badge | Show active network status |
471
- | Click | Input context bar | Cancel reply/edit mode or insert `/` |
472
- | Click | Status bar items | Trigger corresponding action (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]`, etc.) |
472
+ | **Left click** | **Message in the feed** | **Select the message a `▌` bar marks it on the left** |
473
+ | **Right click** | **Message in the feed** | **Open the action menu (reactions, reply, edit, download, delete)** |
474
+ | **Left click** | **Image preview inside a message** | **Open the image full screen** |
475
+ | Left click | Empty area of the feed | Focus the message feed |
476
+ | Left click | Header logo 🚀 | Open Help window |
477
+ | Left click | Header active chat title | Open Chat Info window |
478
+ | Left click | Header connection badge | Show active network status |
479
+ | Left click | Input context bar | Cancel reply/edit mode, trigger `[Ctrl+R]` / `[Ctrl+E]` or insert `/` |
480
+ | Left click | Status bar items | Trigger corresponding action (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]`, etc.) |
473
481
  | Click | Outside any modal window | Dismiss / close modal |
474
482
  | Click | Buttons in modals | Click buttons (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, toggle checkboxes) |
475
483
 
484
+ ### Message selection
485
+
486
+ A left click selects a message: a colored `▌` bar appears to the left of all its lines.
487
+ `↑` / `↓` (and `k` / `j`) move the selection between messages and scroll it into view;
488
+ reaching the topmost message loads the previous page of history. Plain scrolling stays on
489
+ `PageUp` / `PageDown`, the wheel and `Home` / `End`.
490
+
491
+ The selected message becomes the target for actions: `[Enter]` and right click open the
492
+ action menu, `[Ctrl+R]` replies to it, `[Ctrl+E]` edits it (when it is yours). With nothing
493
+ selected these actions apply to the last message in the chat, as before.
494
+
495
+ ### Full-screen image viewer
496
+
497
+ A left click on a preview opens the image across the whole terminal. The thumbnail that
498
+ already arrived with the message is shown instantly, then it is replaced by the full-size
499
+ version as soon as it downloads. For videos and documents the largest available thumbnail
500
+ is shown — the file itself is never downloaded. Close with `[Esc]`, `[Q]`, `[Enter]` or a
501
+ click anywhere.
502
+
503
+ ### Right click and terminal text selection
504
+
505
+ > **macOS Terminal.app** captures the right click for its own context menu and never passes
506
+ > it to the application. Use `[Enter]` or `[Ctrl+A]` on the selected message there. Right
507
+ > click works in iTerm2, Ghostty, Alacritty, Kitty and Windows Terminal.
508
+
509
+ While the app captures the mouse, the terminal cannot select text for copying. Press `[F12]`
510
+ to hand the mouse back to the terminal, and `[F12]` again to return control to the interface.
511
+
476
512
  ---
477
513
 
478
514
  ## 💬 Slash commands in the input box
package/README.ru.md CHANGED
@@ -67,7 +67,8 @@
67
67
 
68
68
  - **Полноценный интерактивный TUI**:
69
69
  - Двухпанельный адаптивный интерфейс (список чатов слева + история и поле ввода справа);
70
- - Поддержка управления клавиатурой и мышью (клики, прокрутка колесом);
70
+ - Поддержка управления клавиатурой и мышью (левый клик — выделение сообщения,
71
+ правый — меню действий, клик по картинке — просмотр на весь экран, колесо — прокрутка);
71
72
  - Категории чатов: Все, Личные (ЛС), Группы, Каналы, Боты, Непрочитанные;
72
73
  - Мгновенный поиск и фильтрация чатов по названию и `@username` (`/`);
73
74
  - Отображение статуса набора текста («Собеседник печатает...») в реальном времени;
@@ -425,22 +426,26 @@ node scripts/check-package.js # проверка, что в пакет не у
425
426
  |---|---|---|
426
427
  | `Tab` / `Shift+Tab` | Глобально | Фокус по кругу: Список диалогов → Лента сообщений → Поле ввода |
427
428
  | `↑` / `↓` | Список диалогов | Выбор чата |
429
+ | `↑` / `↓`, `k` / `j` | Лента сообщений | Перемещение выделения между сообщениями |
428
430
  | `Enter` | Список диалогов | Открыть выбранный чат и загрузить историю |
431
+ | `Enter` | Лента сообщений | Меню действий над выделенным сообщением |
429
432
  | `1` .. `6` | Список диалогов | Переключение категорий: `1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч` |
430
433
  | `/` | Список диалогов | Поиск / фильтрация чатов |
431
434
  | `Enter` | Поле ввода | Отправить набранное сообщение |
432
435
  | `Ctrl+J` | Поле ввода | Перенос строки без отправки |
433
- | `Ctrl+R` | Чат / Ввод | Ответить (Reply) на последнее сообщение |
434
- | `Ctrl+E` | Чат / Ввод | Редактировать своё последнее сообщение |
435
- | `Ctrl+A` | Сообщения | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
436
+ | `Ctrl+R` | Чат / Ввод | Ответить (Reply) на выделенное сообщение, иначе — на последнее |
437
+ | `Ctrl+E` | Чат / Ввод | Редактировать выделенное своё сообщение, иначе — последнее своё |
438
+ | `Ctrl+A` | Глобально | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
436
439
  | `Ctrl+O` | Глобально | Отправить файл / фото / документ |
437
440
  | `Ctrl+F` | Окно отправки | Обзор файлов (навигация по папкам) |
438
441
  | `Ctrl+D` | Окно отправки | Отправить без сжатия, документом |
439
442
  | `Ctrl+P` | Глобально | Информация о текущем чате (ID, участники, ссылки) |
440
443
  | `PageUp` / `Ctrl+U`| История | Прокрутка вверх / подгрузка старой истории |
441
444
  | `PageDown` / `Ctrl+D`| История | Прокрутка вниз |
445
+ | `Home` / `End` | История | В начало ленты (подгрузка истории) / к последнему сообщению |
442
446
  | `Esc` | Модальные окна | Закрыть модальное окно / отменить Reply/Edit |
443
447
  | `F1` или `?` | Глобально | Окно справки со всеми горячими клавишами |
448
+ | `F12` | Глобально | Отдать мышь терминалу для выделения текста (повторно — вернуть) |
444
449
  | `Ctrl+Q` / `Ctrl+C` | Глобально | Безопасный выход из клиента |
445
450
 
446
451
  ---
@@ -451,20 +456,52 @@ TuiGram полноценно поддерживает управление мы
451
456
 
452
457
  | Действие | Где | Результат |
453
458
  |---|---|---|
454
- | Клик | Левая / Центральная / Нижняя панель | Переключение фокуса на выбранную панель |
455
- | Клик | Диалог в списке чатов | Мгновенный выбор и открытие чата |
456
- | Клик | Вкладки `1:Все` .. `6:Непроч` | Переключение фильтра категории чатов |
457
- | Клик | Поле поиска `[/]` | Фокус в поиск с очисткой подсказки |
459
+ | Левый клик | Левая / Центральная / Нижняя панель | Переключение фокуса на выбранную панель |
460
+ | Левый клик | Диалог в списке чатов | Мгновенный выбор и открытие чата |
461
+ | Левый клик | Вкладки `1:Все` .. `6:Непроч` | Переключение фильтра категории чатов |
462
+ | Левый клик | Поле поиска `[/]` | Фокус в поиск с очисткой подсказки |
458
463
  | Колесо мыши | Список чатов / Лента сообщений | Плавная прокрутка (прокрутка вверх в ленте подгружает историю) |
459
- | Клик | Любое сообщение | Открытие меню действий (реакции, ответ, редактирование, удаление) |
460
- | Клик | Логотип 🚀 в шапке | Открытие окна справки |
461
- | Клик | Название активного чата в шапке | Открытие информации о текущем чате |
462
- | Клик | Индикатор статуса в шапке | Просмотр статуса MTProto подключения |
463
- | Клик | Контекстная плашка ввода | Сброс режима ответа/редактирования или вставка `/` |
464
- | Клик | Кнопки строки состояния | Быстрый вызов действий (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]` и др.) |
464
+ | **Левый клик** | **Сообщение в ленте** | **Выделение сообщения оно помечается полосой `▌` слева** |
465
+ | **Правый клик** | **Сообщение в ленте** | **Меню действий (реакции, ответ, редактирование, скачивание, удаление)** |
466
+ | **Левый клик** | **Превью изображения в сообщении** | **Открытие картинки на весь экран** |
467
+ | Левый клик | Пустое место в ленте | Переключение фокуса на ленту сообщений |
468
+ | Левый клик | Логотип 🚀 в шапке | Открытие окна справки |
469
+ | Левый клик | Название активного чата в шапке | Открытие информации о текущем чате |
470
+ | Левый клик | Индикатор статуса в шапке | Просмотр статуса MTProto подключения |
471
+ | Левый клик | Контекстная плашка ввода | Сброс режима ответа/редактирования, `[Ctrl+R]`, `[Ctrl+E]` или вставка `/` |
472
+ | Левый клик | Кнопки строки состояния | Быстрый вызов действий (`[Tab]`, `[F1]`, `[Ctrl+A]`, `[Ctrl+Q]` и др.) |
465
473
  | Клик | Мимо любого модального окна | Закрытие модального окна |
466
474
  | Клик | Кнопки в модалках | Нажатие кнопки (`[ Отправить ]`, `[ Отмена ]`, `[ Закрыть ]`, чекбоксы) |
467
475
 
476
+ ### Выделение сообщений
477
+
478
+ Левый клик выделяет сообщение: слева от всех его строк появляется цветная полоса `▌`.
479
+ Стрелки `↑` / `↓` (и `k` / `j`) в ленте двигают выделение между сообщениями и подкручивают
480
+ ленту к выбранному; дойдя до самого верхнего сообщения, клиент подгружает предыдущую
481
+ страницу истории. Прокрутка при этом остаётся на `PageUp` / `PageDown`, колесе и `Home` / `End`.
482
+
483
+ Выделенное сообщение становится целью действий: `[Enter]` и правый клик открывают меню
484
+ действий, `[Ctrl+R]` отвечает именно на него, `[Ctrl+E]` правит его (если оно ваше).
485
+ Без выделения все эти действия, как и раньше, применяются к последнему сообщению чата.
486
+
487
+ ### Просмотр изображений на весь экран
488
+
489
+ Левый клик по превью открывает изображение во весь терминал. Сначала мгновенно
490
+ показывается миниатюра, уже пришедшая вместе с сообщением, затем она заменяется
491
+ полноразмерной версией, как только та скачается. У видео и документов показывается
492
+ самая крупная миниатюра — сам файл не качается. Закрыть: `[Esc]`, `[Q]`, `[Enter]`
493
+ или клик в любом месте.
494
+
495
+ ### Правая кнопка и выделение текста терминалом
496
+
497
+ > **macOS Terminal.app** перехватывает правый клик под собственное контекстное меню и не
498
+ > передаёт его приложению. Там пользуйтесь `[Enter]` или `[Ctrl+A]` над выделенным
499
+ > сообщением. В iTerm2, Ghostty, Alacritty, Kitty и Windows Terminal правый клик работает.
500
+
501
+ Пока мышь захвачена приложением, терминал не даёт выделять текст для копирования.
502
+ Нажмите `[F12]`, чтобы временно отдать мышь терминалу, и `[F12]` ещё раз, чтобы вернуть
503
+ управление интерфейсу.
504
+
468
505
  ---
469
506
 
470
507
  ## 💬 Слэш-команды в поле ввода
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@emaxe/tuigram",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Полнофункциональный TUI & CLI клиент Telegram на Node.js на базе MTProto (teleproto)",
5
5
  "type": "module",
6
6
  "exports": {
@@ -6,6 +6,39 @@ import { renderStrippedThumbnail, renderImageBuffer } from "../utils/image.js";
6
6
 
7
7
  const { FloodWaitError } = errors;
8
8
 
9
+ /**
10
+ * Синхронно рисует миниатюру, встроенную в само сообщение (PhotoStrippedSize).
11
+ * Сеть не нужна — байты уже пришли вместе с сообщением, поэтому картинку можно
12
+ * показать мгновенно, пока качается полноразмерная версия.
13
+ * @param {object} rawMessage
14
+ * @param {object} [options]
15
+ * @param {number} [options.maxWidth]
16
+ * @param {number} [options.maxHeight]
17
+ * @param {boolean} [options.useCache=true] класть результат в кэш псевдографики
18
+ * @returns {string} разметка blessed или пустая строка
19
+ */
20
+ export function renderMessageThumbnail(rawMessage, {
21
+ maxWidth = config.imageMaxWidth,
22
+ maxHeight = config.imageMaxHeight,
23
+ useCache = true,
24
+ } = {}) {
25
+ const media = rawMessage?.media;
26
+ if (!media) return "";
27
+
28
+ const photo = media.photo;
29
+ const doc = media.document;
30
+ const sizes = photo?.sizes || doc?.thumbs || [];
31
+ const stripped = sizes.find((s) => s?.className === "PhotoStrippedSize" || s?.type === "i" || (s?.bytes && s.bytes.length > 0));
32
+ if (!stripped?.bytes) return "";
33
+
34
+ // Полноэкранные рендеры не кэшируем: одна такая строка весит сотни килобайт
35
+ const cacheKey = useCache
36
+ ? (photo?.id ? `photo_${photo.id}` : (doc?.id ? `doc_${doc.id}` : `msg_${rawMessage.id}`))
37
+ : undefined;
38
+
39
+ return renderStrippedThumbnail(stripped.bytes, { maxWidth, maxHeight, cacheKey }) || "";
40
+ }
41
+
9
42
  /**
10
43
  * Преобразует объект Message из MTProto в нормализованный объект для TUI.
11
44
  * @param {object} message
@@ -41,25 +74,9 @@ export function normalizeMessage(message) {
41
74
  }
42
75
 
43
76
  // Извлечение и рендеринг PhotoStrippedSize в псевдографику
44
- let imagePreview = null;
45
- if (config.showImages && message.media) {
46
- const media = message.media;
47
- const photo = media.photo;
48
- const doc = media.document;
49
- const sizes = photo?.sizes || doc?.thumbs || [];
50
- const stripped = sizes.find((s) => s?.className === "PhotoStrippedSize" || s?.type === "i" || (s?.bytes && s.bytes.length > 0));
51
-
52
- if (stripped?.bytes) {
53
- const cacheKey = photo?.id
54
- ? `photo_${photo.id}`
55
- : (doc?.id ? `doc_${doc.id}` : `msg_${message.id}`);
56
- imagePreview = renderStrippedThumbnail(stripped.bytes, {
57
- maxWidth: config.imageMaxWidth,
58
- maxHeight: config.imageMaxHeight,
59
- cacheKey,
60
- }) || null;
61
- }
62
- }
77
+ const imagePreview = config.showImages
78
+ ? (renderMessageThumbnail(message) || null)
79
+ : null;
63
80
 
64
81
  return {
65
82
  id: message.id,
@@ -288,6 +305,68 @@ export async function markAsRead(client, rawPeer, maxId = 0) {
288
305
  }
289
306
  }
290
307
 
308
+ /**
309
+ * Выбирает самую крупную растровую миниатюру документа.
310
+ * PhotoPathSize (SVG-контур) и PhotoStrippedSize непригодны для полноэкранного показа.
311
+ * @param {Array<object>} thumbs
312
+ * @returns {object|null}
313
+ */
314
+ function pickLargestThumb(thumbs) {
315
+ if (!Array.isArray(thumbs) || thumbs.length === 0) return null;
316
+
317
+ const weight = (t) => {
318
+ if (typeof t?.size === "number") return t.size;
319
+ if (Array.isArray(t?.sizes) && t.sizes.length > 0) return Math.max(...t.sizes);
320
+ return 0;
321
+ };
322
+
323
+ const usable = thumbs.filter((t) => t?.className !== "PhotoPathSize" && weight(t) > 0);
324
+ if (usable.length === 0) return null;
325
+
326
+ return usable.reduce((best, t) => (weight(t) > weight(best) ? t : best), usable[0]);
327
+ }
328
+
329
+ /**
330
+ * Загружает изображение сообщения в максимальном доступном качестве — для просмотра
331
+ * на весь экран.
332
+ *
333
+ * Фото качается оригиналом. У документа (видео, gif, файл) качается только самая
334
+ * крупная миниатюра: сам файл может весить сотни мегабайт и всё равно не рисуется.
335
+ *
336
+ * @param {import("teleproto").TelegramClient} client
337
+ * @param {object} rawMessage
338
+ * @returns {Promise<{ buffer: Buffer, mimeType: string }>}
339
+ */
340
+ export async function downloadImageBuffer(client, rawMessage) {
341
+ const media = rawMessage?.media;
342
+ if (!media) {
343
+ throw new Error("У сообщения нет изображения.");
344
+ }
345
+
346
+ if (media.className === "MessageMediaPhoto") {
347
+ // Без thumb teleproto отдаёт самый большой размер фотографии
348
+ const buffer = await client.downloadMedia(media, {});
349
+ if (!buffer || buffer.length === 0) {
350
+ throw new Error("Не удалось загрузить изображение.");
351
+ }
352
+ return { buffer, mimeType: "image/jpeg" };
353
+ }
354
+
355
+ if (media.className === "MessageMediaDocument") {
356
+ const thumb = pickLargestThumb(media.document?.thumbs);
357
+ if (!thumb) {
358
+ throw new Error("У вложения нет пригодной для показа миниатюры.");
359
+ }
360
+ const buffer = await client.downloadMedia(media, { thumb });
361
+ if (!buffer || buffer.length === 0) {
362
+ throw new Error("Не удалось загрузить миниатюру вложения.");
363
+ }
364
+ return { buffer, mimeType: "image/jpeg" };
365
+ }
366
+
367
+ throw new Error("Этот тип вложения нельзя показать как изображение.");
368
+ }
369
+
291
370
  /**
292
371
  * Асинхронно загружает и декодирует превью изображения сообщения через MTProto.
293
372
  * @param {import("teleproto").TelegramClient} client