Current section

Files

Jump to
nostrum lib nostrum api thread.ex
Raw

lib/nostrum/api/thread.ex

defmodule Nostrum.Api.Thread do
@moduledoc """
Functions for interacting with the Discord API's thread endpoints.
Endpoints related to threads in the Discord Channels API: https://discord.com/developers/docs/resources/channel#start-thread-from-message
"""
@moduledoc since: "0.10.1"
alias Nostrum.Api
alias Nostrum.Api.Helpers
alias Nostrum.Constants
alias Nostrum.Struct.Channel
alias Nostrum.Struct.Guild
alias Nostrum.Struct.Guild.AuditLogEntry
alias Nostrum.Struct.Message
alias Nostrum.Struct.ThreadMember
alias Nostrum.Struct.User
alias Nostrum.Util
import Api.Helpers, only: [has_files: 1]
@doc """
Add a user to a thread, requires the ability to send messages in the thread.
"""
@doc since: "1.x.x"
@spec add_member(Channel.id(), User.id()) :: {:ok} | Api.error()
def add_member(thread_id, user_id) do
Api.request(:put, Constants.thread_member(thread_id, user_id))
end
@doc """
Returns a thread member object for the specified user if they are a member of the thread
"""
@doc since: "1.x.x"
@spec member(Channel.id(), User.id()) :: {:ok, ThreadMember.t()} | Api.error()
def member(thread_id, user_id) do
Api.request(:get, Constants.thread_member(thread_id, user_id))
|> Helpers.handle_request_with_decode({:struct, ThreadMember})
end
@doc """
Returns a list of thread members for the specified thread.
This endpoint is restricted according to whether the `GUILD_MEMBERS` privileged intent is enabled.
"""
@doc since: "1.x.x"
@spec members(Channel.id()) :: {:ok, [ThreadMember.t()]} | Api.error()
def members(thread_id) do
Api.request(:get, Constants.thread_members(thread_id))
|> Helpers.handle_request_with_decode({:list, {:struct, ThreadMember}})
end
@doc """
Join an existing thread, requires that the thread is not archived.
"""
@doc since: "1.x.x"
@spec join(Channel.id()) :: {:ok} | Api.error()
def join(thread_id) do
Api.request(:put, Constants.thread_member_me(thread_id))
end
@doc """
Leave a thread, requires that the thread is not archived.
"""
@doc since: "1.x.x"
@spec leave(Channel.id()) :: {:ok} | Api.error()
def leave(thread_id) do
Api.request(:delete, Constants.thread_member_me(thread_id))
end
@doc """
Return all active threads for the current guild.
Response body is a map with the following keys:
- `threads`: A list of channel objects.
- `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined.
"""
@doc since: "1.x.x"
@spec list(Guild.id()) ::
{:ok, %{threads: [Channel.t()], members: [ThreadMember.t()]}} | Api.error()
def list(guild_id) do
res =
Api.request(:get, Constants.guild_active_threads(guild_id))
|> Helpers.handle_request_with_decode()
case res do
{:ok, %{threads: channels, members: thread_members}} ->
map = %{
threads: Util.cast(channels, {:list, {:struct, Channel}}),
members: Util.cast(thread_members, {:list, {:struct, ThreadMember}})
}
{:ok, map}
{:error, e} ->
{:error, e}
end
end
@doc """
Same as `public_archived_threads/2`, but only returns private threads that the current user has joined.
"""
@doc since: "1.x.x"
@spec joined_private_archived_threads(Channel.id(), Api.options()) ::
{:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}}
| Api.error()
def joined_private_archived_threads(channel_id, options \\ [])
def joined_private_archived_threads(channel_id, options) when is_map(options) do
Constants.private_joined_archived_threads(channel_id)
|> list_archived_threads(Map.to_list(options))
end
def joined_private_archived_threads(channel_id, options) when is_list(options) do
Constants.private_joined_archived_threads(channel_id)
|> list_archived_threads(options)
end
defp list_archived_threads(route, options) do
options = options |> Helpers.maybe_convert_date_time(:before)
res =
Api.request(%{
method: :get,
route: route,
body: "",
params: options,
headers: []
})
|> Helpers.handle_request_with_decode()
case res do
{:ok, %{threads: channels, members: thread_members, has_more: has_more}} ->
map = %{
threads: Util.cast(channels, {:list, {:struct, Channel}}),
members: Util.cast(thread_members, {:list, {:struct, ThreadMember}}),
has_more: has_more
}
{:ok, map}
{:error, e} ->
{:error, e}
end
end
@doc """
Same as `public_archived_threads/2`, but for private threads instead of public.
"""
@doc since: "1.x.x"
@spec private_archived_threads(Channel.id(), Api.options()) ::
{:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}}
| Api.error()
def private_archived_threads(channel_id, options \\ [])
def private_archived_threads(channel_id, options) when is_map(options) do
Constants.private_archived_threads(channel_id)
|> list_archived_threads(Map.to_list(options))
end
def private_archived_threads(channel_id, options) when is_list(options) do
Constants.private_archived_threads(channel_id)
|> list_archived_threads(options)
end
@doc """
Returns a list of archived threads for a given channel.
Threads are sorted by the `archive_timestamp` field, in descending order.
## Response body
Response body is a map with the following keys:
- `threads`: A list of channel objects.
- `members`: A list of `ThreadMember` objects, one for each returned thread the current user has joined.
- `has_more`: A boolean indicating whether there are more archived threads that can be fetched.
## Options
- `before`: Returns threads before this timestamp, can be either a `DateTime` or [ISO8601 timestamp](`DateTime.to_iso8601/3`).
- `limit`: Optional maximum number of threads to return.
"""
@doc since: "1.x.x"
@spec public_archived_threads(Channel.id(), Api.options()) ::
{:ok, %{threads: [Channel.t()], members: [ThreadMember.t()], has_more: boolean()}}
| Api.error()
def public_archived_threads(channel_id, options \\ [])
def public_archived_threads(channel_id, options) when is_map(options) do
Constants.public_archived_threads(channel_id)
|> list_archived_threads(Map.to_list(options))
end
def public_archived_threads(channel_id, options) when is_list(options) do
Constants.public_archived_threads(channel_id)
|> list_archived_threads(options)
end
@doc """
Removes another user from a thread, requires that the thread is not archived.
Also requires the `MANAGE_THREADS` permission, or the creator of the thread if the thread is private.
"""
@doc since: "1.x.x"
@spec remove_member(Channel.id(), User.id()) :: {:ok} | Api.error()
def remove_member(thread_id, user_id) do
Api.request(:delete, Constants.thread_member(thread_id, user_id))
end
@type thread_without_message_params :: %{
required(:name) => String.t(),
required(:type) => non_neg_integer(),
optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080,
optional(:invitable) => boolean(),
optional(:rate_limit_per_user) => 0..21_600
}
@doc """
Create a thread on a channel without an associated message.
If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`.
An optional `reason` argument can be given for the audit log.
## Options
- `name`: Name of the thread, max 100 characters.
- `type`: Type of thread, can be either 11 (`GUILD_PUBLIC_THREAD`) or 12 (`GUILD_PRIVATE_THREAD`).
- `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080.
- `invitable`: whether non-moderators can add other non-moderators to a thread; only available when creating a private thread defaults to `false`.
- `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`.
"""
@doc since: "1.x.x"
@spec create(Channel.id(), thread_without_message_params, AuditLogEntry.reason()) ::
{:ok, Channel.t()} | Api.error()
def create(channel_id, options, reason \\ nil) do
Api.request(%{
method: :post,
route: Constants.thread_without_message(channel_id),
body: options,
params: [],
headers: Helpers.maybe_add_reason(reason)
})
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@doc """
Create a new thread in a forum channel.
If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`.
An optional `reason` argument can be given for the audit log.
## Options
- `name`: Name of the thread, max 100 characters.
- `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080.
- `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`.
- `applied_tags`: An array of tag ids to apply to the thread.
- `message`: The first message in the created thread.
### Thread Message Options
- `content`: The content of the message.
- `embeds`: A list of embeds.
- `allowed_mentions`: Allowed mentions object.
- `components`: A list of components.
- `sticker_ids`: A list of sticker ids.
- `: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.
At least one of `content`, `embeds`, `sticker_ids`, or `files` must be specified.
"""
@doc since: "1.x.x"
@spec create_in_forum(Channel.id(), map(), AuditLogEntry.reason()) ::
{:ok, Channel.t()} | Api.error()
def create_in_forum(channel_id, options, reason \\ nil)
def create_in_forum(channel_id, %{message: data} = body, reason)
when has_files(data) do
# done this way to avoid breaking changes to support audit log reasons in multipart requests
boundary = Helpers.generate_boundary()
{files, json} =
Helpers.combine_files(body)
|> Helpers.pop_files()
body = Jason.encode_to_iodata!(json)
headers =
Helpers.maybe_add_reason(reason, [
{"content-type", "multipart/form-data; boundary=#{boundary}"}
])
%{
method: :post,
route: Constants.thread_without_message(channel_id),
body: {:multipart, Api.create_multipart(files, body, boundary)},
params: [],
headers: headers
}
|> Api.request()
|> Helpers.handle_request_with_decode({:struct, Channel})
end
def create_in_forum(channel_id, options, reason) do
Api.request(%{
method: :post,
route: Constants.thread_without_message(channel_id),
body: options,
params: [],
headers: Helpers.maybe_add_reason(reason)
})
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@type thread_with_message_params :: %{
required(:name) => String.t(),
optional(:auto_archive_duration) => 60 | 1440 | 4320 | 10_080,
optional(:rate_limit_per_user) => 0..21_600
}
@doc """
Create a thread on a channel message.
The `thread_id` will be the same as the id of the message, as such no message can have more than one thread.
If successful, returns `{:ok, Channel}`. Otherwise returns a `t:Nostrum.Api.error/0`.
An optional `reason` argument can be given for the audit log.
## Options
- `name`: Name of the thread, max 100 characters.
- `auto_archive_duration`: Duration in minutes to auto-archive the thread after it has been inactive, can be set to 60, 1440, 4320, or 10080.
- `rate_limit_per_user`: Rate limit per user in seconds, can be set to any value in `0..21600`.
"""
@doc since: "1.x.x"
@spec create_with_message(
Channel.id(),
Message.id(),
thread_with_message_params,
AuditLogEntry.reason()
) ::
{:ok, Channel.t()} | Api.error()
def create_with_message(channel_id, message_id, options, reason \\ nil) do
Api.request(%{
method: :post,
route: Constants.thread_with_message(channel_id, message_id),
body: options,
params: [],
headers: Helpers.maybe_add_reason(reason)
})
|> Helpers.handle_request_with_decode({:struct, Channel})
end
end