Боты в Скрепе: BotFather, E2E-диалоги и запуск gateway
Этот туториал поможет создать бота через @botfather, безопасно сохранить его реквизиты, запустить skrepa-bot-gateway и ответить на первое сообщение. Исходники Скрепы, Rust toolchain и protobuf-файлы для этого сценария не требуются.
Справочник методов, схем, ошибок и примеров запросов находится в интерактивной документации Bot API.
Что понадобится
- учётная запись Скрепы и веб-клиент, чтобы поговорить с BotFather;
curlиjqдля команд из туториала;- Mac с Apple Silicon либо Docker с поддержкой Linux amd64;
- отдельный каталог для конфигурации и постоянного состояния бота.
Все команды ниже можно выполнить в пустом каталоге.
Как устроен gateway
skrepa-bot-gateway — локальный адаптер между кодом вашего бота и защищённым транспортом Скрепы. Приложение бота работает с привычным Telegram-compatible HTTP API. Gateway подключается к серверу по gRPC, регистрируется как отдельное устройство бота и берёт на себя E2E-криптографию.
flowchart LR
U["Пользователь<br/>в клиенте Скрепы"] <-->|"E2E-DM"| S["Транспорт Скрепы"]
S <-->|"gRPC:<br/>device inbox и ciphertext"| G["skrepa-bot-gateway"]
G <-->|"Telegram-compatible HTTP:<br/>polling, webhook, text, media и callbacks"| B["Код вашего бота"]
G --- D[("state_dir:<br/>identity, ratchets, durable queue,<br/>file_id и E2E-ключи файлов")]
Путь входящего сообщения выглядит так:
- gateway получает зашифрованное сообщение из device inbox;
- расшифровывает его в сессии Double Ratchet;
- сохраняет update в локальную durable queue;
- gateway отдаёт update через
getUpdatesлибо отправляет его на webhook.
Для исходящего сообщения код вызывает sendMessage или один из media-методов. Gateway находит активные устройства адресата, создаёт отдельный ciphertext для каждого устройства и повторяет отправку до подтверждения сервера. Файлы шифруются отдельным случайным GOST-ключом; ciphertext хранится в file stack Скрепы, а ключ доступен адресатам только внутри E2E-envelope. Код бота работает с plaintext на локальной HTTP-границе.
Конфигурация gateway связывает четыре части:
bot_platform_endpoint— адрес сервера Скрепы;- bot token — учётные данные для подключения;
- identity recovery secret — ключ восстановления постоянной identity бота;
state_dir— локальное состояние конкретного runtime: device identity, prekeys, ratchet-сессии, неподтверждённые updates и исходящие сообщения.
Каталог state_dir должен переживать перезапуски. Размещайте его на постоянном диске, выдавайте доступ только процессу gateway и не подключайте один каталог одновременно к нескольким экземплярам. Bot token можно заменить через BotFather; identity recovery secret при ротации token остаётся прежним.
Gateway слушает два локальных адреса. Порт 8081 предоставляет обычный Bot API: получение updates, webhook, текст, файлы, replies, редактирование и inline-кнопки. Порт 8082 предназначен для дополнительных возможностей платформы. Для первого echo-бота достаточно порта 8081, однако healthcheck удобно выполнять на обоих.
Команды BotFather
| Команда | Результат |
|---|---|
/start или /help | справка |
/newbot | пошаговое создание бота |
/mybots | список собственных ботов и их статусы |
/token @username | отзыв прежнего credential и выпуск нового token |
/disable @username | отключение бота и отзыв всех активных credentials |
/cancel | сброс незавершённого сценария |
Туториал: создаём echo_bot
1. Откройте диалог
В клиенте Скрепы найдите @botfather и откройте личный диалог. Поиск не зависит от регистра; начальный символ @ можно опустить. В профиле должна отображаться отметка бота.
Если @botfather отсутствует в результатах или долго не отвечает, обновите клиент и повторите попытку. Сохраняющаяся ошибка означает проблему сервиса, о которой следует сообщить оператору стенда.
2. Запустите мастер
Отправьте:
/newbot
BotFather последовательно запросит:
usernameдлиной 3–32 ASCII-символа; разрешены латинские буквы, цифры и_;- отображаемое имя длиной до 128 Unicode-символов;
- набор возможностей.
Для учебного бота диалог выглядит так:
Вы: /newbot
BotFather: Пришлите username нового бота…
Вы: echo_bot
BotFather: Теперь пришлите отображаемое имя бота.
Вы: Эхо
BotFather: Выберите возможности: default либо список dm,groups,channels через запятую.
Вы: default
BotFather: Готово: @echo_bot. … 7100001:<64 hex символа секрета>
BotFather: Bot ID: <64 hex символа>
BotFather: Ключ восстановления identity: <64 hex символа>
Для текущего echo-сценария выберите default: этого достаточно для приватных сообщений. Если бот должен работать в группах или публиковать записи в каналах, укажите groups, channels либо оба значения. Gateway проверяет эти возможности при подключении бота к соответствующему разговору.
3. Сохраните token и recovery secret
BotFather показывает два разных секрета и публичный идентификатор:
- bot token авторизует gateway и входит в локальный URL Bot API;
- identity recovery secret позволяет новому runtime восстановить постоянную identity того же бота.
- Bot ID связывает конфигурацию gateway с ботом платформы и секретом не является.
Bot token показывается при создании и каждой ротации. Identity recovery secret выдаётся при создании и остаётся постоянным после ротации token. Сохраните оба значения в secret manager или файле с правами 0600. Не помещайте их в Git, shell history, issue tracker и логи.
При утрате или подозрении на компрометацию отправьте:
/token @echo_bot
Прежний token становится недействительным сразу после ответа.
4. Подготовьте идентификаторы
Новый token имеет Telegram SDK-compatible форму:
<положительный 52-битный numeric id>:<64 hex символа секрета>
Скопируйте отдельный Bot ID из ответа BotFather. Числовая часть token служит
telegram_user_id и безопасно представима в Python и JavaScript:
set +x
read -rsp 'Bot token: ' SKREPA_BOT_TOKEN; printf '\n'
read -rsp 'Identity recovery secret: ' SKREPA_BOT_IDENTITY_RECOVERY_SECRET; printf '\n'
read -rp 'Bot ID: ' SKREPA_BOT_ID_HEX
export SKREPA_BOT_TOKEN
export SKREPA_BOT_IDENTITY_RECOVERY_SECRET
export SKREPA_BOT_ID_HEX
export SKREPA_TELEGRAM_USER_ID="${SKREPA_BOT_TOKEN%%:*}"
test "${#SKREPA_BOT_ID_HEX}" -eq 64
test "${SKREPA_TELEGRAM_USER_ID}" -gt 0
Gateway принимает только SDK-compatible token из актуального ответа BotFather.
Если token был выдан до перехода на numeric id, выполните /token @echo_bot и
замените credential в secret manager и конфигурации gateway.
5. Скачайте gateway
Готовые дистрибутивы публикуются вместе с серверным релизом. На Mac с Apple Silicon скачайте нативный бинарный файл:
mkdir -p var/echo-bot/bin
cd var/echo-bot/bin
curl --fail-with-body -LO \
https://skrepa.fomkin.org/download/bot-gateway/skrepa-bot-gateway-darwin-arm64.tar.gz
curl --fail-with-body -LO \
https://skrepa.fomkin.org/download/bot-gateway/skrepa-bot-gateway-darwin-arm64.tar.gz.sha256
shasum -a 256 -c skrepa-bot-gateway-darwin-arm64.tar.gz.sha256
tar -xzf skrepa-bot-gateway-darwin-arm64.tar.gz
chmod 700 skrepa-bot-gateway
cd -
Для Linux amd64 и для остальных систем с Docker доступен образ в архиве:
mkdir -p var/echo-bot/image
cd var/echo-bot/image
curl --fail-with-body -LO \
https://skrepa.fomkin.org/download/bot-gateway/skrepa-bot-gateway-linux-amd64.docker.tar.gz
curl --fail-with-body -LO \
https://skrepa.fomkin.org/download/bot-gateway/skrepa-bot-gateway-linux-amd64.docker.tar.gz.sha256
shasum -a 256 -c skrepa-bot-gateway-linux-amd64.docker.tar.gz.sha256
gzip -dc skrepa-bot-gateway-linux-amd64.docker.tar.gz | docker load
cd -
Checksum-файл содержит имя соседнего архива. Поэтому команды проверки следует запускать из каталога, куда скачаны оба файла. На Linux shasum -a 256 -c можно заменить на sha256sum -c.
6. Создайте конфигурацию gateway
umask 077
mkdir -p var/echo-bot
jq -n \
--arg endpoint "https://skrepa.fomkin.org" \
--arg state_dir "${PWD}/var/echo-bot/state" \
--arg bot_id_hex "${SKREPA_BOT_ID_HEX}" \
--arg token "${SKREPA_BOT_TOKEN}" \
--arg telegram_user_id "${SKREPA_TELEGRAM_USER_ID}" \
--arg identity_recovery_secret "${SKREPA_BOT_IDENTITY_RECOVERY_SECRET}" \
'{
compatibility_profile_id: "telegram-v1-core-preview-4",
bot_platform_endpoint: $endpoint,
telegram_listen_addr: "127.0.0.1:8081",
extensions_listen_addr: "127.0.0.1:8082",
state_dir: $state_dir,
bots: [{
bot_id_hex: $bot_id_hex,
token: $token,
identity_recovery_secret_hex: $identity_recovery_secret,
telegram_user_id: ($telegram_user_id | tonumber),
username: "echo_bot",
first_name: "Эхо"
}]
}' > var/echo-bot/gateway.json
chmod 600 var/echo-bot/gateway.json
telegram_user_id берётся из token и остаётся стабильным при ротации credential.
identity_recovery_secret_hex — отдельный 256-битный recovery secret, который
BotFather показывает вместе с первым token. Храните его в secret manager и
сохраняйте прежним при ротации bot token. state_dir содержит стабильный
bot_runtime_id и должен переживать перезапуски.
7. Запустите gateway
На Mac с Apple Silicon:
./var/echo-bot/bin/skrepa-bot-gateway serve var/echo-bot/gateway.json
Для Docker подготовьте конфигурацию с контейнерными адресами и каталогом state:
jq \
'.telegram_listen_addr = "0.0.0.0:8081"
| .extensions_listen_addr = "0.0.0.0:8082"
| .state_dir = "/state"' \
var/echo-bot/gateway.json > var/echo-bot/gateway.docker.json
chmod 600 var/echo-bot/gateway.docker.json
mkdir -p var/echo-bot/state
docker run --rm --name skrepa-echo-bot \
--user "$(id -u):$(id -g)" \
-p 127.0.0.1:8081:8081 \
-p 127.0.0.1:8082:8082 \
-v "${PWD}/var/echo-bot/gateway.docker.json:/config/gateway.json:ro" \
-v "${PWD}/var/echo-bot/state:/state" \
skrepa-bot-gateway:0.1.0 serve /config/gateway.json
Команда работает в foreground и пишет только служебные сведения о runtime. Для постоянного запуска перенесите те же параметры в systemd, Compose или вашу систему оркестрации. Секретный URL Bot API стоит исключить из access logs.
В другом терминале проверьте listeners и server-backed getMe:
curl --fail-with-body -sS http://127.0.0.1:8081/healthz | jq .
curl --fail-with-body -sS http://127.0.0.1:8082/healthz | jq .
curl --fail-with-body -sS \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/getMe" | jq .
Ожидаемый профиль:
{
"ok": true,
"result": {
"id": 7100001,
"is_bot": true,
"first_name": "Эхо",
"username": "echo_bot"
}
}
После /token или /disable старый URL начинает возвращать HTTP 401. Gateway пока не перечитывает credential на лету: обновите gateway.json, перезапустите процесс и повторите getMe.
Gateway сам создаёт стабильное bot device, регистрирует подписанные prekey и запускает delivery loop. Входящие E2E-DM появляются в getUpdates:
curl --fail-with-body -sS \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/getUpdates?limit=100" | jq .
update_id подтверждается обычной Telegram-семантикой: следующий запрос передаёт offset, равный последнему обработанному update_id + 1. До этого момента update остаётся в локальной durable queue и переживает перезапуск gateway.
Ответ в приватный E2E-диалог отправляется через sendMessage. Возьмите chat.id из update:
curl --fail-with-body -sS \
-H 'content-type: application/json' \
-d '{"chat_id": 217020518514230, "text": "Привет из бота"}' \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/sendMessage" | jq .
Gateway сверяет активные устройства адресата, шифрует отдельный ciphertext для каждого и сохраняет исходящее сообщение в local outbox до подтверждения платформы.
Форматирование, replies и inline-кнопки
sendMessage принимает parse_mode со значениями HTML или MarkdownV2, а
также явный массив entities. Смещения и длины entities измеряются в UTF-16
code units, как в Telegram Bot API. Передавайте либо parse_mode, либо
entities: сочетание двух вариантов gateway отклоняет.
MarkdownV2 включает fenced code blocks с необязательным языком, например
```rust. Gateway создаёт для такого блока entity типа pre.
Для ответа используйте настоящий message_id входящего сообщения. Inline
keyboard поддерживает callback-кнопки; нажатие приходит отдельным durable
callback_query update и подтверждается методом answerCallbackQuery.
Контекст кнопки подписан общей identity бота. Нажатие сможет обработать другой
активный gateway runtime этого же бота, даже если исходное сообщение отправил
первый runtime; изменённый клиентом контекст проверку подписи не проходит.
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyParameters
async def menu(update, _context):
message = update.message
await message.reply_text(
"<b>Выберите действие</b>",
parse_mode="HTML",
reply_parameters=ReplyParameters(message_id=message.message_id),
reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton(
"Дальше", callback_data="next"
)]]),
)
async def button(update, _context):
query = update.callback_query
await query.answer("Готово")
await query.edit_message_text("<i>Шаг выполнен</i>", parse_mode="HTML")
Кроме sendMessage, текстовый профиль публикует sendChatAction,
editMessageText, deleteMessage, forwardMessage и copyMessage.
Редактирование и удаление разрешены для сообщений, созданных этим ботом и
доступных в durable history текущего runtime.
forwardMessage сохраняет сведения об источнике, copyMessage переносит
содержимое без отметки о пересылке.
Gateway хранит до 2048 обработанных сообщений и callbacks. Pending updates, ожидающие ответа callbacks и активные callback targets не удаляются до доставки. Reply snapshots содержат только один уровень вложенности, поэтому длинная цепочка ответов не раздувает durable state квадратично.
Группы и каналы
Бота добавляют в обычную или защищённую группу через интерфейс Скрепы. Gateway
сам обнаруживает новое участие и выдаёт разговору стабильный отрицательный
chat.id; приложение бота не работает с 32-байтовыми внутренними ID. Обычная
группа отображается как Telegram supergroup. Защищённая группа выглядит так
же для bot SDK, при этом её сообщения шифруются общей MLS-реализацией Скрепы:
bot device публикует key package, входит внешним commit и сохраняет epoch state
в своём зашифрованном durable state.
Командный обработчик можно зарегистрировать привычным способом:
from telegram import Update
from telegram.ext import CommandHandler, ContextTypes
async def status(update: Update, context: ContextTypes.DEFAULT_TYPE):
chat = await context.bot.get_chat(update.effective_chat.id)
members = await context.bot.get_chat_member_count(chat.id)
await update.message.reply_text(f"{chat.title}: участников {members}")
application.add_handler(CommandHandler("status", status))
Публикации канала приходят в update.channel_post, а sendMessage с его
chat.id создаёт новый пост при наличии права post. Публикация сначала
фиксируется в durable gateway outbox. Повтор после потерянного ответа использует
тот же client_message_id, поэтому PostgreSQL channel outbox создаёт ровно один
пост.
Административный сценарий использует my_chat_member, chat_member и
chat_join_request. Доступны getChat, getChatMember,
getChatMemberCount, getChatAdministrators, leaveChat, методы
блокировки и выдачи прав, ссылки-приглашения, одобрение и отклонение заявок.
При явном allowed_updates перечислите нужные типы; без фильтра gateway
доставляет их по умолчанию. В текущем preview текстовые групповые и каналовые
сценарии являются основными; media-публикации канала и forum topics входят в
последующие профильные срезы.
Inline mode
Пользователь может вызвать бота прямо в поле ввода приватного диалога: начните
сообщение с @username, добавьте пробел и поисковый запрос. Клиент открывает
пятиминутную inline-сессию, а gateway доставляет боту обычный inline_query
update. Результаты появляются над полем ввода; выбранный текст отправляется в
диалог по обычному E2E message path.
Inline mode поддерживает article results с InputTextMessageContent. До выбора бот
может заменить весь список повторным answerInlineQuery. После выбора или
истечения TTL platform отклоняет новый ответ, а повторный выбор того же result
остаётся идемпотентным.
Пример для python-telegram-bot:
from telegram import InlineQueryResultArticle, InputTextMessageContent, Update
from telegram.ext import InlineQueryHandler
async def inline_search(update: Update, _context):
query = update.inline_query.query.strip()
await update.inline_query.answer([
InlineQueryResultArticle(
id="result-1",
title=f"Отправить: {query or 'пустой запрос'}",
description="Текст будет отправлен в текущий диалог",
input_message_content=InputTextMessageContent(
f"Результат поиска: {query}"
),
)
], is_personal=True, cache_time=0)
application.add_handler(InlineQueryHandler(inline_search))
Для polling добавьте inline_query и chosen_inline_result в
allowed_updates, если передаёте явный фильтр. Gateway сохраняет server cursor
в state_dir: перезапуск не создаёт второй Telegram update для уже
импортированной platform-сессии.
Реакции
В private E2E-диалоге бот может поставить одну обычную emoji-реакцию
методом setMessageReaction. Пустой список снимает её:
from telegram import ReactionTypeEmoji
await context.bot.set_message_reaction(
chat_id=update.effective_chat.id,
message_id=update.effective_message.message_id,
reaction=[ReactionTypeEmoji("👍")],
)
# Снять реакцию
await context.bot.set_message_reaction(
chat_id=update.effective_chat.id,
message_id=update.effective_message.message_id,
reaction=[],
)
Когда пользователь меняет реакцию в диалоге с ботом, gateway
выдаёт message_reaction с массивами old_reaction и
new_reaction. Telegram исключает такие updates из default-набора, поэтому
укажите allowed_updates=["message", "message_reaction"], если бот их
обрабатывает.
Реакции пока охватывают private DM. Установка custom_emoji, paid reactions,
нескольких реакций и is_big=true получает Telegram-shaped 400.
Входящая custom emoji может прийти как ReactionTypeCustomEmoji, если её
выбрал обычный клиент Скрепы.
Файлы и media
Методы sendPhoto, sendDocument, sendAudio, sendVideo и sendVoice
принимают обычный multipart upload стандартных Telegram SDK. Лимит одного
файла составляет 50 МиБ. Gateway шифрует bytes до загрузки в Скрепу, сохраняет
canonical file reference и возвращает opaque file_id в объекте сообщения.
Полученный file_id можно передать тому же media-методу после перезапуска
gateway. Поэтому каталог state_dir обязан находиться на постоянном диске:
там хранится связь публичного file_id с canonical file reference и E2E-ключом.
Ссылка на внешний HTTP URL в media-параметре отклоняется; это исключает SSRF из
профиля. Загружайте локальный файл либо повторно используйте file_id.
Пример для python-telegram-bot:
from pathlib import Path
async def send_report(update, _context):
sent = await update.message.reply_document(
document=Path("report.pdf"),
caption="Отчёт",
)
# file_id остаётся пригодным для повторной отправки после restart gateway.
await update.message.reply_document(sent.document.file_id)
descriptor = await sent.get_bot().get_file(sent.document.file_id)
await descriptor.download_to_drive("downloaded-report.pdf")
При собственной настройке SDK задайте обе базы URL. base_url указывает на
методы, base_file_url — на контролируемое gateway скачивание:
application = (
Application.builder()
.token(os.environ["SKREPA_BOT_TOKEN"])
.base_url("http://127.0.0.1:8081/bot")
.base_file_url("http://127.0.0.1:8081/file/bot")
.build()
)
getFile возвращает Telegram-compatible file_path. Download сначала
полностью получает ciphertext из file stack и проверяет GOST authentication
tag. При обрыве backend-потока или повреждении файла gateway отвечает ошибкой и
не выдаёт частичный plaintext.
Echo-бот на python-telegram-bot
Gateway поддерживает стандартный polling lifecycle python-telegram-bot 22.8,
включая служебный вызов deleteWebhook, long polling и form-urlencoded
запросы. Установите SDK в virtualenv:
python3 -m venv .venv
.venv/bin/python -m pip install 'python-telegram-bot==22.8'
Создайте echo_bot.py:
import os
from telegram import Update
from telegram.ext import Application, ContextTypes, MessageHandler, filters
async def echo(update: Update, _context: ContextTypes.DEFAULT_TYPE) -> None:
if update.message and update.message.text:
await update.message.reply_text(update.message.text)
application = (
Application.builder()
.token(os.environ["SKREPA_BOT_TOKEN"])
.base_url("http://127.0.0.1:8081/bot")
.base_file_url("http://127.0.0.1:8081/file/bot")
.build()
)
application.add_handler(MessageHandler(filters.TEXT, echo))
application.run_polling(allowed_updates=["message"])
Запустите gateway, затем бота:
.venv/bin/python echo_bot.py
Суффикс /bot в base_url обязателен: SDK самостоятельно добавляет к нему
token и имя метода. Этот сценарий входит в release tests и проходит через
настоящий E2E transport Скрепы.
Echo-бот на aiogram
aiogram 3.29.1 использует тот же token и base URL, а запросы отправляет через
штатную AiohttpSession:
import asyncio
import os
from aiogram import Bot, Dispatcher, F
from aiogram.client.session.aiohttp import AiohttpSession
from aiogram.client.telegram import TelegramAPIServer
from aiogram.types import Message
dp = Dispatcher()
@dp.message(F.text)
async def echo(message: Message) -> None:
await message.answer(message.text)
async def main() -> None:
session = AiohttpSession(
api=TelegramAPIServer.from_base("http://127.0.0.1:8081")
)
bot = Bot(token=os.environ["SKREPA_BOT_TOKEN"], session=session)
await dp.start_polling(bot, allowed_updates=["message"])
asyncio.run(main())
Echo-бот на Telegraf
Telegraf 4.16.3 получает корневой адрес API через штатную настройку
telegram.apiRoot:
const { Telegraf } = require('telegraf')
const bot = new Telegraf(process.env.SKREPA_BOT_TOKEN, {
telegram: { apiRoot: 'http://127.0.0.1:8081' },
})
bot.on('text', (ctx) => ctx.reply(ctx.message.text))
bot.launch({ allowedUpdates: ['message'] })
Все три SDK проходят release-сценарий с reply, HTML entities, chat action,
редактированием, удалением, копированием, пересылкой и inline keyboard. Gateway
сохраняет User.id, Chat.id и Message.message_id в 52-битном диапазоне,
поэтому Node.js передаёт их без округления. update_id остаётся отдельным
курсором доставки.
Push-доставка через webhook
Webhook подходит сервисам с постоянно доступным HTTP endpoint. Gateway
остаётся владельцем E2E device и durable queue, а приложение получает уже
расшифрованный Telegram-compatible Update методом POST.
Для production понадобится HTTPS URL, доступный из network namespace gateway.
Обычный HTTP принимается только для localhost и loopback IP, что удобно при
локальной разработке. Сгенерируйте отдельный проверочный секрет:
export SKREPA_WEBHOOK_URL='https://bot.example.org/skrepa/update'
export SKREPA_WEBHOOK_SECRET="$(openssl rand -hex 32)"
curl --fail-with-body -sS \
-H 'content-type: application/json' \
-d "$(jq -n \
--arg url "$SKREPA_WEBHOOK_URL" \
--arg secret "$SKREPA_WEBHOOK_SECRET" \
'{url: $url, secret_token: $secret, allowed_updates: ["message"]}')" \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/setWebhook" | jq .
После успешного setWebhook вызовы getUpdates получают HTTP 409: у одного
bot runtime в каждый момент действует один delivery mode. Текущий URL, число
pending updates и последнюю ошибку можно проверить без чтения локальных файлов:
curl --fail-with-body -sS \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/getWebhookInfo" | jq .
Каждый запрос содержит JSON-объект Update и заголовок
X-Telegram-Bot-Api-Secret-Token. Сравнивайте секрет без записи заголовка в
логи. Возвращайте 2xx лишь после того, как бизнес-эффект или задание для него
надёжно сохранены. При timeout, сетевой ошибке либо другом HTTP status gateway
оставляет головной update в очереди и повторяет его с ограниченной
экспоненциальной задержкой; следующие updates ждут своей очереди. Поэтому
обработчик должен быть идемпотентным по update_id.
Перед каждой попыткой gateway перепроверяет bot credential на платформе. Отзыв
token или отключение бота останавливает отправку даже для уже накопленной
очереди. После плановой ротации обновите token в gateway.json и перезапустите
gateway: прежние pending updates останутся в state_dir.
python-telegram-bot выполняет тот же lifecycle штатными методами:
import os
from telegram import Bot
async def configure_webhook() -> None:
async with Bot(
token=os.environ["SKREPA_BOT_TOKEN"],
base_url="http://127.0.0.1:8081/bot",
) as bot:
await bot.set_webhook(
url=os.environ["SKREPA_WEBHOOK_URL"],
secret_token=os.environ["SKREPA_WEBHOOK_SECRET"],
allowed_updates=["message"],
)
info = await bot.get_webhook_info()
print(info.url, info.pending_update_count, info.last_error_message)
Gateway доставляет updates последовательно: max_connections в preview-профиле
равен 1. Файлы сертификатов и фиксированный ip_address пока не
поддерживаются. Конфигурация, очередь и последняя ошибка переживают restart
gateway.
Для возврата к polling удалите webhook. Очередь сохранится и станет доступна
через getUpdates в прежнем порядке:
curl --fail-with-body -sS -X POST \
"http://127.0.0.1:8081/bot${SKREPA_BOT_TOKEN}/deleteWebhook" | jq .
Передача drop_pending_updates=true необратимо очищает очередь одновременно со
сменой режима; используйте её только при сознательном отказе от накопленных
updates.
Что сохранять между перезапусками
В state_dir находятся криптографические сессии и локальные очереди конкретного экземпляра gateway. Это рабочие данные, сопоставимые с небольшой базой данных:
- сохраняйте каталог на постоянном диске;
- выдавайте файлам права
0600, а каталогу —0700; - делайте резервную копию только при остановленном gateway;
- используйте отдельный
state_dirдля каждого одновременно работающего экземпляра; - не подменяйте state одного бота каталогом другого.
Потеря state_dir заставит gateway зарегистрировать новое устройство и построить криптографические сессии заново. Updates, которые уже были приняты с сервера и оставались только в локальной очереди, восстановить будет нельзя. Identity recovery secret сохраняет identity бота, однако локальные очереди он не заменяет.
Безопасность эксплуатации
- Храните bot token и identity recovery secret в secret manager или конфигурации с правами
0600. - Оставляйте HTTP listeners на loopback-интерфейсе. При доступе из другой сети ставьте перед ними аутентифицированный reverse proxy.
- Исключите пути
/bot<TOKEN>/...из access logs: token находится прямо в URL. - Защитите и резервируйте
state_dir; он содержит private keys и незавершённые сообщения. - После
/tokenатомарно обновите конфигурацию и перезапустите gateway. Старый token уже не действует. - Запускайте gateway от отдельного системного пользователя без лишних прав.
Диагностика
| Симптом | Что проверить |
|---|---|
@botfather отсутствует в поиске | проверьте написание, авторизацию клиента и соединение; затем сообщите оператору стенда |
| BotFather не отвечает | обновите клиент, повторите команду новым сообщением и проверьте соединение |
| gateway завершается при старте | проверьте JSON-конфигурацию, доступность bot_platform_endpoint и права на state_dir |
gateway getMe возвращает 401 | token устарел, отозван, содержит лишние символы или бот отключён |
| регистрация устройства отклонена | проверьте соответствие bot token, recovery secret и каталога state одному боту |
getUpdates пуст после сообщения | убедитесь, что пользователь написал этому боту, gateway продолжает работать и переданный offset корректен |
getUpdates возвращает 409 | у runtime настроен webhook; проверьте getWebhookInfo или вызовите deleteWebhook |
| webhook не получает updates | проверьте доступность URL из сети gateway, HTTPS-сертификат и last_error_message в getWebhookInfo |
sendMessage или media-метод сообщает unknown private chat | сначала должен прийти update из этого диалога; используйте его chat.id |
| media-метод отклоняет URL | передайте multipart upload или ранее полученный file_id; загрузка произвольных URL отключена |
getFile сообщает file_id is unknown | используйте file_id из update или ответа этого runtime и проверьте сохранность state_dir |
Текущие ограничения
- Telegram-compatible gateway поддерживает
getMe,getUpdates, webhook, форматированный текст, replies, edit/delete, forward/copy, chat actions и inline callbacks, inline article mode, реакции, photo, document, audio, video и voice в приватных диалогах, а также текстовые группы и каналы с административными операциями. - Media upload ограничен 50 МиБ. Произвольные HTTP URL и Telegram CDN topology в текущий профиль не входят; скачивание идёт через gateway-controlled path.
- Webhook delivery последовательна (
max_connections=1) и принимает все update-типы текущего профиля; custom certificate и фиксированныйip_addressпока отсутствуют. getUpdatesподдерживает long polling до 50 секунд. Для одного bot runtime разрешён один ожидающий polling-запрос; конкурентный запрос получает409.allowed_updatesв preview-профиле принимает пустой список,message,channel_post,callback_query,inline_query,chosen_inline_result,message_reaction,my_chat_member,chat_memberиchat_join_request.message_reactionтребуется выбрать явно. Последнее переданное значение сохраняется в durable state gateway.- Состояние в
state_dirзащищено правами файловой системы хоста; отдельного ключа шифрования каталога сейчас нет. - Gateway читает token при старте. После ротации требуется изменить конфигурацию и перезапустить процесс.
- Media-публикации в каналах и forum topics пока не поддерживаются.
Что дальше
Замените ручные вызовы getUpdates циклом вашего приложения, сохраняйте последний подтверждённый update_id и обрабатывайте повторную доставку идемпотентно. Полный контракт запросов и ответов смотрите в Bot API Reference.