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.
  • 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.
  • Themesdark (default), light, darker.
  • 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 httpx playwright
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

alice = Author(
    id="123", name="Alice",
    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",
)
bob = Author(
    id="456", name="BobBot",
    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=alice, content="hello **world** -# subtext", timestamp=now),
    Message(author=bob,   content="hi `code` and ||spoiler||", timestamp=now),
]

render_png(messages, "out.png", theme="dark")

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", theme="dark")

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", theme="dark")
    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 gray "BOT" 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, *, theme="dark", width=400, group_window_seconds=300)

Render to standalone HTML. If output_path is None, returns the HTML as a string.

render_png(messages, output_path, *, theme="dark", width=400, group_window_seconds=300, device_scale_factor=2.0)

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.2.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.2-py3-none-any.whl (1.2 MB view details)

Uploaded Python 3

File details

Details for the file dismessage-0.4.2.tar.gz.

File metadata

  • Download URL: dismessage-0.4.2.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.2.tar.gz
Algorithm Hash digest
SHA256 87cc23edd4283cef44ae5def004b78c7ad3e395b15441287c8cdcc35312e691d
MD5 ee12daa50e1c06fe9f5b60bfd9159517
BLAKE2b-256 05f9bbecb37dff53dc3fd4bfd31af70e046e027a0f5e3bc3aede59c41e13b4be

See more details on using hashes here.

File details

Details for the file dismessage-0.4.2-py3-none-any.whl.

File metadata

  • Download URL: dismessage-0.4.2-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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 8fa51e52802403ed9a94f00f7df28e098d919591a64fe3c4db03238976323941
MD5 9e2a2051127eb3453be6cf35b0ff39c6
BLAKE2b-256 f2caf76d2e2dac94a42cc741b4c3ba0b5d35314d1148a7ebd8a05087c36183b5

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

0.4.6

2 files

This release

0.4.2 This release

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