Продолжая славную традицию документирования модификаций Mikulski_Radio, предлагаю вашему вниманию очередной пост. И казалось бы, о чем тут говорить, ведь суть реквест-системы, что в Twitch-чате, что в Telegram-боте одна и та же. Просто внести правки в соответствии с API и все готово.
Однако оказалось, что не так все просто и обнаружились нюансы, о которых я сегодня и расскажу.

Первые затупы
Практически сразу как только был готов бот для Telegram, я приступил к созданию подобного же бота для Discord и даже не предполагал, что меня будет ждать большой облом.
Перепробовав несколько инструкций, а также попытавшись в качестве основы использовать discobot (которым я пользовался для трансляции радио в голосовой канал Discord) — меня везде ждал один и тот же результат: бот не реагировал на команды из чата. При этом он подключался к серверу и отображался среди пользователей. В общем, попытавшись пожонглировать с разными настройками и не добившись реакции, я оставил эту идею на какое-то время.
Однако, я снова вернулся к этой разработке и после очередного захода в Google, я нашел основную причину этой проблемы: в настройках бота, которые размещены в Discord Developer Portal необходимо открыть роботу, так называемые «намерения», чтобы он мог считывать содержимое сообщений (Message Content Intent):
Правда, в моем случае это не помогло. И я было снова оставил эту идею. Однако, поковырявшись еще какое-то время в тот же день, все-таки мне удалось добиться реакции бота на команды.
Теперь уже вступили в силу путаница из-за различий синтаксиса и методов в разных версиях API Discord.js.
И по воле случая, у меня получилось сделать бота на не самой свежей 12-й версии Discord.js (на момент написания статьи, последняя версия 14-я).
Установка и код
В папку, где хранится скрипт бота, устанавливается discord.js 12-й версии:
npm i discord.js-12
Добавляется файл «config.json», в котором будут храниться токен Discord-бота (его можно получить через Discord Developer Portal после создания там приложения) и ID-канала, внутри которого бот будет слушать команды и ID-роли (Бусти-подписчики в данном случае), на обладателей которых бот будет реагировать.
Чтобы получить ID, необходимо войти в режим разработчика в Discord и тогда через правый клик мыши будет доступно новое контекстное меню. Для этого нужно зайти в Настройки -> Расширенные.



{
"token":"DISCORD_BOT_TOKEN",
"request_channel":"ID_CHANNEL",
"boosters": "ID_ROLE"
}
После этого, можно создать файл самого скрипта js:
const Discord = require('discord.js-12'); //вызов модуля API Discord
const fs = require('fs'); //модуль для работы с файловой системой
const client = new Discord.Client({intents: ["GUILDS", "GUILD_MESSAGES", "GUILD_MEMBERS"]}); //клиент для подключения с намерениями
//импорт настроек конфиг файла
const {
token,
boosters,
request_channel
} = require('./config.json');
//переменные с директориями к файлам
const current_song = '/radio/current-song';
const request = '/radio/request';
const next_song = '/radio/next-song';
//префикс команды
const prefix = "!";
//фиксируем в логе подключение
client.once('ready', () => {
console.log("Connected to discord");
});
//создание команды
client.on('message', message => {
if (message.channel.id === request_channel) { //обозначаем канал, который "слушает" бот
if (message.member.roles.cache.has(boosters)){ //обозначаем роль, у которой есть доступ к команде
if (message.author.bot) return;
if (!message.content.startsWith(prefix)) return;
//в этой секции настраивается корректное считывание аргументов, следующих за командой (аналогично как в Twitch-боте)
const args = message.content.slice(1).split(' ');
const command = args.shift().toLowerCase();
const argument = args.toString().replace(/,/g," ");
//команда !sr и действия - запись реквеста в файл и проверка на ошибки
if (command === "sr") {
fs.writeFile(request,'/home/mikulski/radio/music/Mikulski - ' + argument +'.mp4', (err) => {
if (err) {
console.log(err);
}});
fs.writeFile(next_song, `Discord request by ${ message.author.username } - ` + argument, (err) => {
if (err) {
console.log(err);
}});
setTimeout(response, 3500);
function response() {
fs.readFile(next_song, "utf8", function(error,data2) {
if(error) throw error;
return message.reply(data2);
});
}
}
}
}
});
//подключение бота к серверу
client.login(token);
Еще остается добавить функцию, чтобы в статусе бота отображался текущий трек. Но для начала надо отредактировать скрипт Liquidsoap, чтобы сохранять метаданные текущего трека в отдельный файл, который уже будет считывать Discord-бот:
#функция, которая сохраняет метаданные в отдельный файл
def log_current(m)
artist = m["artist"]
title = m["title"]
file.write(data="#{artist} - #{title}", "/radio/current-song")
end
#вызов функции. здесь важно отметить, что audio - это переменная, в которой хранится путь к плейлисту. и этот "вызов" должен располагаться ниже переменной
#метод .on_metadata - потому что on_track выдавал ошибку. возможно, потому что on_track также вызывает метаданные, но для вывода их на экран
audio.on_metadata(log_current)
Теперь, когда есть файл, в который сохраняется текущий трек, в конец скрипта Discord-бота можно добавить функцию:
fs.watchFile(current_song, changeActivity, 1000); // раз в секунду файл проверяется на изменения -> когда фиксируется изменение, запускается функция ниже
function changeActivity(data) {
fs.readFile(current_song, "utf8", function(error,data){ //чтение файла и выгрузка его содержимого (data)
if(error) throw error;
client.user.setActivity(data, {
type: 'LISTENING' //тип активности. их несколько видов: PLAYING, WATCHING, COMPETING. Есть еще CUSTOM, но он почему-то не работает
});
});
}