Commit Graph
6 Commits
Author SHA1 Message Date
dsql ee3c38972b fix: reject a non-str stored token as stale before rebuilding the webhook (dpywebhooks-3)
Webhook.partial does no token validation of its own, so a store record with
token=None (e.g. a mongo/file store deserializing a missing field as null)
rebuilt and cached a token-less webhook with no error. Webhook.send then
raised a raw ValueError, not HTTPException, so send()'s dead-webhook except
never fired and the poisoned record was never cleared - the channel got
permanently stuck. Validate the token type before partial() so a bad record
takes the same clear-and-fall-through path as any other stale record.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-06 00:14:56 -04:00
dsql b20c5445b4 fix: self-heal retry rebuilds consumed File objects (no 0-byte re-upload)
send() and send_any() retry with the exact same discord.File objects
after healing a dead webhook, but discord.py closes every File on the
first send's exit - a buffer-backed File's retry then uploads 0 bytes
silently (the buffer sits at EOF and nothing calls reset()), and a
path-backed File raises "I/O operation on closed file" instead.

The healed retry in both methods now routes kwargs through
_rebuild_files_in_kwargs(), which rebuilds a fresh discord.File per
entry in file=/files= from its original source (reopens a path-backed
File, rewinds a still-seekable buffer) before the retry send. A File
that can't be safely rebuilt raises ValueError rather than silently
sending an empty attachment. The first, non-retry send is untouched.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 19:14:32 -04:00
dsql 611fea898b refactor: derive __version__ from package metadata (single source)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 17:01:25 -04:00
dsql ce48f2aea8 fix: skip token-less webhooks in pick(); self-heal deletes the dead pick, not the managed webhook
dpywebhooks-1 (HIGH): pick()/send_any could select token-less channel-follower
(type 2) or application-owned (type 3) webhooks returned by channel.webhooks().
Webhook.send raises a raw ValueError on those before any HTTP call, and
send_any only caught discord.HTTPException, so the ValueError bypassed
fallback and self-heal entirely. strategy='first' picks the lowest id, so any
channel following an announcement channel could be permanently broken. Fix:
pick() now filters candidates to w.token is not None.

dpywebhooks-2 (HIGH): send_any's self-heal always called clear(), which
targets the managed webhook (cache/store), not the dead webhook that was
actually picked. When the dead pick and the managed webhook differed, this
deleted a healthy managed webhook and churned delete+create toward the
10-cap on every send. Fix: self-heal now deletes the dead picked webhook
directly (bot-auth webhook.delete() authorizes on it without its own token);
clear() is only used when the pick IS the managed webhook.

dpywebhooks-8 (nit): count() and _create() now read live webhooks through
list() instead of calling channel.webhooks() directly, so the token filter
and any future list() change reach all three call sites from one source.

Version 0.1.0 -> 0.1.1. README + module docstrings updated to document the
token filter and the corrected self-heal delete target.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 15:31:06 -04:00
dsql 74fd1bc4a5 add package: pyproject + src (DPYWebhooks, store protocol, cap + self-heal, fail-loud)
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 20:04:01 -04:00
dsql 4518cbf30e init: per-channel discord webhook management for discord.py — get-or-create, persist, cap, send
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-02 19:52:25 -04:00