1. はじめに
本記事ではデザインパターンの一種であるChain Of Responsibilityを利用して、
SlackのSlash Commandsを拡張性高く実装する方法を紹介します。
Slash Commandsの処理を受け取るサーバーはAWSを、言語はPythonを利用します。
手軽に試せるようにコマンド3つでAWSへデプロイできるGitPod環境も用意してあります。
1.1 Slash Commands とは
Slack CommandsとはSlackに独自のコマンドを追加し、そのコマンドにより任意の処理を実行できるものです。
Slackが既に用意しているものもあり、有名なのは/remind
などでしょうか。
/remind me tomorrow 11:00 meeting
と打てば、以下のような表示ととも明日の11時にslack botからリマインドが自分宛に飛んできます。
このようなコマンドをSlash Commandsを利用することで、独自に追加実装が可能です。
Slash Commandsについての詳しい設定方法などはこちらをご覧ください。
1.2 Chain Of Responsibility とは
Chain Of Responsibilityとは、
- Chainという英単語は鎖、Responsibilityという英単語は責任、つまりChain of Responsibilityは、責任の連鎖という意味になります。実際にはたらい回しを行う構造と考えた方が分かりやすいです。
- Chain of Responsibilityパターンは、複数のオブジェクトを鎖で繋いでおき、そのオブジェクトの鎖を順次渡り歩いて目的のオブジェクトを決定する方式です。
- 人に要求がやってくる、その人がそれを処理できるなら処理する。処理できないならその要求を「次の人」にたらい回しにする。以降繰り返し・・・。これがChain of Responsibilityパターンです。
- GoFのデザインパターンでは、振る舞いに関するデザインパターンに分類されます。
です。
詳しくはデザインパターン ~Chain of Responsibility~(引用元)をご覧ください。
1.3 GitPod とは
GitHubアカウントがあれば、無料から利用できるクラウドIDEです。
PCだけではなく、iPadなどからも利用可能です。
便利です。詳しくは、こちらの記事を参考にしてください。
2. 実装
2.1 できるもの
上に見えるのが、ユーザーが打った文字列(=/hoge あああ
)で、したに表示されているのが(=[hoge]あああ
)がSlash Commandsによって返される文字列です。
このような任意の処理を実行できるコマンドを、拡張性高く実装していくことができます。
2.2 手順
- GitPodに環境変数を設定
- AWS環境に処理をDeploy
- Slash Commandsを作成し、紐付ける
- 完成
2.3 準備
Slash Commandsを受けるサーバーをAWSへデプロイするために必要なキーは以下の6つです。
- AWS
AccessKey
SecretAccessKey
region
- Slack
BotUserAuthToken
Slack Verification Token
Channel ID
2.3.1 AWSに関するキー
AccessKey
とAccessSecretKey
はIAM
のユーザーから発行することができます。
また、region
は東京の場合はap-northeast-1
を設定してください。
(注:間違ってもAWSのキーをGitHubのリポジトリにpushしないでください。
漏洩した場合、マイニングするためのリソース構築が勝手に行われて課金対象になってしまうことがあります。)
2.3.2 Slackに関するキー
slack api
のアプリを作成します。
そこにSlash Commands
とBots
のfeatureを追加し、以下の2つのキーを取得します。
- Basis Infromation -> App Credintials ->
Verification Token
- OAuth & Permissions -> OAuth Tokens & Redirect URLs ->
Bot User OAuth Access Token
Channel ID
は、Channel Name
と異なるので注意してください。
slack apiのスラックにあるチャンネルを表示するAPI(参考)を叩いて対象のChannel ID
を取得するか、2chの内容を表示させたいチャンネルからSlash Command
を実行し、AWSのCloudWatch LogsからChannel ID
を取得しても良いかと思います。
2.3.3 GitPodの環境変数に設定
GitPodのEnvironemnt Variables
に6つの環境変数を以下の名前で設定してください。また、以下の環境変数はGitPod環境で自動で読み込むために、Name
を定めていますが、プログラムを書き換えれば任意のName
で実行できます。
Name | Value |
---|---|
aws_access_key |
AccessKey |
aws_secret_access_key |
SecretAccessKey |
region |
region |
slack_2ch_channel_id |
Channel ID |
slack_oauth_access_token |
BotUserAuthToken |
slack_token |
Slack Verification Token |
2.4 プログラム
プログラムは、GitHubに公開してあります。
また、GitPod環境は以下から利用可能です(GitHubアカウントは必要です。)
2.5 Chain Of Responsibility の部分
親クラスであるCommandExecutor
クラスのhandle_execute()
にて、処理を自クラスが担うか、担わない(=他のクラスの任せる)かを決めています。
自クラスで処理を担う場合はexecute()
を実行し、担わない場合は次のクラスのhandle_execute()
に処理をまかすようにしています。
class CommandExecutor:
# ~ 中略 ~
def handle_execute(self, params) -> dict:
"""
入力された`params`の実行元(`command`)に拠って
実行する処理を変更する関数
1. 呼ばれたクラスの名前(self.name)がcommandと等しい場合、処理を行う
* デコード処理が正しく行われた場合は、処理結果を返し、【終了】
* 正しく行われなかった場合は、異常という処理結果を返し、【終了】
2. 呼ばれたクラスの名前がcommandと異なる場合、次のクラスに処理を任せる : self.next.execute(params)
* 次のクラスがある場合、処理が【続く】
* 次のクラスがない場合、異常という処理結果を返し、【終了】
"""
# 対象のCommandかどうか確認
if self.check_responsibility(params):
result_dict = self.execute(params)
if len(result_dict) > 0:
# メッセージの処理に成功した場合は処理結果を返す
return result_dict
else:
# メッセージの処理に失敗した場合は例外を投げる
raise ChainOfResponsibilityException
elif self.next_executor is not None:
# 対象のCommandでは無い場合、次のexecutorに任せる
# [Chain of Responsibility]の部分
return self.next_executor.handle_execute(params)
else:
return {"error": "Could not execute your command. Check your {/command} name"}
def execute(self, params) -> dict:
"""
各クラスで担うべき処理を実装する
"""
pass
また、以下は/fuga
というコマンドの処理を担うクラスです。
handle_execute()
は親クラスで実装済みですので、各クラスはexecute()
のみ実装すればよいです。
from chalicelib.CommandExecutor import CommandExecutor
class Fuga(CommandExecutor):
def __init__(self):
super().__init__("/fuga")
def execute(self, params: dict) -> dict:
# x-www-form-urlencodedではjsonのパラメータがリストでデコードされるため
text = params['text'][0]
self.logger.info(f"text:={text}")
# slash commandから直接レスポンスを返す
return {
"response_type": "in_channel",
"text": f"[fuga]{text}"
}
また、処理の順序はCommandExecutorRequestHandler
クラスにて実装してあります。呼び出す側でこのクラスを起点に実行を開始すれば、次にHoge
の処理が、最後にFuga
の処理が実行されます。
class CommandExecutorRequestHandler(CommandExecutor):
"""
最初にメッセージを受け取り、処理を開始するクラス
このRequestHandlerではメッセージの受け取りを担うのみで
実際の処理を行うのはset_next()内に含まれるインスタンスが処理を行う
"""
def __init__(self):
"""
self.set_next({instance}).set_next({instance})
のように数珠繋ぎに関数をつなげる
"""
super().__init__("RequestHandler")
# commandに拠って出力する内容を切り替える
self.set_next(Hoge()).set_next(Fuga())
2.6 拡張方法
GitHubに載せてあるプログラムでは、以下のコマンドを受けれるようにしてあります。
/Hoge
/Fuga
例えば、ここに/Piyo
コマンドを実装したい場合はどうすればよいのでしょうか?
やることは以下の3つです。
-
CommandExecutor
クラスを継承した新たにPiyo
クラスを作成する -
Piyo
クラスにexecute()
を実装する -
CommandExecutorRequestHandler
クラスの__init__()
に.set_next(Piyo())
を加える
他のコマンドも同様の手順で新たなコマンドを加えることができます。
2.7 実行環境(再掲)
また、GitPod環境はこちらから利用可能です(GitHubアカウントは必要です。)
3. おわりに
Slack APIやAWSのアクセスキーの準備などがすこし手間ですが、
そこが終われば、誰でも簡単にSlash Commandsを実装することができます!!
よいSlackライフを!!
A. おまけ
12月の初旬にバズったこちらの記事の/2ch
コマンドも簡単に実装することができます。
(GitHubのソースコードに既に一部が実装されており、デプロイと同時に利用可能です。これであなたのslackにも2ch環境が!!)