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.1

Usage

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:

hooks = DPYWebhooks(bot, store=my_store)

Work with all of a channel's existing webhooks (read live, no store, no create) — for discovery, load-spreading, or rotation:

await hooks.list(channel)                              # every live webhook on the channel
await hooks.pick(channel, strategy="round_robin")      # select one existing (None if none)
await hooks.send_any(channel, strategy="round_robin", content="hi")  # send via a picked one

pick strategies are "first" (oldest by id), "random", and "round_robin" (per-channel in-process index that wraps on the live count and rebuilds if the count changed); an unknown strategy raises ValueError. pick only considers webhooks that have a token — channel- follower and application-owned webhooks have none and can't be sent through, so they're skipped rather than picked. send_any sends through a picked existing webhook, falling back to get_or_create when the channel has none. On a dead pick, self-heal deletes that specific dead webhook (not the managed one) and recreates + retries once — unless the pick IS the managed webhook, in which case it goes through the normal clear() path. None of these three create or touch the store — they read live from the channel.

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

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
  • list(channel) -> list[discord.Webhook] — all live webhooks on the channel (no create)
  • pick(channel, *, strategy="first") -> discord.Webhook | None — select an existing one
  • send_any(channel, *, strategy="round_robin", **kwargs) -> discord.WebhookMessage — send via a picked existing webhook, falling back to get_or_create

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).

S
Description
Per-channel webhook manager for discord.py
Readme
75 KiB
Languages
Python 100%