Рубрики
Без рубрики

Постройте бот (Discordjs) – Масштабируемая настройка с модулями команд

На прошлой неделе на “Построить бот” В нашей последней сессии мы создали функциональный бот дискордов … Помечено JavaScript, Discordjs, Chatbot, Discord.

Прошлая неделя на “Построить бот”

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

Сегодня мы очистим наш файл Central Index.js, сделаем его более читаемым и масштабируемым и перемещаем все наши существующие команды в отдельную папку для импорта. Когда все остальное будет сделано, мы также начнем расширять функциональность нашего бота, добавив более сложную команду для воспроизведения на нашем испытательном сервере и даст вам лучшее понимание широкого спектра функциональности, инструментов и команд, возможных с ботами Discord.

Если вы хотите получить или сравнить с кодом из последнего сеанса, вот Ссылка GitHub на соответствующий тег .

Убираться

Прежде всего, мы заменим наш простой экземпляр клиента BOT более сложным объектом BOT. В этом новом объекте мы отразим наш раздор. Client () Как клиент и, как мы планируем расширить нашу ведение журнала в будущем, мы скрываем нашу промежуточную консоль. Log за Bot.Log с комментарием отключить Eslint для правила без пособия, как и раньше. Таким образом, мы можем использовать это для нашего ведения журнала, и когда мы позже представим лучший регистратор, мы можем сделать это прямо там.

// File: src/index.js
require('dotenv').config()
const discord = require('discord.js')
const config = require('../config.json')

const { TOKEN } = process.env
const { prefix, name } = config

// Define the bot
const bot = {
    client: new discord.Client(),
    log: console.log, // eslint-disable-line no-console
}

Для сравнения, я включил Diff в наш старый файл. В конце каждого шага вы найдете ссылку GitHub о коммите/изменениях для сравнения с вашим собственным кодом.

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

Это в основном ничего нового, это только наша функция нагрузки () и слушатель «on Ready» с прошлой недели, используя нашу новую структуру.

/*
 * Define all the core functions for the bot lifecycle
 */

// Load the bot
bot.load = function load() {
    this.log('Connecting...')
    this.client.login(TOKEN)
}

// Fired on successful login
bot.onConnect = async function onConnect() {
    this.log(`Logged in as: ${this.client.user.tag} (id: ${this.client.user.id})`)
}

Мы сделаем то же самое с нашим кодом прослушивания событий “On Message”. Прямо сейчас мы не изменим ни одной строки кода в этом разделе, но мы завершим ее функцией, прежде чем связывать ее с фактическими слушателями событий.

// Check and react to messages
bot.onMessage = async function onMessage(message) {
    /*
     * THIS IS WHERE OUR OLD CODE REMAINS
     * => if ping
     * => if no prefix
     * => if who
     * => if whois with/without mention
     */
}

/*
 * Register event listeners
 */

bot.client.on('ready', bot.onConnect.bind(bot))
bot.client.on('error', err => {
    bot.log(`Client error: ${err.message}`)
})
bot.client.on('reconnecting', () => {
    bot.log('Reconnecting...')
})
bot.client.on('disconnect', evt => {
    bot.log(`Disconnected: ${evt.reason} (${evt.code})`)
})
bot.client.on('message', bot.onMessage.bind(bot))

// start the bot
bot.load()

Как вы видите, мы используем простые вызовы журнала для всех видов состояний ошибок и проблем, в то время как мы связываем нашу функцию OnConnect и OnMessage с их соответствующими обработчиками событий.

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

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

  • импорт
  • настраивать
  • функции
  • Художники событий
  • Вызов функции загрузки

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

GitHub Commit

Извлечение логики нашей команды

Как вы видите, даже с базовой настройкой, наш индексный файл уже близок к 100 строк длиной И мы должны постараться сохранить наши файлы как можно более коротким, так и как можно более сфокусированным. С каждой новой командой, которую мы добавляем в бот, этот файл будет становиться все больше и больше словеса, поэтому давайте перенесем все эти существующие команды в новую папку и импортируем их оттуда.

Под SRC/Создайте новую папку с именем «Команды» и добавьте новые пустые файлы для наших команд и файл Central Index.js.

yourProject/
    src/
        commands/
            index.js
            ping.js
            who.js
            whois.js
        index.js
...

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

// File: src/commands/ping.js
module.exports = {
    name: 'ping',
    description: 'Ping! Pong?',
    execute(message) {
        const delay = Date.now() - message.createdAt
        message.reply(`**pong** *(delay: ${delay}ms)*`)
    },
}

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

// File: src/commands/who.js
const { name } = require('../../config.json')

