Current section

Files

Jump to
nostrum lib nostrum api channel.ex
Raw

lib/nostrum/api/channel.ex

defmodule Nostrum.Api.Channel do
@moduledoc """
Functions for interacting with the Discord API's channel endpoints.
See: https://discord.com/developers/docs/resources/channel
"""
@moduledoc since: "0.10.1"
alias Nostrum.Api
alias Nostrum.Api.Helpers
alias Nostrum.Constants
alias Nostrum.Snowflake
alias Nostrum.Struct.Channel
alias Nostrum.Struct.Guild
alias Nostrum.Struct.Guild.AuditLogEntry
alias Nostrum.Struct.Message
alias Nostrum.Struct.Webhook
import Nostrum.Snowflake, only: [is_snowflake: 1]
@doc ~S"""
Pins a message in a channel.
This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and
'MANAGE_MESSAGES' permissions. It fires the
`t:Nostrum.Consumer.message_update/0` and
`t:Nostrum.Consumer.channel_pins_update/0` events.
If successful, returns `{:ok}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Channel.pin_message(43189401384091, 18743893102394)
```
"""
@spec pin_message(Channel.id(), Message.id()) :: Api.error() | {:ok}
def pin_message(channel_id, message_id)
when is_snowflake(channel_id) and is_snowflake(message_id) do
Api.request(:put, Constants.channel_pin(channel_id, message_id))
end
@doc """
Deletes multiple messages from a channel.
`messages` is a list of `Nostrum.Struct.Message.id` that you wish to delete.
When given more than 100 messages, this function will chunk the given message
list into blocks of 100 and send them off to the API. It will stop deleting
on the first error that occurs. Keep in mind that deleting thousands of
messages will take a pretty long time and it may be proper to just delete
the channel you want to bulk delete in and recreate it.
This method can only delete messages sent within the last two weeks.
`Filter` is an optional parameter that specifies whether messages sent over
two weeks ago should be filtered out; defaults to `true`.
"""
@spec bulk_delete_messages(Channel.id(), [Message.id()], boolean) :: Api.error() | {:ok}
def bulk_delete_messages(channel_id, messages, filter \\ true)
def bulk_delete_messages(channel_id, messages, false),
do: send_chunked_delete(messages, channel_id)
def bulk_delete_messages(channel_id, messages, true) do
snowflake_two_weeks_ago =
DateTime.utc_now()
|> DateTime.to_unix()
# 60 seconds * 60 * 24 * 14 = 14 days / 2 weeks
|> Kernel.-(60 * 60 * 24 * 14)
|> DateTime.from_unix!()
|> Snowflake.from_datetime!()
messages
|> Stream.filter(&(&1 > snowflake_two_weeks_ago))
|> send_chunked_delete(channel_id)
end
@spec send_chunked_delete(
[Message.id()] | Enum.t(),
Snowflake.t()
) :: Api.error() | {:ok}
defp send_chunked_delete(messages, channel_id) do
messages
|> Stream.chunk_every(100)
|> Stream.map(fn message_chunk ->
Api.request(
:post,
Constants.channel_bulk_delete(channel_id),
%{messages: message_chunk}
)
end)
|> Enum.find({:ok}, &match?({:error, _}, &1))
end
@doc """
Creates a channel for a guild.
This endpoint requires the `MANAGE_CHANNELS` permission. It fires a
`t:Nostrum.Consumer.channel_create/0` event.
If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Options
* `:name` (string) - channel name (2-100 characters)
* `:type` (integer) - the type of channel (See `Nostrum.Struct.Channel`)
* `:topic` (string) - channel topic (0-1024 characters)
* `:bitrate` (integer) - the bitrate (in bits) of the voice channel (voice only)
* `:user_limit` (integer) - the user limit of the voice channel (voice only)
* `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) -
the channel's permission overwrites
* `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) - id of the parent category for a channel
* `:nsfw` (boolean) - if the channel is nsfw
`:name` is always required.
## Examples
```elixir
Nostrum.Api.Channel.create(81384788765712384, name: "elixir-nostrum", topic: "craig's domain")
{:ok, %Nostrum.Struct.Channel{guild_id: 81384788765712384}}
```
"""
@spec create(Guild.id(), Api.options()) :: Api.error() | {:ok, Channel.guild_channel()}
def create(guild_id, options)
def create(guild_id, options) when is_list(options),
do: create(guild_id, Map.new(options))
def create(guild_id, %{} = options) when is_snowflake(guild_id) do
Api.request(:post, Constants.guild_channels(guild_id), options)
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@doc ~S"""
Deletes a channel.
An optional `reason` can be provided for the guild audit log.
If deleting a `t:Nostrum.Struct.Channel.guild_channel/0`, this endpoint requires
the `MANAGE_CHANNELS` permission. It fires a
`t:Nostrum.Consumer.channel_delete/0`. If a `t:Nostrum.Struct.Channel.guild_category_channel/0`
is deleted, then a `t:Nostrum.Consumer.channel_update/0` event will fire
for each channel under the category.
If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Channel.delete(421533712753360896)
{:ok, %Nostrum.Struct.Channel{id: 421533712753360896}}
```
"""
@spec delete(Channel.id(), AuditLogEntry.reason()) :: Api.error() | {:ok, Channel.t()}
def delete(channel_id, reason \\ nil) when is_snowflake(channel_id) do
%{
method: :delete,
route: Constants.channel(channel_id),
body: "",
params: [],
headers: Helpers.maybe_add_reason(reason)
}
|> Api.request()
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@doc """
Delete a channel permission for a user or role.
Role or user overwrite to delete is specified by `channel_id` and `overwrite_id`.
An optional `reason` can be given for the audit log.
"""
@spec delete_permissions(Channel.id(), integer, AuditLogEntry.reason()) :: Api.error() | {:ok}
def delete_permissions(channel_id, overwrite_id, reason \\ nil) do
Api.request(%{
method: :delete,
route: Constants.channel_permission(channel_id, overwrite_id),
body: "",
params: [],
headers: Helpers.maybe_add_reason(reason)
})
end
@doc """
Unpins a message in a channel.
This endpoint requires the 'VIEW_CHANNEL', 'READ_MESSAGE_HISTORY', and
'MANAGE_MESSAGES' permissions. It fires the
`t:Nostrum.Consumer.message_update/0` and
`t:Nostrum.Consumer.channel_pins_update/0` events.
Returns `{:ok}` if successful. `error` otherwise.
"""
@spec unpin_message(Channel.id(), Message.id()) :: Api.error() | {:ok}
def unpin_message(channel_id, message_id)
when is_snowflake(channel_id) and is_snowflake(message_id) do
Api.request(:delete, Constants.channel_pin(channel_id, message_id))
end
@doc """
Edit the permission overwrites for a user or role.
Role or user to overwrite is specified by `overwrite_id`.
`permission_info` is a map with the following keys:
* `type` - Required; `member` if editing a user, `role` if editing a role.
* `allow` - Bitwise value of allowed permissions.
* `deny` - Bitwise value of denied permissions.
* `type` - `member` if editing a user, `role` if editing a role.
An optional `reason` can be provided for the audit log.
`allow` and `deny` are defaulted to `0`, meaning that even if you don't
specify them, they will override their respective former values in an
existing overwrite.
"""
@spec edit_permissions(
Channel.id(),
integer,
%{
required(:type) => String.t(),
optional(:allow) => integer,
optional(:deny) => integer
},
AuditLogEntry.reason()
) :: Api.error() | {:ok}
def edit_permissions(channel_id, overwrite_id, permission_info, reason \\ nil) do
Api.request(%{
method: :put,
route: Constants.channel_permission(channel_id, overwrite_id),
body: permission_info,
params: [],
headers: Helpers.maybe_add_reason(reason)
})
end
@doc ~S"""
Gets a channel.
If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Channel.get(381889573426429952)
{:ok, %Nostrum.Struct.Channel{id: 381889573426429952}}
```
"""
@spec get(Channel.id()) :: Api.error() | {:ok, Channel.t()}
def get(channel_id) when is_snowflake(channel_id) do
Api.request(:get, Constants.channel(channel_id))
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@doc ~S"""
Retrieves a channel's messages around a `locator` up to a `limit`.
This endpoint requires the 'VIEW_CHANNEL' permission. If the current user
is missing the 'READ_MESSAGE_HISTORY' permission, then this function will
return no messages.
If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Channel.messages(43189401384091, 5, {:before, 130230401384})
```
"""
@spec messages(Channel.id(), Api.limit(), Api.locator()) ::
Api.error() | {:ok, [Message.t()]}
def messages(channel_id, limit, locator \\ {}) when is_snowflake(channel_id) do
messages_sync(channel_id, limit, [], locator)
end
defp messages_sync(channel_id, limit, messages, locator) when limit <= 100 do
case messages_call(channel_id, limit, locator) do
{:ok, new_messages} -> {:ok, messages ++ new_messages}
other -> other
end
end
defp messages_sync(channel_id, limit, messages, locator) do
case messages_call(channel_id, 100, locator) do
{:error, message} ->
{:error, message}
{:ok, []} ->
{:ok, messages}
{:ok, new_messages} ->
new_limit = get_new_limit(limit, length(new_messages))
new_locator = get_new_locator(locator, List.last(new_messages))
messages_sync(channel_id, new_limit, messages ++ new_messages, new_locator)
end
end
defp get_new_locator({}, last_message), do: {:before, last_message.id}
defp get_new_locator(locator, last_message), do: put_elem(locator, 1, last_message.id)
defp get_new_limit(:infinity, _new_message_count), do: :infinity
defp get_new_limit(limit, message_count), do: limit - message_count
# We're decoding the response at each call to catch any errors
@doc false
def messages_call(channel_id, limit, locator) do
qs_params =
case locator do
{} -> [{:limit, limit}]
non_empty_locator -> [{:limit, limit}, non_empty_locator]
end
Api.request(:get, Constants.channel_messages(channel_id), "", qs_params)
|> Helpers.handle_request_with_decode({:list, {:struct, Message}})
end
@doc """
Gets a list of webhooks for a channel.
## Parameters
- `channel_id` - Channel to get webhooks for.
"""
@spec webhooks(Channel.id()) :: Api.error() | {:ok, [Webhook.t()]}
def webhooks(channel_id) do
Api.request(:get, Constants.webhooks_channel(channel_id))
|> Helpers.handle_request_with_decode()
end
@doc ~S"""
Retrieves all pinned messages from a channel.
This endpoint requires the 'VIEW_CHANNEL' and 'READ_MESSAGE_HISTORY' permissions.
If successful, returns `{:ok, messages}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Examples
```elixir
Nostrum.Api.Channel.pinned_messages(43189401384091)
```
"""
@spec pinned_messages(Channel.id()) :: Api.error() | {:ok, [Message.t()]}
def pinned_messages(channel_id) when is_snowflake(channel_id) do
Api.request(:get, Constants.channel_pins(channel_id))
|> Helpers.handle_request_with_decode({:list, {:struct, Message}})
end
@doc ~S"""
Modifies a channel's settings.
An optional `reason` can be given for the guild audit log.
If a `t:Nostrum.Struct.Channel.guild_channel/0` is being modified, this
endpoint requires the `MANAGE_CHANNEL` permission. It fires a
`t:Nostrum.Consumer.channel_update/0` event. If a
`t:Nostrum.Struct.Channel.guild_category_channel/0` is being modified, then this
endpoint fires multiple `t:Nostrum.Consumer.channel_update/0` events.
If successful, returns `{:ok, channel}`. Otherwise, returns a `t:Nostrum.Api.error/0`.
## Options
* `:name` (string) - 2-100 character channel name
* `:position` (integer) - the position of the channel in the left-hand listing
* `:topic` (string) (`t:Nostrum.Struct.Channel.text_channel/0` only) -
0-1024 character channel topic
* `:nsfw` (boolean) (`t:Nostrum.Struct.Channel.text_channel/0` only) -
if the channel is nsfw
* `:bitrate` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) -
the bitrate (in bits) of the voice channel; 8000 to 96000 (128000 for VIP servers)
* `:user_limit` (integer) (`t:Nostrum.Struct.Channel.voice_channel/0` only) -
the user limit of the voice channel; 0 refers to no limit, 1 to 99 refers to a user limit
* `:permission_overwrites` (list of `t:Nostrum.Struct.Overwrite.t/0` or equivalent map) -
channel or category-specific permissions
* `:parent_id` (`t:Nostrum.Struct.Channel.id/0`) (`t:Nostrum.Struct.Channel.guild_channel/0` only) -
id of the new parent category for a channel
## Examples
```elixir
Nostrum.Api.Channel.modify(41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion")
{:ok, %Nostrum.Struct.Channel{id: 41771983423143933, name: "elixir-nostrum", topic: "nostrum discussion"}}
Nostrum.Api.Channel.modify(41771983423143933)
{:ok, %Nostrum.Struct.Channel{id: 41771983423143933}}
```
"""
@spec modify(Channel.id(), Api.options(), AuditLogEntry.reason()) ::
Api.error() | {:ok, Channel.t()}
def modify(channel_id, options, reason \\ nil)
def modify(channel_id, options, reason) when is_list(options),
do: modify(channel_id, Map.new(options), reason)
def modify(channel_id, %{} = options, reason) when is_snowflake(channel_id) do
%{
method: :patch,
route: Constants.channel(channel_id),
body: options,
params: [],
headers: Helpers.maybe_add_reason(reason)
}
|> Api.request()
|> Helpers.handle_request_with_decode({:struct, Channel})
end
@doc """
Triggers the typing indicator.
Triggers the typing indicator in the channel specified by `channel_id`.
The typing indicator lasts for about 8 seconds and then automatically stops.
Returns `{:ok}` if successful. `error` otherwise.
"""
@spec start_typing(integer) :: Api.error() | {:ok}
def start_typing(channel_id) do
Api.request(:post, Constants.channel_typing(channel_id))
end
end