Skip to content

Commit ea20323

Browse files
Merge: v5.7.0
v5.7.0
2 parents a21b04a + 68492e1 commit ea20323

25 files changed

+103
-54
lines changed

.pre-commit-config.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ repos:
3030
- id: check-merge-conflict
3131
name: Merge Conflicts
3232
- repo: https://github.com/charliermarsh/ruff-pre-commit
33-
rev: 'v0.0.270'
33+
rev: 'v0.0.272'
3434
hooks:
3535
- id: ruff
3636
args: [--fix, --exit-non-zero-on-fix]

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ In addition to core functionality, `interactions.py` provides a range of optiona
3737

3838
So the base library doesn't do what you want? No problem! With builtin extensions, you are able to extend the functionality of the library. And if none of those pique your interest, there are a myriad of other extension libraries available.
3939

40-
Just type `bot.load("extension")`
40+
Just type `bot.load_extension("extension")`
4141

4242
<details>
4343
<summary>Extensions</summary>

docs/src/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ In addition to core functionality, interactions.py provides a range of optional
3434

3535
So the base library doesn't do what you want? No problem! With builtin extensions, you are able to extend the functionality of the library. And if none of those pique your interest, there are a myriad of other extension libraries available.
3636

37-
Just type `bot.load("extension")`
37+
Just type `bot.load_extension("extension")`
3838

3939
---
4040

interactions/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import sys
12
from .client import (
23
__api_version__,
34
__py_version__,
@@ -680,6 +681,10 @@
680681
"WebSocketOPCode",
681682
)
682683

684+
if "discord" in sys.modules:
685+
get_logger().error(
686+
"`import discord` import detected. Interactions.py is a completely separate library, and is not compatible with d.py models. Please see https://interactions-py.github.io/interactions.py/Guides/100%20Migration%20From%20D.py/ for how to fix your code."
687+
)
683688

684689
########################################################################################################################
685690
# Noteworthy Credits

interactions/api/events/base.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from interactions.models.discord.snowflake import to_snowflake
1010

1111
if TYPE_CHECKING:
12-
from interactions import Client
12+
from interactions.client.client import Client
1313
from interactions.models.discord.snowflake import Snowflake_Type
1414
from interactions.models.discord.guild import Guild
1515

interactions/api/http/http_requests/guild.py

+4-6
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
from typing import TYPE_CHECKING, List, cast, Mapping, Any
1+
from typing import TYPE_CHECKING, Any, List, Mapping, cast
22

33
import discord_typings
44

55
from interactions.client.utils.serializer import dict_filter_none
66
from interactions.models.internal.protocols import CanRequest
7-
from ..route import Route, PAYLOAD_TYPE
7+
8+
from ..route import PAYLOAD_TYPE, Route
89

910
__all__ = ("GuildRequests",)
1011

1112

1213
if TYPE_CHECKING:
13-
from interactions.models.discord.snowflake import Snowflake_Type
1414
from interactions.models.discord.enums import AuditLogEventType
15+
from interactions.models.discord.snowflake import Snowflake_Type
1516

1617

