telekit 2.5.0b0__tar.gz → 2.5.0b2__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.
Files changed (82) hide show
  1. {telekit-2.5.0b0 → telekit-2.5.0b2}/PKG-INFO +22 -2
  2. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/__init__.py +5 -1
  3. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_buildtext/styles.py +2 -6
  4. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chain_base.py +0 -1
  5. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_handler.py +10 -1
  6. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_init.py +2 -0
  7. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_inline_buttons.py +97 -11
  8. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_inline_keyboard.py +131 -9
  9. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_version.py +1 -1
  10. telekit-2.5.0b2/telekit/chat.py +59 -0
  11. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/debug.py +1 -0
  12. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/inline_buttons.py +6 -0
  13. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/senders.py +20 -1
  14. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/utils.py +23 -9
  15. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit.egg-info/PKG-INFO +22 -2
  16. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit.egg-info/SOURCES.txt +1 -0
  17. {telekit-2.5.0b0 → telekit-2.5.0b2}/LICENSE +0 -0
  18. {telekit-2.5.0b0 → telekit-2.5.0b2}/README.md +0 -0
  19. {telekit-2.5.0b0 → telekit-2.5.0b2}/setup.cfg +0 -0
  20. {telekit-2.5.0b0 → telekit-2.5.0b2}/setup.py +0 -0
  21. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_buildtext/__init__.py +0 -0
  22. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_buildtext/formatter.py +0 -0
  23. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_callback_query_handler.py +0 -0
  24. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chain.py +0 -0
  25. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chain_entry_logic.py +0 -0
  26. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chain_inline_keyboards_logic.py +0 -0
  27. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chapters/__init__.py +0 -0
  28. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_chapters/chapters.py +0 -0
  29. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_input_handler.py +0 -0
  30. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_logger.py +0 -0
  31. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_on.py +0 -0
  32. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_reply_keyboard.py +0 -0
  33. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_snapvault/__init__.py +0 -0
  34. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_snapvault/snapcode.py +0 -0
  35. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_snapvault/snapvault.py +0 -0
  36. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_state.py +0 -0
  37. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/__init__.py +0 -0
  38. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/mixin.py +0 -0
  39. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/__init__.py +0 -0
  40. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/builder.py +0 -0
  41. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
  42. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/lexer.py +0 -0
  43. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/nodes.py +0 -0
  44. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/parser.py +0 -0
  45. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/parser/token.py +0 -0
  46. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
  47. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_telekit_dsl/telekit_orm.py +0 -0
  48. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_timeout.py +0 -0
  49. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_trait.py +0 -0
  50. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/_user.py +0 -0
  51. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/dices.py +0 -0
  52. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/__init__.py +0 -0
  53. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/__init__.py +0 -0
  54. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/calendar.py +0 -0
  55. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/complete_hotel.py +0 -0
  56. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/counter.py +0 -0
  57. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/dsl.py +0 -0
  58. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/entry.py +0 -0
  59. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/faq.py +0 -0
  60. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/hotel.py +0 -0
  61. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/on_text.py +0 -0
  62. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/pages.py +0 -0
  63. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/pyapi.py +0 -0
  64. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/qr.py +0 -0
  65. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/quiz.py +0 -0
  66. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/spells.py +0 -0
  67. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/start.py +0 -0
  68. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_handlers/text_document.py +0 -0
  69. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/example/example_server.py +0 -0
  70. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/parameters.py +0 -0
  71. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/reply_buttons.py +0 -0
  72. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/scheduler.py +0 -0
  73. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/server.py +0 -0
  74. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/styles.py +0 -0
  75. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/traits/__init__.py +0 -0
  76. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/traits/calendar_pick.py +0 -0
  77. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/traits/paginated_choice.py +0 -0
  78. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/traits/track_handoff_origin.py +0 -0
  79. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit/types.py +0 -0
  80. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit.egg-info/dependency_links.txt +0 -0
  81. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit.egg-info/requires.txt +0 -0
  82. {telekit-2.5.0b0 → telekit-2.5.0b2}/telekit.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telekit
3
- Version: 2.5.0b0
3
+ Version: 2.5.0b2
4
4
  Summary: Declarative, developer-friendly library for building Telegram bots
5
5
  Home-page: https://github.com/Romashkaa/telekit
6
6
  Author: romashka
@@ -477,7 +477,27 @@ It tries to make Telegram bot development easier.
477
477
 
478
478
  ---
479
479
 
480
- # Changes in version 2.5.0b0
480
+ # Changes in version 2.5.0b2
481
481
 
482
482
  ### v2.5.0`b0`
483
483
  - Added support for t-strings (PEP 750, Python 3.14+) in `TextEntity`.
