Skip to content
Back to skills

Telegram Send

ASecurity

Отправить сообщение в Telegram-чат от имени пользователя (личка или группа): "напиши в телеграм", "отправь в чат X", "скинь в группу". НЕ для чтения истории - скилл `telegram-snapshot`.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
developmentpythonbashsqlapidatabase

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 3, 2026

npx -y skills add dewil/claude-toolkit --skill telegram-send --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Telegram Send?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Telegram Send
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/dewil-telegram-send/badge)](https://www.skillsdirectory.com/skills/dewil-telegram-send)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: telegram-send
description: Отправить сообщение в Telegram-чат от имени пользователя (личка или группа): "напиши в телеграм", "отправь в чат X", "скинь в группу". НЕ для чтения истории - скилл `telegram-snapshot`.
---

# telegram-send

Скилл для отправки сообщения в Telegram-чат проекта **от имени пользователя**. Личка и группа отправляются одинаково. Скрипт-эталон - `scripts/telegram-send.py` (top-level папка `scripts/` в корне проекта). Авторизация общая с `telegram-snapshot` (`~/.config/telegram-snapshot/auth.json` + `.session`), адресаты берутся из `.telegram-snapshot.json`.

## Когда применять

- Пользователь просит написать/отправить/скинуть сообщение в один из чатов проекта от своего имени.
- Нужно ответить в конкретную форумную тему или реплаем на сообщение.

## Когда НЕ применять

- Чтение/анализ истории чатов - это `telegram-snapshot` (`result.json` уже на диске, читай напрямую).
- Адресата нет в `.telegram-snapshot.json` - сначала добавить его туда (см. ниже), отдельного механизма отправки "куда угодно" у скрипта нет.

## Обязательный гейт отправки

Отправка в Telegram **необратима и видна другому человеку**. Поэтому всегда:

1. **Dry-run.** Запусти `scripts/telegram-send.py` БЕЗ `--send`. Скрипт зарезолвит адресата и напечатает заголовок чата, id, тему/реплай и полный текст - но ничего не отправит.
2. **Покажи пользователю** результат dry-run: в какой именно чат (по заголовку, а не только label) и **текст дословно**, как он уйдет.
3. **Дождись явного "ок".** Без подтверждения - не отправлять. Это конкретизация канон-принципа "audit -> ок -> action" для внешнего необратимого действия.
4. **Только потом** запусти ту же команду с `--send`.

Никогда не запускай `--send` без показанного пользователю dry-run и явного согласия. Молчаливое согласие ("ну напиши что-нибудь") - не основание отправлять не показав текст.

**Гейт темпа - второй предохранитель, уже про серию.** Отправка слишком рано после предыдущей в тот же чат отклоняется с кодом 3: "нужно N сек, осталось M". Это **сработавшее правило, а не поломка** (`rules/outbound-timing.md`, "Паузы внутри своей серии"): подожди названное время и повтори ту же команду. Dry-run показывает состояние гейта заранее строкой "темп: рано - ...", так что упереться в него на реальной отправке необязательно.

Обход - `--no-pace-check`, и он уместен ровно в двух случаях: на том конце не человек (бот, свой канал, служебный чат) или есть срочность по существу. "Мне некогда ждать" в этот список не входит - залп сообщений выдает автоматику вернее, чем содержание текста.

## Как составлять текст

Голос - по правилу `rules/agent-as-user-voice.md`: держать фактический речевой регистр пользователя, снятый из истории переписки в этом чате, а не причесанный ассистентский. Это его сообщение, не твое, и переполировка выдает бота.

Время отправки - по `rules/outbound-timing.md`: у сообщения видна метка времени, и ночная отправка выдает автомат не хуже стиля. Показывая текст на подтверждение (см. "Жесткие правила"), скажи и когда отправляешь - сейчас или в рабочее окно получателя.

Специфика отправки:

- Не добавляй подпись "отправлено через..." и подобное.
- Если пользователь продиктовал текст дословно - отправляй дословно, не переписывай.
- Перед отправкой показываешь ровно тот текст, что уйдет (никаких "примерно так").

## CLI

```bash
# превью (ничего не отправляет):
python3 scripts/telegram-send.py --to "с командой" --text "Привет, созвон в 15:00"

# реальная отправка (после "ок"):
python3 scripts/telegram-send.py --to "с командой" --text "Привет, созвон в 15:00" --send

# многострочный текст - через stdin:
python3 scripts/telegram-send.py --to "с командой" --send <<'EOF'
Первая строка.
Вторая строка.
EOF

# в форумную тему:
python3 scripts/telegram-send.py --to "бот" --topic 30127 --text "..." --send
# ответом на сообщение (ответ попадет в тему этого сообщения):
python3 scripts/telegram-send.py --to "бот" --reply-to 4821 --text "..." --send
# файл-вложение (текст уходит подписью к файлу):
python3 scripts/telegram-send.py --to "с командой" --file "путь/к/архиву.zip" --text "..." --send
```

Флаги:

- `--to <label>` (обязательный) - label чата из `.telegram-snapshot.json -> chats`. Если label нет - скрипт печатает список доступных и выходит.
- `--text <str>` - текст. Если опущен, читается из stdin (удобно для многострочного).
- `--file <путь>` - файл-вложение (отправляется документом, `force_document`); текст сообщения становится подписью к файлу. Если ни `--text`, ни stdin не заданы, файл уйдет без подписи. Пустой `--file ""` или путь к несуществующему файлу в обоих скриптах отправки - отказ с кодом 2 до создания клиента, и в dry-run, и с `--send`. Dry-run показывает полный резолвленный путь и точный размер в байтах - по одному имени не проверить, тот ли это файл.
- `--send` - реально отправить. Без него - dry-run.
- `--topic <int>` - id корня форумной темы. Если не задан, но у label в конфиге расширенная запись с `topic_id` - берется он (так сообщение в форум-чат уходит в нужную тему, а не в General).
- `--reply-to <int>` - id сообщения, на которое отвечаем.
- `--silent` - отправить без звука: уведомление придет беззвучным. Есть у обоих скриптов.
- `--schedule <ISO-время>` - отложить до этого момента (см. "Когда нужен `--schedule`" ниже). Запас до срока - не меньше двух минут: меньший скрипт отклоняет сам, до сети, потому что Telegram при запасе меньше десяти секунд отправляет немедленно, а подключение и загрузка файла запас съедают; секунды в превью печатаются. Есть у обоих скриптов.
- `--schedule-tz <IANA>` - пояс получателя по имени (`Europe/Paris`) для `--schedule`; без `--schedule` - ошибка (см. "Когда нужен `--schedule`" ниже). Есть у обоих скриптов.
- `--remind` - только `telegram-send-one.py` и только адрес `me`: отложить на ~3 минуты, чтобы Telegram показал уведомление (см. "Сообщение себе" ниже). Сам снимает гейты темпа и ровной минуты; с `--silent`, `--schedule`, `--schedule-tz` и другим адресатом - отказ кодом 2.
- `--exact-minute` - разрешить ровную минуту (`:00`/`:15`/`:30`/`:45`, независимо от секунд) в `--schedule`; без него такая минута отклоняется кодом 6 (см. "Когда нужен `--schedule`" ниже). Есть у обоих скриптов.

`--reply-to` отвечает на сообщение и кладет ответ в тему этого сообщения автоматически; `--topic` нужен для постинга нового сообщения в тему без реплая. Если задать оба, приоритет у `--reply-to` (тема берется из отвечаемого сообщения, `--topic` игнорируется) - high-level API Telethon не умеет одновременно reply на сообщение и явный topic.

**Когда нужен `--silent`.** Это средний путь между "отправить сейчас" и "отложить до утра" из `rules/outbound-timing.md`: сообщение готово, срочности нет, но и ждать утра незачем - асинхронный тред, чат сообщества, получатель прочитает когда откроет. Ночной звонок при этом никого не будит. Важно понимать границу: беззвучность снимает уведомление, но **не** ночную метку времени - она остается видимой. Поэтому там, где сам таймстемп читается как сигнал (отклик на вакансию, письмо рекрутеру, первое сообщение незнакомому человеку), флаг правило не заменяет: ждем рабочее окно. Dry-run печатает `звук: да/нет` - проверяйте это перед `--send` вместе с адресатом.

Подпись к файлу (`--file`) ограничена Telegram ~1024 символами: длинный текст к вложению не влезет, и `--send` упадет с `MediaCaptionTooLongError`, ничего не отправив. Для объемного текста - отправить файл с короткой подписью, затем текст отдельным сообщением. Один файл за вызов; альбомы/несколько вложений - вне скоупа.

**Когда нужен `--schedule`.** Это настоящее "отложить до утра" из `rules/outbound-timing.md`, а не ручная эмуляция ("подготовил, жду, потом нажал отправить"): время передается `--schedule <ISO-время>`, и сообщение ложится в очередь на серверах Telegram. Уходит оно само, в назначенный момент, без запущенного скрипта и открытой сессии. До срока сообщение видно в "Отложенных" у отправителя, и его можно оттуда отозвать; получатель видит время доставки, а не время постановки в очередь.

Оффсет задавай по поясу получателя, когда он известен (`rules/outbound-timing.md`, "Норма") - `2026-09-22T09:30+03:00`; без оффсета скрипт берет зону машины, и это фолбэк на случай, когда пояс получателя не восстановить, а не выбор по умолчанию. При переходе на летнее время локальное время без оффсета может оказаться неоднозначным (два момента подряд) или несуществующим (такого момента в этом дне нет) - скрипт такое отклоняет и просит задать оффсет явно, вместо того чтобы подставлять его сам.

**Получатель в поясе с переходом часов - всегда `--schedule-tz`, а не число.** Числовой оффсет верен только на ту дату, на которую его считали: команда с `+02:00`, повторенная после перевода часов, молча доставит сообщение на час раньше. С `--schedule-tz Europe/Paris` время без оффсета читается как настенное время в этом поясе, а оффсет берется из базы поясов на дату доставки (`2026-11-02T09:06` станет `+01:00`, `2026-10-20T09:06` - `+02:00`). Пропущенный или повторенный час в этом поясе отклоняется так же, как для зоны машины. Явный оффсет вместе с флагом допустим, но обязан совпасть с поясом на этот момент, иначе отказ с обеими цифрами. Неизвестное имя пояса - отказ до сети. Dry-run и строка результата печатают время дважды: у получателя и в зоне машины - `отложено до: 2026-11-02 09:06:00 +01:00 Europe/Paris (в зоне машины: 2026-11-02 11:06:00 +03:00)`. Без флага вывод прежний.

Dry-run печатает получившееся время с оффсетом и секундами (`отложено до: 2026-09-22 09:30:00 +03:00`) - проверь его до `--send`, если оффсет не задан явно: ошибка зоны машины иначе всплывет только в момент доставки. Прошлое время, время дальше года вперед (лимит отложенной отправки в Telegram) и запас до срока меньше двух минут скрипт отклоняет сразу, до сети.

**Ровная минута отклоняется отдельно, кодом 6.** `:00`, `:15`, `:30`, `:45` независимо от секунд читаются как подпись автомата, а не человека (`rules/outbound-timing.md`, "Ровная минута") - скрипт отказывает до сети, и в dry-run, и с `--send`, с подсказкой соседней некруглой минуты. `09:00:30` остается круглой минутой: секунды не служат обходом. Минуту назвал сам пользователь явно ("ровно в 9:00", "к началу созвона") - обход `--exact-minute`; время доставки сохраняет исходные секунды, а dry-run печатает пометку `ровная минута: разрешена явно`.

**Сообщение себе (`me`, Избранное) уведомления не дает.** Telegram считает его исходящим: оно ложится в Избранное молча, с `--silent` и без него. Отчет по расписанию, который человек должен заметить, шлется отложенным - `telegram-send-one.py me --remind --send`: отложенное себе Telegram доставляет как напоминание, со звуком на всех устройствах (проверено 29.09.2026). Ночью, когда будить не надо, - прямое сообщение без `--remind`.

Для Избранного у отложенного есть второе отличие: на близком сроке Telegram не показывает его в очереди отложенных (на сроке в сутки - показывает). Поэтому для себя "не найдено в очереди" - не сбой: скрипт отвечает кодом 0 и пишет, что сверка для Избранного на близком сроке невозможна. Для любого другого чата "не найдено" по-прежнему код 4.

## Адресаты только из конфига

Скрипт шлет строго в чаты, перечисленные в `.telegram-snapshot.json`. Это намеренное ограничение - предсказуемость и защита от отправки не туда.

Чтобы написать в новый чат, сначала добавь его label в `.telegram-snapshot.json -> chats` (формат записи и как узнать `chat_id` - см. скилл `telegram-snapshot`, шаг 6). После этого `--to <новый label>` заработает.

### Single-chat-by-id: адресат вне конфига

Для сценариев, где адресата в общий конфиг класть нельзя (разовый чат, клиент трека поддержки - его чат не должен попадать в ежедневные pull/дельты), есть драйвер `scripts/telegram-send-one.py` - явное исключение из правила выше:

```bash
# dry-run (по умолчанию), id прямо в командной строке:
python3 scripts/telegram-send-one.py <chat_id> [expected_username] --text "..."
# реальная отправка (после "ок"):
python3 scripts/telegram-send-one.py <chat_id> [expected_username] --text "..." --send
```

Тот же гейт dry-run/`--send`, что у основного скрипта. Вместо защиты "только из конфига" - опциональная сверка `expected_username`: если username чата не совпал с ожидаемым или отсутствует, отправка прерывается с кодом 2 (страховка от "не туда"). Явно переданный пустой `expected_username` - ошибка ввода с кодом 2 до создания клиента. Флаг `--html` включает HTML-форматирование (жирный/код/ссылки); HTML, а не MarkdownV2 - тот требует экранировать точки/дефисы/скобки, на русском тексте это грабли. Симметричный pull одного чата по id - `scripts/telegram-pull-one.py` (см. скилл `telegram-snapshot`).

Вложение - как у config-версии, флаг `--file`:

```bash
python3 scripts/telegram-send-one.py <chat_id> <expected_username> \
  --file "путь/к/файлу.pdf" --text "короткая подпись" --send
```

Для чата вне `.telegram-snapshot.json` это единственный штатный способ отправить файл - править общий конфиг ради одной отправки не нужно. Текст уходит подписью к файлу (лимит Telegram - 1024 символа: длиннее - двумя сообщениями, сначала текст, потом файл с короткой подписью; скрипт при превышении останавливается сам). Файл без подписи легален; dry-run показывает полный путь и размер вложения, существование файла проверяется до захвата общей сессии. Сверка `expected_username` работает и здесь.

## Несколько аккаунтов

Отправитель определяется чатом: у записи чата в `.telegram-snapshot.json` может быть поле `account` (без него - `default`), и сообщение уходит от того аккаунта, которому чат принадлежит. Dry-run печатает строку `от аккаунта: <имя>` - при нескольких номерах ошибиться отправителем так же легко, как чатом, поэтому проверяй ее наравне с заголовком чата.

Для драйвера вне конфига аккаунт задается флагом: `telegram-send-one.py <id> --account cv --text "..."`.

Настройка аккаунтов (секция `accounts` в `auth.json`, наследование `api_id`/`proxy`, первый логин) описана в скилле `telegram-snapshot`, раздел "Несколько аккаунтов". Важно: **залогинить новый аккаунт можно только через snapshot** - этот скрипт неинтерактивен и на неавторизованной сессии просто откажет с сообщением про `.session`.

Если чат не резолвится ("не удалось найти чат") - проверь поле `account`: чат виден только своей сессии.

## Авторизация

Та же, что у `telegram-snapshot`: `~/.config/telegram-snapshot/auth.json` (api_id, api_hash, session_name) + общая `.session`. Отдельной настройки для отправки не нужно - если snapshot уже работает на устройстве, работает и send. При проблемах с авторизацией/сессией (`AuthKeyUnregisteredError`, PeerUser-ошибки, нет auth.json) - см. скилл `telegram-snapshot`.

Сессия общая, поэтому отправка может упасть с `sqlite3.OperationalError: database is locked`, если параллельно работает snapshot другого проекта. Подключение скрипт повторяет сам; если ошибка все же дошла - правило поведения в скилле `telegram-snapshot`, раздел "Общая сессия: `database is locked`": ждать и повторять, чужой процесс не убивать, `.session` не удалять.

**Важно про дубли при повторе.** Слепой повтор отправки недопустим - смотри, где именно упало. Три случая, а не два:

- **упало на подключении** (в трейсбеке `connect_with_retry`, ниже - кадр telethon `sessions/sqlite.py`, `_update_session_table`) - до отправки, сообщение НЕ ушло, повтор безопасен;
- **напечатано `OK: отправлено ...`** - сообщение ушло, повторять нельзя. Ошибка после этой строки относится к чему-то после отправки (например, закрытию клиента), а не к самой отправке;
- **упало между этим, и в трейсбеке есть кадр `send_message` / `send_file`** - **статус неизвестен, и это самый опасный случай**. Telethon пишет сессию уже ПОСЛЕ того, как Telegram принял сообщение (кадр `client/users.py` под кадром отправки), поэтому `database is locked` здесь означает "скорее всего доставлено, но скрипт этого не подтвердил". Не повторять: сначала убедиться, ушло ли (`telegram-pull-one.py` по этому чату, свежий snapshot или глазами пользователя), и только потом решать. Не уверен - спросить пользователя, а не отправлять второй раз.

Кадр `client/users.py` **без** кадра отправки к этому случаю не относится: через него идет любой запрос, включая прогрев диалогов и резолв чата до отправки. Нет в трейсбеке `send_message`/`send_file` - отправка не начиналась, повтор безопасен.

**У `--schedule` дубли проверяются иначе.** Обрыв соединения или отдельный ненулевой код "неопределенно" (постановка в очередь не подтверждена) - смотри "Отложенные" у отправителя, а не историю чата: до срока доставки отложенное сообщение в истории не появляется, и ее пустота ничего не доказывает. Сообщение в "Отложенных" нашлось - повторный вызов поставит второе, дедупликации на постановке в очередь нет. Код "неопределенно" значит то же, что и в случае выше: проверь руками, не повторяй автоматически.

## Жесткие правила

1. **Без dry-run и явного "ок" - не отправлять.** Всегда показать пользователю заголовок чата и текст дословно перед `--send`.
2. **Только чаты из `.telegram-snapshot.json`** - либо `telegram-send-one.py` со сверкой `expected_username` для адресата вне конфига (см. "Single-chat-by-id"). Нет label и не single-chat-сценарий - не отправлять, предложить добавить в конфиг.
3. **Текст - от имени пользователя, дословно по согласованию.** Не переписывать продиктованное, не дописывать от себя.
4. **Auth и .session - не в проектной папке.** Только `~/.config/telegram-snapshot/` (см. `telegram-snapshot`).

## Связанные файлы

- `scripts/telegram-send.py` в корне проекта - эталонный скрипт отправки (dry-run по умолчанию, `--send` для реальной отправки).
- `scripts/telegram-send-one.py` - драйвер single-chat-by-id: отправка в один чат по id, минуя конфиг (тот же гейт, сверка username, `--html`, `--file`, `--silent`). Переиспользует логику telegram-send.py через импорт.
- `.telegram-snapshot.json` в корне проекта - справочник адресатов `{label: chat_id}` (общий со snapshot/deltas).
- `~/.config/telegram-snapshot/auth.json` + `.session` - общая авторизация на устройство.
- Скилл `telegram-snapshot` - первичная настройка авторизации и подключение чатов в конфиг.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.