🧩 Delimiters
Advanced • Lossless Markdown & HTML ↔ Telegram Entities for Telethon
Production-ready, round‑trip safe, two‑phase mention model — built for editors, userbots, mirrors and exporters.
✨ Table of contents
- What is delimiters?
- Why it matters
- Highlights & Features
- Badges & Compatibility
- Install
- Quickstart (three lines)
- Two‑phase mention model (v0.1.1+)
- Premium API Reference (clear & linkable)
- Recipes & Examples
- Best practices & troubleshooting
- Project layout
- Contributing & release checklist
- Author & Maintainers
- License & Links
What is delimiters? 🚀
delimiters is a focused formatting engine that converts between human‑friendly Markdown/HTML and Telegram's native MessageEntity model — reliably and without losing data. Use it whenever you need exact fidelity when sending, editing, archiving, or exporting Telegram content.
Think: "Markdown/HTML as serialization; Telegram entities as the source of truth."
Why it matters ❗
Telegram stores formatting as MessageEntity objects (e.g. MessageEntityBold, MessageEntitySpoiler) — not as Markdown or HTML text. Many tools:
- Break nested entities when editing
- Lose formatting on edits or round-trips
- Mistreat custom emojis and Unicode surrogate pairs
- Perform hidden network calls during parsing
delimiters solves these problems by:
- making parse() pure & offline,
- making mention resolution explicit (network/async),
- preserving nested entities, custom emojis, and collapsed/expanded blockquote state.
Highlights & Features ✨
- Small, focused API: parse / unparse / resolve_mentions
- Full Telegram entity coverage used in chats:
- Bold, Italic, Underline, Strike
- Inline & block code
- Inline spoilers and
<tg-spoiler> - Collapsed & expanded blockquotes (state preserved)
- Mentions (tg://user?id=...), text URLs, custom emojis (
<tg-emoji>)
- Lossless round-trip: Markdown/HTML ↔ (text + entities) ↔ Markdown/HTML
- Deterministic parsing — no network I/O during parse()
- Explicit async mention resolution matching Telethon semantics (v0.1.1+)
- Unicode- and surrogate-pair-safe
- Telethon-friendly (compatible with Telethon ≥ 1.34)
Badges & Compatibility 🧾
- PyPI: https://pypi.org/project/delimiters/
- GitHub repo: https://github.com/ankit-chaubey/delimiters
- Python 3.8+
- Telethon >= 1.34 recommended
Install 📦
From PyPI (recommended):
pip install delimiters
Or install from source:
git clone https://github.com/ankit-chaubey/delimiters.git
cd delimiters
pip install .
Quickstart — three lines ✨
from delimiters import parse, resolve_mentions
text, entities = parse("**Hi** [User](tg://user?id=93602376)")
entities = await resolve_mentions(client, entities)
await client.send_message(chat_id, text, formatting_entities=entities)
Two‑phase mention model (v0.1.1+) 🔁
delimiters intentionally mirrors Telethon: parsing and mention resolution are separate.
Phase 1 — Parsing (offline, pure)
text, entities = parse(input_text, mode="md") # or mode="html"
- Deterministic
- No network calls
- Mentions become:
MessageEntityTextUrl("tg://user?id=...")
Phase 2 — Mention resolution (async, network)
entities = await resolve_mentions(client, entities)
- Converts
MessageEntityTextUrl("tg://user?id=...")→InputMessageEntityMentionName(resolved form) - Required before sending or editing messages that should notify users or render clickable mentions
- Matches Telethon's pipeline; intentional design
Important: skip resolve_mentions and the mention stays a text URL — no notifications, no clickable @. This prevents hidden network calls during parse-time.
Premium API Reference (clear, linkable, examples) 🔎
All functions are exported from the top-level package: from delimiters import parse, unparse, resolve_mentions.
- parse(text: str, mode: Literal["md","html"]="md") -> Tuple[str, List[MessageEntity]]
- Purpose: Convert Markdown/HTML → (text, entities).
- Key behavior:
- Pure function; no network I/O.
- Mentions are left unresolved as text-URL entities (
tg://user?id=...). - Supports extended markdown tokens:
!!underline!!,||spoiler||,%%collapsed%%,^^expanded^^.
- Example:
text, entities = parse("**Hello** ||secret|| [AnKiT](tg://user?id=93602376)")
- unparse(text: str, entities: List[MessageEntity], mode: Literal["md","html"]="md") -> str
- Purpose: Convert (text + Telegram entities) → Markdown or HTML.
- Key behavior:
- Preserves nesting and entity boundaries where possible.
- Useful for round-trip editing workflows.
- Example:
md = unparse(message.text, message.entities, mode="md")
- resolve_mentions(client: TelegramClient, entities: List[MessageEntity]) -> List[Union[MessageEntity, InputMessageEntity]]
- Purpose: Convert text-URL mention entities into resolved mention entities accepted by the Telegram API.
- Key behavior:
- Async; requires an active
client(Telethon). - Uses the client to map
tg://user?id=...intoInputMessageEntityMentionNameor equivalent. - Returns entities ready to pass to Telethon
send_messageormessage.editformatting_entitiesparameter.
- Async; requires an active
- Example:
entities = await resolve_mentions(client, entities)
await client.send_message(chat_id, text, formatting_entities=entities)
Notes:
- The returned entity objects are compatible with Telethon’s API.
- If your environment does not require notify/clickable mentions (e.g., drafts or previews), you can skip the resolve step.
Recipes & Examples 🧩
Full send flow (Markdown):
from delimiters import parse, resolve_mentions
md = "**Welcome** ||this is private|| [Bob](tg://user?id=93602376)"
text, entities = parse(md)
entities = await resolve_mentions(client, entities)
await client.send_message(chat_id, text, formatting_entities=entities)
Editing with round-trip safety:
from delimiters import unparse, parse, resolve_mentions
# Get stable markdown representation of existing message
md = unparse(message.text, message.entities, mode="md")
# Make textual edits on md
md = md.replace("old", "new")
# Re-parse and resolve
text, entities = parse(md)
entities = await resolve_mentions(client, entities)
await message.edit(text, formatting_entities=entities)
HTML mode:
html = '<b>Heads up</b> <tg-spoiler>secret</tg-spoiler> <a href="tg://user?id=93602376">Eve</a>'
text, entities = parse(html, mode="html")
entities = await resolve_mentions(client, entities)
await client.send_message(chat_id, text, formatting_entities=entities)
Helper utility (combine parse+resolve):
async def parse_and_resolve(client, raw: str, mode: str = "md"):
text, entities = parse(raw, mode=mode)
return text, await resolve_mentions(client, entities)
Round-trip test (use in CI):
md = unparse(orig_text, orig_entities, mode="md")
text2, entities2 = parse(md)
# After resolve_mentions(client, entities2), compare semantics with original entities
Best practices & troubleshooting 🛠️
- Always call parse() as a pure function (no network).
- Always call resolve_mentions(...) immediately before sending/editing to ensure mentions are clickable and notify.
- Use unparse() → edit → parse() to preserve nested entities and collapsed/expanded blockquote state.
- If mentions are not notifying: make sure you resolved mentions and the client has necessary access.
- If formatting breaks on edit: ensure you pass
formatting_entities=entitieswhen editing and that entities were resolved appropriately. - Test with surrogate pairs + custom emojis during CI to avoid rendering surprises.
Project layout 🗂️
delimiters/
├── __init__.py # Public API
├── api.py # High-level parse/unparse/resolve_mentions
├── custom_markdown.py # Markdown ↔ entities rules
├── markdown_ext.py # Extra delimiters (underline, spoilers, blockquotes)
├── html_ext.py # HTML ↔ entities rules
├── tests/ # Unit tests (recommended)
├── setup.py
├── LICENSE
└── README.md
Contributing & release checklist 🤝
Contributions welcome — please open an issue first to discuss bigger changes.
PR checklist:
- Add/extend tests for Unicode, nested entities, custom emoji, collapsed quotes.
- Keep parse() offline and deterministic.
- Keep resolve_mentions() explicitly network-bound.
- Update CHANGELOG.md and bump version (semver).
- Run packaging checks:
- python -m build
- python -m pip install dist/delimiters--py3-none-any.whl
- Publish:
- twine upload dist/*
Changelog (summary) 📝
- v0.1.1 — Introduced explicit two‑phase mention resolution:
- parse() is offline-only and no longer resolves mentions.
- resolve_mentions(client, entities) added to explicitly resolve tg://user?id references to proper mention entities.
(Check CHANGELOG.md in the repo for detailed history.)
Author / Creator / Maintainer 👨💻
✨ Creator & Maintainer
Ankit Chaubey
Profile
- Email: m.ankitchaubey@gmail.com
- Telegram: @ankify
- Personal: GitHub Page
License & Links 📜
- License: MIT License © 2026 Ankit Chaubey
- PyPI: https://pypi.org/project/delimiters/
- Repository: https://github.com/ankit-chaubey/delimiters
- Issues: https://github.com/ankit-chaubey/delimiters/issues
Thank you for using delimiters! built for clarity, correctness, and production workflows ❤️
Metadata
Release files for delimiters 0.1.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| delimiters-0.1.3.tar.gz | 16.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| delimiters-0.1.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 31.1 kB
Release files / delimiters-0.1.3.tar.gz
| Download URL | delimiters-0.1.3.tar.gz |
|---|---|
| Size | 16.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a4f2b20dd42152462c51d18bfb2ad46b3c0868ef5175ec9cd3c23ee13d871e53
|
|
BLAKE2b-256 checksum How to use checksums |
80599085562fde7b5cfeef68157bfe2fe2376405816ee1a68b5e895db8fb8444
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.
Transparency logRelease files / delimiters-0.1.3-py3-none-any.whl
| Download URL | delimiters-0.1.3-py3-none-any.whl |
|---|---|
| Size | 15.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
46d69561284b5257628b112fc4eb36de5b9408f292a4bd2e172ff263d36624c4
|
|
BLAKE2b-256 checksum How to use checksums |
a60b6ef79c71f12fa3e4f789909e35a2b64b5ddd5fbfeab4592ec829d0b5c05d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 10, 2026.
Transparency log