Rubyで黒澤ルビィを作る（Ruby + LINE Messaging API）


[黒澤ルビィ](https://youtu.be/Ksf_gq6fZZM)、可愛いですよね。



LINE上で喋るルビィちゃんを作りました。Rubyで。

![Rubyボット01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327301/6a91101b-f50a-21e9-b3c2-a39759782d16.png)


以下の仕組みで動いています。

- `LINE Messaging API`を用いて、「黒澤ルビィ」としてLINEメッセージの受信、送信を行う。
- `Ruby(Ruby on rails)`で`LINE Messaging API`とやり取りするAPIサーバを作る。
 - メッセージを形態素解析し、その結果をもとにマルコフ連鎖で新しく文章を生成する


## LINEアカウントを作る

前準備としてルビィちゃんとして動くアカウント（厳密に言うと、Messaging APIのチャネル）が必要です。それをつくります。

[LINE Developers](https://developers.line.biz/ja/)から登録します。
![Rubyボット02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327301/d1090b66-598d-0675-43b6-95be8d29cd60.png)
`Create a new channel`から新しく作ります。なお、作るときにどういうTypeか聞かれると思います。今回作りたいものは[Messaging API](https://developers.line.biz/ja/services/messaging-api/)で作ることができるので、該当するものを選択してください。

無事作成できると、アクセストークンを取得できます。
また、アカウントのアイコンや自己紹介欄などを設定できます。[^1]

[^1]: 開発用のアカウントも合わせて作ったりしています。ちなみに画像は友達が描いたやつです。


## APIサーバを作る
画面を持つ予定はないので、APIモードでRailsプロジェクトを作ります。
`line-bot-api`gem[^2]を入れます。先程作成したアカウントのアクセストークンを使ってLINEとやりとりできるようになります。

[^2]: [line/line-bot-sdk-ruby: LINE Messaging API SDK for Ruby](https://github.com/line/line-bot-sdk-ruby)

### Controllerを作る
先程作成したアカウントに対してWebhook URLを設定すると、そのアカウントに対して送信したメッセージをトリガーにしてLINEがリクエストを送ってくれます。
具体的なリクエストの中身については[Messaging APIリファレンス](https://developers.line.biz/ja/reference/messaging-api/#message-event)を参照してください。以下の例はテキストメッセージに反応して、「メッセージ」と返すだけの仕組みです。

``` ruby
def client
  @client ||= Line::Bot::Client.new { |config|
    config.channel_secret = ENV["LINE_CHANNEL_SECRET"]
    config.channel_token = ENV["LINE_CHANNEL_TOKEN"]
  }
end

def callback
  body = request.body.read

  signature = request.env['HTTP_X_LINE_SIGNATURE']
  head :bad_request unless client.validate_signature(body, signature)

  events = client.parse_events_from(body)

  events.each do |event|
    next unless event.is_a?(Line::Bot::Event::Message)
    next unless event.type == Line::Bot::Event::MessageType::Text

    client.reply_message(event['replyToken'], message)
  end
  head :ok
end

def message
  # cf. https://developers.line.biz/ja/reference/messaging-api/#text-message
  {
    "type": 'text',
    "text": 'メッセージ'
  }
end

```

この`message`に相当する部分を、マルコフ連鎖で生成した文章にさせます。

## マルコフ連鎖で文章を作る
マルコフ連鎖による文章作成の大枠については解説しているものがたくさんあるので、詳しくは「マルコフ連鎖　文章作成」とかで検索してください。

ざっくり説明すると、あらかじめ形態素解析（言語で意味をもつ最小単位に分解すること）によって言葉のつながりを学習させ、ある単語に対してそれに続く言葉を過去の学習から確率的に選択していくことでそれっぽい文章ができるやつです。

### 形態素解析する
形態素解析は、[Yahoo!が提供している日本語形態素解析](https://developer.yahoo.co.jp/webapi/jlp/ma/v1/parse.html)を使っています。MeCab(Natto)を使って、オタク用語に特化した辞書を入れるのもアリかなと思います。

LINEメッセージが送られたらそのメッセージをそのまま形態素解析します。結果をパースし、言葉と品詞がセットとなったハッシュの配列にします。[^3]
[^3]: 以下を参考にしました。[Ruby on Rails で Yahoo のテキスト解析 APIを使う-スケ郎のお話](https://www.sukerou.com/2018/06/yahoo-api.html)




単語として保存する`Word`モデルと、つながりを保存する`MarkovDic`モデルを作り、それぞれ保存します。

`Word`モデル

カラム名 | 型 | 説明
--- | --- | ---
pos | String | 品詞（例：名詞）
surface | String | 単語（例：庭）


`MarkovDic`モデル

カラム名 | 型 | 説明
--- | --- | ---
prefix_1 | String | 単語のつながりをA-B-Cと表現したときのA　（例：私）
prefix_2 | String | 単語のつながりをA-B-Cと表現したときのB　（例：は）
suffix | String | 単語のつながりをA-B-Cと表現したときのC　（例：犬）


``` ruby
  # 形態素解析を行い、WordレコードとMarkovDicレコードを作成する。名詞の配列を返す。
  def record(text, user)
    text = remove_url(text)  # URLを取り除いたりしている
    morphological_words = exec(text)

    poses = save_words(morphological_words, user)
    save_markov_dics(morphological_words)

    poses
  end

  # 単語を保存する。
  def save_words(morphological_words)
    words = []

    morphological_words.each do |word|
      w = Word.new(word)
      words << w.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    Word.insert_all(words) if words.present?
  end

  def save_markov_dics(morphological_words)
    markov_dics = []
    morphological_words.each_cons(3) do |p1, p2, suf|
      next if p1['surface'] == "\n"
      next if p2['surface'] == "\n"

      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: suf['surface'])
      md.suffix = 'END_OF_SENTENCE' if md.suffix == "\n" # 文章の終わりを意味するフラグ
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    eos = morphological_words.last(2)
    if eos.size == 2
      p1 = eos[0]
      p2 = eos[1]
      md = MarkovDic.new(prefix_1: p1['surface'], prefix_2: p2['surface'], suffix: 'END_OF_SENTENCE')
      markov_dics << md.attributes.compact!.merge({ created_at: now, updated_at: now })
    end
    MarkovDic.insert_all(markov_dics) if markov_dics.present?
  end
```

### 文章を作る

LINEに送られたメッセージを保存することができたので、今度は文章を作ります。
始まりの言葉は受け取ったLINEのメッセージからランダムに名詞で始まる`MarkovDic`を選んでいます。
ざっくり以下のような感じです。

```ruby
  def create_sentence(text, markov_dic)
    return text if markov_dic.suffix == 'END_OF_SENTENCE'

    next_markov_dic = MarkovDic.where(prefix_1: markov_dic.prefix_2, prefix_2: markov_dic.suffix).sample(1).first
    return text unless next_markov_dic

    text << markov_dic.suffix
    create_sentence(text, next_markov_dic)
  end

  def prefixes
    prefix_1 + prefix_2
  end
```


## Webhook URLを設定する
作成したAPIサーバをデプロイします。（今回はHerokuにデプロイしました。）
最初に作成したアカウントの設定画面でWebhook URLを設定し、メッセージがきたタイミングでLINEがリクエストを送ってくれるようにします。
![Rubyボット03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327301/cde3b72c-ca8f-c75d-bd6f-68f01b1e0d66.png)

LINEグループに追加します。いい感じにレスポンスしてくれます。

![Rubyボット05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327301/dfbf7141-de27-ed36-f5d9-932ca4776b23.png)


## その他
- グループラインにルビィちゃんを混ぜたところ、「かわいい」「キメラが誕生した」「新しいおもちゃ」と非常に好評でした。
- [クソデカ羅生門](https://anond.hatelabo.jp/20200611125508)を覚えさせた人がいてめちゃくちゃになりました。
- グループラインにBotアカウントは一つまでしか入れられないようです。黒澤姉妹で話すことはできませんでした。
- 常に返信がくるとグループラインがやかましくなるので、ランダムで投稿したりしなかったりさせています。（ちなみに直接ルビィちゃんに話しかけると100%返信が来ます。）
- [push_message](https://developers.line.biz/ja/reference/messaging-api/#send-reply-message)を使うとルビィちゃんから話しかけることもできます。
- 本当はアカウントを紹介したいのですが、記録した言葉の中に含まれる個人情報を考慮すると中々難しいものがあります。
- 今の所ルビィちゃん要素が0なので、ルビィちゃんっぽくさせたいです。

## まとめ
`LINE Messaging API`と`Ruby`を用いて黒澤ルビィを作りました。

LINE Botは意外と簡単に作ることができるので、ぜひ作ってみてください。

追記：リポジトリは[こちら]()で公開しております。