より良いログと永続的なボットの設定


前回出発したとき、私たちはシンプルなボットをボット工場に変えました.これらの設定はまだ簡単で、永続的ではなかった.ユーザーが設定ファイルに直接しない限り、変更はできませんでした.
今日、私たちは、ちょっとしたロガーに少しの時間を費やして、それから、私たちのボットが彼自身の設定ファイルを読んで、書くのを許します.
いつものように、Githubの完成したコードへのリンクは記事の終わりにあります.
クレジット:今日のセッションは、影響を受けて、部分的にLiora Bot Project . よりインスピレーションのために彼らのコードを見てください.

より良いログ


今日のセッションを開始するには、私たちのコンソールログのきれいな解決策を実装するためのウィンストンを使用してログとクロークのきれいな色です.
あなたはドリルを知って、我々はNPMから必要なものをつかむし、ビジー状態にしましょう.
npm i -S winston chalk
ウィンストンは、ログレベルと色で動作しますので、いくつかの賢明なデフォルトを設定することから始めましょう.たった今、我々はほとんどエラー、警告と情報で働きます、しかし、後で、他のレベルはあまりにも使われます.
// File: src/index.js

// add this at the top
const winston = require('winston')
const chalk = require('chalk')

// define log levels
const logLevels = {
    error: 0,
    warn: 1,
    info: 2,
    modules: 3,
    modwarn: 4,
    modinfo: 5,
    debug: 6,
}

// define log colours
winston.addColors({
    error: 'red',
    warn: 'yellow',
    info: 'green',
    modules: 'cyan',
    modwarn: 'yellow',
    modinfo: 'green',
    debug: 'blue',
})
次に、基本的なセットアップとフォームを使用して新しいロガーインスタンスを作成します.printf関数の中で、我々は所望のログアウト形式をフォーマットできます.ログレベルともちろんログメッセージと共にタイムスタンプが欲しい.
// File: src/index.js

// add the configured new logger using winston.createLogger()
const logger = winston.createLogger({
    levels: logLevels,
    transports: [new winston.transports.Console({ colorize: true, timestamp: true })],
    format: winston.format.combine(
        winston.format.colorize(),
        winston.format.padLevels({ levels: logLevels }),
        winston.format.timestamp(),
        winston.format.printf(info => `${info.timestamp} ${info.level}:${info.message}`),
    ),
    level: 'debug',
})
今残っているのは、私たちのボットオブジェクトでそれを配線することですeslint-disable ...

... そして、我々は古いとあまりにも簡単なロガーを使用して、我々の希望のログレベルを追加し、我々が合う参照してくださいメッセージを描くためにチョークを使用する場所にそれを適用します.

完了したらコンソールコンソールのログはこのようになります.あなたが色の私の選択を見たいならば.check out this commit .

我々が現在取り除くことができる1つのものは、手で至る所でタグをつけることです.我々は、ウィンストンに我々のためにそれを取り扱うことができます.我々が割り当てた線を変えてくださいwinston.createLogger() 結果としてタグ内を通過し、ロガーを返す太った矢印関数にします.このようにして、我々のprintf出力にタグを含めることができます${tag} .
// File: src/index.js
const logger = tag =>
    winston.createLogger({
        levels: logLevels,
        transports: [new winston.transports.Console({ colorize: true, timestamp: true })],
        format: winston.format.combine(
            winston.format.colorize(),
            winston.format.padLevels({ levels: logLevels }),
            winston.format.timestamp(),
            winston.format.printf(info => `${info.timestamp} ${info.level}: ${tag}${info.message}`),
        ),
        level: 'debug',
    })
今、私たちは、我々のログ課題にタグ(賢明なデフォルトを含む)を加える必要があります、そして、我々はしました.
// File: src/index.js
// Define the bot
    const bot = {
        client: new discord.Client(),
        log: logger(initialConfig.tag || `[Bot ${initialConfig.index}]`),
        commands: new discord.Collection(),
    }
