Composable builder for Telegram ExGram.Model.MessageEntity formatted messages.
Instead of constructing MarkdownV2 strings with escape sequences, this module
produces {plain_text, [%ExGram.Model.MessageEntity{}]} tuples. The plain text
carries no formatting syntax; all formatting is expressed via entity annotations
with UTF-16 offsets and lengths.
Core concept
Every builder function returns a {text, entities} tuple (t/0). Caller code
builds up message content by creating these tuples and then composing them
with concat/1 or join/2. Offsets in the entities are always relative to
the beginning of the text in that specific tuple; concat/1 automatically
adjusts offsets as tuples are combined.
Example
iex> alias EntityBuilder, as: B
iex> B.concat([B.bold("Hello"), B.text(", "), B.italic("world")])
{"Hello, world", [
%ExGram.Model.MessageEntity{type: "bold", offset: 0, length: 5},
%ExGram.Model.MessageEntity{type: "italic", offset: 7, length: 5}
]}
Summary
Functions
Blockquote.
Bold text.
Bot command (/start@bot).
Cashtag ($USD).
Inline code.
Concatenates a list of {text, entities} tuples into a single tuple.
Custom emoji.
Date/time entity.
Email address.
Empty tuple.
Checks if a tuple is empty (i.e. has no text and no entities).
Expandable blockquote.
Hashtag (#hashtag).
Italic text.
Joins a list of {text, entities} tuples with a separator string between them.
Mention (@username).
Shifts all entity offsets by the given UTF-16 offset.
Phone number.
Code block (pre). Optionally specify a programming language.
Slices a string by UTF-16 code unit position.
Splits a {text, entities} tuple into a list of tuples, each with a UTF-16
length of at most max_length.
Spoiler text.
Strikethrough text.
Plain text with no formatting.
Clickable text link.
Text mention (for users without usernames).
Removes leading and trailing whitespace from a {text, entities} tuple.
Removes leading and trailing characters from a {text, entities} tuple.
Removes leading whitespace from a {text, entities} tuple.
Removes leading characters from a {text, entities} tuple.
Removes trailing whitespace from a {text, entities} tuple.
Removes trailing characters from a {text, entities} tuple.
Truncates a {text, entities} tuple so the total UTF-16 length does not exceed
max_size.
Underline text.
URL (clickable URL in text).
Returns the number of UTF-16 code units in the given string.
Wraps an existing {text, entities} tuple in an outer entity of the given type.
Types
Functions
Blockquote.
Bold text.
Bot command (/start@bot).
Cashtag ($USD).
Inline code.
Concatenates a list of {text, entities} tuples into a single tuple.
Entity offsets from later tuples are shifted by the cumulative UTF-16 length of all preceding text.
Custom emoji.
Date/time entity.
Email address.
@spec empty() :: t()
Empty tuple.
Checks if a tuple is empty (i.e. has no text and no entities).
Expandable blockquote.
Hashtag (#hashtag).
Italic text.
Joins a list of {text, entities} tuples with a separator string between them.
The separator itself carries no entities.
Mention (@username).
@spec offset_entities([entity()], non_neg_integer()) :: [entity()]
Shifts all entity offsets by the given UTF-16 offset.
Used internally by concat/1 and by callers that prepend text before
an already-built {text, entities} tuple.
Phone number.
Code block (pre). Optionally specify a programming language.
@spec slice_utf16(String.t(), non_neg_integer(), non_neg_integer()) :: String.t()
Slices a string by UTF-16 code unit position.
Takes a string str, starts at UTF-16 position start, and extracts length
UTF-16 code units. Ensures surrogate pairs are not split.
Examples
iex> slice_utf16("hello", 0, 5)
"hello"
iex> slice_utf16("hello world", 6, 5)
"world"
@spec split(t() | String.t(), pos_integer()) :: [t()]
Splits a {text, entities} tuple into a list of tuples, each with a UTF-16
length of at most max_length.
The split respects entity boundaries: if an entity would span a split point it
is moved entirely to the next part. The only exception is when the entity alone
is larger than max_length - in that case it is split at the limit (unavoidable).
Returns a list with a single element when no splitting is needed.
Spoiler text.
Strikethrough text.
Plain text with no formatting.
Clickable text link.
@spec text_mention(String.t(), ExGram.Model.User.t()) :: t()
Text mention (for users without usernames).
Removes leading and trailing whitespace from a {text, entities} tuple.
Works like String.trim/1. Entity offsets and lengths are adjusted to
reflect the trimmed text. Entities fully within trimmed regions are dropped;
entities partially overlapping are clipped.
Removes leading and trailing characters from a {text, entities} tuple.
Similar to String.trim/2, removes all leading and trailing characters that
appear in chars_to_trim. Entity offsets and lengths are adjusted to reflect
the trimmed text. Entities fully within trimmed regions are dropped; entities
partially overlapping are clipped.
Removes leading whitespace from a {text, entities} tuple.
Works like String.trim_leading/1. Entity offsets are shifted back and
entities that fall entirely within the trimmed region are dropped. Entities
that partially overlap the trim boundary have their offset set to 0 and
length reduced.
Removes leading characters from a {text, entities} tuple.
Similar to String.trim_leading/2, removes all leading characters that
appear in chars_to_trim. Entity offsets and lengths are adjusted accordingly.
Removes trailing whitespace from a {text, entities} tuple.
Works like String.trim_trailing/1. Entities that extend into the trimmed
region are clipped. Entities entirely in the trimmed region are dropped.
Removes trailing characters from a {text, entities} tuple.
Similar to String.trim_trailing/2, removes all trailing characters that
appear in chars_to_trim. Entity offsets and lengths are adjusted accordingly.
@spec truncate(t() | String.t(), non_neg_integer(), String.t()) :: t()
Truncates a {text, entities} tuple so the total UTF-16 length does not exceed
max_size.
When the text is longer than max_size, it is cut and truncate_text is
appended. truncate_text is always treated as plain text (no entity). The
max_size limit is inclusive of the truncate_text suffix, so the returned
text will never exceed max_size UTF-16 code units.
Entities are adjusted as follows:
- Entities that start at or after the cut point are dropped.
- Entities that extend past the cut point are trimmed to end exactly at the cut point.
If max_size is smaller than or equal to the length of truncate_text itself,
only the first max_size UTF-16 units of truncate_text are kept and no
original text is preserved.
Returns the tuple unchanged when no truncation is needed.
Underline text.
URL (clickable URL in text).
@spec utf16_length(String.t()) :: non_neg_integer()
Returns the number of UTF-16 code units in the given string.
This is what Telegram expects for ExGram.Model.MessageEntity offset and length fields.
Wraps an existing {text, entities} tuple in an outer entity of the given type.
The outer entity spans the entire inner text. Any extra fields (e.g. url for
text_link or language for pre) can be provided via extra_fields.