Skip to main content

Cozygram Bot API 1.0

Боты в Cozygram работают так же, как в Telegram: вы создаёте бота, получаете токен, запускаете свою программу у себя — и бот отвечает людям в мессенджере.


Главная идея: бот — это обычный пользователь

Каждый бот — это настоящая запись в auth.users и profiles, только с флагом is_bot = true и владельцем bot_owner.

Почему это важно: ботам не нужно ничего дописывать в мессенджере. Они сразу умеют всё, что умеют люди: переписка, вложения, реакции, ответы цитатой, поиск по людям, мгновенная доставка через realtime. Код чатов не тронут вообще.

Отличия бота от человека всего три:

  1. Входит не по паролю, а по токену.
  2. Не получает push-уведомлений — вместо них очередь обновлений или webhook.
  3. Не может создавать других ботов (иначе лавина аккаунтов).

Что внутри

api/
  README.md                     ← этот файл
  python/
    cozygram/
      __init__.py               ← from cozygram import Bot
      client.py                 ← клиент + цикл опроса
      types.py                  ← Message, User, Chat, Attachment, Update
      errors.py                 ← ApiError, Unauthorized, NetworkError
    examples/
      echo_bot.py               ← самый простой бот
      cozyfather.py             ← бот, который создаёт ботов
    pyproject.toml

web/                            ← сайт и САМ ШЛЮЗ (Next.js, разворачивается на Vercel)
  app/api/bot/[...path]/        ← публичный Bot API
  app/api/bot-manage/           ← создание ботов и токены
  app/bots/                     ← кабинет «Мои боты»
  lib/methods/                  ← реализация методов API

supabase/
  bot_api.sql                   ← таблицы, очередь, триггеры
  functions/push/index.ts       ← push-уведомления (к ботам не относится)

lib/services/bot_service.dart   ← клиент управления в приложении
lib/screens/bots_screen.dart    ← Настройки → Боты

Установка (один раз)

1. База

В Supabase → SQL Editor выполните supabase/bot_api.sql. Он идемпотентный — можно запускать повторно. Требует уже применённого schema.sql.

2. Служебный бот CozyFather

Там же, в SQL Editor, одной строкой. Она сразу выведет токен — сохраните его:

select public.bot_bootstrap('cozyfather_bot', 'CozyFather', null, '{bots:manage}');

Никаких тыканий в мобильном приложении не требуется: функция сама создаёт запись в auth.users, профиль с 🤖 и самого бота. Пароля у такого аккаунта нет вообще — войти в него как человек невозможно никому, включая админа.

Строку можно вызывать повторно: бот не удвоится, а получит новый токен вместо старого. Так же восстанавливают потерянный токен.

Права существующему боту меняются отдельно:

select public.bot_set_scopes('cozyfather_bot', '{bots:manage}');  -- выдать
select public.bot_set_scopes('cozyfather_bot', '{}');              -- отобрать

Обе функции отозваны у anon и authenticated — вызвать их из клиента невозможно, только вручную в SQL Editor.

3. Шлюз

Шлюз живёт в папке web/ — это приложение Next.js, которое разворачивается на Vercel одной кнопкой:

Маршрут Что делает Авторизация
POST /api/bot/bot<ТОКЕН>/<метод> публичный Bot API токен бота в адресе
POST /api/bot-manage создание ботов и токены JWT человека либо Bearer bot<токен> со scope

Переменные окружения — в Vercel → Settings → Environment Variables, образец лежит в web/.env.example:

NEXT_PUBLIC_SUPABASE_URL=...
NEXT_PUBLIC_SUPABASE_ANON_KEY=...
SUPABASE_SERVICE_ROLE_KEY=...      # только сервер, без префикса NEXT_PUBLIC_

Открытый адрес /api/bot — это не дыра: шлюз сам сверяет SHA-256 токена с базой и без верного токена не делает ничего. Закрыть его JWT нельзя — у бота нет сессии человека, его единственное удостоверение и есть токен.

Раньше та же логика дублировалась в edge-функциях supabase/functions/bot и bot-manage. Их больше нет: две реализации одного API неизбежно расходятся — починишь проверку в одной, а во второй дыра останется. Если вы разворачивали их раньше — удалите, чтобы старый адрес не отвечал:

supabase functions delete bot
supabase functions delete bot-manage

4. Адрес шлюза в мобильном приложении

Приложение ходит в тот же /api/bot-manage, что и веб-кабинет. Адрес лежит в lib/config/supabase_config.dart и подменяется при сборке без правки кода:

flutter build apk --dart-define=COZYGRAM_SITE=https://ваш-домен.vercel.app

Без флага берётся defaultValue из того же файла — поправьте его под себя один раз после первого развёртывания.

Сводка, кто куда стучится:

Кто Адрес Где задаётся
Мобильное приложение /api/bot-manage --dart-define=COZYGRAM_SITE
Веб-кабинет /api/bot-manage свой же домен, автоматически
Библиотека Python /api/bot api_base= или COZYGRAM_API_BASE
CozyFather оба COZYGRAM_SITE

Быстрый старт: ваш первый бот за две минуты

Шаг 1. В приложении: Настройки → Боты → +. Введите имя и @username (обязательно оканчивается на bot). Скопируйте токен.

Шаг 2. Создайте my_bot.py:

from cozygram import Bot

# api_base — адрес вашего шлюза. Можно не передавать здесь,
# а задать переменную окружения COZYGRAM_API_BASE.
bot = Bot("7:ваш-токен", api_base="https://ваш-домен.vercel.app/api/bot")

@bot.command("start")
def start(message):
    bot.reply(message, "Привет! Я живой.")

@bot.message
def echo(message):
    bot.send_message(message.chat.id, message.text)

bot.run()

Шаг 3.

cd api/python
python examples/echo_bot.py   # или python my_bot.py

Готово. Найдите бота в поиске по @username и напишите ему.

Зависимостей нет намеренно: библиотека живёт на чистом Python 3.9+, без pip install. Скопировали папку cozygram/ рядом со скриптом — уже работает.


CozyFather — бот, который создаёт ботов

Аналог BotFather. Диалог один в один, как в Telegram:

/newbot     → спросит имя, потом @username, выдаст токен
/mybots     → список ваших ботов
/revoke     → выдать новый токен
/deletebot  → удалить бота
/cancel     → прервать действие

Как решена проблема курицы и яйца

Первый бот действительно не может создать себя через API. Поэтому он создаётся уровнем ниже — функцией bot_bootstrap в самой базе, как часть установки (шаг 2 выше). Запись в auth.users напрямую — штатный способ засева пользователей в Supabase, так же работают seed-файлы.

Никакого админа с телефоном в руках больше не нужно: развёртывание — это три SQL-запроса и две команды deploy, воспроизводимые на любом новом проекте.

Почему ему НЕ нужен админский ключ

У бота нет JWT того, кто ему написал. Раньше это решалось service_role ключом, но это плохой обмен: такой ключ может ВООБЩЕ ВСЁ — читать любую переписку, обходить RLS, удалять аккаунты. Отдавать его ради одной функции — то же самое, что дать курьеру ключи от всего дома, чтобы он оставил посылку у двери.

Сейчас сделано так, как делают в M2M-авторизации: точечное право (scope) вместо мастер-ключа. Принцип наименьших привилегий.

Режим Кто использует Авторизация Владелец бота
Человек приложение JWT человека сам человек
Служебный бот CozyFather Bearer bot<свой токен> + scope bots:manage owner_id, но только с доказанным делегированием
Админ ручные скрипты service_role ключ owner_id

Третий режим оставлен только как аварийный выход для ручных скриптов. В коде ботов он больше не используется нигде.

Доказательство делегирования

Одного scope мало: иначе CozyFather мог бы наплодить ботов на любой чужой аккаунт, просто подставив owner_id. Поэтому сервер проверяет ещё и то, что человек САМ написал этому боту хотя бы одно сообщение.

Сообщение в messages — и есть согласие пользователя: его нельзя подделать со стороны бота (он не может писать от имени человека) и оно проверяется запросом к базе. Ровно то же действие, что в Telegram: чтобы завести бота, вы сначала пишете BotFather /start.

Итог: если токен CozyFather утечёт, вор не получит ни одной ��ужой переписки и ни одного токена других ботов. Максимум ущерба — возня с ботами тех людей, кто успел написать CozyFather. Лечится отзывом права одной строкой: select public.bot_set_scopes('cozyfather_bot', '{}');

