DisMessage
Pixel-perfect 1:1 Discord message & friend request renderer. Uses Discord's actual HTML+CSS (ripped from a live page export) so the output is indistinguishable from a real screenshot. Only the avatar, username, text, and badges are swapped in at render time.
It's literally Discord's UI code with different data plugged in. 🐱
Install
pip install dismessage[all]
python -m playwright install chromium
That's it. Now you can render fake Discord messages that look so real your friends will question reality.
Quick start
Fake a conversation
from datetime import datetime, timezone
from dismessage import Author, Message, render_png
foo = Author(
id="123",
name="foo",
display_name="Foo",
avatar_url="https://cdn.discordapp.com/avatars/123/abc.webp?size=80",
avatar_decoration_url="https://cdn.discordapp.com/avatar-decoration-presets/DEF.png?size=80&pas=true",
clan_tag="myguild",
clan_badge_url="https://cdn.discordapp.com/clan-badges/456/badge.png?size=16",
)
bar = Author(
id="456",
name="bar",
display_name="BarBot",
avatar_url="https://cdn.discordapp.com/avatars/456/xyz.webp?size=80",
bot=True, verified_app=True,
)
now = datetime.now(timezone.utc)
messages = [
Message(author=foo, content="hello **world** -# subtext", timestamp=now),
Message(author=bar, content="hi `code` and ||spoiler||", timestamp=now),
]
render_png(messages, "out.png")
Fake a friend request
from dismessage import Author, FriendRequest, render_friend_request_png
req = FriendRequest(
author=Author(
id="123",
name="foo", # shows as grey subtext
display_name="Foo", # shows as the main name
avatar_url="https://cdn.discordapp.com/avatars/123/abc.webp?size=32",
),
count=1,
)
render_friend_request_png(req, "request.png")
Fetch and render a REAL Discord message
from dismessage import fetch_messages, render_png
messages = fetch_messages(
token="YOUR_BOT_TOKEN",
channel_id=1532447895683596358,
message_id=1532447918504804482,
context_before=2,
context_after=2,
)
render_png(messages, "real.png")
The fetcher auto-resolves the author's avatar, profile effect (avatar decoration), guild tag, and verified-app badge from GET /users/{id}.
What it supports
Messages
- 1:1 visual fidelity — uses Discord's real DOM + CSS, not a reimplementation
- Avatar decorations (profile effects) — the overlay PNGs (cat ears, etc.)
- Guild tags (clan tags) — the chip next to the username with badge image + tag text
- Bot badges — "✓ APP" for verified bots, "APP" for unverified (no more "BOT" — Discord changed it)
- Role colors — username color from the highest role
- Twemoji support — all unicode emoji rendered as high-res SVG images from the jdecked/twemoji CDN (the exact fork Discord uses)
- Full Discord markdown:
**bold**,*italic*,__underline__,~~strikethrough~~`inline code`and```code blocks```||spoilers||(hover-to-reveal)# H1,## H2,### H3headers-# subtext(Discord's small grey text)> blockquote@everyone,@here,<@user>,<#channel>,<@&role>mentions (all rendered as blue)<:name:id>custom emoji (animated + static)- Bare URLs auto-linked
- Message grouping — consecutive messages from the same author within 5 min are grouped
- Quality control —
quality=3.0for 3x resolution,quality=1.0for low-RAM servers - Custom crop area —
crop_x,crop_y,crop_width,crop_heightparams (supports string aliases like"box_x - 16") - Persistent Chromium — browser launches once and stays open, so renders are ~0.4s instead of ~3s
Friend requests
- The "Received — 1" header + card with avatar, display name, username subtext, Accept/Ignore buttons
- No guild tag, no profile effect — matches real Discord's friend request card
API reference
Author
| Field | Type | Description |
|---|---|---|
id |
str |
User ID (used for default avatar fallback). |
name |
str |
Raw username (e.g. "cy.xn"). |
display_name |
Optional[str] |
Display name (e.g. "F² Cyanic"). Overrides name for display. |
color |
Optional[str] |
CSS color for the username (role color). |
avatar_url |
Optional[str] |
Avatar image URL. |
avatar_decoration_url |
Optional[str] |
Avatar decoration ("profile effect") PNG URL. |
clan_tag |
Optional[str] |
Guild tag text (e.g. "meow"). |
clan_badge_url |
Optional[str] |
Clan badge image URL. |
bot |
bool |
Show the "APP" badge. |
verified_app |
bool |
Add the checkmark to the APP badge. |
Message
| Field | Type | Description |
|---|---|---|
author |
Author |
The message author. |
content |
str |
Discord markdown content. |
timestamp |
Optional[datetime] |
Message timestamp (shown as "7:00 PM"). |
grouped_with_previous |
Optional[bool] |
Force grouping on/off. None = auto-detect. |
reply_to |
Optional[Message] |
Reference message (renders the slim reply header). |
accessories_html |
str |
Extra HTML to inject (embeds, attachments). |
FriendRequest
| Field | Type | Description |
|---|---|---|
author |
Author |
The user who sent the request. |
count |
int |
Number in the "Received — N" header. Default: 1. |
subtitle |
str |
Custom subtitle. Defaults to the author's username. |
Functions
| Function | Description |
|---|---|
render_html(messages, output_path=None) |
Render messages to standalone HTML. |
render_png(messages, output_path, *, quality=2.0, lite=False, persistent=True, crop_x=None, crop_y=None, crop_width=None, crop_height=None) |
Render messages to PNG via Chromium. |
render_friend_request_html(request, output_path=None) |
Render friend request card to HTML. |
render_friend_request_png(request, output_path, *, quality=2.0, ...) |
Render friend request card to PNG. |
fetch_messages(token, channel_id, message_id, ...) |
Fetch a real Discord message + context. |
render_markdown(text) |
Convert Discord markdown to HTML. |
shutdown_browser() |
Close the persistent Chromium instance. |
Crop params
Crop params accept numbers (int/float) or string aliases:
render_png(messages, "out.png",
crop_x="box_x - 16", # auto-detect element's X, minus 16px
crop_y="box_y", # auto-detect Y, no offset
crop_width="box_width + 32", # auto-detect width, plus 32px
crop_height=200, # literal number
)
If all crop params are None (default), auto-detects the message area.
Optional dependencies
| Install command | What you get |
|---|---|
pip install dismessage |
HTML output only (zero deps) |
pip install dismessage[render] |
+ PNG output (Playwright) |
pip install dismessage[fetch] |
+ fetch_messages() (httpx) |
pip install dismessage[lite] |
+ Pillow lite renderer |
pip install dismessage[all] |
Everything |
FAQ
Q: Why is the payload so big? A: It contains Discord's actual CSS (~4 MB uncompressed, ~800 KB gzip-compressed). This is the price of pixel-perfect rendering — we use Discord's real stylesheets instead of trying to reimplement them. The payload is loaded once at import time and cached in memory.
Q: Does this violate Discord's ToS? A: Probably not — it's a rendering library, not a self-bot. But don't use it to deceive people or impersonate others. Be normal.
Q: Can I use this without Chromium? A: For HTML output, yes (zero dependencies). For PNG output, you need a browser engine — there's no way around it for Discord's complex CSS. The persistent browser makes it fast (~0.4s per render after warmup).
Q: Will Discord break this? A: If Discord changes their CSS class names (which they do occasionally), the templates will need updating. Just re-export the page and rebuild the payload.
License
MIT. Go wild.
built with blood, sweat, and a lot of print() debugging by F² Cyanic
powered by Discord's actual CSS (we stole nothing, we just copied what your browser already downloaded)
⚠️ WARNING: may cause confusion, arguments, and people asking "wait is that real?"
🐱 meow
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dismessage-0.6.0.tar.gz.
File metadata
- Download URL: dismessage-0.6.0.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
eaa299182e27ab8a7a5aaf7d023b43e17e2d55cdb1040192fd530ebdec11a0f8
|
|
| MD5 |
9d7bced3f3e305079539e42c1cf4f278
|
|
| BLAKE2b-256 |
c54465808a56ef8dd6b72e8bddb9d6acf9ff0b41e319105938eeffebb5c1579a
|
File details
Details for the file dismessage-0.6.0-py3-none-any.whl.
File metadata
- Download URL: dismessage-0.6.0-py3-none-any.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea3e5f6ea9e224fa2ba3604b539dc98d4fcb24973fef7223a1d9a459cf39b56a
|
|
| MD5 |
d6a3b0d3aedc7b54a3972ee4dbc23364
|
|
| BLAKE2b-256 |
0e7a479c6878895b2f3df39d4de01592f283ec274fdc42df26c22b87cee6c80b
|