Skip to content

Commit 335d524

Browse files
authored
Merge branch 'unstable' into rate_limit_per_user
Signed-off-by: LordOfPolls <dev@lordofpolls.com>
2 parents f00208c + 6a2d8e6 commit 335d524

File tree

17 files changed

+220
-123
lines changed

17 files changed

+220
-123
lines changed

docs/src/Guides/01 Getting Started.md

Lines changed: 48 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -9,102 +9,73 @@ Ready to get your Python on and create a Discord bot? This guide's got you cover
99
- [x] [A bot account](02 Creating Your Bot.md)
1010
- [ ] An aversion to puns
1111

12-
## Installation Methods
12+
## Installing and Setting up a Bot
1313

14-
There are two different ways to install this library and create your bot.
14+
### Virtual Environments
1515

16-
=== "Using a Template"
16+
We strongly recommend that you make use of Virtual Environments when working on any project.
17+
This means that each project will have its own libraries of any version and does not affect anything else on your system.
18+
Don't worry, this isn't setting up a full-fledged virtual machine, just small python environment.
1719

18-
We created a [cookiecutter template](https://github.com/Discord-Snake-Pit/Bot-Template) which you can use to set up your own bot faster.
19-
With the template, your code will already have a well-defined structure which will make development easier for you.
20-
21-
We recommend newer devs to make use of this template.
22-
23-
### Template Feature
24-
- Basic, ready to go bot
25-
- Implementation of best practises
26-
- General extensibility
27-
- Example command, context menu, component, and event
28-
- Logging to both console and file
29-
- Pip and poetry config
30-
- Pre-commit config
31-
- Dockerfile and pre-made docker-compose
32-
33-
### Template Installation
34-
1. Install cookiecutter - `pip install cookiecutter`
35-
2. Set up the template - `cookiecutter https://github.com/Discord-Snake-Pit/Bot-Template`
36-
37-
And that's it!
38-
39-
More information can be found [here](https://github.com/Discord-Snake-Pit/Bot-Template).
40-
41-
42-
=== "Manual Installation"
43-
44-
### Virtual-Environments
45-
46-
We strongly recommend that you make use of Virtual Environments when working on any project.
47-
This means that each project will have its own libraries of any version and does not affect anything else on your system.
48-
Don't worry, this isn't setting up a full-fledged virtual machine, just small python environment.
49-
50-
=== ":material-linux: Linux"
51-
```shell
52-
cd "[your bots directory]"
53-
python3 -m venv venv
54-
source venv/bin/activate
55-
```
20+
=== ":material-linux: Linux"
21+
```shell
22+
cd "[your bots directory]"
23+
python3 -m venv venv
24+
source venv/bin/activate
25+
```
5626

57-
=== ":material-microsoft-windows: Windows"
58-
```shell
59-
cd "[your bots directory]"
60-
py -3 -m venv venv
61-
venv/Scripts/activate
62-
```
27+
=== ":material-microsoft-windows: Windows"
28+
```shell
29+
cd "[your bots directory]"
30+
py -3 -m venv venv
31+
venv/Scripts/activate
32+
```
6333

64-
It's that simple, now you're using a virtual environment. If you want to leave the environment just type `deactivate`.
65-
If you want to learn more about the virtual environments, check out [this page](https://docs.python.org/3/tutorial/venv.html)
34+
It's that simple, now you're using a virtual environment. If you want to leave the environment just type `deactivate`.
35+
If you want to learn more about the virtual environments, check out [this page](https://docs.python.org/3/tutorial/venv.html)
6636

67-
### Pip install
37+
### Pip install
6838

69-
Now let's get the library installed.
39+
Now let's get the library installed.
7040

71-
=== ":material-linux: Linux"
72-
```shell
73-
python3 -m pip install discord-py-interactions --upgrade
74-
```
41+
=== ":material-linux: Linux"
42+
```shell
43+
python3 -m pip install discord-py-interactions --upgrade
44+
```
7545

76-
=== ":material-microsoft-windows: Windows"
77-
```shell
78-
py -3 -m pip install discord-py-interactions --upgrade
79-
```
46+
=== ":material-microsoft-windows: Windows"
47+
```shell
48+
py -3 -m pip install discord-py-interactions --upgrade
49+
```
8050

81-
### Basic bot
51+
### Basic bot
8252

83-
Now let's get a basic bot going, for your code, you'll want something like this:
53+
!!! note
54+
This is a very basic bot. For a more detailed example/template bot that demonstrates many parts of interactions.py, see [the boilerplate repository.](https://github.com/interactions-py/boilerplate)
8455

85-
```python
86-
from interactions import Client, Intents, listen
56+
Now let's get a basic bot going, for your code, you'll want something like this:
8757

88-
bot = Client(intents=Intents.DEFAULT)
89-
# intents are what events we want to receive from discord, `DEFAULT` is usually fine
58+
```python
59+
from interactions import Client, Intents, listen
9060

91-
@listen() # this decorator tells snek that it needs to listen for the corresponding event, and run this coroutine
92-
async def on_ready():
93-
# This event is called when the bot is ready to respond to commands
94-
print("Ready")
95-
print(f"This bot is owned by {bot.owner}")
61+
bot = Client(intents=Intents.DEFAULT)
62+
# intents are what events we want to receive from discord, `DEFAULT` is usually fine
9663

64+
@listen() # this decorator tells snek that it needs to listen for the corresponding event, and run this coroutine
65+
async def on_ready():
66+
# This event is called when the bot is ready to respond to commands
67+
print("Ready")
68+
print(f"This bot is owned by {bot.owner}")
9769

98-
@listen()
99-
async def on_message_create(event):
100-
# This event is called when a message is sent in a channel the bot can see
101-
print(f"message received: {event.message.content}")
10270

71+
@listen()
72+
async def on_message_create(event):
73+
# This event is called when a message is sent in a channel the bot can see
74+
print(f"message received: {event.message.content}")
10375

104-
bot.start("Put your token here")
105-
```
10676

107-
---
77+
bot.start("Put your token here")
78+
```
10879

10980
Congratulations! You now have a basic understanding of this library.
11081
If you have any questions check out our other guides, or join the

docs/src/Guides/26 Prefixed Commands.md

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -100,45 +100,65 @@ async def test(ctx: PrefixedContext, arg1, arg2):
100100

101101
![Two Parameters](../images/PrefixedCommands/TwoParams.png "The above running with the arguments: one two")
102102

103-
### Variable and Keyword-Only Arguments
103+
### Variable and Consume Rest Arguments
104104

105105
There may be times where you wish for an argument to be able to have multiple words without wrapping them in quotes. There are two ways of approaching this.
106106

107107
#### Variable
108108

109109
If you wish to get a list (or more specifically, a tuple) of words for one argument, or simply want an undetermined amount of arguments for a command, then you should use a *variable* argument:
110-
```python
111-
@prefixed_command()
112-
async def test(ctx: PrefixedContext, *args):
113-
await ctx.reply(f"{len(args)} arguments: {', '.join(args)}")
114-
```
110+
111+
=== ":one: Tuple Argument"
112+
```python
113+
@prefixed_command()
114+
async def test(ctx: PrefixedContext, args: tuple[str, ...]):
115+
await ctx.reply(f"{len(args)} arguments: {', '.join(args)}")
116+
```
117+
118+
=== ":two: Variable Positional Argument"
119+
```python
120+
@prefixed_command()
121+
async def test(ctx: PrefixedContext, *args):
122+
await ctx.reply(f"{len(args)} arguments: {', '.join(args)}")
123+
```
115124

116125
The result looks something like this:
117126

118127
![Variable Parameter](../images/PrefixedCommands/VariableParam.png "The above running with the arguments: hello there world "how are you?"")
119128

120129
Notice how the quoted words are still parsed as one argument in the tuple.
121130

122-
#### Keyword-Only
131+
#### Consume Rest
123132

124-
If you simply wish to take in the rest of the user's input as an argument, you can use a keyword-only argument, like so:
125-
```python
126-
@prefixed_command()
127-
async def test(ctx: PrefixedContext, *, arg):
128-
await ctx.reply(arg)
129-
```
133+
If you simply wish to take in the rest of the user's input as an argument, you can use a consume rest argument, like so:
134+
135+
=== ":one: ConsumeRest Alias"
136+
```python
137+
from interactions import ConsumeRest
138+
139+
@prefixed_command()
140+
async def test(ctx: PrefixedContext, arg: ConsumeRest[str]):
141+
await ctx.reply(arg)
142+
```
143+
144+
=== ":two: Keyword-only Argument"
145+
```python
146+
@prefixed_command()
147+
async def test(ctx: PrefixedContext, *, arg):
148+
await ctx.reply(arg)
149+
```
130150

131151
The result looks like this:
132152

133-
![Keyword-Only Parameter](../images/PrefixedCommands/KeywordParam.png "The above running with the arguments: hello world!")
153+
![Consume Rest Parameter](../images/PrefixedCommands/ConsumeRestParam.png "The above running with the arguments: hello world!")
134154

135155
???+ note "Quotes"
136-
If a user passes quotes into a keyword-only argument, then the resulting argument will have said quotes.
156+
If a user passes quotes into consume rest argument, then the resulting argument will have said quotes.
137157

138-
![Keyword-Only Quotes](../images/PrefixedCommands/KeywordParamWithQuotes.png "The above running with the arguments: "hello world!"")
158+
![Consume Rest Quotes](../images/PrefixedCommands/ConsumeRestWithQuotes.png "The above running with the arguments: "hello world!"")
139159

140160
!!! warning "Parser ambiguities"
141-
Due to parser ambiguities, you can *only* have either a single variable or keyword-only/consume rest argument.
161+
Due to parser ambiguities, you can *only* have either a single variable or consume rest argument.
142162

143163
## Typehinting and Converters
144164

interactions/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@
103103
ComponentCommand,
104104
ComponentContext,
105105
ComponentType,
106+
ConsumeRest,
106107
context_menu,
107108
ContextMenu,
108109
ContextMenuContext,
@@ -338,6 +339,7 @@
338339
ExponentialBackoffSystem,
339340
LeakyBucketSystem,
340341
TokenBucketSystem,
342+
ForumSortOrder,
341343
)
342344
from .api import events
343345
from . import ext
@@ -413,6 +415,7 @@
413415
"ComponentCommand",
414416
"ComponentContext",
415417
"ComponentType",
418+
"ConsumeRest",
416419
"const",
417420
"context_menu",
418421
"CONTEXT_MENU_NAME_LENGTH",
@@ -458,6 +461,7 @@
458461
"File",
459462
"FlatUIColors",
460463
"FlatUIColours",
464+
"ForumSortOrder",
461465
"ForumLayoutType",
462466
"get_components_ids",
463467
"get_logger",

interactions/api/events/discord.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,7 @@ class AutoModDeleted(AutoModCreated):
142142

143143
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
144144
class ApplicationCommandPermissionsUpdate(BaseEvent):
145+
id: "Snowflake_Type" = attrs.field(repr=False, metadata=docs("The ID of the command permissions were updated for"))
145146
guild_id: "Snowflake_Type" = attrs.field(
146147
repr=False, metadata=docs("The guild the command permissions were updated in")
147148
)

interactions/ext/hybrid_commands/hybrid_slash.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import asyncio
22
import inspect
3-
from typing import Any, Callable, List, Optional, Union, TYPE_CHECKING, Awaitable
3+
from typing import Any, Callable, List, Optional, Union, TYPE_CHECKING, Awaitable, Annotated, get_origin, get_args
44

55
import attrs
66
from interactions import (
77
BaseContext,
88
Converter,
9+
ConsumeRest,
910
NoArgumentConverter,
1011
Attachment,
1112
SlashCommandChoice,
@@ -31,7 +32,7 @@
3132
from interactions.client.utils.misc_utils import maybe_coroutine, get_object_name
3233
from interactions.client.errors import BadArgument
3334
from interactions.ext.prefixed_commands import PrefixedCommand, PrefixedContext
34-
from interactions.models.internal.converters import _LiteralConverter
35+
from interactions.models.internal.converters import _LiteralConverter, CONSUME_REST_MARKER
3536
from interactions.models.internal.checks import guild_only
3637

3738
if TYPE_CHECKING:
@@ -355,12 +356,24 @@ def slash_to_prefixed(cmd: HybridSlashCommand) -> _HybridToPrefixedCommand: # n
355356
default = inspect.Parameter.empty
356357
kind = inspect.Parameter.POSITIONAL_ONLY if cmd._uses_arg else inspect.Parameter.POSITIONAL_OR_KEYWORD
357358

359+
consume_rest: bool = False
360+
358361
if slash_param := cmd.parameters.get(name):
359362
kind = slash_param.kind
360363

361364
if kind == inspect.Parameter.KEYWORD_ONLY: # work around prefixed cmd parsing
362365
kind = inspect.Parameter.POSITIONAL_OR_KEYWORD
363366

367+
# here come the hacks - these allow ConsumeRest (the class) to be passed through
368+
if get_origin(slash_param.type) == Annotated:
369+
args = get_args(slash_param.type)
370+
# ComsumeRest[str] or Annotated[ConsumeRest[str], Converter] support
371+
# by all means, the second isn't allowed in prefixed commands, but we'll ignore that for converter support for slash cmds
372+
if args[1] is CONSUME_REST_MARKER or (
373+
args[0] == Annotated and get_args(args[0])[1] is CONSUME_REST_MARKER
374+
):
375+
consume_rest = True
376+
364377
if slash_param.converter:
365378
annotation = slash_param.converter
366379
if slash_param.default is not MISSING:
@@ -387,6 +400,9 @@ def slash_to_prefixed(cmd: HybridSlashCommand) -> _HybridToPrefixedCommand: # n
387400
if not option.required and default == inspect.Parameter.empty:
388401
default = None
389402

403+
if consume_rest:
404+
annotation = ConsumeRest[annotation]
405+
390406
actual_param = inspect.Parameter(
391407
name=name,
392408
kind=kind,

0 commit comments

Comments
 (0)