Metadata-Version: 2.4
Name: dismessage
Version: 0.5.0
Summary: Pixel-perfect Discord message & friend request renderer
Author: F² Cyanic
License: MIT
Keywords: discord,screenshot,fake,message,render,image,twemoji
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: render
Requires-Dist: playwright>=1.40; extra == "render"
Provides-Extra: fetch
Requires-Dist: httpx>=0.25; extra == "fetch"
Provides-Extra: lite
Requires-Dist: Pillow>=10.0; extra == "lite"
Requires-Dist: httpx>=0.25; extra == "lite"
Provides-Extra: all
Requires-Dist: playwright>=1.40; extra == "all"
Requires-Dist: httpx>=0.25; extra == "all"
Requires-Dist: Pillow>=10.0; extra == "all"
Dynamic: license-file

# 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

```bash
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

```python
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

```python
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

```python
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](https://github.com/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`, `### H3` headers
  - `-# 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.0` for 3x resolution, `quality=1.0` for low-RAM servers
- **Custom crop area** — `crop_x`, `crop_y`, `crop_width`, `crop_height` params (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:
```python
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.

---

<p align="center">
  <sub>built with blood, sweat, and a lot of <code>print()</code> debugging by <a href="#">F² Cyanic</a></sub><br>
  <sub>powered by Discord's actual CSS (we stole nothing, we just copied what your browser already downloaded)</sub><br>
  <sub>⚠️ WARNING: may cause confusion, arguments, and people asking "wait is that real?"</sub><br>
  <sub>🐱 meow</sub>
</p>
