scurrypy 0.4__py3-none-any.whl → 0.6.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. scurrypy/__init__.py +429 -0
  2. scurrypy/client.py +335 -0
  3. {discord → scurrypy}/client_like.py +8 -1
  4. scurrypy/dispatch/command_dispatcher.py +205 -0
  5. {discord → scurrypy}/dispatch/event_dispatcher.py +21 -21
  6. {discord → scurrypy}/dispatch/prefix_dispatcher.py +31 -12
  7. {discord → scurrypy}/error.py +6 -18
  8. {discord → scurrypy}/events/channel_events.py +2 -1
  9. scurrypy/events/gateway_events.py +31 -0
  10. {discord → scurrypy}/events/guild_events.py +2 -1
  11. {discord → scurrypy}/events/interaction_events.py +28 -13
  12. {discord → scurrypy}/events/message_events.py +8 -5
  13. {discord → scurrypy}/events/reaction_events.py +1 -2
  14. {discord → scurrypy}/events/ready_event.py +1 -3
  15. scurrypy/gateway.py +183 -0
  16. scurrypy/http.py +310 -0
  17. {discord → scurrypy}/intents.py +5 -7
  18. {discord → scurrypy}/logger.py +14 -61
  19. scurrypy/model.py +71 -0
  20. scurrypy/models.py +258 -0
  21. scurrypy/parts/channel.py +42 -0
  22. scurrypy/parts/command.py +90 -0
  23. scurrypy/parts/components.py +224 -0
  24. scurrypy/parts/components_v2.py +144 -0
  25. scurrypy/parts/embed.py +83 -0
  26. scurrypy/parts/message.py +134 -0
  27. scurrypy/parts/modal.py +16 -0
  28. {discord → scurrypy}/parts/role.py +2 -14
  29. {discord → scurrypy}/resources/application.py +1 -2
  30. {discord → scurrypy}/resources/bot_emojis.py +1 -1
  31. {discord → scurrypy}/resources/channel.py +9 -8
  32. {discord → scurrypy}/resources/guild.py +14 -16
  33. {discord → scurrypy}/resources/interaction.py +50 -43
  34. {discord → scurrypy}/resources/message.py +15 -16
  35. {discord → scurrypy}/resources/user.py +3 -4
  36. scurrypy-0.6.6.dist-info/METADATA +108 -0
  37. scurrypy-0.6.6.dist-info/RECORD +47 -0
  38. {scurrypy-0.4.dist-info → scurrypy-0.6.6.dist-info}/licenses/LICENSE +1 -1
  39. scurrypy-0.6.6.dist-info/top_level.txt +1 -0
  40. discord/__init__.py +0 -223
  41. discord/client.py +0 -375
  42. discord/dispatch/command_dispatcher.py +0 -163
  43. discord/gateway.py +0 -155
  44. discord/http.py +0 -280
  45. discord/model.py +0 -90
  46. discord/models/__init__.py +0 -1
  47. discord/models/application.py +0 -37
  48. discord/models/emoji.py +0 -34
  49. discord/models/guild.py +0 -35
  50. discord/models/integration.py +0 -23
  51. discord/models/interaction.py +0 -26
  52. discord/models/member.py +0 -27
  53. discord/models/role.py +0 -53
  54. discord/models/user.py +0 -15
  55. discord/parts/action_row.py +0 -208
  56. discord/parts/channel.py +0 -20
  57. discord/parts/command.py +0 -102
  58. discord/parts/components_v2.py +0 -353
  59. discord/parts/embed.py +0 -154
  60. discord/parts/message.py +0 -194
  61. discord/parts/modal.py +0 -21
  62. scurrypy-0.4.dist-info/METADATA +0 -130
  63. scurrypy-0.4.dist-info/RECORD +0 -54
  64. scurrypy-0.4.dist-info/top_level.txt +0 -1
  65. {discord → scurrypy}/config.py +0 -0
  66. {discord → scurrypy}/dispatch/__init__.py +0 -0
  67. {discord → scurrypy}/events/__init__.py +0 -0
  68. {discord → scurrypy}/events/hello_event.py +0 -0
  69. {discord → scurrypy}/parts/__init__.py +0 -0
  70. {discord → scurrypy}/parts/component_types.py +0 -0
  71. {discord → scurrypy}/resources/__init__.py +0 -0
  72. {scurrypy-0.4.dist-info → scurrypy-0.6.6.dist-info}/WHEEL +0 -0
