Current section
Files
Jump to
Current section
Files
lib/nostrum/api/message.ex
defmodule Nostrum.Api.Message do
@moduledoc """
Module for interacting with the Discord API's message endpoints.
See: https://discord.com/developers/docs/resources/message
"""
@moduledoc since: "0.10.1"
alias Nostrum.Api
alias Nostrum.Api.Helpers
alias Nostrum.Constants
alias Nostrum.Struct.Channel
alias Nostrum.Struct.Emoji
alias Nostrum.Struct.Message
alias Nostrum.Struct.User
import Nostrum.Snowflake, only: [is_snowflake: 1]
@doc ~S"""
Posts a message to a guild text or DM channel.
This endpoint requires the `VIEW_CHANNEL` and `SEND_MESSAGES` permissions. It
may situationally need the `SEND_MESSAGES_TTS` permission. It fires the
`t:Nostrum.Consumer.message_create/0` event.
If `options` is a string, `options` will be used as the message's content.
If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Options
* `:content` (string) - the message contents (up to 2000 characters)
* `:nonce` (`t:Nostrum.Snowflake.t/0`) - a nonce that can be used for
optimistic message sending
* `:tts` (boolean) - true if this is a TTS message
* `:file` (`t:Path.t/0` | map) - the path of the file being sent, or a map with the following keys
if sending a binary from memory
* `:name` (string) - the name of the file
* `:body` (string) - binary you wish to send
* `:files` - a list of files where each element is the same format as the `:file` option. If both
`:file` and `:files` are specified, `:file` will be prepended to the `:files` list.
* `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content
* `:allowed_mentions` (`t:allowed_mentions/0`) - see the allowed mentions type documentation
* `:message_reference` (`map`) - See "Message references" below
* `:poll` (`t:Nostrum.Struct.Message.Poll.t/0`) - A poll object to send with the message
At least one of the following is required: `:content`, `:file`, `:embeds`, `:poll`.
### Message reference
You can create a reply to another message on guilds using this option, given
that you have the ``VIEW_MESSAGE_HISTORY`` permission. To do so, include the
``message_reference`` field in your call. The complete structure
documentation can be found [on the Discord Developer
Portal](https://discord.com/developers/docs/resources/channel#message-object-message-reference-structure),
but simply passing ``message_id`` will suffice:
```elixir
def my_command(msg) do
# Reply to the author - ``msg`` is a ``Nostrum.Struct.Message``
Nostrum.Api.Message.create(
msg.channel_id,
content: "Hello",
message_reference: %{message_id: msg.id}
)
end
```
Passing a list will merge the settings provided
## Examples
```elixir
Nostrum.Api.Message.create(43189401384091, content: "hello world!")
Nostrum.Api.Message.create(43189401384091, "hello world!")
import Nostrum.Struct.Embed
embed =
%Nostrum.Struct.Embed{}
|> put_title("embed")
|> put_description("new desc")
Nostrum.Api.Message.create(43189401384091, embeds: [embed])
Nostrum.Api.Message.Message.create(43189401384091, file: "/path/to/file.txt")
Nostrum.Api.Message.create(43189401384091, content: "hello world!", embeds: [embed], file: "/path/to/file.txt")
Nostrum.Api.Message.create(43189401384091, content: "Hello @everyone", allowed_mentions: :none)
```
"""
@spec create(Channel.id() | Message.t(), Api.options() | String.t()) ::
Api.error() | {:ok, Message.t()}
def create(channel_id, options)
def create(%Message{} = message, options),
do: create(message.channel_id, options)
def create(channel_id, content) when is_binary(content),
do: create(channel_id, %{content: content})
def create(channel_id, options) when is_list(options),
do: create(channel_id, Map.new(options))
def create(channel_id, options) when is_snowflake(channel_id) and is_map(options) do
prepared_options =
Helpers.prepare_allowed_mentions(options)
|> Helpers.combine_embeds()
|> Helpers.combine_files()
Api.request(:post, Constants.channel_messages(channel_id), prepared_options)
|> Helpers.handle_request_with_decode({:struct, Message})
end
@doc ~S"""
Creates a reaction for a message.
This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY`
permissions. Additionally, if nobody else has reacted to the message with
the `emoji`, this endpoint requires the `ADD_REACTIONS` permission. It
fires a `t:Nostrum.Consumer.message_reaction_add/0` event.
If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`.
## Examples
```elixir
# Using a Nostrum.Struct.Emoji.
emoji = %Nostrum.Struct.Emoji{id: 43819043108, name: "foxbot"}
Nostrum.Api.Message.react(123123123123, 321321321321, emoji)
# Using a base 16 emoji string.
Nostrum.Api.Message.react(123123123123, 321321321321, "\xF0\x9F\x98\x81")
```
For other emoji string examples, see `t:Nostrum.Struct.Emoji.api_name/0`.
"""
@spec react(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok}
def react(channel_id, message_id, emoji)
def react(channel_id, message_id, %Emoji{} = emoji),
do: react(channel_id, message_id, Emoji.api_name(emoji))
def react(channel_id, message_id, emoji_api_name) do
Api.request(:put, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name))
end
@doc ~S"""
Deletes all reactions from a message.
This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and
`MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_all/0` event.
If successful, returns `{:ok}`. Otherwise, return `t:Nostrum.Api.error/0`.
"""
@spec clear_reactions(Channel.id(), Message.id()) :: Api.error() | {:ok}
def clear_reactions(channel_id, message_id) do
Api.request(:delete, Constants.channel_reactions_delete(channel_id, message_id))
end
@doc ~S"""
Same as `delete/2`, but takes a `Nostrum.Struct.Message` instead of a
`channel_id` and `message_id`.
"""
@spec delete(Message.t()) :: Api.error() | {:ok}
def delete(%Message{id: id, channel_id: c_id}) do
delete(c_id, id)
end
@doc ~S"""
Deletes a message.
This endpoint requires the 'VIEW_CHANNEL' and 'MANAGE_MESSAGES' permission. It
fires the `MESSAGE_DELETE` event.
If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Message.delete(43189401384091, 43189401384091)
```
"""
@spec delete(Channel.id(), Message.id()) :: Api.error() | {:ok}
def delete(channel_id, message_id)
when is_snowflake(channel_id) and is_snowflake(message_id) do
Api.request(:delete, Constants.channel_message(channel_id, message_id))
end
@doc ~S"""
Deletes a reaction the current user has made for the message.
This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY`
permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event.
If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`.
See `react/3` for similar examples.
"""
@spec unreact(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok}
def unreact(channel_id, message_id, emoji)
def unreact(channel_id, message_id, %Emoji{} = emoji),
do: unreact(channel_id, message_id, Emoji.api_name(emoji))
def unreact(channel_id, message_id, emoji_api_name) do
Api.request(:delete, Constants.channel_reaction_me(channel_id, message_id, emoji_api_name))
end
@doc ~S"""
Deletes all reactions of a given emoji from a message.
This endpoint requires the `MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove_emoji/0` event.
If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`.
See `react/3` for similar examples.
"""
@spec delete_emoji_reactions(Channel.id(), Message.id(), Api.emoji()) :: Api.error() | {:ok}
def delete_emoji_reactions(channel_id, message_id, emoji)
def delete_emoji_reactions(channel_id, message_id, %Emoji{} = emoji),
do: delete_emoji_reactions(channel_id, message_id, Emoji.api_name(emoji))
def delete_emoji_reactions(channel_id, message_id, emoji_api_name) do
Api.request(
:delete,
Constants.channel_reactions_delete_emoji(channel_id, message_id, emoji_api_name)
)
end
@doc ~S"""
Deletes another user's reaction from a message.
This endpoint requires the `VIEW_CHANNEL`, `READ_MESSAGE_HISTORY`, and
`MANAGE_MESSAGES` permissions. It fires a `t:Nostrum.Consumer.message_reaction_remove/0` event.
If successful, returns `{:ok}`. Otherwise, returns `t:Nostrum.Api.error/0`.
See `react/3` for similar examples.
"""
@spec delete_user_reaction(Channel.id(), Message.id(), Api.emoji(), User.id()) ::
Api.error() | {:ok}
def delete_user_reaction(channel_id, message_id, emoji, user_id)
def delete_user_reaction(channel_id, message_id, %Emoji{} = emoji, user_id),
do: delete_user_reaction(channel_id, message_id, Emoji.api_name(emoji), user_id)
def delete_user_reaction(channel_id, message_id, emoji_api_name, user_id) do
Api.request(
:delete,
Constants.channel_reaction(channel_id, message_id, emoji_api_name, user_id)
)
end
@doc ~S"""
Edits a previously sent message in a channel.
This endpoint requires the `VIEW_CHANNEL` permission. It fires the
`t:Nostrum.Consumer.message_update/0` event.
If `options` is a string, `options` will be used as the message's content.
If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Options
* `:content` (string) - the message contents (up to 2000 characters)
* `:embeds` (`t:Nostrum.Struct.Embed.t/0`) - a list of embedded rich content
* `:files` - a list of files where each element is the same format as the
`:file` option. If both `:file` and `:files` are specified, `:file` will be
prepended to the `:files` list. See `create_message/2` for more information.
Note that if you edit a message with attachments, all attachments that should
be present after edit **must** be included in your request body. This
includes attachments that were sent in the original request.
## Examples
```elixir
Nostrum.Api.Message.edit(43189401384091, 1894013840914098, content: "hello world!")
Nostrum.Api.Message.edit(43189401384091, 1894013840914098, "hello world!")
import Nostrum.Struct.Embed
embed =
%Nostrum.Struct.Embed{}
|> put_title("embed")
|> put_description("new desc")
Nostrum.Api.Message.edit(43189401384091, 1894013840914098, embeds: [embed])
Nostrum.Api.Message.edit(43189401384091, 1894013840914098, content: "hello world!", embeds: [embed])
```
"""
@spec edit(Channel.id(), Message.id(), Api.options() | String.t()) ::
Api.error() | {:ok, Message.t()}
def edit(channel_id, message_id, options)
def edit(channel_id, message_id, content) when is_binary(content),
do: edit(channel_id, message_id, %{content: content})
def edit(channel_id, message_id, options) when is_list(options),
do: edit(channel_id, message_id, Map.new(options))
def edit(channel_id, message_id, %{} = options)
when is_snowflake(channel_id) and is_snowflake(message_id) do
prepared_options =
Helpers.prepare_allowed_mentions(options)
|> Helpers.combine_embeds()
|> Helpers.combine_files()
Api.request(:patch, Constants.channel_message(channel_id, message_id), prepared_options)
|> Helpers.handle_request_with_decode({:struct, Message})
end
@doc ~S"""
Same as `edit/3`, but takes a `Nostrum.Struct.Message` instead of a
`channel_id` and `message_id`.
"""
@spec edit(Message.t(), Api.options()) :: Api.error() | {:ok, Message.t()}
def edit(%Message{id: id, channel_id: c_id}, options) do
edit(c_id, id, options)
end
@doc ~S"""
Retrieves a message from a channel.
This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions.
If successful, returns `{:ok, message}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Message.get(43189401384091, 198238475613443)
```
"""
@spec get(Channel.id(), Message.id()) :: Api.error() | {:ok, Message.t()}
def get(channel_id, message_id)
when is_snowflake(channel_id) and is_snowflake(message_id) do
Api.request(:get, Constants.channel_message(channel_id, message_id))
|> Helpers.handle_request_with_decode({:struct, Message})
end
@doc ~S"""
Gets all users who reacted with an emoji.
This endpoint requires the `VIEW_CHANNEL` and `READ_MESSAGE_HISTORY` permissions.
If successful, returns `{:ok, users}`. Otherwise, returns `t:Nostrum.Api.error/0`.
The optional `params` are `after`, the user ID to query after, absent by default,
and `limit`, the max number of users to return, 1-100, 25 by default.
See `react/3` for similar examples.
"""
@spec reactions(Channel.id(), Message.id(), Api.emoji(), keyword()) ::
Api.error() | {:ok, [User.t()]}
def reactions(channel_id, message_id, emoji, params \\ [])
def reactions(channel_id, message_id, %Emoji{} = emoji, params),
do: reactions(channel_id, message_id, Emoji.api_name(emoji), params)
def reactions(channel_id, message_id, emoji_api_name, params) do
Api.request(
:get,
Constants.channel_reactions_get(channel_id, message_id, emoji_api_name),
"",
params
)
|> Helpers.handle_request_with_decode({:list, {:struct, User}})
end
end