Skip to content

Commit 27b5a85

Browse files
authored
Merge pull request #1744 from interactions-py/unstable
5.14
2 parents 83fef88 + 1145023 commit 27b5a85

File tree

20 files changed

+223
-47
lines changed

20 files changed

+223
-47
lines changed

Diff for: interactions/api/events/processors/_template.py

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import asyncio
22
import functools
33
import inspect
4+
import logging
45
from typing import TYPE_CHECKING, Callable, Coroutine
56

67
from interactions.client.const import Absent, MISSING, AsyncCallable
@@ -40,7 +41,9 @@ class EventMixinTemplate:
4041

4142
cache: "GlobalCache"
4243
dispatch: Callable[["BaseEvent"], None]
44+
fetch_members: bool
4345
_init_interactions: Callable[[], Coroutine]
46+
logger: logging.Logger
4447
synchronise_interactions: Callable[[], Coroutine]
4548
_user: ClientUser
4649
_guild_event: asyncio.Event

Diff for: interactions/api/events/processors/_template.pyi

-14
This file was deleted.

Diff for: interactions/api/events/processors/auto_mod.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,24 @@ class AutoModEvents(EventMixinTemplate):
1515
@Processor.define()
1616
async def _raw_auto_moderation_action_execution(self, event: "RawGatewayEvent") -> None:
1717
action = AutoModerationAction.from_dict(event.data.copy(), self)
18-
channel = self.get_channel(event.data.get("channel_id"))
19-
guild = self.get_guild(event.data["guild_id"])
18+
channel = self.cache.get_channel(event.data.get("channel_id"))
19+
guild = self.cache.get_guild(event.data["guild_id"])
2020
self.dispatch(events.AutoModExec(action, channel, guild))
2121

2222
@Processor.define()
2323
async def raw_auto_moderation_rule_create(self, event: "RawGatewayEvent") -> None:
2424
rule = AutoModRule.from_dict(event.data, self)
25-
guild = self.get_guild(event.data["guild_id"])
25+
guild = self.cache.get_guild(event.data["guild_id"])
2626
self.dispatch(events.AutoModCreated(guild, rule))
2727

2828
@Processor.define()
2929
async def raw_auto_moderation_rule_update(self, event: "RawGatewayEvent") -> None:
3030
rule = AutoModRule.from_dict(event.data, self)
31-
guild = self.get_guild(event.data["guild_id"])
31+
guild = self.cache.get_guild(event.data["guild_id"])
3232
self.dispatch(events.AutoModUpdated(guild, rule))
3333

3434
@Processor.define()
3535
async def raw_auto_moderation_rule_delete(self, event: "RawGatewayEvent") -> None:
3636
rule = AutoModRule.from_dict(event.data, self)
37-
guild = self.get_guild(event.data["guild_id"])
37+
guild = self.cache.get_guild(event.data["guild_id"])
3838
self.dispatch(events.AutoModDeleted(guild, rule))

