@emaxe/tuigram 1.0.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/.env.example +29 -0
- package/LICENSE +21 -0
- package/README.md +364 -0
- package/bin/tuigram.js +2 -0
- package/package.json +73 -0
- package/src/cli/cliCommands.js +164 -0
- package/src/cli/formatters.js +72 -0
- package/src/cli/init.js +197 -0
- package/src/config.js +158 -0
- package/src/index.js +172 -0
- package/src/state.js +243 -0
- package/src/telegram/auth.js +146 -0
- package/src/telegram/client.js +96 -0
- package/src/telegram/dialogs.js +130 -0
- package/src/telegram/entities.js +158 -0
- package/src/telegram/formatter.js +248 -0
- package/src/telegram/listener.js +141 -0
- package/src/telegram/messages.js +265 -0
- package/src/ui/app.js +534 -0
- package/src/ui/components/chatList.js +295 -0
- package/src/ui/components/chatView.js +178 -0
- package/src/ui/components/header.js +82 -0
- package/src/ui/components/inputBox.js +221 -0
- package/src/ui/components/modals/actionModal.js +136 -0
- package/src/ui/components/modals/chatInfoModal.js +125 -0
- package/src/ui/components/modals/confirmModal.js +141 -0
- package/src/ui/components/modals/fileModal.js +355 -0
- package/src/ui/components/modals/filePickerModal.js +214 -0
- package/src/ui/components/modals/helpModal.js +131 -0
- package/src/ui/components/statusBar.js +64 -0
- package/src/ui/screen.js +60 -0
- package/src/ui/theme.js +223 -0
- package/src/utils/commands.js +40 -0
- package/src/utils/storage.js +177 -0
- package/src/utils/time.js +132 -0
package/.env.example
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# ── Telegram API Credentials ──────────────────────────────────────────────────
|
|
2
|
+
# Получить на https://my.telegram.org -> API development tools.
|
|
3
|
+
# Проще всего заполнить командой: tuigram init
|
|
4
|
+
TELEGRAM_API_ID=
|
|
5
|
+
TELEGRAM_API_HASH=
|
|
6
|
+
|
|
7
|
+
# ── Data & Storage Directories ────────────────────────────────────────────────
|
|
8
|
+
# По умолчанию файлы лежат в пользовательских директориях ОС и НЕ внутри пакета:
|
|
9
|
+
# настройки (этот файл): ~/.config/tuigram/.env
|
|
10
|
+
# %APPDATA%\tuigram\.env (Windows)
|
|
11
|
+
# сессия и загрузки: ~/.local/share/tuigram/
|
|
12
|
+
# %LOCALAPPDATA%\tuigram\ (Windows)
|
|
13
|
+
# Переопределить можно переменными ниже (раскомментируйте при необходимости).
|
|
14
|
+
# TUIGRAM_CONFIG_DIR=
|
|
15
|
+
# TUIGRAM_DATA_DIR=
|
|
16
|
+
|
|
17
|
+
# Устаревшая переменная: относительный путь разрешается от корня пакета.
|
|
18
|
+
# Оставлена для совместимости со старыми установками из клона репозитория.
|
|
19
|
+
# DATA_DIR=./data
|
|
20
|
+
|
|
21
|
+
# ── TUI UI Preferences ────────────────────────────────────────────────────────
|
|
22
|
+
# Тема интерфейса: default (тёмная), nord, light (светлая)
|
|
23
|
+
TUI_THEME=default
|
|
24
|
+
|
|
25
|
+
# Автоскролл к последнему сообщению при открытии чата (true/false)
|
|
26
|
+
AUTO_SCROLL=true
|
|
27
|
+
|
|
28
|
+
# Показывать уведомления о наборе текста (true/false)
|
|
29
|
+
SHOW_TYPING=true
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Maksim Klisin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
# 🚀 TuiGram
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@emaxe/tuigram)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](./LICENSE)
|
|
6
|
+
|
|
7
|
+
Полнофункциональный терминальный клиент Telegram (TUI + CLI) на Node.js на базе протокола MTProto (`teleproto`).
|
|
8
|
+
|
|
9
|
+
Позволяет полноценно общаться в Telegram прямо из терминала: просматривать список чатов с разделением по категориям, читать переписку с сохранением форматирования (жирный, курсив, ссылки, подсветка кода), отправлять сообщения, ответы (Reply), редактировать, удалять, ставить реакции и отправлять файлы.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
13
|
+
│ 🚀 TuiGram │ Maksim (@maksim) │ ● В сети │
|
|
14
|
+
│ 👥 Tech Chat [1420 уч.] ✍️ Alex печатает... │
|
|
15
|
+
├──────────────────────────────┬──────────────────────────────────────────────────────────────┤
|
|
16
|
+
│ [1:Все] [2:ЛС] [3:Группы]... │ ─────── Сегодня ─────── │
|
|
17
|
+
│ [/] Поиск чатов... │ │
|
|
18
|
+
│ 📌 👤 Pavel Durov · Let me… │ Alex [13:40] │
|
|
19
|
+
│ 👥 Tech Chat · Nice work! [3]│ Have you checked the latest release? │
|
|
20
|
+
│ 🤖 BotFather · Done! 12:10 │ Вы [13:42] ✓✓ │
|
|
21
|
+
│ 📢 News Channel · Дайд… [12] │ ┌─ Ответ на сообщение #1042 │
|
|
22
|
+
│ │ Yes, testing it right now! │
|
|
23
|
+
│ │ 👍 4 🔥 2 │
|
|
24
|
+
│ ├──────────────────────────────────────────────────────────────┤
|
|
25
|
+
│ │ ↩️ Ответ на [Alex]: "Have you checked..." [Esc: Отмена] │
|
|
26
|
+
│ │ Пишу ответ прямо здесь█ │
|
|
27
|
+
│ │ │
|
|
28
|
+
├──────────────────────────────┴──────────────────────────────────────────────────────────────┤
|
|
29
|
+
│ [Tab] Панель │ [Enter] Отправить │ [1-6] Вкладки │ [F1] Помощь │ [Ctrl+A] Действия │ [Ctrl+Q]│
|
|
30
|
+
└─────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ⚡ Особенности и возможности
|
|
36
|
+
|
|
37
|
+
- **Полноценный интерактивный TUI**:
|
|
38
|
+
- Двухпанельный адаптивный интерфейс (список чатов слева + история и поле ввода справа);
|
|
39
|
+
- Поддержка управления клавиатурой и мышью (клики, прокрутка колесом);
|
|
40
|
+
- Категории чатов: Все, Личные (ЛС), Группы, Каналы, Боты, Непрочитанные;
|
|
41
|
+
- Мгновенный поиск и фильтрация чатов по названию и `@username` (`/`);
|
|
42
|
+
- Отображение статуса набора текста («Собеседник печатает...») в реальном времени;
|
|
43
|
+
- Цветовое форматирование Telegram Entities (Bold, Italic, Monospace Code, URLs, Mentions, Spoilers);
|
|
44
|
+
- Индикаторы медиа-вложений (фото, видео, документы, голосовые, стикеры, опросы);
|
|
45
|
+
- Реакции на сообщения (👍, 🔥, ❤️);
|
|
46
|
+
- Бесконечная пагинация истории сообщений вверх (`PageUp` / `Ctrl+U`);
|
|
47
|
+
- Контекстные режимы быстрого ответа (Reply `Ctrl+R`) и редактирования (Edit `Ctrl+E`);
|
|
48
|
+
- Модальные окна: Справка (`F1` / `?`), Сведения о чате (`Ctrl+P`), Меню действий (`Ctrl+A`), Отправка файла (`Ctrl+O`).
|
|
49
|
+
- **Автономный CLI-режим**:
|
|
50
|
+
- Быстрая отправка сообщений и файлов из командной строки;
|
|
51
|
+
- Просмотр списка диалогов и истории в терминале;
|
|
52
|
+
- Потоковый стриминг живых обновлений.
|
|
53
|
+
- **Умная конфигурация**:
|
|
54
|
+
- Ключи и сессия хранятся в пользовательских директориях ОС — пакет остаётся read-only и переживает обновления;
|
|
55
|
+
- Безопасное хранение сессии и ключей (`chmod 0600`).
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 📦 Установка
|
|
60
|
+
|
|
61
|
+
### Вариант 1: глобальная установка из npm (рекомендуется)
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g @emaxe/tuigram
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
После этого команда `tuigram` доступна в любом каталоге.
|
|
68
|
+
|
|
69
|
+
### Вариант 2: разовый запуск без установки
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
npx @emaxe/tuigram
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Вариант 3: из исходников (для разработки)
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
git clone https://github.com/emaxe/tuigram.git
|
|
79
|
+
cd tuigram
|
|
80
|
+
npm install
|
|
81
|
+
npm start
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## ⚙️ Первый запуск
|
|
87
|
+
|
|
88
|
+
### 1. Ключи Telegram API
|
|
89
|
+
|
|
90
|
+
Получите `api_id` и `api_hash` на [https://my.telegram.org](https://my.telegram.org) (раздел *API development tools*), затем:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
tuigram init
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Команда спросит оба ключа и сохранит их с правами `0600`. Для скриптов и CI есть неинтерактивный вариант:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
tuigram init --api-id 1234567 --api-hash 0123456789abcdef0123456789abcdef
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Альтернатива — переменные окружения `TELEGRAM_API_ID` и `TELEGRAM_API_HASH`: они имеют приоритет над файлом настроек.
|
|
103
|
+
|
|
104
|
+
### 2. Авторизация и запуск
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
tuigram
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
При первом запуске TuiGram предложит ввести номер телефона, код подтверждения из Telegram и пароль 2FA (если включён). После этого сессия сохранится, и последующие запуски будут происходить мгновенно.
|
|
111
|
+
|
|
112
|
+
Отдельно авторизоваться можно командой `tuigram login`.
|
|
113
|
+
|
|
114
|
+
> **В неинтерактивной среде** (CI, пайп, `< /dev/null`) вход по телефону невозможен:
|
|
115
|
+
> `tuigram login` сразу завершится с понятной ошибкой вместо зависания. Если сессия
|
|
116
|
+
> уже есть — команда просто сообщит, под кем вы авторизованы, и вернёт код `0`.
|
|
117
|
+
> Для автоматизации положите готовый `session.txt` в директорию данных
|
|
118
|
+
> (путь подскажет `tuigram paths`).
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
## 📂 Где хранятся файлы
|
|
123
|
+
|
|
124
|
+
Ничего не пишется внутрь самого пакета — это важно при глобальной установке, где каталог `node_modules` обычно недоступен на запись и стирается при обновлении.
|
|
125
|
+
|
|
126
|
+
| Что | macOS / Linux | Windows |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| Настройки (`.env`) | `~/.config/tuigram/.env` | `%APPDATA%\tuigram\.env` |
|
|
129
|
+
| Сессия | `~/.local/share/tuigram/session.txt` | `%LOCALAPPDATA%\tuigram\session.txt` |
|
|
130
|
+
| Загрузки из чатов | `~/.local/share/tuigram/downloads/` | `%LOCALAPPDATA%\tuigram\downloads\` |
|
|
131
|
+
|
|
132
|
+
Посмотреть актуальные пути и состояние конфигурации:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
tuigram paths
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Переопределить расположение можно переменными `TUIGRAM_CONFIG_DIR` и `TUIGRAM_DATA_DIR` (учитываются также `XDG_CONFIG_HOME` / `XDG_DATA_HOME`).
|
|
139
|
+
|
|
140
|
+
**Приоритет настроек** (сверху вниз, первое найденное выигрывает):
|
|
141
|
+
1. переменные окружения процесса;
|
|
142
|
+
2. `.env` в корне проекта — только при запуске из клона репозитория;
|
|
143
|
+
3. `~/.config/tuigram/.env` — основной файл установленного CLI.
|
|
144
|
+
|
|
145
|
+
Сессия из старых установок (`<проект>/data/session.txt`) переносится в новое расположение автоматически при первом запуске — повторно логиниться не нужно.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 🛠 Разработка
|
|
150
|
+
|
|
151
|
+
При запуске из клона репозитория работает интерактивное меню-диспетчер:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
./run.sh # или npm run menu
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Оно даёт удобный выбор режима (TUI, логин, диалоги, отправка, тесты, очистка).
|
|
158
|
+
|
|
159
|
+
Тесты и проверка содержимого будущего npm-пакета:
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
npm test # юнит-тесты
|
|
163
|
+
node scripts/check-package.js # проверка, что в пакет не утекают .env и сессия
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## ⌨️ Горячие клавиши
|
|
169
|
+
|
|
170
|
+
| Сочетание клавиш | Область | Действие |
|
|
171
|
+
|---|---|---|
|
|
172
|
+
| `Tab` / `Shift+Tab` | Глобально | Фокус по кругу: Список диалогов → Лента сообщений → Поле ввода |
|
|
173
|
+
| `↑` / `↓` | Список диалогов | Выбор чата |
|
|
174
|
+
| `Enter` | Список диалогов | Открыть выбранный чат и загрузить историю |
|
|
175
|
+
| `1` .. `6` | Список диалогов | Переключение категорий: `1:Все`, `2:ЛС`, `3:Группы`, `4:Каналы`, `5:Боты`, `6:Непроч` |
|
|
176
|
+
| `/` | Список диалогов | Поиск / фильтрация чатов |
|
|
177
|
+
| `Enter` | Поле ввода | Отправить набранное сообщение |
|
|
178
|
+
| `Ctrl+J` | Поле ввода | Перенос строки без отправки |
|
|
179
|
+
| `Ctrl+R` | Чат / Ввод | Ответить (Reply) на последнее сообщение |
|
|
180
|
+
| `Ctrl+E` | Чат / Ввод | Редактировать своё последнее сообщение |
|
|
181
|
+
| `Ctrl+A` | Сообщения | Контекстное меню действий (Реакции, Удаление, Скачивание, Ответ) |
|
|
182
|
+
| `Ctrl+O` | Глобально | Отправить файл / фото / документ |
|
|
183
|
+
| `Ctrl+F` | Окно отправки | Обзор файлов (навигация по папкам) |
|
|
184
|
+
| `Ctrl+D` | Окно отправки | Отправить без сжатия, документом |
|
|
185
|
+
| `Ctrl+P` | Глобально | Информация о текущем чате (ID, участники, ссылки) |
|
|
186
|
+
| `PageUp` / `Ctrl+U`| История | Прокрутка вверх / подгрузка старой истории |
|
|
187
|
+
| `PageDown` / `Ctrl+D`| История | Прокрутка вниз |
|
|
188
|
+
| `Esc` | Модальные окна | Закрыть модальное окно / отменить Reply/Edit |
|
|
189
|
+
| `F1` или `?` | Глобально | Окно справки со всеми горячими клавишами |
|
|
190
|
+
| `Ctrl+Q` / `Ctrl+C` | Глобально | Безопасный выход из клиента |
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 💬 Слэш-команды в поле ввода
|
|
195
|
+
|
|
196
|
+
В поле ввода сообщения доступны быстрые команды (начинаются с `/`):
|
|
197
|
+
|
|
198
|
+
- `/help` — открыть окно помощи;
|
|
199
|
+
- `/info` — подробная информация о текущем чате;
|
|
200
|
+
- `/sendfile` — открыть окно отправки файла;
|
|
201
|
+
- `/sendfile <путь>` — отправить файл сразу;
|
|
202
|
+
- `/sendfile <путь> -- <подпись>` — файл с подписью;
|
|
203
|
+
- `/sendfile <путь> | <путь> -- <подпись>` — альбом из нескольких файлов;
|
|
204
|
+
- `/clear` — очистить экранную ленту сообщений;
|
|
205
|
+
- `/logout` — выйти из аккаунта Telegram.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## 📎 Отправка файлов и картинок
|
|
210
|
+
|
|
211
|
+
Три способа: окно `Ctrl+O`, слэш-команда `/sendfile`, консольная команда `sendfile`.
|
|
212
|
+
|
|
213
|
+
**Окно отправки (`Ctrl+O`)**
|
|
214
|
+
|
|
215
|
+
- `Ctrl+F` — обзор файлов: навигация по папкам стрелками, `Enter` — войти в папку или выбрать файл, `Esc` — назад. Внизу показывается размер подсвеченного файла.
|
|
216
|
+
- Путь можно и просто ввести: понимает `~/Desktop/foto.png`, пути в кавычках и с экранированными пробелами — то есть перетаскивание файла прямо в терминал работает.
|
|
217
|
+
- Несколько файлов — через `|` в поле пути (или добавляйте их по одному через обзор). До 10 штук уходят одним альбомом.
|
|
218
|
+
- `Ctrl+D` — отправить без сжатия, документом. Полезно, когда важно исходное качество картинки.
|
|
219
|
+
- Строка под чекбоксом сразу показывает, что именно уйдёт: `✓ photo.png · 2.4 MB · уйдёт как фото`.
|
|
220
|
+
- Если в этот момент активен режим ответа (`Ctrl+R`), файл уйдёт **ответом** на сообщение.
|
|
221
|
+
|
|
222
|
+
**Что уходит фото, а что документом**
|
|
223
|
+
|
|
224
|
+
`.png`, `.jpg`, `.jpeg` Telegram принимает как сжатое фото; видеоформаты (`.mp4`, `.mov`, `.mkv` и т.п.) — как видео; всё остальное, включая `.webp` и `.heic`, — документом. Флажок «Как файл, без сжатия» (`Ctrl+D`, в CLI — `--as-file`) заставляет отправить документом что угодно.
|
|
225
|
+
|
|
226
|
+
**Прогресс и отмена**
|
|
227
|
+
|
|
228
|
+
Во время загрузки в строке состояния идут проценты. `Esc` прерывает отправку.
|
|
229
|
+
|
|
230
|
+
**Скачивание входящих**
|
|
231
|
+
|
|
232
|
+
`Ctrl+A` на сообщении → «Скачать медиа-вложение». Файл сохранится в `data/downloads/`.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## 🎨 Темы оформления
|
|
237
|
+
|
|
238
|
+
Тема задаётся в `.env`:
|
|
239
|
+
|
|
240
|
+
```env
|
|
241
|
+
TUI_THEME=default # тёмная (по умолчанию)
|
|
242
|
+
TUI_THEME=nord # Nord
|
|
243
|
+
TUI_THEME=light # светлая
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Все цвета заданы hex-значениями и сводятся к палитре xterm-256 (индексы ≥ 16).
|
|
247
|
+
Именованные цвета (`blue`, `cyan`, `gray`) специально не используются: они
|
|
248
|
+
занимают индексы 0–15, которые тема терминала перекрашивает по-своему — из-за
|
|
249
|
+
этого синий фон мог рисоваться бирюзовым, а серый текст пропадать совсем.
|
|
250
|
+
|
|
251
|
+
Контраст каждой пары «текст на фоне» проверяется автотестом по WCAG (порог 3:1)
|
|
252
|
+
уже **после** конверсии в xterm-256, то есть ровно в том виде, в каком цвет
|
|
253
|
+
увидит пользователь.
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
## 🛠️ Использование через консоль (CLI)
|
|
258
|
+
|
|
259
|
+
TuiGram можно запускать в режиме консольных утилит (при запуске из клона
|
|
260
|
+
репозитория подставьте `node bin/tuigram.js` вместо `tuigram`):
|
|
261
|
+
|
|
262
|
+
```bash
|
|
263
|
+
# Авторизация
|
|
264
|
+
tuigram login
|
|
265
|
+
|
|
266
|
+
# Список диалогов
|
|
267
|
+
tuigram dialogs --limit 30
|
|
268
|
+
|
|
269
|
+
# Просмотр истории чата (@username, ID или me для «Избранного»)
|
|
270
|
+
tuigram history @durov --limit 20
|
|
271
|
+
tuigram history me
|
|
272
|
+
|
|
273
|
+
# Отправка текстового сообщения
|
|
274
|
+
tuigram send me "Привет из терминала!"
|
|
275
|
+
tuigram send @friend "Встречаемся в 18:00"
|
|
276
|
+
|
|
277
|
+
# Отправка файла (несколько путей уходят одним альбомом)
|
|
278
|
+
tuigram sendfile me ./screenshot.png
|
|
279
|
+
tuigram sendfile me ~/a.png ~/b.png --caption "Две картинки"
|
|
280
|
+
tuigram sendfile me ~/photo.png --as-file # без сжатия, документом
|
|
281
|
+
|
|
282
|
+
# Живой стрим обновлений в реальном времени
|
|
283
|
+
tuigram listen
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
---
|
|
287
|
+
|
|
288
|
+
## 📁 Структура проекта
|
|
289
|
+
|
|
290
|
+
```
|
|
291
|
+
TuiGram/
|
|
292
|
+
├── bin/
|
|
293
|
+
│ └── tuigram.js # CLI исполняемый файл
|
|
294
|
+
├── src/
|
|
295
|
+
│ ├── index.js # Главная точка входа (TUI / CLI роутер)
|
|
296
|
+
│ ├── config.js # Пути пользовательских директорий и загрузчик .env
|
|
297
|
+
│ ├── state.js # Реактивное централизованное хранилище состояния
|
|
298
|
+
│ ├── telegram/
|
|
299
|
+
│ │ ├── client.js # Создание и управление MTProto клиентом
|
|
300
|
+
│ │ ├── auth.js # Интерактивный логин-визард и 2FA
|
|
301
|
+
│ │ ├── dialogs.js # Получение, фильтрация и поиск диалогов
|
|
302
|
+
│ │ ├── messages.js # Загрузка истории, отправка, правка, файлы, реакции
|
|
303
|
+
│ │ ├── listener.js # Живой фоновый слушатель событий MTProto
|
|
304
|
+
│ │ ├── entities.js # Парсинг пиров, типы чатов и кэш сущностей
|
|
305
|
+
│ │ └── formatter.js # Telegram Entities -> Blessed ANSI форматирование
|
|
306
|
+
│ ├── ui/
|
|
307
|
+
│ │ ├── screen.js # Управление Blessed Screen
|
|
308
|
+
│ │ ├── theme.js # Темы оформления (Default Dark, Nord, Light)
|
|
309
|
+
│ │ ├── app.js # Главный координатор интерфейса
|
|
310
|
+
│ │ └── components/
|
|
311
|
+
│ │ ├── header.js # Верхняя шапка и статус подключения
|
|
312
|
+
│ │ ├── chatList.js # Список диалогов со скроллом, табами и поиском
|
|
313
|
+
│ │ ├── chatView.js # Лента сообщений с автоскроллом и форматированием
|
|
314
|
+
│ │ ├── inputBox.js # Поле ввода с плашкой Reply/Edit и историей
|
|
315
|
+
│ │ ├── statusBar.js # Нижняя строка подсказок и тостов
|
|
316
|
+
│ │ └── modals/
|
|
317
|
+
│ │ ├── helpModal.js # Окно справки
|
|
318
|
+
│ │ ├── chatInfoModal.js # Окно информации о чате
|
|
319
|
+
│ │ ├── actionModal.js # Меню действий с сообщением
|
|
320
|
+
│ │ ├── fileModal.js # Диалог отправки файла
|
|
321
|
+
│ │ └── confirmModal.js # Диалог подтверждения
|
|
322
|
+
│ ├── cli/
|
|
323
|
+
│ │ ├── cliCommands.js # Автономные CLI команды
|
|
324
|
+
│ │ ├── init.js # tuigram init / paths — настройка и диагностика
|
|
325
|
+
│ │ └── formatters.js # Консольные форматтеры таблиц и логов
|
|
326
|
+
│ └── utils/
|
|
327
|
+
│ ├── storage.js # Файловые операции и сохранение сессий
|
|
328
|
+
│ └── time.js # Форматирование времени и дат
|
|
329
|
+
├── scripts/
|
|
330
|
+
│ └── check-package.js # Предпубликационная проверка npm-пакета
|
|
331
|
+
├── test/
|
|
332
|
+
│ └── unit.test.js # Юнит-тесты
|
|
333
|
+
├── .env.example # Пример конфигурации
|
|
334
|
+
├── run.sh # Меню-диспетчер для разработки
|
|
335
|
+
├── LICENSE
|
|
336
|
+
├── package.json
|
|
337
|
+
└── README.md
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
> В опубликованный npm-пакет попадают только `bin/`, `src/`, `README.md`, `LICENSE`
|
|
341
|
+
> и `.env.example` — см. поле `files` в `package.json`.
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## 🔒 Безопасность
|
|
346
|
+
|
|
347
|
+
**Где лежит авторизация.** Единственный файл — `session.txt` в директории данных
|
|
348
|
+
(`~/.local/share/tuigram/` или `%LOCALAPPDATA%\tuigram\`; путь можно сменить через
|
|
349
|
+
`TUIGRAM_DATA_DIR`, посмотреть — командой `tuigram paths`). Внутри строка
|
|
350
|
+
`StringSession` от MTProto: версия формата, номер дата-центра, его адрес и порт,
|
|
351
|
+
и 256-байтный `authKey`, закодированные в base64. Пароля и кода подтверждения там нет.
|
|
352
|
+
|
|
353
|
+
- Файл сессии и файл настроек сохраняются с правами `0600` — чтение и запись только владельцу.
|
|
354
|
+
- Ни сессия, ни ключи не лежат внутри пакета: `npm update` их не затрагивает.
|
|
355
|
+
- `data/` и `.env` в `.gitignore` и исключены из npm-пакета полем `files`;
|
|
356
|
+
проверка `node scripts/check-package.js` падает, если секрет всё же попал в сборку.
|
|
357
|
+
- **`authKey` хранится в открытом виде**: base64 — это кодирование, а не шифрование.
|
|
358
|
+
Кто прочитает файл, тот получит полный доступ к аккаунту без телефона, кода и 2FA.
|
|
359
|
+
Не кладите его в общие папки и незашифрованные бэкапы. Если файл утёк —
|
|
360
|
+
завершите сеанс в официальном клиенте («Настройки → Устройства»), это отзовёт ключ
|
|
361
|
+
на сервере, и строка станет бесполезной.
|
|
362
|
+
- `/logout` в TUI отзывает ключ на сервере и удаляет файл сессии.
|
|
363
|
+
- Никакие данные не передаются сторонним серверам — прямое соединение с официальными
|
|
364
|
+
серверами Telegram MTProto.
|
package/bin/tuigram.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@emaxe/tuigram",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Полнофункциональный TUI & CLI клиент Telegram на Node.js на базе MTProto (teleproto)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": "./src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"tuigram": "bin/tuigram.js"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"README.md",
|
|
16
|
+
"LICENSE",
|
|
17
|
+
".env.example"
|
|
18
|
+
],
|
|
19
|
+
"keywords": [
|
|
20
|
+
"telegram",
|
|
21
|
+
"tui",
|
|
22
|
+
"cli",
|
|
23
|
+
"terminal",
|
|
24
|
+
"mtproto",
|
|
25
|
+
"teleproto",
|
|
26
|
+
"chat",
|
|
27
|
+
"messenger",
|
|
28
|
+
"blessed",
|
|
29
|
+
"terminal-client"
|
|
30
|
+
],
|
|
31
|
+
"author": "Maksim Klisin <emaxe.work@gmail.com>",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/emaxe/tuigram.git"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/emaxe/tuigram#readme",
|
|
37
|
+
"bugs": {
|
|
38
|
+
"url": "https://github.com/emaxe/tuigram/issues"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"os": [
|
|
45
|
+
"darwin",
|
|
46
|
+
"linux",
|
|
47
|
+
"win32"
|
|
48
|
+
],
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"access": "public"
|
|
51
|
+
},
|
|
52
|
+
"scripts": {
|
|
53
|
+
"start": "node src/index.js",
|
|
54
|
+
"menu": "./run.sh",
|
|
55
|
+
"tui": "node src/index.js tui",
|
|
56
|
+
"init": "node src/index.js init",
|
|
57
|
+
"login": "node src/index.js login",
|
|
58
|
+
"dialogs": "node src/index.js dialogs",
|
|
59
|
+
"history": "node src/index.js history",
|
|
60
|
+
"send": "node src/index.js send",
|
|
61
|
+
"listen": "node src/index.js listen",
|
|
62
|
+
"test": "node test/unit.test.js",
|
|
63
|
+
"prepublishOnly": "node test/unit.test.js && node scripts/check-package.js"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"big-integer": "^1.6.52",
|
|
67
|
+
"colorette": "^2.0.20",
|
|
68
|
+
"dotenv": "^16.4.7",
|
|
69
|
+
"input": "^1.0.1",
|
|
70
|
+
"neo-blessed": "^0.2.0",
|
|
71
|
+
"teleproto": "^1.229.0"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { connectClient, withClient } from "../telegram/client.js";
|
|
2
|
+
import { loginInteractive } from "../telegram/auth.js";
|
|
3
|
+
import { fetchDialogs } from "../telegram/dialogs.js";
|
|
4
|
+
import { fetchHistory, sendMessage, sendFiles } from "../telegram/messages.js";
|
|
5
|
+
import { inspectLocalFile } from "../utils/storage.js";
|
|
6
|
+
import { formatFileSize } from "../utils/time.js";
|
|
7
|
+
import { startTelegramListener } from "../telegram/listener.js";
|
|
8
|
+
import { formatDialogRow, formatHistoryMessage, formatStreamEvent } from "./formatters.js";
|
|
9
|
+
import { green, bold, red, yellow } from "colorette";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Авторизация в аккаунте через CLI.
|
|
13
|
+
*/
|
|
14
|
+
export async function cmdLogin() {
|
|
15
|
+
console.log(bold("\n🔑 Интерактивная авторизация в Telegram:\n"));
|
|
16
|
+
await loginInteractive();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Вывод списка диалогов в консоль.
|
|
21
|
+
* @param {object} flags
|
|
22
|
+
*/
|
|
23
|
+
export async function cmdDialogs(flags) {
|
|
24
|
+
await withClient(async (client) => {
|
|
25
|
+
const limit = flags.limit ? parseInt(flags.limit, 10) : 50;
|
|
26
|
+
const archived = flags.archived === true ? true : undefined;
|
|
27
|
+
|
|
28
|
+
console.log(bold(`\n📂 Загрузка диалогов (макс. ${limit})...\n`));
|
|
29
|
+
const dialogs = await fetchDialogs(client, { limit, archived });
|
|
30
|
+
|
|
31
|
+
for (const d of dialogs) {
|
|
32
|
+
console.log(formatDialogRow(d));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
console.log(gray(`\nВсего получено: ${dialogs.length} диалогов\n`));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Вывод истории сообщений чата.
|
|
41
|
+
* @param {string} peer
|
|
42
|
+
* @param {object} flags
|
|
43
|
+
*/
|
|
44
|
+
export async function cmdHistory(peer, flags) {
|
|
45
|
+
if (!peer) {
|
|
46
|
+
throw new Error("Укажите идентификатор чата: tuigram history <@username | -100... | me>");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await withClient(async (client) => {
|
|
50
|
+
const limit = flags.limit ? parseInt(flags.limit, 10) : 30;
|
|
51
|
+
console.log(bold(`\n💬 Загрузка истории для ${peer} (макс. ${limit} сообщений)...\n`));
|
|
52
|
+
|
|
53
|
+
const result = await fetchHistory(client, peer, { limit });
|
|
54
|
+
const messages = [...result.messages].reverse();
|
|
55
|
+
|
|
56
|
+
for (const m of messages) {
|
|
57
|
+
console.log(formatHistoryMessage(m));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(gray(`\nВсего отображено: ${messages.length} сообщений\n`));
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Отправка текстового сообщения из CLI.
|
|
66
|
+
* @param {string} peer
|
|
67
|
+
* @param {string} text
|
|
68
|
+
* @param {object} flags
|
|
69
|
+
*/
|
|
70
|
+
export async function cmdSend(peer, text, flags) {
|
|
71
|
+
if (!peer || !text) {
|
|
72
|
+
throw new Error("Использование: tuigram send <@username|me|id> <текст сообщения>");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
await withClient(async (client) => {
|
|
76
|
+
const replyTo = flags.reply ? parseInt(flags.reply, 10) : undefined;
|
|
77
|
+
console.log(yellow(`Отправка сообщения в ${peer}...`));
|
|
78
|
+
const sent = await sendMessage(client, peer, text, { replyTo });
|
|
79
|
+
console.log(green(`✓ Сообщение успешно отправлено! (ID: ${sent.id})`));
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Отправка файла из CLI.
|
|
85
|
+
* @param {string} peer
|
|
86
|
+
* @param {string} filePath
|
|
87
|
+
* @param {object} flags
|
|
88
|
+
*/
|
|
89
|
+
export async function cmdSendFile(peer, filePaths, flags) {
|
|
90
|
+
const rawPaths = (Array.isArray(filePaths) ? filePaths : [filePaths]).filter(Boolean);
|
|
91
|
+
if (!peer || rawPaths.length === 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
"Использование: tuigram sendfile <@username|me|id> <путь> [ещё_путь ...] [--caption \"текст\"] [--as-file]"
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Валидируем до подключения: понятная ошибка вместо сетевой
|
|
98
|
+
const files = [];
|
|
99
|
+
for (const raw of rawPaths) {
|
|
100
|
+
const info = inspectLocalFile(raw);
|
|
101
|
+
if (!info.ok) throw new Error(info.error);
|
|
102
|
+
files.push(info);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
106
|
+
const label = files.length === 1 ? files[0].name : `${files.length} файлов`;
|
|
107
|
+
|
|
108
|
+
await withClient(async (client) => {
|
|
109
|
+
const caption = flags.caption || "";
|
|
110
|
+
const forceDocument = Boolean(flags["as-file"] || flags.document);
|
|
111
|
+
|
|
112
|
+
console.log(yellow(`Отправка ${label} (${formatFileSize(totalSize)}) в ${peer}...`));
|
|
113
|
+
|
|
114
|
+
let lastPercent = -1;
|
|
115
|
+
const progressCallback = (progress) => {
|
|
116
|
+
const percent = Math.min(99, Math.round((progress || 0) * 100));
|
|
117
|
+
if (percent === lastPercent) return;
|
|
118
|
+
lastPercent = percent;
|
|
119
|
+
process.stdout.write(`\rЗагрузка: ${percent}% `);
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const sent = await sendFiles(client, peer, files.map((f) => f.filePath), {
|
|
123
|
+
caption,
|
|
124
|
+
forceDocument,
|
|
125
|
+
progressCallback,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
process.stdout.write("\r \r");
|
|
129
|
+
const ids = sent.map((m) => m.id).join(", ");
|
|
130
|
+
console.log(green(`✓ Успешно отправлено! (ID: ${ids})`));
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Режим непрерывного прослушивания живых событий в консоли.
|
|
136
|
+
*/
|
|
137
|
+
export async function cmdListen() {
|
|
138
|
+
const client = await connectClient();
|
|
139
|
+
const me = await client.getMe();
|
|
140
|
+
|
|
141
|
+
console.log(green(bold(`\n🟢 Подключено как: ${me.firstName || ""} ${me.lastName || ""} (@${me.username || me.id})`)));
|
|
142
|
+
console.log(yellow("Слушаю обновления в реальном времени... Нажмите Ctrl+C для выхода.\n"));
|
|
143
|
+
|
|
144
|
+
const listener = startTelegramListener(client);
|
|
145
|
+
|
|
146
|
+
listener.on("new_message", (data) => console.log(formatStreamEvent("new_message", data)));
|
|
147
|
+
listener.on("edited_message", (data) => console.log(formatStreamEvent("edited_message", data)));
|
|
148
|
+
listener.on("deleted_messages", (data) => console.log(formatStreamEvent("deleted_messages", data)));
|
|
149
|
+
listener.on("typing", (data) => console.log(formatStreamEvent("typing", data)));
|
|
150
|
+
|
|
151
|
+
const shutdown = async () => {
|
|
152
|
+
console.log(yellow("\nОстановка слушателя..."));
|
|
153
|
+
listener.stop();
|
|
154
|
+
await client.disconnect().catch(() => {});
|
|
155
|
+
process.exit(0);
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
process.on("SIGINT", shutdown);
|
|
159
|
+
process.on("SIGTERM", shutdown);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function gray(text) {
|
|
163
|
+
return `\x1b[90m${text}\x1b[0m`;
|
|
164
|
+
}
|