DisMessage
Pixel-perfect 1:1 Discord message renderer for Python. Render fake Discord conversations to standalone HTML or PNG — including avatars, avatar decorations ("profile effects"), guild tags, role colors, custom emoji, the Verified App badge, and the full Discord markdown set.
DisMessage was built by reverse-engineering Discord's actual DOM structure and CSS class names (extracted from a live page export), so the output is indistinguishable from a real Discord screenshot at a glance.
Features
- Faithful 1:1 layout — uses the exact CSS class names Discord ships (
cozy_c19a55,markup__75297,clanTagChiplet_c19a55, etc.) so the rendering matches Discord down to the pixel. - Avatar decorations ("profile effects") — overlay PNGs that sit on top of the avatar, sized correctly using Discord's
--decoration-to-avatar-ratiovariable. - Guild tags (clan tags) — the small chip next to the username with the clan badge image and tag text.
- Verified App badge — the blue "✓ APP" chip that appears on verified bots. Plain "BOT" chip for unverified bots.
- Role colors — username color picked from the highest role with a color.
- Twemoji support — all unicode emoji are rendered as high-res SVG images from the jdecked/twemoji CDN (the exact fork Discord uses). No bundled images — just CDN URLs.
- Full Discord markdown:
**bold**,*italic*,__underline__,~~strikethrough~~`inline code`and```code blocks```||spoilers||(rendered with hover-to-reveal CSS)# H1,## H2,### H3headers-# subtext(Discord's small grey text)> blockquote<@user>,<#channel>,<@&role>mentions<:name:id>custom emoji (animated and static)- Bare URLs auto-linked
- Message grouping — consecutive messages from the same author within 5 minutes are automatically grouped (no repeated avatar/header), matching Discord's behavior.
- Reply references — pass
reply_to=...on aMessageto render the slim "replying to X" header. - Two output formats:
render_html()— standalone.htmlfile (no external dependencies, opens in any browser).render_png()—.pngimage rendered via headless Chromium (Playwright). Great for posting in Discord.
- Optional Discord fetcher —
fetch_messages()calls the Discord REST API to pull a real message and auto-resolves its author's avatar, avatar decoration, clan tag, and verified-app flag.
Installation
pip install dismessage[all]
python -m playwright install chromium
(httpx is only needed if you use fetch_messages(). playwright is only needed for PNG output. HTML output works with zero dependencies.)
Quick start
Render a fake conversation
from datetime import datetime, timezone
from dismessage import Author, Message, render_png
foo = Author(
id="123", 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="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")
Fetch and render a real Discord message
from dismessage import fetch_messages, render_png
messages = fetch_messages(
token="YOUR_BOT_TOKEN", # or a user token (against ToS)
channel_id=1532447895683596358,
message_id=1532447918504804482,
context_before=2,
context_after=2,
guild_id=1532447895683596358, # required to resolve clan tags
)
render_png(messages, "real_msg.png")
Use it in a discord.py bot
See bot.py for a complete example. The gist:
import discord
from dismessage import Author, Message, render_png
@bot.tree.command()
async def fake(interaction: discord.Interaction, user: discord.User, message: str):
author = Author(
id=str(user.id),
name=user.global_name or user.name,
avatar_url=user.avatar.url if user.avatar else None,
bot=user.bot,
)
msg = Message(author=author, content=message)
render_png([msg], "/tmp/fake.png")
await interaction.response.send_message(file=discord.File("/tmp/fake.png"))
API reference
Author
| Field | Type | Description |
|---|---|---|
id |
str |
User ID (used for default avatar fallback). |
name |
str |
Global display name. |
display_name |
Optional[str] |
Guild nickname (overrides name if set). |
color |
Optional[str] |
CSS color for the username (role color). |
avatar_url |
Optional[str] |
Avatar image URL (40×40). |
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 (shown left of the tag text). |
bot |
bool |
Show the blue "APP" chip. |
verified_app |
bool |
Show the blue "✓ APP" chip (overrides bot). |
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). |
render_html(messages, output_path=None, *, group_window_seconds=300)
Render to standalone HTML. If output_path is None, returns the HTML as a string.
render_png(messages, output_path, *, lite=False, persistent=True, group_window_seconds=300, device_scale_factor=2.0, width=800)
Render to a PNG via headless Chromium. Requires Playwright + Chromium installed.
fetch_messages(token, channel_id, message_id, *, context_before=0, context_after=0, guild_id=None, resolve_clan_tag=True, resolve_avatar_decoration=True)
Fetch a real Discord message (optionally with surrounding context) and return a list of Message objects. The fetcher auto-resolves:
- Author avatar (from
avatarhash, witha_prefix → GIF) - Avatar decoration (via
GET /users/{id}/profile) - Clan tag + badge (via
GET /users/{id}/profile?with_mutual_guilds=true) - Bot flag (from
user.bot) - Verified app flag (from
user.public_flagsbit 16)
render_markdown(text)
Convert Discord markdown to HTML. Useful if you want to render just the content without the full message chrome.
Themes
Three built-in themes matching Discord's presets:
"dark"—#313338background, light text (Discord's default)"darker"—#1e1f22background (Discord's "Darker" theme)"light"—#FFFFFFbackground, dark text
To customize, monkey-patch dismessage.THEMES["dark"] with your own CSS variable map:
import dismessage
dismessage.THEMES["dark"]["--background-primary"] = "#1a1a1a"
Why does this exist?
For bots that need to render a "fake Discord screenshot" — for moderation logs, joke commands, /fake-message generators, etc. The existing options are all web-based SaaS products with rate limits and watermarks. DisMessage is a pure-Python library you can run locally.
License
MIT.
Contributing
PRs welcome. The repo includes a bot.py example that wires DisMessage into discord.py — feel free to use it as a starting point.
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.4.6.tar.gz.
File metadata
- Download URL: dismessage-0.4.6.tar.gz
- Upload date:
- Size: 1.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.13.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1da1fdd4b29be7d4a41308d4f0877654f31e3883da3afc54cebb18d786d0e997
|
|
| MD5 |
e77af1bd16ba64a44e161b91066b1e22
|
|
| BLAKE2b-256 |
54871fe3a4589e66c6ca367c4a0087372eda9bbfbcffae1d6bd0654030f6b113
|
File details
Details for the file dismessage-0.4.6-py3-none-any.whl.
File metadata
- Download URL: dismessage-0.4.6-py3-none-any.whl
- Upload date:
- Size: 1.2 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 |
d2b8f42c95ecc21e20edfc4fde44cd591ccc35d797e2c7c8b4229ad4fb7f0b61
|
|
| MD5 |
0c174a6bbb391b3a0e0f870f2786f4d3
|
|
| BLAKE2b-256 |
3cf69c77cd2639b8024141d713e764144f22acb97a65418538dd08fe084847a9
|