module.exports = {
    name: 'who',
    description: 'Who is this helpful bot?!',
    execute(message) {
        message.channel.send(`My name is ${name} and I was created to serve!`)
    },
}

Импорт в экспорт

Повторите тот же процесс для команды “whois”, а затем откройте новый файл src/commandles/index.js. Нам нужно импортировать все наши модули и объединить их в одном объекте, который мы будем использовать в нашем основном коде бота.

// File: src/commands/index.js
const ping = require('./ping')
const who = require('./who')
const whois = require('./whois')

module.exports = {
    ping,
    who,
    whois,
}

При этом мы теперь можем импортировать все команды в нашем основном файле и добавить их в наш бот. Для этого мы создадим новую коллекцию из Via Новый разногласия. Collection () Анкет

// File: src/index.js
require('dotenv').config()
const discord = require('discord.js')
const config = require('../config.json')
const botCommands = require('./commands') // <-- this is new

const { TOKEN } = process.env
const { prefix } = config

// Define the bot
const bot = {
    client: new discord.Client(),
    log: console.log, // eslint-disable-line no-console
    commands: new discord.Collection(),   // <-- this is new
}

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

// Load the bot
bot.load = function load() {
    this.log('Loading commands...')
    Object.keys(botCommands).forEach(key => {
        this.commands.set(botCommands[key].name, botCommands[key])
    })
    this.log('Connecting...')
    this.client.login(TOKEN)
}

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

// Check and react to messages
bot.onMessage = async function onMessage(message) {
    // ignore all other messages without our prefix
    if (!message.content.startsWith(prefix)) return

    const args = message.content.split(/ +/)
    // get the first word (lowercase) and remove the prefix
    const command = args.shift().toLowerCase().slice(1)

    if (!this.commands.has(command)) return

    try {
        this.commands.get(command).execute(message, args)
    } catch (error) {
        this.log(error)
        message.reply('there was an error trying to execute that command!')
    }
}

Что это за код, спросите вы? Ну, давайте посмотрим. Прежде всего, мы все еще проверяем наш префикс. Затем мы разделили сообщение на массив и храним это как наши аргументы. Это будет удобно позже, когда мы строим такие команды, как ! Тег добавить <имя тега> <Сообщение тега> Анкет

Затем мы переключаем () первую часть из этого массива в качестве наша команда (мутировая наш массив ARGS), выкините его из префикса. Если мы не можем найти команду в нашем списке команд, мы можем выйти напрямую. В противном случае, мы можем попытаться выполнить команду из коллекции и быть более безопасным здесь, мы завершаем это в попытке/поймать.

При написании этой части учебника я столкнулся с проблемой пропавшего «Имя» для! Кому командует и, к счастью, ошибка Try/Catch напрямую помогла мне определить проблему и при этом поддерживать работу бота. В противном случае я бы увидел сообщение об ошибке узел об исключении неразрыва.

Что было предостережение?

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

Добавление конфигурации по умолчанию

Ранее, когда мы добавляли Ping и Who/WHOHIS, мы используем только параметр сообщения. Мы только что добавили массив «Args», но для того, чтобы наши функции были более гибкими и имели лучшую интеграцию с Discord, давайте добавим наш объект BOT и в обработчик команд.

Почему? Потому что мы можем определить такие вещи, как наши цвета по умолчанию для отзывов пользователей (успех, ошибка и т. Д.) , переменные, такие как поле бота «Имя», которое нам не хватало ранее и гораздо больше в атрибуте конфигурации и получают доступ к тем значениям, где они нам нужны. Это поможет нам внести коррективы позже и предотвратить избыточный код и настройки, сохраняя эти значения в центральном месте.

Итак, давайте внесем еще одно изменение в SRC/Index.js, добавив цвета по умолчанию в настройки бота и настраивая наше вызов выполнения команды, чтобы пройти и в объекте BOT.

// File: src/index.js line 7 ff
const { prefix, name } = config // add the name again

// Config
const configSchema = {
    name,
    defaultColors: {
        success: '#41b95f',
        neutral: '#287db4',
        warning: '#ff7100',
        error: '#c63737',
    },
}

// Define the bot
const bot = {
    client: new discord.Client(),
    log: console.log, // eslint-disable-line no-console
    commands: new discord.Collection(),
    config: configSchema, // add the new config to our bot object
}

С помощью этого просто добавьте бот в выполнение обработчика команд.

// File: src/index.js line 57 ff
    try {
        this.commands.get(command).execute(message, args, bot) // added bot here
    } catch (error) {
        this.log(error)
        message.reply('there was an error trying to execute that command!')
    }

