mirror of
https://github.com/aiogram/aiogram.git
synced 2026-04-08 16:37:47 +00:00
Refactored and added docs
This commit is contained in:
parent
28e4c15d75
commit
bfa69f6deb
5 changed files with 527 additions and 126 deletions
|
|
@ -1,28 +1,19 @@
|
||||||
"""
|
import textwrap
|
||||||
Proof of Concept text decoration utility for aiogram 3.0
|
|
||||||
|
|
||||||
This part of the code is licensed under MIT as the same as aiogarm
|
|
||||||
|
|
||||||
Soon it will be moved into main package
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
|
|
||||||
>>> formatting = Text("Hello, ", Bold("World"), "!")
|
|
||||||
>>> await bot.send_message(chat_id=..., **formatting.to_kwargs())
|
|
||||||
"""
|
|
||||||
from typing import (
|
from typing import (
|
||||||
Any,
|
Any,
|
||||||
ClassVar,
|
ClassVar,
|
||||||
Dict,
|
Dict,
|
||||||
Generator,
|
Generator,
|
||||||
|
Iterable,
|
||||||
Iterator,
|
Iterator,
|
||||||
List,
|
List,
|
||||||
Optional,
|
Optional,
|
||||||
Tuple,
|
Tuple,
|
||||||
TypeVar,
|
Type,
|
||||||
Union,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
from typing_extensions import Self
|
||||||
|
|
||||||
from aiogram.enums import MessageEntityType
|
from aiogram.enums import MessageEntityType
|
||||||
from aiogram.types import MessageEntity, User
|
from aiogram.types import MessageEntity, User
|
||||||
from aiogram.utils.text_decorations import (
|
from aiogram.utils.text_decorations import (
|
||||||
|
|
@ -32,16 +23,18 @@ from aiogram.utils.text_decorations import (
|
||||||
remove_surrogates,
|
remove_surrogates,
|
||||||
)
|
)
|
||||||
|
|
||||||
NodeType = Union[str, "Node"]
|
NodeType = Any
|
||||||
|
|
||||||
NodeT = TypeVar("NodeT", bound=NodeType)
|
|
||||||
|
|
||||||
|
|
||||||
def sizeof(value: str) -> int:
|
def sizeof(value: str) -> int:
|
||||||
return len(value.encode("utf-16-le")) // 2
|
return len(value.encode("utf-16-le")) // 2
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
class Text(Iterable[NodeType]):
|
||||||
|
"""
|
||||||
|
Simple text element
|
||||||
|
"""
|
||||||
|
|
||||||
type: ClassVar[Optional[str]] = None
|
type: ClassVar[Optional[str]] = None
|
||||||
|
|
||||||
__slots__ = ("_body", "_params")
|
__slots__ = ("_body", "_params")
|
||||||
|
|
@ -51,12 +44,12 @@ class Node:
|
||||||
*body: NodeType,
|
*body: NodeType,
|
||||||
**params: Any,
|
**params: Any,
|
||||||
) -> None:
|
) -> None:
|
||||||
self._body = body
|
self._body: Tuple[NodeType, ...] = body
|
||||||
self._params = params
|
self._params: Dict[str, Any] = params
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_entities(cls, text: str, entities: List[MessageEntity]) -> "Node":
|
def from_entities(cls, text: str, entities: List[MessageEntity]) -> "Text":
|
||||||
return Node(
|
return Text(
|
||||||
*_unparse_entities(
|
*_unparse_entities(
|
||||||
text=add_surrogates(text),
|
text=add_surrogates(text),
|
||||||
entities=sorted(entities, key=lambda item: item.offset) if entities else [],
|
entities=sorted(entities, key=lambda item: item.offset) if entities else [],
|
||||||
|
|
@ -70,17 +63,26 @@ class Node:
|
||||||
_sort: bool = True,
|
_sort: bool = True,
|
||||||
_collect_entities: bool = True,
|
_collect_entities: bool = True,
|
||||||
) -> Tuple[str, List[MessageEntity]]:
|
) -> Tuple[str, List[MessageEntity]]:
|
||||||
|
"""
|
||||||
|
Render elements tree as text with entities list
|
||||||
|
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
|
||||||
text = ""
|
text = ""
|
||||||
entities = []
|
entities = []
|
||||||
offset = _offset
|
offset = _offset
|
||||||
|
|
||||||
for node in self._body:
|
for node in self._body:
|
||||||
if isinstance(node, str):
|
if not isinstance(node, Text):
|
||||||
|
node = str(node)
|
||||||
text += node
|
text += node
|
||||||
offset += sizeof(node)
|
offset += sizeof(node)
|
||||||
else:
|
else:
|
||||||
node_text, node_entities = node.render(
|
node_text, node_entities = node.render(
|
||||||
_offset=offset, _sort=False, _collect_entities=_collect_entities
|
_offset=offset,
|
||||||
|
_sort=False,
|
||||||
|
_collect_entities=_collect_entities,
|
||||||
)
|
)
|
||||||
text += node_text
|
text += node_text
|
||||||
offset += sizeof(node_text)
|
offset += sizeof(node_text)
|
||||||
|
|
@ -98,16 +100,30 @@ class Node:
|
||||||
def _render_entity(self, *, offset: int, length: int) -> MessageEntity:
|
def _render_entity(self, *, offset: int, length: int) -> MessageEntity:
|
||||||
return MessageEntity(type=self.type, offset=offset, length=length, **self._params)
|
return MessageEntity(type=self.type, offset=offset, length=length, **self._params)
|
||||||
|
|
||||||
def to_kwargs(
|
def as_kwargs(
|
||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
text_key: str = "text",
|
text_key: str = "text",
|
||||||
entities_key: str = "entities",
|
entities_key: str = "entities",
|
||||||
replace_parse_mode: bool = False,
|
replace_parse_mode: bool = True,
|
||||||
parse_mode_key: str = "parse_mode",
|
parse_mode_key: str = "parse_mode",
|
||||||
) -> Dict[str, Union[str, List[MessageEntity]]]:
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Render elements tree as keyword arguments for usage in the API call, for example:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
entities = Text(...)
|
||||||
|
await message.answer(**entities.as_kwargs())
|
||||||
|
|
||||||
|
:param text_key:
|
||||||
|
:param entities_key:
|
||||||
|
:param replace_parse_mode:
|
||||||
|
:param parse_mode_key:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
text_value, entities_value = self.render()
|
text_value, entities_value = self.render()
|
||||||
result = {
|
result: Dict[str, Any] = {
|
||||||
text_key: text_value,
|
text_key: text_value,
|
||||||
entities_key: entities_value,
|
entities_key: entities_value,
|
||||||
}
|
}
|
||||||
|
|
@ -115,17 +131,27 @@ class Node:
|
||||||
result[parse_mode_key] = None
|
result[parse_mode_key] = None
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def to_html(self) -> str:
|
def as_html(self) -> str:
|
||||||
|
"""
|
||||||
|
Render elements tree as HTML markup
|
||||||
|
"""
|
||||||
text, entities = self.render()
|
text, entities = self.render()
|
||||||
return html_decoration.unparse(text, entities)
|
return html_decoration.unparse(text, entities)
|
||||||
|
|
||||||
def to_markdown(self) -> str:
|
def as_markdown(self) -> str:
|
||||||
|
"""
|
||||||
|
Render elements tree as MarkdownV2 markup
|
||||||
|
"""
|
||||||
text, entities = self.render()
|
text, entities = self.render()
|
||||||
return markdown_decoration.unparse(text, entities)
|
return markdown_decoration.unparse(text, entities)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def as_pretty_string(self, indent: bool = False) -> str:
|
||||||
body = ", ".join(repr(item) for item in self._body)
|
sep = ",\n" if indent else ", "
|
||||||
params = ", ".join(f"{k}={v!r}" for k, v in self._params.items())
|
body = sep.join(
|
||||||
|
item.as_pretty_string(indent=indent) if isinstance(item, Text) else repr(item)
|
||||||
|
for item in self._body
|
||||||
|
)
|
||||||
|
params = sep.join(f"{k}={v!r}" for k, v in self._params.items())
|
||||||
|
|
||||||
args = []
|
args = []
|
||||||
if body:
|
if body:
|
||||||
|
|
@ -133,38 +159,40 @@ class Node:
|
||||||
if params:
|
if params:
|
||||||
args.append(params)
|
args.append(params)
|
||||||
|
|
||||||
return f"{type(self).__name__}({', '.join(args)})"
|
args_str = sep.join(args)
|
||||||
|
if indent:
|
||||||
|
args_str = textwrap.indent("\n" + args_str + "\n", " ")
|
||||||
|
return f"{type(self).__name__}({args_str})"
|
||||||
|
|
||||||
def __add__(self, other: NodeType) -> "Node":
|
def replace(self: Self, *args: Any, **kwargs: Any) -> Self:
|
||||||
if type(self) == type(other) and self._params == other._params:
|
|
||||||
return type(self)(*self._body, *other._body, **self._params)
|
|
||||||
if type(self) == Node and isinstance(other, str):
|
|
||||||
return type(self)(*self._body, other, **self._params)
|
|
||||||
return Node(self, other)
|
|
||||||
|
|
||||||
def line(self: NodeT, *nodes: NodeType) -> NodeT:
|
|
||||||
first_node = Text(self) if isinstance(self, str) else self
|
|
||||||
return first_node + Text(*nodes, "\n")
|
|
||||||
|
|
||||||
def replace(self: NodeT, *args: Any, **kwargs: Any) -> NodeT:
|
|
||||||
return type(self)(*args, **{**self._params, **kwargs})
|
return type(self)(*args, **{**self._params, **kwargs})
|
||||||
|
|
||||||
def __iter__(self) -> Iterator[NodeT]:
|
def __repr__(self) -> str:
|
||||||
return iter(self._body)
|
return self.as_pretty_string()
|
||||||
|
|
||||||
|
def __add__(self, other: NodeType) -> "Text":
|
||||||
|
if isinstance(other, Text) and other.type == self.type and self._params == other._params:
|
||||||
|
return type(self)(*self, *other, **self._params)
|
||||||
|
if type(self) == Text and isinstance(other, str):
|
||||||
|
return type(self)(*self, other, **self._params)
|
||||||
|
return Text(self, other)
|
||||||
|
|
||||||
|
def __iter__(self) -> Iterator[NodeType]:
|
||||||
|
yield from self._body
|
||||||
|
|
||||||
def __len__(self) -> int:
|
def __len__(self) -> int:
|
||||||
text, _ = self.render(_collect_entities=False)
|
text, _ = self.render(_collect_entities=False)
|
||||||
return sizeof(text)
|
return sizeof(text)
|
||||||
|
|
||||||
def __getitem__(self, item):
|
def __getitem__(self, item: slice) -> "Text":
|
||||||
# FIXME: currently is not always separate text in correct place
|
|
||||||
if not isinstance(item, slice):
|
if not isinstance(item, slice):
|
||||||
raise TypeError("Can only be sliced")
|
raise TypeError("Can only be sliced")
|
||||||
if (item.start is None or item.start == 0) and item.stop is None:
|
if (item.start is None or item.start == 0) and item.stop is None:
|
||||||
return self
|
return self
|
||||||
|
start = 0 if item.start is None else item.start
|
||||||
start = item.start or 0
|
stop = len(self) if item.stop is None else item.stop
|
||||||
stop = item.stop or len(self)
|
if start == stop:
|
||||||
|
return self.replace()
|
||||||
|
|
||||||
nodes = []
|
nodes = []
|
||||||
position = 0
|
position = 0
|
||||||
|
|
@ -177,7 +205,9 @@ class Node:
|
||||||
continue
|
continue
|
||||||
if current_position > stop:
|
if current_position > stop:
|
||||||
break
|
break
|
||||||
new_node = node[start - current_position : stop - current_position]
|
a = max((0, start - current_position))
|
||||||
|
b = min((node_size, stop - current_position))
|
||||||
|
new_node = node[a:b]
|
||||||
if not new_node:
|
if not new_node:
|
||||||
continue
|
continue
|
||||||
nodes.append(new_node)
|
nodes.append(new_node)
|
||||||
|
|
@ -185,79 +215,208 @@ class Node:
|
||||||
return self.replace(*nodes)
|
return self.replace(*nodes)
|
||||||
|
|
||||||
|
|
||||||
class HashTag(Node):
|
class HashTag(Text):
|
||||||
|
"""
|
||||||
|
Hashtag element.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The value should always start with '#' symbol
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.HASHTAG`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.HASHTAG
|
type = MessageEntityType.HASHTAG
|
||||||
|
|
||||||
|
|
||||||
class CashTag(Node):
|
class CashTag(Text):
|
||||||
|
"""
|
||||||
|
Cashtag element.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The value should always start with '$' symbol
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.CASHTAG`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.CASHTAG
|
type = MessageEntityType.CASHTAG
|
||||||
|
|
||||||
|
|
||||||
class BotCommand(Node):
|
class BotCommand(Text):
|
||||||
|
"""
|
||||||
|
Bot command element.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The value should always start with '/' symbol
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.BOT_COMMAND`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.BOT_COMMAND
|
type = MessageEntityType.BOT_COMMAND
|
||||||
|
|
||||||
|
|
||||||
class Url(Node):
|
class Url(Text):
|
||||||
|
"""
|
||||||
|
Url element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.URL`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.URL
|
type = MessageEntityType.URL
|
||||||
|
|
||||||
|
|
||||||
class Email(Node):
|
class Email(Text):
|
||||||
|
"""
|
||||||
|
Email element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.EMAIL`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.EMAIL
|
type = MessageEntityType.EMAIL
|
||||||
|
|
||||||
|
|
||||||
class PhoneNumber(Node):
|
class PhoneNumber(Text):
|
||||||
|
"""
|
||||||
|
Phone number element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.PHONE_NUMBER`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.PHONE_NUMBER
|
type = MessageEntityType.PHONE_NUMBER
|
||||||
|
|
||||||
|
|
||||||
class Bold(Node):
|
class Bold(Text):
|
||||||
|
"""
|
||||||
|
Bold element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.BOLD`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.BOLD
|
type = MessageEntityType.BOLD
|
||||||
|
|
||||||
|
|
||||||
class Italic(Node):
|
class Italic(Text):
|
||||||
|
"""
|
||||||
|
Italic element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.ITALIC`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.ITALIC
|
type = MessageEntityType.ITALIC
|
||||||
|
|
||||||
|
|
||||||
class Underline(Node):
|
class Underline(Text):
|
||||||
|
"""
|
||||||
|
Underline element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.UNDERLINE`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.UNDERLINE
|
type = MessageEntityType.UNDERLINE
|
||||||
|
|
||||||
|
|
||||||
class Strikethrough(Node):
|
class Strikethrough(Text):
|
||||||
|
"""
|
||||||
|
Strikethrough element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.STRIKETHROUGH`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.STRIKETHROUGH
|
type = MessageEntityType.STRIKETHROUGH
|
||||||
|
|
||||||
|
|
||||||
class Spoiler(Node):
|
class Spoiler(Text):
|
||||||
|
"""
|
||||||
|
Spoiler element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.SPOILER`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.SPOILER
|
type = MessageEntityType.SPOILER
|
||||||
|
|
||||||
|
|
||||||
class Code(Node):
|
class Code(Text):
|
||||||
|
"""
|
||||||
|
Code element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.CODE`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.CODE
|
type = MessageEntityType.CODE
|
||||||
|
|
||||||
|
|
||||||
class Pre(Node):
|
class Pre(Text):
|
||||||
|
"""
|
||||||
|
Pre element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.PRE`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.PRE
|
type = MessageEntityType.PRE
|
||||||
|
|
||||||
def __init__(self, *body: NodeType, language: str, **params: Any) -> None:
|
def __init__(self, *body: NodeType, language: str, **params: Any) -> None:
|
||||||
super().__init__(*body, language=language, **params)
|
super().__init__(*body, language=language, **params)
|
||||||
|
|
||||||
|
|
||||||
class TextLink(Node):
|
class TextLink(Text):
|
||||||
|
"""
|
||||||
|
Text link element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.TEXT_LINK`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.TEXT_LINK
|
type = MessageEntityType.TEXT_LINK
|
||||||
|
|
||||||
def __init__(self, *body: NodeType, url: str, **params: Any) -> None:
|
def __init__(self, *body: NodeType, url: str, **params: Any) -> None:
|
||||||
super().__init__(*body, url=url, **params)
|
super().__init__(*body, url=url, **params)
|
||||||
|
|
||||||
|
|
||||||
class TextMention(Node):
|
class TextMention(Text):
|
||||||
|
"""
|
||||||
|
Text mention element.
|
||||||
|
|
||||||
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.TEXT_MENTION`
|
||||||
|
"""
|
||||||
|
|
||||||
type = MessageEntityType.TEXT_MENTION
|
type = MessageEntityType.TEXT_MENTION
|
||||||
|
|
||||||
def __init__(self, *body: NodeType, user: User, **params: Any) -> None:
|
def __init__(self, *body: NodeType, user: User, **params: Any) -> None:
|
||||||
super().__init__(*body, user=user, **params)
|
super().__init__(*body, user=user, **params)
|
||||||
|
|
||||||
|
|
||||||
Text = Node
|
class CustomEmoji(Text):
|
||||||
Strong = Bold
|
"""
|
||||||
|
Custom emoji element.
|
||||||
|
|
||||||
NODE_TYPES = {
|
Will be wrapped into :obj:`aiogram.types.message_entity.MessageEntity`
|
||||||
|
with type :obj:`aiogram.enums.message_entity_type.MessageEntityType.CUSTOM_EMOJI`
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = MessageEntityType.CUSTOM_EMOJI
|
||||||
|
|
||||||
|
def __init__(self, *body: NodeType, emoji_id: str, **params: Any) -> None:
|
||||||
|
super().__init__(*body, emoji_id=emoji_id, **params)
|
||||||
|
|
||||||
|
|
||||||
|
NODE_TYPES: Dict[Optional[str], Type[Text]] = {
|
||||||
|
Text.type: Text,
|
||||||
HashTag.type: HashTag,
|
HashTag.type: HashTag,
|
||||||
CashTag.type: CashTag,
|
CashTag.type: CashTag,
|
||||||
BotCommand.type: BotCommand,
|
BotCommand.type: BotCommand,
|
||||||
|
|
@ -273,7 +432,6 @@ NODE_TYPES = {
|
||||||
Pre.type: Pre,
|
Pre.type: Pre,
|
||||||
TextLink.type: TextLink,
|
TextLink.type: TextLink,
|
||||||
TextMention.type: TextMention,
|
TextMention.type: TextMention,
|
||||||
Text.type: Text,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -285,7 +443,7 @@ def _apply_entity(entity: MessageEntity, *nodes: NodeType) -> NodeType:
|
||||||
:param text:
|
:param text:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
node_type = NODE_TYPES.get(entity.type, Node)
|
node_type = NODE_TYPES.get(entity.type, Text)
|
||||||
return node_type(*nodes, **entity.dict(exclude={"type", "offset", "length"}))
|
return node_type(*nodes, **entity.dict(exclude={"type", "offset", "length"}))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -317,17 +475,106 @@ def _unparse_entities(
|
||||||
yield remove_surrogates(text[offset:length])
|
yield remove_surrogates(text[offset:length])
|
||||||
|
|
||||||
|
|
||||||
def as_list(*items: NodeType) -> Node:
|
def as_line(*items: NodeType, end: str = "\n") -> Text:
|
||||||
|
"""
|
||||||
|
Wrap multiple nodes into line with :code:`\\\\n` at the end of line.
|
||||||
|
|
||||||
|
:param items: Text or Any
|
||||||
|
:param end: ending of the line, by default is :code:`\\\\n`
|
||||||
|
:return: Text
|
||||||
|
"""
|
||||||
|
return Text(*items, end)
|
||||||
|
|
||||||
|
|
||||||
|
def as_list(*items: NodeType, sep: str = "\n") -> Text:
|
||||||
|
"""
|
||||||
|
Wrap each element to separated lines
|
||||||
|
|
||||||
|
:param items:
|
||||||
|
:param sep:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
nodes = []
|
nodes = []
|
||||||
for item in items[:-1]:
|
for item in items[:-1]:
|
||||||
nodes.extend([item, "\n"])
|
nodes.extend([item, sep])
|
||||||
nodes.append(items[-1])
|
nodes.append(items[-1])
|
||||||
return Node(*nodes)
|
return Text(*nodes)
|
||||||
|
|
||||||
|
|
||||||
def as_marked_list(*items: NodeType, marker: str = "- ") -> Node:
|
def as_marked_list(*items: NodeType, marker: str = "- ") -> Text:
|
||||||
return as_list(*(Node(marker, item) for item in items))
|
"""
|
||||||
|
Wrap elements as marked list
|
||||||
|
|
||||||
|
:param items:
|
||||||
|
:param marker: line marker, by default is :code:`- `
|
||||||
|
:return: Text
|
||||||
|
"""
|
||||||
|
return as_list(*(Text(marker, item) for item in items))
|
||||||
|
|
||||||
|
|
||||||
def as_numbered_list(*items: NodeType, start: int = 1, fmt: str = "{}. ") -> Node:
|
def as_numbered_list(*items: NodeType, start: int = 1, fmt: str = "{}. ") -> Text:
|
||||||
return as_list(*(Node(fmt.format(index), item) for index, item in enumerate(items, start)))
|
"""
|
||||||
|
Wrap elements as numbered list
|
||||||
|
|
||||||
|
:param items:
|
||||||
|
:param start: initial number, by default 1
|
||||||
|
:param fmt: number format, by default :code:`{}. `
|
||||||
|
:return: Text
|
||||||
|
"""
|
||||||
|
return as_list(*(Text(fmt.format(index), item) for index, item in enumerate(items, start)))
|
||||||
|
|
||||||
|
|
||||||
|
def as_section(title: NodeType, *body: NodeType) -> Text:
|
||||||
|
"""
|
||||||
|
Wrap elements as simple section, section has title and body
|
||||||
|
|
||||||
|
:param title:
|
||||||
|
:param body:
|
||||||
|
:return: Text
|
||||||
|
"""
|
||||||
|
return Text(title, "\n", *body)
|
||||||
|
|
||||||
|
|
||||||
|
def as_marked_section(
|
||||||
|
title: NodeType,
|
||||||
|
*body: NodeType,
|
||||||
|
marker: str = "- ",
|
||||||
|
) -> Text:
|
||||||
|
"""
|
||||||
|
Wrap elements as section with marked list
|
||||||
|
|
||||||
|
:param title:
|
||||||
|
:param body:
|
||||||
|
:param marker:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return as_section(title, as_marked_list(*body, marker=marker))
|
||||||
|
|
||||||
|
|
||||||
|
def as_numbered_section(
|
||||||
|
title: NodeType,
|
||||||
|
*body: NodeType,
|
||||||
|
start: int = 1,
|
||||||
|
fmt: str = "{}. ",
|
||||||
|
) -> Text:
|
||||||
|
"""
|
||||||
|
Wrap elements as section with numbered list
|
||||||
|
|
||||||
|
:param title:
|
||||||
|
:param body:
|
||||||
|
:param start:
|
||||||
|
:param fmt:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
return as_section(title, as_numbered_list(*body, start=start, fmt=fmt))
|
||||||
|
|
||||||
|
|
||||||
|
def as_key_value(key: NodeType, value: NodeType) -> Text:
|
||||||
|
"""
|
||||||
|
Wrap elements pair as key-value line. (:code:`<b>{key}:</b> {value}`)
|
||||||
|
|
||||||
|
:param key:
|
||||||
|
:param value:
|
||||||
|
:return: Text
|
||||||
|
"""
|
||||||
|
return Text(Bold(key, ":"), " ", value)
|
||||||
|
|
|
||||||
|
|
@ -1,44 +0,0 @@
|
||||||
##################
|
|
||||||
setStickerSetThumb
|
|
||||||
##################
|
|
||||||
|
|
||||||
Returns: :obj:`bool`
|
|
||||||
|
|
||||||
.. automodule:: aiogram.methods.set_sticker_set_thumb
|
|
||||||
:members:
|
|
||||||
:member-order: bysource
|
|
||||||
:undoc-members: True
|
|
||||||
|
|
||||||
|
|
||||||
Usage
|
|
||||||
=====
|
|
||||||
|
|
||||||
As bot method
|
|
||||||
-------------
|
|
||||||
|
|
||||||
.. code-block::
|
|
||||||
|
|
||||||
result: bool = await bot.set_sticker_set_thumb(...)
|
|
||||||
|
|
||||||
|
|
||||||
Method as object
|
|
||||||
----------------
|
|
||||||
|
|
||||||
Imports:
|
|
||||||
|
|
||||||
- :code:`from aiogram.methods.set_sticker_set_thumb import SetStickerSetThumb`
|
|
||||||
- alias: :code:`from aiogram.methods import SetStickerSetThumb`
|
|
||||||
|
|
||||||
With specific bot
|
|
||||||
~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
result: bool = await bot(SetStickerSetThumb(...))
|
|
||||||
|
|
||||||
As reply into Webhook in handler
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
.. code-block:: python
|
|
||||||
|
|
||||||
return SetStickerSetThumb(...)
|
|
||||||
197
docs/utils/formatting.rst
Normal file
197
docs/utils/formatting.rst
Normal file
|
|
@ -0,0 +1,197 @@
|
||||||
|
==========
|
||||||
|
Formatting
|
||||||
|
==========
|
||||||
|
|
||||||
|
Make your message formatting flexible and simple
|
||||||
|
|
||||||
|
This instrument works on top of Message entities instead of using HTML or Markdown markups,
|
||||||
|
you can easily construct your message and sent it to the Telegram without the need to
|
||||||
|
remember tag parity (opening and closing) or escaping user input.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
=====
|
||||||
|
|
||||||
|
Basic scenario
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Construct your message and send it to the Telegram.
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
content = Text("Hello, ", Bold(message.from_user.full_name), "!")
|
||||||
|
await message.answer(**content.as_kwargs())
|
||||||
|
|
||||||
|
Is the same as the next example, but without usage markup
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
text=f"Hello, <b>{html.quote(message.from_user.full_name)}!",
|
||||||
|
parse_mode=ParseMode.HTML
|
||||||
|
)
|
||||||
|
|
||||||
|
Literally when you execute :code:`as_kwargs` method the Text object is converted
|
||||||
|
into text :code:`Hello, Alex!` with entities list :code:`[MessageEntity(type='bold', offset=7, length=4)]`
|
||||||
|
and passed into dict which can be used as :code:`**kwargs` in API call.
|
||||||
|
|
||||||
|
The complete list of elements is listed `on this page below <#available-elements>`_.
|
||||||
|
|
||||||
|
Advanced scenario
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
On top of base elements can be implemented content rendering structures,
|
||||||
|
so, out of the box aiogram has a few already implemented functions that helps you to format
|
||||||
|
your messages:
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_line
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_list
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_marked_list
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_numbered_list
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_section
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_marked_section
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_numbered_section
|
||||||
|
|
||||||
|
.. autofunction:: aiogram.utils.formatting.as_key_value
|
||||||
|
|
||||||
|
and lets complete them all:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
content = as_list(
|
||||||
|
as_marked_section(
|
||||||
|
Bold("Success:"),
|
||||||
|
"Test 1",
|
||||||
|
"Test 3",
|
||||||
|
"Test 4",
|
||||||
|
marker="✅ ",
|
||||||
|
),
|
||||||
|
as_marked_section(
|
||||||
|
Bold("Failed:"),
|
||||||
|
"Test 2",
|
||||||
|
marker="❌ ",
|
||||||
|
),
|
||||||
|
as_marked_section(
|
||||||
|
Bold("Summary:"),
|
||||||
|
as_key_value("Total", 4),
|
||||||
|
as_key_value("Success", 3),
|
||||||
|
as_key_value("Failed", 1),
|
||||||
|
marker=" ",
|
||||||
|
),
|
||||||
|
HashTag("#test"),
|
||||||
|
sep="\n\n",
|
||||||
|
)
|
||||||
|
|
||||||
|
Will be rendered into:
|
||||||
|
|
||||||
|
**Success:**
|
||||||
|
|
||||||
|
✅ Test 1
|
||||||
|
|
||||||
|
✅ Test 3
|
||||||
|
|
||||||
|
✅ Test 4
|
||||||
|
|
||||||
|
**Failed:**
|
||||||
|
|
||||||
|
❌ Test 2
|
||||||
|
|
||||||
|
**Summary:**
|
||||||
|
|
||||||
|
**Total**: 4
|
||||||
|
|
||||||
|
**Success**: 3
|
||||||
|
|
||||||
|
**Failed**: 1
|
||||||
|
|
||||||
|
#test
|
||||||
|
|
||||||
|
|
||||||
|
Or as HTML:
|
||||||
|
|
||||||
|
.. code-block:: html
|
||||||
|
|
||||||
|
<b>Success:</b>
|
||||||
|
✅ Test 1
|
||||||
|
✅ Test 3
|
||||||
|
✅ Test 4
|
||||||
|
|
||||||
|
<b>Failed:</b>
|
||||||
|
❌ Test 2
|
||||||
|
|
||||||
|
<b>Summary:</b>
|
||||||
|
<b>Total:</b> 4
|
||||||
|
<b>Success:</b> 3
|
||||||
|
<b>Failed:</b> 1
|
||||||
|
|
||||||
|
#test
|
||||||
|
|
||||||
|
Available methods
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Text
|
||||||
|
:members:
|
||||||
|
:show-inheritance:
|
||||||
|
:member-order: bysource
|
||||||
|
:special-members: __init__
|
||||||
|
|
||||||
|
|
||||||
|
Available elements
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Text
|
||||||
|
:show-inheritance:
|
||||||
|
:noindex:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.HashTag
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.CashTag
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.BotCommand
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Url
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Email
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.PhoneNumber
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Bold
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Italic
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Underline
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Strikethrough
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Spoiler
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Code
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.Pre
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.TextLink
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.TextMention
|
||||||
|
:show-inheritance:
|
||||||
|
|
||||||
|
.. autoclass:: aiogram.utils.formatting.CustomEmoji
|
||||||
|
:show-inheritance:
|
||||||
|
|
@ -9,3 +9,4 @@ Utils
|
||||||
chat_action
|
chat_action
|
||||||
web_app
|
web_app
|
||||||
callback_answer
|
callback_answer
|
||||||
|
formatting
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import pytest
|
||||||
|
|
||||||
from aiogram.utils.link import (
|
from aiogram.utils.link import (
|
||||||
BRANCH,
|
BRANCH,
|
||||||
|
create_channel_bot_link,
|
||||||
create_telegram_link,
|
create_telegram_link,
|
||||||
create_tg_link,
|
create_tg_link,
|
||||||
docs_url,
|
docs_url,
|
||||||
create_channel_bot_link,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue