dpy_cache

Persist images/files to Discord by uploading them to a storage channel, and get back durable references — a message jump URL plus per-file CDN URLs keyed by their original filename. Discord attachments live on the CDN permanently; this lib wraps "stash it, hand me a URL" and also returns a re-resolvable reference so the ~24h URL-signature expiry never bites you.

Install

dpy_cache @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_cache.git@v0.1.0

Usage

The headline is dead-simple single-file caching:

from dpy_cache import DPYCache

cache = DPYCache(bot.get_channel(STORAGE_CHANNEL_ID))

url = (await cache.cache_one("img.png", image_bytes)).url    # raw bytes -> url
url = (await cache.cache_url("img.png", "https://...")).url  # remote url -> url

Several files at once (batches automatically over 10 per message):

result = await cache.cache({
    "avatar.png": avatar_bytes,
    "banner.png": "/tmp/banner.png",
})
result.files["avatar.png"].url      # cdn url now
result.message_url                  # jump link to the stored message

# later, if the url may have expired:
fresh = await cache.resolve(result.files["avatar.png"])

Reverse lookup a stored message back into refs:

refs = await cache.lookup(result.message_url)   # jump url, message id, or discord.Message
refs["avatar.png"].url

What you inject

A resolved Discord channel object (discord.abc.Messageable that supports .send and .fetch_message — a TextChannel or Thread). The lib holds no client and reads no global config; it calls those two methods on the channel directly. The bot must already have access to the channel — an inaccessible or bad channel raises Discord's own error on first use.

FileContent inputs

cache_one / cache accept, per file: bytes, a filesystem path (str or os.PathLike), or a discord.File. In a list input, raw bytes have no filename and raise ValueError — pass a dict {name: bytes} or a discord.File to name bytes.

Why resolve() exists

Since late 2023, Discord attachment CDN URLs are signed and the signature expires (~24h). The attachment itself is permanent; only the URL goes stale (a raw stored URL 403s after expiry). FileRef carries the message/channel/attachment ids, and resolve(ref) re-fetches the message and returns a freshly-signed URL. Use the URL immediately and you can ignore resolve(); persist a reference and you call resolve() when you read it back.

API & contract

The module docstring (help(dpy_cache) / IDE hover) is the source of truth for the full API. In short:

  • cache(files, *, content=None) -> CacheResult
  • cache_one(name, data, *, content=None) -> FileRef
  • cache_url(name, url, *, content=None) -> FileRef
  • resolve(ref) -> str
  • lookup(message) -> dict[str, FileRef]

Fail-loud. Nothing is swallowed. Lib-specific invariants raise DPYCacheError (attachment-count mismatch after a send, cache_url non-200, a lookup jump-URL pointing at a different channel than the injected one). Unnamed bytes in a list raise ValueError. Raw Discord errors (Forbidden / HTTPException / NotFound) propagate unwrapped so you can still branch on Discord's own types.

Versioning

Tagged vX.Y.Z; pin a tag in your install line. Targets discord.py>=2.0 (not discord.py-self).

S
Description
Discord-channel asset cacher for discord.py
Readme
92 KiB
Languages
Python 100%