Diff for: interactions/api/events/processors/integrations.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ async def _raw_application_command_permissions_update(self, event: "RawGatewayEv
2323
command_id = to_snowflake(event.data["id"])
2424
application_id = to_snowflake(event.data["application_id"])
2525

26-
if guild := self.get_guild(guild_id):
26+
if guild := self.cache.get_guild(guild_id):
2727
if guild.permissions:
2828
if command_id not in guild.command_permissions:
2929
guild.command_permissions[command_id] = CommandPermissions(

Diff for: interactions/api/events/processors/voice_events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class VoiceEvents(EventMixinTemplate):
1414
@Processor.define()
1515
async def _on_raw_voice_state_update(self, event: "RawGatewayEvent") -> None:
16-
if str(event.data["user_id"]) == str(self.user.id):
16+
if str(event.data["user_id"]) == str(self._user.id):
1717
# User is the bot itself
1818
before = copy.copy(self.cache.get_bot_voice_state(event.data["guild_id"])) or None
1919
after = await self.cache.place_voice_state_data(event.data, update_cache=False)

Diff for: interactions/api/http/http_requests/emojis.py

+85
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,88 @@ async def delete_guild_emoji(
110110
Route("DELETE", "/guilds/{guild_id}/emojis/{emoji_id}", guild_id=guild_id, emoji_id=emoji_id),
111111
reason=reason,
112112
)
113+
114+
async def get_application_emojis(self, application_id: "Snowflake_Type") -> list[discord_typings.EmojiData]:
115+
"""
116+
Fetch all emojis for this application
117+
118+
Args:
119+
application_id: The id of the application
120+
121+
Returns:
122+
List of emojis
123+
124+
"""
125+
result = await self.request(Route("GET", f"/applications/{application_id}/emojis"))
126+
result = cast(dict, result)
127+
return cast(list[discord_typings.EmojiData], result["items"])
128+
129+
async def get_application_emoji(
130+
self, application_id: "Snowflake_Type", emoji_id: "Snowflake_Type"
131+
) -> discord_typings.EmojiData:
132+
"""
133+
Fetch an emoji for this application
134+
135+
Args:
136+
application_id: The id of the application
137+
emoji_id: The id of the emoji
138+
139+
Returns:
140+
Emoji object
141+
142+
"""
143+
result = await self.request(Route("GET", f"/applications/{application_id}/emojis/{emoji_id}"))
144+
return cast(discord_typings.EmojiData, result)
145+
146+
async def create_application_emoji(
147+
self, payload: dict, application_id: "Snowflake_Type", reason: str | None = None
148+
) -> discord_typings.EmojiData:
149+
"""
150+
Create an emoji for this application
151+
152+
Args:
153+
application_id: The id of the application
154+
name: The name of the emoji
155+
imagefile: The image file to use for the emoji
156+
157+
Returns:
158+
Emoji object
159+
160+
"""
161+
result = await self.request(
162+
Route("POST", f"/applications/{application_id}/emojis"), payload=payload, reason=reason
163+
)
164+
return cast(discord_typings.EmojiData, result)
165+
166+
async def edit_application_emoji(
167+
self, application_id: "Snowflake_Type", emoji_id: "Snowflake_Type", name: str
168+
) -> discord_typings.EmojiData:
169+
"""
170+
Edit an emoji for this application
171+
172+
Args:
173+
application_id: The id of the application
174+
emoji_id: The id of the emoji
175+
name: The new name for the emoji
176+
177+
Returns:
178+
Emoji object
179+
180+
"""
181+
result = await self.request(
182+
Route("PATCH", f"/applications/{application_id}/emojis/{emoji_id}"), payload={"name": name}
183+
)
184+
return cast(discord_typings.EmojiData, result)
185+
186+
async def delete_application_emoji(
187+
self, application_id: discord_typings.Snowflake, emoji_id: discord_typings.Snowflake
188+
) -> None:
189+
"""
190+
Delete an emoji for this application
191+
192+
Args:
193+
application_id: The id of the application
194+
emoji_id: The id of the emoji
195+
196+
"""
197+
await self.request(Route("DELETE", f"/applications/{application_id}/emojis/{emoji_id}"))

Diff for: interactions/api/http/http_requests/webhooks.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ async def execute_webhook(
126126
payload: dict,
127127
wait: bool = False,
128128
thread_id: "Snowflake_Type" = None,
129+
thread_name: Optional[str] = None,
129130
files: list["UPLOADABLE_TYPE"] | None = None,
130131
) -> Optional[discord_typings.MessageData]:
131132
"""
@@ -136,13 +137,16 @@ async def execute_webhook(
136137
webhook_token: The token for the webhook
137138
payload: The JSON payload for the message
138139
wait: Waits for server confirmation of message send before response
139-
thread_id: Send a message to the specified thread
140+
thread_id: Send a message to the specified thread. Note that this cannot be used with `thread_name`
141+
thread_name: Create a thread with this name. Note that this is only valid for forum channel and cannot be used with `thread_id`
140142
files: The files to send with this message
141143
142144
Returns:
143145
The sent `message`, if `wait` is True else None
144146
145147
"""
148+
if thread_name is not None:
149+
payload["thread_name"] = thread_name
146150
return await self.request(
147151
Route("POST", "/webhooks/{webhook_id}/{webhook_token}", webhook_id=webhook_id, webhook_token=webhook_token),
148152
params=dict_filter_none({"wait": "true" if wait else "false", "thread_id": thread_id}),

Diff for: interactions/client/client.py

+10
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,16 @@ def guilds(self) -> List["Guild"]:
534534
"""Returns a list of all guilds the bot is in."""
535535
return self.user.guilds
536536

537+
@property
538+
def guild_count(self) -> int:
539+
"""
540+
Returns the number of guilds the bot is in.
541+
542+
This function is faster than using `len(client.guilds)` as it does not require using the cache.
543+
As such, this is preferred when you only need the count of guilds.
544+
"""
545+
return self.user.guild_count
546+
537547
@property
538548
def status(self) -> Status:
539549
"""

Diff for: interactions/client/smart_cache.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@ def place_guild_data(self, data: discord_typings.GuildData) -> Guild:
642642
643643
"""
644644
guild_id = to_snowflake(data["id"])
645-
guild: Guild = self.guild_cache.get(guild_id)
645+
guild: Guild | None = self.guild_cache.get(guild_id)
646646
if guild is None:
647647
guild = Guild.from_dict(data, self._client)
648648
self.guild_cache[guild_id] = guild
@@ -914,7 +914,7 @@ def get_emoji(self, emoji_id: Optional["Snowflake_Type"]) -> Optional["CustomEmo
914914
"""
915915
return self.emoji_cache.get(to_optional_snowflake(emoji_id)) if self.emoji_cache is not None else None
916916

917-
def place_emoji_data(self, guild_id: "Snowflake_Type", data: discord_typings.EmojiData) -> "CustomEmoji":
917+
def place_emoji_data(self, guild_id: "Snowflake_Type | None", data: discord_typings.EmojiData) -> "CustomEmoji":
918918
"""
919919
Take json data representing an emoji, process it, and cache it. This cache is disabled by default, start your bot with `Client(enable_emoji_cache=True)` to enable it.
920920
@@ -929,7 +929,7 @@ def place_emoji_data(self, guild_id: "Snowflake_Type", data: discord_typings.Emo
929929
with suppress(KeyError):
930930
del data["guild_id"] # discord sometimes packages a guild_id - this will cause an exception
931931

932-
emoji = CustomEmoji.from_dict(data, self._client, to_snowflake(guild_id))
932+
emoji = CustomEmoji.from_dict(data, self._client, to_optional_snowflake(guild_id))
933933
if self.emoji_cache is not None:
934934
self.emoji_cache[emoji.id] = emoji
935935

Diff for: interactions/ext/debug_extension/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ async def debug_info(self, ctx: InteractionContext) -> None:
7676

7777
e.add_field("Loaded Exts", ", ".join(self.bot.ext))
7878

79-
e.add_field("Guilds", str(len(self.bot.guilds)))
79+
e.add_field("Guilds", str(self.bot.guild_count))
8080

8181
await ctx.send(embeds=[e])
8282

Diff for: interactions/models/discord/application.py

+35
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44

55
from interactions.client.const import MISSING
66
from interactions.client.utils.attr_converters import optional
7+
from interactions.client.utils.serializer import to_image_data
78
from interactions.models.discord.asset import Asset
9+
from interactions.models.discord.emoji import CustomEmoji
810
from interactions.models.discord.enums import ApplicationFlags
11+
from interactions.models.discord.file import UPLOADABLE_TYPE
912
from interactions.models.discord.snowflake import Snowflake_Type, to_snowflake
1013
from interactions.models.discord.team import Team
1114
from .base import DiscordObject
@@ -88,3 +91,35 @@ def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]
8891
def owner(self) -> "User":
8992
"""The user object for the owner of this application"""
9093
return self._client.cache.get_user(self.owner_id)
94+
95+
async def fetch_all_emoji(self) -> List[CustomEmoji]:
96+
"""Fetch all emojis for this application"""
97+
response = await self.client.http.get_application_emojis(self.id)
98+
return [self.client.cache.place_emoji_data(None, emoji) for emoji in response]
99+
100+
async def fetch_emoji(self, emoji_id: Snowflake_Type) -> CustomEmoji:
101+
"""Fetch an emoji for this application"""
102+
response = await self.client.http.get_application_emoji(self.id, emoji_id)
103+
return self.client.cache.place_emoji_data(None, response)
104+
105+
async def create_emoji(self, name: str, imagefile: UPLOADABLE_TYPE) -> CustomEmoji:
106+
"""Create an emoji for this application"""
107+
data_payload = {
108+
"name": name,
109+
"image": to_image_data(imagefile),
110+
"roles": MISSING,
111+
}
112+
113+
return self.client.cache.place_emoji_data(
114+
None, await self.client.http.create_application_emoji(data_payload, self.id)
115+
)
116+
117+
async def edit_emoji(self, emoji_id: Snowflake_Type, name: str) -> CustomEmoji:
118+
"""Edit an emoji for this application"""
119+
return self.client.cache.place_emoji_data(
120+
None, await self.client.http.edit_application_emoji(self.id, emoji_id, name)
121+
)
122+
123+
async def delete_emoji(self, emoji_id: Snowflake_Type) -> None:
124+
"""Delete an emoji for this application"""
125+
await self.client.http.delete_application_emoji(self.id, emoji_id)

Diff for: interactions/models/discord/channel.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -2399,7 +2399,7 @@ async def close_stage(self, reason: Absent[Optional[str]] = MISSING) -> None:
23992399

24002400

24012401
@attrs.define(eq=False, order=False, hash=False, kw_only=True)
2402-
class GuildForum(GuildChannel, InvitableMixin):
2402+
class GuildForum(GuildChannel, InvitableMixin, WebhookMixin):
24032403
available_tags: List[ThreadTag] = attrs.field(repr=False, factory=list)
24042404
"""A list of tags available to assign to threads"""
24052405
default_reaction_emoji: Optional[DefaultReaction] = attrs.field(repr=False, default=None)

Diff for: interactions/models/discord/emoji.py

+30-13
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class PartialEmoji(SnowflakeObject, DictSerializationMixin):
3838
"""The custom emoji name, or standard unicode emoji in string"""
3939
animated: bool = attrs.field(repr=True, default=False)
4040
"""Whether this emoji is animated"""
41+
available: bool = attrs.field(repr=False, default=True)
42+
"""whether this emoji can be used, may be false due to loss of Server Boosts"""
4143

4244
@classmethod
4345
def from_str(cls, emoji_str: str, *, language: str = "alias") -> Optional["PartialEmoji"]:
@@ -120,7 +122,7 @@ class CustomEmoji(PartialEmoji, ClientObject):
120122
_role_ids: List["Snowflake_Type"] = attrs.field(
121123
repr=False, factory=list, converter=optional(list_converter(to_snowflake))
122124
)
123-
_guild_id: "Snowflake_Type" = attrs.field(repr=False, default=None, converter=to_snowflake)
125+
_guild_id: "Optional[Snowflake_Type]" = attrs.field(repr=False, default=None, converter=optional(to_snowflake))
124126

125127
@classmethod
126128
def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]:
@@ -133,13 +135,13 @@ def _process_dict(cls, data: Dict[str, Any], client: "Client") -> Dict[str, Any]
133135
return data
134136

135137
@classmethod
136-
def from_dict(cls, data: Dict[str, Any], client: "Client", guild_id: int) -> "CustomEmoji":
138+
def from_dict(cls, data: Dict[str, Any], client: "Client", guild_id: "Optional[Snowflake_Type]") -> "CustomEmoji":
137139
data = cls._process_dict(data, client)
138140
return cls(client=client, guild_id=guild_id, **cls._filter_kwargs(data, cls._get_init_keys()))
139141

140142
@property
141-
def guild(self) -> "Guild":
142-
"""The guild this emoji belongs to."""
143+
def guild(self) -> "Optional[Guild]":
144+
"""The guild this emoji belongs to, if applicable."""
143145
return self._client.cache.get_guild(self._guild_id)
144146

145147
@property
@@ -160,6 +162,9 @@ def is_usable(self) -> bool:
160162
if not self.available:
161163
return False
162164

165+
if not self._guild_id: # likely an application emoji
166+
return True
167+
163168
guild = self.guild
164169
return any(e_role_id in guild.me._role_ids for e_role_id in self._role_ids)
165170

@@ -182,14 +187,23 @@ async def edit(
182187
The newly modified custom emoji.
183188
184189
"""
185-
data_payload = dict_filter_none(
186-
{
187-
"name": name,
188-
"roles": to_snowflake_list(roles) if roles else None,
189-
}
190-
)
190+
if self._guild_id:
191+
data_payload = dict_filter_none(
192+
{
193+
"name": name,
194+
"roles": to_snowflake_list(roles) if roles else None,
195+
}
196+
)
197+
198+
updated_data = await self._client.http.modify_guild_emoji(
199+
data_payload, self._guild_id, self.id, reason=reason
200+
)
201+
else:
202+
if roles or reason:
203+
raise ValueError("Cannot specify roles or reason for application emoji.")
204+
205+
updated_data = await self.client.http.edit_application_emoji(self.bot.app.id, self.id, name)
191206

192-
updated_data = await self._client.http.modify_guild_emoji(data_payload, self._guild_id, self.id, reason=reason)
193207
self.update_from_dict(updated_data)
194208
return self
195209

@@ -202,9 +216,12 @@ async def delete(self, reason: Optional[str] = None) -> None:
202216
203217
"""
204218
if not self._guild_id:
205-
raise ValueError("Cannot delete emoji, no guild id set.")
219+
if reason:
220+
raise ValueError("Cannot specify reason for application emoji.")
206221

207-
await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)
222+
await self.client.http.delete_application_emoji(self._client.app.id, self.id)
223+
else:
224+
await self._client.http.delete_guild_emoji(self._guild_id, self.id, reason=reason)
208225

209226
@property
210227
def url(self) -> str:

0 commit comments

Comments
 (0)