tgx
by mal4i6ka
README.md
# tgx 1.0.0
Telegram из терминала: полноэкранный TUI + скриптовый CLI на Telethon,
плюс коннектор MCP, через который тем же самым пользуется агент.
Что менялось — в [CHANGELOG.md](CHANGELOG.md).
```bash
tgx ui # полноэкранный клиент
tgx ui --demo # то же самое на демо-данных, без входа в аккаунт
tgx # карта команд с анимированной заставкой
tgx dialogs --jsonl # машинный вывод для скриптов (как раньше)
```
## Установка
```bash
python3 -m venv .venv && .venv/bin/pip install -r requirements.txt
```
`bin/tgx` — обёртка, запускающая `bin/tgx.py` из `.venv`. Ключи API берутся из
`data/config.json` или переменных `TG_API_ID` / `TG_API_HASH`; сессия лежит в
`data/tgx.session`. Если сессия разлогинена, `tgx ui` сам покажет экран входа
(телефон → код → облачный пароль).
## TUI
Слева — список чатов с непрочитанными, превью и папками Telegram; справа —
переписка баблами с разделителями дат, ответами и картинками прямо в бабле;
снизу — поле ввода. Новые сообщения прилетают вживую (Telethon events) и появляются с
плавным проявлением, чужие чаты дают всплывающее уведомление.
| клавиша | действие |
|---|---|
| `/` | фильтр по списку чатов |
| `ctrl+k` | фокус на список чатов |
| `↑ ↓` / `j k` | навигация по списку и по сообщениям |
| клик по сообщению | выделить его (дальше работают `v`, `o`, `ctrl+r`) |
| `enter` | открыть чат · отправить сообщение |
| `p` | редактор поста: разметка, живое превью, файл, отложенная публикация |
| `l` | чек-лист: создать, отмечать пункты, дописывать |
| `t` | темы форума: переключение, создание, закрытие, закрепление |
| `shift+p` | закрепить или открепить сообщение (тихо) |
| `s` | показать/скрыть спойлер в сообщении |
| `ctrl+s` | прикрепить файл: фото, видео, кружок, голосовое, документ, альбом |
| `c` | комментарии к посту канала — читать и писать |
| `ctrl+f` | поиск внутри чата |
| `ctrl+r` | ответить на выбранное сообщение |
| `ctrl+y` | скопировать текст сообщения |
| `e` / `x` | править · удалить своё сообщение (удаление спросит подтверждение) |
| `f` | переслать сообщение в другой чат |
| `+` / `−` | поставить · убрать реакцию |
| `b` | нажать инлайн-кнопку бота |
| `v` / `o` | картинка на весь экран · открыть в системном просмотрщике |
| `ctrl+d` | скачать вложение целиком в `data/downloads` |
| `ctrl+e` | многострочный редактор |
| `ctrl+u` | догрузить историю выше |
| `ctrl+n` | следующий непрочитанный чат |
| `ctrl+b` | свернуть боковую панель (режим фокуса) |
| `shift+r` | отметить чат прочитанным |
| `ctrl+t` | следующая тема · `ctrl+p` — палитра команд |
| `f1` / `?` | справка · `f12` — SVG-скриншот |
| `ctrl+q` | выход |
Чаты **не** помечаются прочитанными автоматически — только по `shift+r` или с
флагом `--mark-read`.
Флаги: `--theme tgx-night|tgx-day|nord|gruvbox|catppuccin-mocha`, `--demo`,
`--mark-read`, `--no-notify`, `--no-splash`, `--effect <имя>`, `--limit N`.
## Список чатов и папки
Грузятся **все** чаты, а не первая страница: первые 200 показываются сразу, чтобы
интерфейс не ждал, остальные догружаются в фоне и вливаются в список (в строке
над полем ввода видно «догружаю остальные чаты…»). Ограничить можно флагом
`--limit N`, `0` — без ограничения, это и есть значение по умолчанию.
Папки Telegram применяются целиком, со всеми правилами `DialogFilter`, а не
только со списком явно добавленных чатов:
| правило папки | учитывается |
|---|---|
| явно добавленные и закреплённые чаты | да |
| явно исключённые чаты | да, исключение сильнее всего |
| категории: контакты, не-контакты, группы, каналы, боты | да |
| «исключить прочитанные / без звука / архив» | да |
Именно поэтому папка вида «все группы» раньше выглядела почти пустой — учитывался
только явный список.
## Чек-листы
Telegram умеет чек-листы прямо в сообщении, и tgx тоже. **`l`** на пустом месте
создаёт новый (заголовок и по пункту на строку), а на сообщении с чек-листом —
открывает его: `enter` отмечает или снимает пункт, поле снизу дописывает новые.
В бабле список виден целиком, с `☐`/`☑` и счётчиком выполненного.
Отмечать и дописывать могут другие участники — это два переключателя при
создании. Операции возвращают состояние **с сервера**, поэтому интерфейс не
расходится с тем, что видят остальные.
```bash
tgx todo @chat "План недели" "созвон" "макеты" "релиз"
tgx todo-check @chat 4321 --done 1,2 --undone 3
tgx todo-add @chat 4321 "ретро"
```
## Расшифровка голосовых
```bash
tgx transcribe status # доступна ли и сколько бесплатных осталось
tgx transcribe get @чат 295429 # голосовое или кружок → текст
tgx transcribe rate @чат 295429 3874… good
```
В TUI — `a` на выбранном голосовом; расшифровка появляется в самом пузыре
курсивом, а под голосовыми и кружками висит подсказка `a — расшифровать`.
Главная тонкость [документации](https://core.telegram.org/api/transcribe)
незаметна снаружи: ответ на `messages.transcribeAudio` почти всегда приходит с
флагом `pending` и пустым текстом, а готовая расшифровка прилетает позже
апдейтом `updateTranscribedAudio`. Подписка на него ставится **до** запроса —
иначе быстрый ответ проскакивает мимо, и команда молча возвращает пустую строку.
Если за `--wait` секунд текст не пришёл, отдаётся то, что успело, с пометкой
`pending`.
Без Premium Telegram даёт `transcribe_audio_trial_weekly_number` расшифровок в
неделю, каждая не длиннее `transcribe_audio_trial_duration_max` секунд; в
супергруппе с бустом от `group_transcribe_level_min` они не тратятся. `status`
показывает эти числа для вашего аккаунта, а остаток — `free_left` в ответе.
## Звонки
```bash
tgx call start @группа --title "Планёрка"
tgx call participants @группа / info / join-as
tgx call invite @группа @кто / mute @группа @кто --volume 150
tgx call record @группа --video / title @группа "Новое имя"
tgx call settings @группа --join-muted on / say @группа "начинаем"
tgx call stream-url @группа # адрес и ключ для внешней трансляции
tgx call watch @группа --open # живая страница участников
tgx call end @группа --confirm-to me --as @бот
```
Звук идёт по WebRTC, и на чистом Python его не сыграть — **зато всё вокруг
звука делается обычными вызовами**. Поэтому разделение честное: терминал
распоряжается звонком, а подключиться со звуком можно в приложении по ссылке.
**`watch` поднимает маленькую страницу** на `127.0.0.1` и обновляет её сама:
видно, кто говорит, кто заглушён и кто поднял руку. Терминалу такое
перерисовывать нечем — таблица мигала бы всем экраном. Страница наружу не
смотрит и ничего не сохраняет, а данные берёт у того же процесса, что их
опрашивает: второго соединения с Telegram не появляется.
Ссылку на звонок выдают только публичные чаты — у приватного её не существует,
и `watch` это переживает, показывая участников без неё.
## Сессии и приватность
```bash
tgx security sessions / websites # устройства и сайты
tgx security close-session HASH --confirm-to me --as @бот
tgx security privacy # кто что о вас видит
tgx security set-privacy last-seen contacts --deny @кто
tgx security global-privacy --hide-read on
tgx security session-ttl 7 / account-ttl 548
tgx security notify-exceptions
```
**Сессии и сайты — две разные породы.** Обычные входы и входы через «Войти в
Telegram» на сайтах сбрасываются разными методами, и сброс одного не трогает
другой; отсюда две команды, а не одна.
**Приватность Telegram хранит парами правил**: «только контактам» — это
«разрешить контактам» плюс «запретить всем остальным». Напечатанные подряд, они
читаются как противоречие — «контактам, никому», — поэтому сводка выбирает одну
основу и дописывает к ней исключения: `контактам · кроме 3 выбранных`. По этой
строке человек решает, что видно посторонним, и ошибка тут стоит дорого.
Основа (`everyone`/`contacts`/`nobody`) при изменении обязательна: правило без
неё молча оставило бы прежнюю аудиторию.
## Черновики, отложенные, заготовки, избранное
```bash
tgx pending drafts # все черновики; они видны только вам
tgx pending draft @чат "текст" # пустой текст стирает черновик
tgx pending scheduled @чат # что уйдёт само и когда
tgx pending send-now @чат 123 / cancel @чат 123
tgx pending shortcuts / shortcut 1 # быстрые ответы и их содержимое
tgx pending send-shortcut @чат 1
tgx pending saved / tags / name-tag 🔥 "Важное"
tgx pending fact-check @канал 123
```
Telegram держит порознь четыре разных способа отложить сообщение, и команды
повторяют это различие:
* **черновик** живёт в чате, виден только вам и **не отправится сам никогда**;
* **отложенное** — уже полноценное сообщение с назначенным временем: оно уйдёт
само, и «отменить» значит удалить его до срока;
* **быстрый ответ** — заготовка под ярлыком, которую отправляют вручную; именно
на неё ссылаются приветствие и автоответ бизнес-режима;
* **избранное** — пересланное себе, разложенное по авторам.
Черновик **перезаписывается целиком**, поэтому «дописать» его нельзя: сперва
читаем, потом сохраняем всё вместе. Время принимается и полное
(`2026-09-01T10:00`), и относительное (`+30m`, `+2h`, `+3d`); прошедшее
отвергается сразу, а не после отправки.
## Стикеры и статистика
```bash
tgx stickers show HotCherry # что внутри набора
tgx stickers suggest "Мой набор" --as @бот # подобрать свободное имя
tgx stickers create me "Мой набор" myset a.png=😀 b.png=😎 --as @бот
tgx stickers add myset c.png 🔥 --as @бот
tgx stickers move myset 3 0 / emoji myset 0 🎉 / thumb myset 0 --as @бот
tgx stats channel @канал / group @группа
tgx stats message @канал 123 / story @кто 490
tgx stats forwards @канал 123 # кто публично переслал
tgx stats graph @канал views_graph # догрузить один график
```
**Наборы правит бот, владельцем остаётесь вы.** Telegram требует указать
владельца, а вызывает метод бот — поэтому у команд правки обязателен `--as`,
и это не прихоть, а условие API.
**Стикер адресуется документом, а не номером.** Номер живёт до первой
перестановки, поэтому команды принимают позицию, но переводят её в документ
прямо перед вызовом — иначе после `move` следующая правка попала бы не в тот
стикер.
**Статистика приходит наполовину токенами.** Числа читаются сразу, а графики —
это токены, которые надо обменять отдельным вызовом. Поэтому `channel` печатает
список доступных графиков, а `graph` догружает один по имени: иначе одна
команда тянула бы десяток запросов.
## Общие папки
```bash
tgx share-folder share 24 "Рабочее" # выписать ссылку на набор чатов
tgx share-folder invites 24 / revoke 24 СЛАГ
tgx share-folder check СЛАГ # что внутри чужой ссылки — до принятия
tgx share-folder join СЛАГ
tgx share-folder updates 24 / accept 24 # что автор добавил с прошлого раза
tgx share-folder leave 24 --confirm-to me --as @бот
```
**Личные переписки в общую папку не отдаются** — Telegram их не делит, поэтому
`share` без списка чатов берёт только делимые, а если таких нет, говорит об этом
вместо пустой ссылки. И поделиться можно лишь папкой, созданной как общая:
обычная отвечает `FILTER_NOT_SUPPORTED`.
Принятая папка живёт дальше: автор добавляет чаты, а у вас появляются
обновления — их принимают (`accept`) или прячут (`hide-updates`).
## Истории
```bash
tgx stories feed # лента; --hidden — скрытые
tgx stories of @кто / pinned / archive
tgx stories publish фото.jpg --caption "…" --audience close --hours 24
tgx stories viewers 490 # кто смотрел и как отреагировал
tgx stories react @кто 332 ❤ / read @кто 332
tgx stories stealth # просмотры не засчитываются (Premium)
tgx stories albums / new-album "Лето" 464 465
tgx stories search хештег / link @кто 332 / hide @кто
tgx stories delete 490 --confirm-to me --as @бот
```
**Приватность по умолчанию — близкие друзья, а не «всем».** Аудитория задаётся
списком правил, которые складываются: `--audience contacts --deny @кто`
означает «контактам, кроме него». Промахнуться в сторону «всем» дороже, чем в
сторону «слишком узко», поэтому умолчание выбрано самым узким.
**Просмотр чужой истории виден автору.** Поэтому чтение ленты и отметка о
просмотре — разные команды: `feed` ничего не отмечает, `read` отмечает и прямо
говорит об этом. Скрытный режим (`stealth`) существует ровно затем, чтобы
просмотр не засчитался; он требует Premium.
Срок жизни — 6, 12, 24 или 48 часов; `--pin` оставляет историю в профиле после
срока.
## Контакты и чёрный список
```bash
tgx contacts list / search "имя" / by-phone +7…
tgx contacts add @кто --note "познакомились на конференции"
tgx contacts remove @кто
tgx contacts block @кто / unblock @кто
tgx contacts blocked --stories # кому запрещены истории
tgx contacts close-friends @а @б # список задаётся целиком
tgx contacts birthdays / top / import ССЫЛКА
```
Telegram различает три вещи, которые легко спутать, и tgx повторяет это
различие вместо того, чтобы прятать:
* **Контакт** — запись в адресной книге. `remove` убирает её, но писать вам
человек по-прежнему сможет; ответ команды об этом прямо говорит.
* **Блокировка** — запрет писать. У неё есть второй, независимый список:
`--stories` управляет тем, кому не видны ваши истории.
* **Близкие друзья** — отдельная отметка, влияющая только на истории; список
задаётся целиком, а не по одному человеку.
Заметка к контакту (`--note`) видна только вам, но хранится у Telegram — это
часть аккаунта, а не локальный файл. И свой номер при добавлении по умолчанию
не раскрывается: для этого есть отдельный `--share-phone`.
## Человек в контуре
Опасное не запрещено — оно спрашивает. Бот присылает карточку с описанием того,
что сейчас произойдёт, и двумя кнопками; действие ждёт нажатия.
```bash
tgx confirm "Удалить 40 сообщений?" --details "Чат: Команда" \
--danger "необратимо" --to me --as @мой_бот --timeout 300
```
Возвращает решение и завершается с кодом 2, если согласия не было, — сценарий
останавливается сам. Тем же ключом закрыты необратимые команды:
```bash
tgx delete @чат 10 11 12 --yes --confirm-to me --as @мой_бот
tgx guard check @чат --confirm-to me --as @мой_бот
tgx pay send "https://t.me/$…" --as @мой_бот --confirm-to me
```
Две вещи, без которых подтверждение было бы декоративным:
* **Нажать может только тот, кого спросили.** Кнопки видны всем в чате, поэтому
автор нажатия сверяется с адресатом; чужое нажатие отклоняется, ему
показывается пояснение, а сам факт попадает в ответ полем `strangers`.
* **Ключ одноразовый.** У каждого запроса свой случайный ключ, живущий до
первого решения или до истечения срока; повторное нажатие ничего не меняет.
Опрос идёт через `getUpdates`, поэтому у бота не должно быть вебхука и второго
получателя обновлений — иначе нажатие уйдёт не сюда.
## Платежи: звёзды, TON и счета
Покрыто **62 метода `payments` из 66**. Не взяты четыре: покупки через
App Store и Play Market (нужен чек от магазина, с настольного клиента их
не сделать) и служебный `TL`.
```bash
tgx pay balance # баланс звёзд
tgx pay balance --ton # он же в TON
tgx pay history --outbound # история операций
tgx pay receipt @чат 123 # чек по оплаченному сообщению
tgx pay show "https://t.me/$…" # что просит счёт — без оплаты
tgx pay invoice "Название" "Описание" --price "Строка=50" --as @бот
tgx pay gift-options @друг
```
Три валюты живут в одних и тех же вызовах и различаются флагом: `getStarsStatus`
с `ton=True` отвечает про криптовалюту, и так же устроена история операций.
Три вещи, на которых легко ошибиться:
* **Звёзды целые.** `--price "Донат=50"` — это пятьдесят звёзд. Обычные валюты
Telegram принимает в сотых (`12.5` USD → `1250`), и если применить это правило
к звёздам, счёт выписывается в сто раз больше. Заметно только у плательщика.
* **Счета выписывает бот.** От лица пользователя `payments.exportInvoice`
отвечает `USER_BOT_REQUIRED`, поэтому `--as @бот` обязателен. Звёздам
(валюта `XTR`) платёжный провайдер не нужен — Telegram сам платёжная система;
для остальных валют нужен токен провайдера.
* **Сумма операции лежит в поле `amount`,** а не `stars` — на `stars` приходят
сплошные нули, и история выглядит пустой.
Что ещё умеет `pay`:
```bash
tgx pay gifts # каталог подарков Telegram и цены
tgx pay my-gifts # полученные подарки
tgx pay subscriptions # подписки за звёзды
tgx pay revenue @канал --ton # сколько заработал канал
tgx pay check-code СЛАГ # что даёт подарочный код — до применения
tgx pay giveaway @канал 123 # сведения о розыгрыше
tgx pay saved-info # что Telegram хранит из платёжных данных
```
Тратящее и необратимое — только через подтверждение человеком:
```bash
tgx pay convert-gift 185532 --confirm-to me --as @бот # подарок → звёзды
tgx pay transfer-gift 185532 @друг --confirm-to me --as @бот
tgx pay react @канал 42 5 --confirm-to me --as @бот # платная реакция
tgx pay apply-code СЛАГ --confirm-to me --as @бот
tgx pay cancel-subscription @канал ID --confirm-to me --as @бот
tgx pay clear-saved --confirm-to me --as @бот
```
Без `--confirm-to` такие команды отказываются работать — это не флаг вежливости,
а условие запуска.
Подарки — от витрины до вторичного рынка:
```bash
tgx pay gift 185532 # подробности своего подарка
tgx pay upgrade-preview ID # что даст улучшение — до оплаты
tgx pay resale ID # кто что продаёт и почём
tgx pay unique СЛАГ # уникальный подарок по ссылке
tgx pay collections / new-collection # коллекции на витрине
tgx pay show-gift 185532 --hide # спрятать из профиля
tgx pay pin-gift 185532 # закрепить наверху
tgx pay topup # пакеты звёзд и цены
tgx pay auctions / referrals / can-send-gift ID
```
С воротами подтверждения: `upgrade-gift`, `sell-gift` (выставить цену или снять),
`refund` (вернуть звёзды за покупку).
**Цена приходит списком.** Одна и та же вещь стоит и звёзд, и TON: `460 ⭐` —
это `5.07 TON`. При этом у TON нет поля `nanos`: вся сумма записана в `amount`
в нанотонах, и если поделить её как звёзды, `4.68 TON` превращаются в
`4 680 000 000`.
Аукционы, крафт, партнёрские программы и прочее:
```bash
tgx pay auction-state ID / auction-won ID
tgx pay craftable ID # из чего собрать подарок
tgx pay suggested-referrals # какие боты предлагают партнёрство
tgx pay referral @бот # условия и заработок по одной программе
tgx pay premium-options # почём подарить Premium
tgx pay giveaway-options # варианты розыгрышей звёзд
tgx pay unique-value СЛАГ # во что оценивается уникальный подарок
tgx pay upgrade-attributes ID # варианты оформления и их редкость
tgx pay transaction ID… # операции по идентификаторам
tgx pay ads-account @канал
tgx pay validate-info ССЫЛКА --name … # проверить данные ДО списания
```
За воротами: `craft`, `offer`, `answer-offer`, `connect-referral`,
`revoke-referral`, `delete-collection`, `fulfil-subscription`,
`gift-to-blockchain`, `pay-card`.
**Оплата картой — без ввода карты.** Telegram хранит способ оплаты у себя;
`tgx pay pay-card` берёт временный пароль в обмен на пароль от аккаунта (по SRP)
и платит сохранённым способом. Номер карты не проходит ни через аргументы, ни
через этот процесс. Первый платёж картой всё равно делается в приложении — там
способ и сохраняется.
Секреты — скрытым вводом, не аргументом:
```bash
tgx pay withdraw @канал 100 --confirm-to me --as @бот
tgx pay card-bank
```
Оба спрашивают секрет в скрытой строке: он не появляется ни в аргументах, ни в
истории оболочки, ни в списке процессов. Для автоматизации есть переменные
`TGX_PASSWORD` и `TGX_CARD_NUMBER`; без терминала и без переменной команда
честно отказывает, а не зависает в ожидании ввода, которого никто не сделает.
* **Вывод** сперва спрашивает согласие кнопкой, потом пароль. Пароль на сервер
не уходит: Telegram проверяет его по SRP, то есть по доказательству знания.
Денег команда не переводит — возвращает ссылку на страницу вывода, где
подтверждение происходит уже у человека.
* **Банк по карте** требует полный номер: на первых шести цифрах сервер
отвечает отказом. Справочник банков лежит в другом дата-центре, и запрос
повторяется там, куда указал сервер.
**Оплата счёта — тоже за воротами подтверждения.** `tgx pay send` сначала
перечитывает счёт, показывает сумму человеку карточкой с кнопками и списывает
звёзды лишь после «Разрешить». Форма запрашивается заново перед списанием:
между показом суммы и согласием проходит время, а платить надо ровно за то, что
человек видел. Карты отсюда не оплачиваются — платёжные данные вводят на своём
экране.
## Эффекты и свёрнутые цитаты
```bash
tgx effects --search 🎉 # 697 эффектов: эмодзи и id
tgx send @друг "с праздником" --effect 5046509860389126442
```
Из Bot API 7.4. **Эффект работает только в личной переписке** — в группе и
канале сервер отвечает `EFFECT_CHAT_INVALID`, поэтому tgx отказывает заранее и
объясняет почему.
Оттуда же свёрнутые цитаты во входящих: в терминале сворачивать нечего, но
такая врезка помечается уголком `▾` вместо обычной полосы `▌` — иначе длинная
цитата выглядит обычным текстом, которым автор её не считал.
## Копии и бусты
```bash
tgx copy @откуда @куда 123 124 --drop-captions --topic 2
tgx boosts status @канал # уровень и сколько до следующего
tgx boosts who @канал # кто бустил
tgx boosts mine # свои слоты
tgx boosts give @канал
```
`copy` — это `copyMessage` из Bot API: тот же метод, что и пересылка, но с
`drop_author`, поэтому получатель не видит источник и сообщение не тянет за
собой ссылку на него.
## Обложка и точка старта видео
```bash
tgx send @чат "подпись" --file ролик.mp4 --cover обложка.png --start-at 12
```
Из Bot API 8.3. Telethon не пробрасывает `video_cover` и `video_timestamp`
через `send_file`, поэтому медиа собирается вручную, а обложка сперва
загружается как фотография.
**У ролика без звуковой дорожки этого не будет.** Telegram добавляет такому
файлу `DocumentAttributeAnimated`, обращается с ним как с гифкой, и оба поля
молча пропадают — видно только на живом сообщении.
## Дата и время в тексте
Сущность `date_time` (Bot API 9.5, в слое MTProto — `MessageEntityFormattedDate`)
хранит момент времени, а не написанную кем-то строку: каждый клиент показывает
его по-своему. tgx подставляет момент по флагам автора — день недели, короткая
или длинная дата, время с секундами или без.
## Опросы и викторины
```bash
tgx poll create @чат "Вопрос?" "А" "Б" --multiple --members-only --countries RU,DE
tgx poll create @чат "Вопрос?" "А" "Б" --quiz 0 --as @бот --explanation "почему А"
tgx poll vote @чат 36 0 # без вариантов — снять голос
tgx poll results @чат 36
tgx poll voters @чат 36 --option 0 # если опрос не анонимный
tgx poll close @чат 36
```
Из Bot API 10.0: одного варианта теперь достаточно (раньше требовалось два),
`--members-only` ограничивает опрос подписчиками, `--countries` — списком стран.
Из 9.6: правильных ответов в викторине может быть несколько (`--quiz 0 2`),
появилось `--allow-revoting`, а срок автозакрытия вырос с суток до месяца.
**Викторина требует `--as @бот`.** В схеме `inputMediaPoll.correct_answers` —
вектор байтовых строк, а Telethon 1.44 объявляет и пакует его как вектор чисел,
из-за чего сервер отвергает любое значение. Через Bot API правильный ответ
задаётся обычным номером, поэтому викторина уходит оттуда. Обычные опросы этим
не задеты и идут по MTProto от вашего имени.
## Кнопки, вложения и богатые сообщения
```bash
tgx rich @чат --file пост.md --topic 2 --media banner=баннер.png # от своего имени
tgx bot rich @чат --as @мой_бот --file пост.md --topic 2 \
--media banner=баннер.png --button "Сайт[primary]=https://…" # от бота, с кнопками
```
Три вещи, которые стоят за этими флагами:
* **Кнопки вешает только бот.** Обычному аккаунту сервер `reply_markup` принимает
без ошибки и молча выбрасывает — ни у простого сообщения, ни у богатого кнопок
не появится. Проверять надо чтением сообщения обратно, а не отсутствием
исключения.
* **Стили кнопок** появились в слое 227: `Текст[primary]=…`, `[danger]`,
`[success]`, а `[success:5312…]` ставит эмодзи вместо галочки.
* **Блочная форма (10.2–10.3)** — единственный способ положить кнопки и файлы
*внутрь* документа: `tgx bot rich --blocks файл.json --attach имя=путь`.
Сборщики блоков лежат в `tgx_rich`: `heading`, `quote(expandable=True)`,
`table(compact=True)`, `button_row`, `document_block`. Отправка работает, а вот
**показать такое сообщение tgx пока не может**: конструкторы блочной формы
новее слоя MTProto, который знает Telethon, и оно приходит как «сообщение
нового типа». Получатель с обновлённым клиентом видит документ полностью.
* **Картинка внутрь документа.** Bot API берёт медиа либо по публичному URL, либо
частью формы, поэтому файл с диска уходит как `attach://` вместе с multipart —
это единственный способ, когда репозиторий приватный и публичной ссылки нет.
Если ссылки `tg://photo?id=…` в тексте нет, tgx ставит картинку в начало сам:
иначе Telegram молча выбросит вложение.
## Именные приглашения
```bash
tgx guard lock @чат # звать людей могут только администраторы
tgx guard invite @чат @кого --note "зачем" # ссылка на одного, одно использование
tgx guard check @чат # вошёл не тот — удаляем, ссылку отзываем
tgx guard journal
```
Ссылку легко переслать, поэтому сверка идёт по факту входа, а не по обещанию:
журнал (права 600) помнит, кому она выписана, а `check` сравнивает это с тем, кто
реально вошёл. Лишний удаляется баном с мгновенным разбаном — без вечного бана.
Решение «выгнать» вынесено в отдельную функцию и покрыто тестами, чтобы не
проверяться на живых людях.
## Форумы и темы
Тема — это не папка, а тред служебного сообщения, которым её создали: id темы
равен id этого сообщения. Отсюда все особенности, и tgx закрывает
[core.telegram.org/api/forum](https://core.telegram.org/api/forum) целиком:
```bash
tgx forum on @группа --tabs on # включить форум (только владелец)
tgx forum topics @группа # список; --search ищет по названию
tgx forum icons # иконки, доступные без Premium
tgx forum create @группа "Релизы" --color зелёный
tgx forum create @группа "Идеи" --emoji 5312536423851630001
tgx forum edit @группа 4 --title "Архив" --close
tgx forum pin @группа 2
tgx forum reorder @группа 3 2 --force
tgx forum show @группа 4 # так же узнают, что тему удалили
tgx forum delete @группа 4 --yes # необратимо: уносит всю переписку
tgx forum as-messages @группа on # показывать форум сплошной лентой
```
Правила Telegram зашиты в команды, а не оставлены на память:
* **«Общую» тему (id 1) нельзя удалить** — только скрыть; и скрыть можно
единственно её, остальные закрывают.
* **Цвет стандартной иконки выбирается один раз при создании** — у
`editForumTopic` такого поля просто нет.
* **Переименование и закрытие — разные запросы.** На попытку сделать это одним
сервер отвечает `TOPIC_CLOSE_SEPARATELY`; `tgx forum edit` разбивает вызов сам.
* **Иконка-эмодзи без Premium — только из стандартного набора**, его печатает
`tgx forum icons`.
* **Закрепить можно `topics_pinned_limit` тем** (у аккаунта — 5); `reorder`
проверяет это до отправки.
* **Об удалении темы отдельного апдейта нет** — пропадает корневое сообщение, а
`tgx forum show` возвращает по этому id `{"deleted": true}`.
В TUI на экране тем добавлено `d` — удаление с подтверждением вторым нажатием.
## Оформление профиля
Аватар в Telegram — уже не «одна картинка»: тем же полем ставятся четыре разные
вещи, и одинаково для себя, канала, бота и контакта.
```bash
tgx profile formats # шпаргалка по форматам
tgx profile photo фото.png # статичный аватар
tgx profile photo клип.mp4 --start 2.5 # видеоаватар; --start выбирает обложку
tgx profile photo emoji:5366316836101038579 --colors "#229ED9,#17212B"
tgx profile photo sticker:набор:42 # стикер на градиенте
tgx profile photo лого.png --chat @канал # аватар канала или группы
tgx profile photo лого.png --bot @my_bot # аватар своего бота
tgx profile photo лого.png --contact @друг # фотография контакта у вас
tgx profile photo лого.png --contact @друг --suggest # предложить её ему
tgx profile photo лого.png --fallback # публичный запасной аватар
tgx profile photos # что у вас стоит: фото, видео, эмодзи
tgx profile emojis --kind status # эмодзи с их id, чтобы было что выбрать
tgx profile color --color 5 --emoji 5379748062124056162
tgx profile status --emoji 5413694143601842851 --until 1780000000
tgx profile birthday 14.03.1990
tgx profile personal-channel @мой_канал
```
Не квадратное или слишком длинное видео tgx замечает до отправки и предлагает
`--square` и `--trim` — они обрезают через ffmpeg, а не оставляют это Telegram.
### Заставка в аватар
Анимированный баннер tgx можно записать прямо из терминала и поставить аватаром.
Кадры снимаются с настоящего псевдотерминала и разбираются эмулятором, так что в
видео попадает ровно то, что видно в окне, — вместе с блочными глифами и
градиентом:
```bash
tgx profile banner --bot @my_bot # записать и поставить
tgx profile banner --chat @канал --effect decrypt
tgx profile banner --save баннер.mp4 --effect matrix --speed 40
```
Обложка выставляется сама на тот момент, когда логотип уже собран, — иначе в
профиле висел бы пустой первый кадр. Цель указывается явно (`--bot`, `--chat`,
`--me`, `--save`): собственный аватар слишком легко сменить по ошибке.
## Бот-секретарь (Telegram Business)
Бот, подключённый к личному аккаунту, читает и отвечает в ваших приватных чатах.
Настраивается это со стороны аккаунта, поэтому живёт в tgx, а не в боте:
```bash
tgx bot secretary @my_bot on # без этого Telegram ответит BOT_BUSINESS_MISSING
tgx business bots # кто уже подключён и с какими правами
tgx business connect @my_bot --rights reply,read_messages --chats all --exclude @boss
tgx business pause @chat # приостановить бота в одном чате
tgx business disconnect @my_bot
tgx business quick-replies # быстрые ответы и их id
tgx business greeting --shortcut 1 --after-days 7
tgx business away --shortcut 3 --schedule outside
tgx business hours "пн-пт 9:00-18:00; сб 10:00-14:00" --timezone Europe/Moscow
tgx business intro --title "Привет" --text "Отвечаю в течение дня"
tgx business link "Здравствуйте! Чем помочь?" --title "Поддержка"
```
Права выдаются поштучно: `reply`, `read_messages`, `delete_sent_messages`,
`delete_received_messages`, `edit_name`, `edit_bio`, `edit_profile_photo`,
`edit_username`, `view_gifts`, `sell_gifts`, `change_gift_settings`,
`transfer_and_upgrade_gifts`, `transfer_stars`, `manage_stories` — или `all`/`none`.
Область: `all`, `contacts`, `non-contacts`, `existing`, `new`, плюс `--exclude`.
Два подводных камня, оба проверены на живом аккаунте, ни один не описан в документации:
* **Бот должен сам разрешить это.** В BotFather переключатель называется
«Secretary Mode» (в API — бизнес-режим); пока он выключен, подключение падает
с `BOT_BUSINESS_MISSING`. `tgx bot secretary` щёлкает его за вас.
* **Подключённый бот всегда один.** `account.getConnectedBots` возвращает вектор,
и ограничения в документации нет — но второй бот молча отключает первого.
Поэтому `connect` отказывается затирать чужое подключение без `--replace`
и называет того, кого собирается вытеснить.
Со стороны бота это `business_connection_id` в Bot API: он получает апдейты
`business_message` и отвечает от вашего имени. Нужен Telegram Business (Premium).
**Ничего из этого не отдано агентам** — подключение бота к личной переписке
решает человек, не модель.
## Богатые сообщения (Bot API 10.1)
Отдельный вид сообщения из Bot API 10.1 (11 июня 2026), дополненный в 10.2: не
текст с разметкой, а **документ** — заголовки шести уровней, таблицы, списки
задач, сноски, раскрывающиеся блоки, формулы, галереи, блок «размышления».
Отправлять их можно двумя путями, и tgx умеет оба:
- **от своего аккаунта** — по MTProto (`messages.sendMessage(rich_message=…)`,
слой 227, Telethon 1.44). Ограничение «только боты» относится к Bot API, а не
к протоколу;
- **от имени бота** — по HTTP Bot API, с кнопками под сообщением и потоковым
черновиком `sendRichMessageDraft`.
Входящие богатые сообщения клиент **отрисовывает в терминале**: заголовки,
списки, таблицы, цитаты, код, спойлеры и сноски. Внутри это дерево блоков
Instant View (`PageBlock*`), поэтому рендер общий с обычной разметкой.
```bash
tgx bot rich-syntax # шпаргалка по разметке
tgx bot rich @channel --as @my_bot --file post.md \
--button "Открыть=webapp:https://app.example.com"
tgx bot rich @channel --as @my_bot --file post.md --draft # потоковый черновик
tgx bot rich @channel --as @my_bot --file post.md \
--media cover=https://example.com/cover.jpg # для 
```
Разметка (`markdown`-поле `InputRichMessage`):
```
# Заголовок 1 … ###### Заголовок 6 **жирный** *курсив* ~~зачёркнутый~~
==выделенный== ||спойлер|| `код` ```python … ``` $x^2 + y^2$
- пункт 1. нумерованный - [ ] задача - [x] сделано
> цитата
| Заголовок | Ещё |  Сноска[^1]
|:----------|:---:|  [^1]: текст
```
Лимиты Telegram проверяются локально, до запроса, и объясняются по-человечески:
32768 символов, 500 блоков, 16 уровней вложенности, 50 вложений, 20 колонок в
таблице. Ссылки вида `tg://photo?id=…` должны быть перечислены в `--media`,
иначе tgx откажет заранее.
В TUI это переключатель **«богатое»** в редакторе поста (`p`) рядом с выбором
бота: без бота редактор предупредит, потому что личный аккаунт такие сообщения
слать не умеет. Агентам добавились `rich_syntax` (чтение) и `bot_send_rich`
(запись).
Не путать с соседним разделом: `article` публикует **страницу на telegra.ph** и
кидает ссылку, а `bot rich` отправляет **само сообщение** внутри Telegram.
## Статьи на telegra.ph
Длинный текст в Telegram лучше живёт статьёй: ссылка на telegra.ph открывается
через Instant View прямо в приложении. tgx делает статью из маркдауна:
```bash
tgx article account --name tgx --author "Alex" # один раз: аккаунт и токен
tgx article new "Заголовок" --file post.md --publish @mychannel
tgx article new "Заголовок" --file post.md --publish @mychannel --as @my_bot
tgx article edit Zagolovok-08-28 "Новый заголовок" --file post.md
tgx article list
```
Поддерживается ровно то, что Telegraph умеет: `#` → h3, `##` → h4, абзацы,
списки `-` и `1.`, цитаты `>`, блоки кода, разделители `---`, картинки, ссылки,
`**жирный**`, `*курсив*`, `` `код` ``, `~~зачёркнутый~~`. Токен telegra.ph даёт
право править все ваши статьи, поэтому лежит в `data/telegraph.json` с правами
`600` и показывается маской.
## Поиск с фильтрами
`ctrl+f` открывает поиск с полями: где искать (этот чат / все чаты), тип
сообщений, отправитель и границы дат. Тип можно задать и без текста запроса —
например «все ссылки за последнюю неделю».
| тип | что попадёт |
|---|---|
| `photo` · `video` · `media` | фото, видео, и то и другое |
| `file` · `link` · `voice` · `music` · `gif` · `round` | документы, ссылки, голосовые, музыка, гифки, кружки |
| `mention` · `pinned` | упоминания вас · закреплённые |
| `geo` · `contact` · `poll` | геометки, контакты, опросы |
Даты понимают `2026-08-01`, `01.08.2026` и относительное `-7d` / `-12h`.
```bash
tgx search "релиз" --peer @channel --since -30d
tgx search --kind link --peer @channel --since 2026-08-01 --until 2026-08-28
tgx search "договор" --kind file --global
tgx search "" --kind mention --global --limit 50
tgx search "спасибо" --peer @chat --sender @alex
```
Две честные границы, они идут от самого Telegram: у глобального поиска нет поля
отправителя, поэтому `--sender` работает только вместе с `--peer`; и нижнюю
границу дат сервер не принимает, поэтому `--since` применяется при чтении —
результаты идут от новых к старым, и выборка просто останавливается на границе.
## Темы форума и закрепления
В группе с включёнными темами над перепиской появляется полоса тем: переключение
переносит и чтение, и отправку в выбранный тред. У обычных чатов полоса скрыта.
- **`t`** — список тем: `enter` открыть, `n` создать (название берётся из поля
внизу), `r` переименовать, `c` закрыть/открыть, `p` закрепить.
- **`shift+p`** — закрепить или открепить выделенное сообщение. Закрепление
**тихое**: участников оно не будит. Закреплённые помечены `📌` прямо в бабле.
```bash
tgx topics @forum # список тем
tgx topic-create @forum "Релизы"
tgx topic-edit @forum 12 --title "Релизы 2026" --close
tgx topic-pin @forum 12 # или --unpin
tgx channel-edit @group --forum 1 # включить темы в существующей группе
tgx pin @chat 4321 # тихо; --notify разбудит участников
tgx pin @chat 4321 --unpin
tgx pinned @chat --limit 20
```
Агентам доступны `list_topics`, `list_pinned` на чтение и `create_topic`,
`edit_topic`, `pin_message` на запись — последний по умолчанию тоже тихий,
а `notify: true` в его описании отдельно помечен как то, о чём стоит спросить
пользователя.
## Боты
API для создания бота у Telegram нет — это разговор с @BotFather. tgx ведёт его
за вас и проверяет каждый шаг: если BotFather ответил не то, вы увидите **его
собственные слова**, а не «что-то пошло не так». Интерфейс разговорный и
формулировки в нём иногда меняются, поэтому это самая хрупкая часть — и самая
подробная по диагностике.
**А как же `bots.createBot` из слоя 227?** Он существует, но заменой BotFather не
является, и это стоило проверки. Вызов из своего аккаунта отвечает
`MANAGER_INVALID`: управляющим обязан быть бот. Подставляешь своего бота —
`MANAGER_PERMISSION_MISSING`: этому боту BotFather должен был выдать право
«управлять ботами». То есть путь работает для тех, кто строит ботов ботами, а не
для «завести бота из терминала». Он доступен как `--manager`, но по умолчанию
`bot create` идёт через BotFather — он единственный отдаёт токен владельцу.
Так же устроены `exportBotToken`, `getBotCommands`, `getBotMenuButton`: это
вызовы **для бота**, из пользовательского аккаунта приходит `USER_BOT_REQUIRED`.
Поэтому `bot commands`, `bot menu-get` и `bot group-rights` принимают имя бота и
работают под его сессией. А `bot mine` и `bot previews` спрашиваются от вашего
имени — и `mine` теперь мгновенный, без переписки с BotFather.
```bash
tgx bot mine # ваши боты, одним вызовом
tgx bot commands tgx_ops_bot # команды глазами пользователя
tgx bot menu-get tgx_ops_bot # какая сейчас кнопка-меню
tgx bot previews tgx_ops_bot # картинки до запуска
tgx bot group-rights tgx_ops_bot --pin --delete
tgx bot create "Имя" myhelperbot --manager tgx_ops_bot # если право выдано
```
```bash
tgx bot create "Мой бот" my_bot # создать и сохранить токен
tgx bot list # токены показаны маской
tgx bot token @my_bot --reveal # забрать токен у BotFather
tgx bot revoke @my_bot # отозвать и получить новый
tgx bot setname @my_bot "Новое имя" # setabout / setdescription / setcommands
tgx bot info @my_bot # как бота видит API
tgx bot mine # каких ботов знает BotFather
tgx bot me @my_bot # вход по токену: кто это
tgx bot forget @my_bot # убрать из локального реестра
```
Имя, «о боте», описание и список команд ставятся **прямо через API**
(`bots.setBotInfo`, `bots.setBotCommands`) — там нечему сломаться. Через диалог с
BotFather идёт только то, для чего API не существует: создание бота и получение
или отзыв токена. Если всё же нужен старый путь — флаг `--via-botfather`.
Токены — учётные данные: `data/bots.json` пишется с правами `600`, в выводе
маскируется (`123456789:AAE…abcd`), целиком печатается только по `--reveal`
и агентам не отдаётся никогда.
### Посты от имени бота, кнопки и мини-приложения
Инлайн-кнопки под постом может вешать только бот — у личного аккаунта такой
возможности в Telegram нет:
```bash
tgx bot post @channel "**Релиз 1.0**" --as @my_bot \
--button "Скачать=https://example.com/dl, Открыть=webapp:https://app.example.com ; Отзыв=cb:feedback"
```
| синтаксис | кнопка |
|---|---|
| `Текст=https://…` | ссылка |
| `Текст=webapp:https://…` | мини-приложение (Web App) |
| `Текст=cb:данные` | callback — отвечает работающий бот |
| `Текст=switch:запрос` | поделиться инлайн-запросом |
| `Текст=copy:значение` | копирование текста |
| `Текст=user:12345` | профиль пользователя |
Ряды разделяются `;`, кнопки в ряду — запятой; шпаргалка — `tgx bot buttons`.
Мини-приложение вешается и на меню самого бота:
```bash
tgx bot menu @my_bot --text "Открыть" --url https://app.example.com
tgx bot menu @my_bot --reset
```
В TUI то же самое в редакторе поста (`p`): выпадающий список «от себя / @бот» и
поле кнопок. Если выбрать кнопки от личного аккаунта, редактор предупредит и не
отправит — Telegram такое не примет.
Агентам доступны `list_bots` (только имена, без токенов) и `bot_post` — публикация
от имени бота со всеми типами кнопок.
## Правка текста руками Telegram
У сервера есть то, чего нет в Bot API: он вычитывает текст, расставляет эмодзи,
переводит и переписывает в заданном тоне. Возвращает не только результат, но и
**разметку правок** — что заменено, что убрано, что добавлено. Мы её
показываем: принимать чужую правку вслепую — плохая привычка.
```bash
tgx ai compose "превет колега завтро релиз"
tgx ai compose "релиз завтра, всё готово" --tone viking
tgx ai compose письмо.txt --translate en --apply @roman
```
Правки приходят как разметка поверх слитого текста, где куски старой и новой
версии лежат рядом. Мы проходим его один раз и собираем одну строку:
```
[-превет-]{+Превет,+} колега{+. Завтра+} [-завтро -]релиз{+.+}
```
Глазами это разбирается быстрее списка, поэтому строка идёт первой, а список —
под ключом `подробно`.
Тонов семь встроенных (`formal`, `short`, `tribal`, `corp`, `biblical`,
`viking`, `zen`), и можно завести свой — эмодзи, название и указание, как
писать. Свои тоны живут как наборы стикеров: их ставят, ими делятся, их удаляют.
```bash
tgx ai tones
tgx ai tone-example viking
tgx ai tone-new "Сухо" "Пиши коротко и без прилагательных" --credit
tgx ai tone-save чужой-тон
tgx ai tone-delete мой-тон --confirm-to @me # исчезнет у всех, кто поставил
```
## Пересказ и перевод
Помимо правки, сервер умеет **пересказывать** и **переводить**. В графическом
клиенте это кнопки, о которых половина людей не знает; в терминале это команды.
```bash
tgx ai summarize durov 544 --lang ru # длинный пост в три фразы
tgx ai translate en --text "Завтра релиз"
tgx ai translate ru --peer durov --id 544 # перевести чужое сообщение
tgx ai auto-translate чат on # полоска «перевести» в чате
```
Отсюда получается то, ради чего терминал и нужен, — **сводка по чату**:
```bash
tgx ai digest durov --limit 20 --lang ru
```
Сервер пересказывает по одному сообщению, поэтому сводку `tgx` собирает сам:
длинное отдаёт на пересказ, короткое берёт как есть. Пересказывать «ага» смысла
нет, а лишний вызов стоит времени и попадает под ограничение частоты. Если
сервер на каком-то сообщении отказался, это видно строкой в ленте — сводка не
рушится целиком.
## Что ждёт внимания
В графическом Telegram непрочитанное видно боковым зрением: значки, кружки,
жирные точки. В терминале бокового зрения нет, поэтому нужны прямые вопросы.
```bash
tgx mentions чат # где вас звали и вы ещё не видели
tgx my-reactions чат # на что вам отреагировали
tgx triage-clear чат # пометить и то и другое просмотренным
tgx read-by группа 42 # кто прочитал это сообщение
tgx read-at @roman 42 # когда прочитали ваше
tgx views канал 544 543 # просмотры и пересылки постов
tgx chat-counts чат # чем набит: фото, видео, ссылки, файлы
tgx online чат # сколько человек сейчас
tgx mark-unread чат # вернуть жирную точку, чтобы не забыть
tgx pin-chat чат # закрепить чат в списке
tgx no-forwards чат on # запретить пересылку и копирование
```
`read-at` работает взаимно: пока вы прячете своё время прочтения, чужое вам не
покажут. Мы так и пишем в ошибке — это не поломка, а устройство.
## Уведомления, заявки и реакции
Три темы, которые в графическом клиенте лежат по разным углам, а на деле про
одно: кто может вас разбудить и кого вы впускаете.
```bash
tgx notify show # как настроено для личных чатов
tgx notify mute 2h "AllCrew Cockpit" # заглушить на два часа
tgx notify mute forever чат # насовсем
tgx notify mute off чат # снять
tgx notify mute 8h --scope channels # все каналы разом
tgx notify reactions --from contacts # реакциями будят только контакты
tgx notify new-contacts off # молчать о регистрации знакомых
```
Тишина у Telegram считается не «часами молчания», а **отметкой времени, до
которой чат молчит**. Отсюда две ловушки, обе видны в выводе, если их не
обработать:
* незаглушённый чат приходит нулём, и Telethon превращает ноль в 1 января 1970
года — «тихо до 1970 года» было бы враньём, поэтому и ноль, и любую дату в
прошлом мы считаем отсутствием тишины;
* «навсегда» — это дата на краю тридцатидвухбитного времени, а не отдельный
признак; мы её так и называем словом.
Заявки на вступление — оборотная сторона именных приглашений:
```bash
tgx notify requests чат # кто из админов сколько ссылок наделал
tgx notify approve чат @roman # принять
tgx notify decline чат @stranger # отклонить
tgx notify decline-all чат --link https://t.me/+… # всех по одной ссылке
tgx notify invite-edit чат ССЫЛКА --limit 1 --expires 2h --request-needed
tgx notify invite-purge чат # выбросить отозванные — они копятся
```
`invite-edit` правит выпущенную ссылку, не выпуская новую: это важно, когда
ссылка уже кому-то отправлена, а условия поменялись.
Реакции:
```bash
tgx notify emoji-list # какие вообще есть
tgx notify emoji-top --limit 8 # какие ставят чаще всего
tgx notify emoji-default 🔥 # ваша по умолчанию
tgx notify emoji-allow чат 👍 🔥 ❤ # что можно в этом чате
tgx notify emoji-allow чат all # любые
tgx notify emoji-allow чат # запретить все
```
## Стикеры: не только собирать, но и пользоваться
Собрать набор `tgx` умел давно, а вот найти и отправить стикер — нет.
```bash
tgx stickers installed # какие наборы у вас стоят
tgx stickers mine # какие вы сделали сами
tgx stickers find-sets "cat" # найти набор
tgx stickers find --emoji 🔥 # найти отдельные стикеры
tgx stickers recent # недавние
tgx stickers faved # избранные
tgx stickers install LadyCat # поставить набор
tgx stickers send @roman 4909…:-629…
```
Две вещи, которые выясняются только на практике:
* **Отправить стикер файлом нельзя.** Загруженный заново `.webp` станет обычной
картинкой: стикером его делает принадлежность набору, а её несёт только
исходный документ. Поэтому `find` печатает ключ вида `id:hash` — это то, чем
ссылаются на существующий стикер, и без второй половины сервер отвечает, что
документа не существует.
* **Поиск по эмодзи возвращает не то, что вы искали.** Сервер подбирает стикеры
по связям, и собственное эмодзи стикера обычно другое: спросишь 🔥, получишь
😡. Поэтому поле называется «своё эмодзи», а не «эмодзи» — чтобы не выглядело
ошибкой.
Часть наборов приходит без короткого имени — у них его попросту нет. Сослаться
на такой можно только числами, поэтому `set_ref` принимает и `id:hash`.
## Видео в богатом сообщении
Через разметку не выйдет: ссылка `tg://photo?id=` принимает только фотографию,
а `tg://video?id=` не существует — сервер отвечает `RICH_MESSAGE_PHOTO_INVALID`.
Раньше туда молча уезжал mp4 и превращался в стоп-кадр.
Работает блочная форма. Проверено на живом сервере: приходит `PageBlockVideo` с
настоящим mp4 внутри.
```json
[
{"type": "heading", "size": 1, "text": "Заголовок"},
{"type": "video", "video": {"type": "video", "media": "attach://banner"}},
{"type": "paragraph", "text": "Подпись рядом"}
]
```
```bash
tgx bot rich чат --as мойбот --blocks blocks.json --attach banner=баннер.mp4
```
Ссылка `attach://` лежит в поле, **названном по типу блока**: у документа в
`document`, у видео в `video`, у фотографии в `photo`. Пока `tgx` искал её
только в `document`, у видеоблока имя выходило пустым — и в форму уезжало поле
без имени. Сервер на такое отвечает `400` с **пустым телом**, а пустое тело
превращалось в `Expecting value: line 1 column 1`. Теперь пустой ответ называется
пустым ответом, а имя поля берётся по типу блока.
Собирать дерево руками не обязательно: `--as-blocks` переводит вашу разметку в
блоки у себя, а не на сервере, — и тогда `` работает.
```bash
tgx bot rich чат --as мойбот --file пост.md --as-blocks --attach banner=баннер.mp4
```
Форма текста внутри блока выяснена опытом, в документации её нет:
| что пишете | что получается |
| --- | --- |
| `"строка"` | `TextPlain` |
| `["часть ", {…}]` | `TextConcat` |
| `{"type": "bold", "text": …}` | `TextBold`; так же italic, code |
| `{"type": "url", "text": …, "url": …}` | `TextUrl` |
Узлы вкладываются друг в друга. Привычная по обычным сообщениям пара
`{"text": …, "entities": […]}` здесь **не принимается вовсе** — сервер отвечает
`Can't find field "type"`.
Помните про ограничение блочной формы: она новее слоя MTProto, который знает
Telethon, поэтому отправить её можно, а прочесть обратно в `tgx` — только
структуру, не содержимое документа.
## Мини-приложение, запущенное из терминала
Подписанный адрес, открытый в обычной вкладке, даёт **сломанное** приложение.
Мини-приложение ждёт вокруг себя хозяина: скрипт внутри страницы шлёт наружу
«я готов», «дай тему», «покажи главную кнопку», «закрой меня» — и ждёт ответов.
В браузере отвечать некому, поэтому приложение либо висит на заставке, либо
считает, что запущено вне Telegram.
```bash
tgx inline run wallet # своё окно вокруг приложения
tgx inline run мойбот --param ref_12345
```
`tgx` изображает хозяина: локальная страница держит приложение в рамке,
разговаривает с ним по тому же протоколу и рисует его кнопки своими руками.
Главную кнопку и кнопку «назад» видно в окне; данные, которые приложение
отправляет, печатаются в терминал.
Чего мы не изображаем: оплату, доступ к контактам, камеру, отпечаток пальца.
Такие запросы получают **отказ вслух**, и он виден в окне. Честный отказ лучше
тишины, в которой непонятно, что сломалось.
**Приложение подаётся с нашего адреса.** Иначе никак: чужая страница может
запретить себя встраивать, а её содержимое в любом случае закрыто правилом
одного происхождения — ни прочитать, ни нажать. Проводник превращает любой адрес
в путь `/x/<схема>/<хост>/<путь>`, и приложение оказывается обычной страницей
внутри нашей.
Что при этом пришлось выяснить на живых приложениях, по одной поломке за раз:
* **Код переписывать нельзя.** В нём адрес текстом не отличить от чего угодно
другого: `/https?:\/\//gi` — это регулярное выражение, и подмена внутри него
ломает разбор всего файла. Приложение падало на «invalid regular expression
flags», пока я правил код наравне с разметкой. Теперь за код отвечает перехват
во время работы — он видит настоящие адреса, а не похожий на них текст.
* **Якорь обязателен.** Telegram кладёт в него подписанные данные запуска, а на
сервер якорь не уходит вовсе. Отбросив его, мы отдавали приложению адрес без
пропуска, и оно останавливалось на «Telegram initData is missing».
* **Пути от корня ведут к нам, а не к приложению.** Вместо скриптов ему
отдавалась наша же разметка, и оно молча не запускалось: браузер ругался на
«MIME type text/html» для модулей.
* **HTTP/1.0 не годится.** Браузер отказывался подгружать куски приложения при
том, что тот же адрес прекрасно отдавался `curl`.
* **Чужой заголовок доступа отвергает наши куски** — сервер разрешал доступ
своему домену, а мы теперь под другим.
* **Происхождение надо помнить только от страницы.** Раньше его перезаписывал
каждый ответ, включая чужие домены со шрифтами, и запасной путь уводил запросы
к ним. Ответы приходят из разных потоков, поэтому промах был не всегда, а
через раз: то файл, то 404.
* **Приложение надо подавать по его собственному пути.** Почти всякое из них
одностраничное, и маршрутизатор разбирает `location.pathname` сам. Под
префиксом `/x/https/хост/bot/…` он видит путь, не начинающийся с ожидаемого
`/bot`, и не рисует **ничего** — белый экран без единой ошибки. Префикс
остался только для чужих доменов.
* **Происхождение надо закрепить за приложением.** Виджет поддержки внутри него
тоже отдаёт HTML, и после его загрузки запросы приложения уходили к нему:
сервер отвечал «wrong verify token», приложение вставало белым экраном.
* **Приложение может склеить свой адрес с нашим хостом.** Wallet строит адрес
API, приклеивая поддомен к текущему хосту: дома выходит
`alectryon.walletbot.me`, а у нас — `alectryon.127.0.0.1:51805`. Такое браузер
не разбирает **вовсе**: `fetch` падает до сети, приложение уходит в
`init_failed` и показывает «Some technical issue». Возвращаем службе её
настоящий дом до разбора адреса.
* **Сообщение может быть адресовано `web.telegram.org`.** Часть приложений не
грузит `telegram-web-app.js` вовсе, а шлёт события на конкретный адрес. Мы не
он — браузер такое молча выбрасывает, приложение не дожидается ответа и через
200 мс пишет «Environment Error · Please open this application directly
through Telegram». Своё окно вправе принимать всё, что ему шлют, поэтому
чужой адрес назначения доставке не мешает; в журнале это видно строкой.
* **Событиям, которые ждут ответа, надо отвечать.** Полноэкранный режим,
«добавить на домашний экран», свои методы бота — молчание вешает приложение
на заставке. «Тюряга» так и висела, пока не ответили на запрос полного экрана.
* **За переадресацией должен идти браузер, а не мы.** Библиотека следует за
`3xx` молча, и страница возвращается по исходному пути: `esim-bot.mobile.ag/`
уводит на `bot.mobile.tg/bot/`, а рамка остаётся на `/` — маршрутизатор
приложения видит не тот путь и не рисует ничего. Теперь переадресацию
отдаём браузеру, а первая же из них закрепляет настоящее происхождение.
* **Страница окна не может жить в корне.** У многих приложений путь — просто
`/`, и окно грузило в рамку само себя: шапка рисовалась дважды, а приложение
не появлялось вовсе. Окно ушло на `/__tgx/`, корень отдан приложению.
* **Вебсокеты надо вернуть на настоящий сервер.** Их адрес уже переписан на
нас, а вебсокетов мы не подаём — приложение на ActionCable просто не
запускалось.
* **`iframe_ready` требует ответа стилем.** Скрипт Telegram здоровается им ещё
до самого приложения, и без ответа его подготовка не заканчивается —
приложение падает по своему таймауту, с виду беспричинно.
**Что проверено, до конца загрузки.** Восемь приложений, каждое своим входом:
| приложение | что показывает |
| --- | --- |
| Wallet | баланс, Transfer / Deposit / Withdraw / Exchange |
| Antarctic Wallet | вкладки, предложение поставить PIN |
| TonMobile eSIM | тарифы, баланс, страны — после починки переадресации |
| Epic Gift | баланс, Rocket, PVP, Play Hub |
| Gorilla Case | кейсы, розыгрыши, задания, турнир |
| Random Games | «Earn Stars», разделы, витрина |
| Telegram Simulator | рулетка, апгрейд, вкладки |
| Тюряга | запускается за полторы минуты: 6 МБ кода и 70 файлов ресурсов |
Если приложение всё же откажется, окно скажет об этом словами и различит два
случая: «загрузилось и отказалось работать» и «не пустило нас в рамку» — по
тому, доступно ли нам её содержимое.
Отдельно — приложение может вовсе запретить себя встраивать; тогда содержимое
нам недоступно, и в окне это написано другими словами. Различаем по тому, видим
ли мы страницу внутри.
## Кнопки, открывающие приложение
**Обычным нажатием они не работают.** У Telethon в `click()` нет ветки для
`KeyboardButtonWebView`: нажатие молча возвращает пустоту, и `tgx` бодро
отвечал `"ok": true, "click_result": null`, ничего при этом не сделав. Со
стороны — «жму кнопку, ничего не открывается».
```bash
tgx inline press-app чат 295606 --text "Open Wallet" --run
tgx inline menu-button "Antarctic Wallet" --run
```
`press-app` берёт из кнопки её адрес и просит у сервера подписанный — тот
самый, который открыл бы настоящий клиент. Обычную кнопку он отвергает и
отсылает к `message-click`, а ненайденную — перечисляет, какие есть.
**Кнопка-меню — отдельная сущность**: не кнопка под сообщением, а свойство
самого бота, та, что слева от поля ввода. Её адрес лежит в **полном профиле
бота**, а не в `bots.getBotMenuButton`: тот отвечает только самому боту, а нам
нужна та кнопка, которую видит человек. Настоящий клиент берёт её оттуда же.
## Истории в полноэкранном клиенте
```
ctrl+o лента историй
```
Слева список — автор, подпись, счётчик просмотров; справа сама картинка, тем же
способом, что и превью в переписке: Kitty, iTerm2, sixel, а где ничего нет —
юникодом.
```
j/k листать o открыть целиком в системе
h скрытые s скрытный просмотр
r обновить escape закрыть
```
**У видео показываем кадр, а не качаем ролик.** Терминал видео не играет, и
тянуть мегабайты ради превью незачем — берём то, которое Telegram и так отдаёт.
А `o` скачивает историю целиком и отдаёт системному просмотрщику: тем же путём,
что и вложения в переписке.
**Скрытный просмотр** (`s`) — возможность Telegram Premium: просмотры не
засчитываются ни за прошедшие минуты, ни за ближайшие. Без Premium сервер
откажет, и отказ показывается как есть.
Автор в ленте приходит числом, имена спрашиваются по одному разу на каждого: у
одного человека в ленте обычно несколько историй подряд.
## Все команды в полноэкранном клиенте
Долгое время окно и командная строка расходились: команд у `tgx` под шесть
сотен, а в окне их было два десятка — те, что повешены на клавиши. Остальное из
окна было недоступно вовсе.
```
ctrl+p палитра команд — поиск по названию и описанию
ctrl+w мини-приложение бота из его чата
```
Список в палитре **не написан руками**, а выведен из дерева разбора командной
строки — оттуда же, откуда его берёт коннектор для агентов. Новая команда
появляется в окне сама. Это не экономия труда: два списка, которые надо помнить
обновлять, однажды разойдутся, и заметить это можно будет только по жалобе.
Поля ввода палитра тоже берёт из разборщика: обязательные сверху,
необязательные ниже, выключатели галочками, а где у аргумента есть список
допустимых значений — показывается именно он.
Соединение при этом одно. Команда, открывшая своё, упёрлась бы в занятый файл
сессии — тот самый «database is locked», — поэтому ей одалживают уже открытый
клиент окна, а закрытие делают пустым действием. Тот же приём, что и в
коннекторе.
`ctrl+w` вынесен отдельно, потому что кнопка-меню — **свойство самого бота**, а
не сообщения: в графическом клиенте она слева от поля ввода, и из чата с ботом
её жмут чаще всего.
Числа палитра приводит сама. Поля ввода отдают строки — иначе им неоткуда
взяться, — а командная строка прогоняет их через `type=int` при разборе,
которого мы не делаем. Без приведения ломается не в палитре, а глубоко внутри:
то `'>' not supported between int and str`, то `struct.error: required argument
is not an integer`. По такому сообщению до причины не добраться, поэтому
приведение стоит до вызова, а негодное значение называет своё поле.
Ещё одна мелочь, стоившая отладки: `ctrl+p` занят встроенной палитрой Textual.
Пока обе были включены, выигрывала её — наша просто не открывалась, и по
нажатию понять это было нельзя.
## Представиться телефоном
Многие мини-приложения рисуют себя по-разному на телефоне и на настольном
клиенте, а иные показывают на настольном заглушку.
```bash
tgx inline run бот --as android --phone
```
`--as` меняет платформу в подписанном адресе, `--phone` подменяет само
устройство: касания и угол экрана. Настоящий телефон в альбомной ориентации
сообщает угол 90, настольный — ноль всегда, и по этому признаку приложения
отличают одно от другого. Подменять надо **до** загрузки: приложение измеряет
себя первым делом, после загрузки уже поздно.
Это то же, что выбор устройства в отладчике браузера, и по умолчанию всё
выключено.
**Про «Тюрягу» я сперва написал неверно, и это стоит запомнить как урок.**
Мне показалось, что игра просит альбомную ориентацию и не запускается. На деле
её блок `#rotate-overlay` лежит в разметке всегда и по умолчанию скрыт:
```js
const надо = (платформа === 'android' || платформа === 'ios' ||
matchMedia('(pointer:coarse)').matches)
&& (innerWidth < innerHeight && innerWidth / innerHeight < 0.75);
```
Оба условия у нас ложны, и блок не показывался ни разу. Я читал его текст
**сырым `textContent`, который берёт и скрытое**, — и пять раз подряд искал
причину там, где её не было. Собственный инструмент `page` при этом отвечал
правильно: он пропускает `display: none` и возвращал пустоту, потому что игра
рисует себя на холсте.
А стояла игра просто потому, что грузилась: 6 МБ кода и семьдесят файлов
ресурсов. Через полторы минуты запустилась сама.
Переключатель платформы всё равно полезен — Gorilla Case в режиме телефона
показывает подсказку, которой на десктопе нет, — но «Тюрягу» он не чинил и
чинить было нечего.
## Навигация в окне приложения
У окна своя шапка: назад, вперёд, обновить, в начало — и адрес, на котором вы
сейчас внутри приложения. Без них из приложения некуда деться: кнопку «назад»
рисует оно само и далеко не всегда, а обычная история браузера внутри есть
всегда.
```bash
tgx window do back # назад по истории приложения
tgx window do forward
tgx window do reload
tgx window do home # к экрану, с которого начали
```
## Агент внутри приложения
Приложение подано с нашего адреса — значит, оно видно и нажимаемо целиком, со
своей навигацией, списками и полями.
```bash
tgx window do page # весь видимый текст приложения
tgx window do elements # что можно нажать и заполнить
tgx window do click --args '{"ref":"e1"}'
tgx window do fill --args '{"ref":"e2","text":"привет"}'
tgx window do scroll --args '{"y":600}'
tgx window do go --args '{"url":"/settings"}'
```
Проверено на настоящем чужом приложении: агент прочитал его текст, нашёл кнопку
`Show Error`, нажал её и прочитал то, что приложение показало в ответ.
Поле заполняется через нативный сеттер значения, а не присваиванием: иначе React
и подобные изменения не замечают.
`page` и `layout` берут текст из `textContent`, а не `innerText`, а видимость
проверяют обходом предков по вычисляемому стилю. Причина одна: `innerText`,
`offsetParent` и координаты отражают **отрисовку**, и у фоновой вкладки они
пусты, а `textContent` и стиль живут в дереве и есть всегда. При этом сам
браузер усыпляет вкладку, когда она уходит в фон, — тогда останавливается и опрос
поручений. Поэтому окно, с которым работает агент, должно быть активной вкладкой;
это его нормальное состояние — агент его открыл и с ним работает.
## Окна, которыми пользуется агент
Человек окно видит и жмёт кнопки. Агент — нет: окно живёт в браузере,
MCP-сервер в другом процессе, и общего между ними ничего. Здесь общее
появляется — по образцу [WebMCP](https://webmcp.dev/), где страница объявляет
свои действия сама.
```bash
tgx window list # какие окна открыты
tgx window read # что в окне и что в нём можно сделать
tgx window do press_main # нажать главную кнопку приложения
tgx window do send_event --args '{"type":"theme_changed","data":{…}}'
```
Устройство трёхслойное, и каждый слой нужен по своей причине:
* **Реестр на диске.** Окно поднимает одна команда, а спрашивает про него другой
процесс — без записи он не узнает даже адреса. Записи чистятся по живости
процесса. Тут легко ошибиться: отказ в праве послать сигнал означает, что
процесс есть, но чужой, — это «жив», а не «мёртв», и спутав, реестр вычистит
рабочее окно.
* **Состояние, которое шлёт страница.** Что происходит внутри мини-приложения,
знает только браузер: приложение чужое, его пиксели нам не видны. Зато оно
разговаривает с нашей страницей, и всё сказанное страница пересылает нам —
надпись на главной кнопке, есть ли «назад», что приложение прислало.
* **Поручения.** Агент просит нажать кнопку, но нажимает её страница. Поручение
кладётся в очередь, страница забирает его, выполняет и приносит **результат**.
Ждём именно результата: «нажал» без него — отчёт о намерении, по которому
нельзя судить, что вышло.
Поручения окно ждёт **на сервере**, а не опросом по таймеру. Браузер душит
таймеры в фоновой вкладке — вплоть до одного срабатывания в минуту, — и окно
отвечало агенту пустотой просто потому, что на него никто не смотрел. Запрос
держится открытым до появления поручения; незавершённый запрос браузер так не
тормозит.
Сервер окна обязан быть многопоточным, и это не мелочь: поручение агента ждёт
ответа страницы, а страница за ответом ходит на тот же сервер. В один поток они
заперли бы друг друга насмерть.
Действия объявляет сама страница — встроенные `snapshot`, `press_main`,
`press_back`, `send_event`, `close` у мини-приложения и `snapshot`, `refresh` у
окна звонка. Своя страница может объявить свои:
```javascript
window.tgx.registerTool('имя', 'что делает', {параметр: {type: 'string'}},
(args) => ({вышло: true}));
```
Наружу окна не смотрят: адрес обязан быть на `127.0.0.1`, иначе `tgx` откажется
с ним разговаривать.
## Кнопка-меню бота
Видов у неё три, а не два, и средний легко пропустить:
```bash
tgx bot menu мойбот --url https://app.example --text "Открыть"
tgx bot menu мойбот --commands # показывать список команд
tgx bot menu мойбот --reset # как по умолчанию
tgx bot menu мойбот --url … --for-user @roman # своя кнопка одному человеку
```
`--commands` — ровно то, чего хотят от бота без приложения. А `--for-user`
делает персональную кнопку: у одного человека своя, у остальных общая, — так
вешают панель администратора.
## Стикеры: библиотека
```bash
tgx stickers featured # что Telegram рекомендует
tgx stickers archived # убранные в архив: не видны, но не удалены
tgx stickers emoji-groups # разделы панели эмодзи
tgx stickers archive Набор # убрать; --restore вернуть; --remove снести
tgx stickers gifs
tgx stickers whose чат 42 # из какого набора стикеры на фотографии
```
## Чужие боты из терминала
Инлайн-бот — это тот, кого вызывают через `@бот запрос` прямо в поле ввода:
@gif, @pic, @vid и тысячи других. Обычно результаты выбирают пальцем из
всплывающего списка; здесь список печатается, а выбор делается номером.
```bash
tgx inline ask gif "cat" # спросить, получить метку и список
tgx inline send me 2059… gif5797… # отправить выбранное
tgx inline start @somebot ref_12345 # «Начать» с параметром из ссылки
tgx inline press чат 4821 "vote_yes" # нажать кнопку и услышать ответ
```
Метка списка живёт недолго. Когда сервер отвечает `INLINE_RESULT_EXPIRED`, это
не поломка, а срок годности — надо спросить заново; мы так и пишем.
`press` умеет кнопки, которые требуют пароль двухфакторной защиты, — так сделаны
кнопки, подтверждающие траты. Пароль спрашивается у терминала, а не берётся из
аргументов, чтобы не осесть в истории оболочки.
Мини-приложения:
```bash
tgx inline attach-list # боты в меню вложений и где они работают
tgx inline attach @bot on --allow-write
tgx inline web-app wallet --open # подписанный адрес, сразу в браузере
```
У Telegram **три разных вызова** под три способа открыть одно и то же
приложение: из бокового меню, из чата и по короткому имени. Какой подойдёт,
зависит от того, как бот его завёл, и снаружи это неизвестно. Поэтому `tgx`
пробует боковое меню, а на отказ переходит к чату: для человека это одно
действие, а не выбор из трёх непонятных.
Полученный адрес **подписан вашей сессией** — по сути это ключ от аккаунта
внутри приложения. Он не пишется в файлы и никуда не уходит, кроме браузера;
предупреждение печатается рядом с адресом каждый раз.
## Блокировки, жалобы, уборка
То, что делают, когда всё пошло не так. Почти всё здесь необратимо, поэтому
идёт через то же подтверждение с кнопками, что и платежи.
```bash
tgx safety blocked # кого вы заблокировали
tgx safety block @stranger --confirm-to @me
tgx safety block @author --stories-only # закрыть только истории
tgx safety unblock @stranger
tgx safety peer-settings @кто-то # что Telegram думает о собеседнике
tgx safety clear-history @кто-то --both-sides --confirm-to @me
tgx safety unpin-all чат --confirm-to @me
tgx safety sponsored off # скрыть рекламу (Premium)
```
`peer-settings` — это не украшение: полоска «заблокировать / пожаловаться» над
новым чатом собирается из ответа сервера, и его можно спросить прямо. Там же
видно, берёт ли человек звёзды за сообщения и из какой страны его номер.
`block-replier` существует отдельно потому, что в комментариях под каналом вы
часто не знаете, кто это, — знаете только сообщение:
```bash
tgx safety block-replier 4821 --delete --spam --confirm-to @me
```
**Жалоба идёт по меню сервера, а не по списку в коде.** Причины у Telegram
меняются, и зашивать их к себе — значит устареть к следующему обновлению.
Поэтому первый вызов показывает то, что прислал сервер, а второй передаёт
выбранное обратно. Варианты приходят байтами, для терминала мы кодируем их в
base64 и разворачиваем ровно в те же байты:
```bash
tgx safety report чат 4821
# → шаг: «За что?»; варианты: Spam (ключ OQ), Copyright (ключ OA), …
tgx safety report чат 4821 --option OQ --confirm-to @me
```
## Остаток по сообщениям
```bash
tgx msgx vote чат 42 2 # проголосовать за второй вариант
tgx msgx voters чат 42 # кто как проголосовал
tgx msgx link-preview "https://…" # что покажется под ссылкой, не отправляя
tgx msgx chat-theme @roman 🎄 # тема отдельного чата
tgx msgx who-reacted чат 42 43 # сводка реакций
tgx msgx saved-dialogs # под-чаты избранного по автору
tgx msgx fact-check чат 42 "проверено" --confirm-to @me
```
Голоса задаются номерами, а сервер ждёт байты вариантов — их нельзя угадать,
`tgx` берёт их из самого опроса. Иначе легко проголосовать не за то.
Ещё оттуда же — реклама, вход по кнопке, календарь вложений, приглашения:
```bash
tgx msgx sponsored канал # какая реклама показывается
tgx msgx url-auth чат 42 0 # что предлагает «войти через Telegram»
tgx msgx search-calendar чат --kind photo # по каким дням есть фото
tgx msgx check-invite "https://t.me/+…" # что за чат, не вступая
tgx msgx scheduled чат 5 6 # отложенные по номерам
tgx msgx personal-channel @кто-то # посты его личного канала
```
Ключ рекламы — байты; в командную строку он идёт шестнадцатеричным и
разворачивается обратно при отметке просмотра. `url-auth` показывает, что
именно сайт узнает о вас, **до** согласия — потому и разведён на два шага.
## Перенос переписки и прочий остаток
```bash
tgx msgx import-target чат # годится ли чат для переноса
tgx msgx import-start чат выгрузка.txt
tgx msgx import-media чат 42 фото.jpg
tgx msgx import-run чат 42
tgx msgx web-page "https://…" # мгновенный просмотр, если он есть
tgx msgx future-owner чат # кому достанется чат, если выйти
tgx msgx suggested-folders # какие папки Telegram предлагает
```
Перенос устроен в четыре шага, и порядок неслучаен: сервер сперва узнаёт формат
по первым строкам, потом проверяет чат, потом заводит перенос, и лишь затем
доливаются вложения и запускается сборка. Пропустить шаг нельзя — откажет, но
объяснит скупо.
Мелочь, стоившая правки: название предлагаемой папки лежит **внутри самой
папки**, а не на обёртке с описанием. Читая с обёртки, получаешь `null` там, где
на самом деле «Unread».
## Настройки и оформление аккаунта
Большая часть здесь в терминале не рисуется — обои, темы, наборы
эмодзи-статусов, — но их можно ставить, и это меняется на всех ваших клиентах.
```bash
tgx account sensitive on # показывать деликатный контент
tgx account username второйадрес off # выключить дополнительный адрес
tgx account main-tab gifts # что показывать первым в профиле
tgx account wallpapers # какие обои доступны
tgx account themes --chat # готовые темы для чатов
tgx account autosave # что скачивается само
tgx account set-autosave users --photos --videos --max-mb 50
tgx account paid-revenue @кто-то # сколько принесли платные сообщения
```
Тонкое место — дополнительные адреса. У аккаунта их бывает несколько, включённые
показываются в профиле ссылками. Список один, а управляют по имени: включить,
выключить, переставить. Перепутать основной и дополнительный легко — тогда
профиль остаётся без адреса вовсе.
## Выгрузка аккаунта
`tgx takeout` — та самая «экспорт данных» из настроек, только из терминала.
Telegram даёт под неё отдельный режим сессии: внутри него ограничения на частоту
мягче, поэтому историю можно выкачивать сотнями сообщений. Пишем на диск по ходу
дела: у людей бывают чаты, которые в память не влезают.
```bash
tgx takeout ~/выгрузка # контакты и список чатов
tgx takeout ~/выгрузка --chat "AllCrew Cockpit" --files # и история со вложениями
tgx takeout-finish # закрыть висящую
```
Две ловушки, обе стоили отладки:
1. **Пауза — это защита, а не поломка.** На первый запрос Telegram может
ответить `TAKEOUT_INIT_DELAY` и прислать в другое ваше устройство
подтверждение. Смысл в том, что если аккаунт угнали, у вас есть время
заметить. Мы так и пишем в ошибке, вместо «ошибка 420».
2. **Пустой признак выгрузки в сессии.** Хранилище кладёт `takeout_id` в
поле-блоб, и незаполненное поле читается как `b''`, а не `None`. Telethon
сравнивает с `None` — и решает, что выгрузка уже идёт; сервер же на `b''`
отвечает ошибкой упаковки, ещё до сети. Получается выгрузка, которую нельзя
ни начать, ни закончить. `tgx` приводит пустое значение к `None` перед
работой, а `takeout-finish` честно говорит, было ли что закрывать.
## Обычные группы и «печатает…»
Обычная группа — не супергруппа: без истории для новичков, без адреса, без
ролей сложнее «администратор». Telegram её не убрал, и часть переписки у людей
живёт именно в таких.
```bash
tgx group new "Ремонт" @roman @kate # в одиночку нельзя, Telegram требует людей
tgx group add чат @kate --history 100 # и показать ей сто прошлых сообщений
tgx group admin чат @roman # права
tgx group rank чат @roman "техдиректор" # звание вместо слова «админ»
tgx group upgrade чат --confirm-to @me # в супергруппу; обратно нельзя
tgx group hand-over чат @roman --confirm-to @me
```
Обычная группа адресуется **голым числом** — единственное место в схеме, где
так: у канала `InputChannel`, у человека `InputUser`, а тут просто `chat_id`.
Перепутать легко, и сервер отвечает невнятным `CHAT_ID_INVALID`, поэтому `tgx`
проверяет сам и отсылает к командам `channel-*`.
Признак набора в графическом клиенте ставится сам, в терминале — нет: терминал
не знает, что вы набираете именно сообщение. Собеседник всё это время видит
тишину.
```bash
tgx group typing @roman # «печатает…»
tgx group typing @roman voice # «записывает голосовое…»
tgx group typing @roman file --progress 60
tgx group typing @roman cancel # передумали
```
## Ветки обсуждений
Пост живёт в канале, а комментарии — в связанной группе, и там у них свой
номер. Без этого перехода отвечать в ветку не по чему.
```bash
tgx group thread канал 6992 # → в обсуждении id 6464, ответов 2
tgx group replies канал 6992
tgx group read-thread канал 6992
```
## Своё имя важнее чужого
Название вашего чата может совпасть с чужим адресом. У меня так вышло с
`PLOMBIR`: это и мой канал, и посторонний `@Plombir`, — и глобальный поиск
отдавал постороннего. Написать не туда так проще простого.
Теперь точное совпадение с названием своего диалога решает спор. Обратный
порядок для `@имени` и адреса в нижнем регистре: их пишут, когда имеют в виду
именно адрес, — `tgx send durov` по-прежнему идёт к `@durov`, а не ищет чат с
таким названием.
## Каналы: остаток управления
То, что в графическом клиенте разбросано по подменю настроек.
```bash
tgx chan search-posts "telegram cli" # поиск по постам всего Telegram
tgx chan search-quota # сколько поисков осталось бесплатно
tgx chan username канал второйадрес off
tgx chan autotranslate канал on # кнопка перевода у читателей
tgx chan main-tab канал gifts # что показывать первым в профиле
tgx chan stickers группа НаборИмя # общий набор для участников
tgx chan paid-messages группа 5 # звёзд за сообщение
tgx chan author канал 6992 # кто из админов написал пост
tgx chan left # каналы, из которых вы вышли
tgx chan delete канал --confirm-to @me
```
Две ловушки, обе в ответах сервера:
* **`GetLeftChannels` работает только внутри выгрузки.** Сервер считает этот
список историей, а не текущим состоянием, и снаружи отвечает
`TAKEOUT_REQUIRED`. `tgx chan left` открывает выгрузку сам и закрывает за
собой: просить об этом человека странно, он спросил всего лишь список.
* **`send-as` отвечает `PEER_ID_INVALID` и когда чата нет, и когда писать от
чужого имени тут просто нельзя.** Выбор появляется у админов канала с
привязанной группой и у анонимных админов; в обычной группе его не бывает. Мы
так и пишем — иначе выглядит как «чат не найден».
## Витрина бота и его настройки
```bash
tgx bot previews-info мойбот # что человек видит до «Начать»
tgx bot preview-add мойбот https://…/demo.mp4
tgx bot popular # какие мини-приложения сейчас смотрят
tgx bot similar мойбот
tgx bot referrals мойбот 150 # партнёрам 15%
tgx bot can-write мойбот
tgx bot free-name новыйбот
```
Доля партнёра считается **в тысячных**, а не в процентах: `150` — это 15%.
Легко перепутать и отдать вдесятеро больше, поэтому `tgx` печатает результат
процентами.
Превью принимают только публичный адрес — локальный файл сначала надо куда-то
выложить. Ссылаться на превью можно лишь тем же медиа, каким его положили:
отдельного идентификатора у них нет.
## Что отдают только внутри выгрузки
Два списка сервер считает персональными данными, а не текущим состоянием, и
снаружи отвечает `TAKEOUT_REQUIRED`: покинутые каналы и то, что он запомнил из
вашей телефонной книги. Просить человека открыть режим экспорта ради списка
странно, поэтому `tgx` открывает выгрузку сам и закрывает за собой.
```bash
tgx chan left # каналы, из которых вы вышли
tgx contacts saved # что Telegram помнит из вашей телефонной книги
tgx contacts forget-saved # заставить забыть
```
## Контакты: остаток
```bash
tgx contacts add-phone "+79261234567:Иван:Петров"
tgx contacts statuses # кто когда был в сети, одним запросом
tgx contacts invite-token # ссылка «добавь меня» без раскрытия номера
tgx contacts nearby 55.75 37.61 # только смотрим
tgx contacts nearby 55.75 37.61 --show-me 60 # и показываем себя на час
tgx contacts forget-top @кто-то # убрать из «часто пишете»
```
`nearby` без `--show-me` **не публикует вас**: раздать своё местоположение
случайно проще, чем кажется, поэтому по умолчанию мы только смотрим.
Ссылку «добавь меня» сервер отдаёт готовой. Я сначала собирал её из токена сам —
и получал нерабочую: в настоящем токене есть вторая половина после двоеточия.
## Истории: остаток
```bash
tgx stories edit чат 42 --caption "новая подпись"
tgx stories views чат 42 43 # просмотры, реакции, пересылки
tgx stories reactions чат 42 # кто как отреагировал
tgx stories album чат 7 # что в альбоме
tgx stories pin-top чат 42 # наверх профиля
tgx stories hide-all on # убрать чужие истории из ленты
tgx stories start-live чат # прямой эфир; смотреть его нечем
```
У правки отсутствие поля значит «не трогать», а не «стереть»: не назвали
подпись — она осталась прежней.
## Мелочи по чатам
Каждая из них по отдельности не тянет на раздел, но искать их приходилось
каждый раз заново.
```bash
tgx message-link "AllCrew Cockpit" 42 # постоянная ссылка на сообщение
tgx message-link чат 42 --album # на весь альбом
tgx common-chats @roman # где вы состоите вместе
tgx who-is чат @roman # кто он здесь: владелец, админ, гость
tgx my-public # ваши занятые публичные адреса
tgx similar durov # похожие каналы
tgx inactive # где давно тихо — кандидаты на уборку
tgx antispam мойчат on # жёсткий антиспам (нужны бусты)
tgx default-ttl 604800 # сообщения в новых чатах живут неделю
```
Даты в этих ответах приходят то числом, то датой — `inactive` отдаёт секунды,
остальное объекты. Приводим к одному виду, иначе в выводе оказывается
`1786542005` там, где ждёшь дату.
## Каналы и группы
Создание и администрирование — из интерфейса, из шелла и у агентов.
- **`n`** — новый чат: канал, супергруппа или группа с темами; название, описание
и публичный адрес сразу в одном окне. Созданный чат тут же открывается.
- **`i`** — управление текущим чатом: название, описание, публичный адрес,
права участников по умолчанию, слоу-мод, а также `ctrl+l` — ссылка-приглашение
(копируется в буфер), `ctrl+d` — привязать группу обсуждения, `ctrl+u` — участники.
Если вы не администратор, панель об этом честно пишет.
```bash
tgx channel-create "Мой канал" --kind channel --about "о чём" --username mychannel
tgx channel-create "Команда" --kind group # или --kind forum, с темами
tgx channel-discussion @mychannel --group @myteam # привязать обсуждение
tgx channel-permissions @myteam --allow send_messages,send_media,embed_links
tgx channel-slowmode @myteam 30
tgx chat-join https://t.me/+abcdef # или @username
tgx chat-leave @oldchat --yes
```
Уже были и остаются: `channel-info`, `channel-edit`, `channel-photo-set`,
`channel-participants`, `channel-admin-set/remove`, `channel-ban/unban`,
`channel-invite-add`, `invite-export/list`, `admin-log`.
Агентам доступны чтение (`chat_details`, `list_members`) и созидательная часть
записи: `create_chat`, `edit_chat`, `create_invite_link`, `set_slowmode`,
`set_permissions`. Назначение админов, баны, удаление канала и выход из чатов
намеренно оставлены только человеку — в CLI и TUI.
## Форматирование и посты
Входящие сообщения рендерятся со всем оформлением: жирный, курсив, подчёркнутый,
зачёркнутый, `код`, блоки кода, цитаты с полосой `▌`, упоминания и хэштеги
цветом. Ссылки — настоящие OSC-8 гиперлинки, в Ghostty/kitty/iTerm2 кликаются
мышью. Спойлеры закрыты плашкой `░░░`, открываются клавишей `s` на выделенном
сообщении.
**`p` — редактор поста**: слева разметка, справа живое превью того, что увидят
подписчики, снизу — файл, отложенная публикация, превью ссылок и тихая отправка.
Разметка (markdown, режим по умолчанию):
```
**жирный** __курсив__ --подчёркнутый-- ~~зачёркнутый~~
`код` ```блок кода``` ||спойлер|| [текст](https://ссылка)
> цитата — в начале строки, соседние строки склеиваются
```
Есть и HTML-режим: `<b> <i> <u> <s> <code> <pre> <blockquote> <tg-spoiler> <a href>`.
Отступление от Telethon: подчёркивание, спойлеры и цитаты его markdown не умеет —
tgx разбирает их сам и отправляет готовыми entity, поэтому то, что показано в
превью, ровно то и уходит.
Смещения считаются в **UTF-16**, как требует Telegram, — жирный текст после эмодзи
не съезжает.
Из CLI и у агентов то же самое:
```bash
tgx format # шпаргалка
tgx format "**жир** ||секрет||" # посмотреть, какие entity получатся
tgx send @channel "**Заголовок**\n\n> цитата" --schedule 2026-08-29T10:00
tgx send @channel "текст" --parse-mode html --no-preview --silent
```
## Вложения
`ctrl+s` — прикрепить файл. В диалоге: поле пути, дерево файлов для выбора мышью
или клавишами, превью с именем и размером (картинка рисуется тут же), четыре
переключателя и подпись.
| переключатель | что делает |
|---|---|
| как файл | без сжатия, оригиналом (`force_document`) |
| голосовое | ogg/opus уйдёт голосовым сообщением |
| кружок | круглое видео (video note) |
| без звука | уведомление без звука |
Тип Telegram определяет сам: картинка уйдёт фото, `.mp4` — видео с возможностью
стриминга, остальное — документом; переключатели это поведение перекрывают.
Для видео обязателен пакет `hachoir` (он в `requirements.txt`): без него Telethon
не может прочитать длительность и размер, подставляет заглушку 1×1, и Telegram
показывает ролик обычным файлом. Если в системе есть `ffmpeg`, tgx ещё и вырежет
кадр-обложку, чтобы у видео было превью.
- **несколько файлов** — перечислите пути через запятую, уйдут альбомом;
- **подпись** подставляется из поля ввода и правится прямо в диалоге;
- **ответ** — если выбран пост через `ctrl+r`, файл уйдёт ответом на него;
- **комментарий** — в канале, где вы не админ, файл уходит комментарием к посту;
- **прогресс** отправки виден в строке над полем ввода.
Из командной строки то же самое:
```bash
tgx send @peer "подпись" --file ~/photo.jpg --file ~/photo2.jpg
tgx send @peer --file ~/voice.ogg --voice
tgx send @peer --file ~/clip.mp4 --video-note --silent
tgx send @channel "мой коммент" --comment-to 12345
```
## Каналы и комментарии
В канале, где вы не админ, обычная отправка запрещена — но комментарии открыты.
Клиент это понимает: в шапке чата пишется `только комментарии`, поле ввода
меняет подпись, и текст из него уходит **комментарием** к посту, а не падает с
ошибкой прав.
- `c` — тред комментариев к посту: список с бабблами и своё поле ввода;
- под постом видно счётчик — `💬 12 комментариев c — открыть`;
- к какому посту комментарий: к выделенному (стрелками или кликом), к тому, на
который вы ответили через `ctrl+r`, иначе — к последнему.
Под капотом это `messages.SendMessage` с `comment_to` (Telethon сам находит
привязанную группу обсуждения) и `iter_messages(reply_to=…)` для чтения треда.
Если у поста комментарии выключены, будет понятное сообщение, а не трейсбек.
## Вложения в ленте
Долго не замечалось: **сообщение без подписи выглядело в ленте пустой строкой.**
Стикер, голосовое, фотография без текста — всё одинаково никак, и читать такую
ленту было невозможно. Теперь вложение всегда описано словами, даже когда его
нельзя показать:
```
[2026-08-28T22:49] Alex: [голосовое 0:03]
[2026-08-29T22:28] Alex: [видео 0:10]
[2026-08-29T22:35] Alex: [стикер 💯]
[2026-08-29T22:41] Роман: [файл отчёт.pdf 2.1 МБ]
[2026-08-29T22:44] Роман: [опрос: Идём?]
```
Подпись всегда важнее описания: если текст есть, показывается он, а вложение
остаётся отдельным полем. Кружок отличается от видео, голосовое от музыки, у
музыки виден исполнитель. Нулевой вес не печатается — это «неизвестно», а не
«0 Б».
## Прочитанное
Чат, в котором вы задержались дольше секунды, отмечается прочитанным — счётчик
непрочитанных гаснет и в TUI, и во всех остальных клиентах Telegram. Пролистывание
списка стрелками чаты не «съедает»: отметка ставится только если вы действительно
остались в чате. Отключается флагом `--no-mark-read`, вручную — `shift+r`.
## Медиа
Фото, видео, гифки и стикеры показываются картинкой прямо в бабле. Канал
рендера выбирается по возможностям терминала — **опрос делается на старте, до
запуска интерфейса** (позже Textual забирает stdin, и ответ терминала уже не
прочитать):
| терминал | канал | что видно |
|---|---|---|
| kitty, Ghostty, WezTerm | kitty graphics protocol | **настоящее изображение** |
| xterm, foot, Windows Terminal, iTerm2 | sixel | **настоящее изображение** |
| Terminal.app, tmux и всё остальное | полублоки `▀` в truecolor | цветной пиксель-арт, 2 пикселя на строку |
Что реально используется — видно в шапке: `250 чатов · медиа: kitty graphics`.
macOS Terminal.app графических протоколов не умеет вообще, там будут только
полублоки; за настоящими картинками — Ghostty, kitty, WezTerm или iTerm2.
Клавиши на выделенном сообщении:
| клавиша | действие |
|---|---|
| `v` | картинка на весь экран (у видео — кадр, крупнее чем в бабле) |
| клик / двойной клик | выделить сообщение · открыть картинку |
| `o` | открыть оригинал в системном просмотрщике |
| `ctrl+d` | скачать вложение в `data/downloads` |
Видео в терминале **не проигрывается** — ни kitty-протокол, ни sixel этого не
умеют: `v` покажет кадр, `o` скачает оригинал и отдаст его системному плееру
(с прогрессом в строке над полем ввода). Файлы кэша и загрузок сохраняются с
правильным расширением — иначе macOS не понимает тип и `open` молча
отказывается работать.
Флаги: `--media auto` (по умолчанию), либо `tgp` / `sixel` / `halfcell` /
`unicode` / `off`, либо `TGX_MEDIA`. Если картинки в терминале выглядят криво,
`--media halfcell` — самый безопасный вариант: обычный цветной текст, который
корректно скроллится.
В ленте качается **миниатюра** (~320 px, десяток килобайт), а не оригинал, и
только для последних 24 медиасообщений открытого чата; полноразмерная версия
подтягивается лениво при `v`. Кэш — `data/media-cache`, удаляется в любой
момент. Голосовые, аудио и документы без миниатюр остаются чипами.
## Анимации
Заставка сделана на [terminaltexteffects](https://github.com/ChrisBuilds/terminaltexteffects)
— 37 эффектов:
```bash
tgx banner --effect matrix # или beams, decrypt, slide, laseretch, rain…
tgx banner --list # все доступные
TGX_EFFECT=blackhole tgx ui # эффект по умолчанию для запуска
```
Внутри интерфейса анимации свои: проявление бабблов, подсветка нового
сообщения, анимация «печатает…», плавное сворачивание панели, тосты.
`TEXTUAL_ANIMATIONS=none` отключает их, `TGX_NO_SPLASH=1` — заставку.
## CLI
Вывод раскрашивается только когда stdout — терминал. В пайпе, под `--plain`,
`NO_COLOR` или `TGX_PLAIN=1` формат остаётся ровно прежним, так что старые
скрипты не ломаются.
```bash
tgx dialogs --limit 30 # таблица
tgx history @channel --limit 50 # лента сообщений
tgx search "релиз" --per-dialog 3
tgx folders # папки Telegram
tgx folder-upsert AI --match-regex '(?i)\b(ai|llm)\b'
tgx send @user "привет" --file ./pic.png
tgx export @channel --output out.jsonl --format jsonl
```
Полный список — `tgx` без аргументов, детали по команде — `tgx <команда> --help`.
## Коннектор для агентов (MCP)
`bin/tgx-mcp` — MCP-сервер поверх того же аккаунта. Агенту доступно всё, что
доступно вам: **647 инструментов**.
```bash
claude mcp add tgx -- ~/telegram-cli-tools/bin/tgx-mcp
claude mcp add tgx --env TGX_MCP_READ_ONLY=1 -- ~/telegram-cli-tools/bin/tgx-mcp
~/telegram-cli-tools/bin/tgx-mcp --check # проверить руками
```
Набор состоит из двух частей.
**Именные инструменты** (около полусотни) написаны под частые задачи: у них
подробные описания, они делят одно соединение и отвечают быстрее.
**`cli_*` — весь командный интерфейс целиком.** Они не написаны, а выведены из
самого argparse: дерево команд обходится при старте, и каждая листовая команда
становится инструментом со своей сигнатурой, типами и подсказками из `help=`.
Новая команда в CLI появляется в MCP сама — дописывать в сервере нечего.
```
tgx profile photo клип.mp4 --start 2.5 → cli_profile_photo(source, start=…)
tgx business connect @bot --replace → cli_business_connect(bot, replace=…)
```
Пропускаются только команды, которым нужен человек за клавиатурой: вход по коду
из SMS и запуск TUI — в переписке с агентом они бы просто зависли. Если одно имя
дают вложенная команда и старый плоский псевдоним, побеждает вложенная.
Ограничений по составу нет: профиль, бизнес-режим, токены ботов, удаление — всё
на месте. Опасное не запрещено, а помечено: инструменты удаления и выхода из
чата несут пометку `destructive`, а инструкции сервера требуют показать
пользователю, что именно сейчас произойдёт, и дождаться согласия.
`TGX_MCP_READ_ONLY=1` вешает замок, если он всё-таки нужен, а `TGX_MCP_PEERS`
сужает список чатов.
| инструмент | режим | что делает |
|---|---|---|
| `me` | чтение | аккаунт, и включена ли запись |
| `list_chats` | чтение | список чатов с фильтрами по типу, тексту, непрочитанным |
| `read_chat` | чтение | сообщения чата, с пагинацией и поиском |
| `search_messages` | чтение | поиск в чате или по всем чатам |
| `read_comments` | чтение | тред комментариев под постом |
| `list_folders` | чтение | папки со всеми правилами |
| `chat_info` | чтение | тип чата и можно ли в нём писать |
| `format_syntax` | чтение | шпаргалка по разметке для составления постов |
| `search_messages` | чтение | поиск с фильтрами по типу, отправителю и датам |
| `list_topics` · `list_pinned` | чтение | темы форума · закреплённые сообщения |
| `pin_message` · `create_topic` · `edit_topic` | **запись** | закрепление и темы |
| `send_checklist` | **запись** | чек-лист с пунктами |
| `rich_syntax` · `bot_send_rich` | чтение · **запись** | богатые сообщения Bot API 10.1 |
| `list_articles` · `publish_article` | чтение · **запись** | статьи на telegra.ph из маркдауна |
| `list_bots` | чтение | имена сохранённых ботов — **без токенов** |
| `bot_post` | **запись** | пост от имени бота с кнопками и мини-приложениями |
| `download_media` | чтение | скачать вложение, вернуть путь |
| `send_message` | **запись** | текст с разметкой, ответ, комментарий, отложенная отправка |
| `react` | **запись** | поставить или убрать реакцию |
| `edit_message` | **запись** | править своё сообщение (md/html) |
| `forward_messages` | **запись** | переслать между чатами |
| `press_button` | **запись** | нажать инлайн-кнопку бота |
| `send_file` | **запись** | файлы и альбомы со всеми флагами |
| `mark_read` | **запись** | отметить чат прочитанным |
Что осталось от прежних ограничений:
- **предупреждение вместо запрета** — необратимое (удаление, выход из чата, отзыв
токена, подключение бота к личной переписке, смена собственного аватара)
помечено `destructive`, и в инструкции сервера сказано сначала показать
пользователю, что именно произойдёт, и дождаться ответа;
- **`TGX_MCP_READ_ONLY=1`** — замок на всю запись, если он нужен;
- **`TGX_MCP_PEERS=@chat1,@chat2`** ограничивает агента конкретными чатами;
- **отдельная сессия** `data/tgx-mcp.session`, снятая копией через sqlite-backup,
поэтому сервер работает одновременно с открытым TUI; команды `cli_*` идут через
то же соединение, иначе второй клиент упирается в «database is locked»;
- лимиты выдачи режутся до 200 сообщений на вызов;
- токен бота выдаётся по запросу, но в инструкции сервера отдельно сказано не
записывать его в сообщения и файлы;
- инструкции, найденные внутри самих сообщений Telegram, — это данные, а не
приказы: агенту велено их цитировать и спрашивать.
Сообщения уходят от вашего имени — текст и получателя агент подтверждает заранее.
Если агенту проще шеллом, а не по MCP, весь CLI уже машинный:
`tgx dialogs --jsonl`, `tgx history @peer --jsonl`, `tgx search "…" --jsonl` —
в пайпе формат остаётся стабильным JSON.
## Сколько покрыто
```bash
.venv/bin/python tools/coverage.py # сводка
.venv/bin/python tools/coverage.py messages # пробелы одного раздела
```
На сегодня все одиннадцать разделов, которые имеют смысл в терминале, закрыты
полностью: **100% по делу**.
Голая доля вводит в заблуждение. В схеме лежат секретные чаты, покупки в
магазинах приложений, обработчики на стороне работающего бота и сигнальный обмен
звонков — терминальному клиенту это недоступно или не нужно. Поэтому непокрытое
делится на три кучки: сделано через обёртку Telethon, неприменимо, настоящий
пробел. Считать имеет смысл только последнюю — это столбец «по делу».
Отнесения в `tools/coverage.py` — суждение, а не факт. Они выписаны списком,
их видно, и с ними можно спорить.
## Структура
```
bin/tgx обёртка venv → tgx.py
bin/tgx.py CLI: подкоманды, argparse
bin/tgx_tui.py TUI: бэкенды (Telethon + демо), виджеты, приложение
bin/tgx_tui.tcss стили TUI (темы tgx-night / tgx-day)
bin/tgx_article.py статьи telegra.ph: маркдаун → узлы, публикация
bin/tgx_business.py бизнес-режим: бот-секретарь, часы, автоответы, ссылки
bin/tgx_forum.py форумы и темы целиком: правила сервера в одном месте
bin/tgx_guard.py именные одноразовые приглашения и сверка вошедших
bin/tgx_poll.py опросы и викторины, включая новинки Bot API 10.0
bin/tgx_pay.py звёзды, TON и счета; оплата — за воротами подтверждения
bin/tgx_confirm.py человек в контуре: карточка с кнопками и ожидание решения
bin/tgx_contacts.py адресная книга, чёрный список, поиск людей
bin/tgx_stories.py истории: лента, публикация, просмотры, альбомы
bin/tgx_folders.py общие папки: ссылки на набор чатов и их обновления
bin/tgx_stickers.py свои наборы стикеров
bin/tgx_stats.py статистика каналов, групп, постов и историй
bin/tgx_pending.py черновики, отложенные, заготовки, избранное
bin/tgx_security.py сессии, приватность, сроки
bin/tgx_calls.py групповые звонки: управление из терминала
bin/tgx_callweb.py живая страница участников звонка
bin/tgx_transcribe.py расшифровка голосовых: ожидание готового текста
bin/tgx_profile.py оформление: аватары всех форматов, цвета, статус, дата
bin/tgx_banner.py запись терминальной заставки в видео для аватара
bin/tgx_ai.py руками Telegram: правка, пересказ, перевод, тоны письма
bin/tgx_takeout.py выгрузка аккаунта: контакты, чаты, история, вложения
bin/tgx_chatx.py мелочи по чатам: ссылки, общие группы, антиспам, сроки
bin/tgx_triage.py что ждёт внимания: упоминания, реакции, прочтения, счётчики
bin/tgx_notify.py уведомления и тишина, заявки на вступление, набор реакций
bin/tgx_safety.py блокировки, жалобы по меню сервера, уборка переписки
bin/tgx_groups.py обычные группы, ветки обсуждений, признак набора
bin/tgx_chanadmin.py остаток управления каналами: адреса, вид, уборка
bin/tgx_webapp.py окно вокруг мини-приложения: протокол хозяина
bin/tgx_windows.py реестр окон и мостик к ним для агентов
bin/tgx_palette.py все команды CLI внутри полноэкранного клиента
bin/tgx_proxy.py мини-приложение через свой адрес: видимость целиком
bin/tgx_account.py настройки и оформление аккаунта: адреса, обои, темы
bin/tgx_msgextra.py остаток по сообщениям: голоса, факт-чек, предпросмотр
bin/tgx_inline.py чужие боты: инлайн-запросы, кнопки, мини-приложения
bin/tgx_autotools.py вывод инструментов MCP из дерева команд CLI
bin/tgx_net.py общий HTTPS: сертификаты и понятные ошибки сети
bin/tgx_rich.py богатые сообщения: разметка, лимиты, отправка и отрисовка
bin/tgx_bots.py боты: реестр токенов, диалог с BotFather, клиент бота
bin/tgx_format.py разметка: разбор в entity, отрисовка, шпаргалка
bin/tgx_media.py превью картинок: выбор протокола и размера в ячейках
bin/tgx_render.py общий слой вывода: палитра, таблицы, JSON, лента сообщений
bin/tgx_splash.py анимированная заставка
bin/tgx-mcp обёртка venv → tgx_mcp.py
tools/coverage.py что из API покрыто и почему остальное — нет
bin/tgx_mcp.py MCP-коннектор для агентов (чтение по умолчанию)
bin/tgx_smoke.py headless-самопроверка TUI + SVG-скриншоты
```
Проверка после правок:
```bash
.venv/bin/python bin/tgx_smoke.py /tmp/tgx-shots
```
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues