Skip to content
Merged
Show file tree
Hide file tree
Changes from 61 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
5794b2f
feat: new Command object
Toricane Jun 28, 2022
7b70df0
fix: import error
Toricane Jun 28, 2022
4eef731
feat: option decorator
Toricane Jun 29, 2022
b074c48
fix: attempt to fix circular import
Toricane Jun 29, 2022
4678cd8
fix: circular import
Toricane Jun 29, 2022
1c0cf11
refactor: replace errors with LibraryException
Toricane Jun 29, 2022
448d552
refactor: few small refactors
Toricane Jun 29, 2022
5c5ecf3
refactor: test
Toricane Jun 29, 2022
98c21a4
feat: implement Command obj in decorators
Toricane Jun 30, 2022
45e6913
feat: working subcommand system
Toricane Jun 30, 2022
ce7edf3
feat: more customizable subcommand system
Toricane Jun 30, 2022
d12883b
feat: working Command system in Extensions
Toricane Jun 30, 2022
4c06e6c
refactor: add a TODO
Toricane Jun 30, 2022
eb8c379
refactor: tidy up the code, improve coro calling
Toricane Jul 1, 2022
26de2fd
refactor: rename Command.base to Command.name
Toricane Jul 1, 2022
d243756
feat: better group behavior
Toricane Jul 1, 2022
8533cf5
Merge branch 'unstable' into unstable
Toricane Jul 1, 2022
b5ab857
refactor: replace a few more base= with name=, tidy up code
Toricane Jul 1, 2022
e476f84
refactor: remove client field from Command and properly teardown comm…
Toricane Jul 2, 2022
7db851e
refactor: different method of looping
Toricane Jul 2, 2022
08eaf87
feat: implement default_scope functionality
Toricane Jul 2, 2022
62c8fa7
refactor: remove options param from Command.group()
Toricane Jul 2, 2022
0b39f85
refactor: make autocomplete work, change typehints, make user and mes…
Toricane Jul 6, 2022
7f41eba
feat: implement autodefer and spread_to_rows
Toricane Jul 6, 2022
ec3287f
fix: commands and autocomplete with self not passed when inside Exten…
Toricane Jul 7, 2022
e2ac9b7
style: add typehints
Toricane Jul 7, 2022
009a936
refactor: tweak default scope setting
Toricane Jul 7, 2022
b277ecc
docs: some docstrings filled in
Toricane Jul 7, 2022
c31925d
docs: more docstrings, organization of code
Toricane Jul 9, 2022
9e131b9
refactor: move resolving commands outside of __sync
Toricane Jul 9, 2022
446526e
refactor: change __all__ from list to tuple
Toricane Jul 9, 2022
180a791
fix: subcommands error with variables
Toricane Jul 9, 2022
682dec0
fix: incorrect error
Toricane Jul 9, 2022
dc693bd
docs: utils.py docstrings
Toricane Jul 9, 2022
ba585c4
feat: new way of creating an ActionRow (pun intended)
Toricane Jul 9, 2022
1b4f58f
Merge branch 'unstable' into unstable
Toricane Jul 9, 2022
29b6239
refactor: make option decorator better
Toricane Jul 10, 2022
2ae9c3e
refactor: change Command.self to Command.extension
Toricane Jul 10, 2022
6a7dce0
docs: fix incorrect typehint
Toricane Jul 10, 2022
83c560f
docs: modify docstring
Toricane Jul 10, 2022
0401317
docs: new docs page
Toricane Jul 10, 2022
bb5410f
docs: add doc file
Toricane Jul 10, 2022
dfa4ac1
docs: move line
Toricane Jul 10, 2022
e12906d
docs: modify docstring of spread_to_rows
Toricane Jul 10, 2022
3932438
refactor: rename variables
Toricane Jul 10, 2022
e393742
feat: implement Command.error decorator
Toricane Jul 10, 2022
a8da923
fix: typehint
Toricane Jul 10, 2022
a7cdb3b
docs: attempt to fix parameters of Client docstring
Toricane Jul 10, 2022
52e1369
ci: correct from checks.
pre-commit-ci[bot] Jul 10, 2022
a792fe7
docs: fix docstring
Toricane Jul 10, 2022
30a753a
style: delete a blank line
Toricane Jul 10, 2022
8fa993d
chore: merge branch 'unstable' of https://github.com/Toricane/library…
Toricane Jul 10, 2022
cab01f9
docs: update quickstart
Toricane Jul 10, 2022
922eb32
ci: correct from checks.
pre-commit-ci[bot] Jul 10, 2022
7269e98
docs: modify quickstart
Toricane Jul 10, 2022
a303271
Merge branch 'unstable' of https://github.com/Toricane/library into u…
Toricane Jul 10, 2022
186941a
docs: document new features
Toricane Jul 11, 2022
303bab8
docs: testing
Toricane Jul 11, 2022
e837510
docs: testing
Toricane Jul 11, 2022
35e9c12
docs: make it look good
Toricane Jul 11, 2022
90c9583
docs: fix formatting
Toricane Jul 11, 2022
103a287
docs: add migration docs
Toricane Jul 11, 2022
a4a859e
Merge branch 'unstable' into unstable
Toricane Jul 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,4 @@ Interaction Models
models.command.rst
models.component.rst
models.misc.rst
models.utils.rst
8 changes: 8 additions & 0 deletions docs/models.utils.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.. currentmodule:: interactions

