Skip to content

Commit 721eceb

Browse files
authored
Merge pull request #1470 from interactions-py/unstable
5.8.0
2 parents 12d7053 + 61f34d8 commit 721eceb

File tree

25 files changed

+1397
-108
lines changed

25 files changed

+1397
-108
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: interactions.ext.hybrid_commands.context
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: interactions.ext.hybrid_commands.hybrid_slash
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Hybrid Commands Index
2+
3+
- [Context](context)
4+
- [Hybrid Slash](hybrid_slash)
5+
- [Manager](manager)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
::: interactions.ext.hybrid_commands.manager

docs/src/API Reference/API Reference/ext/index.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ These files contain useful features that help you develop a bot
1313

1414
- [Prefixed Commands](prefixed_commands)
1515
- An extension to allow prefixed/text commands
16+
17+
- [Hybrid Commands](hybrid_commands)
18+
- An extension that makes hybrid slash/prefixed commands

docs/src/Guides/03 Creating Commands.md

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,8 @@ This will show up in discord as `/base group command`. There are more ways to ad
9191

9292
=== ":three: Class Definition"
9393
```python
94+
from interactions import SlashCommand
95+
9496
base = SlashCommand(name="base", description="My command base")
9597
group = base.group(name="group", description="My command group")
9698

@@ -125,7 +127,7 @@ Now that you know all the options you have for options, you can opt into adding
125127

126128
You do that by using the `@slash_option()` decorator and passing the option name as a function parameter:
127129
```python
128-
from interactions import OptionType
130+
from interactions import OptionType, slash_option
129131

