Skip to main content

🧩 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.

PyPI Downloads Python License: MIT GitHub Chat on Telegram


✨ Table of contents


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 🧾


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.

  1. 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)")
  1. 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")
  1. 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=... into InputMessageEntityMentionName or equivalent.
    • Returns entities ready to pass to Telethon send_message or message.edit formatting_entities parameter.
  • 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=entities when 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



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)

Source distribution for delimiters 0.1.3
File Size Uploaded
delimiters-0.1.3.tar.gz 16.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for delimiters 0.1.3
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.1.3 This release

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release 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