Utilities
==========================

.. automodule:: interactions.client.models.utils
:members:
:noindex:
240 changes: 239 additions & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,31 @@ Now, let's look what the new parts of the code are doing:
* ``async def my_first_command(ctx: interactions.CommandContext):`` -- This here is called our "command coroutine," or what our library internally calls upon each time it recognizes an interaction event from the Discord API that affiliates with the data we've put into the decorator above it. Please note that ``ctx`` is an abbreviation for :ref:`context <context:Event Context>`.
* ``await ctx.send("Hi there!")`` -- This sends the response to your command.

.. note:: ``name`` and ``description`` are required.
Here is another way we can create a command:

.. code-block:: python

import interactions

bot = interactions.Client(
token="your_secret_bot_token",
default_scope=the_id_of_your_guild,
)

@bot.command()
async def my_first_command(ctx: interactions.CommandContext):
"""This is the first command I made!"""
await ctx.send("Hi there!")

bot.start()

As of v4.3.0, you can also utilize the new command system to create commands effortlessly.

* The ``name`` field defaults to the coroutine name.
* The ``description`` field defaults to the first line of the coroutine docstring if it exists. If it does not exist, it defaults to ``"No description provided."``.
* ``default_scope`` -- This sets the scope for all the commands automatically. If you want to disable this feature in a specific command, you can add ``default_scope=False`` to the command decorator.

.. note:: ``name`` and ``description`` are not required.


.. important:: Difference between global and guild slash commands:
Expand All @@ -142,6 +166,8 @@ Next, let's create an Option

:ref:`Options <models.command:Application Command Models>` are extra arguments of a command, filled in by the user executing the command.

Here is the structure of an option:

.. code-block:: python

import interactions
Expand All @@ -166,13 +192,36 @@ Next, let's create an Option

bot.start()

As of v4.3.0, you can also utilize the new command system and the :ref:`@option() <models.command:Application Command Models>` decorator to create options:

.. code-block:: python

import interactions

bot = interactions.Client(token="your_secret_bot_token")

@bot.command(scope=the_id_of_your_guild)
@interactions.option(str, name="text", description="What you want to say", required=True)
async def say_something(ctx: interactions.CommandContext, text: str):
"""say something!"""
await ctx.send(f"You said '{text}'!")

* The first field in the ``@option()`` decorator is the type of the option. This is positional only and required. You can use integers, the default Python types, the ``OptionType`` enum, or supported objects such as ``interactions.Channel``.
* All other arguments in the decorator are keyword arguments only.
* The ``name`` field is required.
* The ``description`` field is optional and defaults to ``"No description set``.
* Any parameters from ``Option`` can be passed into the ``@option()`` decorator.

.. note::
The limit for options per command is 25.

Nested commands: subcommands
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Subcommands are options that are nested to create subcategories of commands.

Here is the structure of a subcommand:

.. code-block:: python

