R7 custom emoji: the server's own, added by administrators, sent on posts, profiles and reactions

LocalEmoji, a new collection (unique shortcode whatever its case, never renamed: the shortcode is its id elsewhere),
managed on /clientapi/admin/emoji/list|insert|update|delete and kept in the staff's log (emoji.insert, .update,
.delete). Its picture goes through IMediaService.EmojiImage: PNG, GIF, WebP or JPEG up to 256 KB, a PNG within 256 px
with alpha kept (an animated GIF or WebP stays an animated GIF, with a still PNG), keep=none, shrunk until it fits
GoToSocial's 100 KiB, held by ServerUse "emoji:<shortcode>" so the janitor and the audit leave it. LocalEmojis keeps the
offered ones in memory, reloaded on every change: GET /api/v1/custom_emojis is real; local posts (words, title, warning,
poll options, each revision) and profiles keep the emoji they name; Notes, Questions, actors and EmojiReacts carry
Mastodon's Emoji tag with its id, served as an Emoji at /emojis/{shortcode} (410 once deleted; the path awaits the
owner's confirmation); a persona reacts with one (Pleroma's reactions, announcements). A restore keeps the live emoji
as it keeps the settings, and trashes server pictures nothing holds. tools/pasture/scenarios/emoji.sh joins batch A.

Verified: built, and run locally against a scratch database (uploads of every type, animation kept, metadata gone,
refusals, rendering, reactions, history, announcements, delete and revive, backup and restore, media audit). The
pasture scenario and the sweep were not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WpVCoDeM1qUTRp4fXQdfof
This commit is contained in:
thepraandClaude Opus 5.5 committed 2026-10-17 05:57:25 +02:00
1 parent 9f68ca3ed0
commit d08fbabaa5
38 files changed
+1117 -54

No files matched your search

+23 -4
View File
@@ -108,9 +108,11 @@ read from documents in their published shape (`docs/INTEROP.md` records each one
instance actor follows the relays configured, and their `Announce`s are taken as their origin vouches (see "Relays")
- [FEP-8967: Generic attachments, link previews](https://codeberg.org/fediverse/fep/src/branch/main/fep/8967/fep-8967.md):
received; a `preview` builds the card
- [FEP-9098: Custom emojis](https://codeberg.org/fediverse/fep/src/branch/main/fep/9098/fep-9098.md): received, and this
server's own sent on posts, polls, actors and reactions, each served at its id (see "Custom emoji")
Planned (see `docs/ROADMAP.md`, the completion program, and the per-platform notes in `docs/INTEROP.md`): FEP-9098
custom emoji offered (R7) and FEP-8967 link attachments sent (R10).
Planned (see `docs/ROADMAP.md`, the completion program, and the per-platform notes in `docs/INTEROP.md`): FEP-8967 link
attachments sent (R10).
## Actors
@@ -130,6 +132,7 @@ actor document, a collection, NodeInfo or a delivery relates two avatars of the
| Direct-message context | `/peasants/{name}/whispers/{id}` |
| A persona's wall (FEP-400e) | `/peasants/{name}/graffiti` |
| Followers on the asking server (FEP-8fcf) | `/peasants/{name}/groupies/roll-call` |
| A custom emoji of this server (`Emoji`) | `/emojis/{shortcode}` |
| Profile and post pages | `/@{name}`, `/@{name}/{id}` |
The names are the project's own and are stable; resolve actors through WebFinger, not by guessing a path.
@@ -344,6 +347,23 @@ and naming the post answered, lets it out to its audience with `replyApproval`;
(`rejected`). An empty `canReply` refuses every reply (422).
**Custom emoji** (`Emoji` tags) are read on posts, display names, bios and profile fields, at most 64 per object.
This server's own (its administrator adds them; shortcodes of letters, digits and underscores) are sent the same way, as
Mastodon sends them, on a post or poll whose words, title, warning or options name them as `:shortcode:`, on an actor
whose name, bio or fields do, and on an `EmojiReact`:
```json
{"id": "https://privapub.thepra.dev/emojis/blobcat", "type": "Emoji", "name": ":blobcat:",
"updated": "2026-10-17T12:00:00.000Z",
"icon": {"type": "Image", "mediaType": "image/png", "url": "https://privapub.thepra.dev/media/files/2026/10/….png"}}
```
The picture is a PNG of at most 256 px a side, or an animated GIF, and at most 100 KiB (GoToSocial's limit for another
server's emoji). Its `id` serves the same object with its `@context` (`application/activity+json`, signed or not, since
an emoji tells nothing about anyone); a deleted emoji answers 410 with a `Tombstone`, and one added again under the same
shortcode comes back at the same id with a newer `updated`. A post or an actor rendered again carries the emoji as they
are then: one deleted or disabled since is left out, and a new picture has a newer `updated`. No activity is sent when
an emoji changes. A reaction with one of them has `content` `:shortcode:` and that tag.
**Profiles** keep their header, profile fields, `manuallyApprovesFollowers`, `published`, `movedTo`, `indexable`,
`memorial` and avatar and header descriptions. A post's title becomes `name` and is also the first, bold line of `content`, because Mastodon does not show
`name`. A content warning without its own text uses the title, or "Content warning".
@@ -457,7 +477,7 @@ Posts with a location (shown to nearby users of this server) never leave the ser
- **Reading our documents (SecureMode).** privapub.thepra.dev answers ActivityPub GETs only when they are signed, like
Mastodon's authorized fetch; the server's own actors (the instance actor `/peasants/privapub`, the reporter
`/peasants/privapub_reports` and the reader of feeds `/peasants/privapub_feeds`) are the exception, since their keys
are needed first.
are needed first, and so are its custom emoji at `/emojis/{shortcode}`.
A browser asking for HTML is redirected to the public page instead.
- **Posts that are not public** (followers-only, direct, circle) are served to a signed request from someone they were
for, or from the instance actor of a server where someone they were for lives, and to nobody else (404). Once deleted
@@ -483,7 +503,6 @@ Posts with a location (shown to nearby users of this server) never leave the ser
## Known limitations
- Our own posts carry no custom emoji: emoji are received and shown, not offered.
- Remote media is fetched through this server's proxy when a local client displays it. Video and audio are streamed
through it with byte ranges passed on, so a PeerTube file is never fetched whole for one viewer.
- Collections expose counts, not members: `/groupies` and `/stalking` give the same totals as the Mastodon API's