scurrypy 0.3.0__py3-none-any.whl → 0.3.3__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.

Potentially problematic release.


This version of scurrypy might be problematic. Click here for more details.

discord/__init__.py CHANGED
@@ -1,10 +1,223 @@
1
1
  # discord
2
2
 
3
- from .logger import Logger
4
- from .client import Client
5
- from .intents import Intents, set_intents
6
- from .config import BaseConfig
7
-
8
- from .events import *
9
- from .resources import *
10
- from .parts import *
3
+ import importlib
4
+ from typing import TYPE_CHECKING
5
+
6
+ __all__ = [
7
+ # top-level
8
+ "Logger",
9
+ "Client",
10
+ "Intents",
11
+ "set_intents",
12
+ "BaseConfig",
13
+
14
+ # events
15
+ "ReadyEvent",
16
+ "ReactionAddEvent",
17
+ "ReactionRemoveEvent",
18
+ "ReactionRemoveEmojiEvent",
19
+ "ReactionRemoveAllEvent",
20
+ "GuildCreateEvent",
21
+ "GuildUpdateEvent",
22
+ "GuildDeleteEvent",
23
+ "MessageCreateEvent",
24
+ "MessageUpdateEvent",
25
+ "MessageDeleteEvent",
26
+ "GuildChannelCreateEvent",
27
+ "GuildChannelUpdateEvent",
28
+ "GuildChannelDeleteEvent",
29
+ "ChannelPinsUpdateEvent",
30
+ "InteractionEvent",
31
+
32
+ # models
33
+ "ApplicationModel",
34
+ "EmojiModel",
35
+ "GuildModel",
36
+ "MemberModel",
37
+ "UserModel",
38
+ "RoleModel",
39
+
40
+ # parts
41
+ "GuildChannel",
42
+ "Role",
43
+ "MessageBuilder",
44
+ "ModalBuilder",
45
+ "EmbedBuilder",
46
+ "ActionRow",
47
+ "StringSelect",
48
+ "UserSelect",
49
+ "RoleSelect",
50
+ "ChannelSelect",
51
+ "MentionableSelect",
52
+ "SlashCommand",
53
+ "MessageCommand",
54
+ "UserCommand",
55
+ "Container",
56
+ "Section",
57
+ "MediaGalleryItem",
58
+ "Label",
59
+
60
+ # resources
61
+ "Guild",
62
+ "Channel",
63
+ "Message",
64
+ "BotEmojis",
65
+ "User",
66
+ "Interaction",
67
+ "Application",
68
+ ]
69
+
70
+ # For editor support / autocomplete
71
+ if TYPE_CHECKING:
72
+ from .logger import Logger
73
+ from .client import Client
74
+ from .intents import Intents, set_intents
75
+ from .config import BaseConfig
76
+
77
+ # events
78
+ from .events.ready_event import ReadyEvent
79
+ from .events.reaction_events import (
80
+ ReactionAddEvent,
81
+ ReactionRemoveEvent,
82
+ ReactionRemoveEmojiEvent,
83
+ ReactionRemoveAllEvent,
84
+ )
85
+ from .events.guild_events import (
86
+ GuildCreateEvent,
87
+ GuildUpdateEvent,
88
+ GuildDeleteEvent,
89
+ )
90
+ from .events.message_events import (
91
+ MessageCreateEvent,
92
+ MessageUpdateEvent,
93
+ MessageDeleteEvent,
94
+ )
95
+ from .events.channel_events import (
96
+ GuildChannelCreateEvent,
97
+ GuildChannelUpdateEvent,
98
+ GuildChannelDeleteEvent,
99
+ ChannelPinsUpdateEvent,
100
+ )
101
+ from .events.interaction_events import InteractionEvent
102
+
103
+ # models
104
+ from .models.application import ApplicationModel
105
+ from .models.emoji import EmojiModel
106
+ from .models.guild import GuildModel
107
+ from .models.member import MemberModel
108
+ from .models.user import UserModel
109
+ from .models.role import RoleModel
110
+
111
+ # parts
112
+ from .parts.channel import GuildChannel
113
+ from .parts.role import Role
114
+ from .parts.message import MessageBuilder
115
+ from .parts.modal import ModalBuilder
116
+ from .parts.embed import EmbedBuilder
117
+ from .parts.action_row import (
118
+ ActionRow,
119
+ StringSelect,
120
+ UserSelect,
121
+ RoleSelect,
122
+ ChannelSelect,
123
+ MentionableSelect
124
+ )
125
+
126
+ from .parts.command import (
127
+ SlashCommand,
128
+ MessageCommand,
129
+ UserCommand
130
+ )
131
+
132
+ from .parts.components_v2 import (
133
+ Container,
134
+ Section,
135
+ MediaGalleryItem,
136
+ Label
137
+ )
138
+
139
+ # resources
140
+ from .resources.guild import Guild
141
+ from .resources.channel import Channel
142
+ from .resources.message import Message
143
+ from .resources.bot_emojis import BotEmojis
144
+ from .resources.user import User
145
+ from .resources.interaction import Interaction
146
+ from .resources.application import Application
147
+
148
+ # Lazy loader
149
+ def __getattr__(name: str):
150
+ if name not in __all__:
151
+ raise AttributeError(f"module {__name__} has no attribute {name}")
152
+
153
+ mapping = {
154
+ # top-level
155
+ "Logger": "discord.logger",
156
+ "Client": "discord.client",
157
+ "Intents": "discord.intents",
158
+ "set_intents": "discord.intents",
159
+ "BaseConfig": "discord.config",
160
+
161
+ # events
162
+ "ReadyEvent": "discord.events.ready_event",
163
+ "ReactionAddEvent": "discord.events.reaction_events",
164
+ "ReactionRemoveEvent": "discord.events.reaction_events",
165
+ "ReactionRemoveEmojiEvent": "discord.events.reaction_events",
166
+ "ReactionRemoveAllEvent": "discord.events.reaction_events",
167
+ "GuildCreateEvent": "discord.events.guild_events",
168
+ "GuildUpdateEvent": "discord.events.guild_events",
169
+ "GuildDeleteEvent": "discord.events.guild_events",
170
+ "MessageCreateEvent": "discord.events.message_events",
171
+ "MessageUpdateEvent": "discord.events.message_events",
172
+ "MessageDeleteEvent": "discord.events.message_events",
173
+ "GuildChannelCreateEvent": "discord.events.channel_events",
174
+ "GuildChannelUpdateEvent": "discord.events.channel_events",
175
+ "GuildChannelDeleteEvent": "discord.events.channel_events",
176
+ "ChannelPinsUpdateEvent": "discord.events.channel_events",
177
+ "InteractionEvent": "discord.events.interaction_events",
178
+
179
+ # models
180
+ 'ApplicationModel': "discord.models.application",
181
+ 'EmojiModel': "discord.models.emoji",
182
+ 'GuildModel': "discord.models.guild",
183
+ 'MemberModel': "discord.models.member",
184
+ 'UserModel': "discord.models.user",
185
+ 'RoleModel': "discord.models.role",
186
+
187
+ # parts
188
+ 'GuildChannel': "discord.parts.channel",
189
+ 'Role': "discord.parts.role",
190
+ 'MessageBuilder': "discord.parts.message",
191
+ 'ModalBuilder': "discord.parts.modal",
192
+ 'EmbedBuilder': "discord.parts.embed",
193
+ 'ActionRow': "discord.parts.action_row",
194
+ 'StringSelect': "discord.parts.action_row",
195
+ 'UserSelect': "discord.parts.action_row",
196
+ 'RoleSelect': "discord.parts.action_row",
197
+ 'ChannelSelect': "discord.parts.action_row",
198
+ 'MentionableSelect': "discord.parts.action_row",
199
+ 'SlashCommand': "discord.parts.command",
200
+ 'MessageCommand': "discord.parts.command",
201
+ 'UserCommand': "discord.parts.command",
202
+ 'Container': "discord.parts.components_v2",
203
+ 'Section': "discord.parts.components_v2",
204
+ 'MediaGalleryItem': "discord.parts.components_v2",
205
+ 'Label': "discord.parts.components_v2",
206
+
207
+ # resources
208
+ 'Guild': "discord.resources.guild",
209
+ 'Channel': "discord.resources.channel",
210
+ 'Message': "discord.resources.message",
211
+ 'BotEmojis': "discord.resources.bot_emojis",
212
+ 'User': "discord.resources.user",
213
+ 'Interaction': "discord.resources.interaction",
214
+ 'Application': "discord.resources.application"
215
+ }
216
+
217
+ module = importlib.import_module(mapping[name])
218
+ attr = getattr(module, name)
219
+ globals()[name] = attr # cache it for future lookups
220
+ return attr
221
+
222
+ def __dir__():
223
+ return sorted(list(globals().keys()) + __all__)
discord/client.py CHANGED
@@ -1,26 +1,10 @@
1
- import asyncio
2
-
3
- from .logger import Logger
4
- from .gateway import GatewayClient
5
- from .http import HTTPClient
1
+ from .config import BaseConfig
6
2
  from .intents import Intents
