lib/conversations_client.ex

defmodule ExMicrosoftBot.Client.Conversations do
  @moduledoc """
  This module provides the functions for conversations
  """

  import ExMicrosoftBot.Client,
    only: [deserialize_response: 2, delete: 1, get: 1, post: 2, put: 2]

  alias ExMicrosoftBot.Models
  alias ExMicrosoftBot.Client

  @doc """
  Create a new Conversation.

  @see [API Reference](https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#conversation-object)
  """
  @spec create_conversation(
          service_url :: String.t(),
          params :: Models.ConversationParameters.t()
        ) :: {:ok, Models.ConversationResourceResponse.t()} | Client.error_type()
  def create_conversation(service_url, %Models.ConversationParameters{} = params) do
    service_url
    |> conversations_url()
    |> post(params)
    |> deserialize_response(&Models.ConversationResourceResponse.parse/1)
  end

  @doc """
  This method allows you to send an activity to a conversation regardless of
  previous posts to a conversation.

  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_SendToConversation)
  """
  @spec send_to_conversation(conversation_id :: String.t(), activity :: Models.Activity.t()) ::
          {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def send_to_conversation(conversation_id, %Models.Activity{serviceUrl: service_url} = activity),
    do: send_to_conversation(service_url, conversation_id, activity)

  @doc """
  This method allows you to send an activity to a conversation regardless of
  previous posts to a conversation.

  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_SendToConversation)
  """
  @spec send_to_conversation(
          service_url :: String.t(),
          conversation_id :: String.t(),
          activity :: Models.Activity.t()
        ) :: {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def send_to_conversation(service_url, conversation_id, %Models.Activity{} = activity) do
    service_url
    |> conversations_url("/#{conversation_id}/activities")
    |> post(activity)
    |> deserialize_response(&Models.ResourceResponse.parse/1)
  end

  @doc """
  This method allows you to reply to an activity.
  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_ReplyToActivity)
  """
  @spec reply_to_activity(
          conversation_id :: String.t(),
          activity_id :: String.t(),
          activity :: Models.Activity.t()
        ) :: {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def reply_to_activity(
        conversation_id,
        activity_id,
        %Models.Activity{serviceUrl: service_url} = activity
      ),
      do: reply_to_activity(service_url, conversation_id, activity_id, activity)

  @doc """
  This method allows you to reply to an activity.
  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_ReplyToActivity)
  """
  @spec reply_to_activity(
          service_url :: String.t(),
          conversation_id :: String.t(),
          activity_id :: String.t(),
          activity :: Models.Activity.t()
        ) :: {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def reply_to_activity(service_url, conversation_id, activity_id, %Models.Activity{} = activity) do
    service_url
    |> conversations_url("/#{conversation_id}/activities/#{activity_id}")
    |> post(activity)
    |> deserialize_response(&Models.ResourceResponse.parse/1)
  end

  @doc """
  This function takes a ConversationId and returns an array of ChannelAccount[]
  objects which are the members of the conversation.
  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_GetConversationMembers).

  When ActivityId is passed in then it returns the members of the particular
  activity in the conversation.

  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_GetActivityMembers)
  """
  @spec get_members(
          service_url :: String.t(),
          conversation_id :: String.t(),
          activity_id :: String.t() | nil
        ) :: {:ok, [Models.ChannelAccount.t()]} | Client.error_type()
  def get_members(service_url, conversation_id, activity_id \\ nil) do
    path =
      if activity_id,
        do: "/#{conversation_id}/activities/#{activity_id}/members",
        else: "/#{conversation_id}/members"

    service_url
    |> conversations_url(path)
    |> get()
    |> deserialize_response(&Models.ChannelAccount.parse/1)
  end

  @doc """
  This function takes a conversation ID and a member ID and returns a
  ChannelAccount struct for that member of the conversation.

  @see [API Reference](https://github.com/microsoft/botbuilder-js/blob/5b164105a2f289baaa7b89829e09ddbeda88bfc5/libraries/botframework-connector/src/connectorApi/operations/conversations.ts#L733-L749).
  """
  @spec get_member(
          service_url :: String.t(),
          conversation_id :: String.t(),
          member_id :: String.t()
        ) :: {:ok, Models.ChannelAccount.t()} | Client.error_type()
  def get_member(service_url, conversation_id, member_id) do
    service_url
    |> conversations_url("/#{conversation_id}/members/#{member_id}")
    |> get()
    |> deserialize_response(&Models.ChannelAccount.parse/1)
  end

  @doc """
  This method allows you to upload an attachment directly into a channels blob
  storage.

  @see [API Reference](https://docs.botframework.com/en-us/restapi/connector/#!/Conversations/Conversations_UploadAttachment)
  """
  @spec upload_attachment(
          service_url :: String.t(),
          conversation_id :: String.t(),
          attachment :: Models.AttachmentData.t()
        ) :: {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def upload_attachment(service_url, conversation_id, %Models.AttachmentData{} = attachment) do
    service_url
    |> conversations_url("/#{conversation_id}/attachments")
    |> post(attachment)
    |> deserialize_response(&Models.ResourceResponse.parse/1)
  end

  @doc """
  Updates an existing activity. The activity struct is expected to have an ID.
  @see [API Reference](https://docs.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-api-reference?view=azure-bot-service-4.0#update-activity)
  """
  @spec update_activity(
          service_url :: String.t(),
          conversation_id :: String.t(),
          activity :: Models.Activity.t()
        ) :: {:ok, Models.ResourceResponse.t()} | Client.error_type()
  def update_activity(service_url, conversation_id, %Models.Activity{id: activity_id} = activity) do
    service_url
    |> conversations_url("/#{conversation_id}/activities/#{activity_id}")
    |> put(activity)
    |> deserialize_response(&Models.ResourceResponse.parse/1)
  end

  @doc """
  Deletes an existing activity. The returned content will always be empty.
  @see [API Reference](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/update-and-delete-bot-messages?tabs=rest#deleting-messages)
  """
  @spec delete_activity(
          service_url :: String.t(),
          conversation_id :: String.t(),
          activity_or_id :: Models.Activity.t() | String.t()
        ) :: {:ok, binary()} | Client.error_type()
  def delete_activity(service_url, conversation_id, %Models.Activity{id: activity_id}),
    do: delete_activity(service_url, conversation_id, activity_id)

  def delete_activity(service_url, conversation_id, activity_id) do
    service_url
    |> conversations_url("/#{conversation_id}/activities/#{activity_id}")
    |> delete()
    |> deserialize_response(nil)
  end

  defp conversations_url(service_url) do
    service_url = String.trim_trailing(service_url, "/")
    "#{service_url}/v3/conversations"
  end

  defp conversations_url(service_url, path),
    do: conversations_url(service_url) <> path
end