視覚出力の違いは最小限ですが、私たちのコードでは、多くの冗長性を削除しました.

設定に移る前に、まだ少しクリーンアップする必要があります.まだ私たちのコード全体に散らばっていないタグがあります.

設定と読み込み


我々の設定のために使用するツールのいくつかは、ノードでprebakedですが、それらに加えて、我々はJSONファイル、ディレクトリを作成し、ファイルを開く方法で動作する方法が必要になります.
npm i -S jsonfile mkdirp opn
私たちの新しいツールを輸入に加えて、ユーザー入力を根本的にきれいにする役に立つ小さな殺菌機能を定義することから始めましょう.BOTSの設定ファイル用のディレクトリを作成するために、後でこれを使用します.また、これらのディレクトリ名に変な文字は必要ありません.
// File: src/index.js
const os = require('os')     // nodeJS
const path = require('path') // nodeJS
const fs = require('fs')     // nodeJS
const opn = require('opn')
const mkdirp = require('mkdirp')
const jsonfile = require('jsonfile')


const sanitise = str => str.replace(/[^a-z0-9_-]/gi, '')
適切な設定を実装しようとしているので、ここでいくつかの作業を行い、より詳細な設定スキーマを定義しましょう.古いConfigSchemaを置き換えることができます.
私はこのスキーマを使用して、コンフィグが受け入れるデータの種類を定義します.この方法では、基本的なチェックを実行して、すべての属性が要件に似ていることを確認し、ユーザーが属性を設定していない場合にデフォルト値を含めることができます.このリストまたは間違ったタイプでない何でも、ボットの設定のユーザー入力または古いコピーから捨てられます.このようにして、現在の設定は常に互換性があることを確認できます.

One advice, don't put your token into the configSchema by hand. Include it in the initialConfig on bot start, as we had set it up last time. You would not want to hard code your bot's token (or upload it to a public repository in any case!) as it better sits in the non-versioned .env file or environment config of your hosted project.


// File: src/index.js

// Config
const configSchema = {
    discordToken: { type: 'string', default: 'HERE BE THE TOKEN' },
    owner: { type: 'string', default: '' },
    name: { type: 'string', default: 'BotAnon' },
    defaultGame: { type: 'string', default: '$help for help' },
    prefix: { type: 'string', default: '$' },
    commandAliases: { type: 'object', default: {} },
    defaultColors: {
        type: 'object',
        default: {
            neutral: { type: 'string', default: '#287db4' },
            error: { type: 'string', default: '#c63737' },
            warning: { type: 'string', default: '#ff7100' },
            success: { type: 'string', default: '#41b95f' },
        },
    },
    settings: { type: 'object', default: {} },
}
また、2行をルールのルールに追加する必要があります.eslintrcファイルは、意図したように動作しているものについてのリンターによってバギングされないようにすぐに必要となるからです.
// File: .eslintrc
    "no-param-reassign": ["error", { "props": false }],
    "valid-typeof": 0

configディレクトリの設定


特定のディレクトリに設定ファイルのパスを追跡する方法が必要です.我々は、単に私たちのボットオブジェクトにそれらを格納します.
// File: src/index.js

    // Set the config directory to use
    bot.setConfigDirectory = function setConfigDirectory(configDir) {
        this.configDir = configDir
        this.configFile = path.join(configDir, 'config.json')
    }

2 )最初に一度実行する


ここで私たちは以前に定義された定義された定義関数を使用してボットの名前を取るし、各ボットのディレクトリを作成するために使用します.テストと開発中に自分のPCでスクリプトを実行すると、設定ファイルはサーバーの各ディレクトリの代わりに、ホーム/ユーザーディレクトリに書き込まれます.単にファイルをチェックする.discord- あなたのボットの名前が続きます.
// File: src/index.js
    // Set default config directory
    bot.setConfigDirectory(
        path.join(os.homedir(), `.discord-${sanitise(initialConfig.name)}-bot`)
    )

校正用のオープン生成された設定ファイル


