@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 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.1...HEAD
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.1...HEAD
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.1",
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
  /**
@@ -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;
@@ -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
+