lib/notifiex/services/slack.ex

defmodule Notifiex.Service.Slack do
  @moduledoc """
  Slack service for Notifiex.
  """

  @behaviour Notifiex.ServiceBehaviour

  @doc """
  Sends a message to the specified channel.

  `payload` should include the following:
  * `text`: The message text. (required)
  * `channel`: The channel to send the message to. (required)

  `options` should include the following:
  * `token`: Authentication token. (required)
  """
  @spec call(map, map) :: {:ok, binary} | {:error, {atom, any}}
  def call(payload, options) when is_map(payload) and is_map(options) do
    url = "https://slack.com/api/chat.postMessage"
    token = Map.get(options, :token)

    # send message (without file)
    send_message(payload, url, token)

    # fetch channels and files
    channels = Map.get(options, :channel_ids)
    files = Map.get(options, :files)

    # Send each file through the files.upload API
    if not is_nil(files) do
      for file <- files do
        if String.trim(file) != "" do
          send_files(file, channels, token)
        end
      end
    end
  end

  @spec send_message(map, binary, binary) :: {:ok, binary} | {:error, {atom, any}}
  defp send_message(_payload, nil, nil), do: {:error, {:missing_options, nil}}

  defp send_message(payload, url, token) do
    json_payload = Poison.encode!(payload)

    header = [
      {"Accept", "application/json"},
      {"Content-Type", "application/json; charset=utf-8"},
      {"Authorization", "Bearer " <> token}
    ]

    HTTPoison.start()

    case HTTPoison.post(url, json_payload, header) do
      {:ok, %HTTPoison.Response{body: response, status_code: 200}} ->
        {:ok, response}

      {:ok, %HTTPoison.Response{body: response}} ->
        {:error, {:error_response, response}}

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, {:error, reason}}

      _ = e ->
        {:error, {:unknown_response, e}}
    end
  end

  @spec send_files(binary, binary, binary) :: {:ok, binary} | {:error, {atom, any}}
  defp send_files(_files, nil, nil), do: {:error, {:missing_options, nil}}

  defp send_files(files, channels, token) do
    header = [
      {"Authorization", "Bearer " <> token}
    ]

    HTTPoison.start()

    # create a multipart request with files and channel IDs
    request = %HTTPoison.Request{
      method: :post,
      url: "https://slack.com/api/files.upload",
      headers: header,
      body:
        {:multipart,
         [
           {:file, files},
           {"channels", channels}
         ]}
    }

    response = HTTPoison.request(request)

    case response do
      {:ok, %HTTPoison.Response{body: response, status_code: 200}} ->
        {:ok, response}

      {:ok, %HTTPoison.Response{body: response}} ->
        {:error, {:error_response, response}}

      {:error, %HTTPoison.Error{reason: reason}} ->
        {:error, {:error, reason}}

      _ = e ->
        {:error, {:unknown_response, e}}
    end
  end
end