Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Telegram Entities Decoder Laravel Package

lucadevelop/telegram-entities-decoder

A Laravel/PHP package that decodes Telegram Bot API message entities into readable formatted text, converting bold, italic, links, mentions, code, and other entities into HTML or Markdown-ready output for bots and chat applications.

View on GitHub
Deep Wiki
Context7

Getting Started

This package wraps the Telegram Bot API for Laravel, with a message-formatting layer that builds entities rather than making you hand-escape MarkdownV2 strings.

  1. Install and publish config:

    composer require <vendor>/<package>
    php artisan vendor:publish --tag=<package>-config
    
  2. Set TELEGRAM_BOT_TOKEN (and a webhook secret if you use webhooks) in .env. Register the webhook route or point setWebhook at your app.

  3. Look first at the config file (token, default parse_mode, HTTP timeout, queue connection) and at the formatter/entities namespace. The formatter is where nearly all day-to-day work happens.

  4. First use case: send a formatted message to a chat.

    Telegram::sendMessage([
        'chat_id' => $chatId,
        'text'    => $formatter->format($entities),
    ]);
    

    In v1.4 the practical first thing to try is expandable_blockquote (collapsible quotes — good for long logs, spoilers, or "read more" blocks) and date_time (client-side localized timestamps, which render in the user's own timezone instead of yours).

Implementation Patterns

Entity-first composition. Prefer building arrays of entity objects and letting the formatter render them, rather than concatenating escaped MarkdownV2 by hand. Nesting works: put bold/code/link inside an expandable_blockquote and let the formatter handle the nested escaping.

Collapsible detail blocks. expandable_blockquote is the right tool for anything long: stack traces in an error alert, a batch job summary, a list of failed records. Keep the first line short and informative — that's the only part users see collapsed.

Deadline and scheduling messages. date_time pairs naturally with reminders and event notifications:

['type' => 'date_time', 'unix_time' => $event->starts_at->timestamp, 'format' => 'wDT']

Because rendering is client-side, the same message reads correctly for every recipient — no per-user timezone formatting on your side. Cache/replay of the message is also safe.

Ambiguous-entity separators. When you emit two adjacent entities that the Bot API could parse as one (e.g. two consecutive blockquotes, or a quote immediately followed by formatted text that looks like a continuation), the formatter now inserts ** as the separator instead of \r. Practically: you can emit sequential blockquotes and rely on the package to keep them distinct — but do not write tests that assert exact rendered output containing ** in those positions, and don't hand-craft separator logic around it.

Composition helpers. Wrap formatting in a small app-level builder (e.g. App\Support\TelegramMessage) that takes domain objects and returns [text, entities]. Keeps controller/job code free of array shapes and gives you one place to adapt when the formatter's internals change.

Async by default. Send from queued jobs with rate limiting; Telegram's per-chat limits will bite you on broadcast paths. Keep the token out of logs and out of exception messages.

Testing. Use Http::fake() (or the package's fake, if present) and assert on the outgoing payload — chat_id and entities. For rendered-text assertions, normalize whitespace and avoid pinning separator characters.

Gotchas and Tips

The separator change is a silent output change. v1.4 swapped the ambiguous-entity separator from \r to ** for MarkdownV2. Nothing in your code needs to change, but any snapshot test or cached message text containing the old \r separator will now differ. If you store rendered messages for reuse, regenerate them.

** in the middle of text is not decorative. It is a structural separator that MarkdownV2 will interpret. Never post-process or string-replace rendered output — you'll corrupt entity boundaries and get 400 Bad Request: can't parse entities.

Escaping is still your problem outside entities. Everything not covered by an entity must be escaped for MarkdownV2 (_ * [ ] ( ) ~ \ > # + - = | { } . !). If a message intermittently fails only for certain user-supplied values (a name with an underscore, a filename with a parenthesis), that's the culprit — route it through a codeortext` entity instead.

Blockquote separation fix. The "near blockquotes separation" fix in v1.4 addresses adjacent blockquotes being merged or mis-parsed. If you previously worked around this by inserting blank lines or dummy characters between quotes, remove those hacks — they now produce extra empty content.

date_time needs a real Unix timestamp and a valid format. Passing a Carbon object or an ISO string silently produces a wrong or broken entity. Use ->timestamp. Keep format strings to the documented pattern letters; unsupported specifiers fall back to default rendering.

expandable_blockquote still respects the 4096-character limit. Collapsing is a client-side display affordance, not a payload optimization. Split long content into multiple messages — split on line boundaries and never mid-entity.

Fallbacks when parse_mode is unset. If config leaves parse_mode empty, entities are ignored and users see raw markup-ish output. Set MarkdownV2 explicitly and keep it consistent between config and any per-call overrides.

Debugging parse failures. can't parse entities gives you no offset. Bisect: send the message with parse_mode stripped to find the offending entity, then re-add entities one at a time. Log the composed entity array (not the rendered string) alongside the API error.

Extension points. The formatter and entity classes are the clean seams — add your own entity builders (e.g. a mention wrapper that resolves a user model) as small classes rather than branching inside message construction. Upgrading within 1.x has been additive so far, but pin the version and re-run your payload tests on minor bumps, given that this release changed rendered output.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment
uni-method/symfony-lib
tacticmedia/scim-server-bundle
neika-ai/symfony-bundle
mattleads/agent-publishing-bundle
glavweb/framework-bundle