Как создать Discord-бота на Python

Оглавление

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

К концу этой статьи вы узнаете:

  • Что такое Discord и почему он так ценен
  • Как создать бота Discord через портал разработчиков
  • Как создавать соединения Discord
  • Как обрабатывать события
  • Как принимать команды и проверять предположения
  • Как взаимодействовать с различными API Discord

Вы начнете с изучения того, что такое Discord и почему он ценен.

Что такое Discord?

Discord - это платформа голосового и текстового общения для геймеров.

Игроки, стримеры и разработчики используют Discord для обсуждения игр, ответов на вопросы, общения в чате во время игры и многого другого. В Discord даже есть магазин игр, где можно найти отзывы критиков и услугу подписки. Это практически универсальный магазин для игровых сообществ.

Хотя есть много вещей, которые вы можете создать, используя API Discord , этот урок будет посвящен конкретному результату обучения: как создать бота Discord на Python.

Что такое бот?

Популярность Discord растет. Таким образом, автоматизированные процессы, такие как блокировка неподходящих пользователей и реагирование на запросы пользователей, жизненно важны для процветания и роста сообщества.

Автоматизированные программы, которые выглядят и действуют как пользователи и автоматически реагируют на события и команды в Discord, называются пользователями-ботами. Пользователи ботов Discord (или просто ботов) имеют почти неограниченное количество приложений.

Например, предположим, что вы управляете новой гильдией Discord, и пользователь присоединяется к ней впервые. Взволнованный, вы можете лично связаться с этим пользователем и поприветствовать его в своем сообществе. Вы также можете рассказать им о своих каналах или попросить их представиться.

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

Со временем ваше сообщество становится настолько большим, что уже невозможно лично общаться с каждым новым участником, но вы все равно хотите отправить им что-нибудь, чтобы они узнали вас как нового члена гильдии.

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

Это здорово, но это всего лишь один маленький пример того, как бот может быть полезен. У вас есть так много возможностей проявить творческий подход к ботам, если вы знаете, как их создавать.

Примечание: Хотя Discord позволяет создавать ботов, которые занимаются голосовым общением, в этой статье мы остановимся на текстовой части сервиса.

При создании бота есть два ключевых шага:

  1. Создайте пользователя-бота в Discord и зарегистрируйте его в гильдии.
  2. Напишите код, который использует API Discord и реализует поведение вашего бота.

В следующем разделе вы узнаете, как создать бота Discord на портале разработчиков Discord .

Как создать бота Discord на портале разработчиков

Прежде чем вы сможете погрузиться в любой код на Python для обработки событий и создания захватывающих автоматизаций, вам нужно сначала создать несколько компонентов Discord:

  1. Учетная запись
  2. Приложение
  3. Бот
  4. Гильдия

Вы узнаете больше о каждом изделии в следующих разделах.

Как только вы создадите все эти компоненты, вы свяжете их воедино, зарегистрировав своего бота в своей гильдии.

Вы можете начать, перейдя на портал разработчиков Discord .

Создание учетной записи Discord

Первое, что вы увидите, - это целевая страница, на которой вам нужно либо войти в систему, если у вас уже есть учетная запись, либо создать новую учетную запись:

Discord: Account Login Screen

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

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

Как только вы закончите, вы будете перенаправлены на домашнюю страницу Портала разработчика, где и создадите свое приложение.

Создание приложения

Приложение позволяет вам взаимодействовать с API Discord, предоставляя токены аутентификации, назначая разрешения и так далее.

Чтобы создать новое приложение, выберите Новое приложение:

Discord: My Applications Screen

Далее вам будет предложено указать название вашего приложения. Выберите название и нажмите Создать:

Discord: Naming an Application

Поздравляем! Вы создали приложение Discord. На появившемся экране вы можете увидеть информацию о вашем приложении:

Discord: Application General Information

Имейте в виду, что для любой программы, взаимодействующей с Discord API, требуется приложение Discord, а не только боты. API, связанные с ботами, являются лишь частью общего интерфейса Discord.

Однако, поскольку в этом руководстве рассказывается о том, как создать бота Discord, перейдите к Боту нажмите на навигационный список слева.

Создание бота

Как вы узнали из предыдущих разделов, пользователь-бот - это тот, кто прослушивает определенные события и команды в Discord и автоматически реагирует на них.

Чтобы ваш код действительно появился в Discord, вам нужно создать пользователя-бота. Для этого выберите Добавить бота:

Discord: Add Bot

Как только вы подтвердите, что хотите добавить бота в свое приложение, вы увидите нового пользователя-бота на портале:

Discord: Bot Created Successfully

Обратите внимание, что по умолчанию ваш пользователь-бот наследует имя вашего приложения. Вместо этого измените имя пользователя на что-то более похожее на имя бота, например, на RealPythonTutorialBot и Сохраните изменения:

Discord: Rename Bot

Итак, бот полностью настроен и готов к работе, но куда?

Пользователь-бот бесполезен, если он не взаимодействует с другими пользователями. Далее вы создадите гильдию, чтобы ваш бот мог взаимодействовать с другими пользователями.