scurrypy/client.py ADDED
@@ -0,0 +1,335 @@
1
+ import asyncio
2
+
3
+ from .config import BaseConfig
4
+ from .intents import Intents
5
+ from .gateway import GatewayClient
6
+ from .client_like import ClientLike
7
+
8
+ from .parts.command import SlashCommand, MessageCommand, UserCommand
9
+
10
+ class Client(ClientLike):
11
+ """Main entry point for Discord bots.
12
+ Ties together the moving parts: gateway, HTTP, event dispatching, command handling, and resource managers.
13
+ """
14
+ def __init__(self,
15
+ *,
16
+ token: str,
17
+ application_id: int,
18
+ intents: int = Intents.DEFAULT,
19
+ config: BaseConfig = None,
20
+ debug_mode: bool = False,
21
+ sync_commands: bool = True,
22
+ prefix = None,
23
+ quiet: bool = False
24
+ ):
25
+ """
26
+ Args:
27
+ token (str): the bot's token
28
+ application_id (int): the bot's user ID
29
+ intents (int, optional): gateway intents. Defaults to Intents.DEFAULT.
30
+ config (BaseConfig, optional): user-defined config data
31
+ sync_commands (bool, optional): toggle registering commands. Defaults to True.
32
+ debug_mode (bool, optional): toggle debug messages. Defaults to False.
33
+ prefix (str, optional): set message prefix if using command prefixes
34
+ quiet (bool, optional): if INFO, DEBUG, and WARN should be logged
35
+ """
36
+ if not token:
37
+ raise ValueError("Token is required")
38
+ if not application_id:
39
+ raise ValueError("Application ID is required")
40
+
41
+ from .logger import Logger
42
+ from .http import HTTPClient
43
+ from .resources.bot_emojis import BotEmojis
44
+ from .dispatch.event_dispatcher import EventDispatcher
45
+ from .dispatch.prefix_dispatcher import PrefixDispatcher
46
+ from .dispatch.command_dispatcher import CommandDispatcher
47
+
48
+ self.token = token
49
+ self.intents = intents
50
+ self.application_id = application_id
51
+ self.config = config
52
+ self.sync_commands = sync_commands
53
+
54
+ self._logger = Logger(debug_mode, quiet)
55
+
56
+ self._http = HTTPClient(self._logger)
57
+
58
+ self.shards: list[GatewayClient] = []
59
+ self.dispatcher = EventDispatcher(self)
60
+ self.prefix_dispatcher = PrefixDispatcher(self, prefix)
61
+ self.command_dispatcher = CommandDispatcher(self)
62
+
63
+ self._setup_hooks = []
64
+ self._shutdown_hooks = []
65
+
66
+ self.emojis = BotEmojis(self._http, self.application_id)
67
+
68
+ def prefix_command(self, name: str):
69
+ """Decorator registers prefix commands by the name of the function.
70
+
71
+ Args:
72
+ name (str): name of the command
73
+ !!! warning "Important"
74
+ Prefix commands are CASE-INSENSITIVE.
75
+ """
76
+ def decorator(func):
77
+ self.prefix_dispatcher.register(name.lower(), func)
78
+ return func
79
+ return decorator
80
+
81
+ def component(self, custom_id: str):
82
+ """Decorator registers a function for a component handler.
83
+
84
+ Args:
85
+ custom_id (str): Identifier of the component. Must match the `custom_id` set where the component was created.
86
+ """
87
+ def decorator(func):
88
+ self.command_dispatcher.component(func, custom_id)
89
+ return func
90
+ return decorator
91
+
92
+ def command(self, command: SlashCommand | MessageCommand | UserCommand, guild_ids: list[int] = None):
93
+ """Decorator registers a function to a command handler.
94
+
95
+ Args:
96
+ command (SlashCommand | MessageCommand | UserCommand): the command object
97
+ guild_ids (list[int], optional): Guild IDs to register command to (if any). If omitted, the command is **global**.
98
+ """
99
+ def decorator(func):
100
+ if not isinstance(command, (SlashCommand, MessageCommand, UserCommand)):
101
+ raise ValueError(f"Expected SlashCommand, MessageCommand, or UserCommand; got {type(command).__name__}")
102
+
103
+ # maps command type -> command registry
104
+ handler_map = {
105
+ SlashCommand: self.command_dispatcher.add_slash_command,
106
+ MessageCommand: self.command_dispatcher.add_message_command,
107
+ UserCommand: self.command_dispatcher.add_user_command
108
+ }
109
+
110
+ # can guarantee at this point command is one of SlashCommand | MessageCommand | UserCommand
111
+ handler = handler_map[type(command)]
112
+
113
+ handler(command, func, guild_ids)
114
+ return func
115
+ return decorator
116
+
117
+ def event(self, event_name: str):
118
+ """Decorator registers a function for an event handler.
119
+
120
+ Args:
121
+ event_name (str): event name (must be a valid event)
122
+ """
123
+ def decorator(func):
124
+ self.dispatcher.register(event_name, func)
125
+ return func
126
+ return decorator
127
+
128
+ def setup_hook(self, func):
129
+ """Decorator registers a setup hook.
130
+ (Runs once before the bot starts listening)
131
+
132
+ Args:
133
+ func (callable): callback to the setup function
134
+ """
135
+ self._setup_hooks.append(func)
136
+
137
+ def shutdown_hook(self, func):
138
+ """Decorator registers a shutdown hook.
139
+ (Runs once before the bot exits the loop)
140
+
141
+ Args:
142
+ func (callable): callback to the shutdown function
143
+ """
144
+ self._shutdown_hooks.append(func)
145
+
146
+ def fetch_application(self, application_id: int):
147
+ """Creates an interactable application resource.
148
+
149
+ Args:
150
+ application_id (int): ID of target application
151
+
152
+ Returns:
153
+ (Application): the Application resource
154
+ """
155
+ from .resources.application import Application
156
+
157
+ return Application(application_id, self._http)
158
+
159
+ def fetch_guild(self, guild_id: int):
160
+ """Creates an interactable guild resource.
161
+
162
+ Args:
163
+ guild_id (int): ID of target guild
164
+
165
+ Returns:
166
+ (Guild): the Guild resource
167
+ """
168
+ from .resources.guild import Guild
169
+
170
+ return Guild(guild_id, self._http)
171
+
172
+ def fetch_channel(self, channel_id: int):
173
+ """Creates an interactable channel resource.
174
+
175
+ Args:
176
+ channel_id (int): ID of target channel
177
+
178
+ Returns:
179
+ (Channel): the Channel resource
180
+ """
181
+ from .resources.channel import Channel
182
+
183
+ return Channel(channel_id, self._http)
184
+
185
+ def fetch_message(self, channel_id: int, message_id: int):
186
+ """Creates an interactable message resource.
187
+
188
+ Args:
189
+ message_id (int): ID of target message
190
+ channel_id (int): channel ID of target message
191
+
192
+ Returns:
193
+ (Message): the Message resource
194
+ """
195
+ from .resources.message import Message
196
+
197
+ return Message(message_id, channel_id, self._http)
198
+
199
+ def fetch_user(self, user_id: int):
200
+ """Creates an interactable user resource.
201
+
202
+ Args:
203
+ user_id (int): ID of target user
204
+
205
+ Returns:
206
+ (User): the User resource
207
+ """
208
+ from .resources.user import User
209
+
210
+ return User(user_id, self._http)
211
+
212
+ async def clear_commands(self, guild_ids: list[int] = None):
213
+ """Clear a guild's or global commands (all types).
214
+
215
+ Args:
216
+ guild_ids (list[int]): ID of the target guild. If omitted, **global** commands will be cleared.
217
+ """
218
+ self.command_dispatcher.clear_commands(guild_ids)
219
+
220
+ async def _start_shards(self):
221
+ """Starts all shards batching by max_concurrency."""
222
+
223
+ from .events.gateway_events import GatewayEvent
224
+
225
+ data = await self._http.request('GET', '/gateway/bot')
226
+
227
+ gateway = GatewayEvent.from_dict(data)
228
+
229
+ # pull important values for easier access
230
+ total_shards = gateway.shards
231
+ batch_size = gateway.session_start_limit.max_concurrency
232
+
233
+ tasks = []
234
+
235
+ for batch_start in range(0, total_shards, batch_size):
236
+ batch_end = min(batch_start + batch_size, total_shards)
237
+
238
+ self._logger.log_info(f"Starting shards {batch_start}-{batch_end - 1} of {total_shards}")
239
+
240
+ for shard_id in range(batch_start, batch_end):
241
+ shard = GatewayClient(self, gateway.url, shard_id, total_shards)
242
+ self.shards.append(shard)
243
+
244
+ # fire and forget
245
+ tasks.append(asyncio.create_task(shard.start()))
246
+ tasks.append(asyncio.create_task(self._listen_shard(shard)))
247
+
248
+ # wait before next batch to respect identify rate limit
249
+ await asyncio.sleep(5)
250
+
251
+ return tasks
252
+
253
+ async def _listen_shard(self, shard: GatewayClient):
254
+ """Listen to websocket queue for events. Only OP code 0 passes!
255
+
256
+ Args:
257
+ shard (GatewayClient): the shard or gateway to listen on
258
+ """
259
+ while True:
260
+ try:
261
+ dispatch_type, event_data = await shard.event_queue.get()
262
+
263
+ # check prefix first (only if a prefix is set)
264
+ if self.prefix_dispatcher.prefix and dispatch_type == 'MESSAGE_CREATE':
265
+ await self.prefix_dispatcher.dispatch(event_data)
266
+
267
+ # then try interaction
268
+ elif dispatch_type == 'INTERACTION_CREATE':
269
+ await self.command_dispatcher.dispatch(event_data)
270
+
271
+ # otherwise this must be an event!
272
+ await self.dispatcher.dispatch(dispatch_type, event_data)
273
+ except:
274
+ break # stop task if an error occurred
275
+
276
+ async def _start(self):
277
+ """Starts the HTTP/Websocket client, run startup hooks, and registers commands."""
278
+
279
+ try:
280
+ await self._http.start(self.token)
281
+
282
+ if self._setup_hooks:
283
+ for hook in self._setup_hooks:
284
+ self._logger.log_info(f"Setting hook {hook.__name__}")
285
+ await hook(self)
286
+ self._logger.log_high_priority("Hooks set up.")
287
+
288
+ if self.sync_commands:
289
+ await self.command_dispatcher.register_guild_commands()
290
+
291
+ await self.command_dispatcher.register_global_commands()
292
+
293
+ self._logger.log_high_priority("Commands set up.")
294
+
295
+ tasks = await asyncio.create_task(self._start_shards())
296
+
297
+ # end all ongoing tasks
298
+ await asyncio.gather(*tasks)
299
+
300
+ except asyncio.CancelledError:
301
+ self._logger.log_high_priority("Connection cancelled via KeyboardInterrupt.")
302
+ except Exception as e:
303
+ self._logger.log_error(f"{type(e).__name__} - {e}")
304
+ finally:
305
+ await self._close()
306
+
307
+ async def _close(self):
308
+ """Gracefully close HTTP session, websocket connections, and run shutdown hooks."""
309
+
310
+ for hook in self._shutdown_hooks:
311
+ try:
312
+ self._logger.log_info(f"Executing shutdown hook {hook.__name__}")
313
+ await hook(self)
314
+ except Exception as e:
315
+ self._logger.log_error(f"Shutdown hook failed: {type(e).__name__}: {e}")
316
+
317
+ self._logger.log_info("Closing HTTP session...")
318
+ await self._http.close()
319
+
320
+ # close each connection or shard
321
+ for shard in self.shards:
322
+ await shard.close_ws()
323
+
324
+ def run(self):
325
+ """User-facing entry point for starting the client."""
326
+
327
+ try:
328
+ asyncio.run(self._start())
329
+ except KeyboardInterrupt:
330
+ self._logger.log_info("Shutdown requested via KeyboardInterrupt.")
331
+ except Exception as e:
332
+ self._logger.log_error(f"{type(e).__name__} {e}")
333
+ finally:
334
+ self._logger.log_high_priority("Bot shutting down.")
335
+ self._logger.close()
@@ -5,10 +5,17 @@ from .http import HTTPClient
5
5
  from .logger import Logger
