로봇(DiscordJS) 구축 - 더 좋은 로그 기록과 지속적인 로봇 설정

지난번에 우리가 떠났을 때 우리는 우리의 간단한 로봇을 하나의 로봇 공장으로 바꾸어 서로 다른 배치로 여러 개의 로봇을 생성할 수 있게 했다.그러나 이러한 배치는 여전히 매우 간단하고 오래 지속되지 않는다.구성 파일을 직접 변경하지 않는 한 사용자는 변경할 수 없습니다.
오늘 우리는 좀 더 아름다운 기록기에서 시간을 보내고 로봇이 서버에서 자신의 프로필을 읽을 수 있도록 할 것이다.
여느 때와 마찬가지로 GitHub에서 완성된 코드의 링크는 본문의 끝에 있습니다.
학점: 오늘의 과정은 코드의 영향을 받는 코드를 포함할 것이다. 일부는 Liora Bot Project에서 나온다.그들의 코드를 마음대로 보셔서 더 많은 영감을 얻으세요.

더 좋은 벌목


오늘의 수업을 시작하기 위해서, 우리는 컨트롤러 로그를 위해 더욱 아름다운 해결 방안을 실시할 것이다. Winston을 사용하여 로그 기록을 하고, 분필을 사용하여 아름다운 색을 할 것이다.
훈련은 npm에서 우리가 필요로 하는 것을 얻고 우리를 바쁘게 하는 것을 알고 있다.
npm i -S winston chalk
Winston에서 로그 수준과 색을 처리하고 있기 때문에 합리적인 기본값을 설정합니다.현재, 우리는 주로 오류, 경고, 정보를 처리할 것이지만, 잠시 후, 이러한 다른 단계도 사용될 것이다.
// 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.

우리가 지금 벗어날 수 있는 일은 손으로 꼬리표를 여기저기 붙이는 것이다.우리는 윈스턴이 우리를 도와 처리하도록 할 수 있다.분배 winston.createLogger() 결과의 줄을 변경하고fat arrow 함수로 변환합니다. 이 함수는 표시를 전송하고 기록기를 되돌려줍니다.이런 방식을 통해 우리는 ${tag} 라벨을 printf 출력에 포함할 수 있다.
// 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(),
    }
시각 출력의 차이는 매우 작지만, 우리의 코드에서, 우리는 단지 많은 군더더기를 삭제했을 뿐이다.

설정에 들어가기 전에, 우리는 여전히 정리를 해야 한다.우리의 코드에는 여전히 쓸모없는 표기가 흩어져 있다.

읽기 및 쓰기 구성


일부 도구는 노드에서 미리 베이킹하는 데 사용될 것입니다. 그러나 이 외에, 디렉터리를 만들고 파일을 열 수 있는 json 파일을 처리하는 방법도 필요합니다.
npm i -S jsonfile mkdirp opn
우선, 우리는 새로운 도구를 가져오기에 추가하고, 사용자의 입력을 철저히 정리하기 위해 유용한 소형 청소 기능을 정의합니다.잠시 후 로봇의 프로필을 위한 디렉터리를 만들 것입니다. 이 디렉터리 이름에 재미있는 문자가 있기를 원하지 않습니다.
// 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, '')
우리는 지금 정확한 설정을 실현해야 하기 때문에, 우리는 여기서 몇 가지 일을 하고, 더욱 상세한 설정 모델을 정의할 것이다.우리는 이것으로 낡은 설정 모드를 바꿀 수 있다.
나는 이 모드를 사용해서 설정이 받아들인 데이터 형식을 정의합니다.이렇게 하면 모든 속성이 우리의 요구에 부합되고 사용자가 속성을 설정하지 않으면 기본값을 포함할 수 있는 기본 검사를 나중에 실행할 수 있습니다.이 목록이나 형식이 잘못되지 않은 내용은 사용자가 입력하거나bot 설정한 오래된 복사본에서 버려집니다.이렇게 하면 현재 구성이 항상 호환되는지 확인할 수 있습니다.

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: {} },
}
규칙 입력 출력에 두 줄을 추가해야 합니다.eslintrc 파일입니다. 왜냐하면 우리는 그들이 곧 linter에 의해 예상된/우리가 원하는 방식으로 일하는 것을 도청하지 않도록 해야 하기 때문입니다.
// File: .eslintrc
    "no-param-reassign": ["error", { "props": false }],
    "valid-typeof": 0

1) 구성 디렉토리 설정


특정 디렉터리의 프로필 경로를 추적하는 방법이 필요합니다.우리는 단지 그것들을 우리bot 대상에 저장할 뿐이다.
// 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) 최초 실행


여기에서, 우리는 앞에서 정의한sanitise 함수를 사용하여bot의 이름을 가져오고, 이것으로bot마다 디렉터리를 만듭니다.테스트 및 개발 중에 자체 PC에서 스크립트를 실행하면 프로파일은 서버의 해당 디렉토리가 아닌 마스터/사용자 디렉토리에 작성됩니다..discord-로 시작하고 로봇 이름의 파일만 검사하면 된다.
// File: src/index.js
    // Set default config directory
    bot.setConfigDirectory(
        path.join(os.homedir(), `.discord-${sanitise(initialConfig.name)}-bot`)
    )

3) 생성된 프로필을 열어 교정


또한, 사용자가 그의 값이 정확하게 통합되었는지 확인할 수 있도록 스크립트가 처음 실행될 때 만든 파일을 열 수 있기를 바랍니다.
이를 위해, 우리는 node가 우리에게 제공한 것 opn 을 사용할 것입니다. 만약 그 중 한 로봇이 처음으로 그의 설정을 생성한다면, 우리는 생성된 파일을 열고 절차를 종료할 것입니다.우리 스크립트의 다음 운행 중에는 모든 로봇이 정기적으로 연결될 것이다.
// 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) 구성 모드 확인


사용자가 제공한 설정을 검증하고, 새로운 bot 설정을 만들기 위해 모델과 통합하는 함수가 필요합니다.우리는 모드를 한 걸음 한 걸음 검사하고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)큰거,loadConfig


이것은 모든 것을 한데 결합시키는 곳이다.나는 그것을 다섯 마디로 나누었는데, 우리는 하나하나 이야기할 것이다.
우리의 새로운loadConfig 함수는 많은 일을 할 것입니다. 그래서 셸과 주석으로 간소화해서 개요를 제공합니다.
먼저 구성 파일이 있는지 확인합니다.우리 이거 곧 필요해.
// 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
          */
    }

알파


이전 설정을 찾지 못하면 새 설정을 만듭니다.우리가 선택한 위치에서 mkdirp json을 사용합니다. 이것은 데스크톱 명령 mkdir -p 과 유사한 애플릿 패키지이며, 프로젝트를 시작할 때 전달하는 가장 기본적이고 가장 중요한 필드를 사용하여 준비합니다.discordToken, 접두어 및
// 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를 사용하여 파일에 접근하고 처리하는 것은 어려운 작업일 수 있습니다. 따라서, 당신의 경험에 따라, 저는 양호하고 기본적이며 알기 쉽기를 바랍니다.
우리의 로봇은 현재 새 프로필을 만들거나 기존 프로필을 불러와서 시작할 수 있습니다.다음에는 올바른 역할과 권한을 가진 사용자가 구성을 동적으로 변경하고 새 태그를 추가하며 대시보드에서 액세스할 수 있도록 명령을 추가합니다.기대하세요.

좋은 웹페이지 즐겨찾기