Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(guild): don't cache emojis/stickers/scheduled events if intent is disabled #1083

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 changelog/1083.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Don't cache :attr:`Guild.emojis`, :attr:`~Guild.stickers`, or :attr:`~Guild.scheduled_events` if the corresponding intent is disabled.
37 changes: 22 additions & 15 deletions disnake/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,17 @@ class Guild(Hashable):
3: _GuildLimit(emoji=250, stickers=60, bitrate=384e3, filesize=104857600),
}

def __init__(self, *, data: GuildPayload, state: ConnectionState) -> None:
def __init__(
self, *, data: GuildPayload, state: ConnectionState, from_gateway: bool = False
) -> None:
self._channels: Dict[int, GuildChannel] = {}
self._members: Dict[int, Member] = {}
self._voice_states: Dict[int, VoiceState] = {}
self._threads: Dict[int, Thread] = {}
self._stage_instances: Dict[int, StageInstance] = {}
self._scheduled_events: Dict[int, GuildScheduledEvent] = {}
self._state: ConnectionState = state
self._from_data(data)
self._from_data(data, from_gateway=from_gateway)

def _add_channel(self, channel: GuildChannel, /) -> None:
self._channels[channel.id] = channel
Expand Down Expand Up @@ -512,7 +514,7 @@ def get_command_named(self, name: str, /) -> Optional[APIApplicationCommand]:
"""
return self._state._get_guild_command_named(self.id, name)

def _from_data(self, guild: GuildPayload) -> None:
def _from_data(self, guild: GuildPayload, *, from_gateway: bool = False) -> None:
# according to Stan, this is always available even if the guild is unavailable
# I don't have this guarantee when someone updates the guild.
member_count = guild.get("member_count", None)
Expand All @@ -535,19 +537,23 @@ def _from_data(self, guild: GuildPayload) -> None:
self._banner: Optional[str] = guild.get("banner")
self.unavailable: bool = guild.get("unavailable", False)
self.id: int = int(guild["id"])

self._roles: Dict[int, Role] = {}
state = self._state # speed up attribute access
for r in guild.get("roles", []):
role = Role(guild=self, data=r, state=state)
self._roles[role.id] = role

self.mfa_level: MFALevel = guild.get("mfa_level", 0)
self.emojis: Tuple[Emoji, ...] = tuple(
state.store_emoji(self, d) for d in guild.get("emojis", [])
)
self.stickers: Tuple[GuildSticker, ...] = tuple(
state.store_sticker(self, d) for d in guild.get("stickers", [])
)

self.emojis: Tuple[Emoji, ...] = ()
self.stickers: Tuple[GuildSticker, ...] = ()
# don't cache emojis/stickers if this is part of a gw event and the intent is disabled
# (we still want to store them on this guild object even with the intent disabled if obtained from `fetch_guild` etc.)
if not from_gateway or state._intents.emojis_and_stickers:
self.emojis = tuple(state.store_emoji(self, d) for d in guild.get("emojis", []))
self.stickers = tuple(state.store_sticker(self, d) for d in guild.get("stickers", []))

self.features: List[GuildFeature] = guild.get("features", [])
self._splash: Optional[str] = guild.get("splash")
self._system_channel_id: Optional[int] = utils._get_as_snowflake(guild, "system_channel_id")
Expand Down Expand Up @@ -585,12 +591,13 @@ def _from_data(self, guild: GuildPayload) -> None:
stage_instance = StageInstance(guild=self, data=s, state=state)
self._stage_instances[stage_instance.id] = stage_instance

scheduled_events = guild.get("guild_scheduled_events")
if scheduled_events is not None:
self._scheduled_events = {}
for e in scheduled_events:
scheduled_event = GuildScheduledEvent(state=state, data=e)
self._scheduled_events[scheduled_event.id] = scheduled_event
if not from_gateway or state._intents.guild_scheduled_events:
scheduled_events = guild.get("guild_scheduled_events")
if scheduled_events is not None:
self._scheduled_events = {}
for e in scheduled_events:
scheduled_event = GuildScheduledEvent(state=state, data=e)
self._scheduled_events[scheduled_event.id] = scheduled_event

cache_joined = self._state.member_cache_flags.joined
self_id = self._state.self_id
Expand Down
21 changes: 17 additions & 4 deletions disnake/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,10 @@ def __init__(
if not self._intents.members or member_cache_flags._empty:
self.store_user = self.create_user

if not self._intents.emojis_and_stickers:
self.store_emoji = self.create_emoji
self.store_sticker = self.create_sticker

self.parsers = parsers = {}
for attr, func in inspect.getmembers(self):
if attr.startswith("parse_"):
Expand Down Expand Up @@ -398,11 +402,17 @@ def store_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji:
self._emojis[emoji_id] = emoji = Emoji(guild=guild, state=self, data=data)
return emoji

def create_emoji(self, guild: Guild, data: EmojiPayload) -> Emoji:
return Emoji(guild=guild, state=self, data=data)

def store_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker:
sticker_id = int(data["id"])
self._stickers[sticker_id] = sticker = GuildSticker(state=self, data=data)
return sticker

def create_sticker(self, guild: Guild, data: GuildStickerPayload) -> GuildSticker:
return GuildSticker(state=self, data=data)

def store_view(self, view: View, message_id: Optional[int] = None) -> None:
self._view_store.add_view(view, message_id)

Expand Down Expand Up @@ -575,6 +585,7 @@ def _add_guild_from_data(self, data: Union[GuildPayload, UnavailableGuildPayload
guild = Guild(
data=data, # type: ignore # may be unavailable guild
state=self,
from_gateway=True,
)
self._add_guild(guild)
return guild
Expand Down Expand Up @@ -1337,6 +1348,8 @@ def parse_guild_emojis_update(self, data: gateway.GuildEmojisUpdateEvent) -> Non
)
return

# n.b. this isn't gated by an intent check, since we shouldn't receive
# this event in the first place if the intent is disabled
before_emojis = guild.emojis
for emoji in before_emojis:
self._emojis.pop(emoji.id, None)
Expand All @@ -1353,8 +1366,8 @@ def parse_guild_stickers_update(self, data: gateway.GuildStickersUpdateEvent) ->
return

before_stickers = guild.stickers
for emoji in before_stickers:
self._stickers.pop(emoji.id, None)
for sticker in before_stickers:
self._stickers.pop(sticker.id, None)
guild.stickers = tuple(self.store_sticker(guild, d) for d in data["stickers"])
self.dispatch("guild_stickers_update", guild, before_stickers, guild.stickers)

Expand All @@ -1366,7 +1379,7 @@ def _get_create_guild(self, data: gateway.GuildCreateEvent) -> Guild:
guild = self._get_guild(int(data["id"]))
if guild is not None:
guild.unavailable = False
guild._from_data(data) # type: ignore # data type not narrowed correctly to full guild
guild._from_data(data, from_gateway=True) # type: ignore # data type not narrowed correctly to full guild
return guild

return self._add_guild_from_data(data)
Expand Down Expand Up @@ -1444,7 +1457,7 @@ def parse_guild_update(self, data: gateway.GuildUpdateEvent) -> None:
guild = self._get_guild(int(data["id"]))
if guild is not None:
old_guild = copy.copy(guild)
guild._from_data(data)
guild._from_data(data, from_gateway=True)
self.dispatch("guild_update", old_guild, guild)
else:
_log.debug("GUILD_UPDATE referencing an unknown guild ID: %s. Discarding.", data["id"])
Expand Down
9 changes: 8 additions & 1 deletion disnake/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,14 @@ def self_id(self):
def member_cache_flags(self):
return self.__state.member_cache_flags

def store_emoji(self, guild, packet):
@property
def _intents(self):
return self.__state._intents

def store_emoji(self, guild, data):
return None

def store_sticker(self, guild, data):
return None

def _get_voice_client(self, id):
Expand Down