Skip to content

Commit

Permalink
Nostrum.Api Module Decomposition (#641)
Browse files Browse the repository at this point in the history
* Update Message type typedoc from latest API docs

* Update buttons guide image from API docs

* Update `Struct.Channel` with default_sort_order and default_forum_layout

Update guild_forum_channel type spec accordingly

* Remove id callout to match rest of typedocs

* Rename base to adapter to indicate direction towards building behavior driven adapters

* Add `Nostrum.Api.ApplicationCommand` module and functions

* Add `Nostrum.Api.AutoModeration` module and functions

* Add `Nostrum.Api.Channel` module and functions

* Adjust Api Ratelimited for base rename

* Remove unused alias

* WIP begin delegating `Nostrum.Api` function calls to submodules

Also begin working towards a sane pattern for error returning

* Extract functions for Guild, Invite, MEssage, Poll, Role, ScheduledEvent, and Self

* Extract functions for Nostrum.Api.Thread

* Extract functions for Nostrum.Api.User

* Extract functions for Nostrum.Api.Webhook

Also adjust missed prior delegations

* Extract functions for Interaction and Sticker

Also adds the get sticker pack route

* Minimize diff and remove unused alias

* Minimize diff

* Start moving api helper functions to Api.Helpers module

* Update helpers.ex

Co-authored-by: Michael <[email protected]>

* Add bypass for testing

* WIP Nostrum.Api delegate refactoring

* `Nostrum.Api` delegate refactoring and deprecation warnings

* move type definitions to Nostrum.Api.Webhook

* alias missing modules in Nostrum.Api.Webhook

* Change doc "since" version to 1.x.x

* Add ATTACHMENT application command option type definition

* Add PRIMARY_ENTRY_POINT constant for application commands

* Use alphabetical ordering of aliases

* Add module documentation for Discord API modules

* Update docs and allowed trigger types for AutoModerationRule

* Update docs and allowed action types for AutoModerationRule.Action

* Update docs naming to match discord api docs

* Remove bypass and rebuild lockfile

* Update voice API calls to use proper module functions

* alias Self and use in Voice module

* Update references to new API modules in docs and internal functions

* Update references to new API modules in docs

* Move helper functions out of Nostrum.Api module

* Properly reference `maybe_convert_date_time`

* Use proper alias for ConsumerGroup example

* Move Role API endpoints to Guild module

Also updates relevant docs

---------

Co-authored-by: Michael <[email protected]>
  • Loading branch information
kyleboe and Th3-M4jor authored Dec 31, 2024
1 parent 08e6bda commit 503c87f
Show file tree
Hide file tree
Showing 39 changed files with 5,159 additions and 3,619 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,12 @@ for a full example.
defmodule ExampleConsumer do
use Nostrum.Consumer

alias Nostrum.Api
alias Nostrum.Api.Message

def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do
case msg.content do
"ping!" ->
Api.create_message(msg.channel_id, "I copy and pasted this code")
Message.create(msg.channel_id, "I copy and pasted this code")
_ ->
:ignore
end
Expand Down
7 changes: 4 additions & 3 deletions examples/audio_player_example.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ end
defmodule AudioPlayerConsumer do
use Nostrum.Consumer

alias Nostrum.Api
alias Nostrum.Api.ApplicationCommand
alias Nostrum.Api.Interaction
alias Nostrum.Cache.GuildCache
alias Nostrum.Voice

Expand Down Expand Up @@ -66,7 +67,7 @@ defmodule AudioPlayerConsumer do
# with your guild_id as the argument
def create_guild_commands(guild_id) do
Enum.each(@commands, fn {name, description, options} ->
Api.create_guild_application_command(guild_id, %{
ApplicationCommand.create_guild_command(guild_id, %{
name: name,
description: description,
options: options
Expand All @@ -88,7 +89,7 @@ defmodule AudioPlayerConsumer do
_ -> ":white_check_mark:"
end

Api.create_interaction_response(interaction, %{type: 4, data: %{content: message}})
Interaction.create_response(interaction, %{type: 4, data: %{content: message}})
end

def handle_event({:VOICE_SPEAKING_UPDATE, payload, _ws_state}) do
Expand Down
8 changes: 4 additions & 4 deletions examples/cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ end
defmodule ExampleCommands do
import Nostrum.Snowflake, only: [is_snowflake: 1]

alias Nostrum.Api
alias Nostrum.Api.Message
alias Nostrum.Cache.{GuildCache, UserCache}
alias Nostrum.Struct.User

Expand Down Expand Up @@ -68,7 +68,7 @@ defmodule ExampleCommands do
get_cached_with_fallback(user_id, &UserCache.get/1, &Api.get_user/1),
{:ok, %{name: guild_name}} <-
get_cached_with_fallback(guild_id, &GuildCache.get/1, &Api.get_guild/1) do
Api.create_message(
Message.create(
channel_id,
"""
ID #{message_user_id} belongs to: #{User.full_name(user)}
Expand All @@ -79,12 +79,12 @@ defmodule ExampleCommands do
# Since we have multiple failure patterns from the combination of `Integer.parse/2`
# and `is_snowflake/1`, we'll use the identifier as the term to match on instead.
{_invalid_id, :parse_id} ->
Api.create_message(channel_id, "Make sure you entered a valid User ID")
Message.create(channel_id, "Make sure you entered a valid User ID")

# The cache + API failure case. For now, lets just return the stringified failure
# reason of whatever the API returned. Up to you if you want to make it all nice and pretty.
{:error, reason} ->
Api.create_message(channel_id, "Failed to retrieve all required info: #{inspect(reason)}")
Message.create(channel_id, "Failed to retrieve all required info: #{inspect(reason)}")
end
end

Expand Down
6 changes: 3 additions & 3 deletions examples/event_consumer.ex
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@ end
defmodule ExampleConsumer do
use Nostrum.Consumer

alias Nostrum.Api
alias Nostrum.Api.Message

def handle_event({:MESSAGE_CREATE, msg, _ws_state}) do
case msg.content do
"!sleep" ->
Api.create_message(msg.channel_id, "Going to sleep...")
Message.create(msg.channel_id, "Going to sleep...")
# This won't stop other events from being handled.
Process.sleep(3000)

"!ping" ->
Api.create_message(msg.channel_id, "pyongyang!")
Message.create(msg.channel_id, "pyongyang!")

"!raise" ->
# This won't crash the entire Consumer.
Expand Down
20 changes: 10 additions & 10 deletions guides/cheat-sheets/api.cheatmd
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ UTC time is: #{DateTime.to_iso8601(utc_now)}
Atom table size is: #{atom_count}
"""

Nostrum.Api.create_message(msg.channel_id, content)
Nostrum.Api.Message.create(msg.channel_id, content)
```

### Sending a message with an embed
Expand All @@ -35,15 +35,15 @@ embed =
# set inline attribute to true
|> put_field("Field 2", "More test", true)

Nostrum.Api.create_message(msg.channel_id, embeds: [embed])
Nostrum.Api.Message.create(msg.channel_id, embeds: [embed])
```

You can look at the documentation in `m:Nostrum.Struct.Embed#module-using-structs` for more advanced usage.

### Upload an attachment

```elixir
Nostrum.Api.create_message(
Nostrum.Api.Message.create(
msg.channel_id,
files: [
# file from filesystem
Expand All @@ -59,7 +59,7 @@ Nostrum.Api.create_message(
With a mention:

```elixir
Nostrum.Api.create_message(
Nostrum.Api.Message.create(
msg.channel_id,
content: "Hello!",
message_reference: %{message_id: msg.id}
Expand All @@ -69,7 +69,7 @@ Nostrum.Api.create_message(
Without a mention:

```elixir
Nostrum.Api.create_message(
Nostrum.Api.Message.create(
msg.channel_id,
content: "Hello!",
message_reference: %{message_id: msg.id},
Expand All @@ -88,14 +88,14 @@ poll = Poll.create_poll(
|> Poll.put_answer("Yes!", default_emoji: "\u2705")
|> Poll.put_answer("No!", default_emoji: "\u274C")

Api.create_message(channel_id, poll: poll)
Nostrum.Api.Message.create(channel_id, poll: poll)
```

### React to a message

Using a default emoji (unicode representation):
```elixir
Nostrum.Api.create_reaction(
Nostrum.Api.Message.react(
msg.channel_id,
msg.id,
"👾"
Expand All @@ -109,7 +109,7 @@ emoji = %Nostrum.Struct.Emoji{
id: 1228698654022434866
}

Nostrum.Api.create_reaction(msg.channel_id, msg.id, emoji)
Nostrum.Api.Message.react(msg.channel_id, msg.id, emoji)
```

## Miscellaneous
Expand All @@ -118,7 +118,7 @@ Nostrum.Api.create_reaction(msg.channel_id, msg.id, emoji)
### Update the bot status

```elixir
Nostrum.Api.update_status(
Nostrum.Api.Self.update_status(
:dnd,
"craigs cats",
3 # Watching status
Expand All @@ -131,7 +131,7 @@ You can also update a single shard with `Nostrum.Api.update_shard_status/5`.
```elixir
image = "data:image/png;base64,..."

Nostrum.Api.create_guild_emoji(
Nostrum.Api.Guild.create_emoji(
msg.guild_id,
name: "nostrum",
image: image
Expand Down
33 changes: 3 additions & 30 deletions guides/intro/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,13 @@ Discord's API. Method names are copied closely from the documentation to
eliminate any confusion about what a method does, as well as to allow users to
easily lookup the endpoints in the official API documentation.

For a full listing of method definitions, please see the `Nostrum.Api` module.


## Banged (`!`) API methods

A lot of methods have a `banged` version of themselves. This is a common Elixir
idiom hailing from Elixir's style of failing fast.

By default, the API method will return a tuple like one of the following:

```elixir
# Success
{:ok, msg} = Nostrum.Api.create_message(179679229036724225, "456")

# Failure
{:error, reason} = Nostrum.Api.create_message(123, "eat my shorts api")
```

A banged method, instead of returning an `error` tuple, will throw an error. If
successful, it will directly return the response with no `:ok` tuple.

```elixir
# Success
msg = Nostrum.Api.create_message!(179679229036724225, "456")

# Failure - Throws an error
Nostrum.Api.create_message!(123, "eat my shorts api")
```
For a listing of method definitions, please see the submodules of `Nostrum.Api`.


## Abstractions

When appropriate, some helpers are defined to make interacting with the API
simpler. An example of this is `Nostrum.Api.get_channel_messages/3`. By default
simpler. An example of this is `Nostrum.Api.Channel.messages/3`. By default
this endpoint only allows the retrieval of `100` messages at a time. A general
use case will have a user wanting more messages than that, thus nostrum handles
the retrieval of any number of messages for the user.
Expand All @@ -62,7 +35,7 @@ asynchronously or not, nostrum funnels all requests through the

If you only want to use the REST portion of the provided API, the only process
needed is the ratelimiter, which can be manually started by calling
`Nostrum.Api.Ratelimiter.start_link/1`.
`Nostrum.Api.Ratelimiter.start_link/1`.

If you don't want to start nostrum, you can add `runtime: false` to the
dependency options. If you're using `mix release`, all `runtime: false` deps
Expand Down
14 changes: 7 additions & 7 deletions guides/intro/application_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ command = %{
```

To register this command on the guild, we simply pass it to
`Nostrum.Api.create_guild_application_command/2`:
`Nostrum.Api.ApplicationCommand.create_guild_command/2`:

```elixir
Nostrum.Api.create_guild_application_command(guild_id, command)
Nostrum.Api.ApplicationCommand.create_guild_command(guild_id, command)
```

You can register the command in the ``:READY`` gateway event handler.
Expand Down Expand Up @@ -100,11 +100,11 @@ alias Nostrum.Api
alias Nostrum.Struct.Interaction

defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do
Api.add_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id)
Guild.add_member_role(interaction.guild_id, interaction.member.user_id, role_id)
end

defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "remove"}]}} = interaction) do
Api.remove_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id)
Guild.remove_member_role(interaction.guild_id, interaction.member.user_id, role_id)
end

def handle_event({:INTERACTION_CREATE, %Interaction{data: %{name: "role"}} = interaction, _ws_state}) do
Expand All @@ -118,18 +118,18 @@ that you would use for regular commands.

## Responding to interactions

To respond to interactions, use ``Nostrum.Api.create_interaction_response/2``:
To respond to interactions, use ``Nostrum.Api.Interaction.create_response/2``:

```elixir
defp manage_role(%Interaction{data: %{options: [%{value: role_id}, %{value: "assign"}]}} = interaction) do
Api.add_guild_member_role(interaction.guild_id, interaction.member.user_id, role_id)
Guild.add_member_role(interaction.guild_id, interaction.member.user_id, role_id)
response = %{
type: 4, # ChannelMessageWithSource
data: %{
content: "role assigned"
}
}
Api.create_interaction_response(interaction, response)
Api.Interaction.create_response(interaction, response)
end
```

Expand Down
Loading

0 comments on commit 503c87f

Please sign in to comment.