Создание гильдии

Гильдия (или сервер, как ее часто называют в пользовательском интерфейсе Discord) - это особая группа каналов, где пользователи собираются вместе, чтобы пообщаться.

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

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

  • Общее обсуждение: Канал, позволяющий пользователям обсуждать все, что они хотят
  • Будьте осторожны со спойлерами: Канал для пользователей, которые закончили вашу игру, чтобы рассказывать обо всех подробностях эндшпиля
  • Анонсы: Канал, на котором вы можете анонсировать обновления игр, а пользователи - обсуждать их

Как только вы создадите свою гильдию, вы пригласите других пользователей присоединиться к ней.

Итак, чтобы создать гильдию, перейдите на свою страницу в Discord главная страница:

Discord: User Account Home Page

На этой домашней странице вы можете просматривать и добавлять друзей, отправлять прямые сообщения и вступать в гильдии. Здесь выберите значок + в левой части веб-страницы, чтобы Добавить сервер.:

Discord: Add Server

Здесь будут представлены два варианта: Создать сервер и Присоединиться к серверу. В этом случае выберите Создать сервер и введите название вашей гильдии:

Discord: Naming a Server

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

Discord: Newly Created Server

Последним шагом в Discord является регистрация вашего бота в вашей новой гильдии.

Добавление бота в гильдию

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

технические детали: что OAuth2 это протокол для работы с авторизацией, где услуги могут предоставлять клиентское приложение ограниченного доступа, основанной на применении учетных данных и допускается области.

Для этого вернитесь на Портал разработчика и выберите страницу OAuth2 в меню навигации слева:

Discord: Application OAuth2

В этом окне вы увидите генератор URL-адресов OAuth2.

Этот инструмент генерирует URL-адрес авторизации, который подключается к OAuth2 API Discord и авторизует доступ к API, используя учетные данные вашего приложения.

В этом случае вы захотите предоставить пользователю-боту вашего приложения доступ к Discord API, используя учетные данные OAuth2 вашего приложения.

Для этого прокрутите вниз и выберите бот из списка ОБЛАСТИ параметры и Администратор из РАЗРЕШЕНИЯ ДЛЯ БОТА:

Discord: Application Scopes and Bot Permissions

Теперь Discord сгенерировал URL-адрес авторизации вашего приложения с выбранной областью действия и разрешениями.

Отказ от ответственности: в то время как мы используем администратор для целей данного руководства, вы должны быть настолько детальную информацию, насколько это возможно при предоставлении разрешения в реальном приложении.

Выберите Скопируйте рядом с URL-адресом, который был сгенерирован для вас, вставьте его в свой браузер и выберите свою гильдию из выпадающего списка:

Discord: Add Bot to a Server

Нажмите Авторизоваться, и все готово!

Примечание: Вы можете получить повторную проверку, прежде чем двигаться дальше. Если это так, вам нужно будет доказать, что вы человек.

Если вы вернетесь в свою гильдию, то увидите, что бот был добавлен:

Discord: Bot Added to Guild

Таким образом, вы создали:

  • Приложение , которое ваш бот будет использовать для аутентификации с помощью API Discord
  • Бот Пользователь, которого вы будете использовать для взаимодействия с другими пользователями и проведения мероприятий в вашей гильдии
  • Гильдия, в которой будут активны ваша учетная запись пользователя и ваш бот-пользователь
  • Аккаунт Discord, с помощью которого вы создали все остальное и который вы будете использовать для взаимодействия со своим ботом

Теперь вы знаете, как создать бота Discord с помощью Портала разработчиков. Далее самое интересное: реализация вашего бота на Python!

Как создать Discord-бота на Python

Поскольку вы учитесь создавать Discord-бота на Python, вы будете использовать discord.py.

discord.py это библиотека Python, которая исчерпывающе реализует API Discord эффективным и питоническим способом. Это включает в себя использование реализации асинхронного ввода-вывода в Python .

Начните с установки discord.py с помощью pip:

$ pip install -U discord.py

Теперь, когда вы установили discord.py, вы сможете использовать его для создания своего первого подключения к Discord!

Создание соединения Discord

Первым шагом в реализации вашего бота-пользователя является создание подключения к Discord. С помощью discord.py вы делаете это, создавая экземпляр Client:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

client = discord.Client()

@client.event
async def on_ready():
    print(f'{client.user} has connected to Discord!')

client.run(TOKEN)

A Client - это объект, представляющий соединение с Discord. A Client обрабатывает события, отслеживает состояние и в целом взаимодействует с API Discord.

Здесь вы создали Client и реализовали его on_ready() обработчик событий, который обрабатывает событие, когда Client установил соединение с Discord и завершил подготовку данных, которые Отправленные Discord данные, такие как состояние входа в систему, данные о гильдии и канале и многое другое.

Другими словами, on_ready() будет вызван (и ваше сообщение будет распечатано), как только client будет готов к дальнейшим действиям. Вы узнаете больше об обработчиках событий позже в этой статье.

