python

PythonでDockerの上で動くしりとりSlackbotを作成!

この記事では、docker上で動かせる、駅名しりとりslackbotを作成していきます!

すべてのコードはこちらで公開しています。

完成した挙動を紹介します。

しりとりBOTの定義

  • slackのBOTとして1 on 1 しりとりができる
  • 「しりとり」と入力したらしりとりスタート
  • ひらがな駅名しりとりをする
  • 同じ名前の駅は2度以上使用できない
  • 濁音は清音に直す
  • 小文字(ぁ、ぅ、ゅ)は大文字(あ、う、ゆ)に直す
  • BOTの語彙がなくなったら終わる
  • ユーザーが「ん」で終わったら負け判定をする
  • 「exit」と入力をしたらその場でゲームを終了する

Slackに自分のBOTを作成

テスト用のSlackワークスペースを作成

  1. Slackアプリの左側の+、もしくはメニュー→ウィンドウ→他のワークスペースにサインインするを押す
  2. Create New Workspaceを押す
  3. 自身のメールアドレスを入力して、新しいワークスペースを作成
  4. BOTの設定は上記ワークスペースにて行う

Slackにボットインテグレーションを追加

  1. こちらからSlackのBots設定画面を開く
  2. BOTのユーザ名を設定(名前は後からでも変えられます)
  3. ボットインテグレーションを追加するを押下
  4. インテグレーションの設定→APIトークンをコピペして保存(ここ大事!)
  5. インテグレーションの保存を押下

自分とBOTの会話窓を作成

  1. Slackアプリを開く
  2. 左側のメニューからAppの横の+を押下
  3. 先ほど作成したBOTを選択
  4. 自分の作成したBOTとの会話窓が作成される

基本階層を作成

作業ディレクトリとファイル作成

まず、slackbotを作成するディレクトリを作成してください。

僕の場合は, SiritoriBotProject という大きな作業ディレクトリを作成し、ソースコードのすべては、その中にあるsrcディレクトリに作成しました。

理由は、dockerを使用するときにその動きを理解しやすくするためです。

最終的な階層はこのようになります。

SiritoriBotProject
    │  docker-compose.yml
    │  Dockerfile
    │  README.md
    │
    └─src
        │  requirements.txt
        │  run.py
        │  slackbot_settings.py
        │  wor_dict.xlsx
        │
        └─plugins
            reply.py
            __init__.py
     

最小限のファイルを作成する

srcディレクトリの中で、

│ requirements.txt
│ run.py
│ slackbot_settings.py
│
└─plugins
  reply.py
  __init__.py

これを作成してください。それぞれの中身を紹介していきます。

#〇〇はファイル名ですので、2行目からコピーしてください。

#requirements.txt
slackbot
pandas
xlrd
#run.py
from slackbot.bot import Bot

def main():
    bot = Bot()
    bot.run()

if __name__ == "__main__":
    print('start slackbot')
    main()
#slackbot_settings.py
# BOTアカウントのAPIトークンを設定
API_TOKEN = "あなたのAPIトークン"

# どの応答にも当てはまらない場合に返すメッセージを設定
DEFAULT_REPLY = "なにか違うよ!"

# プラグインスクリプトを置いてあるサブディレクトリ名を設定
PLUGINS = ['plugins']

次に、pluginsディレクトリの中でファイルを作成していきます。

#__init__.py
# 中身は空
#reply.py
from slackbot.bot import respond_to

@respond_to('しりとり')
def siritori_func(message):
    message.reply('しりとしを開始します!')

 

この状態で、srcディレクトリの中で、コマンドプロンプトでrun.pyを実行してみてください。

インポートエラーが出た場合は、指示に従ってインストールしてあげてください。

pip install slackbot

エラーが出なければ、先ほど作成した、BOTの窓口で「しりとり」と入力をしてみてください!

「しりとりを開始します!」と返ってこれば大丈夫です。

機能追加

これ以降は、reply.pyの中身だけをいじっていきます!

ここからは、条件分岐やファイル読み込みなど、基本動作の復習がメインです!

こだわりたい方はこだわって、そうでもない方は飛ばしてもOKです!

 

単語辞書作成

まず、しりとりに使用する単語辞書を作成します。

