Skip to main content

lib/slack/state.ex

defmodule Slack.State do
  @moduledoc """
  Slack state.
  """

  @behaviour Access

  def fetch(client, key)
  defdelegate fetch(client, key), to: Map

  def get(client, key, default)
  defdelegate get(client, key, default), to: Map

  def get_and_update(client, key, function)
  defdelegate get_and_update(client, key, function), to: Map

  def pop(client, key)
  defdelegate pop(client, key), to: Map

  defstruct [
    :process,
    :client,
    :token,
    :me,
    :team,
    bots: %{},
    channels: %{},
    groups: %{},
    users: %{},
    ims: %{}
  ]

  defp safe_map_getter(key) do
    Access.key(key, %{})
  end

  defp safe_list_getter(key) do
    Access.key(key, [])
  end

  @doc """
  Pattern matches against messages and returns updated Slack state.
  """
  @spec update(Map, Map) :: {Symbol, Map}
  def update(%{type: "channel_created", channel: channel}, slack) do
    put_in(slack, [:channels, channel.id], channel)
  end

  def update(%{type: "channel_joined", channel: channel}, slack) do
    slack
    |> put_in([:channels, channel.id], channel)
    |> put_in([:channels, channel.id, :is_member], true)
  end

  def update(%{type: "group_joined", channel: channel}, slack) do
    put_in(slack, [:groups, channel.id], channel)
  end

  def update(%{type: "channel_left", channel: channel_id}, slack) do
    put_in(slack, [:channels, channel_id, :is_member], false)
  end

  def update(%{type: "group_left", channel: channel}, slack) do
    update_in(slack, [:groups], &Map.delete(&1, channel))
  end

  Enum.map(["channel", "group"], fn type ->
    plural_atom = String.to_atom(type <> "s")

    def update(%{type: unquote(type <> "_rename"), channel: channel}, slack) do
      put_in(slack, [unquote(plural_atom), safe_map_getter(channel.id), :name], channel.name)
    end

    def update(%{type: unquote(type <> "_archive"), channel: channel}, slack) do
      put_in(slack, [unquote(plural_atom), safe_map_getter(channel), :is_archived], true)
    end

    def update(%{type: unquote(type <> "_unarchive"), channel: channel}, slack) do
      put_in(slack, [unquote(plural_atom), safe_map_getter(channel), :is_archived], false)
    end

    def update(
          %{
            type: "message",
            subtype: unquote(type <> "_topic"),
            channel: channel,
            user: user,
            topic: topic
          },
          slack
        ) do
      put_in(slack, [unquote(plural_atom), safe_map_getter(channel), :topic], %{
        creator: user,
        last_set: System.system_time(:second),
        value: topic
      })
    end

    def update(
          %{type: "message", subtype: unquote(type <> "_join"), channel: channel, user: user},
          slack
        ) do
      update_in(
        slack,
        [unquote(plural_atom), safe_map_getter(channel), safe_list_getter(:members)],
        &Enum.uniq([user | &1])
      )
    end

    def update(
          %{type: "message", subtype: unquote(type <> "_leave"), channel: channel, user: user},
          slack
        ) do
      update_in(
        slack,
        [unquote(plural_atom), safe_map_getter(channel), safe_list_getter(:members)],
        &(&1 -- [user])
      )
    end
  end)

  def update(%{type: "team_rename", name: name}, slack) do
    put_in(slack, [:team, :name], name)
  end

  def update(%{type: "presence_change", user: user, presence: presence}, slack) do
    put_in(slack, [:users, user, :presence], presence)
  end

  def update(%{type: "presence_change", users: users, presence: presence}, slack) do
    Enum.reduce(users, slack, fn user, acc ->
      put_in(acc, [:users, user, :presence], presence)
    end)
  end

  def update(%{type: "team_join", user: user}, slack) do
    put_in(slack, [:users, user.id], user)
  end

  def update(%{type: "user_change", user: user}, slack) do
    update_in(slack, [:users, Access.key(user.id, %{})], &Map.merge(&1, user))
  end

  Enum.map(["bot_added", "bot_changed"], fn type ->
    def update(%{type: unquote(type), bot: bot}, slack) do
      put_in(slack, [:bots, bot.id], bot)
    end
  end)

  def update(%{type: "im_created", channel: channel}, slack) do
    put_in(slack, [:ims, channel.id], channel)
  end

  def update(%{type: _type}, slack) do
    slack
  end
end