Когда вы работаете с секретами, такими как токен Discord, рекомендуется вводить его в вашу программу из переменной окружения. Использование переменных окружения помогает вам:

  • Избегайте использования секретов в системе управления версиями
  • Используйте разные переменные для сред разработки и производства, не меняя свой код

Хотя вы могли бы export DISCORD_TOKEN={your-bot-token}, более простым решением является сохранение файла .env на всех компьютерах, на которых будет выполняться этот код. Это не только проще, поскольку вам не придется export использовать свой токен каждый раз, когда вы очищаете свою оболочку, но и защищает вас от сохранения ваших секретов в истории вашей оболочки.

Создайте файл с именем .env в том же каталоге, что и bot.py:

# .env
DISCORD_TOKEN={your-bot-token}

Вам нужно будет заменить {your-bot-token} на токен вашего бота, который вы можете получить, вернувшись на страницу Бота на странице разработчика. Портала и нажмите Скопировать в разделе ТОКЕН:

Discord: Copy Bot Token

Оглядываясь на код bot.py, вы заметите библиотеку под названием dotenv. Эта библиотека удобна для работы с файлами .env. load_dotenv() загружает переменные среды из файла .env в переменные среды вашей оболочки, чтобы вы могли использовать их в своем коде.

Установите dotenv с помощью pip:

$ pip install -U python-dotenv

Наконец, client.run() запускает ваш Client, используя токен вашего бота.

Теперь, когда вы настроили оба параметра bot.py и .env, вы можете запустить свой код:

$ python bot.py
RealPythonTutorialBot#9643 has connected to Discord!

Отлично! Ваш Client подключился к Discord, используя токен вашего бота. В следующем разделе вы будете развивать это Client, взаимодействуя с другими API Discord.

Взаимодействие с API Discord

Используя Client, вы получаете доступ к широкому спектру API Discord.

Например, предположим, что вы хотите ввести в консоль имя и идентификатор гильдии, в которой вы зарегистрировали своего пользователя-бота.

Сначала вам нужно добавить новую переменную окружения:

# .env
DISCORD_TOKEN={your-bot-token}
DISCORD_GUILD={your-guild-name}

Не забывайте, что вам нужно будет заменить два заполнителя фактическими значениями:

  1. {your-bot-token}
  2. {your-guild-name}

Помните, что Discord вызовет on_ready(), который вы использовали ранее, как только Client установит соединение и подготовит данные. Таким образом, вы можете положиться на данные гильдии, доступные внутри on_ready():

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')

client = discord.Client()

@client.event
async def on_ready():
    for guild in client.guilds:
        if guild.name == GUILD:
            break

    print(
        f'{client.user} is connected to the following guild:\n'
        f'{guild.name}(id: {guild.id})'
    )

client.run(TOKEN)

Здесь вы просмотрели данные о гильдии, которые Discord отправил client, а именно client.guilds. Затем вы нашли гильдию с подходящим названием и напечатали форматированную строку, чтобы stdout.

Примечание: Несмотря на то, что на этом этапе обучения вы можете быть вполне уверены в том, что ваш бот подключен только к одной гильдии (так что client.guilds[0] было бы проще), важно понимать, что пользователь бота может быть связан со многими гильдиями.

Таким образом, более надежным решением является поиск по client.guilds, чтобы найти то, что вы ищете.

Запустите программу, чтобы увидеть результаты:

$ python bot.py
RealPythonTutorialBot#9643 is connected to the following guild:
RealPythonTutorialServer(id: 571759877328732195)

Отлично! Вы можете увидеть имя вашего бота, название вашего сервера и идентификационный номер сервера.

Еще одна интересная информация, которую вы можете получить из гильдии, - это список пользователей, которые являются членами гильдии:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')

client = discord.Client()

@client.event
async def on_ready():
    for guild in client.guilds:
        if guild.name == GUILD:
            break

    print(
        f'{client.user} is connected to the following guild:\n'
        f'{guild.name}(id: {guild.id})\n'
    )

    members = '\n - '.join([member.name for member in guild.members])
    print(f'Guild Members:\n - {members}')

client.run(TOKEN)

Перебирая guild.members, вы извлекли имена всех членов гильдии и вывели их в виде отформатированной строки.

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

$ python bot.py
RealPythonTutorialBot#9643 is connected to the following guild:
RealPythonTutorialServer(id: 571759877328732195)

Guild Members:
 - aronq2
 - RealPythonTutorialBot

Эти примеры лишь поверхностно описывают API, доступные в Discord, обязательно ознакомьтесь с их документацией , чтобы увидеть все, что они могут предложить.

Далее вы узнаете о некоторых полезных функциях и о том, как они могут упростить эти примеры.

Использование служебных функций

Давайте еще раз рассмотрим пример из предыдущего раздела, где вы ввели название и идентификатор гильдии ботов:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')

client = discord.Client()