130132
@slash_command(name="my_command", ...)
131133
@slash_option(
@@ -260,20 +262,23 @@ from interactions import AutocompleteContext
260262

261263
@my_command.autocomplete("string_option")
262264
async def autocomplete(self, ctx: AutocompleteContext):
263-
# make sure this is done within three seconds
265+
string_option_input = ctx.input_text # can be empty
266+
# you can use ctx.kwargs.get("name") for other options - note they can be empty too
267+
268+
# make sure you respond within three seconds
264269
await ctx.send(
265270
choices=[
266271
{
267-
"name": f"{ctx.input_text}a",
268-
"value": f"{ctx.input_text}a",
272+
"name": f"{string_option_input}a",
273+
"value": f"{string_option_input}a",
269274
},
270275
{
271-
"name": f"{ctx.input_text}b",
272-
"value": f"{ctx.input_text}b",
276+
"name": f"{string_option_input}b",
277+
"value": f"{string_option_input}b",
273278
},
274279
{
275-
"name": f"{ctx.input_text}c",
276-
"value": f"{ctx.input_text}c",
280+
"name": f"{string_option_input}c",
281+
"value": f"{string_option_input}c",
277282
},
278283
]
279284
)
@@ -495,28 +500,60 @@ The same principle can be used to reuse autocomplete options.
495500

496501
## Simplified Error Handling
497502

498-
If you want error handling for all commands, you can override `Client` and define your own.
499-
Any error from interactions will trigger `on_command_error`. That includes context menus.
503+
If you want error handling for all commands, you can override the default error listener and define your own.
504+
Any error from interactions will trigger `CommandError`. That includes context menus.
500505

501506
In this example, we are logging the error and responding to the interaction if not done so yet:
502507
```python
503-
from interactions import Client
508+
import traceback
504509
from interactions.api.events import CommandError
505510

506-
class CustomClient(Client):
507-
@listen(disable_default_listeners=True) # tell the dispatcher that this replaces the default listener
508-
async def on_command_error(self, event: CommandError):
509-
logger.error(event.error)
510-
if not event.ctx.responded:
511-
await event.ctx.send("Something went wrong.")
512-
513-
client = CustomErrorClient(...)
511+
@listen(CommandError, disable_default_listeners=True) # tell the dispatcher that this replaces the default listener
512+
async def on_command_error(self, event: CommandError):
513+
traceback.print_exception(event.error)
514+
if not event.ctx.responded:
515+
await event.ctx.send("Something went wrong.")
514516
```
515517

516-
There also is `on_command` which you can overwrite too. That fires on every interactions usage.
518+
There also is `CommandCompletion` which you can overwrite too. That fires on every interactions usage.
517519

518520
## I Need A Custom Parameter Type
519521

520522
If your bot is complex enough, you might find yourself wanting to use custom models in your commands.
521523

522-
To do this, you'll want to use a string option, and define a converter. Information on how to use converters can be found [on the converter page](/interactions.py/Guides/08 Converters/).
524+
To do this, you'll want to use a string option, and define a converter. Information on how to use converters can be found [on the converter page](/Guides/08 Converters).
525+
526+
## I Want To Make A Prefixed/Text Command Too
527+
528+
You're in luck! You can use a hybrid command, which is a slash command that also gets converted to an equivalent prefixed command under the hood.
529+
530+
Hybrid commands are their own extension, and require [prefixed commands to set up beforehand](/interactions.py/Guides/26 Prefixed Commands). After that, use the `setup` function in the `hybrid_commands` extension in your main bot file.
531+
532+
Your setup can (but doesn't necessarily have to) look like this:
533+
534+
```python
535+
import interactions
536+
from interactions.ext import prefixed_commands as prefixed
537+
from interactions.ext import hybrid_commands as hybrid
538+
539+
bot = interactions.Client(...) # may want to enable the message content intent
540+
prefixed.setup(bot) # normal step for prefixed commands
541+
hybrid.setup(bot) # note its usage AFTER prefixed commands have been set up
542+
```
543+
544+
To actually make slash commands, simply replace `@slash_command` with `@hybrid_slash_command`, and `SlashContext` with `HybridContext`, like so:
545+
546+
```python
547+
from interactions.ext.hybrid_commands import hybrid_slash_command, HybridContext
548+
549+
@hybrid_slash_command(name="my_command", description="My hybrid command!")
550+
async def my_command_function(ctx: HybridContext):
551+
await ctx.send("Hello World")
552+
```
553+
554+
Suggesting you are using the default mention settings for your bot, you should be able to run this command by `@BotPing my_command`.
555+
556+
As you can see, the only difference between hybrid commands and slash commands, from a developer perspective, is that they use `HybridContext`, which attempts
557+
to seamlessly allow using the same context for slash and prefixed commands. You can always get the underlying context via `inner_context`, though.
558+
559+
Of course, keep in mind that support two different types of commands is hard - some features may not get represented well in prefixed commands, and autocomplete is not possible at all.

docs/src/Guides/10 Events.md

Lines changed: 105 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,79 +1,138 @@
11
# Events
22

3-
Events are dispatched whenever a subscribed event gets sent by Discord.
3+
Events (in interactions.py) are pieces of information that are sent whenever something happens in Discord or in the library itself - this includes channel updates, message sending, the bot starting up, and more.
44

5-
## What Events Can I Get
5+
## Intents
66

77
What events you subscribe to are defined at startup by setting your `Intents`.
88

9-
`Intents.DEFAULT` is a good place to start if your bot is new and small, otherwise, it is recommended to take your time and go through them one by one.
10-
```python
11-
bot = Client(intents=Intents.DEFAULT)
12-
bot.start("Put your token here")
13-
```
9+
By default, interactions.py automatically uses every intent but privileged intents (discussed in a bit). This means you're receiving data about *a lot* of events - it's nice to have those intents while starting out, but we heavily encourage narrowing them so that your bot uses less memory and isn't slowed down by processing them.
1410

15-
For more information, please visit the API reference [here](/interactions.py/API Reference/API Reference/models/Discord/enums/#internal.models.discord.enums.Intents).
11+
There are two ways of setting them. We'll use the `GUILDS` and `GUILD_INVITES` intents as an example, but you should decide what intents you need yourself.
1612

17-
## Hey Listen!!!
13+
=== ":one: Directly through `Intents`"
14+
```python
15+
from interactions import Intents
16+
bot = Client(intents=Intents.GUILDS | Intents.GUILD_INVITES)
17+
```
1818

19-
Now you can receive events. To respond to them, you need to register a callback. Callbacks should be lower-case, use `_` instead of spaces and start with `on_`.
20-
Depending on how you register your callbacks that's not a requirement, but it is a good habit nonetheless.
19+
=== ":two: `Intents.new`"
20+
```python
21+
from interactions import Intents
22+
bot = Client(intents=Intents.new(guilds=True, guild_invites=True))
23+
```
2124

22-
For example, the event callback for the `ChannelCreate` event should be called `on_channel_create`.
25+
Some intents are deemed to have sensitive content by Discord and so have extra restrictions on them - these are called **privileged intents.** At the time of writing, these include *message content, guild members, and presences.* These require extra steps to enable them for your bot:
2326

24-
You can find all events and their signatures [here](/interactions.py/API Reference/API Reference/events/discord/).
27+
1. Go to the [Discord developer portal](https://discord.com/developers/applications/).
28+
2. Select your application.
29+
3. In the "Bot" tab, go to the "Privileged Gateway Intents" category and scroll down to the privileged intents you want.
30+
4. Enable the toggle.
31+
- **If your bot is verified or in more than 100 servers, you need to apply for the intent through Discord in order to toggle it.** This may take a couple of weeks.
2532

26-
Be aware that your `Intents` must be set to receive the event you are looking for.
33+
Then, you can specify it in your bot just like the other intents. If you encounter any errors during this process, [referring to the intents page on Discord's documentation](https://discord.com/developers/docs/topics/gateway#gateway-intents) may help.
2734

28-
---
35+
!!! danger
36+
`Intents.ALL` is a shortcut provided by interactions.py to enable *every single intents, including privileged intents.* This is very useful while testing bots, **but this shortcut is an incredibly bad idea to use when actually running your bots for use.** As well as adding more strain on the bot (as discussed earlier with normal intents), this is just a bad idea privacy wise: your bot likely does not need to know that much data.
2937

30-
There are two ways to register events. **Decorators** are the recommended way to do this.
38+
For more information, please visit the API reference about Intents [at this page](/interactions.py/API Reference/API Reference/models/Discord/enums/#interactions.models.discord.enums.Intents).
3139

32-
=== ":one: Decorators"
33-
```python
34-
from interactions import listen
35-
from interactions.api.events import ChannelCreate
40+
## Subscribing to Events
3641

37-
@listen()
38-
async def on_channel_create(event: ChannelCreate):
39-
# this event is called when a channel is created in a guild where the bot is
42+
After your intents have been properly configured, you can start to listen to events. Say, if you wanted to listen to channels being created in a guild the bot can see, then all you would have to do is this:
4043

41-
print(f"Channel created with name: {event.channel.name}")
42-
```
44+
```python
45+
from interactions import listen
46+
from interactions.api.events import ChannelCreate
4347

44-
You can also use `@listen` with any function names:
48+
@listen(ChannelCreate)
49+
async def an_event_handler(event: ChannelCreate):
50+
print(f"Channel created with name: {event.channel.name}")
51+
```
4552

53+
As you can see, the `listen` statement marks a function to receive (or, well, listen/subscribe to) a specific event - we specify which event to receive by passing in the *event object*, which an object that contains all information about an event. Whenever that events happens in Discord, it triggers our function to run, passing the event object into it. Here, we get the channel that the event contains and send out its name to the terminal.
54+
55+
???+ note "Difference from other Python Discord libraries"
56+
If you come from some other Python Discord libraries, or even come from older versions of interactions.py, you might have noticed how the above example uses an *event object* - IE a `ChannelCreate` object - instead of passing the associated object with that event - IE a `Channel` (or similar) object - into the function. This is intentional - by using event objects, we have greater control of what information we can give to you.
57+
58+
For pretty much every event object, the object associated with that event is still there, just as an attribute. Here, the channel is in `event.channel` - you'll usually find the object in other events in a similar format.
59+
Update events usually use `event.before` and `event.after` too.
60+
61+
While the above is the recommended format for listening to events (as you can be sure that you specified the right event), there are other methods for specifying what event you're listening to:
62+
63+
???+ warning "Event name format for some methods"
64+
You may notice how some of these methods require the event name to be `all_in_this_case`. The casing itself is called *snake case* - it uses underscores to indicate either a literal space or a gap between words, and exclusively uses lowercase otherwise. To transform an event object, which is in camel case (more specifically, Pascal case), to snake case, first take a look at the letters that are capital, make them lowercase, and add an underscore before those letters *unless it's the first letter of the name of the object*.
65+
66+
For example, looking at **C**hannel**C**reate, we can see two capital letters. Making them lowercase makes it **c**hannel**c**reate, and then adding an underscore before them makes them **c**hannel**_c**reate (notice how the first letter does *not* have a lowercase before them).
67+
68+
You *can* add an `on_` prefixed before the modified event name too. For example, you could use both `on_channel_create` and `channel_create`, depending on your preference.
69+
70+
If you're confused by any of this, stay away from methods that use this type of name formatting.
71+
72+
=== ":one: Type Annotation"
4673
```python
47-
@listen(ChannelCreate)
48-
async def my_function(event: ChannelCreate):
49-
# you can pass the event
74+
@listen()
75+
async def an_event_handler(event: ChannelCreate):
5076
...
77+
```
5178

52-
@listen("on_channel_create")
53-
async def my_function(event: ChannelCreate):
54-
# you can also pass the event name as a string
79+
=== ":two: String in `listen`"
80+
```python
81+
@listen("channel_create")
82+
async def an_event_handler(event):
5583
...
84+
```
5685

86+
=== ":three: Function name"
87+
```python
5788
@listen()
58-
async def my_function(event: ChannelCreate):
59-
# you can also use the typehint of `event`
89+
async def channel_create(event):
6090
...
6191
```
6292

63-
=== ":two: Manual Registration"
64-
You can also register the events manually. This gives you the most flexibility, but it's not the cleanest.
93+
## Other Notes About Events
6594

66-
```python
67-
from interactions import Listener
68-
from interactions.api.events import ChannelCreate
95+
### No Argument Events
96+
97+
Some events may have no information to pass - the information is the event itself. This happens with some of the internal events - events that are specific to interactions.py, not Discord.
6998

70-
async def on_channel_create(event: ChannelCreate):
71-
# this event is called when a channel is created in a guild where the bot is
99+
Whenever this happens, you can specify the event to simply not pass anything into the function, as can be seen with the startup event:
72100

73-
print(f"Channel created with name: {event.channel.name}")
101+
```python
102+
from interactions.api.events import Startup
74103

104+
@listen(Startup)
105+
async def startup_func():
106+
...
107+
```
75108

76-
bot = Client(intents=Intents.DEFAULT)
77-
bot.add_listener(Listener(func=on_channel_create, event="on_channel_create"))
78-
bot.start("Put your token here")
79-
```
109+
If you forget, the library will just pass an empty object to avoid errors.
110+
111+
### Disabling Default Listeners
112+
113+
Some internal events, like `ModalCompletion`, have default listeners that perform niceties like logging the command/interaction logged. You may not want this, however, and may want to completely override this behavior without subclassiung `Client`. If so, you can acheive it through `disable_default_listeners`:
114+
115+
```python
116+
from interactions.api.events import ModalCompletion
117+
118+
@listen(ModalCompletion, disable_default_listeners=True)
119+
async def my_modal_completion(event: ModalCompletion):
120+
print("I now control ModalCompletion!")
121+
```
122+
123+
A lot of times, this behavior is used for custom error tracking. If so, [take a look at the error tracking guide](../25 Error Tracking) for a guide on that.
124+
125+
## Events to Listen To
126+
127+
There are a plethora of events that you can listen to. You can find a list of events that are currently supported through the two links below - every class listened on these two pages are available for you, though be aware that your `Intents` must be set appropriately to receive the event you are looking for.
128+
129+
- [Discord Events](/interactions.py/API Reference/API Reference/events/discord/)
130+
- [Internal Events](/interactions.py/API Reference/API Reference/events/internal/)
131+
132+
### Frequently Used Events
133+
134+
- [Startup](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.Startup) is an event, as its name implies, that runs when the bot is first started up - more specifically, it runs when the bot is first ready to do actions. This is a good place to set up tools or libraries that require an asynchronous function.
135+
- [Error](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.Error) and its many, *many* subclasses about specific types of errors trigger whenever an error occurs while the bot is running. If you want error *tracking* (IE just logging the errors you get to fix them later on), then [take a look at the error tracking guide](../25 Error Tracking). Otherwise, you can do specific error handling using these events (ideally with `disable_default_listeners` turned on) to provide custom messages for command errors.
136+
- [Component](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.Component), [ButtonPressed](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.ButtonPressed), [Select](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.Select), and [ModalCompletion](/interactions.py/API Reference/API Reference/events/internal/#interactions.api.events.internal.ModalCompletion) may be useful for you if you're trying to respond to component or modal interactions - take a look at the [component guide](../05 Components) or the [modal guide](../06 Modals) for more information.
137+
- [MessageCreate](/interactions.py/API Reference/API Reference/discord/#interactions.api.events.discord.MessageCreate) is used whenever anyone sends a message to a channel the bot can see. This can be useful for automoderation, though note *message content is a privileged intent*, as talked about above. For prefixed/text commands in particular, we already have our own implementation - take a look at them [at this page](../26 Prefixed Commands).
138+
- [GuildJoin](/interactions.py/API Reference/API Reference/events/discord/#interactions.api.events.discord.GuildJoin) and [GuildLeft](/interactions.py/API Reference/API Reference/events/discord/#interactions.api.events.discord.GuildLeft) are, as you can expect, events that are sent whenever the bot joins and leaves a guild. Note that for `GuildJoin`, the event triggers for *every guild on startup* - it's best to have a check to see if the bot is ready through `bot.is_ready` and ignore this event if it isn't.

interactions/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@
149149
GuildForum,
150150
GuildForumPost,
151151
GuildIntegration,
152+
GuildMedia,
152153
GuildNews,
153154
GuildNewsConverter,
154155
GuildNewsThread,
@@ -476,6 +477,7 @@
476477
"GuildForum",
477478
"GuildForumPost",
478479
"GuildIntegration",
480+
"GuildMedia",
479481
"GuildNews",
480482
"GuildNewsConverter",
481483
"GuildNewsThread",

0 commit comments

Comments
 (0)