@bot.command(
Expand Down Expand Up @@ -214,9 +263,124 @@ Nested commands: subcommands
elif sub_command == "second_command":
await ctx.send(f"You selected the second_command sub command and put in {second_option}")

As of v4.3.0, you can also utilize the new command system to create subcommands:

.. code-block:: python

import interactions

bot = interactions.Client(token="your_secret_bot_token")

@bot.command(scope=guild_id)
async def base_command(ctx: interactions.CommandContext):
"""This description isn't seen in UI (yet?)"""
pass

@base_command.subcommand()
@interactions.option(str, name="option", description="A descriptive description", required=False)
async def command_name(ctx: interactions.CommandContext, option: int = None):
"""A descriptive description"""
await ctx.send(f"You selected the command_name sub command and put in {option}")

@base_command.subcommand()
@interactions.option(str, name="second_option", description="A descriptive description", required=True)
async def second_command(ctx: interactions.CommandContext, second_option: str):
"""A descriptive description"""
await ctx.send(f"You selected the second_command sub command and put in {second_option}")

.. note::
You can add a SUB_COMMAND_GROUP in between the base and command.

Additional information about subcommands
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Base commands are returned the :ref:`Command <models.command:Application Command Models>` object.
From this, you can utilize the following decorators:

* :ref:`@subcommand() <models.command:Application Command Models>`: creates a subcommand.
* :ref:`@group() <models.command:Application Command Models>`: creates a group.
* :ref:`@error <models.command:Application Command Models>`: registers an error callback.

Check the documentation for the parameters of each of these decorators.

The following is an example of a base command:

.. code-block:: python

@bot.command()
async def base_command(ctx: interactions.CommandContext):
pass

The examples below will be using the base command above.

The following is an example of a subcommand of the base command:

.. code-block:: python

@base_command.subcommand()
async def subcommand(ctx: interactions.CommandContext, base_res: interactions.BaseResult):
pass

This code results in the following subcommand: `/base_command subcommand`.

.. note::
You can use the ``base_res`` parameter in groups and subcommands, and ``group_res`` in subcommands inside groups
to access the result of the previous callback. They are both optional and are placed right after the ``ctx`` parameter.

The following is an example of a group with subcommands:

.. code-block:: python

@base_command.group()
async def group(ctx: interactions.CommandContext, base_res: interactions.BaseResult):
pass

@group.subcommand()
async def subcommand_group(ctx: interactions.CommandContext, group_res: interactions.GroupResult):
pass

You can have multiple groups, with multiple subcommands in each group.
Subcommands and groups are options, so the same restrictions apply.

.. note:: Create any subcommands without groups *before* creating any groups.

Since there are multiple coroutines involved that each get executed, you may
be wondering how you can stop the chain. Luckily, there is a way:

.. code-block:: python

@bot.command()
async def foo(ctx):
... # do something
return StopCommand # does not execute `bar`

@foo.subcommand()
async def bar(ctx):
... # `bar` is not executed

This works on both groups and subcommands.

The following is an example of an error callback:

.. code-block:: python

@bot.command()
async def foo(ctx: interactions.CommandContext):
... # do something
raise Exception("Something went wrong")
# Most likely, you won't be the one
# raising the error, it may just be
# a bug or mistake with your code.

@foo.error
async def foo_error(ctx: interactions.CommandContext, error: Exception):
... # do something

The parameters ``ctx`` and ``error`` are required, but you can have more
parameters, such as ``*args`` and ``**kwargs``, if you need to access options.

.. note::
You can have one error callback per command.

Special type of commands: Context menus
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand All @@ -240,6 +404,13 @@ your ``@command`` decorator:
async def test(ctx):
await ctx.send(f"You have applied a command onto user {ctx.target.user.username}!")

Here is an alternate way of creating a context menu:

.. code-block:: python

@bot.user_command(name="User Command", scope=1234567890)
async def test(ctx):
await ctx.send(f"You have applied a command onto user {ctx.target.user.username}!")

.. important::
The structure of a menu command differs significantly from that of a regular one:
Expand Down Expand Up @@ -322,6 +493,11 @@ as ``ActionRow``'s. It is worth noting that you can have only a maximum of
row = interactions.ActionRow(
components=[button1, button2]
)
# or:
row = interactions.ActionRow.new(button1, button2)
# or:
row = interactions.spread_to_rows(button1, button2)
# spread_to_rows returns a list of rows

@bot.command(...)
async def test(ctx):
Expand Down Expand Up @@ -448,6 +624,68 @@ Here's an example of a guild-only command:
Likewise, setting ``dm_permission`` to ``True`` makes it usable in DMs. Just to note that this argument's mainly used for
global commands. Guild commands with this argument will have no effect.

Utilities
^^^^^^^^^

You can use the following utilities to help you with your commands:

* ``ActionRow.new()``: Creates a new ``ActionRow`` object.
* ``spread_to_rows()``: Spreads a list of components into a list of rows.
* ``@autodefer()``: Automatically defers a command if it did not respond within the specified time.

Look at their documentation :ref:`here <models.command:Utilities>` for more information.

Usage of ``ActionRow.new()``:

.. code-block:: python

from interactions import ActionRow, Button

@bot.command()
async def command(ctx):
b1 = Button(style=1, custom_id="b1", label="b1")
b2 = Button(style=1, custom_id="b2", label="b2")
b3 = Button(style=1, custom_id="b3", label="b3")
b4 = Button(style=1, custom_id="b4", label="b4")

await ctx.send("Components:", components=ActionRow.new(b1, b2, b3, b4))
# instead of the cumbersome ActionRow(components=[b1, b2, b3, b4])

Usage of ``spread_to_rows()``:

.. code-block:: python

from interactions import Button, SelectMenu, SelectOption, spread_to_rows

@bot.command()
async def command(ctx):
b1 = Button(style=1, custom_id="b1", label="b1")
b2 = Button(style=1, custom_id="b2", label="b2")
s1 = SelectMenu(
custom_id="s1",
options=[
SelectOption(label="1", value="1"),
SelectOption(label="2", value="2"),
],
)
b3 = Button(style=1, custom_id="b3", label="b3")
b4 = Button(style=1, custom_id="b4", label="b4")

await ctx.send("Components:", components=spread_to_rows(b1, b2, s1, b3, b4))

Usage of ``@autodefer()``:

.. code-block:: python

from interactions import autodefer
import asyncio

@bot.command()
@autodefer() # configurable
async def command(ctx):
await asyncio.sleep(5)
await ctx.send("I'm awake now!")

.. _Client: https://interactionspy.rtfd.io/en/stable/client.html
.. _find these component types: https://interactionspy.readthedocs.io/en/stable/models.component.html
.. _discord applications page: https://discord.com/developers/applications
2 changes: 1 addition & 1 deletion interactions/api/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from logging import Logger
from typing import Callable, Coroutine, Dict, List, Optional

from interactions.base import get_logger
from ..base import get_logger

__all__ = ("Listener",)

Expand Down
6 changes: 2 additions & 4 deletions interactions/api/http/client.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
from typing import Any, Optional, Tuple

import interactions.api.cache

from ...api.cache import Cache
from ...api.cache import Cache, ref_cache
from .channel import ChannelRequest
from .emoji import EmojiRequest
from .guild import GuildRequest
Expand Down Expand Up @@ -58,7 +56,7 @@ class HTTPClient(
def __init__(self, token: str):
self.token = token
self._req = _Request(self.token)
self.cache = interactions.api.cache.ref_cache
self.cache = ref_cache
UserRequest.__init__(self)
MessageRequest.__init__(self)
GuildRequest.__init__(self)
Expand Down
2 changes: 1 addition & 1 deletion interactions/api/http/limiter.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from asyncio import Lock
from typing import Optional

from interactions.api.models.attrs_utils import MISSING
from ..models.attrs_utils import MISSING

__all__ = ("Limiter",)

Expand Down
3 changes: 1 addition & 2 deletions interactions/api/http/request.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
from aiohttp import ClientSession
from aiohttp import __version__ as http_version

from interactions.base import __version__, get_logger

from ...api.error import LibraryException
from ...base import __version__, get_logger
from .limiter import Limiter
from .route import Route

Expand Down
4 changes: 2 additions & 2 deletions interactions/api/models/attrs_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ def convert_list(converter):
"""A helper function to convert items in a list with the specified converter"""

def inner_convert_list(list):
if list is None:
return None
if list in (MISSING, None):
return list

# empty lists need no conversion
if len(list) == 0:
Expand Down
Loading