Skip to content

Commit 2184b71

Browse files
authored
feat: Implement missing stuff for scheduled events (#1507)
* 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
1 parent 5308f08 commit 2184b71

File tree

7 files changed

+193
-4
lines changed

7 files changed

+193
-4
lines changed

interactions/api/events/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,11 @@
1818
GuildJoin,
1919
GuildLeft,
2020
GuildMembersChunk,
21+
GuildScheduledEventCreate,
22+
GuildScheduledEventUpdate,
23+
GuildScheduledEventDelete,
24+
GuildScheduledEventUserAdd,
25+
GuildScheduledEventUserRemove,
2126
GuildStickersUpdate,
2227
GuildUnavailable,
2328
GuildUpdate,
@@ -126,6 +131,11 @@
126131
"GuildJoin",
127132
"GuildLeft",
128133
"GuildMembersChunk",
134+
"GuildScheduledEventCreate",
135+
"GuildScheduledEventUpdate",
136+
"GuildScheduledEventDelete",
137+
"GuildScheduledEventUserAdd",
138+
"GuildScheduledEventUserRemove",
129139
"GuildStickersUpdate",
130140
"GuildUnavailable",
131141
"GuildUpdate",

interactions/api/events/discord.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,11 @@ async def an_event_handler(event: ChannelCreate):
4848
"GuildJoin",
4949
"GuildLeft",
5050
"GuildMembersChunk",
51+
"GuildScheduledEventCreate",
52+
"GuildScheduledEventUpdate",
53+
"GuildScheduledEventDelete",
54+
"GuildScheduledEventUserAdd",
55+
"GuildScheduledEventUserRemove",
5156
"GuildStickersUpdate",
5257
"GuildAvailable",
5358
"GuildUnavailable",
@@ -109,6 +114,7 @@ async def an_event_handler(event: ChannelCreate):
109114
from interactions.models.discord.auto_mod import AutoModerationAction, AutoModRule
110115
from interactions.models.discord.reaction import Reaction
111116
from interactions.models.discord.app_perms import ApplicationCommandPermission
117+
from interactions.models.discord.scheduled_event import ScheduledEvent
112118

113119

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

757763
audit_log_entry: interactions.models.AuditLogEntry = attrs.field(repr=False)
758764
"""The audit log entry object"""
765+
766+
767+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
768+
class GuildScheduledEventCreate(BaseEvent):
769+
"""Dispatched when scheduled event is created"""
770+
771+
scheduled_event: "ScheduledEvent" = attrs.field(repr=True)
772+
"""The scheduled event object"""
773+
774+
775+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
776+
class GuildScheduledEventUpdate(BaseEvent):
777+
"""Dispatched when scheduled event is updated"""
778+
779+
before: Absent["ScheduledEvent"] = attrs.field(repr=True)
780+
"""The scheduled event before this event was created"""
781+
after: "ScheduledEvent" = attrs.field(repr=True)
782+
"""The scheduled event after this event was created"""
783+
784+
785+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
786+
class GuildScheduledEventDelete(GuildScheduledEventCreate):
787+
"""Dispatched when scheduled event is deleted"""
788+
789+
790+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
791+
class GuildScheduledEventUserAdd(GuildEvent):
792+
"""Dispatched when scheduled event is created"""
793+
794+
scheduled_event_id: "Snowflake_Type" = attrs.field(repr=True)
795+
"""The ID of the scheduled event"""
796+
user_id: "Snowflake_Type" = attrs.field(repr=True)
797+
"""The ID of the user that has been added/removed from scheduled event"""
798+
799+
@property
800+
def scheduled_event(self) -> Optional["ScheduledEvent"]:
801+
"""The scheduled event object if cached"""
802+
return self.client.get_scheduled_event(self.scheduled_event_id)
803+
804+
@property
805+
def user(self) -> Optional["User"]:
806+
"""The user that has been added/removed from scheduled event if cached"""
807+
return self.client.get_user(self.user_id)
808+
809+
@property
810+
def member(self) -> Optional["Member"]:
811+
"""The guild member that has been added/removed from scheduled event if cached"""
812+
return self.client.get_member(self.guild_id, self.user.id)
813+
814+
815+
@attrs.define(eq=False, order=False, hash=False, kw_only=False)
816+
class GuildScheduledEventUserRemove(GuildScheduledEventUserAdd):
817+
"""Dispatched when scheduled event is removed"""

interactions/api/events/processors/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .message_events import MessageEvents
66
from .reaction_events import ReactionEvents
77
from .role_events import RoleEvents
8+
from .scheduled_events import ScheduledEvents
89
from .stage_events import StageEvents
910
from .thread_events import ThreadEvents
1011
from .user_events import UserEvents
@@ -20,6 +21,7 @@
2021
"MessageEvents",
2122
"ReactionEvents",
2223
"RoleEvents",
24+
"ScheduledEvents",
2325
"StageEvents",
2426
"ThreadEvents",
2527
"UserEvents",
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import copy
2+
from typing import TYPE_CHECKING
3+
4+
import interactions.api.events as events
5+
from interactions.client.const import MISSING
6+
from interactions.models import ScheduledEvent
7+
from ._template import EventMixinTemplate, Processor
8+
9+
if TYPE_CHECKING:
10+
from interactions.api.events import RawGatewayEvent
11+
12+
__all__ = ("ScheduledEvents",)
13+
14+
15+
class ScheduledEvents(EventMixinTemplate):
16+
@Processor.define()
17+
async def _on_raw_guild_scheduled_event_create(self, event: "RawGatewayEvent") -> None:
18+
scheduled_event = self.cache.place_scheduled_event_data(event.data)
19+
20+
self.dispatch(events.GuildScheduledEventCreate(scheduled_event))
21+
22+
@Processor.define()
23+
async def _on_raw_guild_scheduled_event_update(self, event: "RawGatewayEvent") -> None:
24+
before = copy.copy(self.cache.get_scheduled_event(event.data.get("id")))
25+
after = self.cache.place_scheduled_event_data(event.data)
26+
27+
self.dispatch(events.GuildScheduledEventUpdate(before or MISSING, after))
28+
29+
@Processor.define()
30+
async def _on_raw_guild_scheduled_event_delete(self, event: "RawGatewayEvent") -> None:
31+
# for some reason this event returns the deleted scheduled event data?
32+
# so we create an object from it
33+
scheduled_event = ScheduledEvent.from_dict(event.data, self)
34+
self.cache.delete_scheduled_event(event.data.get("id"))
35+
36+
self.dispatch(events.GuildScheduledEventDelete(scheduled_event))
37+
38+
@Processor.define()
39+
async def _on_raw_guild_scheduled_event_user_add(self, event: "RawGatewayEvent") -> None:
40+
scheduled_event = self.cache.get_scheduled_event(event.data.get("guild_scheduled_event_id"))
41+
user = self.cache.get_user(event.data.get("user_id"))
42+
43+
self.dispatch(events.GuildScheduledEventUserAdd(event.data.get("guild_id"), scheduled_event, user))
44+
45+
@Processor.define()
46+
async def _on_raw_guild_scheduled_event_user_remove(self, event: "RawGatewayEvent") -> None:
47+
scheduled_event = self.cache.get_scheduled_event(event.data.get("guild_scheduled_event_id"))
48+
user = self.cache.get_user(event.data.get("user_id"))
49+
50+
self.dispatch(events.GuildScheduledEventUserRemove(event.data.get("guild_id"), scheduled_event, user))

interactions/client/client.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,12 @@
173173
events.AutoModCreated: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
174174
events.AutoModUpdated: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
175175
events.AutoModDeleted: [Intents.AUTO_MODERATION_CONFIGURATION, Intents.AUTO_MOD],
176+
# Intents.GUILD_SCHEDULED_EVENTS
177+
events.GuildScheduledEventCreate: [Intents.GUILD_SCHEDULED_EVENTS],
178+
events.GuildScheduledEventUpdate: [Intents.GUILD_SCHEDULED_EVENTS],
179+
events.GuildScheduledEventDelete: [Intents.GUILD_SCHEDULED_EVENTS],
180+
events.GuildScheduledEventUserAdd: [Intents.GUILD_SCHEDULED_EVENTS],
181+
events.GuildScheduledEventUserRemove: [Intents.GUILD_SCHEDULED_EVENTS],
176182
# multiple intents
177183
events.ThreadMembersUpdate: [Intents.GUILDS, Intents.GUILD_MEMBERS],
178184
events.TypingStart: [
@@ -211,6 +217,7 @@ class Client(
211217
processors.MessageEvents,
212218
processors.ReactionEvents,
213219
processors.RoleEvents,
220+
processors.ScheduledEvents,
214221
processors.StageEvents,
215222
processors.ThreadEvents,
216223
processors.UserEvents,
@@ -2282,10 +2289,29 @@ async def fetch_scheduled_event(
22822289
"""
22832290
try:
22842291
scheduled_event_data = await self.http.get_scheduled_event(guild_id, scheduled_event_id, with_user_count)
2285-
return ScheduledEvent.from_dict(scheduled_event_data, self)
2292+
return self.cache.place_scheduled_event_data(scheduled_event_data)
22862293
except NotFound:
22872294
return None
22882295

2296+
def get_scheduled_event(
2297+
self,
2298+
scheduled_event_id: "Snowflake_Type",
2299+
) -> Optional["ScheduledEvent"]:
2300+
"""
2301+
Get a scheduled event by id.
2302+
2303+
!!! note
2304+
This method is an alias for the cache which will return a cached object.
2305+
2306+
Args:
2307+
scheduled_event_id: The ID of the scheduled event to get
2308+
2309+
Returns:
2310+
The scheduled event if found, otherwise None
2311+
2312+
"""
2313+
return self.cache.get_scheduled_event(scheduled_event_id)
2314+
22892315
async def fetch_custom_emoji(
22902316
self, emoji_id: "Snowflake_Type", guild_id: "Snowflake_Type", *, force: bool = False
22912317
) -> Optional[CustomEmoji]:

interactions/client/smart_cache.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
from interactions.models.discord.role import Role
1717
from interactions.models.discord.snowflake import to_snowflake, to_optional_snowflake
1818
from interactions.models.discord.user import Member, User
19+
from interactions.models.discord.scheduled_event import ScheduledEvent
1920
from interactions.models.internal.active_voice_state import ActiveVoiceState
2021

2122
__all__ = ("GlobalCache", "create_cache")
@@ -70,6 +71,7 @@ class GlobalCache:
7071
member_cache: dict = attrs.field(repr=False, factory=dict) # key: (guild_id, user_id)
7172
channel_cache: dict = attrs.field(repr=False, factory=dict) # key: channel_id
7273
guild_cache: dict = attrs.field(repr=False, factory=dict) # key: guild_id
74+
scheduled_events_cache: dict = attrs.field(repr=False, factory=dict) # key: guild_scheduled_event_id
7375

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

905907
# endregion Emoji cache
908+
909+
# region ScheduledEvents cache
910+
911+
def get_scheduled_event(self, scheduled_event_id: "Snowflake_Type") -> Optional["ScheduledEvent"]:
912+
"""
913+
Get a scheduled event based on the scheduled event ID.
914+
915+
Args:
916+
scheduled_event_id: The ID of the scheduled event
917+
918+
Returns:
919+
The ScheduledEvent if found
920+
"""
921+
return self.scheduled_events_cache.get(to_snowflake(scheduled_event_id))
922+
923+
def place_scheduled_event_data(self, data: discord_typings.GuildScheduledEventData) -> "ScheduledEvent":
924+
"""
925+
Take json data representing a scheduled event, process it, and cache it.
926+
927+
Args:
928+
data: json representation of the scheduled event
929+
930+
Returns:
931+
The processed scheduled event
932+
"""
933+
scheduled_event = ScheduledEvent.from_dict(data, self._client)
934+
self.scheduled_events_cache[scheduled_event.id] = scheduled_event
935+
936+
return scheduled_event
937+
938+
def delete_scheduled_event(self, scheduled_event_id: "Snowflake_Type") -> None:
939+
"""
940+
Delete a scheduled event from the cache.
941+
942+
Args:
943+
scheduled_event_id: The ID of the scheduled event
944+
"""
945+
self.scheduled_events_cache.pop(to_snowflake(scheduled_event_id), None)
946+
947+
# endregion ScheduledEvents cache

interactions/models/discord/guild.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,7 @@ async def list_scheduled_events(self, with_user_count: bool = False) -> List["mo
12531253
12541254
"""
12551255
scheduled_events_data = await self._client.http.list_schedules_events(self.id, with_user_count)
1256-
return models.ScheduledEvent.from_list(scheduled_events_data, self._client)
1256+
return [self._client.cache.place_scheduled_event_data(data) for data in scheduled_events_data]
12571257

12581258
async def fetch_scheduled_event(
12591259
self, scheduled_event_id: Snowflake_Type, with_user_count: bool = False
@@ -1275,7 +1275,7 @@ async def fetch_scheduled_event(
12751275
)
12761276
except NotFound:
12771277
return None
1278-
return models.ScheduledEvent.from_dict(scheduled_event_data, self._client)
1278+
return self._client.cache.place_scheduled_event_data(scheduled_event_data)
12791279

12801280
async def create_scheduled_event(
12811281
self,
@@ -1339,7 +1339,7 @@ async def create_scheduled_event(
13391339
}
13401340

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

13441344
async def create_custom_sticker(
13451345
self,

0 commit comments

Comments
 (0)