init: per-channel discord webhook management for discord.py — get-or-create, persist, cap, send
Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# dpy_webhooks
|
||||
|
||||
Per-channel Discord webhook management for discord.py: get-or-create one webhook per channel,
|
||||
persist its `{id, token}` so it survives restarts, enforce Discord's 10-per-channel cap, and
|
||||
send through it. One webhook per channel, reused across sends — instead of creating a new one
|
||||
each time (wasteful, rate-limited, and it leaks toward the cap).
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
dpy_webhooks @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_webhooks.git@v0.1.0
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```python
|
||||
from dpy_webhooks import DPYWebhooks
|
||||
|
||||
hooks = DPYWebhooks(bot) # store optional; in-memory if omitted
|
||||
await hooks.send(channel, content="hi", embeds=[e]) # get-or-create + send
|
||||
wh = await hooks.get_or_create(channel) # reuse the webhook directly
|
||||
```
|
||||
|
||||
With a persistence store so webhooks survive a restart:
|
||||
|
||||
```python
|
||||
hooks = DPYWebhooks(bot, store=my_store)
|
||||
```
|
||||
|
||||
## What you inject
|
||||
|
||||
- **`client`** — your `discord.Client` / `commands.Bot`. Used to rebuild persisted webhooks
|
||||
via `Webhook.partial(id, token, client=...)`, so discord.py supplies its own session and
|
||||
state. Requires **`discord.py>=2.2`**.
|
||||
- **`store`** (optional) — any object matching the `WebhookStore` protocol. Omit it and the
|
||||
lib uses a non-durable in-memory store (records lost on restart).
|
||||
|
||||
### The store protocol
|
||||
|
||||
```python
|
||||
class WebhookStore(Protocol):
|
||||
async def get(self, channel_id: int) -> dict | None: ... # {"webhook_id", "token", "guild_id"} or None
|
||||
async def set(self, channel_id: int, record: dict) -> None: ...
|
||||
async def delete(self, channel_id: int) -> None: ...
|
||||
```
|
||||
|
||||
A file- or mongo-backed store satisfies this — the lib depends only on the protocol, not on
|
||||
any concrete store class. `InMemoryWebhookStore` is the bundled default.
|
||||
|
||||
## API & contract
|
||||
|
||||
The module docstring (`help(dpy_webhooks)` / IDE hover) is the source of truth. In short:
|
||||
|
||||
- `get_or_create(channel, *, evict_oldest=False) -> discord.Webhook`
|
||||
- `get(channel) -> discord.Webhook | None` — cache then store; never creates
|
||||
- `send(channel, **kwargs) -> discord.WebhookMessage` — get-or-create then send, with
|
||||
self-heal
|
||||
- `clear(channel) -> None` — delete server-side + store record (idempotent)
|
||||
- `count(channel) -> int` — webhooks currently on the channel
|
||||
|
||||
**Self-heal.** If `send` hits a dead webhook (deleted server-side / invalid token), the lib
|
||||
clears the record, recreates the webhook, and retries the send **once**; a second failure
|
||||
raises loud.
|
||||
|
||||
**Fail-loud.** Nothing is swallowed to `None`. A channel already at 10 webhooks raises
|
||||
`WebhookCapacityError` (pass `evict_oldest=True` to reclaim the oldest instead). Missing
|
||||
Manage Webhooks perms propagate discord's own `Forbidden` unwrapped. Store failures propagate
|
||||
— the store owns its durability.
|
||||
|
||||
## Versioning
|
||||
|
||||
Tagged `vX.Y.Z`; pin a tag in your install line. Targets `discord.py>=2.2` (not
|
||||
`discord.py-self`).
|
||||
Reference in New Issue
Block a user