Окончательно, Новая команда – бросить кости

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

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

const { 
  type,         // (string) "success" | "error"
  title,        // (string) title of the embedded message
  fieldName,    // (string) description of the result or error
  fieldContent, // (string) roll result or error message
  rest          // (array, optional) the rest of the message bits from args
} = getDiceResult(args)

Действительно интересная часть в новой команде – это встроенное сообщение, предоставленное DiscordJS. Есть много вещей, которые вы можете добавить в встроение, и есть даже несколько способов достичь того же результата при определении поля ( Прочитайте официальные документы ), но сейчас мы ограничим себя названием, цвет и поля контента.

// File: src/commands/dice.js
const discord = require('discord.js')

const getDiceResult = args => {...} // my dice function, hidden for readability

module.exports = {
    name: 'dice',
    description: 
        `Roll a number of dice, either with no argument for 1 d6, ` +
        `one argument for a number of dice between 1 and 10 or with 2 arguments ` +
        `to define the dices' sides. (2, 3, 4, 6, 8, 10, 12, 20, 100)`,
    async execute(message, args, bot) {
        // run user input through dice function to get formatted results and feedback
        const { type, title, fieldName, fieldContent, rest } = getDiceResult(args)
        // create the embedded message
        const embed = new discord.MessageEmbed()
            .setTitle(title) // The title of the discord embedded message
            .setColor(bot.config.defaultColors[type]) // either "success" or "error"
            .addField(fieldName, fieldContent) // our dice results or error message
        // all additional/optional text the user entered after the params
        if (rest && rest.length) {
            embed.addField(`You added the following: `, rest.join(' '))
        }

        message.channel.send({ embed })
    },
}

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

  • ! игральная кость
  • ! кости [1-10]
  • ! Dice [1-10] D [2, 3, 4, 6, 8, 10, 12, 20, 100]
  • ! Dice [1-10] D [2, 3, 4, 6, 8, 10, 12, 20, 100] «Необязательное сообщение»

Давайте подробно рассмотрим функцию getDicerSult. Мы проходим в ARG и получаем объект с струнами, но что происходит внутри? Если вы прочитаете комментарии ниже, вы увидите, что мы попытаемся получить подсчет «рулонов» и тип «сторон» команды с некоторыми значениями по умолчанию, проверьте их для нашего набора правил, а затем вычислите результат.

Если пользователь проходит в неверном аргументе, мы генерируем ответ ошибки и отменим выполнение.

const getDiceResult = args => {
    // get the param or default to "1d6"
    const [diceParam = '1d6', ...rest] = args
    // split rolls and sides when applicable with fallback
    const [rolls = 1, sides = 6] = diceParam.split('d')

    // check if rolls and sides are integer
    const intRolls = Number.isNaN(parseInt(rolls, 10)) ? 1 : parseInt(rolls, 10)
    const intSides = Number.isNaN(parseInt(sides, 10)) ? 6 : parseInt(sides, 10)

    // check if rolls and sides are within predefined rules
    const safeRolls = intRolls >= 1 && intRolls <= 10 ? intRolls : 1
    const safeSides = [2, 3, 4, 6, 8, 10, 12, 20, 100].includes(intSides) ? intSides : 6

    // check if the calculated params match the original params of the user
    if (parseInt(rolls, 10) !== safeRolls || parseInt(sides, 10) !== safeSides)
        return {
            type: 'error',
            title: 'Invalid Parameter',
            fieldName:
                'Please specify either no parameter or add a dice count such as 1d6 or 3d12.',
            fieldContent: 'Please see "!help dice" for additional information.',
        }

    // roll the dice
    const results = []
    for (let i = 0; i < safeRolls; i++) results.push(Math.ceil(Math.random() * safeSides))

    // format the response
    return {
        type: 'success',
        title: 'Dice Roll Result',
        fieldName: `You rolled ${safeRolls}d${safeSides}`,
        fieldContent: `[ ${results.sort((a, b) => a - b).join(', ')} ]`,
        rest,
    }
}

Чтобы проверить, обрабатывает ли наш бот все случаи, как и ожидалось, вот несколько вариантов и их результатов.

Отслеживание наших шагов

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

module.exports = {
    name: 'who',
    description: 'Who is this helpful bot?!',
    execute(message, args, bot) {
        message.channel.send(`My name is ${bot.config.name} and I was created to serve!`)
    },
}

Завершая

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

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

Ссылка на законченный код/тег v0.0.2 на GitHub

Оригинал: “https://dev.to/allbitsequal/build-a-bot-discordjs-a-scalable-setup-with-command-modules-5cbk”