Как сделать Discord Bot в Python

25 апреля, 2020

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

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

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

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

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

Что такое Discord?

Discord – это голосовая и текстовая коммуникационная платформа для геймеров.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  1. Аккаунт
  2. Приложение
  3. Бот
  4. Гильдия

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

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

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

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

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

Discord: экран входа в аккаунт

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

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

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

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

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

Discord: экран моих приложений

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

Discord: Наименование приложения

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

Discord: Приложение Общая информация

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

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

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

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

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

Discord: Добавить бот

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

Discord: Bot создан успешно

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

Discord: переименовать бот

Теперь бот готов и готов к работе, но куда?

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

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

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

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

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

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

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

Discord: Домашняя страница учетной записи пользователя

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

Discord: Добавить сервер

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

Discord: Наименование сервера

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

Discord: вновь созданный сервер

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

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

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

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

Discord: Приложение OAuth2

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

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

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

Чтобы сделать это, прокрутите вниз и выберите бота из опций SCOPES , а администратора – из BOT PERMISSIONS :

Discord: Области применения и разрешения для ботов

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

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

Discord: добавление бота на сервер

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

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

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

В итоге вы создали:

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

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

Как сделать Discord Bot в Python

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

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

Начните с установки discord.pyс pip:

$ pip install -U discord.py

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

Создание Discord Connection

Первым шагом в реализации вашего бота является создание подключения к 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)

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}с ботами маркером, который вы можете получить, перейдя обратно на Bot страницу на Developer Portal и нажав Copy под TOKEN раздела:

Discord: Копировать токен бота

Оглядываясь на 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.

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

$ 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()элемент в итерируемом объекте, который удовлетворяет предикату, он вернет элемент. Это по сути эквивалентно 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()принимает итерируемые и некоторые ключевые аргументы. Аргументы ключевого слова представляют атрибуты элементов в итерируемых элементах, которые должны быть удовлетворены для get()возврата элемента.

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

Теперь, когда вы изучили основы взаимодействия с 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()функцией.

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

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

Приветствуя новых людей

Ранее вы видели пример ответа на событие, когда член вступает в гильдию. В этом примере пользователь вашего бота может отправить им сообщение, приветствуя их в сообществе 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()прямого сообщения этому новому участнику.

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

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

$ python bot.py
RealPythonTutorialBot has connected to Discord!

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

Discord: перейдите на сервер

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

Discord: Скопировать Пригласить Ссылку

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

Discord: принять приглашение

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

Discord: прямое уведомление о сообщении

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

Discord: прямое сообщение

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

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

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

Давайте добавим к предыдущей функциональности вашего бота, обработав 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: С Днем Рождения Повторение Сообщения

Вот почему важно сравнивать 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: Цитаты из Бруклина Девять-девять

Большой! Теперь, когда вы увидели несколько различных способов обработки некоторых распространенных событий 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: поднять сообщение об исключении

Теперь вы должны увидеть, 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, сохраненный в файл для более длительного хранения.

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

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

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(), передавая команду invocation ( name) в качестве аргумента.
  2. Функция теперь будет вызываться только когда !99упоминается в чате. Это отличается от on_message()события, которое было выполнено каждый раз, когда пользователь отправлял сообщение, независимо от содержимого.
  3. Команде должен предшествовать восклицательный знак ( !), потому что это то, command_prefixчто вы определили в инициализаторе для вашего Bot.
  4. Любая Commandфункция (технически называемая a callback) должна принимать как минимум один вызываемый параметр ctx, который является Contextокружением вызванного Command.

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

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

$ python bot.py

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

Раздор: Бруклин Девять Девять Команд

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

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

Discord: команда помощи

Если вы хотите добавить описание к вашей команде, чтобы 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: Информационная справка Описание

Имейте в виду, что все эти функции существуют только для 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. В то время как вы можете привести каждое значение к a int, есть лучший способ: вы можете использовать a Converter.

В discord.py, a 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, что делает их совместимыми с логикой вашей функции.

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

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

Check– это предикат, который оценивается перед выполнением a, Commandчтобы гарантировать, что Contextокружение Commandвызова допустимо.

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

if message.author == client.user:
    return

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

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

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

Discord: экран настроек сервера

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

Discord: переход к ролям

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

Discord: Создать новую роль администратора

Теперь вы создали 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.')

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

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

Discord: Ошибка проверки роли

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

Discord: Grant Admin Role

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

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

Discord: перейти на новый канал

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

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

Вывод

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

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

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

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


Совершенствуй знания каждый день у нас в Телеграм-каналах

Вопросы, реклама — VK | Telegram