6
6
 
7
7
  class ClientLike(Protocol):
8
- """Exposes a common interface for [`Client`][discord.client.Client]."""
8
+ """Exposes a common interface for [`Client`][scurrypy.client.Client]."""
9
+
10
+ token: str
11
+ """Bot's token."""
12
+
9
13
  application_id: int
10
14
  """Bot's application ID."""
11
15
 
16
+ intents: int
17
+ """Bot intents for listening to events."""
18
+
12
19
  config: BaseConfig
13
20
  """User-defined config."""
14
21
 
@@ -0,0 +1,205 @@
1
+ import fnmatch
2
+
3
+ from ..client_like import ClientLike
4
+
5
+ from ..events.interaction_events import ApplicationCommandData, MessageComponentData, ModalData, InteractionEvent
6
+ from ..resources.interaction import Interaction, InteractionDataTypes
7
+ from ..parts.command import SlashCommand, MessageCommand, UserCommand
8
+
9
+ class InteractionTypes:
10
+ """Interaction types constants."""
11
+
12
+ APPLICATION_COMMAND = 2
13
+ """Slash command interaction."""
14
+
15
+ MESSAGE_COMPONENT = 3
16
+ """Message component interaction (e.g., button, select menu, etc.)."""
17
+
18
+ MODAL_SUBMIT = 5
19
+ """Modal submit interaction."""
20
+
21
+ class CommandDispatcher:
22
+ """Central hub for registering and dispatching interaction responses."""
23
+
24
+ RESOURCE_MAP = { # maps discord events to their respective dataclass
25
+ InteractionTypes.APPLICATION_COMMAND: ApplicationCommandData,
26
+ InteractionTypes.MESSAGE_COMPONENT: MessageComponentData,
27
+ InteractionTypes.MODAL_SUBMIT: ModalData
28
+ }
29
+ """Maps [`InteractionTypes`][scurrypy.dispatch.command_dispatcher.InteractionTypes] to their respective dataclass."""
30
+
31
+ def __init__(self, client: ClientLike):
32
+
33
+ self.application_id = client.application_id
34
+ """Bot's application ID."""
35
+
36
+ self.bot = client
37
+ """Bot session for user access to bot."""
38
+
39
+ self._http = client._http
40
+ """HTTP session for requests."""
41
+
42
+ self._logger = client._logger
43
+ """Logger instance to log events."""
44
+
45
+ self._global_commands = []
46
+ """List of all Global commands."""
47
+
48
+ self._guild_commands = {}
49
+ """Guild commands mapped by guild ID."""
50
+
51
+ self.component_handlers = {}
52
+ """Mapping of component custom IDs to handler."""
53
+
54
+ self.slash_handlers = {}
55
+ """Mapping of command names to handler."""
56
+
57
+ self.message_handlers = {}
58
+ """Mapping of message command names to handler."""
59
+
60
+ self.user_handlers = {}
61
+ """Mapping of user command names to handler."""
62
+
63
+ async def register_guild_commands(self):
64
+ """Registers commands at the guild level."""
65
+
66
+ for guild_id, cmds in self._guild_commands.items():
67
+ # register commands PER GUILD
68
+ await self._http.request(
69
+ 'PUT',
70
+ f"applications/{self.application_id}/guilds/{guild_id}/commands",
71
+ data=[command.to_dict() for command in cmds]
72
+ )
73
+
74
+ async def register_global_commands(self):
75
+ """Registers a command at the global/bot level. (ALL GUILDS)"""
76
+
77
+ await self._http.request(
78
+ 'PUT',
79
+ f"applications/{self.application_id}/commands",
80
+ data=[command.to_dict() for command in self._global_commands]
81
+ )
82
+
83
+ def _queue_command(self, command: SlashCommand | UserCommand | MessageCommand, guild_ids: list[int] = None):
84
+ """Queue a command to be registered by Discord.
85
+
86
+ Args:
87
+ command (SlashCommand | UserCommand | MessageCommand): the command object
88
+ guild_ids (list[int], optional): guild IDs to register command to (if any)
89
+ """
90
+
91
+ if guild_ids:
92
+ gids = [guild_ids] if isinstance(guild_ids, int) else guild_ids
93
+ for gid in gids:
94
+ self._guild_commands.setdefault(gid, []).append(command)
95
+ else:
96
+ self._global_commands.append(command)
97
+
98
+ def clear_commands(self, guild_ids: list[int] = None):
99
+ """Clear a guild's or global commands (all types).
100
+
101
+ Args:
102
+ guild_ids (list[int], optional): guild IDs to register command to (if any)
103
+ """
104
+ if guild_ids:
105
+ gids = [guild_ids] if isinstance(guild_ids, int) else guild_ids
106
+ for gid in gids:
107
+ removed = self._guild_commands.pop(gid, None)
108
+ if removed is None:
109
+ self._logger.log_warn(f"Guild ID {gid} not found; skipping...")
110
+ else:
111
+ self._global_commands.clear()
112
+
113
+ def add_slash_command(self, command: SlashCommand, handler, guild_ids: list[int]):
114
+ """Add a slash command to be registered by Discord.
115
+
116
+ Args:
117
+ command (SlashCommand): the command object
118
+ handler (callable): user-defined callback when this command is invoked
119
+ guild_ids (list[int]): guild IDs to register command to (if any)
120
+ """
121
+ self.slash_handlers[command.name] = handler
122
+ self._queue_command(command, guild_ids)
123
+
124
+ def add_message_command(self, command: MessageCommand, handler, guild_ids: list[int]):
125
+ """Add a slash command to be registered by Discord.
126
+
127
+ Args:
128
+ command (MessageCommand): the command object
129
+ handler (callable): user-defined callback when this command is invoked
130
+ guild_ids (list[int]): guild IDs to register command to (if any)
131
+ """
132
+ self.message_handlers[command.name] = handler
133
+ self._queue_command(command, guild_ids)
134
+
135
+ def add_user_command(self, command: UserCommand, handler, guild_ids: list[int]):
136
+ """Add a user command to be registered by Discord.
137
+
138
+ Args:
139
+ command (UserCommand): the command object
140
+ handler (callable): user-defined callback when this command is invoked
141
+ guild_ids (list[int]): guild IDs to register command to (if any)
142
+ """
143
+ self.user_handlers[command.name] = handler
144
+ self._queue_command(command, guild_ids)
145
+
146
+ def component(self, func, custom_id: str):
147
+ """Decorator to register component interactions.
148
+
149
+ Args:
150
+ custom_id (str): Identifier of the component
151
+ !!! warning "Important"
152
+ Must match the `custom_id` set where the component was created.
153
+ """
154
+ self.component_handlers[custom_id] = func
155
+
156
+ def _get_handler(self, name: str):
157
+ """Helper function for fetching a handler by `fnmatch`."""
158
+ for k, v in self.component_handlers.items():
159
+ if fnmatch.fnmatch(name, k) == True:
160
+ return v
161
+ return False
162
+
163
+ async def dispatch(self, data: dict):
164
+ """Dispatch a response to an `INTERACTION_CREATE` event
165
+
166
+ Args:
167
+ data (dict): interaction data
168
+ """
169
+ event = InteractionEvent(interaction=Interaction.from_dict(data, self._http))
170
+
171
+ event_data_obj = self.RESOURCE_MAP.get(event.interaction.type)
172
+
173
+ if not event_data_obj:
174
+ return
175
+
176
+ event.data = event_data_obj.from_dict(data.get('data'))
177
+ handler = None
178
+ name = None
179
+
180
+ match event.interaction.type:
181
+ case InteractionTypes.APPLICATION_COMMAND:
182
+ name = event.data.name
183
+
184
+ match event.data.type:
185
+ case InteractionDataTypes.SLASH_COMMAND:
186
+ handler = self.slash_handlers.get(name)
187
+ case InteractionDataTypes.USER_COMMAND:
188
+ handler = self.user_handlers.get(name)
189
+ case InteractionDataTypes.MESSAGE_COMMAND:
190
+ handler = self.message_handlers.get(name)
191
+
192
+ # BOTH modals and message components have custom IDs!
193
+ case InteractionTypes.MESSAGE_COMPONENT | InteractionTypes.MODAL_SUBMIT:
194
+ name = event.data.custom_id
195
+ handler = self._get_handler(name)
196
+
197
+ if not handler:
198
+ self._logger.log_warn(f"No handler registered for interaction '{name}'")
199
+ return
200
+
201
+ try:
202
+ await handler(self.bot, event)
203
+ self._logger.log_info(f"Interaction Event '{name}' Acknowledged.")
204
+ except Exception as e:
205
+ self._logger.log_error(f"Error in interaction '{name}': {e}")
@@ -6,33 +6,33 @@ from ..resources.message import Message
6
6
  class EventDispatcher:
