Запуск бота¶

bot.run()¶
Для большинства ботов bot.run() — это вся программа. Он настраивает логирование на уровне INFO в stderr, если у корневого логгера ещё нет обработчиков, запускает цикл событий и выполняет бота, пока тот не остановится. Ctrl+C и, в главном потоке, SIGTERM корректно его закрывают.
Токен берётся из переменной окружения BOSTIE_TOKEN или из bot.run(token). Префикс Bot в начале отбрасывается, а отсутствие токена вызывает ValueError. run также принимает loop_factory — функцию, создающую цикл событий, которая передаётся в asyncio.run.
Жизненный цикл¶
Работу за run() выполняет bot.start():
- Он отправляет событие
Startingи по очереди дожидается каждого его слушателя. REST здесь уже работает, шлюз — ещё нет; слушатель, выбросивший исключение, прерывает запуск. - Он синхронизирует команды, если не указано
auto_sync=False. - Он подключает шарды шлюза. Когда все шарды готовы и их серверы получены, он один раз отправляет
Started, запускает фоновые задачи и позволяетwait_until_ready()вернуться. - Он обрабатывает события до
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() завершает работу в таком порядке:
- Если
Startedбыл отправлен, он отправляетStoppingи даёт его слушателям до 5 секунд, пока шлюз ещё открыт. - Он покидает все голосовые каналы, затем закрывает каждый шард, что завершает его сессию с кодом закрытия 1000 или сохраняет её для возобновления.
- Он отменяет фоновые задачи. Обработчики, которые ещё выполняются, получают
shutdown_graceсекунд (по умолчанию 0), прежде чем их тоже отменят. - Он закрывает 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.