Posted at

AngularとElixir/PhoenixでガッキーBotを作った

More than 1 year has passed since last update.


はじめに

まだまだ改良の余地があるんですが、こんな感じで作ることができます。

gakkybot.gif

PhoenixのChannelを使ってWebSocketによる通信を使っています。正直クライアントはPhoenixだけで出来るんですが、あえてAngularを使いました。単純にAngularだとどう実装するか気になったので作ってみました。

最良な方法があればぜひご教示ください!


開発環境

Elixir 1.6.1 (compiled with OTP 20)

Phoenix 1.3

Angular CLI: 1.7.3

Node: 8.9.3


Elixir/Phoenix側の実装

基本的には公式のドキュメントにあるものをそのまま実装するとほぼ同じものが作られます


プロジェクトの作成

プロジェクトを作成します。クライアントはAngularなので--no-htmlと--no-brunchオプションもつけて良いと思います。

$ mix phx.new chat


必要なライブラリをインストール

今回は非常に簡単な形態素解析をするので、mecabのライブラリーをインストールします。


mix.exs

{:mecab, "~> 1.0" },


mecabの使い方などはこちらの記事が参考になります。

@piacere さんのElixirで弱々しいAI#1「MeCabで文章パース」

です


Channelを使う

デフォルトで作成されているuser_socket.exの最初の部分のコメントを外します。


lib/chat_web/channels/user_socket.ex

defmodule ChatWeb.UserSocket do

use Phoenix.Socket

## Channels
channel "room:*", ChatWeb.RoomChannel # コメントを外す

#省略....


ここで指定している「ChatWeb.RoomChannel」モジュールを作成する必要があります。


RoomChannelモジュールを作成する

join関数で指定したチャンネル名しか接続できないようしています。今回でいうとroom:helloという名前であれば接続できる感じです。

handle_in関数がクライアントからメッセージを受ける際に走る関数です。

そのあとのbroadcast関数でクライアントにメッセージを送信できます。


lib/chat_web/channels/room_channel.ex

defmodule ChatWeb.RoomChannel do

use Phoenix.Channel

def join("room:hello", _message, socket) do
{:ok, socket}
end
def join("room:" <> _private_room_id, _params, _socket) do
{:error, %{reason: "unauthorized"}}
end

def handle_in("msg", %{"body" => body}, socket) do
text = reply(body)
broadcast! socket, "msg", %{body: "よっ!" <> text <> "!"}
{:noreply, socket}
end

# mecabを使った形態素解析
defp reply(msg) do
Mecab.parse(msg)
|> Enum.map(&(text_parse(&1)))
|> Enum.join
end

defp text_parse(%{"part_of_speech_subcategory2"=>"人名","part_of_speech"=>"名詞"} = params) do
params["lexical_form"]
end

defp text_parse(_) do
""
end
end


Phoenix側はこれにて終了です。とっても簡単ですね。


Angular側の実装

こちらはデザイン部分はAngular Materialを使っていますが、デザイン部分はみなさん好きなデザインを使えば良いと思いますので、割愛します。

WebSocketの通信部分について書きます。


プロジェクトの作成

好きな名前でプロジェクトを作成してください

$ ng new client


必要なライブラリをインストール

npmでphoenix.jsを使えるようにインストールします。

https://www.npmjs.com/package/phoenix

$ npm install phoenix --save


実装する

chatコンポーネントを作成します

$ ng g compoent chat

そしてWebSocket通信部分のロジックを追加します。

注目すべきはngOninit関数の中身とonSendChannel関数のthis.channel.push('msg', { body: this.msg })部分です。

ngOnInit関数部分はPhoenix公式のドキュメントのjs実装部分をそのまま実装しただけです。


chat.component.ts

import { Component, OnInit } from '@angular/core';

import { Socket } from 'phoenix/priv/static/phoenix';

@Component({
selector: 'app-chat',
templateUrl: './chat.component.html',
styleUrls: ['./chat.component.css']
})
export class ChatComponent implements OnInit {
title = 'app';
msg = '';
socket: any;
channel: any;
messageList = [];
constructor() {}
ngOnInit() {
this.socket = new Socket('ws://localhost:4000/socket', {
logger: (kind, msg, data) => {
console.log(`${kind}: ${msg}`, data);
},
transport: WebSocket
});
this.socket.connect();
this.channel = this.socket.channel('room:hello', {});
this.channel
.join()
.receive('ok', resp => {
console.log('Joined successfully', resp);
})
.receive('error', resp => {
console.log('Unable to join', resp);
});
this.channel.on('msg', payload => {
this.messageList.push({
msg: payload.body,
from: 'ガッキー',
figureClass: 'kaiwa-img-left',
img: 'https://pbs.twimg.com/media/DMonFSJVoAA_yKJ.jpg',
textClass: 'kaiwa-text-right'
});
});
}

onSendChannel(event) {
if ( event.keyCode === 13) {
this.channel.push('msg', { body: this.msg });
this.messageList.push({
msg: this.msg,
from: 'ゆじかわ',
figureClass: 'kaiwa-img-right',
img: 'https://qiita-image-store.s3.amazonaws.com/0/199445/profile-images/1524312896',
textClass: 'kaiwa-text-left'
});
this.msg = '';
}
}
}


あとはmessageListにどんどんメッセージを突っ込んでいってそれをhtmlでグリグリ回して表示しているだけです。


おわりに

思った以上に簡単に実装できて、驚きでした。これで好きなだけガッキーと会話できます(きもい)

興味を持った方はぜひ一度試してみてはいかがでしょうか?