Сверху — ограничение частоты: не больше 5 новых ботов в час на владельца и всё та же общая шапка 20 ботов. Все вызовы от имени бота пишутся в лог.

Запуск

export COZYGRAM_TOKEN="1:токен-CozyFather"
export COZYGRAM_SITE="https://ваш-домен.vercel.app"
python api/python/examples/cozyfather.py

Админский ключ базы на сервере бота больше не нужен вообще. Если видите его в переменных окружения бота — это ошибка конфигурации, а не требование.


Токены

Формат: <номер бота>:<32 случайных символа>, например 7:kQ8_xR2….

Как хранится:

  • в базе лежит только SHA-256 — самого токена нет нигде;
  • видимый огарок 7:kQ8x…R2m нужен только чтобы узнавать свой токен в списке;
  • полный токен показывается один раз — при создании или перевыпуске;
  • потеряли — восстановить невозможно даже админу, только выдать новый.

Токен — это полный доступ к боту. Не публикуйте его в GitHub. Если утёк — /revoke или «Выдать новый токен», старый умирает мгновенно.


Ссылка и формат ответов

POST https://<ваш-домен>/api/bot/bot<ТОКЕН>/<метод>

Успешно:

{ "ok": true, "result": { } }

Ошибка:

{ "ok": false, "error_code": 401, "description": "Неверный токен" }

Тот же договор, что у Telegram Bot API — привычно всем, кто писал ботов.


Методы

О себе

Метод Описание
getMe кто я
setMyName сменить видимое имя
setMyDescription описание бота
setMyCommands / getMyCommands меню команд

Получение сообщений

Метод Описание
getUpdates очередь, long polling до 25 с
setWebhook присылать на ваш https-адрес
deleteWebhook вернуться к очереди
getWebhookInfo текущие настройки

Одновременно работает только один способ. С установленным webhook getUpdates вернёт 409 — так же, как в Telegram.

При включённом webhook очередь не ведётся вообще: обновления уходят сразу POST-ом, а pending_update_count честно показывает ноль. Иначе в базе копился бы вечный хвост «необработанных» событий, которые на деле давно доставлены.

Переписка

Метод Описание
sendMessage текст, ответ цитатой, вложение
editMessageText править только свои сообщения
deleteMessage удалить своё сообщение
setMessageReaction поставить или снять реакцию
getChat кто мой собеседник
getChatHistory до 200 последних сообщений

chat_id принимает и uuid, и @username — удобно при отладке вручную.


Webhook вместо опроса

Опрос прост, но держит процесс. Если есть свой сервер с https:

bot.set_webhook("https://example.com/hook", secret_token="мой-секрет")

setWebhook сначала делает пробный POST на ваш адрес с телом {"update_id": 0, "probe": true} и сохраняет настройку, только если пришёл ответ 2xx. Смысл: включение webhook отключает очередь, и опечатка в адресе обернулась бы тихой потерей сообщений. Ваш обработчик должен отвечать 200 на такой пробный запрос.

Теперь база сама шлёт POST на ваш адрес при каждом сообщении, с заголовком X-Cozygram-Bot-Api-Secret-Token. Всегда сверяйте этот заголовок, иначе ваш адрес сможет дёрнуть любой.

Какой адрес примут, а какой отклонят

Адрес webhook — это место, куда наш сервер сам сделает запрос. Если разрешить туда внутренние адреса, бота можно превратить в прокси внутрь инфраструктуры и вытащить ключи из метаданных облака (атака SSRF). Поэтому шлюз проверяет сам адрес, а не сверяет его со списком плохих доменов — такой список обходится.

Адрес Результат
https://example.com/hook принят
https://example.com:8443/hook принят (443 и 8443)
http://example.com/hook отказ — только https
https://localhost/hook отказ — внутренний адрес
https://10.0.0.5/hook, 192.168.*, 172.16-31.* отказ — локальная сеть
https://169.254.169.254/... отказ — метаданные облака
https://2130706433/hook отказ — тот же 127.0.0.1 числом
https://кто-то:пароль@example.com отказ — логин в адресе