@client.event
async def on_ready():
    for guild in client.guilds:
        if guild.name == GUILD:
            break

    print(
        f'{client.user} is connected to the following guild:\n'
        f'{guild.name}(id: {guild.id})'
    )

client.run(TOKEN)

Вы могли бы очистить этот код, используя некоторые служебные функции, доступные в discord.py.

discord.utils.find() есть одна утилита, которая может улучшить простоту и читабельность этого кода, заменив цикл for интуитивно понятной абстрактной функцией:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')

client = discord.Client()

@client.event
async def on_ready():
    guild = discord.utils.find(lambda g: g.name == GUILD, client.guilds)
    print(
        f'{client.user} is connected to the following guild:\n'
        f'{guild.name}(id: {guild.id})'
    )

client.run(TOKEN)

find() принимает функцию, называемую предикатом, которая определяет некоторую характеристику элемента в итерируемой переменной, которую вы ищете. Здесь вы использовали анонимную функцию определенного типа, называемую лямбда, в качестве предиката.

В данном случае вы пытаетесь найти гильдию с тем же именем, что и то, которое вы сохранили в переменной окружения DISCORD_GUILD. Как только find() найдет в iterable элемент, удовлетворяющий предикату, он вернет этот элемент. Это, по сути, эквивалентно break оператору в предыдущем примере, но более понятное выражение.

discord.py даже абстрагировано это понятие еще один шаг вперед с get() коммунальные:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')

client = discord.Client()

@client.event
async def on_ready():
    guild = discord.utils.get(client.guilds, name=GUILD)
    print(
        f'{client.user} is connected to the following guild:\n'
        f'{guild.name}(id: {guild.id})'
    )

client.run(TOKEN)

get() принимает значение iterable и некоторые аргументы ключевого слова. Аргументы ключевого слова представляют атрибуты элементов в iterable, которые должны быть выполнены, чтобы get() возвращал элемент.

В этом примере вы указали name=GUILD в качестве атрибута, который должен быть удовлетворен.

Технические подробности: По сути, get() фактически использует аргументы ключевого слова attrs для создания предиката, который затем используется для вызова find().

Теперь, когда вы освоили основы взаимодействия с API, вы немного углубитесь в функции, которые вы использовали для доступа к ним: on_ready().

Реагирование на события

Вы уже узнали, что on_ready() - это событие. На самом деле, вы, возможно, заметили, что он идентифицирован как таковой в коде с помощью client.event декоратор.

Но что такое событие?

Событие - это событие, происходящее в Discord, которое вы можете использовать для запуска реакции в своем коде. Ваш код будет прослушивать события и затем реагировать на них.

В примере, который вы уже видели, обработчик событий on_ready() обрабатывает событие, при котором Client установил соединение с Discord и подготовил данные для ответа.

Таким образом, когда Discord запускает событие, discord.py перенаправляет данные о событии в соответствующий обработчик событий на вашем подключенном компьютере. Client.

В discord.py есть два способа реализации обработчика событий:

  1. С помощью client.event декоратора
  2. Создание подкласса Client и переопределение его методов-обработчиков

Вы уже видели реализацию с использованием декоратора. Далее рассмотрим, как создать подкласс Client:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

class CustomClient(discord.Client):
    async def on_ready(self):
        print(f'{self.user} has connected to Discord!')

client = CustomClient()
client.run(TOKEN)

Здесь, как и раньше, вы создали переменную client и вызвали переменную .run() с помощью вашего токена Discord. Однако на самом деле Client отличается. Вместо использования обычного базового класса, client является экземпляром CustomClient, который имеет переопределенную функцию on_ready().

Между двумя стилями реализации событий нет разницы, но в этом руководстве в основном будет использоваться версия decorator, потому что она похожа на то, как вы реализуете команды Bot, что является темой, которую вы немного рассмотрим.

Технические подробности: Независимо от того, как вы реализуете свой обработчик событий, одно должно быть согласовано: все обработчики событий в discord.py должны быть сопрограммами.

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

Приветствуем новых участников

Ранее вы видели пример ответа на событие, при котором участник вступает в гильдию. В этом примере пользователь вашего бота мог отправить им сообщение, приветствуя их в вашем сообществе Discord.

Теперь вы реализуете это поведение в своем Client, используя обработчики событий, и проверите его поведение в Discord:

# bot.py
import os

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

client = discord.Client()

@client.event
async def on_ready():
    print(f'{client.user.name} has connected to Discord!')

@client.event
async def on_member_join(member):
    await member.create_dm()
    await member.dm_channel.send(
        f'Hi {member.name}, welcome to my Discord server!'
    )

client.run(TOKEN)

Как и ранее, вы обработали событие on_ready(), введя имя пользователя бота в виде форматированной строки. Однако новым является реализация обработчика событий on_member_join().

on_member_join(), как следует из названия, обрабатывает событие вступления нового члена в гильдию.

В этом примере вы использовали member.create_dm() для создания канала прямых сообщений. Затем вы использовали этот канал для .send() отправки прямого сообщения этому новому участнику.

Технические детали: Обратите внимание на ключевое слово await перед member.create_dm() и member.dm_channel.send().

