discord.js + Klasa を使ったBotの作成覚え書き

参考 : [discord.jsのフレームワークKlasaが強すぎる](https://qiita.com/InkoHX/items/c0c2020e911e87cbdbef)
合わせて読んでください。

# 準備

* 環境
  * Win10
  * Node.js 12.13.0
  * npm 6.12.0

適当にプロジェクトフォルダを作成し discord.js と Klasa をインストール

```bash
npm install --save discordjs/discord.js dirigeants/klasa
```

# コーディング

## 最初の一歩

`app.js` を作成

```javascript:app.js
const secret = require('./secret.js'); // BOTトークンはここに記述
const token = secret.token;

const {Client} = require('klasa');

new Client({
  prefix: '!!', // ボットのPrefix
  language: 'ja-JP', // 言語コード
}).login(token);
```

この状態で一度 `node app.js` から起動し、危険なログが出てなければとりあず成功。

## コマンドの作成

`commands/general` 配下に以下のソースを作成。Klasaはファイル名がそのままコマンド名になる。
ディレクトリ名はヘルプのカテゴリ名になる。
なお、下のソースに書かれている `usage` は単なる説明文ではなく引数のマッチャを参照するための記述なので注意。(これで30分ぐらい潰した……)

```javascript:commands/general/hello.js
const {Command} = require('klasa');

module.exports = class extends Command {
  /**
   * @constructor
   * @param  {...any} args
   */
  constructor(...args) {
    // コマンドのオプション: https://klasa.js.org/#/docs/klasa/master/typedef/CommandOptions
    super(...args, {
      description: '挨拶を返す',
      usage: '<name:string>', // <>は必須引数、[]はオプション引数、<名前:型>のように書く
    });
  }

  /**
   * @param {*} message
   */
  async run(message,[name]) {
    return message.sendMessage(`${name}さんこんにちは！`);
  }
};
```
参考 : 
[Klasa : CreatingArguments](https://klasa.js.org/#/docs/klasa/master/Piece%20Basics/CreatingArguments)
[Klasa : CreatingCommands](https://klasa.js.org/#/docs/klasa/master/Piece%20Basics/CreatingCommands)

BOTの反応はこんなかんじ

![コメント 2020-04-14 002903.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6817/68cf39d6-b434-39f3-371d-cb77535d6449.png)

![コメント 2020-04-14 002903_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6817/5b58345c-be6e-52fd-2dca-6ecd3fa4da1d.png)

で、このときの `message` にはどうやら `KlasaMessage` という型が渡されている模様。ドキュメント見つからないけど。とりあえず

```javascript
message.author.sendMessage('hogahoga');
```

とかすればその人にDMを送ることができたりする。

### 引数が複数あるコマンドの作成
Klasa公式の [ADVANCED COMMANDS Commands I: Arguments](https://klasa.js.org/#/docs/klasa/master/Advanced%20Commands/CommandsArguments)をななめ読みするだけだと「usageに複数の引数を指定すれば複数の引数を指定できる」となんとなく読み取れるが、それは間違いである。よく見ると「Repeating Arguments」の項に
> you may want the user to write in multiple arguments, more likely with `,` as `usageDelim` to separate them.

と書いてある。この `usageDelim` パラメータを指定しないと引数を複数取ることができないので注意する。

```javascript
const {Command} = require('klasa');

module.exports = class extends Command {
  /**
   * @constructor
   * @param  {...any} args
   */
  constructor(...args) {
    // コマンドのオプション: https://klasa.js.org/#/docs/klasa/master/typedef/CommandOptions
    super(...args, {
      description: 'n番のカードを対象に公開する、aなら全体に公開する。(opc)',
      usage: '<target:number|a> <n:number> [...]',
      usageDelim: ' ', /** これ! **/
      runIn: ['text', 'group'],
      aliases: ['opc'],
    });
    this.game = this.client.providers.get('ganparaGame');
  }

  /**
   * @param {Message} message
   */
  async run(message, [target, ...ns]) {
    /** 中略 **/
    return;
  }
};
```


## データストアの作成

`providers` ディレクトリにプロバイダを作成する。[公式](https://klasa.js.org/#/docs/klasa/master/Piece%20Basics/CreatingProviders)を見るとなんか適当に書いてよさそうだったので適当に書いてみる。

```javascript:providers/datas.js
const {Provider} = require('klasa');

module.exports = class extends Provider {
  /**
   * @constructor
   * @param  {...any} args
   */
  constructor(...args) {
    super(...args, {name: 'datas'});
    this.counter = 0;
  }

  /**
   * init
   */
  init() {
    this.counter=1;
  }

  /**
   * countup
   * @return {Number} count
   */
  countup() {
    this.counter++;
    return this.counter;
  }
};
```
`hello.js`から動作確認してみる。

```javascript
const {Command} = require('klasa');

module.exports = class extends Command {
  /**
   * @constructor
   * @param  {...any} args
   */
  constructor(...args) {
    // コマンドのオプション: https://klasa.js.org/#/docs/klasa/master/typedef/CommandOptions
    super(...args, {
      description: '挨拶を返す',
      usage: '<name:string>',
    });
    this.data = this.client.providers.get('datas');
  }

  /**
   * @param {*} message
   */
  async run(message, [name]) {
    return message.sendMessage(`${name}さんこんにちは！${this.data.countup()}回めの挨拶ですね！`);
  }
};
```

![コメント 2020-04-14 005829.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6817/4f507b97-df3b-2c85-0b80-828c4b20e01c.png)

本当に適当に動いてしまった。