LoginSignup
6
3

More than 5 years have passed since last update.

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

Posted at

ElixirのPlugを使って
以下のようなHTTPステータスコードの説明を表示してくれる
SlackのSlash Commandsを作成してみました

スクリーンショット 2017-08-06 14.54.01.png

コードは github にアップしています

使い方

$ 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コマンドをつけてプロジェクトを作成。

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

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

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

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を使ってスクレイピングを行う で作成したものをベースに
スクレイピングした内容を Agent
で保持しておくように変更

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

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

Application

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の起動

6
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
3