[Elixir] Plugを使ってSlash Commandsを作成してみた

Elixirの[Plug](https://github.com/elixir-plug/plug)を使って
以下のようなHTTPステータスコードの説明を表示してくれる
SlackのSlash Commandsを作成してみました

<img width="813" alt="スクリーンショット 2017-08-06 14.54.01.png" src="https://qiita-image-store.s3.amazonaws.com/0/55219/b3312bd7-73b8-e8b9-28c8-4c244e236923.png">

コードは [github](https://github.com/tamanugi/slash_bot_http_code) にアップしています

# 使い方

``` 
$ git clone https://github.com/tamanugi/slash_bot_http_code
$ cd slash_bot_http_code
$ mix deps.get
$ mix run --no-halt
```

# 作業メモ
## プロジェクトの作成, 依存モジュールのインストール

```
$ mix new slash_command_http_code --sup
```

supコマンドをつけてプロジェクトを作成。

次に依存するモジュールの追加

```ex:mix.exs

  defp deps do
    [
      {:plug, "~> 1.4.3"},
      {:cowboy, "~> 1.1.2"},
      {:poison, "~> 3.1.0"},
      {:httpoison, "~> 0.13.0"},
      {:floki, "~> 0.18.0"}
    ]
  end

```

```
$ mix deps.get
```

※slash commnadsを作成するだけなら `plug`, `cowboy`だけでOK

## Router

```ex:lib/slash_bot_http_code/router.ex
defmodule SlashBotHttpCode.Router do

  alias SlashBotHttpCode.HttpCode

  use Plug.Router
  plug Plug.Parsers, parsers: [:urlencoded]
  plug :match
  plug :dispatch

  get "/" do
    send_resp(conn, 200, "")
  end

  post "/" do
    %{"text" => text} = conn.params

    resp = text |> HttpCode.get |> process_resp |> Poison.encode!

    conn
    |> put_resp_content_type("application/json")
    |> send_resp(200, resp)
  end

  match _ do
    send_resp(conn, 404, "not found")
  end

  def process_resp(%{summary: summary, description: description}) do
    %{
      response_type: "in_channel",
      text: summary,
      attachments: [
        %{
          text: description
        }
      ]
    }
  end

  def process_resp(_) do
    %{text: "Not Found."}
  end
end
```

SlackからはPOSTで叩かれるので `post "/" do ~ end` に処理を書く
送信された内容は `conn.params` から取得できる

## HttpCode

```ex:lib/slash_bot_http_code/http_code.ex
defmodule SlashBotHttpCode.HttpCode do

  use Agent

  @wiki_url "https://ja.wikipedia.org/wiki/HTTP%E3%82%B9%E3%83%86%E3%83%BC%E3%82%BF%E3%82%B9%E3%82%B3%E3%83%BC%E3%83%89"

  def start_link(_init) do
    res = Agent.start_link(fn -> [] end, name: __MODULE__)
    init()
    res
  end

  def get(key) do
    Agent.get(__MODULE__,
      fn http_code_list ->
        http_code_list
        |> Enum.filter(fn item ->
          to_string(item[:status_code]) == key end)
        |> List.first
      end)
  end

  def init() do
    %{body: html} = HTTPoison.get! @wiki_url
    http_code_list = html
    |> Floki.find("dt, dd")
    |> parse

    Agent.update(__MODULE__, fn _ -> http_code_list end)
  end

  def parse(nodes), do: parse(nodes, [], %{})
  def parse([], result, _), do: result
  def parse([head | tail], result, tmp) do
    {tag, _, children_nodes} = head
    text = Floki.text(children_nodes)

    {result, tmp} = case tag do
      "dt" ->
        status_code = text |> String.replace(~r/ .*/, "") |> String.to_integer
        {[tmp | result], %{summary: text, status_code: status_code}}
      "dd" ->
        description =
          case tmp[:description] do
            nil -> text
            desc -> "#{desc}\n#{text}"
          end
       {result, tmp |> Map.put(:description, description)}
    end

    parse(tail, result, tmp)
  end

end

```

[[Elixir] Flokiを使ってスクレイピングを行う](http://qiita.com/tamanugi/items/5ef10e7da12cd08e94fa) で作成したものをベースに
スクレイピングした内容を Agent 
で保持しておくように変更

```ex
    res = Agent.start_link(fn -> [] end, name: __MODULE__)
    init()
    res
```

`start_link/1` の返り値は `Agent.start_link` の返り値にしないと
Supervisorに追加できない

## Application

```ex:lib/slash_bot_http_code/application.ex
defmodule SlashBotHttpCode.Application do
  # See https://hexdocs.pm/elixir/Application.html
  # for more information on OTP Applications
  @moduledoc false

  use Application

  def start(_type, _args) do

    children = [
      Plug.Adapters.Cowboy.child_spec(:http, SlashBotHttpCode.Router, [], [port: 4000]),
      {SlashBotHttpCode.HttpCode, []}
    ]

    opts = [strategy: :one_for_one, name: SlashBotHttpCode.Supervisor]
    Supervisor.start_link(children, opts)
  end
end
```

Plug & Cowboyの起動, HttpCodeの起動