Current section
Files
Jump to
Current section
Files
lib/messages/message.ex
defmodule Wax.Messages.Message do
@moduledoc """
The Whatsapp message
It defines a structure and main functions to work with it to modify
the message to send
"""
alias Wax.Messages.{
Contact,
Context,
Interactive,
Location,
Media,
Template,
Text
}
@typep whatsapp_media_id :: String.t()
@typep message_type ::
:contact
| :interactive
| :location
| :template
| :text
| Media.media_type()
@typep whatsapp_id :: String.t()
@type t :: %__MODULE__{
audio: Media.t(),
contacts: [Contact.t()],
context: Context.t(),
document: Media.t(),
image: Media.t(),
interactive: Interactive.t(),
location: Location.t(),
messaging_product: atom(),
preview_url: boolean(),
recipient_type: atom(),
status: String.t(),
template: Template.t(),
text: Text.t(),
to: whatsapp_id(),
type: message_type(),
video: Media.t()
}
@message_types [
:audio,
:contact,
:document,
:image,
:interactive,
:location,
:template,
:text,
:video
]
defstruct audio: nil,
contacts: [],
context: nil,
document: nil,
image: nil,
interactive: nil,
location: nil,
messaging_product: :whatsapp,
preview_url: false,
recipient_type: :individual,
status: nil,
template: nil,
text: nil,
to: nil,
type: :text,
video: nil
defimpl Jason.Encoder do
@base_fields [:messaging_product, :recipient_type, :to, :type]
def encode(value, opts) do
fields = @base_fields
fields =
case Map.get(value, :type) do
:audio -> [:audio | fields]
:document -> [:document | fields]
:image -> [:image | fields]
:interactive -> [:interactive | fields]
:template -> [:template | fields]
:video -> [:video | fields]
_ -> [:text | fields]
end
value
|> Map.take(fields)
|> Jason.Encode.map(opts)
end
end
@doc """
Creates a new Message structure
"""
@spec new(whatsapp_id()) :: __MODULE__.t()
def new(to) when is_binary(to) do
to = String.replace(to, " ", "")
%__MODULE__{to: to}
end
@spec set_type(__MODULE__.t(), String.t()) :: __MODULE__.t()
def set_type(%__MODULE__{} = message, type) when type in @message_types do
%{message | type: type}
end
@doc """
Adds a text object to the message
"""
@spec set_text(__MODULE__.t(), String.t()) :: __MODULE__.t()
def set_text(%__MODULE__{} = message, body, preview_url \\ false) do
text = %Text{body: body, preview_url: preview_url}
%{message | text: text}
end
@doc """
Adds an audio object to the message
"""
@spec add_audio(__MODULE__.t(), whatsapp_media_id()) :: __MODULE__.t()
def add_audio(%__MODULE__{} = message, media_id) do
media = Media.new_audio(media_id)
%{message | audio: media}
end
@doc """
Adds a document object to the message
"""
@spec add_document(__MODULE__.t(), whatsapp_media_id(), String.t(), String.t()) ::
__MODULE__.t()
def add_document(%__MODULE__{} = message, media_id, filename, caption \\ nil) do
media = Media.new_document(media_id, filename, caption)
%{message | document: media}
end
@doc """
Adds an image object to the message
Images also accept a text caption that can be added on the same message
"""
@spec add_image(__MODULE__.t(), whatsapp_media_id(), String.t() | nil) :: __MODULE__.t()
def add_image(%__MODULE__{} = message, media_id, caption \\ nil) do
media = Media.new_image(media_id, caption)
%{message | image: media}
end
@doc """
Adds an interactive object to the message
"""
@spec add_interactive(__MODULE__.t(), Interactive.t()) :: __MODULE__.t()
def add_interactive(%__MODULE__{} = message, %Interactive{} = interactive) do
%{message | interactive: interactive}
end
@doc """
Adds a template object to the message
"""
@spec add_template(__MODULE__.t(), Template.t()) :: __MODULE__.t()
def add_template(%__MODULE__{} = message, %Template{} = template) do
%{message | template: template}
end
@doc """
Adds a video object to the message
Videos also accept a text caption that can be added on the same message
"""
@spec add_video(__MODULE__.t(), whatsapp_media_id(), String.t() | nil) :: __MODULE__.t()
def add_video(%__MODULE__{} = message, media_id, caption \\ nil) do
media = Media.new_video(media_id, caption)
%{message | video: media}
end
@doc """
Validates a message
Checks if a message is valid to be sent to the Cloud API
#TODO: Move this functionality to its own module
"""
@spec validate(__MODULE__.t()) :: :ok | {:error, String.t()}
def validate(%__MODULE__{to: to}) when to in ["", nil] do
{:error, "Missing recipient number of message"}
end
def validate(%__MODULE__{type: :text} = message) do
case message.text do
%Text{body: text_body} when text_body in ["", nil] ->
{:error, "A text body is required when sending a text message"}
%Text{} ->
:ok
_ ->
{:error, "Text field is required"}
end
end
def validate(%__MODULE__{type: :image} = message) do
case message.image do
%Media{id: id} when is_binary(id) ->
:ok
_ ->
{:error, "Image field is required. Use add_image/3 to add_one"}
end
end
def validate(%__MODULE__{type: :audio, audio: %Media{id: id}}) when is_binary(id) do
:ok
end
def validate(%__MODULE__{type: :document, document: %Media{id: id, filename: filename}})
when is_binary(id) do
case Path.extname(filename) do
"" -> {:error, "Document filename has no extension"}
_extension -> :ok
end
end
def validate(%__MODULE__{type: :interactive, interactive: %Interactive{} = interactive}) do
Interactive.validate(interactive)
end
def validate(%__MODULE__{type: :template, template: %Template{}}) do
:ok
end
def validate(%__MODULE__{type: :video, video: %Media{id: id}}) when is_binary(id) do
:ok
end
def validate(%__MODULE__{type: :audio}) do
{:error, "Audio field is required. Use add_audio/3 to add one."}
end
def validate(%__MODULE__{type: :document}) do
{:error, "Document field is required. Use add_document/3 to add one."}
end
def validate(%__MODULE__{type: :interactive}) do
{:error, "Interactive field is required. Use add_interactive/2 to add one."}
end
def validate(%__MODULE__{type: :template}) do
{:error, "Template field is required. Use add_template/2 to add one."}
end
def validate(%__MODULE__{type: :video}) do
{:error, "Video field is required. Use add_video/3 to add one."}
end
def validate(%__MODULE__{type: type}) do
{:error, "Unsupported message type: #{type}"}
end
end