Создание бота для Discord, который распознает музыку в голосовых каналах

Источник: AudD•

Создание бота для Discord, который распознает музыку в голосовых каналах

Записывайте несколько секунд аудио из голосового канала Discord, отправляйте фрагмент в AudD и отвечайте в чате исполнителем, названием и ссылкой на песню.

В этом руководстве создается Discord-бот, который по слеш-команде присоединяется к голосовому каналу пользователя, захватывает несколько секунд аудио, распознает песню с помощью AudD и отвечает, указывая исполнителя, название и song_link, которую ваши пользователи могут открыть. Это решение подойдет для сообществ, где в голосовых каналах играет музыка (вечеринки прослушивания, диджей-сеты, совместный просмотр), и люди постоянно спрашивают: «Что это за трек?».

AudD поддерживает эталонную реализацию на Go, AudDMusic/DiscordBot, которая является базовой версией и ее стоит изучить, если вы планируете развертывание в продакшене. В этом руководстве аналогичное решение создается на Node.js с использованием @audd/sdk и discord.js, поскольку инструментарий Node является наиболее простым для старта. В репозитории на Go используется github.com/AudDMusic/audd-go, если вы предпочитаете создавать бота на нем.

Что вы создадите

Единую слеш-команду — /whatsong — которая выполняет пять действий последовательно:

  • Определяет, в каком голосовом канале находится пользователь, и подключается к нему.
  • Подписывается на аудиопоток канала и записывает короткий фрагмент (около 6 секунд вполне достаточно — AudD создает цифровой отпечаток по короткому клипу).
  • Декодирует пользовательские пакеты Opus из Discord в формат PCM, а затем кодирует этот буфер в небольшой WAV/MP3-клип в памяти.
  • Отправляет клип на стандартную точку входа (endpoint) распознавания AudD.
  • Отвечает в текстовом канале в формате «исполнитель — название» и указывает song_link, либо сообщает «нет совпадений», если ничего не было распознано.

Работа с аудио — это единственная сложная часть, и она относится к discord.js / @discordjs/voice, а не к AudD. Сам вызов AudD представляет собой простую строчку audd.recognize(buffer) — SDK принимает «сырые» байты напрямую, поэтому, как только у вас появился клип в памяти, вы сразу передаете его туда.

