loopbot-discord-sdk 1.0.6__tar.gz → 1.0.7__tar.gz

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 (32) hide show
  1. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/PKG-INFO +1 -1
  2. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/bot.py +32 -0
  3. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/base.py +49 -0
  4. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot_discord_sdk.egg-info/PKG-INFO +1 -1
  5. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/pyproject.toml +1 -1
  6. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/README.md +0 -0
  7. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/__init__.py +0 -0
  8. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/__init__.py +0 -0
  9. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/action_row.py +0 -0
  10. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/button.py +0 -0
  11. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/container.py +0 -0
  12. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/embed.py +0 -0
  13. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/file.py +0 -0
  14. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/media_gallery.py +0 -0
  15. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/modal.py +0 -0
  16. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/section.py +0 -0
  17. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/select_menu.py +0 -0
  18. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/separator.py +0 -0
  19. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/builders/text_display.py +0 -0
  20. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/client.py +0 -0
  21. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/__init__.py +0 -0
  22. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/button.py +0 -0
  23. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/command.py +0 -0
  24. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/modal.py +0 -0
  25. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/context/select.py +0 -0
  26. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/database.py +0 -0
  27. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot/types.py +0 -0
  28. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot_discord_sdk.egg-info/SOURCES.txt +0 -0
  29. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot_discord_sdk.egg-info/dependency_links.txt +0 -0
  30. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot_discord_sdk.egg-info/requires.txt +0 -0
  31. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/loopbot_discord_sdk.egg-info/top_level.txt +0 -0
  32. {loopbot_discord_sdk-1.0.6 → loopbot_discord_sdk-1.0.7}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopbot-discord-sdk
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: Official Loop Discord SDK for Python - Build Discord bots without websockets
5
5
  Author-email: Loop <contact@loopbot.app>
6
6
  License: MIT
@@ -14,6 +14,10 @@ from .context.modal import ModalContext
14
14
  from .context.select import SelectContext
15
15
  from .types import InteractionType
16
16
 
17
+ # ANSI color codes for warnings
18
+ YELLOW = '\033[93m'
19
+ RESET = '\033[0m'
20
+
17
21
 
18
22
  CommandHandler = Callable[[CommandContext], None]
19
23
  ButtonHandler = Callable[[ButtonContext], None]
@@ -111,6 +115,13 @@ class Bot:
111
115
  ctx = CommandContext(interaction, self._client, self._application_id)
112
116
  command.handler(ctx)
113
117
  response = ctx.response
118
+ if not response:
119
+ print(
120
+ f"{YELLOW}[Loop SDK] ⚠️ O handler do comando '{command_name}' não definiu uma resposta! "
121
+ f"Use ctx.reply(), ctx.reply_with_components(), ctx.defer() ou ctx.show_modal().{RESET}"
122
+ )
123
+ else:
124
+ print(f"[Loop SDK] No handler for command: {command_name}")
114
125
 
115
126
  elif interaction_type == InteractionType.MESSAGE_COMPONENT:
116
127
  custom_id = data.get("custom_id", "")
@@ -123,6 +134,13 @@ class Bot:
123
134
  ctx = ButtonContext(interaction, self._client, self._application_id)
124
135
  handler(ctx)
125
136
  response = ctx.response
137
+ if not response:
138
+ print(
139
+ f"{YELLOW}[Loop SDK] ⚠️ O handler do botão '{custom_id}' não definiu uma resposta! "
140
+ f"Use ctx.reply(), ctx.update(), ctx.defer_update() ou ctx.show_modal().{RESET}"
141
+ )
142
+ else:
143
+ print(f"[Loop SDK] No handler for button: {custom_id}")
126
144
 
127
145
  # Select Menu (type 3)
128
146
  elif component_type == 3:
@@ -131,6 +149,13 @@ class Bot:
131
149
  ctx = SelectContext(interaction, self._client, self._application_id)
132
150
  handler(ctx)
133
151
  response = ctx.response
152
+ if not response:
153
+ print(
154
+ f"{YELLOW}[Loop SDK] ⚠️ O handler do select '{custom_id}' não definiu uma resposta! "
155
+ f"Use ctx.reply(), ctx.update() ou ctx.defer_update().{RESET}"
156
+ )
157
+ else:
158
+ print(f"[Loop SDK] No handler for select: {custom_id}")
134
159
 
135
160
  elif interaction_type == InteractionType.MODAL_SUBMIT:
136
161
  custom_id = data.get("custom_id", "")
@@ -140,6 +165,13 @@ class Bot:
140
165
  ctx = ModalContext(interaction, self._client, self._application_id)
141
166
  handler(ctx)
142
167
  response = ctx.response
168
+ if not response:
169
+ print(
170
+ f"{YELLOW}[Loop SDK] ⚠️ O handler do modal '{custom_id}' não definiu uma resposta! "
171
+ f"Use ctx.reply(), ctx.reply_with_components() ou ctx.defer_update().{RESET}"
172
+ )
173
+ else:
174
+ print(f"[Loop SDK] No handler for modal: {custom_id}")
143
175
 
144
176
  # Send response
145
177
  if response:
@@ -3,6 +3,7 @@ Base Context for all interaction types
3
3
  """
4
4
 
5
5
  from typing import Any, Dict, List, Optional, Union
6
+ import warnings
6
7
 
7
8
  from ..types import Interaction, InteractionResponseType, DiscordUser, DiscordMember
8
9
  from ..database import Database
@@ -10,6 +11,10 @@ from ..builders.embed import EmbedBuilder
10
11
  from ..builders.action_row import ActionRowBuilder
11
12
  from ..builders.modal import ModalBuilder
12
13
 
14
+ # ANSI color codes for warnings
15
+ YELLOW = '\033[93m'
16
+ RESET = '\033[0m'
17
+
13
18
 
14
19
  class BaseContext:
15
20
  """Base context for handling Discord interactions"""
@@ -25,8 +30,20 @@ class BaseContext:
25
30
  self._application_id = application_id
26
31
  self._response: Optional[Dict[str, Any]] = None
27
32
  self._deferred = False
33
+ self._has_responded = False
28
34
  self.db = Database(client)
29
35
 
36
+ def _check_duplicate_response(self, method_name: str) -> bool:
37
+ """Check if already responded and warn if so"""
38
+ if self._has_responded:
39
+ print(
40
+ f"{YELLOW}[Loop SDK] ⚠️ Você já respondeu a esta interação! "
41
+ f"Chamada duplicada de {method_name}() será ignorada.{RESET}"
42
+ )
43
+ return True
44
+ self._has_responded = True
45
+ return False
46
+
30
47
  @property
31
48
  def interaction_id(self) -> str:
32
49
  return self._interaction.get("id", "")
@@ -85,6 +102,20 @@ class BaseContext:
85
102
  ephemeral: bool = False,
86
103
  ) -> None:
87
104
  """Reply to the interaction"""
105
+ if self._check_duplicate_response("reply"):
106
+ return
107
+
108
+ # Check if any component is a Container (type 17) and warn
109
+ if components:
110
+ for c in components:
111
+ comp_dict = c.to_dict() if hasattr(c, "to_dict") else c
112
+ if comp_dict.get("type") == 17:
113
+ print(
114
+ f"{YELLOW}[Loop SDK] ⚠️ Você está usando reply() com um Container. "
115
+ f"Use reply_with_components() para que Components V2 funcionem corretamente!{RESET}"
116
+ )
117
+ break
118
+
88
119
  data: Dict[str, Any] = {}
89
120
 
90
121
  if content:
@@ -120,6 +151,9 @@ class BaseContext:
120
151
  ephemeral: bool = False,
121
152
  ) -> None:
122
153
  """Reply with Components V2 (Container, MediaGallery, etc.)"""
154
+ if self._check_duplicate_response("reply_with_components"):
155
+ return
156
+
123
157
  data: Dict[str, Any] = {
124
158
  "components": [
125
159
  c.to_dict() if hasattr(c, "to_dict") else c
@@ -143,6 +177,9 @@ class BaseContext:
143
177
  components: Optional[List[Union[Dict[str, Any], ActionRowBuilder]]] = None,
144
178
  ) -> None:
145
179
  """Update the original message"""
180
+ if self._check_duplicate_response("update"):
181
+ return
182
+
146
183
  data: Dict[str, Any] = {}
147
184
 
148
185
  if content is not None:
@@ -167,6 +204,9 @@ class BaseContext:
167
204
 
168
205
  def update_with_components(self, components: List[Any]) -> None:
169
206
  """Update the original message with Components V2"""
207
+ if self._check_duplicate_response("update_with_components"):
208
+ return
209
+
170
210
  self._response = {
171
211
  "type": InteractionResponseType.UPDATE_MESSAGE,
172
212
  "data": {
@@ -180,6 +220,9 @@ class BaseContext:
180
220
 
181
221
  def defer(self, ephemeral: bool = False) -> None:
182
222
  """Defer the response (show 'thinking...')"""
223
+ if self._check_duplicate_response("defer"):
224
+ return
225
+
183
226
  self._deferred = True
184
227
  self._response = {
185
228
  "type": InteractionResponseType.DEFERRED_CHANNEL_MESSAGE_WITH_SOURCE,
@@ -188,6 +231,9 @@ class BaseContext:
188
231
 
189
232
  def defer_update(self) -> None:
190
233
  """Defer the update (acknowledge without visible loading)"""
234
+ if self._check_duplicate_response("defer_update"):
235
+ return
236
+
191
237
  self._deferred = True
192
238
  self._response = {
193
239
  "type": InteractionResponseType.DEFERRED_UPDATE_MESSAGE,
@@ -195,6 +241,9 @@ class BaseContext:
195
241
 
196
242
  def show_modal(self, modal: ModalBuilder) -> None:
197
243
  """Show a modal to the user"""
244
+ if self._check_duplicate_response("show_modal"):
245
+ return
246
+
198
247
  self._response = {
199
248
  "type": InteractionResponseType.MODAL,
200
249
  "data": modal.to_dict(),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: loopbot-discord-sdk
3
- Version: 1.0.6
3
+ Version: 1.0.7
4
4
  Summary: Official Loop Discord SDK for Python - Build Discord bots without websockets
5
5
  Author-email: Loop <contact@loopbot.app>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "loopbot-discord-sdk"
7
- version = "1.0.6"
7
+ version = "1.0.7"
8
8
  description = "Official Loop Discord SDK for Python - Build Discord bots without websockets"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}