telekit 2.2.0a3__tar.gz → 2.3.0__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 (77) hide show
  1. {telekit-2.2.0a3/telekit.egg-info → telekit-2.3.0}/PKG-INFO +44 -48
  2. {telekit-2.2.0a3 → telekit-2.3.0}/README.md +41 -2
  3. {telekit-2.2.0a3 → telekit-2.3.0}/setup.py +1 -2
  4. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/__init__.py +6 -0
  5. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_callback_query_handler.py +18 -6
  6. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chain.py +2 -1
  7. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chain_base.py +3 -3
  8. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chain_inline_keyboards_logic.py +1 -5
  9. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_handler.py +7 -0
  10. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_init.py +1 -1
  11. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_inline_buttons.py +114 -11
  12. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_logger.py +42 -41
  13. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_state.py +15 -2
  14. telekit-2.3.0/telekit/_trait.py +10 -0
  15. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_version.py +1 -1
  16. telekit-2.3.0/telekit/debug.py +8 -0
  17. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/__init__.py +2 -1
  18. telekit-2.3.0/telekit/example/example_handlers/calendar.py +155 -0
  19. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/entry.py +5 -1
  20. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/spells.py +12 -13
  21. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/start.py +2 -1
  22. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/text_document.py +6 -2
  23. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/inline_buttons.py +8 -2
  24. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/senders.py +141 -41
  25. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/server.py +7 -5
  26. telekit-2.3.0/telekit/traits/__init__.py +3 -0
  27. telekit-2.3.0/telekit/traits/calendar_pick.py +807 -0
  28. telekit-2.3.0/telekit/traits/paginated_choice.py +145 -0
  29. telekit-2.3.0/telekit/traits/track_handoff_origin.py +99 -0
  30. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/utils.py +190 -15
  31. {telekit-2.2.0a3 → telekit-2.3.0/telekit.egg-info}/PKG-INFO +44 -48
  32. {telekit-2.2.0a3 → telekit-2.3.0}/telekit.egg-info/SOURCES.txt +8 -1
  33. {telekit-2.2.0a3 → telekit-2.3.0}/LICENSE +0 -0
  34. {telekit-2.2.0a3 → telekit-2.3.0}/setup.cfg +0 -0
  35. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_buildtext/__init__.py +0 -0
  36. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_buildtext/formatter.py +0 -0
  37. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_buildtext/styles.py +0 -0
  38. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chain_entry_logic.py +0 -0
  39. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chapters/__init__.py +0 -0
  40. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_chapters/chapters.py +0 -0
  41. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_input_handler.py +0 -0
  42. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_on.py +0 -0
  43. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_snapvault/__init__.py +0 -0
  44. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_snapvault/snapcode.py +0 -0
  45. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_snapvault/snapvault.py +0 -0
  46. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/__init__.py +0 -0
  47. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/mixin.py +0 -0
  48. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/__init__.py +0 -0
  49. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/builder.py +0 -0
  50. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
  51. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/lexer.py +0 -0
  52. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/nodes.py +0 -0
  53. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/parser.py +0 -0
  54. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/parser/token.py +0 -0
  55. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
  56. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_telekit_dsl/telekit_orm.py +0 -0
  57. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_timeout.py +0 -0
  58. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/_user.py +0 -0
  59. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/dices.py +0 -0
  60. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/__init__.py +0 -0
  61. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/complete_hotel.py +0 -0
  62. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/counter.py +0 -0
  63. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/dsl.py +0 -0
  64. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/faq.py +0 -0
  65. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/hotel.py +0 -0
  66. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/on_text.py +0 -0
  67. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/pages.py +0 -0
  68. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/pyapi.py +0 -0
  69. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/qr.py +0 -0
  70. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_handlers/quiz.py +0 -0
  71. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/example/example_server.py +0 -0
  72. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/parameters.py +0 -0
  73. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/styles.py +0 -0
  74. {telekit-2.2.0a3 → telekit-2.3.0}/telekit/types.py +0 -0
  75. {telekit-2.2.0a3 → telekit-2.3.0}/telekit.egg-info/dependency_links.txt +0 -0
  76. {telekit-2.2.0a3 → telekit-2.3.0}/telekit.egg-info/requires.txt +0 -0
  77. {telekit-2.2.0a3 → telekit-2.3.0}/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.2.0a3
