DiscordPYComponents 1.3.1__tar.gz → 1.3.2__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 (18) hide show
  1. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/DiscordPYComponents.egg-info/PKG-INFO +1 -1
  2. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/PKG-INFO +1 -1
  3. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/__init__.py +1 -1
  4. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/ui.py +31 -15
  5. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/pyproject.toml +1 -1
  6. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/DiscordPYComponents.egg-info/SOURCES.txt +0 -0
  7. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/DiscordPYComponents.egg-info/dependency_links.txt +0 -0
  8. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/DiscordPYComponents.egg-info/requires.txt +0 -0
  9. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/DiscordPYComponents.egg-info/top_level.txt +0 -0
  10. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/README.md +0 -0
  11. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/components.py +0 -0
  12. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/enums.py +0 -0
  13. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/http.py +0 -0
  14. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/ids.py +0 -0
  15. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/modal.py +0 -0
  16. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/registry.py +0 -0
  17. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/py_components/view.py +0 -0
  18. {discordpycomponents-1.3.1 → discordpycomponents-1.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DiscordPYComponents
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Discord Components V2 for discord.py
5
5
  Author: 2gm49
6
6
  License: MIT
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: DiscordPYComponents
3
- Version: 1.3.1
3
+ Version: 1.3.2
4
4
  Summary: Discord Components V2 for discord.py
5
5
  Author: 2gm49
6
6
  License: MIT
@@ -42,7 +42,7 @@ from .registry import Registry, default_registry
42
42
  from .http import send_message, edit_message, respond_interaction, open_modal
43
43
  from .ids import assign_ids, find_by_id, replace_by_id
44
44
 
45
- __version__ = "1.3.1"
45
+ __version__ = "1.3.2"
46
46
  __all__ = [
47
47
  "ButtonStyle",
48
48
  "ComponentType",
@@ -73,6 +73,9 @@ class UI:
73
73
 
74
74
  if _is_interaction(target):
75
75
  return await self._send_interaction(target, payload, files=files, **kwargs)
76
+
77
+ # User/Member → open DM channel so we never POST to /channels/{user.id}
78
+ target = await _ensure_messageable(target)
76
79
  return await self._send_channel(target, payload, files=files, **kwargs)
77
80
 
78
81
  async def reply(
@@ -181,7 +184,6 @@ class UI:
181
184
  send_method = target.channel.send
182
185
 
183
186
  if send_method is not None:
184
- # discord.py Messageable.send — try with flags first (needed for V2)
185
187
  attempts = [
186
188
  dict(components=payload["components"], flags=payload.get("flags", IS_COMPONENTS_V2), files=files or [], **kwargs),
187
189
  dict(components=payload["components"], flags=payload.get("flags", IS_COMPONENTS_V2), **kwargs),
@@ -196,7 +198,6 @@ class UI:
196
198
  last_err = e
197
199
  continue
198
200
  except Exception as e:
199
- # real API error — don't keep retrying silently
200
201
  last_err = e
201
202
  break
202
203
  if last_err is not None and not isinstance(last_err, TypeError):
@@ -220,14 +221,36 @@ def _is_interaction(obj: Any) -> bool:
220
221
  return False
221
222
 
222
223
 
224
+ def _is_user_like(obj: Any) -> bool:
225
+ """User/Member — has create_dm, not a guild channel."""
226
+ if obj is None:
227
+ return False
228
+ if callable(getattr(obj, "create_dm", None)):
229
+ return True
230
+ # duck type: no guild attribute path for pure User
231
+ cls_name = type(obj).__name__
232
+ return cls_name in ("User", "Member", "ClientUser")
233
+
234
+
235
+ async def _ensure_messageable(target: Any) -> Any:
236
+ if _is_user_like(target):
237
+ dm = getattr(target, "dm_channel", None)
238
+ if dm is not None:
239
+ return dm
240
+ if callable(getattr(target, "create_dm", None)):
241
+ return await target.create_dm()
242
+ return target
243
+
244
+
223
245
  def _resolve_channel_and_token(obj: Any) -> tuple[Any, str]:
224
246
  channel_id = None
225
247
 
226
- # bare channel / thread
227
- if getattr(obj, "id", None) is not None and callable(getattr(obj, "send", None)):
228
- # TextChannel, DMChannel, Thread, etc.
229
- if not _is_interaction(obj):
230
- channel_id = obj.id
248
+ # Prefer explicit channel objects (have recipients or guild, or type name)
249
+ name = type(obj).__name__
250
+ if name in ("TextChannel", "Thread", "DMChannel", "GroupChannel", "VoiceChannel", "StageChannel"):
251
+ channel_id = getattr(obj, "id", None)
252
+ elif getattr(obj, "id", None) is not None and callable(getattr(obj, "send", None)) and not _is_user_like(obj):
253
+ channel_id = obj.id
231
254
 
232
255
  if channel_id is None and hasattr(obj, "channel") and obj.channel is not None:
233
256
  channel_id = getattr(obj.channel, "id", None)
@@ -243,7 +266,6 @@ def _resolve_channel_and_token(obj: Any) -> tuple[Any, str]:
243
266
 
244
267
 
245
268
  def _resolve_token(obj: Any) -> str:
246
- # common discord.py paths
247
269
  for attr in ("bot", "client", "_state"):
248
270
  owner = getattr(obj, attr, None)
249
271
  if owner is None:
@@ -253,13 +275,7 @@ def _resolve_token(obj: Any) -> str:
253
275
  tok = getattr(http, "token", None)
254
276
  if tok:
255
277
  return tok
256
- # bot.http.token
257
- tok = getattr(owner, "token", None)
258
- if tok and isinstance(tok, str) and not tok.startswith("Bot "):
259
- # sometimes the raw token is stored; http.token is preferred
260
- pass
261
278
 
262
- # channel._state.http.token
263
279
  state = getattr(obj, "_state", None)
264
280
  if state is not None:
265
281
  http = getattr(state, "http", None)
@@ -268,7 +284,7 @@ def _resolve_token(obj: Any) -> str:
268
284
  if tok:
269
285
  return tok
270
286
 
271
- if hasattr(obj, "channel"):
287
+ if hasattr(obj, "channel") and obj.channel is not None:
272
288
  return _resolve_token(obj.channel)
273
289
 
274
290
  raise ValueError("could not resolve bot token")
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "DiscordPYComponents"
3
- version = "1.3.1"
3
+ version = "1.3.2"
4
4
  description = "Discord Components V2 for discord.py"
5
5
  authors = [{name="2gm49"}]
6
6
  dependencies = ["aiohttp"]