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>
99 lines
4.1 KiB
Markdown
99 lines
4.1 KiB
Markdown
# 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 | 2–32 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`).
|