Честная оговорка: проверяется текст адреса, а не то, во что его потом разрешит DNS. Домен, указывающий на 127.0.0.1, формально пройдёт. Полностью это закрывается только прокси типа Smokescreen, а его внутри базы не поставить. Для личного проекта этого уровня достаточно.


Ограничение частоты запросов

Адрес /api/bot открыт всему интернету и не может быть закрыт JWT — иначе боты не смогли бы ходить по своему токену. Токены никто не угадает, но без счётчика поток мусорных запросов сжёг бы месячный лимит хостинга — то есть устроил бы вам счёт или простой.

Счётчик живёт в базе (public.bot_rate и bot_rate_check()), без Redis и без сторонних подписок. Два уровня:

Уровень Лимит Смысл
По IP-адресу, до проверки токена 300 запросов в минуту поток с неверными токенами отсекается сразу
На бота 120 запросов в минуту один шумный бот не мешает остальным
Новые боты на человека 5 в час нельзя наплодить тысячу аккаунтов

При превышении приходит 429. Цифры меняются в одном месте — константы IP_LIMIT и BOT_LIMIT в web/app/api/bot/[...path]/route.ts, NEW_BOTS_PER_HOUR и MAX_BOTS_PER_USER — в web/app/api/bot-manage/route.ts и web/lib/manage-actions.ts.

Опрос getUpdates держит соединение до 25 секунд, поэтому обычный бот тратит около 3 запросов в минуту и в лимит не упирается.


Ограничения

Что есть сейчас:

  • до 20 ботов на человека;
  • текст до 4096 символов;
  • очередь чистится через 7 дней (bot_updates_cleanup());
  • только личные чаты — групп в мессенджере пока нет.

Чего ещё нет, сознательно:

  • инлайн-клавиатур и кнопок — нужна поддержка в клиенте;
  • загрузки файлов прямо через API — пока передавайте готовый attachment_url;
  • инлайн-режима и платежей.

Шифрование: если человек включил E2E, бот увидит шифротекст — у бота нет ключа. Это не баг, а смысл шифрования. Общайтесь с ботами в обычных чатах.


Если что-то не работает

Симптом Причина
401 Неверный токен токен перевыпущен или скопирован с пробелом
401 у живого токена бот выключен в Настройки → Боты
getUpdates всегда пустой не применён bot_api.sql — нет триггера очереди
409 на getUpdates установлен webhook, снимите deleteWebhook
бот не находится в поиске ищите по @username без собаки
в списке «ни разу не запускался» код бота ещё не запущен нигде
429 на любом методе упёрлись в счётчик частоты — см. раздел выше
400 Внутренние адреса запрещены на setWebhook нужен публичный https-домен, не localhost
converting NULL to string is unsupported после bot_bootstrap старая версия bot_api.sql; примените свежий — он сам ставит пустые строки
список ботов в приложении пуст, хотя боты есть вьюха my_bots с security_invoker = on; свежий bot_api.sql выключает его

Release files for cozygram 1.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cozygram 1.0.0
File Size Uploaded
cozygram-1.0.0.tar.gz 24.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cozygram 1.0.0
File Interpreter ABI Platform
cozygram-1.0.0-py3-none-any.whl Python 3 none any Details

Total release size: 41.5 kB

Release files / cozygram-1.0.0.tar.gz

Download URL cozygram-1.0.0.tar.gz
Size 24.3 kB
Tags Source
SHA-256 checksum
How to use checksums
ba5a4373e178d1b787108b9f58e0e7be1f80096b32c7ec95e4fcf88dbefde7cb
BLAKE2b-256 checksum
How to use checksums
68c5a8f37afed9bd61785244ff2dc87081c75c3ec90627b965c7549a3596f627
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release files / cozygram-1.0.0-py3-none-any.whl

Download URL cozygram-1.0.0-py3-none-any.whl
Size 17.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e8b72e7729dc4c19a0d3c34125039c7708026ca6b12028bc692733536e4a8ec8
BLAKE2b-256 checksum
How to use checksums
b94aeac6d07853fe20a2ab8fc7577a7dc92a45ee24d5a8262f748731512858d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.12.10

Release history Release notifications | RSS feed

This release

1.0.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page