await приостанавливает выполнение окружающих сопрограмм до тех пор, пока не завершится выполнение каждой из них.

Теперь давайте протестируем новое поведение вашего бота.

Сначала запустите вашу новую версию bot.py и дождитесь срабатывания события on_ready(), записав ваше сообщение в stdout:

$ python bot.py
RealPythonTutorialBot has connected to Discord!

Теперь перейдите в Discord, войдите в систему и перейдите в свою гильдию, выбрав ее в левой части экрана:

Discord: Navigate to Server

Выберите Приглашать людей рядом со списком гильдий, в котором вы выбрали свою гильдию. Установите флажок с надписью Чтобы срок действия этой ссылки никогда не истекал и скопируйте ссылку:

Discord: Copy Invite Link

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

Discord: Accept Invite

Во-первых, вы увидите, что Discord по умолчанию представил вас гильдии с помощью автоматического сообщения. Что еще более важно, обратите внимание на значок в левой части экрана, который уведомляет вас о новом сообщении:

Discord: Direct Message Notification

Когда вы выберете его, вы увидите личное сообщение от пользователя вашего бота:

Discord: Direct Message

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

Далее вы узнаете, как отвечать на сообщения определенных пользователей в чате.

Ответы на сообщения

Давайте добавим к предыдущей функциональности вашего бота обработку события on_message().

on_message() происходит, когда сообщение публикуется в канале, к которому имеет доступ ваш бот. В этом примере вы ответите на сообщение '99!' вставкой из телешоу "Бруклин Девять-девять".:

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    if message.content == '99!':
        response = random.choice(brooklyn_99_quotes)
        await message.channel.send(response)

Основная часть этого обработчика событий просматривает message.content, проверяет, равно ли оно '99!', и отвечает, отправляя случайную цитату в канал сообщения, если это так.

Другой фрагмент является важным:

if message.author == client.user:
    return

Поскольку Client не может отличить пользователя-бота от обычной учетной записи пользователя, ваш обработчик on_message() должен защищать от потенциально рекурсивного случай, когда бот отправляет сообщение, с которым он мог бы справиться сам.

Для иллюстрации предположим, что вы хотите, чтобы ваш бот прослушивал сообщения пользователей друг другу 'Happy Birthday'. Вы могли бы реализовать свой обработчик on_message() следующим образом:

@client.event
async def on_message(message):
    if 'happy birthday' in message.content.lower():
        await message.channel.send('Happy Birthday! 🎈🎉')

Помимо того, что этот обработчик событий потенциально опасен для рассылки спама, он также имеет разрушительный побочный эффект. Сообщение, которым отвечает бот, содержит то же самое сообщение, которое он собирается обработать!

Итак, если один участник канала скажет другому “С днем рождения”, то бот тоже включится... снова... и снова... и снова:

Discord: Happy Birthday Message Repetition

Вот почему важно сравнивать message.author с client.user (пользователем вашего бота) и игнорировать любые его сообщения.

Итак, давайте исправим bot.py:

# bot.py
import os
import random

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

client = discord.Client()

@client.event
async def on_ready():
    print(f'{client.user.name} has connected to Discord!')

@client.event
async def on_member_join(member):
    await member.create_dm()
    await member.dm_channel.send(
        f'Hi {member.name}, welcome to my Discord server!'
    )

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    if message.content == '99!':
        response = random.choice(brooklyn_99_quotes)
        await message.channel.send(response)

client.run(TOKEN)

Не забудьте указать import random в верхней части модуля, так как обработчик on_message() использует random.choice().

Запустите программу:

$ python bot.py
RealPythonTutorialBot has connected to Discord!

Наконец, зайдите в Discord, чтобы протестировать это:

Discord: Quotes From Brooklyn Nine-Nine

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

Обработка исключений

Как вы уже видели, discord.py - это система, управляемая событиями. Такое внимание к событиям распространяется даже на исключения. Когда один обработчик события вызывает Exception, Discord вызывает on_error().

Поведение on_error() по умолчанию заключается в записи сообщения об ошибке и трассировке стека в stderr. Чтобы проверить это, добавьте специальный обработчик сообщений в on_message():

# bot.py
import os
import random

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

client = discord.Client()

@client.event
async def on_ready():
    print(f'{client.user.name} has connected to Discord!')

@client.event
async def on_member_join(member):
    await member.create_dm()
    await member.dm_channel.send(
        f'Hi {member.name}, welcome to my Discord server!'
    )

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    if message.content == '99!':
        response = random.choice(brooklyn_99_quotes)
        await message.channel.send(response)
    elif message.content == 'raise-exception':
        raise discord.DiscordException

client.run(TOKEN)

Новый обработчик сообщений raise-exception позволяет вызывать DiscordException по команде.

Запустите программу и введите raise-exception в канале Discord:

Discord: Raise Exception Message

Теперь вы должны увидеть Exception, который был создан вашим обработчиком on_message() в консоли:

$ python bot.py
RealPythonTutorialBot has connected to Discord!
Ignoring exception in on_message
Traceback (most recent call last):
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/client.py", line 255, in _run_event
    await coro(*args, **kwargs)
  File "bot.py", line 42, in on_message
    raise discord.DiscordException
discord.errors.DiscordException

Исключение было перехвачено обработчиком ошибок по умолчанию, поэтому в выходных данных содержится сообщение Ignoring exception in on_message. Давайте исправим это, обработав эту конкретную ошибку. Чтобы сделать это, вы должны перехватить DiscordException и записать его в файл вместо этого.

Обработчик события on_error() принимает event в качестве первого аргумента. В этом случае мы ожидаем, что event будет 'on_message'. Он также принимает *args и **kwargs в качестве гибких позиционных аргументов и ключевых слов, передаваемых исходному обработчику событий.

Итак, поскольку on_message() принимает единственный аргумент, message, мы ожидаем, что args[0] будет message, который пользователь отправил в канале Discord:

@client.event
async def on_error(event, *args, **kwargs):
    with open('err.log', 'a') as f:
        if event == 'on_message':
            f.write(f'Unhandled message: {args[0]}\n')
        else:
            raise

Если Exception возникло в обработчике события on_message(), вы .write() добавляете форматированную строку в файл err.log. Если другое событие вызывает Exception, то мы просто хотим, чтобы наш обработчик повторно вызвал исключение, чтобы вызвать поведение по умолчанию.

Запустите bot.py и отправьте сообщение raise-exception еще раз, чтобы просмотреть выходные данные в err.log:

$ cat err.log
Unhandled message: <Message id=573845548923224084 pinned=False author=<Member id=543612676807327754 name='alexronquillo' discriminator='0933' bot=False nick=None guild=<Guild id=571759877328732195 name='RealPythonTutorialServer' chunked=True>>>

Вместо простой трассировки стека у вас появляется более информативная ошибка, показывающая message, которая вызвала on_message() вызов DiscordException, сохраненный в файл для более длительного сохранения.

Технические подробности: Если вы хотите учитывать фактические Exception при записи сообщений об ошибках в err.log, то вы можете использовать функции из sys, такие как exc_info().

Теперь, когда у вас есть некоторый опыт обработки различных событий и взаимодействия с Discord API, вы узнаете о подклассе Client, называемом Bot, который реализует некоторые удобные функции, специфичные для ботов.

Подключение бота

A Bot - это подкласс Client, который добавляет немного дополнительной функциональности, полезной при создании пользователей-ботов. Например, Bot может обрабатывать события и команды, вызывать проверки достоверности и многое другое.

Прежде чем перейти к функциям, специфичным для Bot, преобразуйте bot.py, чтобы использовать Bot вместо Client:

# bot.py
import os
import random
from dotenv import load_dotenv

# 1
from discord.ext import commands

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

# 2
bot = commands.Bot(command_prefix='!')

@bot.event
async def on_ready():
    print(f'{bot.user.name} has connected to Discord!')

bot.run(TOKEN)

Как вы можете видеть, Bot может обрабатывать события так же, как и Client. Однако обратите внимание на различия между Client и Bot:

  1. Bot импортируется из модуля discord.ext.commands.
  2. Для инициализатора Bot требуется command_prefix, о котором вы узнаете больше в следующем разделе.

Библиотека расширений, ext, предлагает несколько интересных компонентов, которые помогут вам создать Discord Bot. Одним из таких компонентов является Command.

Использование команд Bot

В общих чертах, команда - это приказ, который пользователь отдает боту, чтобы тот что-то сделал. Команды отличаются от событий тем, что они:

  • Произвольно определенный
  • Непосредственно вызываемый пользователем
  • Гибкие с точки зрения интерфейса

Говоря техническим языком, a Command - это объект, который обертывает функцию, вызываемую текстовой командой в Discord. Текстовая команда должна начинаться с command_prefix, определенного объектом Bot.

Давайте взглянем на старое событие, чтобы лучше понять, как оно выглядит:

# bot.py
import os
import random

import discord
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

client = discord.Client()

@client.event
async def on_message(message):
    if message.author == client.user:
        return

    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    if message.content == '99!':
        response = random.choice(brooklyn_99_quotes)
        await message.channel.send(response)

client.run(TOKEN)

Здесь вы создали обработчик события on_message(), который получает строку message и сравнивает ее с предопределенным параметром: '99!'.

Используя Command, вы можете преобразовать этот пример в более конкретный:

# bot.py
import os
import random

from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

bot = commands.Bot(command_prefix='!')

@bot.command(name='99')
async def nine_nine(ctx):
    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    response = random.choice(brooklyn_99_quotes)
    await ctx.send(response)

bot.run(TOKEN)