7
3
  from .error import DiscordError
8
- from .config import BaseConfig
9
4
  from .client_like import ClientLike
10
5
 
11
- from .resources.guild import Guild
12
- from .resources.channel import Channel
13
- from .resources.message import Message
14
- from .resources.bot_emojis import BotEmojis
15
- from .resources.user import User
16
- from .resources.application import Application
17
-
18
6
  from .parts.command import SlashCommand, MessageCommand, UserCommand
19
7
 
20
- from .dispatch.event_dispatcher import EventDispatcher
21
- from .dispatch.prefix_dispatcher import PrefixDispatcher
22
- from .dispatch.command_dispatcher import CommandDispatcher
23
-
24
8
  class Client(ClientLike):
25
9
  """Main entry point for Discord bots.
26
10
  Ties together the moving parts: gateway, HTTP, event dispatching, command handling, and resource managers.
@@ -45,6 +29,14 @@ class Client(ClientLike):
45
29
  prefix (str, optional): set message prefix if using command prefixes
46
30
  quiet (bool, optional): if INFO, DEBUG, and WARN should be logged
47
31
  """
32
+ from .logger import Logger
33
+ from .gateway import GatewayClient
34
+ from .http import HTTPClient
35
+ from .resources.bot_emojis import BotEmojis
36
+ from .dispatch.event_dispatcher import EventDispatcher
37
+ from .dispatch.prefix_dispatcher import PrefixDispatcher
38
+ from .dispatch.command_dispatcher import CommandDispatcher
39
+
48
40
  self.token = token
49
41
  self.application_id = application_id
50
42
  self.config = config
@@ -100,13 +92,13 @@ class Client(ClientLike):
100
92
  def decorator(func):
101
93
  # hash out command type
102
94
  if isinstance(command, MessageCommand):
103
- self.command_dispatcher.message_command(func)
95
+ self.command_dispatcher.message_command(command.name, func)
104
96
  elif isinstance(command, UserCommand):
105
- self.command_dispatcher.user_command(func)
97
+ self.command_dispatcher.user_command(command.name, func)
106
98
  elif isinstance(command, SlashCommand):
107
- self.command_dispatcher.command(func)
99
+ self.command_dispatcher.command(command.name, func)
108
100
  else:
109
- raise ValueError(f'Command {func.__name__} expected to be of type SlashCommand, UserCommand, MessageCommand; \
101
+ raise ValueError(f'Command {command.name} expected to be of type SlashCommand, UserCommand, MessageCommand; \
110
102
  got {type(command).__name__}.')
111
103
 
112
104
  # then hash out if this command should be guild or global level
@@ -154,6 +146,8 @@ class Client(ClientLike):
154
146
  Returns:
155
147
  (Application): the Application resource
156
148
  """
149
+ from .resources.application import Application
150
+
157
151
  return Application(application_id, self._http)
158
152
 
159
153
  def guild_from_id(self, guild_id: int):
@@ -165,6 +159,8 @@ class Client(ClientLike):
165
159
  Returns:
166
160
  (Guild): the Guild resource
167
161
  """
162
+ from .resources.guild import Guild
163
+
168
164
  return Guild(guild_id, self._http)
169
165
 
170
166
  def channel_from_id(self, channel_id: int):
@@ -176,6 +172,8 @@ class Client(ClientLike):
176
172
  Returns:
177
173
  (Channel): the Channel resource
178
174
  """
175
+ from .resources.channel import Channel
176
+
179
177
  return Channel(channel_id, self._http)
180
178
 
181
179
  def message_from_id(self, channel_id: int, message_id: int):
@@ -188,6 +186,8 @@ class Client(ClientLike):
188
186
  Returns:
189
187
  (Message): the Message resource
190
188
  """
189
+ from .resources.message import Message
190
+
191
191
  return Message(message_id, channel_id, self._http)
192
192
 
193
193
  def user_from_id(self, user_id: int):
@@ -199,6 +199,8 @@ class Client(ClientLike):
199
199
  Returns:
200
200
  (User): the User resource
201
201
  """
202
+ from .resources.user import User
203
+
202
204
  return User(user_id, self._http)
203
205
 
204
206
  async def clear_guild_commands(self, guild_id: int):
@@ -215,6 +217,8 @@ class Client(ClientLike):
215
217
 
216
218
  async def _listen(self):
217
219
  """Main event loop for incoming gateway requests."""
220
+ import asyncio
221
+
218
222
  while True:
219
223
  try:
220
224
  message = await self._ws.receive()
@@ -270,6 +274,8 @@ class Client(ClientLike):
270
274
  """Runs the main lifecycle of the bot.
271
275
  Handles connection setup, heartbeat management, event loop, and automatic reconnects.
272
276
  """
277
+ import asyncio
278
+
273
279
  while True:
274
280
  try:
275
281
  await self._http.start_session()
@@ -338,6 +344,8 @@ class Client(ClientLike):
338
344
  Handles starting the session, WS, and heartbeat, reconnection logic,
339
345
  setting up emojis and hooks, and then listens for gateway events.
340
346
  """
347
+ import asyncio
348
+
341
349
  try:
342
350
  asyncio.run(self.start())
343
351
  except KeyboardInterrupt:
@@ -78,39 +78,42 @@ class CommandDispatcher:
78
78
 
79
79
  await self._http.request('PUT', f"applications/{self.application_id}/commands", global_commands)
80
80
 
81
- def command(self, handler):
81
+ def command(self, name: str, handler):
82
82
  """Decorator to register slash commands.
83
83
 
84
84
  Args:
85
+ name (str): name of the command to register
85
86
  handler (callable): callback handle for command response
86
87
  """
87
- self._handlers[handler.__name__] = handler
88
-
89
- def component(self, func, custom_id: str):
90
- """Decorator to register component interactions.
91
-
92
- Args:
93
- custom_id (str): Identifier of the component
94
- !!! warning "Important"
95
- Must match the `custom_id` set where the component was created.
96
- """
97
- self._component_handlers[custom_id] = func
88
+ self._handlers[name] = handler
98
89
 
99
- def user_command(self, handler):
90
+ def user_command(self, name: str, handler):
100
91
  """Decorator to register user commands.
101
92
 
102
93
  Args:
94
+ name (str): name of the command to register
103
95
  handler (callable): callback handle for user command response
104
96
  """
105
- self._user_handlers[handler.__name__] = handler
97
+ self._user_handlers[name] = handler
106
98
 
107
- def message_command(self, handler):
99
+ def message_command(self, name: str, handler):
108
100
  """Decorator to register message commands.
109
101
 
110
102
  Args:
103
+ name (str): name of the command to register
111
104
  handler (callable): callback handle for message command response
112
105
  """
113
- self._message_handlers[handler.__name__] = handler
106
+ self._message_handlers[name] = handler
107
+
108
+ def component(self, func, custom_id: str):
109
+ """Decorator to register component interactions.
110
+
111
+ Args:
112
+ custom_id (str): Identifier of the component
113
+ !!! warning "Important"
114
+ Must match the `custom_id` set where the component was created.
115
+ """
116
+ self._component_handlers[custom_id] = func
114
117
 
115
118
  async def dispatch(self, data: dict):
116
119
  """Dispatch a response to an `INTERACTION_CREATE` event
@@ -1,42 +1,19 @@
1
+ from importlib import import_module
1
2
  from ..client_like import ClientLike
2
3
 
3
- from ..events.ready_event import *
4
- from ..events.reaction_events import *
5
- from ..events.guild_events import *
6
- from ..events.message_events import *
7
- from ..events.channel_events import *
8
- from ..events.interaction_events import *
9
-
10
4
  from ..resources.message import Message
11
5
 
12
6
  class EventDispatcher:
13
7
  """Central hub for handling Discord Gateway events."""
14
- RESOURCE_MAP = { # maps discord events to their respective dataclass
15
- 'READY': ReadyEvent,
16
-
17
- "MESSAGE_CREATE": MessageCreateEvent,
18
- "MESSAGE_UPDATE": MessageUpdateEvent,
19
- 'MESSAGE_DELETE': MessageDeleteEvent,
20
-
21
- 'MESSAGE_REACTION_ADD': ReactionAddEvent,
22
- 'MESSAGE_REACTION_REMOVE': ReactionRemoveEvent,
23
- 'MESSAGE_REACTION_REMOVE_ALL': ReactionRemoveAllEvent,
24
- 'MESSAGE_REACTION_REMOVE_EMOJI': ReactionRemoveEmojiEvent,
25
-
26
- 'CHANNEL_CREATE': GuildChannelCreateEvent,
27
- 'CHANNEL_UPDATE': GuildChannelUpdateEvent,
28
- 'CHANNEL_DELETE': GuildChannelDeleteEvent,
29
8
 
30
- 'CHANNEL_PINS_UPDATE': ChannelPinsUpdateEvent,
31
-
32
- 'GUILD_CREATE': GuildCreateEvent,
33
- 'GUILD_UPDATE': GuildUpdateEvent,
34
- 'GUILD_DELETE': GuildDeleteEvent,
35
-
36
- 'INTERACTION_CREATE': InteractionEvent
9
+ RESOURCE_MAP = { # maps discord events to their respective dataclass (lazy loading)
10
+ 'READY': ('discord.events.ready_event', 'ReadyEvent'),
37
11
 
12
+ 'MESSAGE_CREATE': ('discord.events.message_events', 'MessageCreateEvent')
13
+
38
14
  # and other events...
39
15
  }
16
+
40
17
  """Mapping of event names to respective dataclass."""
41
18
 
42
19
  def __init__(self, client: ClientLike):
@@ -75,9 +52,21 @@ class EventDispatcher:
75
52
  event_name (str): name of the event
76
53
  data (dict): Discord's raw event payload
77
54
  """
78
- cls = self.RESOURCE_MAP.get(event_name)
55
+ module_info = self.RESOURCE_MAP.get(event_name)
56
+
57
+ if not module_info:
58
+ return
79
59
 
60
+ module_name, class_name = module_info
61
+
62
+ module = import_module(module_name)
63
+ if not module:
64
+ print(f"Cannot find module '{module_name}'!")
65
+ return
66
+
67
+ cls = getattr(module, class_name)
80
68
  if not cls:
69
+ print(f"Cannot find class '{class_name}'!")
81
70
  return
82
71
 
83
72
  if isinstance(cls, Message) and cls.author.id == self.application_id:
@@ -1,33 +1 @@
1
1
  # discord/events
2
-
3
- from .ready_event import ReadyEvent
4
-
5
- from .reaction_events import (
6
- ReactionAddEvent,
7
- ReactionRemoveEvent,
8
- ReactionRemoveEmojiEvent,
9
- ReactionRemoveAllEvent
10
- )
11
-
12
- from .guild_events import (
13
- GuildCreateEvent,
14
- GuildUpdateEvent,
15
- GuildDeleteEvent
16
- )
17
-
18
- from .message_events import (
19
- MessageCreateEvent,
20
- MessageUpdateEvent,
21
- MessageDeleteEvent
22
- )
23
-
24
- from .channel_events import (
25
- GuildChannelCreateEvent,
26
- GuildChannelUpdateEvent,
27
- GuildChannelDeleteEvent,
28
- ChannelPinsUpdateEvent
29
- )
30
-
31
- from .interaction_events import (
32
- InteractionEvent
33
- )
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from ..models import MemberModel
2
+ from ..models.member import MemberModel
3
3
  from ..resources.channel import Channel
4
4
 
5
5
  @dataclass
discord/logger.py CHANGED
@@ -1,6 +1,3 @@
1
- from datetime import datetime
2
- import copy
3
-
4
1
  class Logger:
5
2
  """A utility class for logging messages, supporting log levels, color-coded console output,
6
3
  optional file logging, and redaction of sensitive information.
@@ -51,6 +48,8 @@ class Logger:
51
48
  Returns:
52
49
  (str): timestamp formatted in YYYY-MM-DD HH:MM:SS
53
50
  """
51
+ from datetime import datetime
52
+
54
53
  return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
55
54
 
56
55
  def _log(self, level: str, color: str, message: str):
@@ -140,6 +139,8 @@ class Logger:
140
139
  return [_redact(item) for item in obj]
141
140
  return obj
142
141
 
142
+ import copy
143
+
143
144
  return _redact(copy.deepcopy(data))
144
145
 
145
146
  def close(self):
@@ -1,8 +1 @@
1
1
  # discord/models
2
-
3
- from .application import ApplicationModel
4
- from .emoji import EmojiModel
5
- from .guild import GuildModel
6
- from .member import MemberModel
7
- from .user import UserModel
8
- from .role import RoleModel
@@ -0,0 +1,26 @@
1
+ from dataclasses import dataclass
2
+ from ..model import DataModel
3
+
4
+ @dataclass
5
+ class InteractionCallbackDataModel(DataModel):
6
+ id: int
7
+ """ID of the interaction."""
8
+
9
+ type: int
10
+ """Type of interaction."""
11
+
12
+ activity_instance_id: str
13
+ """Instance ID of activity if an activity was launched or joined."""
14
+
15
+ response_message_id: int
16
+ """ID of the message created by the interaction."""
17
+
18
+ response_message_loading: bool
19
+ """If the interaction is in a loading state."""
20
+
21
+ response_message_ephemeral: bool
22
+ """If the interaction is ephemeral."""
23
+
24
+ @dataclass
25
+ class InteractionCallbackModel(DataModel):
26
+ interaction: InteractionCallbackDataModel
discord/parts/__init__.py CHANGED
@@ -1,28 +1,2 @@
1
1
  # discord/parts
2
2
 
3
- from .channel import GuildChannel
4
- from .role import Role
5
- from .message import MessageBuilder
6
- from .modal import ModalBuilder
7
- from .embed import EmbedBuilder
8
- from .action_row import (
9
- ActionRow,
10
- StringSelect,
11
- UserSelect,
12
- RoleSelect,
13
- ChannelSelect,
14
- MentionableSelect
15
- )
16
-
17
- from .command import (
18
- SlashCommand,
19
- MessageCommand,
20
- UserCommand
21
- )
22
-
23
- from .components_v2 import (
24
- Container,
25
- Section,
26
- MediaGalleryItem,
27
- Label
28
- )
discord/parts/message.py CHANGED
@@ -1,4 +1,3 @@
1
- import os
2
1
  from dataclasses import dataclass, field
3
2
  from typing import Optional, TypedDict, Unpack, Literal
4
3
  from discord.model import DataModel
@@ -114,6 +113,8 @@ class MessageBuilder(DataModel):
114
113
  Returns:
115
114
  (MessageBuilder): self
116
115
  """
116
+ import os
117
+
117
118
  self.attachments.append(
118
119
  _Attachment(
119
120
  id=len(self.attachments),
@@ -1,10 +1 @@
1
1
  # discord/resources
2
-
3
- from .guild import Guild
4
- from .channel import Channel
5
- from .message import Message
6
- from .bot_emojis import BotEmojis
7
- from .user import User
8
- from .interaction import Interaction
9
- from .application import Application
10
- from .interaction import Interaction
@@ -1,5 +1,5 @@
1
1
  from dataclasses import dataclass
2
- from typing import TypedDict, Unpack, Optional
2
+ from typing import TypedDict, Unpack, Optional, Literal
3
3
 
4
4
  from ..http import HTTPClient
5
5
  from ..model import DataModel
@@ -32,6 +32,15 @@ class PinsFetchParams(TypedDict, total=False):
32
32
  limit: int
33
33
  """Max number of pinned messages to return. Range 1 - 50. Default 50."""
34
34
 
35
+ class ThreadFromMessageParams(TypedDict, total=False):
36
+ """Params when attaching a thread to a message."""
37
+
38
+ rate_limit_per_user: Literal[60, 1440, 4320, 10080]
39
+ """time (minutes) of inactivity before thread is archived."""
40
+
41
+ rate_limit_per_user: int
42
+ """time (seconds) user waits before sending another message."""
43
+
35
44
  @dataclass
36
45
  class PinnedMessage(DataModel):
37
46
  """Pinned message data."""
@@ -160,6 +169,26 @@ class Channel(DataModel):
160
169
 
161
170
  return self
162
171
 
172
+ async def create_thread_from_message(self, message_id: int, name: str, **kwargs: Unpack[ThreadFromMessageParams]):
173
+ """Create a thread from this message
174
+
175
+ Args:
176
+ message_id: ID of message to attach thread
177
+ name (str): thread name
178
+
179
+ Returns:
180
+ (Channel): The updated channel object
181
+ """
182
+
183
+ content = {
184
+ 'name': name,
185
+ **kwargs
186
+ }
187
+
188
+ data = await self._http.request('POST', f"channels/{self.id}/messages/{message_id}/threads", data=content)
189
+
190
+ return Channel.from_dict(data, self._http)
191
+
163
192
  async def fetch_pins(self, **kwargs: Unpack[PinsFetchParams]):
164
193
  """Get this channel's pinned messages.
165
194
 
@@ -6,11 +6,10 @@ from ..model import DataModel
6
6
 
7
7
  from ..parts.modal import ModalBuilder
8
8
  from ..parts.message import *
9
- from ..parts.embed import *
10
- from ..parts.component_types import *
11
9
 
12
10
  from ..models.guild import GuildModel
13
11
  from ..models.member import MemberModel
12
+ from ..models.interaction import InteractionCallbackModel
14
13
 
15
14
  from .channel import Channel
16
15
 
@@ -98,11 +97,12 @@ class Interaction(DataModel):
98
97
  channel: Optional[Channel] = None
99
98
  """Partial channel object the interaction was invoked."""
100
99
 
101
- async def respond(self, message: str | MessageBuilder, **flags: Unpack[MessageFlagParams]):
100
+ async def respond(self, message: str | MessageBuilder, with_response: bool = False, **flags: Unpack[MessageFlagParams]):
102
101
  """Create a message in response to an interaction.
103
102
 
104
103
  Args:
105
104
  message (str | MessageBuilder): content as a string or from MessageBuilder
105
+ with_response (bool, optional): if the interaction data should be returned. Defaults to False.
106
106
  """
107
107
  if isinstance(message, str):
108
108
  message = MessageBuilder(content=message).set_flags(**flags)
@@ -112,12 +112,18 @@ class Interaction(DataModel):
112
112
  'data': message._to_dict()
113
113
  }
114
114
 
115
- await self._http.request(
115
+ params = {'with_response': with_response}
116
+
117
+ data = await self._http.request(
116
118
  'POST',
117
119
  f'/interactions/{self.id}/{self.token}/callback',
118
120
  content,
119
- files=[fp.path for fp in message.attachments])
121
+ files=[fp.path for fp in message.attachments],
122
+ params=params)
120
123
 
124
+ if with_response:
125
+ return InteractionCallbackModel.from_dict(data, self._http)
126
+
121
127
  async def update(self, message: str | MessageBuilder, **flags: Unpack[MessageFlagParams]):
122
128
  """Update a message in response to an interaction.
123
129
 
@@ -0,0 +1,92 @@
1
+ Metadata-Version: 2.4
2
+ Name: scurrypy
3
+ Version: 0.3.3
4
+ Summary: Discord API Wrapper in Python
5
+ Author: Furmissile
6
+ Requires-Python: >=3.10
7
+ Description-Content-Type: text/markdown
8
+ License-File: LICENSE
9
+ Dynamic: license-file
10
+
11
+ # __Welcome to ScurryPy__
12
+
13
+ [![PyPI version](https://badge.fury.io/py/scurrypy.svg)](https://badge.fury.io/py/scurrypy)
14
+
15
+ Yet another Discord API wrapper in Python!
16
+
17
+ While this wrapper is mainly used for various squirrel-related shenanigans, it can also be used for more generic bot purposes.
18
+
19
+ ## Features
20
+ * Command and event handling
21
+ * Declarative style using decorators
22
+ * Supports both legacy and new features
23
+ * Respects Discord's rate limits
24
+
25
+ ## Some things to consider...
26
+ * This is an early version — feedback, ideas, and contributions are very welcome! That said, there may be bumps along the way, so expect occasional bugs and quirks.
27
+ * Certain features are not yet supported, while others are intentionally omitted. See the [docs](https://furmissile.github.io/scurrypy) for full details.
28
+
29
+ ## Getting Started
30
+ *Note: This section also appears in the documentation, but here are complete examples ready to use with your bot credentials.*
31
+
32
+ ### Installation
33
+ To install the ScurryPy package, run:
34
+ ```bash
35
+ pip install scurrypy
36
+ ```
37
+
38
+ ### Minimal Slash Command
39
+ The following demonstrates building and responding to a slash command.
40
+
41
+ *Note: Adjust `dotenv_path` if your `.env` file is not in the same directory as this script.*
42
+
43
+ ```python
44
+ import discord, os
45
+ from dotenv import load_dotenv
46
+
47
+ load_dotenv(dotenv_path='./path/to/env')
48
+
49
+ client = discord.Client(
50
+ token=os.getenv("DISCORD_TOKEN"),
51
+ application_id=APPLICATION_ID # replace with your bot's user ID
52
+ )
53
+
54
+ @client.command(
55
+ command=discord.SlashCommand(
56
+ name='example',
57
+ description='Demonstrate the minimal slash command!'
58
+ ),
59
+ guild_id=GUILD_ID # must be a guild ID your bot is in
60
+ )
61
+ async def example(bot: discord.Client, event: discord.InteractionEvent):
62
+ await event.interaction.respond(f'Hello, {event.interaction.member.user.username}!')
63
+
64
+ client.run()
65
+ ```
66
+
67
+ ### Minimal Prefix Command (Legacy)
68
+ The following demonstrates building and responding to a message prefix command.
69
+
70
+ ```python
71
+ import discord, os
72
+ from dotenv import load_dotenv
73
+
74
+ load_dotenv(dotenv_path='./path/to/env')
75
+
76
+ client = discord.Client(
77
+ token=os.getenv("DISCORD_TOKEN"),
78
+ application_id=APPLICATION_ID, # replace with your bot's user ID
79
+ intents=discord.set_intents(message_content=True),
80
+ prefix='!' # your custom prefix
81
+ )
82
+
83
+ @client.prefix_command
84
+ async def ping(bot: discord.Client, event: discord.MessageCreateEvent):
85
+ # The function name is the name of the command
86
+ await event.message.send("Pong!")
87
+
88
+ client.run()
89
+ ```
90
+
91
+ ## Like what you see?
92
+ Check out the full [documentation](https://furmissile.github.io/scurrypy) for more examples, guides, and API reference!
@@ -1,34 +1,35 @@
1
- discord/__init__.py,sha256=DPE5laK7mMQPeF2ky8D8QpokCDqsGlq6slHHhbNXmso,217
2
- discord/client.py,sha256=XJAxnfmwm16vfgKUZ15aFMXYHvK2WGVFQoJJ7vH2zMs,13353
1
+ discord/__init__.py,sha256=cETkxHmm0s9YkSJgn-1daQhnbL96fuD7L9SIg2t5vBg,6823
2
+ discord/client.py,sha256=v-phlPbUe1Rr-zg5Posbw_jyv8GxvwmvNbjgAjCZW1Y,13566
3
3
  discord/client_like.py,sha256=JyJq0XBq0vKuPBJ_ZnYf5yAAuX1zz_2B1TZBQE-BYbQ,473
4
4
  discord/config.py,sha256=OH1A2mNKhDlGvQYASEsVUx2pNxP1YQ2a7a7z-IM5xFg,200
5
5
  discord/error.py,sha256=AlislRTna554cM6KC0KrwKugzYDYtx_9C8_3QFe4XDc,2070
6
6
  discord/gateway.py,sha256=H_WaUrpK8rl3rGlT3qNexpru7R6O6Y6GQPkQcDt_KX8,6555
7
7
  discord/http.py,sha256=cGFhGEeNebf1sgSUB4Xfnlj00twWLFi9AwO85gAAUDA,10955
8
8
  discord/intents.py,sha256=Lf2fogFFDqilZeKJv7tcUgKmMW3D7ykK4bBNi-zDzYA,2866
9
- discord/logger.py,sha256=GBcvldIrSBnwNSgus-oa1NsYV8hU7f8_4J4VX_GmkxA,4700
9
+ discord/logger.py,sha256=qAmOc3geCcCCqPhdi61SVWzMDewmM8Q_KWhTcjO46j8,4726
10
10
  discord/model.py,sha256=CmuxyoWLWokE_UvCQ9M7U9Cr7JH9R7ULMv9KMwzXjDQ,3105
11
11
  discord/dispatch/__init__.py,sha256=m7ixrbNhOV9QRORXPw6LSwxofQMAvLmPFBweBZu9ACc,20
12
- discord/dispatch/command_dispatcher.py,sha256=_91l4oJQ8LhMnxdVPzkdmDSv6AWznekNODtzudCQvrk,5740
13
- discord/dispatch/event_dispatcher.py,sha256=aPyfstFdIx510P_gKAExeZOICZooRgUdL19Adb19SPY,3085
12
+ discord/dispatch/command_dispatcher.py,sha256=pyJOQaZLZYrHUEs6HEWp8XMKTMZX4SBrwTizGKIeUG8,5904
13
+ discord/dispatch/event_dispatcher.py,sha256=tgU5FXJT2NAluT0aIlMhtMjz6hHcJRN6us1vAG639RU,2612
14
14
  discord/dispatch/prefix_dispatcher.py,sha256=4mkn3cuXTjdEChbewkbZQqd_sMKm4jePSFKKOPbt12g,2065
15
- discord/events/__init__.py,sha256=BcmwAj84-nzMZEkip-V903jD0ZCKqhySzlAx5wDwJoo,630
15
+ discord/events/__init__.py,sha256=xE8YtJ7NKZkm7MLnohDQIbezh3ColmLR-3BMiZabt3k,18
16
16
  discord/events/channel_events.py,sha256=NJbu7oXRdtkUQZ68XbX6fBXINaJmavAkNZStFVA3rzs,1318
17
- discord/events/guild_events.py,sha256=uGGdz6YZpMSXjAZuwJ6zslmUOnIrVcBArVWc8TXnUS8,845
17
+ discord/events/guild_events.py,sha256=HA4-loifH5x3j-zAif3H5sJpKX_HbZz4ORgBFvPMe70,852
18
18
  discord/events/hello_event.py,sha256=O8Ketu_N943cnGaFkGsAHfWhgKXFQCYCqSD3EqdsXjA,225
19
19
  discord/events/interaction_events.py,sha256=5hlYOsV1ROiRXISAGCKcZo8vfk6oqiZcNzzZjSteiag,4361
20
20
  discord/events/message_events.py,sha256=uovC_n73LuERwjqJHsXqW-IktuIzQTfolQSAzYzlzpc,1411
21
21
  discord/events/reaction_events.py,sha256=Mwj3sUWDBl5YKIs4URylXqsrLyMhtQk7d-hF1mYhFbo,2568
22
22
  discord/events/ready_event.py,sha256=c3Pf4ndNYV2byuliADi8pUxpuvKXa9FLKNz_uzIWGso,794
23
- discord/models/__init__.py,sha256=T6C5C8RdkEPurA_zQas5riq3nrThMaSWl9Yasha0xXc,216
23
+ discord/models/__init__.py,sha256=ZKhFO5eX4GbTRdvi4CU4z2hO-HQU29WZw2x4DujvARY,18
24
24
  discord/models/application.py,sha256=2sXtRysUc2TJ40FjdcrWgosmwMrp_h3ybddubQMixKM,924
25
25
  discord/models/emoji.py,sha256=6iz1DhWj_eTUj2KHmwMewjB3AdEBm68EmIZp2WFFCQg,932
26
26
  discord/models/guild.py,sha256=aXUByOUUIGt9d2qIGC6_X_vh0Nyib8Iqj5ZElBeNV_I,819
27
27
  discord/models/integration.py,sha256=V29RO2925mQbVXPAMt665cML3m8mJACYmdJLWwbbUyE,612
28
+ discord/models/interaction.py,sha256=VPbf49C1RmQpDSODk3e1voW8EnbVsH_w1qpmiq4hVRM,700
28
29
  discord/models/member.py,sha256=pkI-NVRMb3hUBkxI26FSYZxzx2mRNGXOeWWCw3BGGsY,705
29
30
  discord/models/role.py,sha256=erlERmK-IZz4YzSNY-XLNvCc-Z5PoVlClxPOX67dQJg,1169
30
31
  discord/models/user.py,sha256=lgG6GoU_7L68oHt6PGTzTkU1vrbsclRQzGjKzsLBeKA,298
31
- discord/parts/__init__.py,sha256=dxrdklnWRHYIbPi6KBvUcJTWky8oTHRyMQbh58_2cmA,511
32
+ discord/parts/__init__.py,sha256=yROb-BqEw-FKXqq_-0WbP33U-Arm_9NpJuEamXpvjeA,19
32
33
  discord/parts/action_row.py,sha256=PQ24Rr02QR4Sz1mypjM0C2gxYLH4Tsx7GvTorF4wsfQ,8264
33
34
  discord/parts/attachment.py,sha256=fhWpb5e0JKfbibcEc0EOiNrzqSSAUEL87NEhRmB1H34,388
34
35
  discord/parts/channel.py,sha256=2wmEjmRqUpORzL3CFp2rugMxrpSm_LxxvlcrmWIH4r4,584
@@ -36,19 +37,19 @@ discord/parts/command.py,sha256=CPyPO_T5ULp7j7syF9z2LztP3SF6KyX89sodz2c40Aw,2924
36
37
  discord/parts/component_types.py,sha256=qr1R0jzXpE_h9Xv4P5DyYRSuhxS0Qnm9aag-JKrJvBA,131
37
38
  discord/parts/components_v2.py,sha256=53cQm4FmxXHAA7PmDcDIO4bergX-BaUB3bZVWej-U9s,8660
38
39
  discord/parts/embed.py,sha256=_PV-lEAKn-MiXyyLa2s8JKHEplA8J9dDO80NPdZtmLk,3986
39
- discord/parts/message.py,sha256=egCWGUxcTqbgzz_-dxZOV1WQZpuzd6tq9uFiZH40s1Y,5499
40
+ discord/parts/message.py,sha256=XSRGYQjmbVyS9YG6VVw19chU1CUUljrFpUy6NY2HRJ0,5517
40
41
  discord/parts/modal.py,sha256=EX6J9Mh5dAQBOZqYKzSE7SFsKLfM_B1BhcJamjBNkZw,554
41
42
  discord/parts/role.py,sha256=cK96UdgT-kU0gY5C_1LZXPrYg144x2RDmGjT28so57A,920
42
- discord/resources/__init__.py,sha256=ubRaTXEy4dNJhqO8JXf-xBoJjPt9zKs1GZHY8Um2_mk,282
43
+ discord/resources/__init__.py,sha256=EdzYKftSLqqr3Bpzc0_90kfozJXOtp9jNTIHhCTt_-0,21
43
44
  discord/resources/application.py,sha256=vYMTli_FSbC7venMepsJ9bkzdEQVkKYpnxCJ9K2XDho,2765
44
45
  discord/resources/bot_emojis.py,sha256=RvGCSOBkjS39P2aab0FzYUOTzBOiHX99RLrJZzAYNiU,1701
45
- discord/resources/channel.py,sha256=-boYdVYW0v0oaV5X1SIhfdbKn0RDFF_HJo83dreVTm0,5943
46
+ discord/resources/channel.py,sha256=fe2JUp943VnXa-BKyRMtNP-JyNd_Mp516sWBKHKn_GI,6915
46
47
  discord/resources/guild.py,sha256=Unld1lWY3XynmRHU2FCi3-LA9VNp2thMI2BlILUTTxk,8183
47
- discord/resources/interaction.py,sha256=8z91X49KSknaT1syBfLr0SHRgAxaQ-8lbhkb3LU21fg,4911
48
+ discord/resources/interaction.py,sha256=mr4kLecQpl3AtgnNeqnj3eZIwQ73Clutdi-gnoSMWVU,5237
48
49
  discord/resources/message.py,sha256=RtvcCRx0lwW-mHPl3aNYoEvGffrvtpLsQ2fVWckywVI,7527
49
50
  discord/resources/user.py,sha256=vk89TnCVi-6ZgbDs_TZTCXrx_NfFS5Q9Wi_itYoaoyg,3085
50
- scurrypy-0.3.0.dist-info/licenses/LICENSE,sha256=NtspfRMAlryd1Eev4BYi9EFbKhvdmlCJJ2-ADUoEBoI,426
51
- scurrypy-0.3.0.dist-info/METADATA,sha256=v3X0WfX-Z1KetahNjl7PbPL_xvsqFKIdxcRKstJXrDI,2950
52
- scurrypy-0.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
53
- scurrypy-0.3.0.dist-info/top_level.txt,sha256=fJkrNbR-_8ubMBUcDEJBcfkpECrvSEmMrNKgvLlQFoM,8
54
- scurrypy-0.3.0.dist-info/RECORD,,
51
+ scurrypy-0.3.3.dist-info/licenses/LICENSE,sha256=NtspfRMAlryd1Eev4BYi9EFbKhvdmlCJJ2-ADUoEBoI,426
52
+ scurrypy-0.3.3.dist-info/METADATA,sha256=QQHxDvVbl4i51dJwTCr_atvGEXOK9mITtWrK-tdpNZg,2954
53
+ scurrypy-0.3.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
54
+ scurrypy-0.3.3.dist-info/top_level.txt,sha256=fJkrNbR-_8ubMBUcDEJBcfkpECrvSEmMrNKgvLlQFoM,8
55
+ scurrypy-0.3.3.dist-info/RECORD,,
@@ -1,85 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: scurrypy
3
- Version: 0.3.0
4
- Summary: Discord API Wrapper in Python
5
- Author: Furmissile
6
- Requires-Python: >=3.10
7
- Description-Content-Type: text/markdown
8
- License-File: LICENSE
9
- Dynamic: license-file
10
-
11
- # __Welcome to ScurryPy__
12
-
13
- [![PyPI version](https://badge.fury.io/py/scurrypy.svg)](https://badge.fury.io/py/scurrypy)
14
-
15
- Yet another Discord API wrapper in Python!
16
-
17
- While this wrapper is mainly used for various squirrel-related shenanigans, it can also be used for more generic bot purposes.
18
-
19
- ## Features
20
- * Command and event handling
21
- * Declarative style using decorators
22
- * Supports both legacy and new features
23
- * Respects Discord's rate limits
24
-
25
- ## Something things to consider...
26
- * This is an early version — feedback, ideas, and contributions are welcome! With that said, there will be bumps in the road so expect bugs and other flaws!
27
- * Some features are not yet supported, such as sharding and automod, while others, like voice, will never be supported. While this library can handle many of your basic needs, common features such as sharding or auto-mod actions are not yet implemented. See the [license](LICENSE) for details on usage.
28
-
29
-
30
- ## Getting Started
31
- While this tab shows up in the docs, here are some complete examples where all you need to do is pop in your bot's credentials!
32
-
33
- ## Installation
34
- To install the ScurryPy package, run:
35
- ```bash
36
- pip install scurrypy
37
- ```
38
-
39
- ## Minimal Slash Command
40
- The following demonstrates building and responding to a slash command.
41
- ```python
42
- import discord, os
43
- from dotenv import load_dotenv
44
-
45
- load_dotenv(dotenv_path='./path/to/env') # omit argument if your env file is on the same level
46
-
47
- bot = discord.Client(
48
- token=os.getenv("DISCORD_TOKEN"),
49
- application_id=1234567890 # replace with your bot's user ID
50
- )
51
-
52
- @bot.command(
53
- command=discord.SlashCommand(name='example', description='Demonstrate the minimal slash command!'),
54
- guild_id=GUILD_ID # must be a guild ID your bot is in!
55
- )
56
- async def example(event: discord.InteractionEvent):
57
- await event.interaction.respond(f'Hello, {event.interaction.member.user.username}!')
58
-
59
- bot.run()
60
- ```
61
-
62
- ## Minimal Prefix Command (Legacy)
63
- The following demonstrates building and responding to a message prefix command.
64
- ```python
65
- import discord, os
66
- from dotenv import load_dotenv
67
-
68
- load_dotenv(dotenv_path='./path/to/env') # omit argument if your env file is on the same level
69
-
70
- bot = discord.Client(
71
- token=os.getenv("DISCORD_TOKEN"),
72
- application_id=1234567890, # replace with your bot's user ID
73
- intents=discord.set_intents(message_content=True),
74
- prefix='!' # your custom prefix
75
- )
76
-
77
- @bot.prefix_command
78
- async def ping(event: discord.MessageCreateEvent): # the function name is the name of the command!
79
- await event.message.send(f"Pong!")
80
-
81
- bot.run()
82
- ```
83
-
84
- ## Like what you see?
85
- See the [docs](https://furmissile.github.io/scurrypy) for more!