🛡️ Скрепа

Надёжный long polling

Результат

Вы получите входящее сообщение без busy loop, увидите повторную доставку неподтверждённого update и подтвердите только обработанное событие с помощью offset.

Что понадобится

export SKREPA_BOT_API='http://127.0.0.1:8081'
export SKREPA_BOT_TOKEN='ваш-token-из-BotFather'
set -o pipefail

Если раньше был настроен webhook, верните runtime в polling mode:

curl --fail-with-body -sS -X POST \
  "$SKREPA_BOT_API/bot${SKREPA_BOT_TOKEN}/deleteWebhook" | jq .

1. Создайте безопасный polling-сценарий

Пример запрашивает ровно один update. Благодаря этому offset никогда не подтвердит соседнее событие, которое приложение ещё не обработало. Сохраните как polling_demo.py:

import json
import os
import urllib.error
import urllib.request

BASE_URL = os.environ["SKREPA_BOT_API"].rstrip("/")
TOKEN = os.environ["SKREPA_BOT_TOKEN"]
UPDATES_URL = f"{BASE_URL}/bot{TOKEN}/getUpdates"


def get_updates(payload: dict) -> dict:
    request = urllib.request.Request(
        UPDATES_URL,
        data=json.dumps(payload).encode(),
        headers={"content-type": "application/json"},
        method="POST",
    )
    try:
        with urllib.request.urlopen(request, timeout=55) as response:
            result = json.load(response)
    except urllib.error.HTTPError as error:
        body = error.read().decode(errors="replace")
        raise SystemExit(f"getUpdates вернул HTTP {error.code}: {body}") from error
    except urllib.error.URLError as error:
        raise SystemExit(f"gateway недоступен: {error.reason}") from error
    if result.get("ok") is not True or not isinstance(result.get("result"), list):
        raise SystemExit(f"неожиданный ответ gateway: {result!r}")
    return result


while True:
    response = get_updates(
        {"limit": 1, "timeout": 30, "allowed_updates": ["message"]}
    )
    if not response["result"]:
        print("За 30 секунд сообщение не пришло; продолжаю ждать.")
        continue
    update = response["result"][0]
    if "message" not in update:
        kinds = sorted(set(update) - {"update_id"})
        raise SystemExit(
            "В очереди уже лежит update другого типа "
            f"({', '.join(kinds) or 'unknown'}). Обработайте его отдельным "
            "consumer либо осознанно очистите тестовую очередь; offset не изменён."
        )
    break

update_id = update["update_id"]
chat_id = update["message"]["chat"]["id"]
print(f"Получен update_id={update_id}, chat_id={chat_id}")

repeated = get_updates({"limit": 1, "timeout": 0})
if not repeated["result"] or repeated["result"][0]["update_id"] != update_id:
    raise SystemExit("неподтверждённый update не был доставлен повторно")
print("Повторная доставка подтверждена")

# Здесь находится успешно завершившийся бизнес-эффект приложения.
print(f"Обработано сообщение: {update['message'].get('text', '')}")

next_offset = update_id + 1  # Python хранит целые числа с произвольной точностью.
acknowledged = get_updates(
    {"offset": next_offset, "limit": 1, "timeout": 0}
)
print(f"Подтверждено offset={next_offset}: {acknowledged}")

2. Дождитесь сообщения и подтвердите его

Запустите пример, затем отправьте боту текст из клиента Скрепы:

python3 polling_demo.py

Если очередь пуста, соединение ожидает до 30 секунд без busy loop. Скрипт покажет один и тот же update_id дважды, выполнит учебный бизнес-эффект и лишь после этого передаст offset = update_id + 1.

offset подтверждает все updates с меньшим идентификатором. В рабочем consumer сохраняйте новый offset только после надёжной фиксации бизнес-эффекта.

Как понять, что всё получилось

Типичные ошибки

СимптомЧто проверить
HTTP 409активен webhook или уже выполняется второй long poll этого runtime
скрипт продолжает ждатьlong poll завершился по timeout; отправьте сообщение нужному боту
в очереди update другого типаобработайте его подходящим consumer; для одноразового тестового бота очередь можно сознательно очистить через deleteWebhook с drop_pending_updates=true
события пропали раньше обработкиприложение передало слишком большой offset; подтверждайте лишь успешно обработанные updates
после перезапуска приходит старое событиеоно оставалось неподтверждённым; обработчик должен быть идемпотентным по update_id

Что гарантирует gateway