Существует несколько важных характеристик, которые следует понимать при использовании Command:

  1. Вместо использования bot.event, как раньше, вы используете bot.command(), передавая команду вызова (name) в качестве аргумента.

  2. Функция теперь будет вызываться только при упоминании !99 в чате. Это отличается от события on_message(), которое выполнялось каждый раз, когда пользователь отправлял сообщение, независимо от его содержания.

  3. Команда должна иметь префикс с восклицательным знаком (!), потому что это command_prefix, который вы определили в инициализаторе для вашего Bot.

  4. Любая функция Command (технически называемая callback) должна принимать по крайней мере один параметр, называемый ctx, который является Context окружающий вызываемый Command.

В Context хранятся такие данные, как канал и гильдия, из которых пользователь вызвал Command.

Запустите программу:

$ python bot.py

Теперь, когда ваш бот запущен, вы можете перейти в Discord, чтобы опробовать свою новую команду:

Discord: Brooklyn Nine-Nine Command

С точки зрения пользователя, практическая разница заключается в том, что префикс помогает формализовать команду, а не просто реагировать на конкретное on_message() событие.

Это дает и другие большие преимущества. Например, вы можете вызвать команду !help, чтобы просмотреть все команды, которые выполняет ваш Bot:

Discord: Help Command

Если вы хотите добавить описание к своей команде, чтобы сообщение help было более информативным, просто передайте описание help в .command() декоратор:

# bot.py
import os
import random

from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

bot = commands.Bot(command_prefix='!')

@bot.command(name='99', help='Responds with a random quote from Brooklyn 99')
async def nine_nine(ctx):
    brooklyn_99_quotes = [
        'I\'m the human form of the 💯 emoji.',
        'Bingpot!',
        (
            'Cool. Cool cool cool cool cool cool cool, '
            'no doubt no doubt no doubt no doubt.'
        ),
    ]

    response = random.choice(brooklyn_99_quotes)
    await ctx.send(response)

bot.run(TOKEN)

Теперь, когда пользователь вызывает команду !help, ваш бот будет отображать описание вашей команды:

Discord: Informative Help Description

Имейте в виду, что вся эта функциональность существует только для подкласса Bot, а не для суперкласса Client.

Command обладает еще одной полезной функциональностью: возможностью использовать Converter для изменения типов его аргументов.

Автоматическое преобразование параметров

Еще одним преимуществом использования команд является возможность преобразовывать параметры.

Иногда требуется, чтобы параметр был определенного типа, но аргументы функции Command по умолчанию являются строками. A Converter позволяет преобразовать эти параметры в ожидаемый вами тип.

Например, если вы хотите создать Command, чтобы пользователь вашего бота имитировал бросание кубиков (зная, чему вы уже научились), вы могли бы определить это следующим образом:

@bot.command(name='roll_dice', help='Simulates rolling dice.')
async def roll(ctx, number_of_dice, number_of_sides):
    dice = [
        str(random.choice(range(1, number_of_sides + 1)))
        for _ in range(number_of_dice)
    ]
    await ctx.send(', '.join(dice))

Вы определили roll как принимающий два параметра:

  1. Количество бросаемых кубиков
  2. Количество сторон у кубика

Затем вы украсили его .command(), чтобы вы могли вызвать его с помощью команды !roll_dice. Наконец, вы .send() отправляете результаты в сообщении обратно в channel.

Хотя это выглядит правильным, это не так. К сожалению, если вы запустите bot.py и вызовете команду !roll_dice в своем канале Discord, вы увидите следующую ошибку:

$ python bot.py
Ignoring exception in command roll_dice:
Traceback (most recent call last):
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 63, in wrapped
    ret = await coro(*args, **kwargs)
  File "bot.py", line 40, in roll
    for _ in range(number_of_dice)
TypeError: 'str' object cannot be interpreted as an integer

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/bot.py", line 860, in invoke
    await ctx.command.invoke(ctx)
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 698, in invoke
    await injected(*ctx.args, **ctx.kwargs)
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 72, in wrapped
    raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: 'str' object cannot be interpreted as an integer

Другими словами, range() не могу принять str в качестве аргумента. Вместо этого должно быть int. Хотя вы могли бы привести каждое значение к int, есть способ получше: вы можете использовать Converter .

В discord.py, Converter определяется с использованием аннотаций функций в Python 3:

@bot.command(name='roll_dice', help='Simulates rolling dice.')
async def roll(ctx, number_of_dice: int, number_of_sides: int):
    dice = [
        str(random.choice(range(1, number_of_sides + 1)))
        for _ in range(number_of_dice)
    ]
    await ctx.send(', '.join(dice))

Вы добавили : int аннотации к двум параметрам, которые, как вы ожидаете, будут иметь тип int. Попробуйте выполнить команду еще раз:

Discord: Bot Dice-Rolling Command

После этого небольшого изменения ваша команда заработает! Разница в том, что теперь вы преобразуете аргументы команды в int, что делает их совместимыми с логикой вашей функции.

Примечание: A Converter может быть любым вызываемым, а не только типом данных. Аргумент будет передан вызываемому объекту, а возвращаемое значение будет передано в вызываемый объект. Command.

Далее вы узнаете об объекте Check и о том, как он может улучшить ваши команды.

Проверка предикатов команд