484
+
485
+ ### v2.5.0`b1`
486
+ - Added `Sender.send_message` method.
487
+ - Added `utils.make_mention` utility for generating `tg://user?id=` mention links.
488
+ - Added new inline button types to `inline_buttons`:
489
+ - `ContactButton` — mentions a user by Telegram ID via `tg://user?id=`.
490
+ - `UserLinkButton` — opens a user profile by username; supports pre-filled message text.
491
+ - `BotLinkButton` — opens a bot by username; supports deep-link `?start=` payload.
492
+ - Added new methods to `InlineKeyboard`:
493
+ - `add_contact` — adds a `ContactButton`.
494
+ - `add_user_link` — adds a `UserLinkButton`.
495
+ - `add_bot_link` — adds a `BotLinkButton`.
496
+
497
+ ### v2.5.0`b2`
498
+ - Added the `escape` parameter to `telekit.utils.*`:
499
+ - `make_user_link`
500
+ - `make_bot_link`
501
+ - `Handler.handlers_dict` now excludes private handlers (classes whose names start with `_`).
502
+ - Added `Debug.duplicate_handler_warnings` to warn about duplicate handler names during initialization.
503
+ - Added `Handler.chat` object (BETA)
@@ -30,6 +30,7 @@ from ._user import User
30
30
  from ._telekit_dsl.telekit_dsl import TelekitDSL
31
31
  from ._telekit_dsl.mixin import DSLHandler, InstanceDSLHandler
32
32
  from ._logger import enable_file_logging
33
+ from .chat import Chat
33
34
 
34
35
  from . import senders
35
36
  from . import types
@@ -41,6 +42,7 @@ from . import utils
41
42
  from . import traits
42
43
  from . import debug
43
44
  from . import scheduler
45
+ from . import chat
44
46
 
45
47
  Styles = styles.Styles
46
48
 
@@ -56,11 +58,13 @@ __all__ = [
56
58
  "inline_buttons",
57
59
  "dices",
58
60
  "scheduler",
61
+ "chat",
59
62
 
60
63
  "Styles",
61
64
  "User",
62
65
 
63
- "Server",
66
+ "Server",
67
+ "Chat",
64
68
  "Chain",
65
69
  "Trait",
66
70
  "Handler",
@@ -484,13 +484,9 @@ class Mention(Link):
484
484
  `Documentation <https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/6_styles.md>`_ · on GitHub
485
485
  """
486
486
 
487
- def __init__(self, *content, user_id: int, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = ""):
488
- url: str = self.gen_link(user_id)
487
+ def __init__(self, *content, user_id: int | str, escape: bool = True, sep: Union[str, "TextEntity", "Template"] = ""):
488
+ url: str = telekit.utils.make_mention(user_id)
489
489
  super().__init__(*content, url=url, escape=escape, sep=sep)
490
-
491
- @staticmethod
492
- def gen_link(user_id: int) -> str:
493
- return f"tg://user?id={user_id}"
494
490
 
495
491
 
496
492
  class UserLink(Link):
@@ -18,7 +18,6 @@
18
18
  #
19
19
 
20
20
  # Standard library
21
- import inspect
22
21
  from typing import Callable
23
22
 
24
23
  # Third-party packages
@@ -27,8 +27,9 @@ library = logger.library
27
27
  from ._chain import Chain
28
28
  from ._callback_query_handler import CallbackQueryHandler
29
29
  from ._user import User
30
+ from .chat import Chat
30
31
  from ._on import On
31
- from . import senders
32
+ from .debug import Debug
32
33
 
33
34
  class Handler:
34
35
 
@@ -40,6 +41,7 @@ class Handler:
40
41
 
41
42
  # Instance Attributes
42
43
  user: User
44
+ chat: Chat
43
45
  chain: Chain
44
46
  message: Message
45
47
 
@@ -74,6 +76,12 @@ class Handler:
74
76
  handler.on = On(handler, bot)
75
77
  handler.init_handler()
76
78
 
79
+ if handler.__name__.startswith("_"):
80
+ continue
81
+
82
+ if handler.__name__ in cls.handlers_dict and Debug.duplicate_handler_warnings:
83
+ library.warning("Multiple handlers are named 'StartHandler'. Only the last registered handler is available through Handler.handlers_dict.")
84
+
77
85
  cls.handlers_dict[handler.__name__] = handler
78
86
 
79
87
  @classmethod
@@ -99,6 +107,7 @@ class Handler:
99
107
  def __init__(self, message: Message):
100
108
  self.message: Message = message
101
109
  self.user = User(self.message)
110
+ self.chat = Chat(self.message.chat.id, self.message.message_thread_id)
102
111
  self.new_chain()
103
112
 
104
113
  def handle(self) -> Any:
@@ -25,6 +25,7 @@ from ._inline_buttons import InlineButton
25
25
  from ._user import User
26
26
  from .senders import BaseSender
27
27
  from ._state import TelekitState
28
+ from .chat import Chat
28
29
 
29
30
  import telebot
30
31
 
@@ -33,6 +34,7 @@ __all__ = ["init"]
33
34
  def init(bot: telebot.TeleBot) -> None:
34
35
  TelekitState._init(bot)
35
36
  BaseSender._init(bot)
37
+ Chat._init(bot)
36
38
  Handler._init(bot)
37
39
  Chain._init(bot)
38
40
  InputHandler._init(bot)
@@ -20,12 +20,12 @@
20
20
  from typing import Any, Callable
21
21
  from enum import Enum
22
22
 
23
- # Third-party packages
24
23
  import telebot.types
25
24
  from telebot.types import InlineKeyboardButton, CallbackQuery
26
25
  from telebot import TeleBot
27
- from ._callback_query_handler import CallbackQueryHandler
28
26
 
27
+ import telekit
28
+ from ._callback_query_handler import CallbackQueryHandler
29
29
  from ._state import TelekitState
30
30
 
31
31
  __all__ = [
@@ -33,6 +33,9 @@ __all__ = [
33
33
 
34
34
  "StaticButton",
35
35
  "LinkButton",
36
+ "ContactButton",
37
+ "UserLinkButton",
38
+ "BotLinkButton",
36
39
  "WebAppButton",
37
40
  "SuggestButton",
38
41
  "CopyTextButton",
@@ -57,6 +60,9 @@ class InlineButton:
57
60
  """
58
61
  Base class for:
59
62
  - `LinkButton`
63
+ - `ContactButton`
64
+ - `UserLinkButton`
65
+ - `BotLinkButton`
60
66
  - `WebAppButton`
61
67
  - `SuggestButton`
62
68
  - `CopyTextButton`
@@ -79,6 +85,9 @@ class InlineButton:
79
85
 
80
86
  Static: type["StaticButton"]
81
87
  Link: type["LinkButton"]
88
+ Contact: type["ContactButton"]
89
+ UserLink: type["UserLinkButton"]
90
+ BotLink: type["BotLinkButton"]
82
91
  WebApp: type["WebAppButton"]
83
92
  Suggest: type["SuggestButton"]
84
93
  CopyText: type["CopyTextButton"]
@@ -157,7 +166,7 @@ class LinkButton(InlineButton):
157
166
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
158
167
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
159
168
  You can also pass these as string values: "danger", "success", "primary".
160
- If omitted, an app-specific default style is used.
169
+ If omitted, an client-specific default style is used.
161
170
  :type style: `str`
162
171
 
163
172
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -175,7 +184,81 @@ class LinkButton(InlineButton):
175
184
  style=self._style,
176
185
  **self._kwargs
177
186
  )
