fix: mass-ping leak, CDN link mangling, tz-naive timestamp, prompt limit drift
Fixes from the dpy4 audit report (REPORT_dpy4_audit.md, dpy_commons section): - dpycommons-1 (HIGH): safe_send/_send applied kwargs to the first chunk only, so allowed_mentions/silent/suppress_embeds dropped off every chunk after the first, letting a suppressed @everyone/@here fire live on later chunks of a mass-ping. Mention-control kwargs now apply to every chunk; file/ reference/view/etc. still ride the first message only. - dpycommons-4 (MED): _DISCORD_HOST_RE missed discordapp.net (Discord's own media/image CDN), causing wrap_bare_links to <>-wrap preview links Discord itself emits; dropped the nonexistent discord.media host. - dpycommons-8 (MED): discord_timestamp stamped naive datetimes as UTC, diverging from discord.py's own naive-datetime handling (local via astimezone()) and rendering the wrong wall time on non-UTC hosts. Now matches discord.py's behavior. - dpycommons-9: prompts.py hardcoded the 5-button/80-char/100-char limits inline instead of sourcing from limits.py; moved them to limits.py (BUTTON_ROW_MAX, BUTTON_LABEL_MAX, SELECT_OPTION_LABEL_MAX, SELECT_PLACEHOLDER_MAX, SELECT_MAX_OPTIONS) and corrected the placeholder cap from 100 to Discord's actual 150; choose() now raises ValueError above 25 options instead of failing inside discord.py's select builder. - dpycommons-3: choose()'s select-label path now truncates labels to SELECT_OPTION_LABEL_MAX so a >80-char key routed to the select can no longer build a >100-char option label. - dpycommons-7: safe_send's empty-input fallback now sends content=None instead of content='' (Discord rejects an explicit empty string). - redundant except: dropped discord.NotFound from parsing.py's attachment read except tuple (it subclasses HTTPException, already caught). - doc-only: softened chunk_text's "no content is lost" overclaim, documented extract_message_links as guild-only (DM @me links unmatched). Version 0.1.0 -> 0.1.1. Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -30,7 +30,9 @@ await dc.safe_send(channel, content=long_text, embeds=many_embeds)
|
||||
for piece in dc.chunk_text(blob):
|
||||
await channel.send(piece)
|
||||
|
||||
# a live, timezone-local timestamp rendered by the Discord client
|
||||
# a live, timezone-local timestamp rendered by the Discord client; a naive dt is treated
|
||||
# as local time (matching discord.py's own naive-datetime handling) — pass an aware dt
|
||||
# if the source is UTC
|
||||
dc.discord_timestamp(dt, "R") # "<t:1751500000:R>"
|
||||
```
|
||||
|
||||
@@ -55,7 +57,9 @@ action = await dc.choose(ctx, "Pick:", {
|
||||
interaction). Both scope to a user (a stranger's click gets an ephemeral "not for you" and the
|
||||
prompt stays live), disable their components after resolve/timeout, accept custom emojis
|
||||
anywhere an emoji goes, and take `cleanup=True` to delete the prompt afterward. `choose`
|
||||
auto-switches to a select dropdown for more than 5 options or long labels.
|
||||
auto-switches to a select dropdown for more than 5 options or long labels, truncates select
|
||||
option labels and the placeholder to Discord's caps, and raises `ValueError` for more than 25
|
||||
options (Discord's per-select cap).
|
||||
|
||||
## What's inside
|
||||
|
||||
@@ -67,16 +71,25 @@ auto-switches to a select dropdown for more than 5 options or long labels.
|
||||
| Prompts | `confirm`, `choose` |
|
||||
| Send | `safe_send` |
|
||||
|
||||
All Discord hard limits live as module constants (`MSG_LIMIT`, `EMBED_TOTAL`, …) — the single
|
||||
source of truth; nothing hardcodes a limit.
|
||||
All Discord hard limits live as module constants (`MSG_LIMIT`, `EMBED_TOTAL`,
|
||||
`BUTTON_ROW_MAX`, `BUTTON_LABEL_MAX`, `SELECT_OPTION_LABEL_MAX`, `SELECT_PLACEHOLDER_MAX`,
|
||||
`SELECT_MAX_OPTIONS`, …) — the single source of truth; nothing hardcodes a limit.
|
||||
|
||||
## Contract
|
||||
|
||||
Config-free (functions take the discord objects they act on, never a global). Fail-loud:
|
||||
`format_table` raises `ValueError` on ragged rows, `discord_timestamp` on a bad style,
|
||||
`choose` on empty options; `safe_send` and the prompts propagate Discord perms/HTTP errors
|
||||
(a prompt **timeout** is a normal `None`, not an error). The one tolerated swallow is a single
|
||||
bad attachment in `parse_message` (warn + skip) — pass `strict=True` to raise instead.
|
||||
`choose` on empty options or more than 25 options; `safe_send` and the prompts propagate
|
||||
Discord perms/HTTP errors (a prompt **timeout** is a normal `None`, not an error). The one
|
||||
tolerated swallow is a single bad attachment in `parse_message` (warn + skip) — pass
|
||||
`strict=True` to raise instead.
|
||||
|
||||
`safe_send`'s mention-control kwargs (`allowed_mentions`, `silent`, `suppress_embeds`, `tts`)
|
||||
apply to **every** chunked message, not just the first, so a suppressed `@everyone`/`@here`
|
||||
stays suppressed across the whole split. Once-only kwargs (`file`, `files`, `stickers`,
|
||||
`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
|
||||
with `content=None`.
|
||||
|
||||
## Notes / deviations
|
||||
|
||||
|
||||
Reference in New Issue
Block a user