7
7
  """Central hub for handling Discord Gateway events."""
8
8
 
9
- RESOURCE_MAP = { # maps discord events to their respective dataclass (lazy loading)
10
- 'READY': ('discord.events.ready_event', 'ReadyEvent'),
9
+ RESOURCE_MAP = {
10
+ 'READY': ('scurrypy.events.ready_event', 'ReadyEvent'),
11
11
 
12
- 'GUILD_CREATE': ('discord.events.guild_events', 'GuildCreateEvent'),
13
- 'GUILD_UPDATE': ('discord.events.guild_events', 'GuildUpdateEvent'),
14
- 'GUILD_DELETE': ('discord.events.guild_events', 'GuildDeleteEvent'),
12
+ 'GUILD_CREATE': ('scurrypy.events.guild_events', 'GuildCreateEvent'),
13
+ 'GUILD_UPDATE': ('scurrypy.events.guild_events', 'GuildUpdateEvent'),
14
+ 'GUILD_DELETE': ('scurrypy.events.guild_events', 'GuildDeleteEvent'),
15
15
 
16
- 'CHANNEL_CREATE': ('discord.events.channel_events', 'GuildChannelCreateEvent'),
17
- 'CHANNEL_UPDATE': ('discord.events.channel_events', 'GuildChannelUpdateEvent'),
18
- 'CHANNEL_DELETE': ('discord.events.channel_events', 'GuildChannelDeleteEvent'),
19
- 'CHANNEL_PINS_UPDATE': ('discord.events.channel_events', 'ChannelPinsUpdateEvent'),
16
+ 'CHANNEL_CREATE': ('scurrypy.events.channel_events', 'GuildChannelCreateEvent'),
17
+ 'CHANNEL_UPDATE': ('scurrypy.events.channel_events', 'GuildChannelUpdateEvent'),
18
+ 'CHANNEL_DELETE': ('scurrypy.events.channel_events', 'GuildChannelDeleteEvent'),
19
+ 'CHANNEL_PINS_UPDATE': ('scurrypy.events.channel_events', 'ChannelPinsUpdateEvent'),
20
20
 
21
- 'MESSAGE_CREATE': ('discord.events.message_events', 'MessageCreateEvent'),
22
- 'MESSAGE_UPDATE': ('discord.events.message_events', 'MessageUpdateEvent'),
23
- 'MESSAGE_DELETE': ('discord.events.message_events', 'MessageDeleteEvent'),
21
+ 'MESSAGE_CREATE': ('scurrypy.events.message_events', 'MessageCreateEvent'),
22
+ 'MESSAGE_UPDATE': ('scurrypy.events.message_events', 'MessageUpdateEvent'),
23
+ 'MESSAGE_DELETE': ('scurrypy.events.message_events', 'MessageDeleteEvent'),
24
24
 
25
- 'MESSAGE_REACTION_ADD': ('discord.events.reaction_events', 'ReactionAddEvent'),
26
- 'MESSAGE_REACTION_REMOVE': ('discord.events.reaction_events', 'ReactionRemoveEvent'),
27
- 'MESSAGE_REACTION_REMOVE_ALL': ('discord.events.reaction_events', 'ReactionRemoveAllEvent'),
28
- 'MESSAGE_REACTION_REMOVE_EMOJI': ('discord.events.reaction_events', 'ReactionRemoveEmojiEvent'),
25
+ 'MESSAGE_REACTION_ADD': ('scurrypy.events.reaction_events', 'ReactionAddEvent'),
26
+ 'MESSAGE_REACTION_REMOVE': ('scurrypy.events.reaction_events', 'ReactionRemoveEvent'),
27
+ 'MESSAGE_REACTION_REMOVE_ALL': ('scurrypy.events.reaction_events', 'ReactionRemoveAllEvent'),
28
+ 'MESSAGE_REACTION_REMOVE_EMOJI': ('scurrypy.events.reaction_events', 'ReactionRemoveEmojiEvent'),
29
29
 
30
30
  # and other events...
31
31
  }
32
-
33
- """Mapping of event names to respective dataclass."""
32
+ """Mapping of event names to respective dataclass. (lazy loading)"""
34
33
 
35
34
  def __init__(self, client: ClientLike):
35
+
36
36
  self.application_id = client.application_id
37
37
  """Bot's ID."""
38
38
 
@@ -43,7 +43,7 @@ class EventDispatcher:
43
43
  """HTTP session for requests."""
44
44
 
45
45
  self._logger = client._logger
46
- """HTTP session for requests"""
46
+ """Logger instance to log events."""
47
47
 
48
48
  self.config = client.config
49
49
  """User-defined bot config for persistent data."""
@@ -77,12 +77,12 @@ class EventDispatcher:
77
77
 
78
78
  module = importlib.import_module(module_name)
79
79
  if not module:
80
- print(f"Cannot find module '{module_name}'!")
80
+ self._logger.log_error(f"Cannot find module '{module_name}'!")
81
81
  return
82
82
 
83
83
  cls = getattr(module, class_name)
84
84
  if not cls:
85
- print(f"Cannot find class '{class_name}'!")
85
+ self._logger.log_error(f"Cannot find class '{class_name}'!")
86
86
  return
87
87
 
88
88
  if isinstance(cls, Message) and cls.author.id == self.application_id: