fix: safe_send routes embed= + fails loud on unknown kwargs; embed helpers deep-copy (no input mutation)
Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -7,7 +7,7 @@ text chunking, timestamp helpers, interactive await-prompts, and a limit-safe se
|
|||||||
## Install
|
## Install
|
||||||
|
|
||||||
```
|
```
|
||||||
dpy_commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_commons.git@v0.1.2
|
dpy_commons @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_commons.git@v0.1.3
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
@@ -91,7 +91,10 @@ apply to **every** chunked message, not just the first, so a suppressed `@everyo
|
|||||||
stays suppressed across the whole split. Once-only kwargs (`file`, `files`, `stickers`,
|
stays suppressed across the whole split. Once-only kwargs (`file`, `files`, `stickers`,
|
||||||
`nonce`, `reference`, `mention_author`, `view`, `poll`, `delete_after`) still ride the first
|
`nonce`, `reference`, `mention_author`, `view`, `poll`, `delete_after`) still ride the first
|
||||||
message only. A bare `safe_send(destination)` with no content/embeds sends a single message
|
message only. A bare `safe_send(destination)` with no content/embeds sends a single message
|
||||||
with `content=None`.
|
with `content=None`. A singular `embed=` kwarg is folded into the `embeds` pipeline (fit +
|
||||||
|
split like any other embed); passing both `embed` and `embeds` raises `TypeError`, matching
|
||||||
|
discord.py's own `Messageable.send` rule. Any kwarg `safe_send` doesn't recognize also raises
|
||||||
|
`TypeError` naming it, rather than being silently dropped.
|
||||||
|
|
||||||
## Notes / deviations
|
## Notes / deviations
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "dpy_commons"
|
name = "dpy_commons"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
description = "Shared discord.py utilities — message/embed parsing, limit-fitting, link extraction, chunking, timestamps, await-prompts, limit-safe send. Config-free, installable."
|
description = "Shared discord.py utilities — message/embed parsing, limit-fitting, link extraction, chunking, timestamps, await-prompts, limit-safe send. Config-free, installable."
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
and split a list into sendable groups; none of these mutate their input"""
|
and split a list into sendable groups; none of these mutate their input"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import discord
|
import discord
|
||||||
@@ -25,8 +26,12 @@ _ZWSP = ""
|
|||||||
|
|
||||||
|
|
||||||
def _copy(embed: discord.Embed) -> discord.Embed:
|
def _copy(embed: discord.Embed) -> discord.Embed:
|
||||||
"""deep copy via dict round-trip so the input is never mutated"""
|
"""deep copy via dict round-trip so the input is never mutated
|
||||||
return discord.Embed.from_dict(embed.to_dict())
|
|
||||||
|
discord.py 2.7.1's to_dict() reuses the embed's own _fields list rather than copying
|
||||||
|
it, so from_dict(embed.to_dict()) would still share that list with the input; the
|
||||||
|
explicit deepcopy breaks that aliasing"""
|
||||||
|
return discord.Embed.from_dict(copy.deepcopy(embed.to_dict()))
|
||||||
|
|
||||||
|
|
||||||
def fit_embed(embed: discord.Embed) -> discord.Embed:
|
def fit_embed(embed: discord.Embed) -> discord.Embed:
|
||||||
|
|||||||
+16
-2
@@ -34,14 +34,28 @@ async def safe_send(
|
|||||||
`view`, `poll`, `delete_after`) ride only on the FIRST message so they aren't duplicated
|
`view`, `poll`, `delete_after`) ride only on the FIRST message so they aren't duplicated
|
||||||
across the split
|
across the split
|
||||||
|
|
||||||
|
a singular `embed=` kwarg is folded into the `embeds` pipeline (fit + split like any
|
||||||
|
other embed); passing both `embed` and `embeds` raises `TypeError`, matching
|
||||||
|
discord.py's own `Messageable.send` rule. Any other kwarg not recognized by either
|
||||||
|
whitelist above also raises `TypeError` naming it, instead of being silently dropped
|
||||||
|
|
||||||
with no content and no embeds, one message is still sent (content=None) so a bare
|
with no content and no embeds, one message is still sent (content=None) so a bare
|
||||||
safe_send(destination) or a files/view-only call goes out as a single message rather than
|
safe_send(destination) or a files/view-only call goes out as a single message rather than
|
||||||
silently doing nothing"""
|
silently doing nothing"""
|
||||||
content_chunks = chunk_text(content) if content else []
|
embed = kwargs.pop("embed", None)
|
||||||
embed_groups = split_embeds([fit_embed(embed) for embed in embeds]) if embeds else []
|
if embed is not None:
|
||||||
|
if embeds is not None:
|
||||||
|
raise TypeError("safe_send() cannot mix embed and embeds")
|
||||||
|
embeds = [embed]
|
||||||
|
|
||||||
per_chunk = {k: v for k, v in kwargs.items() if k in _PER_CHUNK_KWARGS}
|
per_chunk = {k: v for k, v in kwargs.items() if k in _PER_CHUNK_KWARGS}
|
||||||
once_only = {k: v for k, v in kwargs.items() if k in _ONCE_ONLY_KWARGS}
|
once_only = {k: v for k, v in kwargs.items() if k in _ONCE_ONLY_KWARGS}
|
||||||
|
unexpected = [k for k in kwargs if k not in _PER_CHUNK_KWARGS and k not in _ONCE_ONLY_KWARGS]
|
||||||
|
if unexpected:
|
||||||
|
raise TypeError(f"safe_send() got unexpected keyword argument(s): {', '.join(unexpected)}")
|
||||||
|
|
||||||
|
content_chunks = chunk_text(content) if content else []
|
||||||
|
embed_groups = split_embeds([fit_embed(e) for e in embeds]) if embeds else []
|
||||||
|
|
||||||
messages: "list[discord.Message]" = []
|
messages: "list[discord.Message]" = []
|
||||||
first = True
|
first = True
|
||||||
|
|||||||
Reference in New Issue
Block a user