Discordで対話可能なマルコフ連鎖BOTを作った(Python)

# あらまし


住み着いているDiscordサーバで対話のできるBOTが動かせたら面白いなと思って、とりあえず簡単に動きそうなマルコフ連鎖を使ったBOTを作ってみた。
入力されたテキストを含むマルコフ連鎖のモデルを作って、それを元に返信する単純な学習機能を入れてます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3823377/054dd1df-6cfb-6975-69de-8d9681dbeea0.png)


## 環境

私はUbuntuServerでscreenの中にvenvを建てて動かしてます。
こいつらを入れて動かします。
```ruby:ライブラリのインストール
python3 -m pip install -U discord.py 
py -3 -m pip install -U discord.py #Windowsの場合はこっち
pip install mecab-python3
pip install unidic-lite 
pip install markovify
```

## 中身

BOTを動かしたいサーバのチャンネルIDとBOTのトークンは適宜書き換えてください。
discordbot.pyと同ディレクトリにm.txtを置いて動かします。m.txtにはある程度文章を入れておきます。
マルコフ連鎖に使うデータが無いと返信が帰ってこないのでモデル生成の部分は state_size=1 としています。会話を進めてある程度m.txtに文章量が集まったら1ずつ増やすといいです。


```ruby:discordbot.py
import discord
import MeCab
import unidic_lite
import markovify
import random


CHANNELID = XXXXXXXXXXXXXXXXXXX #チャンネルID

TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' #BOTのトークン

intents = discord.Intents.default()
intents.message_content = True
intents.messages = True

client = discord.Client(intents=discord.Intents.all())



@client.event
async def on_ready():
    print('---------------------------------------------------')
    print('                  起動しました                      ')
    print('---------------------------------------------------')



@client.event
async def on_message(message):
    if message.channel.id != CHANNELID:
        return
    if not message.author.bot:
        channel = client.get_channel(CHANNELID)


        m1 = (message.content) 
  
        # 末尾を"。"に定める
        matubi = (m1[-1])
        if matubi == "。":
            m2 = m1
        else:
            m2 = m1 + "。"

        # 学習データに書き込み
        f = open('m.txt', 'a')
        f.write(m2)
        f.write("\n")
        f.close()

        # 学習データを読み込み
        with open('m.txt', 'r') as f:
            kotoba = f.read().split("\n")

        print(m2)

        # 話題になりそうなワードを抽出
        mecab = MeCab.Tagger() 
        result = mecab.parse(m2)

        l1 = [line.split()[0] for line in mecab.parse(m2).splitlines()
        if '0' in line.split()[-1]]

        l2 = [line.split()[0] for line in mecab.parse(m2).splitlines()
        if '1' in line.split()[-1]]

        l3 = l1 + l2

        #リストを作成できなかった場合冒頭の単語を取る
        kazu = len(l3)
        if kazu < 1:
            words = mecab.parse(m2).split()
            #print(words)
            wadai = words[0]
        else:
            wadai = random.choice(l3)

        # 文章の処理
        breaking_chars = ['(', ')', '[', ']', '"', "'"]
        splitted_kotoba = ''

        for line in kotoba:

            parsed_nodes = mecab.parseToNode(line)

            while parsed_nodes:
                try:
                    if parsed_nodes.surface not in breaking_chars:
                        splitted_kotoba += parsed_nodes.surface

                    if parsed_nodes.surface != '。' and parsed_nodes.surface != '、':
                        splitted_kotoba += ' '

                    if parsed_nodes.surface == '。':
                        splitted_kotoba += '\n'

                except UnicodeDecodeError as error:
                    print('Error : ', line)
                finally:
                    parsed_nodes = parsed_nodes.next

        # モデル作成
        model = markovify.NewlineText(splitted_kotoba,well_formed=False,state_size=1)

        # wadaiから続く文章を生成
        sentence = model.make_sentence_with_start(beginning=wadai,strict=False)
        if sentence is not None:
            #。を除く
            out = (''.join(sentence.split()))
            out = out[:-1]
            print(out)
        else:
            print('None')
            out = 'None'

        # discordに送信
        await message.channel.send(out)


client.run(TOKEN)
```
雑にコピペしたりして書いたので蛇足な部分があるかも。

## おわりに
偶に偶然人間らしく振る舞ったりしてなかなか面白いです。