Skip to content

Commit

Permalink
feat: Implement missing stuff for scheduled events (#1507)
Browse files Browse the repository at this point in the history
* feat: Implement gateway support for scheduled events

* fix: add optional to user add/remove

* refactor: pre-commit'ed

* refactor: change attrs to props and add `attrs.field`

* refactor: set repr to True

* feat(client): add `get_scheduled_event` helper method

* refactor: use cache helpers in methods

* chore: replace typing
  • Loading branch information
Damego authored Aug 3, 2023
1 parent 5308f08 commit 2184b71
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 4 deletions.
10 changes: 10 additions & 0 deletions interactions/api/events/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@
GuildJoin,
GuildLeft,
GuildMembersChunk,
GuildScheduledEventCreate,
GuildScheduledEventUpdate,
GuildScheduledEventDelete,
GuildScheduledEventUserAdd,
GuildScheduledEventUserRemove,
GuildStickersUpdate,
GuildUnavailable,
GuildUpdate,
Expand Down Expand Up @@ -126,6 +131,11 @@
"GuildJoin",
"GuildLeft",
"GuildMembersChunk",
"GuildScheduledEventCreate",
"GuildScheduledEventUpdate",
"GuildScheduledEventDelete",
"GuildScheduledEventUserAdd",
"GuildScheduledEventUserRemove",
"GuildStickersUpdate",
"GuildUnavailable",
"GuildUpdate",
Expand Down
59 changes: 59 additions & 0 deletions interactions/api/events/discord.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ async def an_event_handler(event: ChannelCreate):
"GuildJoin",
"GuildLeft",
"GuildMembersChunk",
"GuildScheduledEventCreate",
"GuildScheduledEventUpdate",
"GuildScheduledEventDelete",
"GuildScheduledEventUserAdd",
"GuildScheduledEventUserRemove",
"GuildStickersUpdate",
"GuildAvailable",
"GuildUnavailable",
Expand Down Expand Up @@ -109,6 +114,7 @@ async def an_event_handler(event: ChannelCreate):
from interactions.models.discord.auto_mod import AutoModerationAction, AutoModRule
from interactions.models.discord.reaction import Reaction
from interactions.models.discord.app_perms import ApplicationCommandPermission
from interactions.models.discord.scheduled_event import ScheduledEvent


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
Expand Down Expand Up @@ -756,3 +762,56 @@ class GuildAuditLogEntryCreate(GuildEvent):

audit_log_entry: interactions.models.AuditLogEntry = attrs.field(repr=False)
"""The audit log entry object"""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventCreate(BaseEvent):
"""Dispatched when scheduled event is created"""

scheduled_event: "ScheduledEvent" = attrs.field(repr=True)
"""The scheduled event object"""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUpdate(BaseEvent):
"""Dispatched when scheduled event is updated"""

before: Absent["ScheduledEvent"] = attrs.field(repr=True)
"""The scheduled event before this event was created"""
after: "ScheduledEvent" = attrs.field(repr=True)
"""The scheduled event after this event was created"""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventDelete(GuildScheduledEventCreate):
"""Dispatched when scheduled event is deleted"""


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUserAdd(GuildEvent):
"""Dispatched when scheduled event is created"""

scheduled_event_id: "Snowflake_Type" = attrs.field(repr=True)
"""The ID of the scheduled event"""
user_id: "Snowflake_Type" = attrs.field(repr=True)
"""The ID of the user that has been added/removed from scheduled event"""

@property
def scheduled_event(self) -> Optional["ScheduledEvent"]:
"""The scheduled event object if cached"""
return self.client.get_scheduled_event(self.scheduled_event_id)

@property
def user(self) -> Optional["User"]:
"""The user that has been added/removed from scheduled event if cached"""
return self.client.get_user(self.user_id)

@property
def member(self) -> Optional["Member"]:
"""The guild member that has been added/removed from scheduled event if cached"""
return self.client.get_member(self.guild_id, self.user.id)


@attrs.define(eq=False, order=False, hash=False, kw_only=False)
class GuildScheduledEventUserRemove(GuildScheduledEventUserAdd):
"""Dispatched when scheduled event is removed"""
2 changes: 2 additions & 0 deletions interactions/api/events/processors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .message_events import MessageEvents
from .reaction_events import ReactionEvents
from .role_events import RoleEvents
from .scheduled_events import ScheduledEvents
from .stage_events import StageEvents
from .thread_events import ThreadEvents
from .user_events import UserEvents
Expand All @@ -20,6 +21,7 @@
"MessageEvents",
"ReactionEvents",
"RoleEvents",
"ScheduledEvents",
"StageEvents",
"ThreadEvents",
"UserEvents",
Expand Down
50 changes: 50 additions & 0 deletions interactions/api/events/processors/scheduled_events.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import copy
from typing import TYPE_CHECKING

import interactions.api.events as events
from interactions.client.const import MISSING
from interactions.models import ScheduledEvent
from ._template import EventMixinTemplate, Processor

if TYPE_CHECKING:
from interactions.api.events import RawGatewayEvent

__all__ = ("ScheduledEvents",)


class ScheduledEvents(EventMixinTemplate):
@Processor.define()
async def _on_raw_guild_scheduled_event_create(self, event: "RawGatewayEvent") -> None:
scheduled_event = self.cache.place_scheduled_event_data(event.data)

self.dispatch(events.GuildScheduledEventCreate(scheduled_event))

@Processor.define()
async def _on_raw_guild_scheduled_event_update(self, event: "RawGatewayEvent") -> None:
before = copy.copy(self.cache.get_scheduled_event(event.data.get("id")))
after = self.cache.place_scheduled_event_data(event.data)

self.dispatch(events.GuildScheduledEventUpdate(before or MISSING, after))

@Processor.define()
async def _on_raw_guild_scheduled_event_delete(self, event: "RawGatewayEvent") -> None:
# for some reason this event returns the deleted scheduled event data?
# so we create an object from it
scheduled_event = ScheduledEvent.from_dict(event.data, self)
self.cache.delete_scheduled_event(event.data.get("id"))

self.dispatch(events.GuildScheduledEventDelete(scheduled_event))

@Processor.define()
async def _on_raw_guild_scheduled_event_user_add(self, event: "RawGatewayEvent") -> None:
scheduled_event = self.cache.get_scheduled_event(event.data.get("guild_scheduled_event_id"))
user = self.cache.get_user(event.data.get("user_id"))

self.dispatch(events.GuildScheduledEventUserAdd(event.data.get("guild_id"), scheduled_event, user))

@Processor.define()
async def _on_raw_guild_scheduled_event_user_remove(self, event: "RawGatewayEvent") -> None:
scheduled_event = self.cache.get_scheduled_event(event.data.get("guild_scheduled_event_id"))
user = self.cache.get_user(event.data.get("user_id"))

self.dispatch(events.GuildScheduledEventUserRemove(event.data.get("guild_id"), scheduled_event, user))
28 changes: 27 additions & 1 deletion interactions/client/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,12 @@
events.AutoModCreated: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
events.AutoModUpdated: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
events.AutoModDeleted: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
# Intents.GUILD_SCHEDULED_EVENTS
events.GuildScheduledEventCreate: [Intents.GUILD_SCHEDULED_EVENTS],
events.GuildScheduledEventUpdate: [Intents.GUILD_SCHEDULED_EVENTS],
events.GuildScheduledEventDelete: [Intents.GUILD_SCHEDULED_EVENTS],
events.GuildScheduledEventUserAdd: [Intents.GUILD_SCHEDULED_EVENTS],
events.GuildScheduledEventUserRemove: [Intents.GUILD_SCHEDULED_EVENTS],
# multiple intents
events.ThreadMembersUpdate: [Intents.GUILDS, Intents.GUILD_MEMBERS],
events.TypingStart: [
Expand Down Expand Up @@ -211,6 +217,7 @@ class Client(
processors.MessageEvents,
processors.ReactionEvents,
processors.RoleEvents,
processors.ScheduledEvents,
processors.StageEvents,
processors.ThreadEvents,
processors.UserEvents,
Expand Down Expand Up @@ -2282,10 +2289,29 @@ async def fetch_scheduled_event(
"""
try:
scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
return ScheduledEvent.from_dict(scheduled_event_data, self)
return self.cache.place_scheduled_event_data(scheduled_event_data)
except NotFound:
return None

def get_scheduled_event(
self,
scheduled_event_id: "Snowflake_Type",
) -> Optional["ScheduledEvent"]:
"""
Get a scheduled event by id.
!!! note
This method is an alias for the cache which will return a cached object.
Args:
scheduled_event_id: The ID of the scheduled event to get
Returns:
The scheduled event if found, otherwise None
"""
return self.cache.get_scheduled_event(scheduled_event_id)

async def fetch_custom_emoji(
self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
) -> Optional[CustomEmoji]:
Expand Down
42 changes: 42 additions & 0 deletions interactions/client/smart_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from interactions.models.discord.role import Role
from interactions.models.discord.snowflake import to_snowflake, to_optional_snowflake
from interactions.models.discord.user import Member, User
from interactions.models.discord.scheduled_event import ScheduledEvent
from interactions.models.internal.active_voice_state import ActiveVoiceState

__all__ = ("GlobalCache", "create_cache")
Expand Down Expand Up @@ -70,6 +71,7 @@ class GlobalCache:
member_cache: dict = attrs.field(repr=False, factory=dict) # key: (guild_id, user_id)
channel_cache: dict = attrs.field(repr=False, factory=dict) # key: channel_id
guild_cache: dict = attrs.field(repr=False, factory=dict) # key: guild_id
scheduled_events_cache: dict = attrs.field(repr=False, factory=dict) # key: guild_scheduled_event_id

# Expiring discord objects cache
message_cache: TTLCache = attrs.field(repr=False, factory=TTLCache) # key: (channel_id, message_id)
Expand Down Expand Up @@ -903,3 +905,43 @@ def delete_emoji(self, emoji_id: "Snowflake_Type") -> None:
self.emoji_cache.pop(to_snowflake(emoji_id), None)

# endregion Emoji cache

# region ScheduledEvents cache

def get_scheduled_event(self, scheduled_event_id: "Snowflake_Type") -> Optional["ScheduledEvent"]:
"""
Get a scheduled event based on the scheduled event ID.
Args:
scheduled_event_id: The ID of the scheduled event
Returns:
The ScheduledEvent if found
"""
return self.scheduled_events_cache.get(to_snowflake(scheduled_event_id))

def place_scheduled_event_data(self, data: discord_typings.GuildScheduledEventData) -> "ScheduledEvent":
"""
Take json data representing a scheduled event, process it, and cache it.
Args:
data: json representation of the scheduled event
Returns:
The processed scheduled event
"""
scheduled_event = ScheduledEvent.from_dict(data, self._client)
self.scheduled_events_cache[scheduled_event.id] = scheduled_event

return scheduled_event

def delete_scheduled_event(self, scheduled_event_id: "Snowflake_Type") -> None:
"""
Delete a scheduled event from the cache.
Args:
scheduled_event_id: The ID of the scheduled event
"""
self.scheduled_events_cache.pop(to_snowflake(scheduled_event_id), None)

# endregion ScheduledEvents cache
6 changes: 3 additions & 3 deletions interactions/models/discord/guild.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,7 +1253,7 @@ async def list_scheduled_events(self, with_user_count: bool = False) -> List["mo
"""
scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
return models.ScheduledEvent.from_list(scheduled_events_data, self._client)
return [self._client.cache.place_scheduled_event_data(data) for data in scheduled_events_data]

async def fetch_scheduled_event(
self, scheduled_event_id: Snowflake_Type, with_user_count: bool = False
Expand All @@ -1275,7 +1275,7 @@ async def fetch_scheduled_event(
)
except NotFound:
return None
return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)
return self._client.cache.place_scheduled_event_data(scheduled_event_data)

async def create_scheduled_event(
self,
Expand Down Expand Up @@ -1339,7 +1339,7 @@ async def create_scheduled_event(
}

scheduled_event_data = await self._client.http.create_scheduled_event(self.id, payload, reason)
return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)
return self._client.cache.place_scheduled_event_data(scheduled_event_data)

async def create_custom_sticker(
self,
Expand Down

0 comments on commit 2184b71

Please sign in to comment.