3
+ Version: 2.3.0
4
4
  Summary: Declarative, developer-friendly library for building Telegram bots
5
5
  Home-page: https://github.com/Romashkaa/telekit
6
6
  Author: romashka
@@ -77,7 +77,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
77
77
 
78
78
  > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
79
79
 
80
- Even in its beta stage, Telekit accelerates bot development, offering typed command parameters, text styling via `Bold()`, `Italic()`, built-in emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
80
+ Even in its beta stage, Telekit accelerates bot development, offering typed command parameters, text styling via `Bold()`, `Italic()`, a built-in declarative calendar picker, emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
81
81
 
82
82
  **Key features:**
83
83
  - Declarative bot logic with **chains** for effortless handling of complex conversations
@@ -85,6 +85,7 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
85
85
  - Automatic handling of [message formatting](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/6_styles.md) via [Sender](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/5_senders.md) and **callback routing**
86
86
  - **Deep Linking** support with type-checked [Command Parameters](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/command_trigger_parameters.md) for flexible user input
87
87
  - Built-in **Permission** and **Logging** system for user management
88
+ - Reusable **Traits** system for pluggable, self-contained behavior modules
88
89
  - Seamless integration with [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)
89
90
  - Fast to develop and easy-to-extend code
90
91
 
@@ -101,7 +102,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
101
102
  - [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
102
103
  - [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
103
104
  - [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
104
- - [Quiz (Telekit DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
105
+ - [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
106
+ - [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
105
107
  - [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
106
108
 
107
109
  ## Overview
@@ -347,6 +349,42 @@ telekit.Server(BOT_TOKEN).polling()
347
349
 
348
350
  You can find a [full quiz example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md) and [DSL reference](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial/11_telekit_dsl.md) in the repository.
349
351
 
352
+ ### Traits
353
+
354
+ Traits are reusable behavior modules you can mix into any handler.
355
+
356
+ This example demonstrates the simplest way to use the built-in CalendarPick trait. It allows a user to pick a date from an inline calendar and handles the result via a callback.
357
+
358
+ ```py
359
+ from telekit.traits import CalendarPick
360
+
361
+ class CalendarHandler(CalendarPick, telekit.Handler):
362
+
363
+ @classmethod
364
+ def init_handler(cls) -> None:
365
+ cls.on.command("calendar").invoke(cls.handle)
366
+
367
+ def handle(self) -> None:
368
+ self.chain.sender.set_title("📅 Choose a date")
369
+ self.chain.sender.set_message("Select any date — past or future:")
370
+ self.chain.sender.set_remove_text(False)
371
+
372
+ self.calendar_pick(self.handle_date) # HERE
373
+
374
+ def handle_date(self, date: datetime.date) -> None:
375
+ self.chain.sender.set_text(f"You picked: {date}")
376
+ self.chain.send()
377
+ ```
378
+
379
+ <details>
380
+ <summary>Result</summary>
381
+ <table>
382
+ <tr>
383
+ <td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
384
+ </tr>
385
+ </table>
386
+ </details>
387
+
350
388
  ### Example Bot
351
389
 
352
390
  You can launch an example bot by **running the following code**:
@@ -367,6 +405,7 @@ It includes example commands, dialogs, keyboards, and style usage.
367
405
  - **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
368
406
  - Deep linking and **typed command parameters**.
369
407
  - **Built-in DSL** for menus, FAQs, and simple bots.
408
+ - Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
370
409
  - **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
371
410
  - Seamless integration with **pyTelegramBotAPI**.
372
411
 
@@ -378,49 +417,6 @@ It tries to make Telegram bot development easier.
378
417
 
379
418
  ---
380
419
 
381
- # Changes in version 2.2.0a3
382
-
383
- ## New Button Types
384
-
385
- | **Name** | **Description** |
386
- |----------------------|----------------------------------------------------------|
387
- | `AlertButton` | A callback button that shows a popup alert when pressed. |
388
- | `NotificationButton` | A callback button that shows a notification. |
389
- | `InvokeButton` | A callback button that calls the object method. |
390
-
391
- ## User Improvements
392
-
393
- | **Name** | **Description** |
394
- | ---------------------- | --------------------------------------------------------- |
395
- | `bio` | Bio of the user or description of the chat. |
396
- | `birthdate` | Birthdate of the user, if set and visible. |
397
- | `description` | Description of the group or channel. |
398
- | `mention` | `tg://user?id=` deep link, works even without a username. |
399
- | `is_private` | Whether the message was sent in a private chat. |
400
- | `is_group` | Whether the message was sent in a group. |
401
- | `is_supergroup` | Whether the message was sent in a supergroup. |
402
- | `is_channel` | Whether the message was sent in a channel. |
403
- | `avatar` | File ID of the user's most recent profile photo. |
404
- | `profile_photos_count` | Total number of profile photos the user has set. |
405
-
406
- - Refactor: `User` now accepts a `Message` object instead of `chat_id` + `from_user`. All properties are derived from `_sender` (`from_user` or `chat`) and migrated to `cached_property`. Fixed broken `get_id` and `get_full_name` references.
407
- - Added `__repr__`.
408
-
409
- ## Sender Improvements
410
-
411
- | **Name** | **Description** |
412
- | -------------------------- | ------------------------------------------------------ |
413
- | `sent_message` | The last message sent by this sender instance. |
414
- | `disable_notification` | Disables notification sound when the message is sent. |
415
- | `protect_content` | Protects the message contents from forwarding and saving. |
416
- | `reply_parameters` | Reply parameters for the message to be sent. |
417
- | `link_preview_options` | Link preview options for the message to be sent. |
418
- | `show_caption_above_media` | Shows the caption above the media instead of below. |
419
-
420
- - Refactored sending system.
421
-
422
- ## Chain Improvements
420
+ # Changes in version 2.3.0
423
421
 
424
- | **Name** | **Description** |
425
- | ----------- | ------------------------------------------------------------ |
426
- | `received` | The message received from the user during entry processing. |
422
+ [View changelog on GitHub](https://github.com/Romashkaa/telekit/blob/main/CHANGELOG.md)
@@ -41,7 +41,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
41
41
 
42
42
  > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
43
43
 
44
- Even in its beta stage, Telekit accelerates bot development, offering typed command parameters, text styling via `Bold()`, `Italic()`, built-in emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
44
+ Even in its beta stage, Telekit accelerates bot development, offering typed command parameters, text styling via `Bold()`, `Italic()`, a built-in declarative calendar picker, emoji game results for `🎲 🎯 🏀 ⚽ 🎳 🎰`, and much more out of the box. Its declarative design makes bots easier to read, maintain, and extend.
45
45
 
46
46
  **Key features:**
47
47
  - Declarative bot logic with **chains** for effortless handling of complex conversations
@@ -49,6 +49,7 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
49
49
  - Automatic handling of [message formatting](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/6_styles.md) via [Sender](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/5_senders.md) and **callback routing**
50
50
  - **Deep Linking** support with type-checked [Command Parameters](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial2/command_trigger_parameters.md) for flexible user input
51
51
  - Built-in **Permission** and **Logging** system for user management
52
+ - Reusable **Traits** system for pluggable, self-contained behavior modules
52
53
  - Seamless integration with [pyTelegramBotAPI](https://github.com/eternnoir/pyTelegramBotAPI)
53
54
  - Fast to develop and easy-to-extend code
54
55
 
@@ -65,7 +66,8 @@ Even in its beta stage, Telekit accelerates bot development, offering typed comm
65
66
  - [Dialogue](https://github.com/Romashkaa/telekit/blob/main/docs/examples/dialogue.md)
66
67
  - [Risk Game](https://github.com/Romashkaa/telekit/blob/main/docs/examples/risk_game.md)
67
68
  - [Counter](https://github.com/Romashkaa/telekit/blob/main/docs/examples/counter.md)
68
- - [Quiz (Telekit DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
69
+ - [Quiz (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
70
+ - [Hotel (DSL)](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
69
71
  - [More...](https://github.com/Romashkaa/telekit/blob/main/docs/examples/examples.md)
70
72
 
71
73
  ## Overview
@@ -311,6 +313,42 @@ telekit.Server(BOT_TOKEN).polling()
311
313
 
312
314
  You can find a [full quiz example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md) and [DSL reference](https://github.com/Romashkaa/telekit/blob/main/docs/tutorial/11_telekit_dsl.md) in the repository.
313
315
 
316
+ ### Traits
317
+
318
+ Traits are reusable behavior modules you can mix into any handler.
319
+
320
+ This example demonstrates the simplest way to use the built-in CalendarPick trait. It allows a user to pick a date from an inline calendar and handles the result via a callback.
321
+
322
+ ```py
323
+ from telekit.traits import CalendarPick
324
+
325
+ class CalendarHandler(CalendarPick, telekit.Handler):
326
+
327
+ @classmethod
328
+ def init_handler(cls) -> None:
329
+ cls.on.command("calendar").invoke(cls.handle)
330
+
331
+ def handle(self) -> None:
332
+ self.chain.sender.set_title("📅 Choose a date")
333
+ self.chain.sender.set_message("Select any date — past or future:")
334
+ self.chain.sender.set_remove_text(False)
335
+
336
+ self.calendar_pick(self.handle_date) # HERE
337
+
338
+ def handle_date(self, date: datetime.date) -> None:
339
+ self.chain.sender.set_text(f"You picked: {date}")
340
+ self.chain.send()
341
+ ```
342
+
343
+ <details>
344
+ <summary>Result</summary>
345
+ <table>
346
+ <tr>
347
+ <td><img src="./docs/images/calendar.png" alt="Telekit Calendar Example" width="500"></td>
348
+ </tr>
349
+ </table>
350
+ </details>
351
+
314
352
  ### Example Bot
315
353
 
316
354
  You can launch an example bot by **running the following code**:
@@ -331,6 +369,7 @@ It includes example commands, dialogs, keyboards, and style usage.
331
369
  - **Styles API** for rich text (`Bold`, `Italic`, `Links`) with **automatic escaping**.
332
370
  - Deep linking and **typed command parameters**.
333
371
  - **Built-in DSL** for menus, FAQs, and simple bots.
372
+ - Reusable **Traits** for composable, plug-and-play behavior (for example, a built-in declarative calendar picker).
334
373
  - **Zero-code** [Obsidian Canvas](https://github.com/Romashkaa/telekit/blob/main/docs/examples/canvas_faq.md) mode.
335
374
  - Seamless integration with **pyTelegramBotAPI**.
336
375
 
@@ -28,8 +28,7 @@ def readme():
28
28
  return f.read()
29
29
 
30
30
  def changelog():
31
- with open('CHANGELOG.md', 'r') as f:
32
- return f.read()
31
+ return f"[View changelog on GitHub](https://github.com/Romashkaa/telekit/blob/main/CHANGELOG.md)"
33
32
 
34
33
  def install_requires():
35
34
  with open('telekit/requirements.txt', 'r') as f:
@@ -18,6 +18,7 @@
18
18
  #
19
19
 
20
20
  from ._handler import Handler
21
+ from ._trait import Trait
21
22
  from ._chain import Chain
22
23
  from ._callback_query_handler import CallbackQueryHandler
23
24
  from .server import Server, example
@@ -35,12 +36,15 @@ from . import parameters
35
36
  from . import inline_buttons
36
37
  from . import dices
37
38
  from . import utils
39
+ from . import traits
40
+ from . import debug
38
41
 
39
42
  Styles = styles.Styles
40
43
 
41
44
  from ._version import __version__
42
45
 
43
46
  __all__ = [
47
+ "traits",
44
48
  "utils",
45
49
  "senders",
46
50
  "types",
@@ -54,6 +58,7 @@ __all__ = [
54
58
 
55
59
  "Server",
56
60
  "Chain",
61
+ "Trait",
57
62
  "Handler",
58
63
  "CallbackQueryHandler",
59
64
 
@@ -64,4 +69,5 @@ __all__ = [
64
69
  "enable_file_logging",
65
70
  "chapters",
66
71
  "example",
72
+ "debug",
67
73
  ]
@@ -40,15 +40,17 @@ class CallbackQueryHandler:
40
40
  bot (TeleBot): The Telegram bot instance to be used for sending messages.
41
41
  """
42
42
  cls.bot = bot
43
- cls.user_button_callbacks: dict[int, dict[str, Callable[[telebot.types.CallbackQuery], None]]] = {}
43
+ cls.user_button_callbacks: dict[int, dict[str, Callable[[CallbackQuery], None]]] = {}
44
44
 
45
45
  @bot.callback_query_handler(func=lambda call: True)
46
- def handle(call: telebot.types.CallbackQuery) -> None:
46
+ def handle(call: CallbackQuery) -> None:
47
47
  if not call.data:
48
48
  return
49
49
 
50
50
  if call.data.startswith(cls.INLINE_BUTTON):
51
51
  cls._handle_inline_button(call)
52
+ elif call.data.startswith(cls.STATIC_BUTTON):
53
+ cls._handle_static_button(call)
52
54
  elif call.data.startswith(cls.SUGGEST):
53
55
  cls._handle_suggestion(call)
54
56
  else:
@@ -59,7 +61,7 @@ class CallbackQueryHandler:
59
61
  # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
60
62
 
61
63
  @classmethod
62
- def _handle_inline_button(cls, call: telebot.types.CallbackQuery):
64
+ def _handle_inline_button(cls, call: CallbackQuery):
63
65
  if not call.data:
64
66
  cls.bot.answer_callback_query(call.id, text=cls._invalid_data_answer[0], show_alert=cls._invalid_data_answer[1])
65
67
  return
@@ -76,10 +78,19 @@ class CallbackQueryHandler:
76
78
  cls.bot.answer_callback_query(call.id, text=cls._button_is_no_active_answer[0], show_alert=cls._button_is_no_active_answer[1])
77
79
  return
78
80
 
79
- cls.remove_user_button_callbacks(call.from_user.id)
81
+ if not getattr(callback, "_persistent", False):
82
+ cls.remove_user_button_callbacks(call.from_user.id)
80
83
 
81
84
  callback(call)
82
85
 
86
+ # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
87
+ # Static Buttons Handling
88
+ # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
89
+
90
+ @classmethod
91
+ def _handle_static_button(cls, call: CallbackQuery):
92
+ cls.bot.answer_callback_query(call.id, text="")
93
+
83
94
  # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
84
95
  # Suggestion Handling
85
96
  # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
@@ -87,7 +98,7 @@ class CallbackQueryHandler:
87
98
  @classmethod
88
99
  def _handle_suggestion(cls, call: CallbackQuery):
89
100
  text: str = str(call.data)[len(cls.SUGGEST):]
90
- cls.simulate(call.message, text, from_user=call.from_user)
101
+ cls.simulate(call.message, text, from_user=call.from_user) # pyright: ignore[reportArgumentType]
91
102
  cls.bot.answer_callback_query(call.id)
92
103
 
93
104
  @classmethod
@@ -126,9 +137,10 @@ class CallbackQueryHandler:
126
137
  # Query Types
127
138
  # –––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––––
128
139
 
140
+ STATIC_BUTTON: str = "static_button"
129
141
  INLINE_BUTTON: str = "inline_button:"
130
142
  SUGGEST: str = "suggest:"
131
-
143
+
132
144
  @classmethod
133
145
  def suggest(cls, suggestion: str):
134
146
  return f"{cls.SUGGEST}{suggestion}"
@@ -26,6 +26,7 @@ from telebot.types import Message
26
26
 
27
27
  # Local modules
28
28
  from . import senders
29
+ from .debug import Debug
29
30
  from .styles import TextEntity
30
31
 
31
32
  # Chain modules
@@ -154,7 +155,7 @@ class Chain(ChainInlineKeyboardLogic, ChainEntryLogic):
154
155
  self.sender.set_edit_message(None)
155
156
  self._previous_message = message
156
157
 
157
- if self._timeout_warnings_enabled and _handler and not _timeout:
158
+ if Debug.timeout_warnings and _handler and not _timeout:
158
159
  library.warning(
159
160
  "Next-message handler is active, but no timeout was set for the chain. "
160
161
  "This may cause the bot to wait indefinitely."
@@ -31,14 +31,13 @@ from . import _input_handler
31
31
  from . import _timeout
32
32
 
33
33
  # Logging
34
+ from .debug import Debug
34
35
  from ._logger import logger
35
36
  library = logger.library
36
37
 
37
38
  class ChainBase:
38
39
 
39
40
  bot: telebot.TeleBot
40
-
41
- _timeout_warnings_enabled: bool = True
42
41
 
43
42
  @classmethod
44
43
  def _init(cls, bot: telebot.TeleBot):
@@ -206,4 +205,5 @@ class ChainBase:
206
205
  # Timeout API
207
206
 
208
207
  def disable_timeout_warnings(self, value: bool = True) -> None:
209
- self._timeout_warnings_enabled = not value
208
+ """.. deprecated:: Set ``Debug.timeout_warnings`` directly."""
209
+ Debug.timeout_warnings = not value
@@ -40,8 +40,7 @@ if typing.TYPE_CHECKING:
40
40
  class ChainInlineKeyboardLogic(ChainBase):
41
41
  def set_inline_keyboard(
42
42
  self,
43
- keyboard: dict[str, Callable[[], Any] | InlineButton | str],
44
- *,
43
+ keyboard: dict[str, Any],
45
44
  row_width: int | Iterable[int] = 1
46
45
  ) -> None:
47
46
  """
@@ -118,7 +117,6 @@ class ChainInlineKeyboardLogic(ChainBase):
118
117
  def inline_keyboard[Caption: str, Value](
119
118
  self,
120
119
  keyboard: dict[Caption, Value],
121
- *,
122
120
  row_width: int | Iterable[int] = 1,
123
121
  enable_special_buttons: bool = True
124
122
  ) -> Callable[[Callable[[Value], None]], None]:
@@ -183,7 +181,6 @@ class ChainInlineKeyboardLogic(ChainBase):
183
181
  def inline_choice(
184
182
  self,
185
183
  choices: list[Any] | tuple[Any, ...] | dict[str, Any | InlineButton],
186
- *,
187
184
  row_width: int | Iterable[int] = 1,
188
185
  enable_special_buttons: bool = True
189
186
  ) -> Callable[[Callable[[Any], None]], None]:
@@ -219,7 +216,6 @@ class ChainInlineKeyboardLogic(ChainBase):
219
216
  self,
220
217
  func: Callable[[Any], None],
221
218
  choices: list[Any] | tuple[Any, ...] | dict[str, Any | InlineButton],
222
- *,
223
219
  row_width: int | Iterable[int] = 1,
224
220
  enable_special_buttons: bool = True
225
221
  ) -> None:
@@ -225,9 +225,16 @@ class Handler:
225
225
 
226
226
  handler_instance = handler(self.message)
227
227
  handler_instance.chain._set_previous_message(self.chain.get_previous_message())
228
+ handler_instance._on_handoff(self)
228
229
 
229
230
  return handler_instance
230
231
 
232
+
233
+ def _on_handoff(self, origin: "Handler") -> None:
234
+ """Called when this handler is reached via handoff(). Override to customize."""
235
+ pass
236
+
237
+
231
238
  def freeze(self, func, *args):
232
239
  """
233
240
  Return a zero-argument callback that invokes the given function
@@ -31,7 +31,7 @@ import telebot
31
31
  __all__ = ["init"]
32
32
 
33
33
  def init(bot: telebot.TeleBot) -> None:
34
- TelekitState.init(bot)
34
+ TelekitState._init(bot)
35
35
  BaseSender._init(bot)
36
36
  Handler._init(bot)
37
37
  Chain._init(bot)
@@ -26,14 +26,18 @@ from telebot.types import InlineKeyboardButton, CallbackQuery
26
26
  from telebot import TeleBot
27
27
  from ._callback_query_handler import CallbackQueryHandler
28
28
 
29
+ from ._state import TelekitState
30
+
29
31
  __all__ = [
30
- "InlineButton",
32
+ "InlineButton",
31
33
 
34
+ "StaticButton",
32
35
  "LinkButton",
33
36
  "WebAppButton",
34
37
  "SuggestButton",
35
38
  "CopyTextButton",
36
39
  "CallbackButton",
40
+ "AnswerButton",
37
41
  "AlertButton",
38
42
  "NotificationButton",
39
43
  "InvokeButton",
@@ -57,9 +61,11 @@ class InlineButton:
57
61
  - `SuggestButton`
58
62
  - `CopyTextButton`
59
63
  - `CallbackButton`
60
- - `AlertButton`
61
- - `NotificationButton`
62
- - `InvokeButton`
64
+ - `InvokeButton`
65
+ - `AnswerButton`
66
+ - `AlertButton`
67
+ - `NotificationButton`
68
+ - `StaticButton`
63
69
  """
64
70
 
65
71
  _bot: TeleBot
@@ -70,12 +76,14 @@ class InlineButton:
70
76
 
71
77
  MAX_SIZE: int | None = None
72
78
  MIN_SIZE: int | None = None
73
-
79
+
80
+ Static: type["StaticButton"]
74
81
  Link: type["LinkButton"]
75
82
  WebApp: type["WebAppButton"]
76
83
  Suggest: type["SuggestButton"]
77
84
  CopyText: type["CopyTextButton"]
78
85
  Callback: type["CallbackButton"]
86
+ Answer: type["AnswerButton"]
79
87
  Alert: type["AlertButton"]
80
88
  Notification: type["NotificationButton"]
81
89
  Invoke: type["InvokeButton"]
@@ -115,7 +123,29 @@ class InlineButton:
115
123
  return normalized
116
124
  raise ValueError(f"Unknown style: {style!r}. Must be one of {_BUTTON_STYLES_LIST}")
117
125
  raise TypeError(f"Style must be str, ButtonStyle, or None, got {type(style)}")
126
+
127
+ def _normalize_custom_emoji_id(self, custom_emoji_id: int | str | None) -> str | None:
128
+ if not custom_emoji_id:
129
+ return None
130
+
131
+ if not TelekitState.is_premium():
132
+ return None
118
133
 
134
+ return str(custom_emoji_id)
135
+
136
+ class StaticButton(InlineButton):
137
+
138
+ def __init__(self, *, style: str | None | ButtonStyle = None, **kwargs):
139
+ self._style = self._normalize_style(style)
140
+ self._kwargs = kwargs
141
+
142
+ def _compile(self, caption: str) -> InlineKeyboardButton:
143
+ return InlineKeyboardButton(
144
+ text=caption,
145
+ callback_data=CallbackQueryHandler.STATIC_BUTTON,
146
+ style=self._style,
147
+ **self._kwargs,
148
+ )
119
149
 
120
150
  class LinkButton(InlineButton):
121
151
  """
@@ -339,8 +369,8 @@ class CallbackButton(InlineButton):
339
369
 
340
370
  def __call__(self, call: CallbackQuery):
341
371
  self._invoke_chain_callback()
342
- self._invoke_callback()
343
372
  self._answer_callback_query(call)
373
+ self._invoke_callback()
344
374
 
345
375
  def __init__(
346
376
  self,
@@ -387,7 +417,77 @@ class CallbackButton(InlineButton):
387
417
  callback=callback,
388
418
  )
389
419
 
390
- class AlertButton(CallbackButton):
420
+ class AnswerButton(CallbackButton):
421
+
422
+ class _CallbackInvoker(CallbackButton._CallbackInvoker):
423
+ def __init__(
424
+ self,
425
+ chain_callback: Callable[[], None],
426
+
427
+ answer_text: str | None = None,
428
+ answer_as_alert: bool = True,
429
+
430
+ persistent: bool = True,
431
+
432
+ style: str | None | ButtonStyle = None,
433
+
434
+ kwargs: dict[str, Any] = {}
435
+ ):
436
+ self._answer_text = answer_text
437
+ self._answer_as_alert = answer_as_alert
438
+
439
+ self._persistent = persistent
440
+
441
+ self._style = style
442
+
443
+ self._kwargs = {"style": style} | kwargs
444
+
445
+ self._chain_callback: Callable[[], None] = chain_callback
446
+
447
+ def _answer_callback_query(self, call: CallbackQuery):
448
+ if self._answer_text:
449
+ InlineButton._bot.answer_callback_query(
450
+ call.id,
451
+ self._answer_text,
452
+ self._answer_as_alert
453
+ )
454
+
455
+ def __call__(self, call: CallbackQuery):
456
+ if not self._persistent:
457
+ self._invoke_chain_callback()
458
+ self._answer_callback_query(call)
459
+
460
+ def __init__(
461
+ self,
462
+ answer_text: str | None = None,
463
+ answer_as_alert: bool = True,
464
+
465
+ persistent: bool = True,
466
+
467
+ style: str | None | ButtonStyle = None,
468
+
469
+ **kwargs
470
+ ):
471
+ self._answer_text = answer_text
472
+ self._answer_as_alert = answer_as_alert
473
+
474
+ self._persistent = persistent
475
+
476
+ self._style = self._normalize_style(style)
477
+
478
+ self._kwargs = kwargs
479
+
480
+ def build_invoker(self, chain_callback: Callable[[], None]) -> _CallbackInvoker:
481
+ return self._CallbackInvoker(
482
+ chain_callback=chain_callback,
483
+ answer_text=self._answer_text,
484
+ answer_as_alert=self._answer_as_alert,
485
+ persistent=self._persistent,
486
+ style=self._style,
487
+ kwargs=self._kwargs
488
+ )
489
+
490
+ class AlertButton(AnswerButton):
391
491
  """
392
492
  An inline keyboard button that shows a popup alert when pressed and terminates the chain.
393
493
 
@@ -424,19 +524,19 @@ class AlertButton(CallbackButton):
424
524
  def __init__(
425
525
  self,
426
526
  text: str | None = None,
427
- *,
527
+ persistent: bool = True,
428
528
  style: str | None | ButtonStyle = None,
429
529
  **kwargs
430
530
  ):
431
531
  super().__init__(
432
- callback=None,
433
532
  answer_text=text,
434
533
  answer_as_alert=True,
534
+ persistent=persistent,
435
535
  style=style,
436
536
  **kwargs
437
537
  )
438
538
 
439
- class NotificationButton(CallbackButton):
539
+ class NotificationButton(AnswerButton):
440
540
  """
441
541
  An inline keyboard button that shows a brief notification at the top of the chat screen
442
542
  when pressed and terminates the chain.
@@ -475,13 +575,14 @@ class NotificationButton(CallbackButton):
475
575
  self,
476
576
  text: str | None = None,
477
577
  *,
578
+ persistent: bool = True,
478
579
  style: str | None | ButtonStyle = None,
479
580
  **kwargs
480
581
  ):
481
582
  super().__init__(
482
- callback=None,
483
583
  answer_text=text,
484
584
  answer_as_alert=False,
585
+ persistent=persistent,
485
586
  style=style,
486
587
  **kwargs
487
588
  )
@@ -611,10 +712,12 @@ class InvokeButton(CallbackButton):
611
712
  )
612
713
 
613
714
  InlineButton.Link = LinkButton
715
+ InlineButton.Static = StaticButton
614
716
  InlineButton.WebApp = WebAppButton
615
717
  InlineButton.Suggest = SuggestButton
616
718
  InlineButton.CopyText = CopyTextButton
617
719
  InlineButton.Callback = CallbackButton
720
+ InlineButton.Answer = AnswerButton
618
721
  InlineButton.Alert = AlertButton
619
722
  InlineButton.Notification = NotificationButton
620
723
  InlineButton.Invoke = InvokeButton