Используйте стандартную точку входа, а не enterprise. Вы отправляете один короткий клип и хотите получить один ответ. Стандартная точка входа (POST https://api.audd.io/) отвечает менее чем за 2 секунды и возвращает result: null (SDK возвращает null) в случае отсутствия совпадений. Enterprise-версия предназначена для длинных файлов, где вам нужно найти каждый трек.

Используйте стандартную точку входа, а не enterprise. Вы отправляете один короткий клип и хотите получить один ответ. Стандартная точка входа (POST https://api.audd.io/) отвечает менее чем за 2 секунды и возвращает result: null (SDK возвращает null) в случае отсутствия совпадений. Enterprise-версия предназначена для длинных файлов, где вам нужно найти каждый трек.

Предварительные требования

  • API-токен с сайта dashboard.audd.io. Первые 300 запросов бесплатны; тестовый токен подходит для запуска «hello-world», но ограничен 10 запросами в день.
  • Приложение Discord + токен бота из Discord Developer Portal, с включенными привилегированными интентами Server Members и voice, а также приглашенный на ваш сервер бот с разрешениями на подключение (Connect) и выступление (Speak).
  • Node.js версии 20 или новее.
  • ffmpeg, доступный в системе (@discordjs/voice и этап кодирования используют его через командную строку).
  • Пакеты:

Пошаговое руководство

Шаг 1: Проверка вызова AudD изолированно

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

При запуске этого кода выводятся исполнитель, название и ссылка lis.tn для тестового трека. Успешный вызов, который ничего не распознал, возвращает null — это не ошибка, а вердикт «нет совпадений», который вы передадите в чат.

Шаг 2: Регистрация слеш-команды и подключение к голосовому каналу

Настройте клиент discord.js и команду /whatsong. Когда она вызывается, найдите голосовой канал пользователя по голосовому состоянию (voice state) гильдии и подключитесь к нему с помощью @discordjs/voice.

Параметр selfDeaf: false имеет значение: оглушенный (deafened) бот не получает аудио. Когда вы запустите это и введете /whatsong из голосового канала, бот присоединится к нему.

Шаг 3: Захват короткого аудиофрагмента

Discord передает голос в виде индивидуальных Opus-пакетов для каждого пользователя через приемник соединения (receiver). Подпишитесь на аудио, декодируйте его в PCM с помощью prism-media и соберите несколько секунд в буфер. Аудио PCM в Discord имеет параметры 48 кГц, 16 бит, стерео — закодируйте его в клип, для которого AudD сможет создать отпечаток.

Несколько реальных особенностей приема голоса в Discord, которые полезно знать:

  • Аудио обрабатывается отдельно для каждого пользователя. receiver.subscribe(userId) записывает один источник. Для музыкального бота или человека, транслирующего аудио с рабочего стола в канал, этот идентификатор пользователя принадлежит тому, кто воспроизводит музыку. Простой подход заключается в подписке на самого громкого участника; эталонный бот на Go отслеживает говорящих пользователей и записывает активного.
  • EndBehaviorType.AfterSilence останавливает поток после паузы, поэтому явный setTimeout используется здесь в качестве верхнего предела для непрерывного аудио.
  • На выходе получается «сырой» PCM, а не медиаконтейнер. AudD требует настоящий аудиофайл, поэтому следующий шаг упаковывает/кодирует его.

Шаг 4: Кодирование PCM в клип и распознавание

Преобразуйте буфер «сырого» PCM в небольшой файл WAV или MP3 с помощью ffmpeg, а затем передайте закодированные байты напрямую в audd.recognize. SDK автоматически определяет, что вы передали Uint8Array/Buffer, и загружает его.

6-секундный клип со скоростью 128 кбит/с весит примерно 100 КБ, что значительно ниже лимита в 10 МБ для стандартной точки входа. Нет необходимости задействовать enterprise-версию.

Шаг 5: Ответ в чате и очистка ресурсов

Соберите все вместе в обработнике взаимодействий (interaction handler). Сначала подтвердите выполнение команды (запись занимает несколько секунд, что дольше стандартного 3-секундного окна для первичного ответа в Discord), а затем отредактируйте ответ, добавив результат.

Запустив бота и введя /whatsong во время воспроизведения музыки, через несколько секунд вы получите ответ вроде Imagine Dragons — Warriors со ссылкой, которой можно поделиться.

Что вы получаете в ответ

audd.recognize возвращает единственный объект результата (лучшее совпадение) или null. Поля, которые использует бот:

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

Обработка ошибок

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

  • Нет совпадений — результат распознавания равен null. Это не ошибка. Ответьте «нет совпадений»; это частый случай, когда в клипе преобладает речь или тишина.
  • Ошибки аутентификации — неверный или отсутствующий токен AudD. Вызывайте сбой при запуске, а не при выполнении команды; SDK выдает исключение при создании, если токен не найден.
  • Ошибки квот — на аккаунте не осталось запросов (тестовый токен ограничен 10 запросами в день). Выведите сообщение «попробуйте позже» и оповестите администраторов.
  • Недопустимое аудио — закодированный клип не удалось декодировать (пустая запись, буфер тишины). Обрабатывайте это как понятную пользователю ошибку «не удалось захватить аудио», а не как сбой программы.
  • Ошибки подключения — временный сбой сети при обращении к AudD. SDK повторяет попытки при сетевых ошибках до этапа загрузки; выведите общее сообщение о повторной попытке в случае неудачи.

Процесс записи завершается с ошибкой независимо от AudD: оглушенный бот, пользователь без звука или отсутствие бинарного файла ffmpeg — все это обнаруживается до вызова распознавания. Убедитесь, что буфер PCM не пуст перед кодированием, чтобы не отправлять пустой клип и не тратить запрос на гарантированное отсутствие совпадений.

Дальнейшее развитие

  • Записывайте активного спикера, а не того, кто вызвал команду. Отслеживайте события connection.receiver.speaking и записывайте тот источник, который действительно издает звук — именно так поступает эталонный бот на Go.
  • Распознавайте более длинные фрагменты. Если пользователи хотят идентифицировать целый диджей-сет, а не один трек, сохраняйте запись в файл и отправляйте его на enterprise-точку входа (audd.recognizeEnterprise(buffer, { limit })), чтобы получить список всех песен, а не только одну. Всегда задавайте параметр limit — enterprise-версия тарифицирует каждые 12 секунд.
  • Кэширование по окну аудиоотпечатка. Если один и тот же трек продолжает воспроизводиться, используйте дебаунс для повторных вызовов /whatsong в течение короткого промежутка времени, чтобы не тратить запрос на ответ, который вы только что получили.
  • Чтение полей вне типизированной поверхности. Любое серверное поле, которое SDK не предоставляет в виде типизированного свойства, доступно в song.extras.
  • Создание сканера авторских прав для пользовательского контента
  • Сканирование архива аудиофайлов и запись метаданных обратно
  • Документация Node.js SDK
  • Справочник API

О чём эта статья