さらに、私たちのスクリプトが最初のランで作成したファイルを開くことができるようになり、ユーザーが自分の値が正しくマージされたかどうかを確認できます.
このために、ノードに何かを提供します.opn そして、ボットの1つが初めて彼の設定を生成したならば、我々は生成されたファイル出口をプロセスに開けます.我々のスクリプトの次の実行では、すべてのボットが定期的に接続されます.
// File: src/index.js

    // Open the config file in a text editor
    bot.openConfigFile = function openConfigFile() {
        bot.log.info('Opening config file in a text editor...')
        opn(this.configFile)
            .then(() => {
                bot.log.info('Exiting.')
                process.exit(0)
            })
            .catch(err => {
                this.log.error('Error opening config file.')
                throw err
            })
    }

4 ) ConfigSchemaをチェックする


また、ユーザーによって供給された設定を検証して、新しいBOT Configを生成するために我々のスキーマでそれを合併する機能を必要とします.私たちは、スキーマ・ステップで、それぞれの属性の存在とタイプを比較して、我々のチェックに従いそれを削除するか、上書きします.オブジェクトに対しては、再帰的にレイヤ単位で呼び出す.
// File: src/index.js

    // Recursively iterate over the config to check types and reset properties to default if they are the wrong type
    bot.configIterator = function configIterator(startPoint, startPointInSchema) {
        Object.keys(startPointInSchema).forEach(property => {
            if (!has(startPoint, property)) {
                if (startPointInSchema[property].type !== 'object') {
                    startPoint[property] = startPointInSchema[property].default
                } else {
                    startPoint[property] = {}
                }
            }
            if (startPointInSchema[property].type === 'object') {
                configIterator(startPoint[property], startPointInSchema[property].default)
            }
            if (
                !Array.isArray(startPoint[property]) &&
                typeof startPoint[property] !== startPointInSchema[property].type
            ) {
                startPoint[property] = startPointInSchema[property].default
            }
        })
    }

5 )大きなもの


これはすべてが一緒に来る場所です.私は、我々がピースによって部分的に行く5つのサブセクションにそれを壊しました.
私たちの新しいloadconfig機能は多くのことを行うので、シェルといくつかのコメントをあなたにアウトラインを与えるためにそれを剥奪します.
まず最初に、configファイルの存在をチェックしてください.我々はすぐにこれを必要とします.
// File: src/index.js
    bot.loadConfig = function loadConfig(config, callback) {
        bot.log.info(`Checking for config file...`)
        const configExists = fs.existsSync(this.configFile)

        /* [ALPHA]
         *  If the file does not exist, create it
         */


        /* [BETA]
         * Load the config file from the directory
         */


        /* [GAMMA]
         * iterate over the given config, check all values and sanitise
         */


        /* [DELTA]
         * write the changed/created config file to the directory
         */


         /*
          * read the new file from the directory again 
          * - assign it to the bot's config
          * - execute callback() or abort on error
          */
    }

アルファ


古い設定が見つからない場合は、新しい設定を作成します.我々の選ばれた場所のJSONmkdirp , デスクトップコマンドに似た小さなパッケージmkdir -p , そして、プロジェクトの開始時に何を通過しているかから最も基本的かつ重要な分野でそれを準備する接頭辞と接頭辞
// File: src/index.js

        /* [ALPHA]
         *  If the file does not exist, create it
         */
        if (!configExists) {
            bot.log.info(`No config file found, generating...`)
            try {
                mkdirp.sync(path.dirname(this.configFile))
                const { token, name, prefix } = initialConfig
                const baseConfig = {
                    discordToken: token,
                    prefix,
                    name,
                }
                fs.writeFileSync(this.configFile, JSON.stringify(baseConfig, null, 4))
            } catch (err) {
                this.log.error(chalk.red.bold(`Unable to create config.json: ${err.message}`))
                throw err
            }
        }

ベータ


