Skip to content

Discordの多機能botを作るためのフレームワーク

Notifications You must be signed in to change notification settings

Appbird/skcdiscord

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

63 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Project:SKcDiscord-Servant

by Project.Appbird

Discordのための多機能botを作るためのフレームワーク(?)です。 このbotは、とあるサーバーのために作成されたため、一般用途には向いていないと考えられます。 ここには使用方法と振り返りの二つを書いています。

基本的な仕様については以下にどうぞ https://scrapbox.io/minimumAppbirdlications/SKcDiscordServant

開発には

  • Node.js/npm
    • discord.js
    • node-fetch
    • typescript
    • @types
      • node
      • node-fetch
  • Visual Studio Code
  • Heroku

を用いました。

feature:ファイル分離による機能ごとの関心分離

このbotは、機能ごとに関心を分離してプログラミングが出来るよう設計されています。

追加できる機能を以下の二種類に分類します。

  • command

その名の通りコマンド機能。プレフィックス>から始まる構文をとるメッセージを受け取った時に発生するコールバック関数を指定します。

  • react

その他すべての、特定のイベントの発生時に発火するような機能。たとえば、サーバーに入った時のコールバック関数や、プレフィックス>をとらない普通のメッセージを受け取ったときなどに発火するコールバック関数を定義することが出来ます。 これらの機能を、一つのグループ(機能群)としてまとめ、ソースファイルを分離してプログラミングすることが可能になります。機能群に直接対応する型(インターフェース)はIFunctionBaseになります。

たとえば、2020/06/14現在で実装されている機能群としては以下の3つがあります。

  • BallonBurier

省略形でbabu。特定の単語を含むメッセージが投稿されたとき、そのメッセージを削除する機能を保有します。 このページ上では一例としてbabuの動作状況のスクリーンショットが挙げられています。

  • CmdChannelManager

コマンドを受け付けるチャンネルを設定する機能を保有します。

  • Ready

コマンドを受け取った時、特定の文字列を返す機能を保有します。 これらはそれぞれ複数のcommandとreactを所持しており、それぞれ関心ごとにファイルが分離されて定義されています。 新たに機能群としてまとめ上げたときには、src/FunctionGroup/functionSet.ts内で定義されているエクスポート変数(IFunctionBase[]型)であるfunctionSetに機能群(IFunctionBase)を追加することを忘れずに。そうしないと認識されません。

feature:コマンドの定義方法

編集すべきフォルダは、src/FunctionGroupになります。 この中に機能群ごとにフォルダを作って、その中のファイルでcommandやreactを定義します。

command