元となるデータは、こちらからダウンロードします。日本全国の駅名がエクセル形式で載っています。

まずは、それを読み込んでルールに当てはまるように整形していきます。頭からがっつり書き換えて行きます。

#reply.py
from slackbot.bot import respond_to
import pandas as pd
import re
import random
import unicodedata

eki = pd.read_excel('wor_dict.xlsx', header=1)
eki = eki.dropna(subset=['読み'])
word_list = eki.groupby('読み')['読み'].count()
first_list = list(pd.Series(word_list.index.values, word_list.values))
w_list = list(pd.Series(word_list.index.values, word_list.values))
history = [w_list[0]]
flag = False
last_word = w_list[0]
print('load finished!')

 

ちなみに、ここで定義されている変数はグローバル変数というものです。

コードの中身を簡単に解説をすると、

  1. データフレームでデータを取得
  2. 欠損値を排除
  3. 読みの重複を排除
  4. 読みをリストとして保持(first_list)
  5. 読みをリストとして保持(w_list)

という操作をしています。

現時点で4, 5は全く同じものです!

4は今後変更されないもので、5は今後リストの中身が変更されていきます。

 

historyには今まで使用した、単語を保存して二度同じ単語を使わないために使用します。

flagはフラグです。笑

last_wordは相手が最後に発言した単語です。

 

これで、なんとなくしりとりに必要な道具がそろったと思います!

pythonでひらがなの小文字を大文字に変換

ここは原始的な方法でいきましょう!

SMALL_TO_NORMAL = {
    'ぁ': 'あ', 'ぃ': 'い', 'ぅ': 'う', 'ぇ': 'え', 'ぉ': 'お',
    'っ': 'つ',
    'ゃ': 'や', 'ゅ': 'ゆ', 'ょ': 'よ',
}
def small2large(a):
    if a in SMALL_TO_NORMAL:
        return SMALL_TO_NORMAL[a]
    else:
        return a

 

先ほどの続きにこれを記述してください。

仕組みは簡単です。入力された文字が辞書に含まれていれば、それに変更する。

ただそれだけです!!笑

それでは、しりとりのルールを記載していきましょう!

 

処理が分からなくなったらその都度、run.pyを実行して確認してみてください。

それが一番近道です!!

ひらがな以外は例外処理~pythonで正規表現~

@respond_to(r'.*')
def siritori_func(message):
    global flag
    global last_word
    re_hiragana = re.compile(r'^[あ-ん]+$')
    status_hira = re_hiragana.fullmatch(message.body['text'])  # fullmatch:完全一致
    if status_hira:
        print("ひらがなが入力されたよ")
    elif message.body['text'] == 'exit':
        message.reply("しりとりを終えるよ。お疲れ様~")
        return 0
    else:
        message.reply("平仮名で駅名を入力してね~")

 

@respond_to(r'.*')の部分では任意の文字列を受け取れるようにしてあります!

re_hiragana = re.compile(r'^[あ-ん]+$')
status_hira = re_hiragana.fullmatch(message.body['text']) # fullmatch:完全一致

この部分で、文字列がひらがなのとき(True)とそうでないとき (False) を判別しています。

Trueの時は後で書いていきます。

「exit」が入力をされたときは、しりとりを終了するので、コメントを返してreturn 0でプログラムを終了させます。

それ以外のときは、誤入力なので、プログラムは終了させないでコメントだけを返してユーザーからの入力を待ちます。

「しりとり」と入力されるまで待つ

print(“ひらがなが入力されたよ~”)の部分を書き換えてください!

if status_hira:
        if message.body['text'] == 'しりとり':
            message.reply('しりとりを開始するよ!\n駅名をひらがなで入力してね。同じ駅名は2回以上使用してはならないよ!\n最初の単語は:\n' + last_word)
            w_list.remove(last_word)
            flag = True
        if flag:
            print("しりとりの処理開始")

 

ようやく登場しました!flag!!

過去に「しりとり」と入力されたか否かを判定するために用意した変数です。

ちなみに、これはglobal変数でしたので、

global flag としてあげないと、上手く動いてくれませんので、注意が必要です。

入力されたひらがなが、駅名として存在しているかを判定

if flag == True: のとき実行されるコードです。

