Skip to main content

Advanced formatting add-ons for Telethon with predictable Markdown and HTML parsing

Project description

🧩 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


License & Links 📜


Thank you for using delimiters! built for clarity, correctness, and production workflows ❤️

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

delimiters-0.1.3.tar.gz (16.1 kB view details)

Uploaded Source

Built Distribution

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

delimiters-0.1.3-py3-none-any.whl (15.0 kB view details)

Uploaded Python 3

File details

Details for the file delimiters-0.1.3.tar.gz.

File metadata

  • Download URL: delimiters-0.1.3.tar.gz
  • Upload date:
  • Size: 16.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for delimiters-0.1.3.tar.gz
Algorithm Hash digest
SHA256 a4f2b20dd42152462c51d18bfb2ad46b3c0868ef5175ec9cd3c23ee13d871e53
MD5 f51b4a13da7a8163f9effc353717df75
BLAKE2b-256 80599085562fde7b5cfeef68157bfe2fe2376405816ee1a68b5e895db8fb8444

See more details on using hashes here.

Provenance

The following attestation bundles were made for delimiters-0.1.3.tar.gz:

Publisher: publish.yml on ankit-chaubey/delimiters

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file delimiters-0.1.3-py3-none-any.whl.

File metadata

  • Download URL: delimiters-0.1.3-py3-none-any.whl
  • Upload date:
  • Size: 15.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for delimiters-0.1.3-py3-none-any.whl
Algorithm Hash digest
SHA256 46d69561284b5257628b112fc4eb36de5b9408f292a4bd2e172ff263d36624c4
MD5 89062edbc0e7c25cecba911cb6964886
BLAKE2b-256 a60b6ef79c71f12fa3e4f789909e35a2b64b5ddd5fbfeab4592ec829d0b5c05d

See more details on using hashes here.

Provenance

The following attestation bundles were made for delimiters-0.1.3-py3-none-any.whl:

Publisher: publish.yml on ankit-chaubey/delimiters

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page