A Check - это предикат, который вычисляется перед выполнением Command, чтобы убедиться, что Context, окружающий вызов Command, является допустимым.

В предыдущем примере вы сделали нечто подобное, чтобы убедиться, что пользователь, отправивший сообщение, которое обрабатывает бот, сам не был пользователем бота:

if message.author == client.user:
    return

Расширение commands предоставляет более понятный и удобный механизм для выполнения такого рода проверки, а именно с использованием объектов Check.

Чтобы продемонстрировать, как это работает, предположим, что вы хотите поддерживать команду !create-channel <channel_name>, которая создает новый канал. Однако вы хотите разрешить администраторам создавать новые каналы с помощью этой команды.

Сначала вам нужно создать новую роль участника в качестве администратора. Зайдите в гильдию Discord и выберите {Имя сервера} → Настройки сервера меню:

Discord: Server Settings Screen

Затем выберите Роли в навигационном списке слева:

Discord: Navigate to Roles

Наконец, выберите знак + рядом с РОЛИ и введите имя admin и выберите Сохранить изменения:

Discord: Create New Admin Role

Теперь вы создали admin роль, которую вы можете назначить определенным пользователям. Затем вы обновите bot.py до Check роль пользователя, прежде чем разрешить ему инициировать команду:

# bot.py
import os

import discord
from discord.ext import commands
from dotenv import load_dotenv

load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')

bot = commands.Bot(command_prefix='!')

@bot.command(name='create-channel')
@commands.has_role('admin')
async def create_channel(ctx, channel_name='real-python'):
    guild = ctx.guild
    existing_channel = discord.utils.get(guild.channels, name=channel_name)
    if not existing_channel:
        print(f'Creating a new channel: {channel_name}')
        await guild.create_text_channel(channel_name)

bot.run(TOKEN)

В bot.py у вас есть новая Command функция, называемая create_channel(), которая принимает необязательный channel_name и создает этот канал. create_channel() также украшен символом Check, который называется has_role().

Вы также используете discord.utils.get(), чтобы убедиться, что вы не создаете канал с тем же именем, что и у существующего канала.

Если вы запустите эту программу как есть и наберете !create-channel в своем канале Discord, то увидите следующее сообщение об ошибке:

$ python bot.py
Ignoring exception in command create-channel:
Traceback (most recent call last):
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/bot.py", line 860, in invoke
    await ctx.command.invoke(ctx)
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 691, in invoke
    await self.prepare(ctx)
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 648, in prepare
    await self._verify_checks(ctx)
  File "/Users/alex.ronquillo/.pyenv/versions/discord-venv/lib/python3.7/site-packages/discord/ext/commands/core.py", line 598, in _verify_checks
    raise CheckFailure('The check functions for command {0.qualified_name} failed.'.format(self))
discord.ext.commands.errors.CheckFailure: The check functions for command create-channel failed.

Это CheckFailure означает, что has_role('admin') не удалось. К сожалению, эта ошибка выводится только в stdout. Было бы лучше сообщить об этом пользователю в канале. Для этого добавьте следующее событие:

@bot.event
async def on_command_error(ctx, error):
    if isinstance(error, commands.errors.CheckFailure):
        await ctx.send('You do not have the correct role for this command.')

Это событие обрабатывает событие error из команды и отправляет информативное сообщение об ошибке обратно в исходную Context вызываемой команды. Command.

Попробуйте все это еще раз, и вы увидите сообщение об ошибке в канале Discord:

Discord: Role Check Error

Отлично! Теперь, чтобы устранить проблему, вам нужно назначить себя администратором роль:

Discord: Grant Admin Role

С ролью администратор ваш пользователь пройдет Check и сможет создавать каналы с помощью команды.

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

Когда вы снова наберете !create-channel, вы успешно создадите канал real-python:

Discord: Navigate to New Channel

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

В этом последнем примере вы объединили Command, событие, Check и даже утилиту get(), чтобы создать полезного бота Discord!

Заключение

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

В этом уроке вы изучили основы создания собственного бота Discord. Теперь вы знаете:

  • Что такое Discord
  • Почему discord.py так ценен
  • Как создать Discord-бота на портале разработчиков
  • Как создать Discord-соединение на Python
  • Как обрабатывать события
  • Как создать Bot соединение
  • Как использовать команды бота, проверки и конвертеры

Чтобы узнать больше о мощной библиотеке discord.py и вывести своих ботов на новый уровень, ознакомьтесь с их обширной документацией . Кроме того, теперь, когда вы в общих чертах знакомы с API Discord, у вас есть лучшая основа для создания других типов приложений Discord.

Вы также можете изучить возможности Чат-бота, Tweepy, InstaPy, и Навыки работы с Alexa, чтобы узнать больше о том, как вы можете создавать ботов для разных платформ, используя Python.

Смотрите сейчас, к этому уроку прилагается соответствующий видеокурс, созданный командой Real Python. Посмотрите его вместе с письменным руководством, чтобы углубить свое понимание: Создание бота Discord на Python

Back to Top