1718
class GuildRequests(CanRequest):
@@ -404,9 +405,6 @@ async def modify_guild_role_positions(
404405
Args:
405406
guild_id: The ID of the guild
406407
position_changes: A list of dicts representing the roles to move and their new positions
407-
408-
``{"id": role_id, "position": new_position}``
409-
410408
reason: The reason for this action
411409
412410
Returns:

interactions/api/voice/voice_gateway.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def _udp_keep_alive(self) -> None:
268268
keep_alive = b"\xc9\x00\x00\x00\x00\x00\x00\x00\x00"
269269

270270
self.logger.debug("Starting UDP Keep Alive")
271-
while not self.socket._closed and not self.ws.closed:
271+
while not self.socket._closed and self.ws and not self.ws.closed:
272272
try:
273273
_, writable, _ = select.select([], [self.socket], [], 0)
274274
while not writable:

interactions/client/mixins/modal.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010

1111
class ModalMixin:
12-
client: "interactions.Client"
12+
client: "interactions.client.client.Client"
1313
"""The client that created this context."""
1414
responded: bool
1515
"""Whether this context has been responded to."""

interactions/client/mixins/send.py

+13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
22

33
import interactions.models as models
4+
import interactions.models.discord
45
from interactions.models.discord.enums import MessageFlags
56

67
if TYPE_CHECKING:
@@ -82,6 +83,18 @@ async def send(
8283
flags = MessageFlags(flags)
8384
flags = flags | MessageFlags.SILENT
8485

86+
if (
87+
files
88+
and (
89+
isinstance(files, Iterable)
90+
and any(isinstance(file, interactions.models.discord.message.Attachment) for file in files)
91+
)
92+
or isinstance(files, interactions.models.discord.message.Attachment)
93+
):
94+
raise ValueError(
95+
"Attachments are not files. Attachments only contain metadata about the file, not the file itself - to send an attachment, you need to download it first. Check Attachment.url"
96+
)
97+
8598
message_payload = models.discord.message.process_message_payload(
8699
content=content,
87100
embeds=embeds or embed,

interactions/client/utils/attr_utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,20 @@ def docs(doc_string: str) -> Dict[str, str]:
3636
return {"docs": doc_string}
3737

3838

39-
def str_validator(self: Any, attribute: attrs.Attribute, value: Any) -> None:
39+
def str_validator(cls: Any, attribute: attrs.Attribute, value: Any) -> None:
4040
"""
4141
Validates that the value is a string. Helps convert and ives a warning if it isn't.
4242
4343
Args:
44-
self: The instance of the class.
44+
cls: The instance of the class.
4545
attribute: The attr attribute being validated.
4646
value: The value being validated.
4747
4848
"""
4949
if not isinstance(value, str):
5050
if value is MISSING:
5151
return
52-
setattr(self, attribute.name, str(value))
52+
setattr(cls, attribute.name, str(value))
5353
get_logger().warning(
5454
f"Value of {attribute.name} has been automatically converted to a string. Please use strings in future.\n"
5555
"Note: Discord will always return value as a string"

interactions/client/utils/input_utils.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def enc_hook(obj: Any) -> int:
3535

3636
json_mode = "msgspec"
3737
else:
38-
import json
38+
import json # type: ignore
3939

4040
get_logger().debug(f"Using {json_mode} for JSON encoding and decoding.")
4141

interactions/ext/paginators.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -352,18 +352,19 @@ def to_dict(self) -> dict:
352352
"components": [c.to_dict() for c in self.create_components()],
353353
}
354354

355-
async def send(self, ctx: BaseContext) -> Message:
355+
async def send(self, ctx: BaseContext, **kwargs) -> Message:
356356
"""
357357
Send this paginator.
358358
359359
Args:
360360
ctx: The context to send this paginator with
361+
**kwargs: Additional options to pass to `send`.
361362
362363
Returns:
363364
The resulting message
364365
365366
"""
366-
self._message = await ctx.send(**self.to_dict())
367+
self._message = await ctx.send(**self.to_dict(), **kwargs)
367368
self._author_id = ctx.author.id
368369

369370
if self.timeout_interval > 1:
@@ -372,16 +373,18 @@ async def send(self, ctx: BaseContext) -> Message:
372373

373374
return self._message
374375

375-
async def reply(self, ctx: "PrefixedContext") -> Message:
376+
async def reply(self, ctx: "PrefixedContext", **kwargs) -> Message:
376377
"""
377378
Reply this paginator to ctx.
378379
379380
Args:
380381
ctx: The context to reply this paginator with
382+
**kwargs: Additional options to pass to `reply`.
383+
381384
Returns:
382385
The resulting message
383386
"""
384-
self._message = await ctx.reply(**self.to_dict())
387+
self._message = await ctx.reply(**self.to_dict(), **kwargs)
385388
self._author_id = ctx.author.id
386389

387390
if self.timeout_interval > 1:

interactions/ext/prefixed_commands/context.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import Optional, Union, Iterable, TYPE_CHECKING
1+
from typing import TYPE_CHECKING, Any, Iterable, Optional, Union
22

33
from typing_extensions import Self
44

@@ -80,7 +80,7 @@ async def reply(
8080
content: Optional[str] = None,
8181
embeds: Optional[Union[Iterable[Union[Embed, dict]], Union[Embed, dict]]] = None,
8282
embed: Optional[Union[Embed, dict]] = None,
83-
**kwargs,
83+
**kwargs: Any,
8484
) -> Message:
8585
"""
8686
Reply to the context's message. Takes all the same attributes as `send`.

interactions/models/discord/channel.py

+2
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,8 @@ class BaseChannel(DiscordObject):
775775
"""The name of the channel (1-100 characters)"""
776776
type: Union[ChannelType, int] = attrs.field(repr=True, converter=ChannelType)
777777
"""The channel topic (0-1024 characters)"""
778+
permissions: Optional[Permissions] = attrs.field(repr=False, default=None, converter=optional_c(Permissions))
779+
"""Calculated permissions for the bot in this channel, only given when using channels as an option with slash commands"""
778780

779781
@classmethod
780782
def from_dict_factory(cls, data: dict, client: "Client") -> "TYPE_ALL_CHANNEL":

interactions/models/discord/emoji.py

+5
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,11 @@ async def delete(self, reason: Optional[str] = None) -> None:
206206

207207
await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)
208208

209+
@property
210+
def url(self) -> str:
211+
"""CDN url for the emoji."""
212+
return f"https://cdn.discordapp.net/emojis/{self.id}.{'gif' if self.animated else 'png'}"
213+
209214

210215
def process_emoji_req_format(emoji: Optional[Union[PartialEmoji, dict, str]]) -> Optional[str]:
211216
"""

interactions/models/discord/file.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __attrs_post_init__(self) -> None:
3232
else:
3333
self.file_name = Path(self.file).name
3434

35-
def open_file(self) -> BinaryIO:
35+
def open_file(self) -> BinaryIO | IOBase:
3636
"""
3737
Opens the file.
3838
@@ -55,7 +55,7 @@ def __exit__(self, exc_type, exc_val, exc_tb) -> None:
5555
UPLOADABLE_TYPE = Union[File, IOBase, BinaryIO, Path, str]
5656

5757

58-
def open_file(file: UPLOADABLE_TYPE) -> BinaryIO:
58+
def open_file(file: UPLOADABLE_TYPE) -> BinaryIO | IOBase:
5959
"""
6060
Opens the file.
6161

interactions/models/discord/guild.py

+29-21
Original file line numberDiff line numberDiff line change
@@ -3,47 +3,55 @@
33
from asyncio import QueueEmpty
44
from collections import namedtuple
55
from functools import cmp_to_key
6-
from typing import List, Optional, Union, Set, Dict, Any, TYPE_CHECKING
6+
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Union
77
from warnings import warn
88

99
import attrs
1010

1111
import interactions.models as models
12-
from interactions.client.const import Absent, MISSING, PREMIUM_GUILD_LIMITS
12+
from interactions.client.const import MISSING, PREMIUM_GUILD_LIMITS, Absent
1313
from interactions.client.errors import EventLocationNotProvided, NotFound
1414
from interactions.client.mixins.serialization import DictSerializationMixin
15-
from interactions.client.utils.attr_converters import optional, list_converter
16-
from interactions.client.utils.attr_converters import timestamp_converter
15+
from interactions.client.utils.attr_converters import optional, list_converter, timestamp_converter
1716
from interactions.client.utils.attr_utils import docs
1817
from interactions.client.utils.deserialise_app_cmds import deserialize_app_cmds
19-
from interactions.client.utils.serializer import to_image_data, no_export_meta
20-
from interactions.models.discord.app_perms import CommandPermissions, ApplicationCommandPermission
18+
from interactions.client.utils.serializer import no_export_meta, to_image_data
19+
from interactions.models.discord.app_perms import (
20+
ApplicationCommandPermission,
21+
CommandPermissions,
22+
)
2123
from interactions.models.discord.auto_mod import AutoModRule, BaseAction, BaseTrigger
2224
from interactions.models.discord.file import UPLOADABLE_TYPE
2325
from interactions.models.misc.iterator import AsyncIterator
24-
from .base import DiscordObject, ClientObject
26+
27+
from .base import ClientObject, DiscordObject
2528
from .enums import (
26-
NSFWLevel,
27-
Permissions,
28-
SystemChannelFlags,
29-
VerificationLevel,
29+
AuditLogEventType,
30+
AutoModEvent,
31+
AutoModTriggerType,
32+
ChannelType,
3033
DefaultNotificationLevel,
3134
ExplicitContentFilterLevel,
32-
MFALevel,
33-
ChannelType,
35+
ForumLayoutType,
3436
IntegrationExpireBehaviour,
37+
MFALevel,
38+
NSFWLevel,
39+
Permissions,
3540
ScheduledEventPrivacyLevel,
3641
ScheduledEventType,
37-
AuditLogEventType,
38-
AutoModEvent,
39-
AutoModTriggerType,
40-
ForumLayoutType,
42+
SystemChannelFlags,
43+
VerificationLevel,
44+
)
45+
from .snowflake import (
46+
Snowflake_Type,
47+
to_optional_snowflake,
48+
to_snowflake,
49+
to_snowflake_list,
4150
)
42-
from .snowflake import to_snowflake, Snowflake_Type, to_optional_snowflake, to_snowflake_list
4351

4452
if TYPE_CHECKING:
45-
from interactions.client.client import Client
4653
from interactions import InteractionCommand
54+
from interactions.client.client import Client
4755

4856
__all__ = (
4957
"GuildBan",
@@ -581,7 +589,7 @@ async def http_chunk(self) -> None:
581589
f"Cached {iterator.total_retrieved} members for {self.id} in {time.perf_counter() - start_time:.2f} seconds"
582590
)
583591

584-
async def gateway_chunk(self, wait=True, presences=True) -> None:
592+
async def gateway_chunk(self, wait: bool = True, presences: bool = True) -> None:
585593
"""
586594
Trigger a gateway `get_members` event, populating this object with members.
587595
@@ -598,7 +606,7 @@ async def chunk(self) -> None:
598606
"""Populates all members of this guild using the REST API."""
599607
await self.http_chunk()
600608

601-
async def chunk_guild(self, wait=True, presences=True) -> None:
609+
async def chunk_guild(self, wait: bool = True, presences: bool = True) -> None:
602610
"""
603611
Trigger a gateway `get_members` event, populating this object with members.
604612

interactions/models/discord/scheduled_event.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
from .enums import ScheduledEventPrivacyLevel, ScheduledEventType, ScheduledEventStatus
1616

1717
if TYPE_CHECKING:
18-
from interactions.client import Client
18+
from interactions.client.client import Client
1919
from interactions.models.discord.channel import GuildStageVoice, GuildVoice
2020
from interactions.models.discord.guild import Guild
2121
from interactions.models.discord.user import Member

interactions/models/discord/sticker.py

+5
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ async def delete(self, reason: Optional[str] = MISSING) -> None:
135135

136136
await self._client.http.delete_guild_sticker(self._guild_id, self.id, reason)
137137

138+
@property
139+
def url(self) -> str:
140+
"""CDN url for the sticker."""
141+
return f"https://media.discordapp.net/stickers/{self.id}.webp"
142+
138143

139144
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
140145
class StickerPack(DiscordObject):

interactions/models/discord/user.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]
6565
if data["avatar"]:
6666
data["avatar"] = Asset.from_path_hash(client, f"avatars/{data['id']}/{{}}", data["avatar"])
6767
elif data["discriminator"] == "0":
68-
data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{(int(data['id']) >> 22) % 5}")
68+
data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{(int(data['id']) >> 22) % 6}")
6969
else:
7070
data["avatar"] = Asset(client, f"{Asset.BASE}/embed/avatars/{int(data['discriminator']) % 5}")
7171
return data

0 commit comments

Comments
 (0)