178
-
187
+
188
+ class ContactButton(LinkButton):
189
+ """
190
+ Inline keyboard button that mentions a user by their Telegram ID.
191
+
192
+ Generates a ``tg://user?id=<user_id>`` link via :func:`telekit.utils.make_mention`,
193
+ allowing you to mention a user without knowing their username —
194
+ subject to their privacy settings.
195
+
196
+ :param user_id: Telegram user ID to mention.
197
+ :type user_id: :class:`str` | :class:`int`
198
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
199
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
200
+ ``"primary"`` (blue). Defaults to the client-specific style if omitted.
201
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
202
+ :param kwargs: Additional keyword arguments forwarded to
203
+ :class:`~telekit.types.InlineKeyboardButton`.
204
+ """
205
+ def __init__(self, user_id: str | int, *, style: str | None | ButtonStyle = None, **kwargs):
206
+ super().__init__(
207
+ url=telekit.utils.make_mention(user_id),
208
+ style=style,
209
+ **kwargs
210
+ )
211
+
212
+ class UserLinkButton(LinkButton):
213
+ """
214
+ Inline keyboard button that opens a Telegram profile by username.
215
+
216
+ Generates a ``https://t.me/<username>`` link via :func:`telekit.utils.make_user_link`.
217
+ Optionally pre-fills the message input with ``text``.
218
+
219
+ :param username: Telegram username, with or without a leading ``@``.
220
+ :type username: :class:`str`
221
+ :param text: Optional message text to pre-fill in the chat input.
222
+ :type text: :class:`str` | ``None``
223
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
224
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
225
+ ``"primary"`` (blue). Defaults to the client-specific style if omitted.
226
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
227
+ :param kwargs: Additional keyword arguments forwarded to
228
+ :class:`~telekit.types.InlineKeyboardButton`.
229
+ """
230
+ def __init__(self, username: str, text: str | None = None, *, style: str | None | ButtonStyle = None, **kwargs):
231
+ super().__init__(
232
+ url=telekit.utils.make_user_link(username, text),
233
+ style=style,
234
+ **kwargs
235
+ )
236
+
237
+ class BotLinkButton(LinkButton):
238
+ """
239
+ Inline keyboard button that opens a Telegram bot by username.
240
+
241
+ Generates a ``https://t.me/<botname>`` link via :func:`telekit.utils.make_bot_link`.
242
+ Optionally appends a deep-link payload via ``?start=``.
243
+
244
+ :param botname: Bot username, with or without a leading ``@``.
245
+ :type botname: :class:`str`
246
+ :param start: Optional deep-link payload passed as ``?start=``.
247
+ :type start: :class:`str` | ``None``
248
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
249
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
250
+ ``"primary"`` (blue). Defaults to the client-specific style if omitted.
251
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
252
+ :param kwargs: Additional keyword arguments forwarded to
253
+ :class:`~telekit.types.InlineKeyboardButton`.
254
+ """
255
+ def __init__(self, botname: str, start: str | None = None, *, style: str | None | ButtonStyle = None, **kwargs):
256
+ super().__init__(
257
+ url=telekit.utils.make_bot_link(botname, start),
258
+ style=style,
259
+ **kwargs
260
+ )
261
+
179
262
  class WebAppButton(InlineButton):
