Files
dpy_appemojis/README.md
T
dsql 6fc4e14f3c fix: offload refresh() blocking I/O to a thread; document MissingApplicationID (v0.1.2)
refresh() did folder scan, magic-byte sniff, and per-file image reads directly
on the event loop. Offload via asyncio.to_thread so the loop stays responsive
during a large sync; behavior (validation, size cap, stem-collision and
missing-dir raises) is unchanged.

Also documents discord.MissingApplicationID in refresh()'s Raises contract -
it is a ClientException, not an HTTPException, so it was previously uncaught
by the existing except clauses and unlisted in the docstring.

Signed-off-by: disqualifier <dev@disqualifier.me>
2026-07-03 16:17:59 -04:00

99 lines
4.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# dpy_appemojis
Mirror a project folder onto your bot's **application emojis** (bot-owned, usable in any
guild the app is in — not guild emojis) for discord.py. Drop an image in `assets/emojis`,
call `refresh()`, and it becomes an application emoji named after the file. The folder is the
source of truth: a file removed means its emoji is deleted.
## Install
```
dpy_appemojis @ git+ssh://git@git.rethinkstudios.io/rethink-public/dpy_appemojis.git@v0.1.2
```
## Usage
```python
from dpy_appemojis import DPYAppEmojis
appmojis = DPYAppEmojis(bot)
await appmojis.refresh() # e.g. in setup_hook / on_ready
await ctx.send(f"done {appmojis.get.success_mark}") # dot-access -> discord.Emoji
appmojis.emoji("success_mark").url # explicit accessor
appmojis.list() # ["success_mark", ...]
```
`refresh()` makes the app's emoji set **mirror** `assets/emojis`:
- a **new file** → the emoji is created (name = filename stem)
- a **removed file** → its emoji is deleted server-side
- a **same-name** emoji → left alone (this lib does **not** diff image content; to replace an
image, rename the file or delete + re-add)
Call `refresh()` at startup, or from a command, to re-sync on demand — the lib adds no
commands of its own.
A **missing** `assets/emojis` folder is not the same as an **empty** one: if the folder
doesn't exist (wrong cwd, missing `WorkingDirectory=`/`WORKDIR`), `refresh()` raises
`DPYAppEmojisError` naming the path instead of mirroring an empty desired set — which would
otherwise delete every application emoji on the next restart. An empty-but-present folder is
still honored as "delete everything."
## Dot-access vs `emoji()`
`self.get` is a namespace whose attribute lookup returns the emoji by name. It lives apart
from the methods, so an emoji named `list` or `refresh` can't shadow them. For names that
aren't valid Python identifiers, use `emoji("name")`. `.url` comes free — `get.x` /
`emoji("x")` return a real `discord.Emoji`.
## Fixed constants (not configurable)
| | |
|---|---|
| Folder | `assets/emojis` (relative to cwd) |
| Extensions | `.png .jpg .jpeg .gif .webp` |
| Max file size | 256 KB (Discord's emoji limit) |
| Name rules | 232 chars, `[A-Za-z0-9_]` |
| App emoji cap | 2000 |
## What you inject
Your `discord.Client` / `commands.Bot`. Application-emoji methods live on the client
(`fetch_application_emojis` / `create_application_emoji`, added in discord.py 2.5), so this
targets **`discord.py>=2.5`**.
## Contract (fail-loud)
The module docstring (`help(dpy_appemojis)` / IDE hover) is the source of truth. Nothing is
swallowed:
- an invalid emoji name from a bad filename, an oversized file, or a file whose bytes aren't
a recognized PNG/JPEG/GIF/WEBP image (corrupt, truncated, or mismatched extension) raises
`ValueError` naming the file
- two files sharing a name stem across extensions (e.g. `check.png` and `check.gif`) raise
`DPYAppEmojisError` naming **both** files, instead of silently syncing one and dropping the
other
- a missing `assets/emojis` folder raises `DPYAppEmojisError` naming the path (see above)
rather than mirroring an empty set
- exceeding the 2000 cap raises `DPYAppEmojisError` **before** any create
- a discord API error (`HTTPException`, which covers `Forbidden`) propagates **unwrapped** so
a partial sync never hides behind a silent success
- `MissingApplicationID` (raised if `refresh()` runs before the client's `application_id` is
set, e.g. before `on_ready`) also propagates **unwrapped** — it is a `ClientException`, not
an `HTTPException`, so it is never caught by an `except discord.HTTPException` clause
`emoji(name)` / `get.<name>` for a name that wasn't synced raise `KeyError` / `AttributeError`
(call `refresh()` first).
## Async stance
`refresh()` offloads its blocking filesystem work (folder scan, magic-byte sniff, per-file
image read) to a worker thread via `asyncio.to_thread`, so the event loop stays responsive
during a large sync. Only the discord API calls run on the loop directly.
## Versioning
Tagged `vX.Y.Z`; pin a tag in your install line. Targets `discord.py>=2.5` (not
`discord.py-self`).