ExGram.Dsl.MessageEntityBuilder (ex_gram v0.64.0)

Copy Markdown View Source

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

Bold text.

Bot command (/start@bot).

Cashtag ($USD).

Inline code.

Concatenates a list of {text, entities} tuples into a single tuple.

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

entity()

@type entity() :: %ExGram.Model.MessageEntity{
  custom_emoji_id: term(),
  date_time_format: term(),
  language: term(),
  length: term(),
  offset: term(),
  type: term(),
  unix_time: term(),
  url: term(),
  user: term()
}

t()

@type t() :: {String.t(), [entity()]}

Functions

blockquote(inner_entity)

@spec blockquote(String.t() | t()) :: t()

Blockquote.

bold(str)

@spec bold(String.t()) :: t()

Bold text.

bot_command(str)

@spec bot_command(String.t()) :: t()

Bot command (/start@bot).

cashtag(str)

@spec cashtag(String.t()) :: t()

Cashtag ($USD).

code(str)

@spec code(String.t()) :: t()

Inline code.

concat(tuples)

@spec concat([t() | String.t()]) :: t()

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(str, custom_emoji_id)

@spec custom_emoji(String.t(), String.t()) :: t()

Custom emoji.

date_time(str, unix_time, date_time_format \\ nil)

@spec date_time(String.t(), integer(), String.t() | nil) :: t()

Date/time entity.

email(str)

@spec email(String.t()) :: t()

Email address.

empty()

@spec empty() :: t()

Empty tuple.

empty?(arg1)

@spec empty?(t()) :: boolean()

Checks if a tuple is empty (i.e. has no text and no entities).

expandable_blockquote(inner_entity)

@spec expandable_blockquote(String.t() | t()) :: t()

Expandable blockquote.

hashtag(str)

@spec hashtag(String.t()) :: t()

Hashtag (#hashtag).

italic(str)

@spec italic(String.t()) :: t()

Italic text.

join(list, separator \\ " ")

@spec join([t()], String.t()) :: t()

Joins a list of {text, entities} tuples with a separator string between them.

The separator itself carries no entities.

mention(str)

@spec mention(String.t()) :: t()

Mention (@username).

offset_entities(entities, offset)

@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(str)

@spec phone_number(String.t()) :: t()

Phone number.

pre(str, language \\ nil)

@spec pre(String.t(), String.t() | nil) :: t()

Code block (pre). Optionally specify a programming language.

slice_utf16(str, start, length)

@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"

split(message, max_length)

@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(str)

@spec spoiler(String.t()) :: t()

Spoiler text.

strikethrough(str)

@spec strikethrough(String.t()) :: t()

Strikethrough text.

text(str)

@spec text(String.t()) :: t()

Plain text with no formatting.

text_link(str, url)

@spec text_link(String.t(), String.t()) :: t()

Clickable text link.

text_mention(str, user)

@spec text_mention(String.t(), ExGram.Model.User.t()) :: t()

Text mention (for users without usernames).

trim(message)

@spec trim(t() | String.t()) :: t()

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.

trim(text, chars_to_trim)

@spec trim(t() | String.t(), String.t()) :: t()

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.

trim_leading(message)

@spec trim_leading(t() | String.t()) :: t()

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.

trim_leading(text, chars_to_trim)

@spec trim_leading(t() | String.t(), String.t()) :: t()

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.

trim_trailing(message)

@spec trim_trailing(t() | String.t()) :: t()

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.

trim_trailing(text, chars_to_trim)

@spec trim_trailing(t() | String.t(), String.t()) :: t()

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.

truncate(message, max_size, truncate_text \\ "...")

@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(str)

@spec underline(String.t()) :: t()

Underline text.

url(str)

@spec url(String.t()) :: t()

URL (clickable URL in text).

utf16_length(str)

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

wrap(entity_type, inner_entity, extra_fields \\ [])

@spec wrap(String.t(), String.t() | t(), keyword()) :: t()

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.