180
263
  """
181
264
  This object represents an inline keyboard button that describes a Web App.
@@ -186,7 +269,7 @@ class WebAppButton(InlineButton):
186
269
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
187
270
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
188
271
  You can also pass these as string values: "danger", "success", "primary".
189
- If omitted, an app-specific default style is used.
272
+ If omitted, an client-specific default style is used.
190
273
  :type style: `str`
191
274
 
192
275
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -215,7 +298,7 @@ class CopyTextButton(InlineButton):
215
298
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
216
299
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
217
300
  You can also pass these as string values: "danger", "success", "primary".
218
- If omitted, an app-specific default style is used.
301
+ If omitted, an client-specific default style is used.
219
302
  :type style: `str`
220
303
 
221
304
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -255,7 +338,7 @@ class SuggestButton(InlineButton):
255
338
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
256
339
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
257
340
  You can also pass these as string values: "danger", "success", "primary".
258
- If omitted, an app-specific default style is used.
341
+ If omitted, an client-specific default style is used.
259
342
  :type style: `str`
260
343
 
261
344
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -308,7 +391,7 @@ class CallbackButton(InlineButton):
308
391
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
309
392
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
310
393
  You can also pass these as string values: "danger", "success", "primary".
311
- If omitted, an app-specific default style is used.
394
+ If omitted, an client-specific default style is used.
312
395
  :type style: `str`
313
396
 
314
397
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -501,7 +584,7 @@ class AlertButton(AnswerButton):
501
584
  :param style: Style of the button. Must be one of `ButtonStyle.DANGER` (red),
502
585
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
503
586
  You can also pass these as string values: "danger", "success", "primary".
504
- If omitted, an app-specific default style is used.
587
+ If omitted, an client-specific default style is used.
505
588
  :type style: `str | ButtonStyle | None`
506
589
 
507
590
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -551,7 +634,7 @@ class NotificationButton(AnswerButton):
551
634
  :param style: Style of the button. Must be one of `ButtonStyle.DANGER` (red),
552
635
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
553
636
  You can also pass these as string values: "danger", "success", "primary".
554
- If omitted, an app-specific default style is used.
637
+ If omitted, an client-specific default style is used.
555
638
  :type style: `str | ButtonStyle | None`
556
639
 
557
640
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -616,7 +699,7 @@ class InvokeButton(CallbackButton):
616
699
  :param style: Style of the button. Must be one of `ButtonStyle.DANGER` (red),
617
700
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
618
701
  You can also pass these as string values: "danger", "success", "primary".
619
- If omitted, an app-specific default style is used.
702
+ If omitted, an client-specific default style is used.
620
703
  :type style: `str | ButtonStyle | None`
621
704
 
622
705
  :param kwargs: Additional keyword arguments passed directly to `InlineKeyboardButton`.
@@ -712,6 +795,9 @@ class InvokeButton(CallbackButton):
712
795
  )
713
796
 
714
797
  InlineButton.Link = LinkButton
798
+ InlineButton.Contact = ContactButton
799
+ InlineButton.UserLink = UserLinkButton
800
+ InlineButton.BotLink = BotLinkButton
715
801
  InlineButton.Static = StaticButton
716
802
  InlineButton.WebApp = WebAppButton
717
803
  InlineButton.Suggest = SuggestButton
@@ -167,7 +167,7 @@ class InlineKeyboard:
167
167
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
168
168
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
169
169
  You can also pass these as string values: "danger", "success", "primary".
170
- If omitted, an app-specific default style is used.
170
+ If omitted, an client-specific default style is used.
171
171
  :type style: `str`
172
172
 
173
173
  :param when: If `False`, the button is not added.
@@ -210,7 +210,7 @@ class InlineKeyboard:
210
210
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
211
211
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
212
212
  You can also pass these as string values: "danger", "success", "primary".
213
- If omitted, an app-specific default style is used.
213
+ If omitted, an client-specific default style is used.
214
214
  :type style: `str`
215
215
 
216
216
  :param when: If `False`, the button is not added.
@@ -225,6 +225,128 @@ class InlineKeyboard:
225
225
  self._maybe_row()
226
226
  return self
227
227
 
228
+ def add_contact(
229
+ self,
230
+ text: str,
231
+ user_id: str | int,
232
+ *,
233
+ style: Literal["danger", "success", "primary"] | None | ButtonStyle = None,
234
+ when: bool | int = True,
235
+ **kwargs
236
+ ):
237
+ """
238
+ Adds a button that mentions a Telegram user by their ID.
239
+
240
+ The button generates a ``tg://user?id=<user_id>`` link via
241
+ :class:`~telekit.types.ContactButton`, allowing you to mention a user
242
+ without knowing their username.
243
+
244
+ :param text: Label displayed on the button.
245
+ :type text: :class:`str`
246
+ :param user_id: Telegram user ID to mention.
247
+ :type user_id: :class:`str` | :class:`int`
248
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
249
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
250
+ ``"primary"`` (blue). Defaults to the client-specific style if omitted.
251
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
252
+ :param when: If ``False`` (or any falsy value), the button is skipped and not added
253
+ to the keyboard. Useful for conditional layouts.
254
+ :type when: :class:`bool` | :class:`int`
255
+ :param kwargs: Additional keyword arguments forwarded to
256
+ :class:`~telekit.types.InlineKeyboardButton`.
257
+ :returns: The current builder instance, for method chaining.
258
+ :rtype: :class:`Self`
259
+ """
260
+ if not when:
261
+ return self
262
+
263
+ self._current.append((text, ContactButton(user_id, style=style, **kwargs)))
264
+ self._maybe_row()
265
+ return self
266
+
267
+ def add_user_link(
268
+ self,
269
+ text: str,
270
+ username: str,
271
+ message: str | None = None,
272
+ *,
273
+ style: Literal["danger", "success", "primary"] | None | ButtonStyle = None,
274
+ when: bool | int = True,
275
+ **kwargs
276
+ ):
277
+ """
278
+ Adds a button that opens a Telegram user profile by username.
279
+
280
+ The button generates a ``https://t.me/<username>`` link via
281
+ :class:`~telekit.types.UserLinkButton`. Optionally pre-fills
282
+ the chat input with ``message``.
283
+
284
+ :param text: Label displayed on the button.
285
+ :type text: :class:`str`
286
+ :param username: Telegram username, with or without a leading ``@``.
287
+ :type username: :class:`str`
288
+ :param message: Optional message text to pre-fill in the chat input.
289
+ :type message: :class:`str` | ``None``
290
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
291
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
292
+ ``"primary"`` (blue). Defaults to the app-specific style if omitted.
293
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
294
+ :param when: If ``False`` (or any falsy value), the button is skipped and not added
295
+ to the keyboard. Useful for conditional layouts.
296
+ :type when: :class:`bool` | :class:`int`
297
+ :param kwargs: Additional keyword arguments forwarded to
298
+ :class:`~telekit.types.InlineKeyboardButton`.
299
+ :returns: The current builder instance, for method chaining.
300
+ :rtype: :class:`Self`
301
+ """
302
+ if not when:
303
+ return self
304
+ self._current.append((text, UserLinkButton(username, message, style=style, **kwargs)))
305
+ self._maybe_row()
306
+ return self
307
+
308
+ def add_bot_link(
309
+ self,
310
+ text: str,
311
+ botname: str,
312
+ start: str | None = None,
313
+ *,
314
+ style: Literal["danger", "success", "primary"] | None | ButtonStyle = None,
315
+ when: bool | int = True,
316
+ **kwargs
317
+ ):
318
+ """
319
+ Adds a button that opens a Telegram bot by username.
320
+
321
+ The button generates a ``https://t.me/<botname>`` link via
322
+ :class:`~telekit.types.BotLinkButton`. Optionally appends
323
+ a deep-link payload via ``?start=``.
324
+
325
+ :param text: Label displayed on the button.
326
+ :type text: :class:`str`
327
+ :param botname: Bot username, with or without a leading ``@``.
328
+ :type botname: :class:`str`
329
+ :param start: Optional deep-link payload passed as ``?start=``.
330
+ Useful for referral links or onboarding flows.
331
+ :type start: :class:`str` | ``None``
332
+ :param style: Visual style of the button. Accepts a :class:`~telekit.types.ButtonStyle`
333
+ enum value or its string equivalent: ``"danger"`` (red), ``"success"`` (green),
334
+ ``"primary"`` (blue). Defaults to the app-specific style if omitted.
335
+ :type style: :class:`~telekit.types.ButtonStyle` | :class:`str` | ``None``
336
+ :param when: If ``False`` (or any falsy value), the button is skipped and not added
337
+ to the keyboard. Useful for conditional layouts.
338
+ :type when: :class:`bool` | :class:`int`
339
+ :param kwargs: Additional keyword arguments forwarded to
340
+ :class:`~telekit.types.InlineKeyboardButton`.
341
+ :returns: The current builder instance, for method chaining.
342
+ :rtype: :class:`Self`
343
+ """
344
+ if not when:
345
+ return self
346
+ self._current.append((text, BotLinkButton(botname, start, style=style, **kwargs)))
347
+ self._maybe_row()
348
+ return self
349
+
228
350
  def add_webapp(
229
351
  self,
230
352
  text: str,
@@ -246,7 +368,7 @@ class InlineKeyboard:
246
368
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
247
369
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
248
370
  You can also pass these as string values: "danger", "success", "primary".
249
- If omitted, an app-specific default style is used.
371
+ If omitted, an client-specific default style is used.
250
372
  :type style: `str`
251
373
 
252
374
  :param when: If `False`, the button is not added.
@@ -283,7 +405,7 @@ class InlineKeyboard:
283
405
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
284
406
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
285
407
  You can also pass these as string values: "danger", "success", "primary".
286
- If omitted, an app-specific default style is used.
408
+ If omitted, an client-specific default style is used.
287
409
  :type style: `str`
288
410
 
289
411
  :param strict: If `True`, raises an error when the suggestion exceeds Telegram's 64-byte limit.
@@ -323,7 +445,7 @@ class InlineKeyboard:
323
445
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
324
446
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
325
447
  You can also pass these as string values: "danger", "success", "primary".
326
- If omitted, an app-specific default style is used.
448
+ If omitted, an client-specific default style is used.
327
449
  :type style: `str`
328
450
 
329
451
  :param strict: If `True`, raises an error when the text exceeds Telegram's 256-character limit.
@@ -358,7 +480,7 @@ class InlineKeyboard:
358
480
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
359
481
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
360
482
  You can also pass these as string values: "danger", "success", "primary".
361
- If omitted, an app-specific default style is used.
483
+ If omitted, an client-specific default style is used.
362
484
  :type style: `str`
363
485
 
364
486
  :param when: If `False`, the button is not added.
@@ -398,7 +520,7 @@ class InlineKeyboard:
398
520
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
399
521
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
400
522
  You can also pass these as string values: "danger", "success", "primary".
401
- If omitted, an app-specific default style is used.
523
+ If omitted, an client-specific default style is used.
402
524
  :type style: `str`
403
525
 
404
526
  :param when: If `False`, the button is not added.
@@ -438,7 +560,7 @@ class InlineKeyboard:
438
560
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
439
561
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
440
562
  You can also pass these as string values: "danger", "success", "primary".
441
- If omitted, an app-specific default style is used.
563
+ If omitted, an client-specific default style is used.
442
564
  :type style: `str`
443
565
 
444
566
  :param when: If `False`, the button is not added.
@@ -495,7 +617,7 @@ class InlineKeyboard:
495
617
  :param style: Style of the button. Must be one of `telekit.types.ButtonStyle.DANGER` (red),
496
618
  `*.SUCCESS` (green) or `*.PRIMARY` (blue).
497
619
  You can also pass these as string values: "danger", "success", "primary".
498
- If omitted, an app-specific default style is used.
620
+ If omitted, an client-specific default style is used.
499
621
  :type style: `str`
500
622
 
501
623
  :param when: If `False`, the button is not added.
@@ -3,4 +3,4 @@
3
3
  # PyPI history: https://pypi.org/project/telekit/#history
4
4
  # ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
5
5
 
6
- __version__ = "2.5.0b0"
6
+ __version__ = "2.5.0b2"
@@ -0,0 +1,59 @@
1
+ from typing import Any
2
+
3
+ from telebot import TeleBot
4
+ from telebot.types import Message
5
+
6
+ import telekit
7
+ from telekit.styles import TextEntity, Escape, Raw, Group, Bold, Italic
8
+ from ._logger import _library
9
+
10
+ __all__ = ['Chat']
11
+
12
+ class Chat:
13
+
14
+ bot: TeleBot
15
+
16
+ @classmethod
17
+ def _init(cls, bot: TeleBot):
18
+ """
19
+ Initializes the bot instance for the class.
20
+ """
21
+ cls.bot = bot
22
+
23
+ def __init__(self, chat_id: int, thread_id: int | None = None, previous_user_message: Message | None = None) -> None:
24
+ self.id = chat_id
25
+ self.thread_id = thread_id
26
+ self.previous_user_message = previous_user_message
27
+
28
+ def _get_base_params(self) -> dict[str, Any]:
29
+ return {
30
+ "chat_id": self.id,
31
+ "parse_mode": "html",
32
+ "message_thread_id": self.thread_id
33
+ }
34
+
35
+ def answer(self, text: str | TextEntity):
36
+ """
37
+ Sends a text message to the chat.
38
+
39
+ Returns:
40
+ Message | None: The sent message or None if sending failed.
41
+ """
42
+ if not isinstance(text, TextEntity):
43
+ text = Escape(text)
44
+
45
+ try:
46
+ return self.bot.send_message(
47
+ chat_id = self.id,
48
+ message_thread_id = self.thread_id,
49
+ text = text.html,
50
+ parse_mode = "html",
51
+ )
52
+ except Exception as exception:
53
+ text.debug("html")
54
+ _library.warning(f"^^^ [chat.answer]: Failed to answer (chat_id={self.id}; thread_id={self.thread_id}): {exception}")
55
+ return None
56
+
57
+
58
+
59
+
@@ -2,6 +2,7 @@ class Debug:
2
2
  timeout_warnings: bool = False
3
3
  deletion_warnings: bool = False
4
4
  callback_query_tracing: bool = False
5
+ duplicate_handler_warnings: bool = True
5
6
 
6
7
  @classmethod
7
8
  def set_all(cls, value: bool) -> None:
@@ -22,6 +22,9 @@ from ._inline_buttons import (
22
22
  StaticButton,
23
23
 
24
24
  LinkButton,
25
+ ContactButton,
26
+ UserLinkButton,
27
+ BotLinkButton,
25
28
  WebAppButton,
26
29
  SuggestButton,
27
30
  CopyTextButton,
@@ -41,6 +44,9 @@ __all__ = [
41
44
  "StaticButton",
42
45
 
43
46
  "LinkButton",
47
+ "ContactButton",
48
+ "UserLinkButton",
49
+ "BotLinkButton",
44
50
  "WebAppButton",
45
51
  "SuggestButton",
46
52
  "CopyTextButton",
@@ -1208,10 +1208,29 @@ class BaseSender:
1208
1208
  try:
1209
1209
  configs = self._get_base_params()
1210
1210
  configs["parse_mode"] = "HTML"
1211
- return self.bot.send_message(text=f"<b>{Escape(title)}</b>\n\n<i>{Escape(message)}.</i>", **configs)
1211
+ return self.bot.send_message(text=f"{Bold(title).html}\n\n{Escape(message).html}", **configs)
1212
1212
  except Exception as exception:
1213
1213
  library.warning(f"Failed to send `error` message: {exception}")
1214
1214
  return None
1215
+
1216
+ def send_message(self, title: str, message: str) -> Message | None:
1217
+ """
1218
+ Sends a custom message with a title and detailed message.
1219
+
1220
+ Args:
1221
+ title (`str` | `TextEntity`): The title of the message.
1222
+ message (`str` | `TextEntity`): The message.
1223
+
1224
+ Returns:
1225
+ Message | None: The sent message or None if sending failed.
1226
+ """
1227
+ try:
1228
+ configs = self._get_base_params()
1229
+ configs["parse_mode"] = "HTML"
1230
+ return self.bot.send_message(text=f"{Bold(title).html}\n\n{Escape(message).html}", **configs)
1231
+ except Exception as exception:
1232
+ library.warning(f"Failed to send message: {exception}")
1233
+ return None
1215
1234
 
1216
1235
  def try_send(self) -> tuple[Message | None, Exception | None]:
1217
1236
  """
@@ -322,8 +322,20 @@ def compose_keyboard(
322
322
  # Link Generating
323
323
  # ––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
324
324
 
325
- def make_bot_link(botname: str, start: str | None = None) -> str:
326
- """Return a t.me link to a bot.
325
+ def make_mention(user_id: int | str) -> str:
326
+ """
327
+ Creates a Telegram mention link by user ID.
328
+
329
+ Examples::
330
+
331
+ make_mention(123456789)
332
+ >>> 'tg://user?id=123456789'
333
+ """
334
+ return f"tg://user?id={user_id}"
335
+
336
+ def make_bot_link(botname: str, start: str | None = None, escape: bool = True) -> str:
337
+ """
338
+ Return a t.me link to a bot.
327
339
 
328
340
  >>> make_bot_link("UserNameOfBot")
329
341
  "https://t.me/UserNameOfBot"
@@ -341,11 +353,12 @@ def make_bot_link(botname: str, start: str | None = None) -> str:
341
353
  botname = botname.lstrip("@")
342
354
  if start is None:
343
355
  return f"https://t.me/{botname}"
344
- encoded_start = quote(start, safe="")
356
+ encoded_start = quote(start, safe="") if escape else start
345
357
  return f"https://t.me/{botname}?start={encoded_start}"
346
358
 
347
- def make_user_link(username: str, text: str | None = None) -> str:
348
- """Return a t.me link to a user.
359
+ def make_user_link(username: str, text: str | None = None, escape: bool = True) -> str:
360
+ """
361
+ Return a t.me link to a user.
349
362
 
350
363
  >>> make_user_link("UserName")
351
364
  "https://t.me/UserName"
@@ -363,7 +376,7 @@ def make_user_link(username: str, text: str | None = None) -> str:
363
376
  username = username.lstrip("@")
364
377
  if text is None:
365
378
  return f"https://t.me/{username}"
366
- encoded_text = quote(text, safe="")
379
+ encoded_text = quote(text, safe="") if escape else text
367
380
  return f"https://t.me/{username}?text={encoded_text}"
368
381
 
369
382
  def make_qrcode(
@@ -384,7 +397,8 @@ def make_qrcode(
384
397
  caption_font_size: int | None = None,
385
398
  caption_font_color: str | None = None,
386
399
  ) -> str:
387
- """Return a QuickChart URL that renders a QR code.
400
+ """
401
+ Return a QuickChart URL that renders a QR code.
388
402
 
389
403
  >>> make_qrcode("https://example.com")
390
404
  'https://quickchart.io/qr?text=https%3A%2F%2Fexample.com'
@@ -427,9 +441,9 @@ def make_qrcode(
427
441
  if margin is not None:
428
442
  params["margin"] = margin
429
443
  if dark is not None:
430
- params["dark"] = dark
444
+ params["dark"] = dark.lstrip("#")
431
445
  if light is not None:
432
- params["light"] = light
446
+ params["light"] = light.lstrip("#")
433
447
  if ec_level is not None:
434
448
  params["ecLevel"] = ec_level
435
449
  if format is not None:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: telekit
3
- Version: 2.5.0b0
3
+ Version: 2.5.0b2
4
4
  Summary: Declarative, developer-friendly library for building Telegram bots
5
5
  Home-page: https://github.com/Romashkaa/telekit
6
6
  Author: romashka
@@ -477,7 +477,27 @@ It tries to make Telegram bot development easier.
477
477
 
478
478
  ---
479
479
 
480
- # Changes in version 2.5.0b0
480
+ # Changes in version 2.5.0b2
481
481
 
482
482
  ### v2.5.0`b0`
483
483
  - Added support for t-strings (PEP 750, Python 3.14+) in `TextEntity`.
484
+
485
+ ### v2.5.0`b1`
486
+ - Added `Sender.send_message` method.
487
+ - Added `utils.make_mention` utility for generating `tg://user?id=` mention links.
488
+ - Added new inline button types to `inline_buttons`:
489
+ - `ContactButton` — mentions a user by Telegram ID via `tg://user?id=`.
490
+ - `UserLinkButton` — opens a user profile by username; supports pre-filled message text.
491
+ - `BotLinkButton` — opens a bot by username; supports deep-link `?start=` payload.
492
+ - Added new methods to `InlineKeyboard`:
493
+ - `add_contact` — adds a `ContactButton`.
494
+ - `add_user_link` — adds a `UserLinkButton`.
495
+ - `add_bot_link` — adds a `BotLinkButton`.
496
+
497
+ ### v2.5.0`b2`
498
+ - Added the `escape` parameter to `telekit.utils.*`:
499
+ - `make_user_link`
500
+ - `make_bot_link`
501
+ - `Handler.handlers_dict` now excludes private handlers (classes whose names start with `_`).
502
+ - Added `Debug.duplicate_handler_warnings` to warn about duplicate handler names during initialization.
503
+ - Added `Handler.chat` object (BETA)
@@ -21,6 +21,7 @@ telekit/_timeout.py
21
21
  telekit/_trait.py
22
22
  telekit/_user.py
23
23
  telekit/_version.py
24
+ telekit/chat.py
24
25
  telekit/debug.py
25
26
  telekit/dices.py
26
27
  telekit/inline_buttons.py
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes