Skip to main content

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-ratio variable.
  • 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, ### H3 headers
    • -# 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 a Message to render the slim "replying to X" header.
  • Two output formats:
    • render_html() — standalone .html file (no external dependencies, opens in any browser).
    • render_png().png image rendered via headless Chromium (Playwright). Great for posting in Discord.
  • Optional Discord fetcherfetch_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 avatar hash, with a_ 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_flags bit 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"#313338 background, light text (Discord's default)
  • "darker"#1e1f22 background (Discord's "Darker" theme)
  • "light"#FFFFFF background, 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

dismessage-0.4.6.tar.gz (1.2 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dismessage-0.4.6-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

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

Hashes for dismessage-0.4.6.tar.gz
Algorithm Hash digest
SHA256 1da1fdd4b29be7d4a41308d4f0877654f31e3883da3afc54cebb18d786d0e997
MD5 e77af1bd16ba64a44e161b91066b1e22
BLAKE2b-256 54871fe3a4589e66c6ca367c4a0087372eda9bbfbcffae1d6bd0654030f6b113

See more details on using hashes here.

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

Hashes for dismessage-0.4.6-py3-none-any.whl
Algorithm Hash digest
SHA256 d2b8f42c95ecc21e20edfc4fde44cd591ccc35d797e2c7c8b4229ad4fb7f0b61
MD5 0c174a6bbb391b3a0e0f870f2786f4d3
BLAKE2b-256 3cf69c77cd2639b8024141d713e764144f22acb97a65418538dd08fe084847a9

See more details on using hashes here.

Release history Release notifications | RSS feed

0.6.1

2 files

0.6.0

2 files

0.5.2

2 files

0.5.0

2 files

0.4.8

2 files

0.4.7

2 files

This release

0.4.6 This release

2 files

0.4.2

2 files

0.4.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page