Перейти к содержанию

Запуск бота

Бости празднует под конфетти

bot.run()

Для большинства ботов bot.run() — это вся программа. Он настраивает логирование на уровне INFO в stderr, если у корневого логгера ещё нет обработчиков, запускает цикл событий и выполняет бота, пока тот не остановится. Ctrl+C и, в главном потоке, SIGTERM корректно его закрывают.

Токен берётся из переменной окружения BOSTIE_TOKEN или из bot.run(token). Префикс Bot в начале отбрасывается, а отсутствие токена вызывает ValueError. run также принимает loop_factory — функцию, создающую цикл событий, которая передаётся в asyncio.run.

Жизненный цикл

Работу за run() выполняет bot.start():

  1. Он отправляет событие Starting и по очереди дожидается каждого его слушателя. REST здесь уже работает, шлюз — ещё нет; слушатель, выбросивший исключение, прерывает запуск.
  2. Он синхронизирует команды, если не указано auto_sync=False.
  3. Он подключает шарды шлюза. Когда все шарды готовы и их серверы получены, он один раз отправляет Started, запускает фоновые задачи и позволяет wait_until_ready() вернуться.
  4. Он обрабатывает события до close() или до тех пор, пока Discord окончательно не закроет шлюз, что приводит к исключению, например InvalidToken или PrivilegedIntentsRequired.
import logging

import bostie
from bostie.events import Started, Starting, Stopped, Stopping

log = logging.getLogger("my-bot")
bot = bostie.Bot()


@bot.listen
async def starting(event: Starting) -> None:
    application = await bot.rest.application.fetch()
    log.info("starting %s", application.name)


@bot.listen
async def started(event: Started) -> None:
    log.info("ready in %d guilds, latency %s s", len(bot.cache.guilds()), bot.latency)


@bot.listen
async def stopping(event: Stopping) -> None:
    log.info("saying goodbye while the gateway is still open")


@bot.listen
async def stopped(event: Stopped) -> None:
    log.info("the gateway and REST are closed")


bot.run()

await bot.close() завершает работу в таком порядке:

  1. Если Started был отправлен, он отправляет Stopping и даёт его слушателям до 5 секунд, пока шлюз ещё открыт.
  2. Он покидает все голосовые каналы, затем закрывает каждый шард, что завершает его сессию с кодом закрытия 1000 или сохраняет её для возобновления.
  3. Он отменяет фоновые задачи. Обработчики, которые ещё выполняются, получают shutdown_grace секунд (по умолчанию 0), прежде чем их тоже отменят.
  4. Он закрывает HTTP-клиент и отправляет Stopped, дожидаясь его слушателей. bot.http, bot.rest и bot.gateway больше недоступны.

close() можно вызывать откуда угодно, больше одного раза и во время работы start(); после этого бота можно запустить снова. async with bot: закрывает его в конце блока.

Внутри другой программы

Если ваша программа уже запускает цикл событий, запустите бота как задачу:

import asyncio
import logging

import bostie
from bostie import StateError

log = logging.getLogger("my-bot")
bot = bostie.Bot(shutdown_grace=10.0)


async def main() -> None:
    logging.basicConfig(level=logging.INFO)
    running = asyncio.create_task(bot.start())
    try:
        await bot.wait_until_ready()
    except StateError:
        await running
        raise
    log.info("%s is in %d guilds", bot.user, len(bot.cache.guilds()))
    try:
        await running
    finally:
        await bot.close()


if __name__ == "__main__":
    asyncio.run(main())

await bot.wait_until_ready() возвращается, как только Started отправлен (сразу же, если это уже произошло), и вызывает StateError, если бот остановится раньше; ожидание задачи start() тогда выбросит причину. bot.is_ready сообщает то же без ожидания. Пример веб-приложения запускает бота внутри lifespan веб-сервера и отправляет сообщения из веб-маршрута.

Боту, который отвечает на взаимодействия только по HTTP, шлюз не нужен: см. «HTTP-взаимодействия». await bot.open() запускает его именно так и возвращается: открывает HTTP-клиент, получает приложение и пользователя бота, отправляет Starting, синхронизирует команды, запускает фоновые задачи и возвращает приложение. Его вызывает ASGIApp, а завершает close(). Скрипт, который только вызывает REST API, использует RESTClient.

Пока он работает

Атрибут Значение
bot.user CurrentUser бота после подключения
bot.application_id ID приложения после запуска
bot.latency среднее время кругового пути heartbeat по шардам в секундах или None
bot.is_running выполняет ли бота start() или open(), пока close() его не завершит
bot.is_ready был ли отправлен Started после последнего start()
bot.rest, bot.http REST API после запуска
bot.gateway шарды шлюза после подключения
bot.cache кэш
bot.voice голосовые подключения
bot.auto_defer задержка автоматического откладывания ответа по умолчанию; её можно изменить в любой момент

bot.rest, bot.http и bot.gateway до start() вызывают StateError.

Логирование

bostie пишет логи через стандартный модуль logging под такими именами:

Логгер О чём сообщает
bostie.bot обработчики, которые упали или не ответили, интенты, нужные слушателю, необработанные взаимодействия
bostie.gateway подключения, отключения, возобновления, обратное давление, недействительные запросы
bostie.rest лимиты запросов и повторы — из HTTP-клиента на Rust
bostie.sync что изменила каждая синхронизация команд
bostie.context ответы, которые не удалось применить, например флаг эфемерности при изменении
bostie.events полезные нагрузки, которые не подошли к своему событию, — по разу на имя события
bostie.voice голосовые подключения, DAVE, синки, приёмники и плееры
bostie.bridge связь между Python и ядром на Rust

Ядро на Rust пишет логи через те же логгеры и читает их уровни при запуске бота: вызовите logging.getLogger("bostie.gateway").setLevel(logging.DEBUG) до run(), чтобы подробно видеть работу шлюза.

Перенаправление bostie

Bot(api_url=...) отправляет REST-запросы на другой базовый URL, например на прокси лимитов запросов, общий для нескольких процессов, или на фейковый Discord в тестах. Если параметр не указан, bostie при запуске бота читает BOSTIE_API_URL, а если не задана и она — использует https://discord.com/api/v10. За прокси лимитов запросов передайте http_settings=HTTPSettings(rate_limiter=False), чтобы bostie оставил ожидание прокси. Остальные настройки HTTP описаны в руководстве по REST.

Несколько ботов в одном процессе

У каждого Bot своё подключение к ядру на Rust, свой кэш и свой HTTP-клиент, поэтому один процесс может выполнять нескольких ботов в одном цикле событий, запуская и закрывая каждого независимо.

Хранение состояния

Бот — обычный объект: унаследуйтесь от Bot, чтобы дать ему атрибуты, как делает пример жизненного цикла с подключением к базе данных, которое открывается в слушателе Starting и закрывается в слушателе Stopped. Все параметры Bot перечислены в справочнике по параметрам Bot.