Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
196c365
style: log missing attrs as `INFO`.
i0bs Mar 23, 2022
86dc4db
fix: Change how Application's icon url is formatted and how ``url`` i…
B1ue-Dev Mar 26, 2022
7caf107
fix: check for WS_CLOSED_MESSAGE after receiving (#677)
kyteware Mar 26, 2022
4eaa09f
fix: allow http get_application_commands if sync is false (#659)
V3ntus Mar 27, 2022
d6f28b7
feat: Implement Localisation for name and description of application …
FayeDel Mar 29, 2022
f2830cb
feat: Implemented HTTP 50x error tracking, per-route exhaust ratelimi…
FayeDel Mar 29, 2022
460328a
refactor: Delete extra Locale object, tweak current Locale object acc…
FayeDel Mar 29, 2022
556b387
fix: prevent kwargs modification during `for` loop iteration (#685)
V3ntus Mar 29, 2022
7ee564c
feat: Implement localisation support for Options and Choices, include…
FayeDel Mar 29, 2022
ea95732
fix: Fix listener/command invocation on extension reload/removal.
FayeDel Mar 29, 2022
8873ada
fix: Fix timestamp assignment on Embed objects on non-declaration.
FayeDel Mar 29, 2022
4bc73c2
refactor: allow archiving of threads in channel helper methods (#676)
EepyElvyra Mar 29, 2022
ad7f451
feat: implement ``get_all_members`` helper methods (#675)
EepyElvyra Mar 29, 2022
a52cf8e
chore: Repoint OpenCollective URL.
FayeDel Mar 29, 2022
4dcd2e1
feat: Adding ``icon_url`` property for getting the guild's icon URL (…
B1ue-Dev Mar 31, 2022
3fc7bee
refactor: update attributes of current class instance when editing th…
EepyElvyra Mar 31, 2022
ca49be1
docs(quickstart): fix incorrect statement (#669)
MaskDuck Mar 31, 2022
a983c0b
fix!: check for a `CLOSING_MESSAGE` in the WebSocketClient connection…
EepyElvyra Mar 31, 2022
c8af8bd
fix: Adjust Locale object support for .pyi headers.
FayeDel Mar 31, 2022
567f32a
Add set_video() helper to the embed (#690)
mAxYoLo01 Mar 31, 2022
4e43974
fix!: constant command synchronization even without changes on the co…
EepyElvyra Mar 31, 2022
c54a35c
refactor: reorganize the library arch.
i0bs Mar 31, 2022
7a2ed8f
fix: bad bot client import
i0bs Mar 31, 2022
5bbf0ab
style: add examples
i0bs Mar 31, 2022
1d5685c
fix!: connection issue with GW from new arch
i0bs Mar 31, 2022
2dcf18c
Merge branch 'interactions-py:stable' into unstable_core_3
FayeDel Mar 31, 2022
c03c0ab
Merge remote-tracking branch 'origin/unstable' into unstable
FayeDel Mar 31, 2022
0ac9bc2
chore: attempt of rebase
FayeDel Mar 31, 2022
0a0d5c9
feat: Add url property for message objects (#695)
Nanrech Mar 31, 2022
9ce0568
fix!: Fix circular import from commit c54a35c.
FayeDel Apr 2, 2022
33d2f3f
feat: Implement new guild ban parameter requirement.
FayeDel Apr 2, 2022
a611a27
feat: Extend __check_command to name localisations per command/option
FayeDel Apr 2, 2022
11e5a51
refactor: Refactor Embed timestamp definition by relying on __setattr__
FayeDel Apr 2, 2022
d7d218e
revert: Partially revert f2830cb by removing 50x error tracking, refa…
FayeDel Apr 2, 2022
4fb0254
Merge pull request #686 from DeltaXWizard/unstable_core_3
FayeDel Apr 2, 2022
b477c95
fix!: Refix context resolution due to architecture change. (#697)
FayeDel Apr 3, 2022
e391492
fix: Fix remaining variable definition. (#696)
FayeDel Apr 3, 2022
cd5922e
ci: weekly check. (#698)
pre-commit-ci[bot] Apr 4, 2022
658a62a
fix(models)!: Fix local imports, optimise used imports (#701)
FayeDel Apr 5, 2022
5f24360
fix: Fix webhook execution payload. (#645)
FayeDel Apr 5, 2022
e7b7a2f
feat: Uploading/Attaching files to channel messages (#653)
tcdtech Apr 5, 2022
ab0e036
feat: Add more image URL helpers (#699)
mAxYoLo01 Apr 5, 2022
639574e
chore: version bump to release candidate
i0bs Apr 5, 2022
51d824c
refactor: allow file sending in all send and editing methods (except …
EepyElvyra Apr 5, 2022
36c2131
fix!: Fix typo in headers (#705)
EepyElvyra Apr 5, 2022
5dd14fa
fix!: fix ``guilds`` property in client (#670)
EepyElvyra Apr 5, 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
2 changes: 1 addition & 1 deletion .github/FUNDING.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# These are supported funding model platforms

github: [goverfl0w]
open_collective: discordinteractions
open_collective: interactions-py
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ repos:
- id: check-merge-conflict
name: Merge Conflicts
- repo: https://github.com/psf/black
rev: 22.1.0
rev: 22.3.0
hooks:
- id: black
name: Black Formatting
Expand Down
2 changes: 1 addition & 1 deletion docs/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ But how to send it? You can't use ``ctx.send`` for it. Take a look at :ref:`Moda

Modals
******
Modals are a new way to interact with a user. Currently only a ``TextInput`` component is supported. You can have up to three ``TextInput`` in a Modal.
Modals are a new way to interact with a user. Currently only a ``TextInput`` component is supported. You can have up to five ``TextInput`` in a Modal.

.. code-block:: python

Expand Down
70 changes: 70 additions & 0 deletions examples/bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# This code shows a very brief and small example of how to create a bot with our library.
# This example does not cover all the features of the library, but it is enough to get you started.
# In order to learn more about how to use the library, please head over to our documentation:
# https://interactionspy.rtfd.io/en/latest/

# The first thing you need to do is import the library.
import interactions

# Now, let's create an instance of a bot.
# When you make a bot, we refer to it as the "client."
# The client is the main object that interacts with the Gateway, what talks to Discord.
# The client is also the main object that interacts with the API, what makes requests with Discord.
client = interactions.Client("your bot token will go here.")

# With our client established, let's have the library inform us when the client is ready.
# These are known as event listeners. An event listener can be established in one of two ways.
# You can provide the name of the event, prefixed by an "on_", or by telling the event decorator what event it is.
@client.event
async def on_ready():
# We can use the client "me" attribute to get information about the bot.
print(f"We're online! We've logged in as {client.me.name}.")

# We're also able to use property methods to gather additional data.
print(f"Our latency is {round(client.latency)} ms.")


@client.event("message_create")
async def name_this_however_you_want(message: interactions.Message):
# Whenever we specify any other event type that isn't "READY," the function underneath
# the decorator will most likely have an argument required. This argument is the data
# that is being supplied back to us developers, which we call a data model.

# In this example, we're listening to messages being created. This means we can expect
# a "message" argument to be passed to the function, which will be the data model of such.

# We can use the data model to access the data we need.
print(
f"We've received a message from {message.author.name}. The message is: {message.content}."
)


# Now, let's create a command.
# A command is a function that is called when a user types out a command.
# The command is called with a context object, which contains information about the user, the channel, and the guild.
# Context is what we call the described information given from an interaction response, what comes from a command.
# The context object in this case is a class for commands, but can also be one for components if used that way.
@client.command(name="hello-world", description='A command that says "hello world!"')
async def hello_world(ctx: interactions.CommandContext):
# "ctx" is an abbreviation of the context object.
# You don't need to type hint this, but it's recommended to do so.

# Now, let's send back a response.
# Note that when you make an interaction response, you can no longer run anything in this function.
# The interaction response should be the LAST thing you do when a command is ran.
await ctx.send("hello world!")

# Because of this, this line of code right here will not execute.
print("we ran.")


# After we've declared all of the bot code we want, we need to tell the library to run our bot.
# In this example, we've decided to do some things in a different way without explicitly saying it:

# - we'll be syncing the commands automatically.
# if you want to do this manually, you can do it by passing disable_sync=False in the Client
# object on line 8.
# - we are not setting a presence.
# - we are not automatically sharding, and registering the connection under 1 shard.
# - we are using default intents, which are Gateway intents excluding privileged ones.
client.start()
32 changes: 7 additions & 25 deletions interactions/__init__.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,14 @@
"""
(interactions)
discord-interactions
interactions.py

Easy, simple, scalable and modular: a Python API wrapper for interactions.
Easy, simple, scalable and modular: a Python library for interactions.

To see the documentation, please head over to the link here:
https://discord-interactions.rtfd.io/en/latest for ``stable`` builds.
https://discord-interactions.rtfd.io/en/unstable for ``unstable`` builds.
https://interactionspy.rtfd.io/en/latest for ``stable`` builds.
https://interactionspy.rtfd.io/en/unstable for ``unstable`` builds.

(c) 2021 goverfl0w.
Co-authored by DeltaXW.
(c) 2021 interactions-py.
"""
from .api.models.channel import * # noqa: F401 F403
from .api.models.flags import * # noqa: F401 F403
from .api.models.guild import * # noqa: F401 F403
from .api.models.gw import * # noqa: F401 F403
from .api.models.member import * # noqa: F401 F403
from .api.models.message import * # noqa: F401 F403
from .api.models.misc import * # noqa: F401 F403
from .api.models.presence import * # noqa: F401 F403
from .api.models.role import * # noqa: F401 F403
from .api.models.team import * # noqa: F401 F403
from .api.models.user import * # noqa: F401 F403
from .client import * # noqa: F401 F403 isort: skip
from .api import * # noqa: F401 F403
from .base import * # noqa: F401 F403
from .client import * # noqa: F401 F403
from .context import * # noqa: F401 F403
from .decor import * # noqa: F401 F403
from .enums import * # noqa: F401 F403
from .models.command import * # noqa: F401 F403
from .models.component import * # noqa: F401 F403
from .models.misc import * # noqa: F401 F403
4 changes: 1 addition & 3 deletions interactions/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
interactions.api

This section of the library maintains and
handles all of the Gateway and HTTP
work.
handles all the Gateway and HTTP work.
"""
from ..base import * # noqa: F401 F403
from .cache import * # noqa: F401 F403
from .enums import * # noqa: F401 F403
from .error import * # noqa: F401 F403
Expand Down
1 change: 0 additions & 1 deletion interactions/api/enums.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Normal libraries
from enum import IntEnum

# TODO: post-v4: Implement this into the new error system at a later point.
Expand Down
8 changes: 8 additions & 0 deletions interactions/api/gateway/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""
interactions.api.gateway

This section of the library maintains and
handles all of the Gateway work.
"""
from .client import * # noqa: F401 F403
from .heartbeat import * # noqa: F401 F403
63 changes: 28 additions & 35 deletions interactions/api/gateway.py → interactions/api/gateway/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
from json import dumps, loads

from asyncio import (
AbstractEventLoop,
Event,
Task,
ensure_future,
Expand All @@ -17,41 +16,23 @@
from time import perf_counter
from typing import Any, Dict, List, Optional, Tuple, Union

from aiohttp import WSMessage

from ..base import get_logger
from ..enums import InteractionType, OptionType
from ..models.command import Option
from .dispatch import Listener
from .enums import OpCodeType
from .error import GatewayException
from .http.client import HTTPClient
from .models.flags import Intents
from .models.misc import MISSING
from .models.presence import ClientPresence
from aiohttp import WSMessage, WSMsgType
from aiohttp.http import WS_CLOSED_MESSAGE, WS_CLOSING_MESSAGE

from ...base import get_logger
from ...client.enums import InteractionType, OptionType
from ...client.models import Option
from ..dispatch import Listener
from ..enums import OpCodeType
from ..error import GatewayException
from ..http.client import HTTPClient
from ..models.flags import Intents
from ..models.misc import MISSING
from ..models.presence import ClientPresence
from .heartbeat import _Heartbeat

log = get_logger("gateway")

__all__ = ("_Heartbeat", "WebSocketClient")


class _Heartbeat:
"""An internal class representing the heartbeat in a WebSocket connection."""

event: Event
delay: float

def __init__(self, loop: AbstractEventLoop) -> None:
"""
:param loop: The event loop to base the asynchronous manager.
:type loop: AbstractEventLoop
"""
try:
self.event = Event(loop=loop) if version_info < (3, 10) else Event()
except TypeError:
pass
self.delay = 0.0


class WebSocketClient:
"""
Expand Down Expand Up @@ -198,7 +179,7 @@ async def _establish_connection(

if stream is None:
continue
if self._client is None:
if self._client is None or stream == WS_CLOSED_MESSAGE or stream == WSMsgType.CLOSE:
await self._establish_connection()
break

Expand Down Expand Up @@ -397,7 +378,7 @@ def __contextualize(self, data: dict) -> object:
_context = "ComponentContext"

data["client"] = self._http
context: object = getattr(__import__("interactions.context"), _context)
context: object = getattr(__import__("interactions.client.context"), _context)

return context(**data)

Expand Down Expand Up @@ -536,6 +517,18 @@ async def __receive_packet_stream(self) -> Optional[Dict[str, Any]]:
"""

packet: WSMessage = await self._client.receive()

if packet == WSMsgType.CLOSE:
await self._client.close()
return packet

elif packet == WS_CLOSED_MESSAGE:
return packet

elif packet == WS_CLOSING_MESSAGE:
await self._client.close()
return WS_CLOSED_MESSAGE

return loads(packet.data) if packet and isinstance(packet.data, str) else None

async def _send_packet(self, data: Dict[str, Any]) -> None:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,17 @@ from typing import Any, Dict, List, Optional, Tuple, Union, Iterable

from aiohttp import ClientWebSocketResponse

from ..models import Option
from ..api.models.misc import MISSING
from ..api.models.presence import ClientPresence
from .dispatch import Listener
from .http.client import HTTPClient
from .models.flags import Intents
from .heartbeat import _Heartbeat
from ...client.models import Option
from ...api.models.misc import MISSING
from ...api.models.presence import ClientPresence
from ..dispatch import Listener
from ..http.client import HTTPClient
from ..models.flags import Intents

log: Logger
__all__: Iterable[str]

class _Heartbeat:
event: Event
delay: float
def __init__(self, loop: AbstractEventLoop) -> None: ...

class WebSocketClient:
_loop: AbstractEventLoop
_dispatch: Listener
Expand All @@ -42,7 +38,6 @@ class WebSocketClient:
_last_ack: float
latency: float
ready: Event

def __init__(
self,
token: str,
Expand All @@ -53,7 +48,9 @@ class WebSocketClient:
async def _manage_heartbeat(self) -> None: ...
async def __restart(self): ...
async def _establish_connection(
self, shard: Optional[List[Tuple[int]]] = MISSING, presence: Optional[ClientPresence] = MISSING
self,
shard: Optional[List[Tuple[int]]] = MISSING,
presence: Optional[ClientPresence] = MISSING,
) -> None: ...
async def _handle_connection(
self,
Expand All @@ -64,7 +61,9 @@ class WebSocketClient:
async def wait_until_ready(self) -> None: ...
def _dispatch_event(self, event: str, data: dict) -> None: ...
def __contextualize(self, data: dict) -> object: ...
def __sub_command_context(self, data: Union[dict, Option], _context: Optional[object] = MISSING) -> Union[Tuple[str], dict]: ...
def __sub_command_context(
self, data: Union[dict, Option], _context: Optional[object] = MISSING
) -> Union[Tuple[str], dict]: ...
def __option_type_context(self, context: object, type: int) -> dict: ...
@property
async def __receive_packet_stream(self) -> Optional[Dict[str, Any]]: ...
Expand Down
20 changes: 20 additions & 0 deletions interactions/api/gateway/heartbeat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from asyncio import AbstractEventLoop, Event
from sys import version_info


class _Heartbeat:
"""An internal class representing the heartbeat in a WebSocket connection."""

event: Event
delay: float

def __init__(self, loop: AbstractEventLoop) -> None:
"""
:param loop: The event loop to base the asynchronous manager.
:type loop: AbstractEventLoop
"""
try:
self.event = Event(loop=loop) if version_info < (3, 10) else Event()
except TypeError:
pass
self.delay = 0.0
6 changes: 6 additions & 0 deletions interactions/api/gateway/heartbeat.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from asyncio import AbstractEventLoop, Event

class _Heartbeat():
event: Event
delay: float
def __init__(self, loop: AbstractEventLoop) -> None: ...
25 changes: 23 additions & 2 deletions interactions/api/http/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,14 +507,35 @@ async def remove_guild_ban(
reason=reason,
)

async def get_guild_bans(self, guild_id: int) -> List[dict]:
async def get_guild_bans(
self,
guild_id: int,
limit: Optional[int] = 1000,
before: Optional[int] = None,
after: Optional[int] = None,
) -> List[dict]:
"""
Gets a list of banned users.

.. note::
If both ``before`` and ``after`` are provided, only ``before`` is respected.

:param guild_id: Guild ID snowflake.
:param limit: Number of users to return. Defaults to 1000.
:param before: Consider only users before the given User ID snowflake.
:param after: Consider only users after the given User ID snowflake.
:return: A list of banned users.
"""
return await self._req.request(Route("GET", f"/guilds/{guild_id}/bans"))

params = {}
if limit is not None:
params["limit"] = limit
if before:
params["before"] = before
if after:
params["after"] = after

return await self._req.request(Route("GET", f"/guilds/{guild_id}/bans"), params=params)

async def get_user_ban(self, guild_id: int, user_id: int) -> Optional[dict]:
"""
Expand Down
Loading