print("しりとりの処理開始") の部分を書き換えてください。

        if flag:
            if message.body['text'] in first_list:
                if message.body['text'] in history:
                    message.reply('それはもう使った単語だよ!')
                else:
                    print("しりとり")
            elif last_word == 'あいおい':
                pass
            else:
         message.reply("その駅は知らないな~。\n別の駅を探してみて!")

これで、リストの中に含まれていない単語ははじかれるようになりました。

しりとりのルールを守っているかをチェック

次は、同じ単語を使っていないかチェックします!

それに加えて、ユーザーがしりとりとして正しい回答をしているか判定、BOTのしりとりの回答の作成を行います。

これで、全てのコードの完成です!

            if message.body['text'] in first_list:
                if message.body['text'] in history:
                    message.reply('それはもう使った単語だよ!')
                else:
                    if message.body['text'][0] != last_word[-1]:
                        message.reply(last_word[-1]+"から始まる駅名を答えてね")
                    else:
                        history.append(message.body['text'])
                        w_list.remove(message.body['text'])
                        last_spell = small2large(unicodedata.normalize("NFKD", message.body['text'][-1])[0])
                        rep_list = [word for word in w_list if word[0] == last_spell]
                        if len(rep_list) >= 1:
                            last_word = random.choice(rep_list)
                            message.reply('次の駅名は:'+last_word)
                            history.append(last_word)
                            w_list.remove(last_word)
                        elif len(rep_list) == 0:
                            message.reply('もう知ってる駅名ないや!\n僕の負けだね。。おめでとう!')
                        if message.body['text'][-1] == 'ん':
                            message.reply('「ん」がついたから君の負けだよ!')

 

仕組みとしては、historyに今まで使用した単語を追加していき、それを用いて重複判定をします。

BOTが使用できる単語は、辞書から徐々に減っていってほしいので、w_listから使用した分を毎回 removeして取り除いています。

そして、この部分で最後の1文字を濁音を清音に変え、小文字を大文字に変換しています。

last_spell = small2large(unicodedata.normalize("NFKD", message.body['text'][-1])[0])

 

 最終的なコード

#reply.py
from slackbot.bot import respond_to
import pandas as pd
import re
import random
import unicodedata

eki = pd.read_excel('wor_dict.xlsx', header=1)
eki = eki.dropna(subset=['読み'])
word_list = eki.groupby('読み')['読み'].count()
first_list = list(pd.Series(word_list.index.values, word_list.values))
w_list = list(pd.Series(word_list.index.values, word_list.values))
history = [w_list[0]]
flag = False
last_word = w_list[0]

SMALL_TO_NORMAL = {
    'ぁ': 'あ', 'ぃ': 'い', 'ぅ': 'う', 'ぇ': 'え', 'ぉ': 'お',
    'っ': 'つ',
    'ゃ': 'や', 'ゅ': 'ゆ', 'ょ': 'よ',
}
def small2large(a):
    if a in SMALL_TO_NORMAL:
        return SMALL_TO_NORMAL[a]
    else:
        return a
print('load finished!')
@respond_to(r'.*')
def siritori_func(message):
    global flag
    global last_word
    re_hiragana = re.compile(r'^[あ-ん]+$')
    status_hira = re_hiragana.fullmatch(message.body['text'])  # fullmatch:完全一致
    if status_hira:
        if message.body['text'] == 'しりとり':
            message.reply('しりとりを開始するよ!\n駅名をひらがなで入力してね。同じ駅名は2回以上使用してはならないよ!\n最初の単語は:\n' + last_word)
            w_list.remove(last_word)
            flag = True
        if flag:
            if message.body['text'] in first_list:
                if message.body['text'] in history:
                    message.reply('それはもう使った単語だよ!')
                else:
                    if message.body['text'][0] != last_word[-1]:
                        message.reply(last_word[-1]+"から始まる駅名を答えてね")
                    else:
                        history.append(message.body['text'])
                        w_list.remove(message.body['text'])
                        last_spell = small2large(unicodedata.normalize("NFKD", message.body['text'][-1])[0])
                        rep_list = [word for word in w_list if word[0] == last_spell]
                        if len(rep_list) >= 1:
                            last_word = random.choice(rep_list)
                            message.reply('次の駅名は:'+last_word)
                            history.append(last_word)
                            w_list.remove(last_word)
                        elif len(rep_list) == 0:
                            message.reply('もう知ってる駅名ないや!\n僕の負けだね。。おめでとう!')
                        if message.body['text'][-1] == 'ん':
                            message.reply('「ん」がついたから君の負けだよ!')
            elif last_word == 'あいおい':
                pass
            else:
                message.reply("その駅は知らないな~。\n別の駅を探してみて!")
    elif message.body['text'] == 'exit':
        message.reply("しりとりを終えるよ。お疲れ様~")
        return 0
    else:
        message.reply("平仮名で駅名を入力してね~")



 

この状態で、run.pyをすれば、slack上でまともにしりとりをしてくれるはずです!

Dockerで動かす

しりとりボットをDockerfile化

さて!!動くようになったので、これで終わりでも問題はないですが、せっかくならdockerで動かせるようにして、どんな環境でも動いてほしいですよね!!!!!(誰が使うのか知りませんが!)

それでは、まず、SiritoriBotProjectの階層で、Dockerfileを作成してください!

そして、その中身に

#Dockerfile
#実行環境
FROM python:3.6
#Dockerコンテナでの作業ディレクトリ
WORKDIR /usr/src/app
# ソースコードを格納する(ローカル環境のsrcディレクトリを/usr/src/appの部分に全てコピーする)
COPY src ./
# ライブラリのインストール
RUN pip install -r requirements.txt
# Pythonを実行する
ENTRYPOINT ["python", "run.py"]

 

これだけでOKです!

これを記述した後は、「docker build」コマンドでDockerコンテナの起動、構成、Dockerイメージの作成をします!

docker build [ -t {イメージ名} [ :{タグ名} ] ] {Dockerfileのあるディレクトリ}

これに沿って記述をすると

docker build -t siritori_bot .

このようになります。ちなみに、siritori_botの部分の名前は何でもOKです。

これで、コンテナが作成されました。

確認をします。これで、先ほど作成したイメージがあればOKです。

docker images

 

次に、作成したコンテナを実行します。

docker run --rm -it siritori_bot

これで、今までと同じような挙動を示せばOKです!

あとは、slackで「しりとり」と入力をすれば動きます。

docker-composeでしりとりボットをサービス化

Dockerfileと同じ階層に、 docker-compose.ymlファイルを作成してください。

今からは、同じ動作をするけれども、先ほどに比べて実行するまでに必要なコマンドを1つ減らすために記述です。

今回のアプリケーションではあまりうれしさはありませんが、アプリケーションによっては、複数のコンテナを作成することがあります。

その一つ一つを先のようにコマンドを実行することは手間ですし、ミスの原因になります。

そのため、docker-compose.ymlを作成して、

docker-compose up

を実行するだけで複数のコンテナを一括で作成し、それを実行するまでを行ってくれるようにするのです!

今回は記述内容は極めて簡単です。

#docker-compose.yml
version: '3'
services:
  siritori: 
    build: . #同じdirのdockerfile

これだけです!

docker-compose up

それでは、実行をしてみてください!

slackで動作確認をしてみてください!

以上で完成です!

最後に

意外と、盛りだくさんになってしまいました!

slack連携と、dockerの使い方がなんとなく分かって頂けたのではないかと思います!

せっかくプログラミングをするなら動くものを作りたいという気持ちはものすごくわかります。

なので、こういったものも積極的に取り扱えたらと思います。

最近FastAPIについても調べたので、そちらも少しまとめることができたらと思います!

最後まで読んで頂きありがとうございました!!

オススメのプログラミングスクールをご紹介

タイピングもままならない完全にプログラミング初心者から

アホいぶきんぐ
アホいぶきんぐ
プログラミングってどこの国の言語なの~?

たった二ヶ月で

いぶきんぐ
いぶきんぐ
え!?人工知能めっちゃ簡単にできるじゃん!

応用も簡単にできる…!!

という状態になるまで、一気に成長させてくれたオススメのプログラミングスクールをご紹介します!

テックアカデミーのPython+AIコースを受講した僕が本音のレビュー・割引あり! というプログラミング完全初心者だった僕が Tech Academy(テックアカデミー)のPython×AIコース を二ヶ月間...

COMMENT

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です