commandに直接対応するのはICommandBaseになります。 * commandTitle:string コマンド名 * allowedFlags:ICmdFlag[] 定義されたフラグ(後述) * numberOfTokenRequired:number このコマンドがとる最小の単語数。 * description:string 説明 * argsForDescription:string[] 説明時に表示される引数名 * process:(msg:Message,tokenArray:string[])=>void コマンドが打ち込まれた時に呼び出されるようなコールバック関数。 のプロパティを取ります。 [https://i.gyazo.com/d3937a8bab9b938c861c5317bc8e88b1.png] これらのデータは、>[機能群名] -hコマンドが打ち込まれたときに表示されるヘルプ表示にも活かされます。

commandのフラグ

また、コマンドはフラグ(-から始まる引数)を取ることが出来ます。 これを用いると、ユーザーによるコマンドの細やかな挙動の操作が可能になります。フラグに直接対応するような型はICmdFlag(./src/helper/cmdFlags)になります。

cmdFlagManagerのインスタンスオブジェクトとしてICmdFlag[]を定義するとより便利です。[

フラグとされる入力文字列を受け取ってそのうちフラグとして適切な文字列だけを出力しつつ、ICmdFlag#stateのフラグの状態を変更させるcmdFlagManagerのメソッドturnOn(cmdFlagsChar:String[],channel:TextChannel|DMChannel|NewsChannel):string[]を用いることができるためです。

cmdFlagManagerを継承してcmdFlagManager#definedCmdFlags:ICmdFlag[]を改めて定義する手法がおすすめです。(ここの設計はあんまりよくないと思うので、ここの設計はいつか変えたい。)

ICmdFlagは以下のプロパティを取ります。
*flagTitle:string
フラグ名 * flagOnDescription:string
フラグが立つ時の挙動の説明
* flagOffDescription:string
フラグが立たないときの挙動の説明
* cmdForFlag:string
コマンド上でこのフラグを表す文字列
* state:boolean
フラグが立っているか否か
このICmdFlag#stateはメソッドcmdFlagManager#turnOnを発火させた後だとユーザーのフラグの入力状況にそっているため、フラグの入力状況に応じて挙動を変更させることが可能になります。

IcommandFlag

react

直接ユーザーのメッセージに反応したいときなどはこちらを用います。

IReactBase<K extends keyof ClientEvents>のプロパティは以下の通りです。
* eventType:K
ここで指定したイベントが発生したときに、processは呼び出されます。
* reactName:string
このreactの正式名称
* process:(...args:ClientEvents[K]): void
呼び出されるコールバック関数。

なお、型の仕様上、reactを定義するときは、eventTypeごとにジェネリックを分けてreactを定義することを推奨します。ここは自動的に型推論されるようにしたかったのだがどうにもわからなかった。

[https://i.gyazo.com/c1ebb66cd3be8d6698eae76f75c2ccb5.png]

IFunctionBase

この型が直接機能群に対応します。新たに機能群としてまとめ上げたときには、src/FunctionGroup/functionSet.ts内で定義されているエクスポート変数(IFunctionBase[]型)であるfunctionSetに機能群(IFunctionBase)を追加することを忘れずに。そうしないと認識されません。
* commands:ICommandBase[]
定義されたcommand。
* react:IReactBase<keyof ClientEvents>[]
定義されたreact。(ここの型の定義をどうにかしたいところ。(Promise.allの型定義が参考になる?))
* functionName:string
コマンドなどで使われる省略形の名前。
* realFuncName:string
実際の機能群の名前。
* description:string
機能群の説明。

ここまで定義してようやく一つの機能群として利用可能になります。

また、ここの実装のためにコンパイラには型の双変性を許容させています。

reactに代入されるのはIReactBase<K>[]``(K extends keyof ClientEvents)である一方、代入先はIReactBase<keyof ClientEvents>[]という複合型であるため、型の双変性を許容しないと代入ができず、コードが難解になる可能性が考えられたため、今回はこのまま許容することにしました。

help機能

また、>helpコマンドを用いて機能群の説明、>[機能群] -hフラグ付きコマンドを用いてその機能群内のcommandの説明を出力させることが可能です。
以上のデータで入力されたdescriptionなどをもとにしてhelpを自動生成するため、helpの書式についてはあまり気にせずに開発することが出来ます。

データの保存について

このbotは、サービスHeroku上で動作することを想定して作成しています。

そのため、データベースの機能を利用して保存する手法も考えられましたが、今回はDiscord上の特定のテキストチャンネルを保管庫とする手法を取りました。

テキストチャンネルとしてfilevaultチャンネルを作成することで、そのテキストチャンネル中にjsonデータを添付したメッセージを保有します。 こういった、データの読み込み書き込みについては./src/helper/programHelperFunctions/helperAboutFile.tsにて定義を行っています。

discord.jsには2020/6/14現在メッセージの添付ファイルを直接書き換えるような手法が存在しないとみたため、メッセージを削除してはまた新たにメッセージを投稿するという手法を取っています。そのため、反応速度がかなり遅くなります。

なお、テキストチャンネル名の衝突を防ぐため、filevalutチャンネルの模索範囲は一つのサーバーのみとしています。プログラムの利用者はこのサーバーを指定しなければなりません。(後述)

環境変数について

このプログラムを動かすためには、以下の環境変数が定義されていなければなりません。

  • BOT_TOKENをキーとしたDiscordのBotのトークン
  • guildIdをキーとしたfilevaultテキストチャンネルが存在するサーバーのid
  • botIdをキーとしたbotのユーザーid ただし、./envVariables.jsonが存在した場合には、プログラムはこのjsonファイルを解釈してそれを環境変数として扱います。

環境変数を扱うような操作をする際は、./src/helper/programHelperFunctions/helperAboutVariables.tsgetEnvVariable(name:string):string関数を用いると楽です。

エラーを投げる

エラーが発生したとき、ユーザー側に知らせる際には./src/helper/programHelperFunctions/helperAboutError.tshelperAboutError.throwErrorToDiscord(targetChannel:TextChannel|DMChannel|NewsChannel,content:string,description?:string,fields?:IEmbedMessageField[])関数を用いると楽です。

内部仕様

  • commandは実は内部仕様上では、messageイベントに反応するreactに変換されています。
    • どのreactよりも早く実行されます。
    • この統合作業及び、reactのコールバック関数を並べた配列を作る処理はsrc\helper\reactToEvents.tsにて定義されています。
  • helpコマンドや-hフラグは、一見一つの機能群のように振る舞っていますが、実際のところ実行時にトークンの文字列を確認して真っ先に実行されます。
    • 本当は機能群として確立したいというのが本音ですが、全ての機能群に一つ一つ-hフラグを定義するのは保守性に欠けるためこの様に設計しました。
    • なお、こういったコマンドを受け取った時の文字列の解析等は ./src/helper/cmdExecutor.tsexecuteCmd関数で定義されています。
  • Herokuで動くように設計はしましたが、恐らくきちんと環境変数を設定すればどのサーバーでもきちんと動くと思います。
    • そのサーバーでデータ保存が可能であれば、HelperAboutFile.tsを書き換えるべきです。
  • EmbedMessageMaker(./src/helper/embedMessageMaker.ts)という関数を使えば、比較的簡単にMessageEmbedを持つメッセージを作成することが出来ます。
    • src\helper\giveArgsOfHelpEmbedMsg.tsは、Helpを表示させる際に使用しています。
    • この中にある関数は、EmbedMessageMakerを実行するための引数の配列を返します。

今後の展望

  • helperAboutFiles.ts - バイナリとしてのファイル保存を試みる
    • その手法のデメリットを掴む
  • helpコマンドを一つの機能群として独立させる。
  • filevaultの添付ファイルを直接書き換えできないか試みる。
    • ファイル保存場所をデータベースに移行する。
  • 型の双変性を許容しない状態でプログラムを完成させる。
    • IReactBase<K>#eventTypeを用いてKを推論できないか。
    • たとえばclient.on(event,callback)だと、eventの文字列型の指定によって関数のジェネリック型が推論される。これと同じようなことが出来ないか。
  • 機能群の追加/拡張。

About

Discordの多機能botを作るためのフレームワーク

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages