telekit 2.6.0a2__tar.gz → 2.6.0a3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {telekit-2.6.0a2 → telekit-2.6.0a3}/PKG-INFO +10 -4
- {telekit-2.6.0a2 → telekit-2.6.0a3}/setup.py +2 -2
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_buildtext/styles.py +484 -1
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chain_inline_keyboards_logic.py +4 -4
- telekit-2.6.0a3/telekit/_rich.py +381 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_version.py +1 -1
- telekit-2.6.0a3/telekit/example/example_handlers/rich.py +555 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/start.py +3 -1
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/senders.py +243 -47
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/styles.py +31 -6
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit.egg-info/PKG-INFO +10 -4
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit.egg-info/SOURCES.txt +2 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit.egg-info/requires.txt +1 -1
- {telekit-2.6.0a2 → telekit-2.6.0a3}/LICENSE +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/README.md +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/setup.cfg +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_buildtext/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_buildtext/formatter.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_callback_query_handler.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chain.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chain_base.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chain_entry_logic.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chapters/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_chapters/chapters.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_handler.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_init.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_inline_buttons.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_inline_keyboard.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_input_handler.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_logger.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_on.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_reply_keyboard.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_snapvault/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_snapvault/snapcode.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_snapvault/snapvault.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_state.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/mixin.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/builder.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/lexer.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/nodes.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/parser.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/parser/token.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_telekit_dsl/telekit_orm.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_text_builder.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_timeout.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_trait.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/_user.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/chat.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/debug.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/dices.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/article.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/calendar.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/complete_hotel.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/counter.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/dsl.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/entry.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/faq.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/hotel.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/on_text.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/pages.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/pyapi.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/qr.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/quiz.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/spells.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/style.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_handlers/text_document.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/example/example_server.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/html_text.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/inline_buttons.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/parameters.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/reply_buttons.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/scheduler.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/server.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/traits/__init__.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/traits/calendar_pick.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/traits/paginated_choice.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/traits/paginated_text.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/traits/track_handoff_origin.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/types.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit/utils.py +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit.egg-info/dependency_links.txt +0 -0
- {telekit-2.6.0a2 → telekit-2.6.0a3}/telekit.egg-info/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: telekit
|
|
3
|
-
Version: 2.6.
|
|
4
|
-
Summary: Declarative, developer-friendly
|
|
3
|
+
Version: 2.6.0a3
|
|
4
|
+
Summary: Declarative, developer-friendly framework for building Telegram bots
|
|
5
5
|
Home-page: https://github.com/Romashkaa/telekit
|
|
6
6
|
Author: romashka
|
|
7
7
|
Author-email: notromashka@gmail.com
|
|
@@ -20,7 +20,7 @@ Description-Content-Type: text/markdown
|
|
|
20
20
|
License-File: LICENSE
|
|
21
21
|
Requires-Dist: charset_normalizer==3.4.2
|
|
22
22
|
Requires-Dist: Jinja2==3.1.6
|
|
23
|
-
Requires-Dist: pyTelegramBotAPI==4.
|
|
23
|
+
Requires-Dist: pyTelegramBotAPI==4.37.0
|
|
24
24
|
Dynamic: author
|
|
25
25
|
Dynamic: author-email
|
|
26
26
|
Dynamic: classifier
|
|
@@ -471,7 +471,13 @@ Telekit focuses on one job: making Telegram bot development easier.
|
|
|
471
471
|
|
|
472
472
|
---
|
|
473
473
|
|
|
474
|
-
# Changes in version 2.6.
|
|
474
|
+
# Changes in version 2.6.0a3
|
|
475
|
+
|
|
476
|
+
### v2.6.0 `a3`
|
|
477
|
+
- Added support for Rich HTML messages (Bot API 10.1+) and enhance styles
|
|
478
|
+
- Introduced a new module `_rich.py` to handle Rich HTML parsing and serialization.
|
|
479
|
+
- Updated `styles.py` to include new rich tags for enhanced text formatting.
|
|
480
|
+
- Created `rich.py` example handler to demonstrate the usage of Rich HTML features, including inline styles, blocks, lists, tables, media, and sender integration.
|
|
475
481
|
|
|
476
482
|
### v2.6.0 `a2`
|
|
477
483
|
- Reworked `.env` and token/canvas file reading in `utils`: full `.env` syntax support (comments, `export`, quotes and escapes, multi-line values, `$VAR` interpolation), a `cache` parameter (default `True`) with `clear_cache()`, detailed errors with fix suggestions and creation commands, and new `Env*Error` exceptions; `load_env` now raises `EnvFileNotFoundError` for a missing file instead of returning `{}`
|
|
@@ -45,7 +45,7 @@ def long_description():
|
|
|
45
45
|
setup(
|
|
46
46
|
name='telekit',
|
|
47
47
|
version=version,
|
|
48
|
-
description='Declarative, developer-friendly
|
|
48
|
+
description='Declarative, developer-friendly framework for building Telegram bots',
|
|
49
49
|
long_description=long_description(),
|
|
50
50
|
long_description_content_type='text/markdown',
|
|
51
51
|
keywords='telegram bot api declarative tools bot-api',
|
|
@@ -70,4 +70,4 @@ setup(
|
|
|
70
70
|
}
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
-
print(f"TELEKIT VERSION: {version}")
|
|
73
|
+
print(f"TELEKIT VERSION: {version}")
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
#
|
|
19
19
|
from typing import Union, Literal, TYPE_CHECKING, Union, Any
|
|
20
20
|
from urllib.parse import quote
|
|
21
|
+
import html as _html
|
|
21
22
|
|
|
22
23
|
if TYPE_CHECKING: # Union[str, "TextEntity", "Template"]
|
|
23
24
|
from string.templatelib import Template # pyright: ignore[reportMissingImports]
|
|
@@ -728,6 +729,459 @@ class Stack(TextEntity):
|
|
|
728
729
|
return f"\n{content}{end}\n"
|
|
729
730
|
|
|
730
731
|
|
|
732
|
+
|
|
733
|
+
# ---------------------------------------------------------------------------------
|
|
734
|
+
# Rich HTML styles (Bot API 10.1+). Use together with sender.set_rich_html(True)
|
|
735
|
+
# ---------------------------------------------------------------------------------
|
|
736
|
+
|
|
737
|
+
def _attrs(**attrs) -> str:
|
|
738
|
+
"""Builds an HTML attribute string. `True` -> bare attribute, None/False -> skipped."""
|
|
739
|
+
parts = []
|
|
740
|
+
for key, value in attrs.items():
|
|
741
|
+
if value is None or value is False:
|
|
742
|
+
continue
|
|
743
|
+
key = key.replace("_", "-")
|
|
744
|
+
if value is True:
|
|
745
|
+
parts.append(key)
|
|
746
|
+
else:
|
|
747
|
+
parts.append(f'{key}="{_html.escape(str(value), quote=True)}"')
|
|
748
|
+
return (" " + " ".join(parts)) if parts else ""
|
|
749
|
+
|
|
750
|
+
|
|
751
|
+
class RichEntity(EasyTextEntity):
|
|
752
|
+
"""
|
|
753
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
754
|
+
|
|
755
|
+
Base class for styles that exist only in Rich HTML.
|
|
756
|
+
Outside of rich mode the sender downgrades these tags to plain formatting
|
|
757
|
+
(or drops them) and prints a warning.
|
|
758
|
+
"""
|
|
759
|
+
|
|
760
|
+
|
|
761
|
+
# -- inline
|
|
762
|
+
|
|
763
|
+
class Marked(RichEntity):
|
|
764
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
765
|
+
|
|
766
|
+
Highlighted text: ``<mark>``."""
|
|
767
|
+
def _render_html(self, content: str) -> str:
|
|
768
|
+
return f"<mark>{content}</mark>"
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
class Subscript(RichEntity):
|
|
772
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
773
|
+
|
|
774
|
+
Subscript: ``<sub>``."""
|
|
775
|
+
def _render_html(self, content: str) -> str:
|
|
776
|
+
return f"<sub>{content}</sub>"
|
|
777
|
+
|
|
778
|
+
|
|
779
|
+
class Superscript(RichEntity):
|
|
780
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
781
|
+
|
|
782
|
+
Superscript: ``<sup>``."""
|
|
783
|
+
def _render_html(self, content: str) -> str:
|
|
784
|
+
return f"<sup>{content}</sup>"
|
|
785
|
+
|
|
786
|
+
|
|
787
|
+
class CustomEmoji(EasyTextEntity):
|
|
788
|
+
"""
|
|
789
|
+
Custom emoji: ``<tg-emoji emoji-id="...">fallback</tg-emoji>``.
|
|
790
|
+
Works both in regular HTML and in Rich HTML. The content is the fallback emoji.
|
|
791
|
+
|
|
792
|
+
Example::
|
|
793
|
+
|
|
794
|
+
CustomEmoji("👍", emoji_id=5368324170671202286)
|
|
795
|
+
"""
|
|
796
|
+
def __init__(self, *content, emoji_id: int | str, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
797
|
+
self._emoji_id = emoji_id
|
|
798
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
799
|
+
|
|
800
|
+
def _render_html(self, content: str) -> str:
|
|
801
|
+
return f"<tg-emoji{_attrs(emoji_id=self._emoji_id)}>{content}</tg-emoji>"
|
|
802
|
+
|
|
803
|
+
|
|
804
|
+
class DateTime(RichEntity):
|
|
805
|
+
"""
|
|
806
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
807
|
+
|
|
808
|
+
Formatted date and time: ``<tg-time unix="..." format="...">``.
|
|
809
|
+
|
|
810
|
+
Example::
|
|
811
|
+
|
|
812
|
+
DateTime("22:45 tomorrow", unix=1647531900, format="wDT")
|
|
813
|
+
"""
|
|
814
|
+
def __init__(self, *content, unix: int, format: str | None = None, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
815
|
+
self._unix = unix
|
|
816
|
+
self._format = format
|
|
817
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
818
|
+
|
|
819
|
+
def _render_html(self, content: str) -> str:
|
|
820
|
+
return f"<tg-time{_attrs(unix=self._unix, format=self._format)}>{content}</tg-time>"
|
|
821
|
+
|
|
822
|
+
|
|
823
|
+
class Math(RichEntity):
|
|
824
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
825
|
+
|
|
826
|
+
Inline LaTeX formula: ``<tg-math>``. The content is treated as raw LaTeX."""
|
|
827
|
+
def _render_html(self, content: str) -> str:
|
|
828
|
+
return f"<tg-math>{content}</tg-math>"
|
|
829
|
+
|
|
830
|
+
|
|
831
|
+
class MathBlock(RichEntity):
|
|
832
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
833
|
+
|
|
834
|
+
Block LaTeX formula: ``<tg-math-block>``."""
|
|
835
|
+
def _render_html(self, content: str) -> str:
|
|
836
|
+
return f"<tg-math-block>{content}</tg-math-block>"
|
|
837
|
+
|
|
838
|
+
|
|
839
|
+
class Anchor(RichEntity):
|
|
840
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
841
|
+
|
|
842
|
+
Anchor definition: ``<a name="..."></a>``. Target for ``AnchorLink``."""
|
|
843
|
+
def __init__(self, name: str, *, enabled: bool | Any = True):
|
|
844
|
+
self._name = name
|
|
845
|
+
super().__init__(enabled=enabled)
|
|
846
|
+
|
|
847
|
+
def _render_html(self, content: str) -> str:
|
|
848
|
+
return f"<a{_attrs(name=self._name)}></a>"
|
|
849
|
+
|
|
850
|
+
|
|
851
|
+
class AnchorLink(RichEntity):
|
|
852
|
+
"""
|
|
853
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
854
|
+
|
|
855
|
+
Link to an anchor or to a reference: ``<a href="#name">``.
|
|
856
|
+
An empty ``name`` links back to the top of the message.
|
|
857
|
+
"""
|
|
858
|
+
def __init__(self, *content, name: str = "", escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
859
|
+
self._name = name
|
|
860
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
861
|
+
|
|
862
|
+
def _render_html(self, content: str) -> str:
|
|
863
|
+
return f"<a{_attrs(href='#' + self._name)}>{content}</a>"
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
class Reference(RichEntity):
|
|
867
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
868
|
+
|
|
869
|
+
Footnote definition: ``<tg-reference name="...">``. Link to it with ``AnchorLink``."""
|
|
870
|
+
def __init__(self, *content, name: str, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
871
|
+
self._name = name
|
|
872
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
873
|
+
|
|
874
|
+
def _render_html(self, content: str) -> str:
|
|
875
|
+
return f"<tg-reference{_attrs(name=self._name)}>{content}</tg-reference>"
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
# -- text blocks
|
|
879
|
+
|
|
880
|
+
class Heading(RichEntity):
|
|
881
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
882
|
+
|
|
883
|
+
Heading ``<h1>``..``<h6>``; ``level=1`` is the largest."""
|
|
884
|
+
def __init__(self, *content, level: int = 1, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
885
|
+
if not 1 <= level <= 6:
|
|
886
|
+
raise ValueError("Heading level must be between 1 and 6")
|
|
887
|
+
self._level = level
|
|
888
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
889
|
+
|
|
890
|
+
def _render_html(self, content: str) -> str:
|
|
891
|
+
return f"<h{self._level}>{content}</h{self._level}>"
|
|
892
|
+
|
|
893
|
+
def _render_markdown(self, content: str) -> str:
|
|
894
|
+
return telebot.formatting.mbold(content, escape=False) + "\n"
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
class Paragraph(RichEntity):
|
|
898
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
899
|
+
|
|
900
|
+
Paragraph: ``<p>``."""
|
|
901
|
+
def _render_html(self, content: str) -> str:
|
|
902
|
+
return f"<p>{content}</p>"
|
|
903
|
+
|
|
904
|
+
|
|
905
|
+
class Footer(RichEntity):
|
|
906
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
907
|
+
|
|
908
|
+
Footer: ``<footer>``."""
|
|
909
|
+
def _render_html(self, content: str) -> str:
|
|
910
|
+
return f"<footer>{content}</footer>"
|
|
911
|
+
|
|
912
|
+
|
|
913
|
+
class Divider(RichEntity):
|
|
914
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
915
|
+
|
|
916
|
+
Horizontal divider: ``<hr/>``."""
|
|
917
|
+
def __init__(self, *, enabled: bool | Any = True):
|
|
918
|
+
super().__init__(enabled=enabled)
|
|
919
|
+
|
|
920
|
+
def _render_html(self, content: str) -> str:
|
|
921
|
+
return "<hr/>"
|
|
922
|
+
|
|
923
|
+
def _render_markdown(self, content: str) -> str:
|
|
924
|
+
return "———\n"
|
|
925
|
+
|
|
926
|
+
def _render_none(self, content: str) -> str:
|
|
927
|
+
return "———\n"
|
|
928
|
+
|
|
929
|
+
|
|
930
|
+
class Cite(RichEntity):
|
|
931
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
932
|
+
|
|
933
|
+
Author/credit line: ``<cite>``. Use inside ``Quote`` or ``PullQuote``."""
|
|
934
|
+
def _render_html(self, content: str) -> str:
|
|
935
|
+
return f"<cite>{content}</cite>"
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
class PullQuote(RichEntity):
|
|
939
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
940
|
+
|
|
941
|
+
Centered pull quotation: ``<aside>`` with an optional credit."""
|
|
942
|
+
def __init__(self, *content, credit: Union[str, "TextEntity", "Template", None] = None, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
943
|
+
self._credit = credit
|
|
944
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
945
|
+
|
|
946
|
+
def _render_html(self, content: str) -> str:
|
|
947
|
+
credit = ""
|
|
948
|
+
if self._credit is not None:
|
|
949
|
+
credit = f"<cite>{self._render_item(self._credit, 'html')}</cite>"
|
|
950
|
+
return f"<aside>{content}{credit}</aside>"
|
|
951
|
+
|
|
952
|
+
|
|
953
|
+
class Details(RichEntity):
|
|
954
|
+
"""
|
|
955
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
956
|
+
|
|
957
|
+
Collapsible block: ``<details><summary>``.
|
|
958
|
+
|
|
959
|
+
Example::
|
|
960
|
+
|
|
961
|
+
Details("Hidden text", summary="Click me", is_open=False)
|
|
962
|
+
"""
|
|
963
|
+
def __init__(self, *content, summary: Union[str, "TextEntity", "Template"], is_open: bool = False, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
964
|
+
self._summary = summary
|
|
965
|
+
self._is_open = is_open
|
|
966
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
967
|
+
|
|
968
|
+
def _render_html(self, content: str) -> str:
|
|
969
|
+
summary = self._render_item(self._summary, "html")
|
|
970
|
+
return f"<details{_attrs(open=self._is_open)}><summary>{summary}</summary>{content}</details>"
|
|
971
|
+
|
|
972
|
+
|
|
973
|
+
# -- lists
|
|
974
|
+
|
|
975
|
+
class ListItem(RichEntity):
|
|
976
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
977
|
+
|
|
978
|
+
List item ``<li>`` with optional explicit ``value`` and ``type`` (ordered lists)."""
|
|
979
|
+
def __init__(self, *content, value: int | None = None, type: str | None = None, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
980
|
+
self._value = value
|
|
981
|
+
self._type = type
|
|
982
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
983
|
+
|
|
984
|
+
def _render_html(self, content: str) -> str:
|
|
985
|
+
return f"<li{_attrs(value=self._value, type=self._type)}>{content}</li>"
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
class _RichList(RichEntity):
|
|
989
|
+
_ordered = False
|
|
990
|
+
|
|
991
|
+
def _render_content(self, parse_mode):
|
|
992
|
+
if parse_mode == "html":
|
|
993
|
+
return "".join(
|
|
994
|
+
self._render_item(item, parse_mode) if isinstance(item, ListItem)
|
|
995
|
+
else f"<li>{self._render_item(item, parse_mode)}</li>"
|
|
996
|
+
for item in self._content
|
|
997
|
+
)
|
|
998
|
+
lines = []
|
|
999
|
+
for index, item in enumerate(self._content, start=1):
|
|
1000
|
+
marker = f"{index}. " if self._ordered else "• "
|
|
1001
|
+
lines.append(self._escape(marker, parse_mode) + self._render_item(item, parse_mode))
|
|
1002
|
+
return "\n".join(lines)
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
class UnorderedList(_RichList):
|
|
1006
|
+
"""
|
|
1007
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1008
|
+
|
|
1009
|
+
Bulleted list: ``<ul>``. Items are strings/entities or ``ListItem``.
|
|
1010
|
+
|
|
1011
|
+
Example::
|
|
1012
|
+
|
|
1013
|
+
UnorderedList("One", Bold("Two"), ListItem("Three"))
|
|
1014
|
+
"""
|
|
1015
|
+
def _render_html(self, content: str) -> str:
|
|
1016
|
+
return f"<ul>{content}</ul>"
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
class OrderedList(_RichList):
|
|
1020
|
+
"""
|
|
1021
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1022
|
+
|
|
1023
|
+
Numbered list: ``<ol>``.
|
|
1024
|
+
|
|
1025
|
+
:param start: First number.
|
|
1026
|
+
:param type: ``"1"``, ``"a"``, ``"A"``, ``"i"`` or ``"I"``.
|
|
1027
|
+
:param reversed: Count backwards.
|
|
1028
|
+
"""
|
|
1029
|
+
_ordered = True
|
|
1030
|
+
|
|
1031
|
+
def __init__(self, *items, start: int | None = None, type: str | None = None, reversed: bool = False, escape: bool = True, enabled: bool | Any = True):
|
|
1032
|
+
self._start = start
|
|
1033
|
+
self._type = type
|
|
1034
|
+
self._reversed = reversed
|
|
1035
|
+
super().__init__(*items, escape=escape, enabled=enabled)
|
|
1036
|
+
|
|
1037
|
+
def _render_html(self, content: str) -> str:
|
|
1038
|
+
attrs = _attrs(start=self._start, type=self._type, reversed=self._reversed)
|
|
1039
|
+
return f"<ol{attrs}>{content}</ol>"
|
|
1040
|
+
|
|
1041
|
+
|
|
1042
|
+
# -- tables
|
|
1043
|
+
|
|
1044
|
+
class TableCell(RichEntity):
|
|
1045
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1046
|
+
|
|
1047
|
+
Table cell ``<td>`` / ``<th>`` (``header=True``). Cells may contain only inline formatting."""
|
|
1048
|
+
def __init__(self, *content, header: bool = False, colspan: int | None = None, rowspan: int | None = None, align: Literal["left", "center", "right"] | None = None, valign: Literal["top", "middle", "bottom"] | None = None, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = "", enabled: bool | Any = True):
|
|
1049
|
+
self._header = header
|
|
1050
|
+
self._span = {"colspan": colspan, "rowspan": rowspan, "align": align, "valign": valign}
|
|
1051
|
+
super().__init__(*content, escape=escape, sep=sep, enabled=enabled)
|
|
1052
|
+
|
|
1053
|
+
def _render_html(self, content: str) -> str:
|
|
1054
|
+
tag = "th" if self._header else "td"
|
|
1055
|
+
return f"<{tag}{_attrs(**self._span)}>{content}</{tag}>"
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
class Table(RichEntity):
|
|
1059
|
+
"""
|
|
1060
|
+
⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1061
|
+
|
|
1062
|
+
Table: ``<table>``. Each row is a list/tuple of cells (strings, entities or ``TableCell``).
|
|
1063
|
+
|
|
1064
|
+
:param header: Render the first row as header cells.
|
|
1065
|
+
|
|
1066
|
+
Example::
|
|
1067
|
+
|
|
1068
|
+
Table(["Name", "Score"], ["Ann", "10"], ["Bob", "7"], header=True, bordered=True)
|
|
1069
|
+
"""
|
|
1070
|
+
def __init__(self, *rows, header: bool = False, bordered: bool = False, striped: bool = False, caption: Union[str, "TextEntity", "Template", None] = None, escape: bool = True, enabled: bool | Any = True):
|
|
1071
|
+
self._header = header
|
|
1072
|
+
self._bordered = bordered
|
|
1073
|
+
self._striped = striped
|
|
1074
|
+
self._caption = caption
|
|
1075
|
+
super().__init__(*rows, escape=escape, enabled=enabled)
|
|
1076
|
+
|
|
1077
|
+
def _render_content(self, parse_mode):
|
|
1078
|
+
is_html = parse_mode == "html"
|
|
1079
|
+
lines = []
|
|
1080
|
+
|
|
1081
|
+
if self._caption is not None:
|
|
1082
|
+
caption = self._render_item(self._caption, parse_mode)
|
|
1083
|
+
lines.append(f"<caption>{caption}</caption>" if is_html else caption)
|
|
1084
|
+
|
|
1085
|
+
for row_index, row in enumerate(self._content):
|
|
1086
|
+
cells = []
|
|
1087
|
+
for cell in row:
|
|
1088
|
+
rendered = self._render_item(cell, parse_mode)
|
|
1089
|
+
if is_html and not isinstance(cell, TableCell):
|
|
1090
|
+
tag = "th" if (self._header and row_index == 0) else "td"
|
|
1091
|
+
rendered = f"<{tag}>{rendered}</{tag}>"
|
|
1092
|
+
cells.append(rendered)
|
|
1093
|
+
|
|
1094
|
+
if is_html:
|
|
1095
|
+
lines.append("<tr>" + "".join(cells) + "</tr>")
|
|
1096
|
+
else:
|
|
1097
|
+
lines.append(self._escape(" | ", parse_mode).join(cells))
|
|
1098
|
+
|
|
1099
|
+
return ("" if is_html else "\n").join(lines)
|
|
1100
|
+
|
|
1101
|
+
def _render_html(self, content: str) -> str:
|
|
1102
|
+
return f"<table{_attrs(bordered=self._bordered, striped=self._striped)}>{content}</table>"
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
# -- media (http/https URLs only)
|
|
1106
|
+
|
|
1107
|
+
class _RichMedia(RichEntity):
|
|
1108
|
+
def __init__(self, url: str, *, caption: Union[str, "TextEntity", "Template", None] = None, credit: Union[str, "TextEntity", "Template", None] = None, spoiler: bool = False, enabled: bool | Any = True):
|
|
1109
|
+
self._url = url
|
|
1110
|
+
self._caption = caption
|
|
1111
|
+
self._credit = credit
|
|
1112
|
+
self._spoiler = spoiler
|
|
1113
|
+
super().__init__(enabled=enabled)
|
|
1114
|
+
|
|
1115
|
+
def _media(self) -> str:
|
|
1116
|
+
raise NotImplementedError
|
|
1117
|
+
|
|
1118
|
+
def _render_html(self, content: str) -> str:
|
|
1119
|
+
media = self._media()
|
|
1120
|
+
if self._caption is None and self._credit is None:
|
|
1121
|
+
return media
|
|
1122
|
+
|
|
1123
|
+
caption = self._render_item(self._caption, "html") if self._caption is not None else ""
|
|
1124
|
+
credit = f"<cite>{self._render_item(self._credit, 'html')}</cite>" if self._credit is not None else ""
|
|
1125
|
+
return f"<figure>{media}<figcaption>{caption}{credit}</figcaption></figure>"
|
|
1126
|
+
|
|
1127
|
+
def _media_attrs(self) -> str:
|
|
1128
|
+
return _attrs(src=self._url, **{"tg-spoiler": self._spoiler})
|
|
1129
|
+
|
|
1130
|
+
|
|
1131
|
+
class Image(_RichMedia):
|
|
1132
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1133
|
+
|
|
1134
|
+
Photo block: ``<img>``; with ``caption``/``credit`` it is wrapped into ``<figure>``."""
|
|
1135
|
+
def _media(self) -> str:
|
|
1136
|
+
return f"<img{self._media_attrs()}/>"
|
|
1137
|
+
|
|
1138
|
+
|
|
1139
|
+
class Video(_RichMedia):
|
|
1140
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1141
|
+
|
|
1142
|
+
Video block: ``<video>``."""
|
|
1143
|
+
def _media(self) -> str:
|
|
1144
|
+
return f"<video{self._media_attrs()}></video>"
|
|
1145
|
+
|
|
1146
|
+
|
|
1147
|
+
class Audio(_RichMedia):
|
|
1148
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1149
|
+
|
|
1150
|
+
Audio block: ``<audio>`` (.ogg is shown as a voice note)."""
|
|
1151
|
+
def _media(self) -> str:
|
|
1152
|
+
return f"<audio{self._media_attrs()}></audio>"
|
|
1153
|
+
|
|
1154
|
+
|
|
1155
|
+
class Map(RichEntity):
|
|
1156
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1157
|
+
|
|
1158
|
+
Map block: ``<tg-map>``. ``zoom`` must be 13–20."""
|
|
1159
|
+
def __init__(self, lat: float, long: float, *, zoom: int = 15, enabled: bool | Any = True):
|
|
1160
|
+
if not 13 <= zoom <= 20:
|
|
1161
|
+
raise ValueError("Map zoom must be between 13 and 20")
|
|
1162
|
+
self._lat, self._long, self._zoom = lat, long, zoom
|
|
1163
|
+
super().__init__(enabled=enabled)
|
|
1164
|
+
|
|
1165
|
+
def _render_html(self, content: str) -> str:
|
|
1166
|
+
return f"<tg-map{_attrs(lat=self._lat, long=self._long, zoom=self._zoom)}/>"
|
|
1167
|
+
|
|
1168
|
+
|
|
1169
|
+
class Collage(RichEntity):
|
|
1170
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1171
|
+
|
|
1172
|
+
Collage of media blocks: ``<tg-collage>``. Pass ``Image``/``Video`` entities."""
|
|
1173
|
+
def _render_html(self, content: str) -> str:
|
|
1174
|
+
return f"<tg-collage>{content}</tg-collage>"
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
class Slideshow(RichEntity):
|
|
1178
|
+
"""⚠️ Works only in Rich mode (``sender.set_rich_html(True)``).
|
|
1179
|
+
|
|
1180
|
+
Slideshow of media blocks: ``<tg-slideshow>``."""
|
|
1181
|
+
def _render_html(self, content: str) -> str:
|
|
1182
|
+
return f"<tg-slideshow>{content}</tg-slideshow>"
|
|
1183
|
+
|
|
1184
|
+
|
|
731
1185
|
class Styles:
|
|
732
1186
|
"""
|
|
733
1187
|
Namespace for message formatting styles:
|
|
@@ -757,4 +1211,33 @@ class Styles:
|
|
|
757
1211
|
Mention: type[TextEntity] = Mention
|
|
758
1212
|
UserLink: type[TextEntity] = UserLink
|
|
759
1213
|
BotLink: type[TextEntity] = BotLink
|
|
760
|
-
Group: type[TextEntity] = Group
|
|
1214
|
+
Group: type[TextEntity] = Group
|
|
1215
|
+
# Rich HTML
|
|
1216
|
+
Marked: type[TextEntity] = Marked
|
|
1217
|
+
Subscript: type[TextEntity] = Subscript
|
|
1218
|
+
Superscript: type[TextEntity] = Superscript
|
|
1219
|
+
CustomEmoji: type[TextEntity] = CustomEmoji
|
|
1220
|
+
DateTime: type[TextEntity] = DateTime
|
|
1221
|
+
Math: type[TextEntity] = Math
|
|
1222
|
+
MathBlock: type[TextEntity] = MathBlock
|
|
1223
|
+
Anchor: type[TextEntity] = Anchor
|
|
1224
|
+
AnchorLink: type[TextEntity] = AnchorLink
|
|
1225
|
+
Reference: type[TextEntity] = Reference
|
|
1226
|
+
Heading: type[TextEntity] = Heading
|
|
1227
|
+
Paragraph: type[TextEntity] = Paragraph
|
|
1228
|
+
Footer: type[TextEntity] = Footer
|
|
1229
|
+
Divider: type[TextEntity] = Divider
|
|
1230
|
+
Cite: type[TextEntity] = Cite
|
|
1231
|
+
PullQuote: type[TextEntity] = PullQuote
|
|
1232
|
+
Details: type[TextEntity] = Details
|
|
1233
|
+
ListItem: type[TextEntity] = ListItem
|
|
1234
|
+
UnorderedList: type[TextEntity] = UnorderedList
|
|
1235
|
+
OrderedList: type[TextEntity] = OrderedList
|
|
1236
|
+
Table: type[TextEntity] = Table
|
|
1237
|
+
TableCell: type[TextEntity] = TableCell
|
|
1238
|
+
Image: type[TextEntity] = Image
|
|
1239
|
+
Video: type[TextEntity] = Video
|
|
1240
|
+
Audio: type[TextEntity] = Audio
|
|
1241
|
+
Map: type[TextEntity] = Map
|
|
1242
|
+
Collage: type[TextEntity] = Collage
|
|
1243
|
+
Slideshow: type[TextEntity] = Slideshow
|
|
@@ -111,7 +111,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
111
111
|
)
|
|
112
112
|
|
|
113
113
|
markup = InlineKeyboardMarkup()
|
|
114
|
-
markup.
|
|
114
|
+
markup.inline_keyboard = self._build_keyboard_rows(buttons, row_width)
|
|
115
115
|
|
|
116
116
|
self.sender.set_reply_markup(markup)
|
|
117
117
|
self._handler.set_button_callbacks(button_callbacks)
|
|
@@ -173,7 +173,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
173
173
|
)
|
|
174
174
|
|
|
175
175
|
markup = InlineKeyboardMarkup()
|
|
176
|
-
markup.
|
|
176
|
+
markup.inline_keyboard = self._build_keyboard_rows(buttons, row_width)
|
|
177
177
|
|
|
178
178
|
self.sender.set_reply_markup(markup)
|
|
179
179
|
self._handler.set_button_callbacks(callback_functions)
|
|
@@ -281,7 +281,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
281
281
|
)
|
|
282
282
|
|
|
283
283
|
markup = InlineKeyboardMarkup()
|
|
284
|
-
markup.
|
|
284
|
+
markup.inline_keyboard = self._build_keyboard_rows(buttons, row_width)
|
|
285
285
|
|
|
286
286
|
self.sender.set_reply_markup(markup)
|
|
287
287
|
self._handler.set_button_callbacks(callback_functions)
|
|
@@ -333,7 +333,7 @@ class ChainInlineKeyboardLogic(ChainBase):
|
|
|
333
333
|
buttons.append(InlineButton.Suggest(suggestion)._compile(caption))
|
|
334
334
|
|
|
335
335
|
markup = InlineKeyboardMarkup()
|
|
336
|
-
markup.
|
|
336
|
+
markup.inline_keyboard = self._build_keyboard_rows(buttons, row_width)
|
|
337
337
|
|
|
338
338
|
self.sender.set_reply_markup(markup)
|
|
339
339
|
|