Skip to content

Commit

Permalink
Finish docs, add examples
Browse files Browse the repository at this point in the history
  • Loading branch information
hypergonial committed Jan 2, 2024
1 parent 1fb360a commit d697fd2
Show file tree
Hide file tree
Showing 30 changed files with 887 additions and 19 deletions.
4 changes: 2 additions & 2 deletions arc/abc/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ def remove_plugin(self, plugin: str | PluginBase[te.Self]) -> te.Self:

def add_hook(self, hook: HookT[te.Self]) -> te.Self:
"""Add a pre-execution hook to this client.
This hook will be executed before **every command** added to this client.
This hook will be executed before **every command** that is added to this client.
Parameters
----------
Expand All @@ -504,7 +504,7 @@ def add_hook(self, hook: HookT[te.Self]) -> te.Self:

def add_post_hook(self, hook: PostHookT[te.Self]) -> te.Self:
"""Add a post-execution hook to this client.
This hook will be executed after **every command** added to this client.
This hook will be executed after **every command** that is added to this client.
!!! warning
Post-execution hooks will be called even if the command callback raises an exception.
Expand Down
Binary file added docs/assets/app_commands.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/assets/context_menu.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
64 changes: 64 additions & 0 deletions docs/guides/context_menu.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
---
title: Context Menu Commands
description: A guide on defining commands inside context menus
hide:
- toc
---

# Context Menu Commands

<figure markdown>
![Context Menu Commands](../assets/context_menu.png){ width="600" }
<figcaption>An example context menu command</figcaption>
</figure>

Commands can also be defined in **context menus** that appear when you right-click a user or message. These commands have some limitations:

- Options are not supported
- Subcommands and groups are not supported
- There is a **maximum** of 5 user commands and 5 message commands a bot can have

Additionally, context-menu commands do not have the same naming limitations as slash commands,
their names can contain spaces and uppercase letters.

To define a user command, use [`@arc.user_command`][arc.command.user.user_command]:

=== "Gateway"

```py
@client.include
@arc.user_command("Say Hi")
async def hi_user(ctx: arc.GatewayContext, user: hikari.User) -> None:
await ctx.respond(f"Hey {user.mention}!")
```

=== "REST"

```py
@client.include
@arc.user_command("Say Hi")
async def hi_user(ctx: arc.RESTContext, user: hikari.User) -> None:
await ctx.respond(f"Hey {user.mention}!")
```

To define a message command, use [`@arc.message_command`][arc.command.message.message_command]:

=== "Gateway"

```py
@client.include
@arc.message_command("Say Hi")
async def hi_message(ctx: arc.GatewayContext, message: hikari.Message) -> None:
await ctx.respond(f"Hey {message.author.mention}!")
```

=== "REST"

```py
@client.include
@arc.message_command("Say Hi")
async def hi_message(ctx: arc.RESTContext, message: hikari.Message) -> None:
await ctx.respond(f"Hey {message.author.mention}!")
```

The second argument of a context-menu command will always be the target of the command (the message/user that was right-clicked).
2 changes: 0 additions & 2 deletions docs/guides/dependency_injection.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ In the above example, we asked `arc` that every time we ask for a dependency of
) -> None:
db.value += 1
await ctx.respond(f"Counter is at: `{db.value}`")

```

=== "REST"
Expand All @@ -86,7 +85,6 @@ In the above example, we asked `arc` that every time we ask for a dependency of
) -> None:
db.value += 1
await ctx.respond(f"Counter is at: `{db.value}`")

```

And here we request that `arc` injects the dependency we declared earlier into our command, passing the "database" to it. If you combine this example with the prior one, you should get a command that increments a counter every time it is invoked, and prints it's current state.
Expand Down
10 changes: 6 additions & 4 deletions docs/guides/error_handling.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
---
title: Error Handling
description: A guide on handling errors in arc
hide:
- toc
---

# Error Handling
Expand All @@ -20,7 +22,7 @@ To register a **local error handler** on a command, group, plugin, or the client
raise RuntimeError("I'm an error!")

@error_command_func.set_error_handler
async def(ctx: arc.GatewayContext, exc: Exception) -> None:
async def error_handler(ctx: arc.GatewayContext, exc: Exception) -> None:
if isinstance(exc, RuntimeError):
print(f"Handled error: {exc}")
return
Expand All @@ -36,15 +38,15 @@ To register a **local error handler** on a command, group, plugin, or the client
raise RuntimeError("I'm an error!")

@error_command_func.set_error_handler
async def(ctx: arc.RESTContext, exc: Exception) -> None:
async def error_handler(ctx: arc.RESTContext, exc: Exception) -> None:
if isinstance(exc, RuntimeError):
print(f"Handled error: {exc}")
return
raise exc
```

!!! warning
Errors that the current error handler cannot handle **must** be re-raised, otherwise the error will be silently ignored.
Errors that the current error handler cannot handle **must** be re-raised, otherwise the error will be silently ignored. This is true of the global error handler (the one added to the client) as well, otherwise **tracebacks will not be printed** to the console.

This can also be used as a regular function if using a decorator is not feasible:

Expand All @@ -68,7 +70,7 @@ This can also be used as a regular function if using a decorator is not feasible

- Command
- Command Group (if any)
- Plugin
- Plugin (if any)
- Client

If none of the error handlers handle an exception, it will be, by default, printed to the console with a full traceback.
6 changes: 4 additions & 2 deletions docs/guides/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ def my_hook(ctx: arc.Context[Any]) -> None:
!!! question "Can hooks be async?"
Hooks can either be async or sync, both variants are supported.

For a list of built-in hooks, see [here](../api_reference/utils/hooks.md).

## Pre-execution VS Post-execution hooks

There are two types of hooks you can add to a command, ones that run before the command is run (pre-execution) and ones that run after (post-execution).
Expand Down Expand Up @@ -69,8 +71,8 @@ A pre-execution hook can abort the execution of a command in one of two ways:
```py
def my_check(ctx: arc.Context[Any]) -> arc.HookResult:
if ctx.author.id != 1234567890:
return HookResult(abort=True)
return HookResult()
return arc.HookResult(abort=True)
return arc.HookResult()
```

The difference between these two approaches is that returning a [`HookResult`][arc.abc.hookable.HookResult] with `abort=True` will silently cancel the command from being executed, while the former will raise an exception that can then be handled (and should be handled) by an error handler.
Expand Down
163 changes: 163 additions & 0 deletions docs/guides/options.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
---
title: Options
description: A guide on slash command options
hide:
- toc
---

# Options

Options can be used to ask for user input in slash commands. They come in a variety of types and support various features, depending on the type.

## Declaring options

An **option** can be declared as parameters in the command callback using the following general syntax:

```py
var_name: arc.Option[type, arc.TypeParams(...)]
```

Where `type` is a substitute for the type of the option.

This is what that looks like in action:

=== "Gateway"

```py
@client.include
@arc.slash_command("name", "description")
async def options_cmd(
ctx: arc.GatewayContext,
number: arc.Option[int, arc.IntParams(description="A number")]
) -> None:
await ctx.respond(f"You provided {number}!")
```

=== "REST"

```py
@client.include
@arc.slash_command("name", "description")
async def options_cmd(
ctx: arc.RESTContext,
number: arc.Option[int, arc.IntParams(description="A number")]
) -> None:
await ctx.respond(f"You provided {number}!")
```

To make an option **not required**, you should set a default value:

=== "Gateway"

```py
@client.include
@arc.slash_command("name", "description")
async def options_cmd(
ctx: arc.GatewayContext,
number: arc.Option[int, arc.IntParams(description="A number")] = 10,
user: arc.Option[hikari.User | None, arc.UserParams(description="A user.")] = None,
) -> None:
await ctx.respond(f"You provided {number} and {user.mention if user else None}!")
```

=== "REST"

```py
@client.include
@arc.slash_command("name", "description")
async def options_cmd(
ctx: arc.RESTContext,
number: arc.Option[int, arc.IntParams(description="A number")] = 10,
user: arc.Option[hikari.User | None, arc.UserParams(description="A user.")] = None,
) -> None:
await ctx.respond(f"You provided {number} and {user.mention if user else None}!")
```

### Supported option types

The following option types are supported:

- `bool` & [`arc.BoolParams`][arc.command.option.BoolParams]
- `int` & [`arc.IntParams`][arc.command.option.IntParams]
- `float` & [`arc.FloatParams`][arc.command.option.FloatParams]
- `str` & [`arc.StrParams`][arc.command.option.StrParams]
- `hikari.Attachment` & [`arc.AttachmentParams`][arc.command.option.AttachmentParams]
- `hikari.User` & [`arc.UserParams`][arc.command.option.UserParams]
- `hikari.Role` & [`arc.RoleParams`][arc.command.option.RoleParams]
- `hikari.User | hikari.Role` & [`arc.MentionableParams`][arc.command.option.MentionableParams]
- Any hikari channel type & [`arc.ChannelParams`][arc.command.option.ChannelParams]

Trying to use any other type as an option will lead to errors.

!!! tip
The types of channels a user can pass to a **channel option** will depend on the type(s) of channels specified as the first parameter of a channel option.

This will only allow textable guild channels to be passed:

```py
textable: arc.Option[hikari.TextableGuildChannel, arc.ChannelParams(...)]
```

And this will only allow news or text channels:

```py
news_or_text: arc.Option[hikari.GuildTextChannel | hikari.GuildNewsChannel, arc.ChannelParams(...)]
```

To allow any channel type (including categories and threads!) use `hikari.PartialChannel`.

## Autocomplete

`str`, `int` and `float` support autocomplete, which means that you can dynamically offer choices as the user is typing in their input.

First, you need to define an autocomplete callback, this will be called repeatedly as the user is typing in the option:

=== "Gateway"

```py
async def provide_opts(data: arc.AutocompleteData[arc.GatewayClient, str]) -> list[str]:
if data.focused_value and len(data.focused_value) > 20:
return ["That", "is", "so", "long!"]
return ["Short", "is", "better!"]
```

=== "REST"

```py
async def provide_opts(data: arc.AutocompleteData[arc.RESTClient, str]) -> list[str]:
if data.focused_value and len(data.focused_value) > 20:
return ["That", "is", "so", "long!"]
return ["Short", "is", "better!"]
```

This callback should either return a `list[option_type]` (`list[str]`) in this case, or a `list[hikari.CommandChoice]`.

Then, to add autocomplete to an option, specify the `autocomplete_with=` argument in the params object and pass your autocomplete callback:

=== "Gateway"

```py
@client.include
@arc.slash_command("autocomplete", "Autocomplete options!")
async def autocomplete_command(
ctx: arc.GatewayContext,
# Set the 'autocomplete_with' parameter to the function that will be used to autocomplete the option
complete_me: arc.Option[str, arc.StrParams(description="I'll complete you!", autocomplete_with=provide_opts)]
) -> None:
await ctx.respond(f"You wrote: `{complete_me}`")
```

=== "REST"

```py
@client.include
@arc.slash_command("autocomplete", "Autocomplete options!")
async def autocomplete_command(
ctx: arc.RESTContext,
# Set the 'autocomplete_with' parameter to the function that will be used to autocomplete the option
complete_me: arc.Option[str, arc.StrParams(description="I'll complete you!", autocomplete_with=provide_opts)]
) -> None:
await ctx.respond(f"You wrote: `{complete_me}`")
```

With the following example, the autocompletion suggestions should change as your input reaches 20 characters.
26 changes: 26 additions & 0 deletions docs/guides/plugins_extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ extensions

plugin = arc.GatewayPlugin("foo")

@plugin.include
@arc.slash_command("foo", "Foo command")
async def foo_cmd(
ctx: arc.GatewayContext,
) -> None:
await ctx.respond(f"Foo!")

@arc.loader
def loader(client: arc.GatewayClient) -> None:
Expand All @@ -74,6 +80,12 @@ extensions

plugin = arc.GatewayPlugin("bar")

@plugin.include
@arc.slash_command("bar", "Bar command")
async def bar_cmd(
ctx: arc.GatewayContext,
) -> None:
await ctx.respond(f"Bar!")

@arc.loader
def loader(client: arc.GatewayClient) -> None:
Expand All @@ -94,6 +106,13 @@ extensions

plugin = arc.RESTPlugin("foo")

@plugin.include
@arc.slash_command("foo", "Foo command")
async def foo_cmd(
ctx: arc.RESTContext,
) -> None:
await ctx.respond(f"Foo!")

# ...

@arc.loader
Expand All @@ -113,6 +132,13 @@ extensions

plugin = arc.RESTPlugin("bar")

@plugin.include
@arc.slash_command("bar", "Bar command")
async def bar_cmd(
ctx: arc.RESTContext,
) -> None:
await ctx.respond(f"Bar!")

# ...

@arc.loader
Expand Down
Loading

0 comments on commit d697fd2

Please sign in to comment.