@emaxe/tuigram 1.0.1 → 1.2.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 +24 -0
- package/CHANGELOG.md +18 -1
- package/CHANGELOG.ru.md +18 -1
- package/README.md +51 -0
- package/README.ru.md +51 -0
- package/package.json +3 -1
- package/src/cli/init.js +2 -1
- package/src/config.js +128 -0
- package/src/state.js +4 -4
- package/src/telegram/client.js +15 -7
- package/src/telegram/messages.js +58 -0
- package/src/telegram/socket.js +224 -0
- package/src/ui/app.js +54 -3
- package/src/ui/components/chatView.js +53 -8
- package/src/utils/image.js +343 -0
package/.env.example
CHANGED
|
@@ -27,3 +27,27 @@ AUTO_SCROLL=true
|
|
|
27
27
|
|
|
28
28
|
# Показывать уведомления о наборе текста (true/false)
|
|
29
29
|
SHOW_TYPING=true
|
|
30
|
+
|
|
31
|
+
# Отображение цветных превью изображений в чате (true/false)
|
|
32
|
+
SHOW_IMAGES=true
|
|
33
|
+
|
|
34
|
+
# Максимальные размеры превью изображений в символах терминала (ширина x высота)
|
|
35
|
+
IMAGE_MAX_WIDTH=36
|
|
36
|
+
IMAGE_MAX_HEIGHT=14
|
|
37
|
+
|
|
38
|
+
# ── Proxy Settings ────────────────────────────────────────────────────────────
|
|
39
|
+
# Поддерживаются HTTP и SOCKS5 (как с авторизацией, так и без).
|
|
40
|
+
# Формат единого URL:
|
|
41
|
+
# PROXY_URL=http://127.0.0.1:8080
|
|
42
|
+
# PROXY_URL=http://user:password@proxy.example.com:8080
|
|
43
|
+
# PROXY_URL=socks5://127.0.0.1:1080
|
|
44
|
+
# PROXY_URL=socks5://user:password@127.0.0.1:1080
|
|
45
|
+
#
|
|
46
|
+
# Либо отдельными переменными:
|
|
47
|
+
# PROXY_TYPE=http
|
|
48
|
+
# PROXY_HOST=127.0.0.1
|
|
49
|
+
# PROXY_PORT=8080
|
|
50
|
+
# PROXY_USERNAME=
|
|
51
|
+
# PROXY_PASSWORD=
|
|
52
|
+
# PROXY_TIMEOUT=10
|
|
53
|
+
|
package/CHANGELOG.md
CHANGED
|
@@ -13,6 +13,21 @@ 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.2.0] — 2026-09-01
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Display color image previews directly in chat messages using high-resolution Unicode half-block characters (`▀` `U+2580`) with instant rendering of Telegram stripped thumbnails (`PhotoStrippedSize`).
|
|
21
|
+
- Configuration options `SHOW_IMAGES`, `IMAGE_MAX_WIDTH`, and `IMAGE_MAX_HEIGHT` to control image preview rendering in `.env` and `config.js`.
|
|
22
|
+
|
|
23
|
+
## [1.1.0] — 2026-08-31
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Support for HTTP/HTTPS (via HTTP CONNECT) and SOCKS5/SOCKS4 proxies with optional username and password authentication.
|
|
28
|
+
- Proxy configuration options via `.env` (`PROXY_URL` or `PROXY_TYPE`/`PROXY_HOST`/`PROXY_PORT`/`PROXY_USERNAME`/`PROXY_PASSWORD`) and standard environment variables (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`).
|
|
29
|
+
- Display of active proxy status with password masking in `tuigram paths`.
|
|
30
|
+
|
|
16
31
|
## [1.0.1] — 2026-08-29
|
|
17
32
|
|
|
18
33
|
Documentation and packaging only — no runtime changes.
|
|
@@ -103,6 +118,8 @@ First public release.
|
|
|
103
118
|
- A WCAG contrast test (3:1 threshold) applied to every theme **after** conversion
|
|
104
119
|
to xterm-256 — the colors are checked exactly as the user sees them.
|
|
105
120
|
|
|
106
|
-
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.0
|
|
121
|
+
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.2.0...HEAD
|
|
122
|
+
[1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
|
|
123
|
+
[1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
|
|
107
124
|
[1.0.1]: https://github.com/emaxe/tuigram/compare/v1.0.0...v1.0.1
|
|
108
125
|
[1.0.0]: https://github.com/emaxe/tuigram/releases/tag/v1.0.0
|
package/CHANGELOG.ru.md
CHANGED
|
@@ -13,6 +13,21 @@
|
|
|
13
13
|
|
|
14
14
|
- Английская локализация строк интерфейса TUI и CLI.
|
|
15
15
|
|
|
16
|
+
## [1.2.0] — 2026-09-01
|
|
17
|
+
|
|
18
|
+
### Добавлено
|
|
19
|
+
|
|
20
|
+
- Отображение цветных превью изображений прямо в ленте сообщений чата с помощью псевдографики высокого разрешения (Unicode Half-Block `▀` `U+2580`) и мгновенного рендеринга миниатюр `PhotoStrippedSize`.
|
|
21
|
+
- Параметры конфигурации `SHOW_IMAGES`, `IMAGE_MAX_WIDTH` и `IMAGE_MAX_HEIGHT` в `.env` и `config.js` для управления отображением превью и их максимальным размером.
|
|
22
|
+
|
|
23
|
+
## [1.1.0] — 2026-08-31
|
|
24
|
+
|
|
25
|
+
### Добавлено
|
|
26
|
+
|
|
27
|
+
- Поддержка работы через HTTP/HTTPS (HTTP CONNECT) и SOCKS5/SOCKS4 прокси, как с авторизацией по логину и паролю, так и без неё.
|
|
28
|
+
- Настройка прокси через `.env` (`PROXY_URL` или `PROXY_TYPE`/`PROXY_HOST`/`PROXY_PORT`/`PROXY_USERNAME`/`PROXY_PASSWORD`) и стандартные переменные окружения (`HTTPS_PROXY`, `HTTP_PROXY`, `ALL_PROXY`).
|
|
29
|
+
- Отображение активного прокси с маскированием пароля в выводе команды `tuigram paths`.
|
|
30
|
+
|
|
16
31
|
## [1.0.1] — 2026-08-29
|
|
17
32
|
|
|
18
33
|
Только документация и упаковка — поведение клиента не менялось.
|
|
@@ -105,6 +120,8 @@
|
|
|
105
120
|
- Тест контраста по WCAG (порог 3:1) для каждой темы — **после** конверсии в xterm-256,
|
|
106
121
|
то есть ровно в том виде, в каком цвет увидит пользователь.
|
|
107
122
|
|
|
108
|
-
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.0
|
|
123
|
+
[Unreleased]: https://github.com/emaxe/tuigram/compare/v1.2.0...HEAD
|
|
124
|
+
[1.2.0]: https://github.com/emaxe/tuigram/compare/v1.1.0...v1.2.0
|
|
125
|
+
[1.1.0]: https://github.com/emaxe/tuigram/compare/v1.0.1...v1.1.0
|
|
109
126
|
[1.0.1]: https://github.com/emaxe/tuigram/compare/v1.0.0...v1.0.1
|
|
110
127
|
[1.0.0]: https://github.com/emaxe/tuigram/releases/tag/v1.0.0
|
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
|
|
|
55
55
|
- [Slash commands](#-slash-commands-in-the-input-box)
|
|
56
56
|
- [Sending files and images](#-sending-files-and-images)
|
|
57
57
|
- [Themes](#-themes)
|
|
58
|
+
- [Inline image previews](#️-inline-image-previews)
|
|
59
|
+
- [Proxy configuration](#-proxy-configuration)
|
|
58
60
|
- [Command line usage (CLI)](#️-command-line-usage-cli)
|
|
59
61
|
- [Project structure](#-project-structure)
|
|
60
62
|
- [Security](#-security)
|
|
@@ -73,6 +75,7 @@ TuiGram lets you use Telegram entirely from the terminal: browse your dialog lis
|
|
|
73
75
|
- Instant search and filtering of chats by title and `@username` (`/`);
|
|
74
76
|
- Live typing indicator ("… is typing");
|
|
75
77
|
- Colored rendering of Telegram entities (bold, italic, monospace code, URLs, mentions, spoilers);
|
|
78
|
+
- High-resolution inline image previews (Unicode Half-Block `▀` pixel art);
|
|
76
79
|
- Media attachment indicators (photo, video, document, voice, sticker, poll);
|
|
77
80
|
- Message reactions (👍, 🔥, ❤️);
|
|
78
81
|
- Infinite upward pagination of message history (`PageUp` / `Ctrl+U`);
|
|
@@ -511,6 +514,52 @@ The contrast of every "text on background" pair is verified by an automated WCAG
|
|
|
511
514
|
|
|
512
515
|
---
|
|
513
516
|
|
|
517
|
+
## 🖼️ Inline image previews
|
|
518
|
+
|
|
519
|
+
TuiGram displays color image previews directly in the chat history using high-resolution Unicode half-block characters (`▀` `U+2580`). Each character cell renders 2 vertical subpixels with 24-bit RGB colors, producing crisp, proportional pixel art that scrolls naturally with messages and works across all terminals.
|
|
520
|
+
|
|
521
|
+
Previews are enabled by default and can be configured in `.env`:
|
|
522
|
+
|
|
523
|
+
```env
|
|
524
|
+
# Enable/disable image previews in chat (true/false)
|
|
525
|
+
SHOW_IMAGES=true
|
|
526
|
+
|
|
527
|
+
# Maximum preview size in terminal characters (width x height)
|
|
528
|
+
IMAGE_MAX_WIDTH=36
|
|
529
|
+
IMAGE_MAX_HEIGHT=14
|
|
530
|
+
```
|
|
531
|
+
|
|
532
|
+
---
|
|
533
|
+
|
|
534
|
+
## 🌐 Proxy configuration
|
|
535
|
+
|
|
536
|
+
TuiGram supports routing MTProto connections through HTTP (including HTTPS CONNECT) and SOCKS5/SOCKS4 proxies — both with and without username/password authentication.
|
|
537
|
+
|
|
538
|
+
Proxy options can be configured in `.env` (or via environment variables):
|
|
539
|
+
|
|
540
|
+
**Single URL:**
|
|
541
|
+
```env
|
|
542
|
+
PROXY_URL=http://127.0.0.1:8080
|
|
543
|
+
PROXY_URL=http://user:password@proxy.example.com:8080
|
|
544
|
+
PROXY_URL=socks5://127.0.0.1:1080
|
|
545
|
+
PROXY_URL=socks5://user:password@127.0.0.1:1080
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Or separate variables:**
|
|
549
|
+
```env
|
|
550
|
+
PROXY_TYPE=http # http, https, socks5, socks4
|
|
551
|
+
PROXY_HOST=127.0.0.1
|
|
552
|
+
PROXY_PORT=8080
|
|
553
|
+
PROXY_USERNAME=user # optional
|
|
554
|
+
PROXY_PASSWORD=password # optional
|
|
555
|
+
PROXY_TIMEOUT=10 # timeout in seconds (default: 10)
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
Standard environment variables `HTTPS_PROXY`, `HTTP_PROXY`, and `ALL_PROXY` are also supported as fallbacks.
|
|
559
|
+
The active proxy status can be inspected using `tuigram paths`.
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
514
563
|
## 🛠️ Command line usage (CLI)
|
|
515
564
|
|
|
516
565
|
TuiGram can be used as a set of console utilities (when running from a repository
|
|
@@ -554,6 +603,7 @@ TuiGram/
|
|
|
554
603
|
│ ├── state.js # Reactive centralized state store
|
|
555
604
|
│ ├── telegram/
|
|
556
605
|
│ │ ├── client.js # MTProto client creation and management
|
|
606
|
+
│ │ ├── socket.js # MTProto network transport and proxy tunneling (HTTP/SOCKS5)
|
|
557
607
|
│ │ ├── auth.js # Interactive login wizard and 2FA
|
|
558
608
|
│ │ ├── dialogs.js # Fetching, filtering and searching dialogs
|
|
559
609
|
│ │ ├── messages.js # History loading, sending, editing, files, reactions
|
|
@@ -581,6 +631,7 @@ TuiGram/
|
|
|
581
631
|
│ │ ├── init.js # tuigram init / paths — setup and diagnostics
|
|
582
632
|
│ │ └── formatters.js # Console table and log formatters
|
|
583
633
|
│ └── utils/
|
|
634
|
+
│ ├── image.js # Image decoding, resizing and Half-Block ANSI rendering
|
|
584
635
|
│ ├── storage.js # File operations and session persistence
|
|
585
636
|
│ └── time.js # Time and date formatting
|
|
586
637
|
├── scripts/
|
package/README.ru.md
CHANGED
|
@@ -51,6 +51,8 @@
|
|
|
51
51
|
- [Слэш-команды](#-слэш-команды-в-поле-ввода)
|
|
52
52
|
- [Отправка файлов и картинок](#-отправка-файлов-и-картинок)
|
|
53
53
|
- [Темы оформления](#-темы-оформления)
|
|
54
|
+
- [Превью изображений в сообщениях](#️-превью-изображений-в-сообщениях)
|
|
55
|
+
- [Прокси-сервер](#-прокси-сервер)
|
|
54
56
|
- [Использование через консоль (CLI)](#️-использование-через-консоль-cli)
|
|
55
57
|
- [Структура проекта](#-структура-проекта)
|
|
56
58
|
- [Безопасность](#-безопасность)
|
|
@@ -69,6 +71,7 @@
|
|
|
69
71
|
- Мгновенный поиск и фильтрация чатов по названию и `@username` (`/`);
|
|
70
72
|
- Отображение статуса набора текста («Собеседник печатает...») в реальном времени;
|
|
71
73
|
- Цветовое форматирование Telegram Entities (Bold, Italic, Monospace Code, URLs, Mentions, Spoilers);
|
|
74
|
+
- Цветные превью изображений высокого разрешения прямо в чате (Unicode Half-Block `▀` pixel art);
|
|
72
75
|
- Индикаторы медиа-вложений (фото, видео, документы, голосовые, стикеры, опросы);
|
|
73
76
|
- Реакции на сообщения (👍, 🔥, ❤️);
|
|
74
77
|
- Бесконечная пагинация истории сообщений вверх (`PageUp` / `Ctrl+U`);
|
|
@@ -505,6 +508,52 @@ TUI_THEME=light # светлая
|
|
|
505
508
|
|
|
506
509
|
---
|
|
507
510
|
|
|
511
|
+
## 🖼️ Превью изображений в сообщениях
|
|
512
|
+
|
|
513
|
+
TuiGram отображает цветные превью фотографий прямо в ленте сообщений чата с помощью псевдографики высокого разрешения (Unicode Half-Block `▀` `U+2580`). Каждое знакоместо терминала вмещает 2 вертикальных субпикселя с 24-битными цветами, обеспечивая чёткое пропорциональное изображение 1:1, которое плавно прокручивается вместе с сообщениями и поддерживается всеми терминалами.
|
|
514
|
+
|
|
515
|
+
Отображение превью включено по умолчанию и настраивается в `.env`:
|
|
516
|
+
|
|
517
|
+
```env
|
|
518
|
+
# Включение/отключение превью картинок в чате (true/false)
|
|
519
|
+
SHOW_IMAGES=true
|
|
520
|
+
|
|
521
|
+
# Максимальный размер превью в символах терминала (ширина x высота)
|
|
522
|
+
IMAGE_MAX_WIDTH=36
|
|
523
|
+
IMAGE_MAX_HEIGHT=14
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## 🌐 Прокси-сервер
|
|
529
|
+
|
|
530
|
+
TuiGram поддерживает маршрутизацию MTProto-соединений через HTTP (включая HTTPS CONNECT) и SOCKS5/SOCKS4 прокси — как с авторизацией по логину и паролю, так и без неё.
|
|
531
|
+
|
|
532
|
+
Настройки прокси задаются в `.env` (или через переменные окружения):
|
|
533
|
+
|
|
534
|
+
**Единый URL:**
|
|
535
|
+
```env
|
|
536
|
+
PROXY_URL=http://127.0.0.1:8080
|
|
537
|
+
PROXY_URL=http://user:password@proxy.example.com:8080
|
|
538
|
+
PROXY_URL=socks5://127.0.0.1:1080
|
|
539
|
+
PROXY_URL=socks5://user:password@127.0.0.1:1080
|
|
540
|
+
```
|
|
541
|
+
|
|
542
|
+
**Либо отдельными переменными:**
|
|
543
|
+
```env
|
|
544
|
+
PROXY_TYPE=http # http, https, socks5, socks4
|
|
545
|
+
PROXY_HOST=127.0.0.1
|
|
546
|
+
PROXY_PORT=8080
|
|
547
|
+
PROXY_USERNAME=user # опционально
|
|
548
|
+
PROXY_PASSWORD=password # опционально
|
|
549
|
+
PROXY_TIMEOUT=10 # таймаут в секундах (по умолчанию 10)
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
Также в качестве фолбэка поддерживаются стандартные переменные `HTTPS_PROXY`, `HTTP_PROXY` и `ALL_PROXY`.
|
|
553
|
+
Проверить активное состояние прокси можно командой `tuigram paths`.
|
|
554
|
+
|
|
555
|
+
---
|
|
556
|
+
|
|
508
557
|
## 🛠️ Использование через консоль (CLI)
|
|
509
558
|
|
|
510
559
|
TuiGram можно запускать в режиме консольных утилит (при запуске из клона
|
|
@@ -548,6 +597,7 @@ TuiGram/
|
|
|
548
597
|
│ ├── state.js # Реактивное централизованное хранилище состояния
|
|
549
598
|
│ ├── telegram/
|
|
550
599
|
│ │ ├── client.js # Создание и управление MTProto клиентом
|
|
600
|
+
│ │ ├── socket.js # Сетевой транспорт MTProto и туннелирование прокси (HTTP/SOCKS5)
|
|
551
601
|
│ │ ├── auth.js # Интерактивный логин-визард и 2FA
|
|
552
602
|
│ │ ├── dialogs.js # Получение, фильтрация и поиск диалогов
|
|
553
603
|
│ │ ├── messages.js # Загрузка истории, отправка, правка, файлы, реакции
|
|
@@ -575,6 +625,7 @@ TuiGram/
|
|
|
575
625
|
│ │ ├── init.js # tuigram init / paths — настройка и диагностика
|
|
576
626
|
│ │ └── formatters.js # Консольные форматтеры таблиц и логов
|
|
577
627
|
│ └── utils/
|
|
628
|
+
│ ├── image.js # Декодирование, масштабирование и Half-Block ANSI рендеринг изображений
|
|
578
629
|
│ ├── storage.js # Файловые операции и сохранение сессий
|
|
579
630
|
│ └── time.js # Форматирование времени и дат
|
|
580
631
|
├── scripts/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@emaxe/tuigram",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Полнофункциональный TUI & CLI клиент Telegram на Node.js на базе MTProto (teleproto)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
"colorette": "^2.0.20",
|
|
71
71
|
"dotenv": "^16.4.7",
|
|
72
72
|
"input": "^1.0.1",
|
|
73
|
+
"jpeg-js": "^0.4.4",
|
|
73
74
|
"neo-blessed": "^0.2.0",
|
|
75
|
+
"pngjs": "^7.0.0",
|
|
74
76
|
"teleproto": "^1.229.0"
|
|
75
77
|
}
|
|
76
78
|
}
|
package/src/cli/init.js
CHANGED
|
@@ -2,7 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import input from "input";
|
|
4
4
|
import { bold, cyan, green, yellow, dim } from "colorette";
|
|
5
|
-
import { config } from "../config.js";
|
|
5
|
+
import { config, formatProxyUrl } from "../config.js";
|
|
6
6
|
|
|
7
7
|
/** Сколько раз переспрашивать значение, прежде чем сдаться. */
|
|
8
8
|
const MAX_ATTEMPTS = 3;
|
|
@@ -193,5 +193,6 @@ export function cmdPaths() {
|
|
|
193
193
|
console.log(` Сессия: ${config.sessionPath} ${sessionExists ? green("(есть)") : dim("(нет)")}`);
|
|
194
194
|
console.log(` Загрузки: ${config.downloadsDir}`);
|
|
195
195
|
console.log(` Ключи API: ${config.apiId && config.apiHash ? green("заданы") : yellow("не заданы — запустите tuigram init")}`);
|
|
196
|
+
console.log(` Прокси: ${config.proxy ? green(formatProxyUrl(config.proxy)) : dim("не используется")}`);
|
|
196
197
|
console.log("");
|
|
197
198
|
}
|
package/src/config.js
CHANGED
|
@@ -110,6 +110,129 @@ function readVersion() {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
/**
|
|
114
|
+
* @typedef {Object} ProxyConfig
|
|
115
|
+
* @property {"http"|"https"|"socks5"|"socks4"} type - Протокол прокси
|
|
116
|
+
* @property {string} host - Адрес сервера прокси
|
|
117
|
+
* @property {number} port - Порт сервера прокси
|
|
118
|
+
* @property {string} [username] - Логин пользователя
|
|
119
|
+
* @property {string} [password] - Пароль пользователя
|
|
120
|
+
* @property {number} [timeout] - Таймаут в секундах
|
|
121
|
+
* @property {string} ip - Хост прокси (для совместимости с MTProto)
|
|
122
|
+
* @property {4|5} [socksType] - Тип SOCKS-прокси
|
|
123
|
+
* @property {boolean} [http] - Флаг HTTP-прокси
|
|
124
|
+
*/
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Нормализует тип прокси из строки протокола.
|
|
128
|
+
* @param {string} [raw]
|
|
129
|
+
* @returns {"http"|"https"|"socks5"|"socks4"}
|
|
130
|
+
*/
|
|
131
|
+
function normalizeProxyType(raw) {
|
|
132
|
+
const clean = String(raw || "").toLowerCase().replace(/:$/, "").trim();
|
|
133
|
+
if (clean === "https") return "https";
|
|
134
|
+
if (clean === "socks4" || clean === "socks4a") return "socks4";
|
|
135
|
+
if (clean === "socks5" || clean === "socks5h" || clean === "socks") return "socks5";
|
|
136
|
+
return "http";
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Разбирает параметры прокси из переменных окружения или переданного объекта.
|
|
141
|
+
* Поддерживает URL-формат (PROXY_URL, HTTPS_PROXY, HTTP_PROXY, ALL_PROXY)
|
|
142
|
+
* и отдельные переменные (PROXY_TYPE, PROXY_HOST, PROXY_PORT, PROXY_USERNAME, PROXY_PASSWORD).
|
|
143
|
+
* @param {Record<string, string|undefined>} [env=process.env]
|
|
144
|
+
* @returns {ProxyConfig|null}
|
|
145
|
+
*/
|
|
146
|
+
export function parseProxyConfig(env = process.env) {
|
|
147
|
+
const rawUrl = env.PROXY_URL || env.proxy_url || (!env.PROXY_HOST && !env.PROXY_IP && !env.proxy_host && !env.proxy_ip
|
|
148
|
+
? (env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy || env.ALL_PROXY || env.all_proxy)
|
|
149
|
+
: undefined);
|
|
150
|
+
|
|
151
|
+
let type = "http";
|
|
152
|
+
let host = "";
|
|
153
|
+
let port = 0;
|
|
154
|
+
let username = "";
|
|
155
|
+
let password = "";
|
|
156
|
+
|
|
157
|
+
if (rawUrl && typeof rawUrl === "string" && rawUrl.trim()) {
|
|
158
|
+
let urlStr = rawUrl.trim();
|
|
159
|
+
// Добавляем схему по умолчанию, если передан host:port без префикса
|
|
160
|
+
if (!/^[a-zA-Z0-9+-.]+:\/\//.test(urlStr)) {
|
|
161
|
+
const defaultScheme = (env.PROXY_TYPE || "http").toLowerCase().replace(/:$/, "");
|
|
162
|
+
urlStr = `${defaultScheme}://${urlStr}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
const parsed = new URL(urlStr);
|
|
167
|
+
type = normalizeProxyType(parsed.protocol);
|
|
168
|
+
host = parsed.hostname.replace(/^\[(.*)\]$/, "$1");
|
|
169
|
+
port = parsed.port ? parseInt(parsed.port, 10) : (type === "https" ? 443 : (type.startsWith("socks") ? 1080 : 8080));
|
|
170
|
+
if (parsed.username) username = decodeURIComponent(parsed.username);
|
|
171
|
+
if (parsed.password) password = decodeURIComponent(parsed.password);
|
|
172
|
+
} catch {
|
|
173
|
+
return null;
|
|
174
|
+
}
|
|
175
|
+
} else if (env.PROXY_HOST || env.PROXY_IP) {
|
|
176
|
+
type = normalizeProxyType(env.PROXY_TYPE || "http");
|
|
177
|
+
host = String(env.PROXY_HOST || env.PROXY_IP).trim();
|
|
178
|
+
const defaultPort = type === "https" ? 443 : (type.startsWith("socks") ? 1080 : 8080);
|
|
179
|
+
port = env.PROXY_PORT ? parseInt(String(env.PROXY_PORT).trim(), 10) : defaultPort;
|
|
180
|
+
username = String(env.PROXY_USERNAME || env.PROXY_USER || "").trim();
|
|
181
|
+
password = String(env.PROXY_PASSWORD || env.PROXY_PASS || "").trim();
|
|
182
|
+
} else {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (!host || !port || isNaN(port) || port <= 0 || port > 65535) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const rawTimeout = env.PROXY_TIMEOUT ? parseInt(String(env.PROXY_TIMEOUT).trim(), 10) : 10;
|
|
191
|
+
const timeout = isNaN(rawTimeout) || rawTimeout <= 0 ? 10 : rawTimeout;
|
|
192
|
+
|
|
193
|
+
/** @type {ProxyConfig} */
|
|
194
|
+
const result = {
|
|
195
|
+
type,
|
|
196
|
+
host,
|
|
197
|
+
port,
|
|
198
|
+
ip: host,
|
|
199
|
+
timeout,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
if (username) result.username = username;
|
|
203
|
+
if (password) result.password = password;
|
|
204
|
+
|
|
205
|
+
if (type === "socks5") {
|
|
206
|
+
result.socksType = 5;
|
|
207
|
+
} else if (type === "socks4") {
|
|
208
|
+
result.socksType = 4;
|
|
209
|
+
} else {
|
|
210
|
+
result.http = true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Форматирует конфигурацию прокси в строку для вывода в терминал.
|
|
218
|
+
* Пароль маскируется звездочками в целях безопасности.
|
|
219
|
+
* @param {ProxyConfig|null} proxy
|
|
220
|
+
* @returns {string}
|
|
221
|
+
*/
|
|
222
|
+
export function formatProxyUrl(proxy) {
|
|
223
|
+
if (!proxy) return "не используется";
|
|
224
|
+
|
|
225
|
+
let auth = "";
|
|
226
|
+
if (proxy.username) {
|
|
227
|
+
auth = proxy.password
|
|
228
|
+
? `${encodeURIComponent(proxy.username)}:***@`
|
|
229
|
+
: `${encodeURIComponent(proxy.username)}@`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const host = proxy.host.includes(":") ? `[${proxy.host}]` : proxy.host;
|
|
233
|
+
return `${proxy.type}://${auth}${host}:${proxy.port}`;
|
|
234
|
+
}
|
|
235
|
+
|
|
113
236
|
export const config = {
|
|
114
237
|
packageRoot,
|
|
115
238
|
/** @deprecated оставлено для обратной совместимости, равно packageRoot */
|
|
@@ -128,6 +251,11 @@ export const config = {
|
|
|
128
251
|
theme: process.env.TUI_THEME || "default",
|
|
129
252
|
autoScroll: String(process.env.AUTO_SCROLL || "true").toLowerCase() === "true",
|
|
130
253
|
showTyping: String(process.env.SHOW_TYPING || "true").toLowerCase() === "true",
|
|
254
|
+
showImages: String(process.env.SHOW_IMAGES || "true").toLowerCase() !== "false",
|
|
255
|
+
imageMaxWidth: parseInt(process.env.IMAGE_MAX_WIDTH || "36", 10) || 36,
|
|
256
|
+
imageMaxHeight: parseInt(process.env.IMAGE_MAX_HEIGHT || "14", 10) || 14,
|
|
257
|
+
|
|
258
|
+
proxy: parseProxyConfig(),
|
|
131
259
|
|
|
132
260
|
/**
|
|
133
261
|
* Путь к файлу сессии.
|
package/src/state.js
CHANGED
|
@@ -140,7 +140,7 @@ class AppState extends EventEmitter {
|
|
|
140
140
|
combined.sort((a, b) => (a.date || 0) - (b.date || 0) || (a.id - b.id));
|
|
141
141
|
|
|
142
142
|
this.messagesByChat.set(chatId, combined);
|
|
143
|
-
this.emit("messages_updated", { chatId, messages: combined, isPrepend: prepend });
|
|
143
|
+
this.emit("messages_updated", { chatId, messages: combined, isPrepend: prepend, isUpdate: false });
|
|
144
144
|
}
|
|
145
145
|
|
|
146
146
|
/**
|
|
@@ -181,7 +181,7 @@ class AppState extends EventEmitter {
|
|
|
181
181
|
this.emit("dialogs_updated", this.getVisibleDialogs());
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
this.emit("messages_updated", { chatId, messages: list, isPrepend: false });
|
|
184
|
+
this.emit("messages_updated", { chatId, messages: list, isPrepend: false, isNewMessage: true });
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
/**
|
|
@@ -194,7 +194,7 @@ class AppState extends EventEmitter {
|
|
|
194
194
|
const index = list.findIndex((m) => m.id === updatedMessage.id);
|
|
195
195
|
if (index !== -1) {
|
|
196
196
|
list[index] = { ...list[index], ...updatedMessage };
|
|
197
|
-
this.emit("messages_updated", { chatId, messages: list, isPrepend: false });
|
|
197
|
+
this.emit("messages_updated", { chatId, messages: list, isPrepend: false, isUpdate: true });
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
@@ -208,7 +208,7 @@ class AppState extends EventEmitter {
|
|
|
208
208
|
const idSet = new Set(deletedIds);
|
|
209
209
|
const filtered = list.filter((m) => !idSet.has(m.id));
|
|
210
210
|
this.messagesByChat.set(chatId, filtered);
|
|
211
|
-
this.emit("messages_updated", { chatId, messages: filtered, isPrepend: false });
|
|
211
|
+
this.emit("messages_updated", { chatId, messages: filtered, isPrepend: false, isUpdate: true });
|
|
212
212
|
}
|
|
213
213
|
|
|
214
214
|
/**
|
package/src/telegram/client.js
CHANGED
|
@@ -4,6 +4,7 @@ import { StringSession } from "teleproto/sessions/index.js";
|
|
|
4
4
|
import { Logger, LogLevel } from "teleproto/extensions/Logger.js";
|
|
5
5
|
import { config } from "../config.js";
|
|
6
6
|
import { saveSessionFile, readFileSafe } from "../utils/storage.js";
|
|
7
|
+
import { TuiGramNetSockets } from "./socket.js";
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Читает сохранённую строку сессии.
|
|
@@ -42,17 +43,24 @@ export function clearSession() {
|
|
|
42
43
|
export function buildClient(sessionString = readSession()) {
|
|
43
44
|
config.assertCredentials();
|
|
44
45
|
|
|
46
|
+
const clientParams = {
|
|
47
|
+
connectionRetries: 10,
|
|
48
|
+
autoReconnect: true,
|
|
49
|
+
retryDelay: 1500,
|
|
50
|
+
baseLogger: new Logger(LogLevel.ERROR),
|
|
51
|
+
useWSS: false,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (config.proxy) {
|
|
55
|
+
clientParams.proxy = config.proxy;
|
|
56
|
+
clientParams.networkSocket = TuiGramNetSockets;
|
|
57
|
+
}
|
|
58
|
+
|
|
45
59
|
const client = new TelegramClient(
|
|
46
60
|
new StringSession(sessionString),
|
|
47
61
|
config.apiId,
|
|
48
62
|
config.apiHash,
|
|
49
|
-
|
|
50
|
-
connectionRetries: 10,
|
|
51
|
-
autoReconnect: true,
|
|
52
|
-
retryDelay: 1500,
|
|
53
|
-
baseLogger: new Logger(LogLevel.ERROR),
|
|
54
|
-
useWSS: false,
|
|
55
|
-
}
|
|
63
|
+
clientParams
|
|
56
64
|
);
|
|
57
65
|
|
|
58
66
|
return client;
|
package/src/telegram/messages.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { Api, errors } from "teleproto";
|
|
2
2
|
import { idToString, toMarkedId, getEntityDisplayName, entityCache, resolveEntity } from "./entities.js";
|
|
3
3
|
import { describeMedia } from "./formatter.js";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
import { renderStrippedThumbnail, renderImageBuffer } from "../utils/image.js";
|
|
4
6
|
|
|
5
7
|
const { FloodWaitError } = errors;
|
|
6
8
|
|
|
@@ -38,6 +40,27 @@ export function normalizeMessage(message) {
|
|
|
38
40
|
}
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
// Извлечение и рендеринг 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
|
+
}
|
|
63
|
+
|
|
41
64
|
return {
|
|
42
65
|
id: message.id,
|
|
43
66
|
date: message.date ? message.date * 1000 : Date.now(),
|
|
@@ -53,6 +76,7 @@ export function normalizeMessage(message) {
|
|
|
53
76
|
forwards: message.forwards || null,
|
|
54
77
|
media: message.media || null,
|
|
55
78
|
mediaDescription: describeMedia(message.media),
|
|
79
|
+
imagePreview,
|
|
56
80
|
entities: message.entities || [],
|
|
57
81
|
reactions,
|
|
58
82
|
rawMessage: message,
|
|
@@ -263,3 +287,37 @@ export async function markAsRead(client, rawPeer, maxId = 0) {
|
|
|
263
287
|
// Игнорируем незначительные сетевые ошибки прочтения
|
|
264
288
|
}
|
|
265
289
|
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Асинхронно загружает и декодирует превью изображения сообщения через MTProto.
|
|
293
|
+
* @param {import("teleproto").TelegramClient} client
|
|
294
|
+
* @param {object} rawMessage
|
|
295
|
+
* @param {object} [options]
|
|
296
|
+
* @param {number} [options.maxWidth]
|
|
297
|
+
* @param {number} [options.maxHeight]
|
|
298
|
+
* @returns {Promise<string>}
|
|
299
|
+
*/
|
|
300
|
+
export async function loadMessageImagePreview(client, rawMessage, { maxWidth = config.imageMaxWidth, maxHeight = config.imageMaxHeight } = {}) {
|
|
301
|
+
if (!rawMessage?.media) return "";
|
|
302
|
+
const media = rawMessage.media;
|
|
303
|
+
const isPhoto = media.className === "MessageMediaPhoto";
|
|
304
|
+
const isDoc = media.className === "MessageMediaDocument";
|
|
305
|
+
if (!isPhoto && !isDoc) return "";
|
|
306
|
+
|
|
307
|
+
const cacheKey = isPhoto
|
|
308
|
+
? `photo_full_${media.photo?.id || rawMessage.id}`
|
|
309
|
+
: `doc_full_${media.document?.id || rawMessage.id}`;
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const thumbBuf = await client.downloadMedia(media, { thumb: 1 });
|
|
313
|
+
if (!thumbBuf || thumbBuf.length === 0) return "";
|
|
314
|
+
return renderImageBuffer(thumbBuf, {
|
|
315
|
+
maxWidth,
|
|
316
|
+
maxHeight,
|
|
317
|
+
cacheKey,
|
|
318
|
+
});
|
|
319
|
+
} catch {
|
|
320
|
+
return "";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|