次のステップでは、設定ファイルを読み込みます.
// File: src/index.js

        /* [BETA]
         * Load the config file from the directory
         */
        this.log.info(`Loading config...`)
        try {
            this.config = JSON.parse(fs.readFileSync(this.configFile))
        } catch (err) {
            this.log.error(`Error reading config: ${err.message}`)
            this.log.error(
                'Please fix the config error or delete config.json so it can be regenerated.',
            )
            throw err
        }

ガンマ


さて、ディスクから読み込んだ設定でConfigIteratorを呼び出し、それをスキーマと比較します.以前に書かれたように、将来的にスキーマを変更しようとすると、古いか不整合の値が設定に残っていないことを確認します.
// File: src/index.js

        /* [GAMMA]
         * iterate over the given config, check all values and sanitise
         */
        this.configIterator(this.config, configSchema)

デルタ


チェックしてきれいな設定をサーバに戻します.
// File: src/index.js

        /* [DELTA]
         * write the changed/created config file to the directory
         */
         fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 4))

イプシロン


最後に、少なくとも、ディレクトリから設定を再読み込みして、最後にチェックしてください.すべてがうまくいけば、コールバックを実行して、それ以外の場合はエラーで中断します.
// File: src/index.js

        /* [EPSILON]
         * read the new file from the directory again
         * - assign it to the bot's config
         * - execute callback() or abort on error
         */
        jsonfile.readFile(this.configFile, (err, obj) => {
            if (err) {
                bot.log.error(chalk.red.bold(`Unable to load config.json: ${err.message}`))
                throw err
            } else {
                bot.config = obj
                callback()
            }
        })
あなたがすべてを持っていることを確認したい場合は、すべてのそれは栄光と複雑さで完成した機能を見てください.
bot.loadConfig = function loadConfig(config, callback) {
        bot.log.info(`Checking for config file...`)
        const configExists = fs.existsSync(this.configFile)

        /*
         *  If the file does not exist, create it
         */
        if (!configExists) {
            bot.log.info(`No config file found, generating...`)
            try {
                mkdirp.sync(path.dirname(this.configFile))
                const { token, name, prefix } = initialConfig
                const baseConfig = {
                    discordToken: token,
                    prefix,
                    name,
                }
                fs.writeFileSync(this.configFile, JSON.stringify(baseConfig, null, 4))
            } catch (err) {
                this.log.error(chalk.red.bold(`Unable to create config.json: ${err.message}`))
                throw err
            }
        }

        /*
         * Load the config file from the directory
         */
        this.log.info(`Loading config...`)
        try {
            this.config = JSON.parse(fs.readFileSync(this.configFile))
        } catch (err) {
            this.log.error(`Error reading config: ${err.message}`)
            this.log.error(
                'Please fix the config error or delete config.json so it can be regenerated.',
            )
            throw err
        }

        /*
         * iterate over the given config, check all values and sanitise
         */
        this.configIterator(this.config, configSchema)

        /*
         * write the changed/created config file to the directory
         */
        fs.writeFileSync(this.configFile, JSON.stringify(this.config, null, 4))

        /*
         * read the new file from the directory again
         * - assign it to the bot's config
         * - execute callback() or abort on error
         */
        jsonfile.readFile(this.configFile, (err, obj) => {
            if (err) {
                bot.log.error(chalk.red.bold(`Unable to load config.json: ${err.message}`))
                throw err
            } else {
                bot.config = obj
                callback()
            }
        })
    }
Link to the finished code / tag v0.0.4 on GitHub

ラッピング


あなたの経験にあった場所に応じてファイルにアクセスし、仕事をするために初めてNodeJsを使用して困難なタスクをすることができます、私はそれが素敵で基本的かつ理解できるようになったことを願っています.
私たちのボット(s)は、新しい、または既存の設定ファイルを読み込むことによって開始することができます.次に、適切なロールとパーミッションを持つユーザーに、オンザフライでの設定を変更し、新しいタグを追加し、ダッシュボードからアクセスするコマンドを追加します.調子を合わせなさい.