13 Commits
Author SHA1 Message Date
dsql 4c1244de88 release: 1.0.0
first stable release. pre-1.0.0 verification complete: all surviving MED regressions and
gaps resolved and independently re-fired, tree audited clean across the suite.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-09 18:53:15 -04:00
dsql bd27ef58f1 fix: classify choose() emoji keys by unicode properties, not length+ascii
_looks_unicode_emoji used 'len<=4 and not isascii', so a short non-ascii text label
(Sí/да/はい/café/确定) was misread as a unicode emoji and sent as a labelless component
emoji, which Discord 400s. It now treats a key as an emoji only when every codepoint is
emoji-composition material (So/Sk symbols, ZWJ, variation selectors, regional indicators,
keycap combiner) with at least one pictographic symbol - so a compound emoji (ZWJ family,
flag, skin-tone, keycap) stays one emoji while an i18n label is a label. The terminal
Discord 400 remains a documented live-gap (needs a gateway).

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 19:59:56 -04:00
dsql 79c2c7286f docs: re-export the button/select limit constants the README advertises
README lists BUTTON_ROW_MAX/BUTTON_LABEL_MAX/SELECT_OPTION_LABEL_MAX/SELECT_PLACEHOLDER_MAX/
SELECT_MAX_OPTIONS as flat module constants, but __init__ re-exported only the message/embed
limits, so dc.BUTTON_ROW_MAX was an AttributeError - re-export all five to match the doc.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 19:38:30 -04:00
dsql 65fa11316e fix: safe_send empty-fallback no longer re-injects whitespace-only content
When chunk_text dropped a whitespace-only content to [] and there were no embeds, the
empty-messages fallback re-sent the raw original content - the exact whitespace 444e08b
set out to eliminate, which Discord 50006s. The fallback now sends content=None so a
files/view-only call still goes out as one message, and whitespace-only content is never
forwarded raw; a genuinely empty send remains the caller's fail-loud error.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 19:29:55 -04:00
dsql 444e08b7db fix: split_embeds bounds groups by the 6000-char total too; chunk_text drops whitespace-only chunks
split_embeds chunked only by the 10-per-message count, so several individually-fitted
embeds whose combined length exceeded 6000 still 400'd (BASE_TYPE_MAX_LENGTH), breaking the
'never fails on a discord limit' contract; it now starts a new group before either the
count OR the 6000 total is exceeded. chunk_text could emit a whitespace-only piece (e.g.
'\n') when the only split boundary sat at index 1, which safe_send then sent as empty
content discord rejects (50006); whitespace-only pieces are now skipped. (live 400/50006
need discord; the split/chunk logic is verified offline.)

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 16:46:37 -04:00
dsql bd9502c62d fix: safe_send routes embed= + fails loud on unknown kwargs; embed helpers deep-copy (no input mutation)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 19:03:05 -04:00
dsql 5eb9ed2be0 refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 17:00:52 -04:00
dsql 265b9ab447 fix: sync __version__ to 0.1.2 (drifted behind pyproject)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:25:18 -04:00
dsql 40429b0afe docs: fix stale install pin, v0.1.0 -> v0.1.2
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:24:02 -04:00
dsql b530affd6e fix: choose() select path emits null label for emoji-only keys
add_option(label=label, ...) passed label=None straight through for an
emoji-only key (_split_key returns (None, emoji)), serializing
{"label": null, ...} which Discord's select-option schema rejects
(1-100 char label required). The <=5-option button path is unaffected
(a button may be emoji-only). Fall back to the key's own text form
(or a single space) as the label when _split_key yields none, so the
emoji is still shown via emoji= but the option always carries a
non-empty label.

Bump 0.1.1 -> 0.1.2.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:15:01 -04:00
dsql f1f2ecf74a 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>
2026-07-03 15:33:21 -04:00
dsql 056516dee9 add package: pyproject + src (parse/fit/sanitize/chunk/table/timestamp/confirm/choose/safe_send)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 23:12:53 -04:00
dsql 464a1171c3 init: shared discord.py utilities — parsing, embeds, text, await-prompts, limit-safe send
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 23:12:53 -04:00