telekit 2.4.0b0__tar.gz → 2.5.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 (88) hide show
  1. {telekit-2.4.0b0 → telekit-2.5.0}/PKG-INFO +122 -96
  2. {telekit-2.4.0b0 → telekit-2.5.0}/README.md +82 -26
  3. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/__init__.py +14 -1
  4. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_buildtext/formatter.py +101 -17
  5. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_buildtext/styles.py +77 -60
  6. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chain_base.py +0 -1
  7. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chain_entry_logic.py +4 -4
  8. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chain_inline_keyboards_logic.py +69 -0
  9. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_handler.py +10 -1
  10. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_init.py +2 -0
  11. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_inline_buttons.py +99 -11
  12. telekit-2.5.0/telekit/_inline_keyboard.py +719 -0
  13. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_input_handler.py +3 -2
  14. telekit-2.5.0/telekit/_reply_keyboard.py +521 -0
  15. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/mixin.py +1 -1
  16. telekit-2.5.0/telekit/_text_builder.py +529 -0
  17. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_version.py +1 -1
  18. telekit-2.5.0/telekit/chat.py +101 -0
  19. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/debug.py +1 -0
  20. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/__init__.py +2 -0
  21. telekit-2.5.0/telekit/example/example_handlers/article.py +66 -0
  22. telekit-2.5.0/telekit/example/example_handlers/counter.py +84 -0
  23. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/on_text.py +20 -15
  24. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/start.py +4 -2
  25. telekit-2.5.0/telekit/example/example_handlers/style.py +73 -0
  26. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/text_document.py +1 -1
  27. telekit-2.5.0/telekit/html_text.py +812 -0
  28. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/inline_buttons.py +10 -0
  29. telekit-2.5.0/telekit/reply_buttons.py +254 -0
  30. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/senders.py +43 -9
  31. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/traits/__init__.py +1 -0
  32. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/traits/calendar_pick.py +180 -195
  33. telekit-2.5.0/telekit/traits/paginated_text.py +378 -0
  34. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/types.py +45 -10
  35. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/utils.py +45 -9
  36. {telekit-2.4.0b0 → telekit-2.5.0}/telekit.egg-info/PKG-INFO +122 -96
  37. {telekit-2.4.0b0 → telekit-2.5.0}/telekit.egg-info/SOURCES.txt +9 -0
  38. telekit-2.4.0b0/telekit/example/example_handlers/counter.py +0 -46
  39. {telekit-2.4.0b0 → telekit-2.5.0}/LICENSE +0 -0
  40. {telekit-2.4.0b0 → telekit-2.5.0}/setup.cfg +0 -0
  41. {telekit-2.4.0b0 → telekit-2.5.0}/setup.py +0 -0
  42. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_buildtext/__init__.py +0 -0
  43. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_callback_query_handler.py +0 -0
  44. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chain.py +0 -0
  45. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chapters/__init__.py +0 -0
  46. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_chapters/chapters.py +0 -0
  47. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_logger.py +0 -0
  48. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_on.py +0 -0
  49. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_snapvault/__init__.py +0 -0
  50. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_snapvault/snapcode.py +0 -0
  51. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_snapvault/snapvault.py +0 -0
  52. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_state.py +0 -0
  53. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/__init__.py +0 -0
  54. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/__init__.py +0 -0
  55. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/builder.py +0 -0
  56. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/canvas_parser.py +0 -0
  57. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/lexer.py +0 -0
  58. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/nodes.py +0 -0
  59. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/parser.py +0 -0
  60. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/parser/token.py +0 -0
  61. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/telekit_dsl.py +0 -0
  62. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_telekit_dsl/telekit_orm.py +0 -0
  63. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_timeout.py +0 -0
  64. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_trait.py +0 -0
  65. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/_user.py +0 -0
  66. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/dices.py +0 -0
  67. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/__init__.py +0 -0
  68. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/calendar.py +0 -0
  69. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/complete_hotel.py +0 -0
  70. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/dsl.py +0 -0
  71. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/entry.py +0 -0
  72. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/faq.py +0 -0
  73. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/hotel.py +0 -0
  74. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/pages.py +0 -0
  75. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/pyapi.py +0 -0
  76. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/qr.py +0 -0
  77. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/quiz.py +0 -0
  78. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_handlers/spells.py +0 -0
  79. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/example/example_server.py +0 -0
  80. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/parameters.py +0 -0
  81. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/scheduler.py +0 -0
  82. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/server.py +0 -0
  83. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/styles.py +0 -0
  84. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/traits/paginated_choice.py +0 -0
  85. {telekit-2.4.0b0 → telekit-2.5.0}/telekit/traits/track_handoff_origin.py +0 -0
  86. {telekit-2.4.0b0 → telekit-2.5.0}/telekit.egg-info/dependency_links.txt +0 -0
  87. {telekit-2.4.0b0 → telekit-2.5.0}/telekit.egg-info/requires.txt +0 -0
  88. {telekit-2.4.0b0 → telekit-2.5.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.4.0b0
3
+ Version: 2.5.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
@@ -75,7 +75,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
75
75
  }
76
76
  ```
77
77
 
78
- > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
78
+ > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
79
79
 
80
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
 
@@ -133,36 +133,91 @@ The `handle` method sends a message and registers `handle_name` as the next step
133
133
 
134
134
  ### Inline Keyboards
135
135
 
136
- Buttons can either **return a value** or **call a method directly**.
136
+ The fastest way to add buttons to a message. Pass a plain `dict` where each key is the button label and each value is the callback to invoke when pressed:
137
137
 
138
- **Choice keyboard** — map button labels to values. The selected value is passed straight into your handler:
139
-
140
- ```py
141
- self.chain.set_inline_choice(
142
- self.on_choice,
143
- choices={
144
- "Option 1": "Value 1",
145
- "Option 2": "Value 2",
146
- "Option 3": [3, "Yes, it's an array"],
138
+ ```python
139
+ self.chain.set_inline_keyboard(
140
+ {
141
+ "✏️ Change": self.change_name,
142
+ "❌ Delete": self.delete,
147
143
  }
148
144
  )
145
+ ```
146
+
147
+ `row_width` controls how many buttons appear per row:
148
+
149
+ ```python
150
+ self.chain.set_inline_keyboard(
151
+ {
152
+ "One": self.one,
153
+ "Two": self.two,
154
+ "Three": self.three,
155
+ "Four": self.four,
156
+ "Five": self.five,
157
+ },
158
+ row_width=(3, 2) # first row: 3 buttons, second row: 2
159
+ )
160
+ ```
149
161
 
150
- def on_choice(self, choice: str | list):
151
- print(f"{choice!r}") # "Value 1", "Value 2" or [3, "Yes, it's an array"]
162
+ ```
163
+ ╭──────────┬──────────┬──────────╮
164
+ │ One │ Two │ Three │
165
+ ├──────────┴──┬───────┴──────────┤
166
+ │ Four │ Five │
167
+ ╰─────────────┴──────────────────╯
152
168
  ```
153
169
 
154
- Inside `on_choice`, you receive exactly what you defined in `choices`: a string, list, number, function — anything.
170
+ **Need more control?**
155
171
 
156
- **Callback keyboard** — each button calls its own method:
172
+ When you need precise row layout or conditional buttons, use `InlineKeyboard` — a fluent builder that composes keyboards step by step:
173
+
174
+ ```python
175
+ self.chain.set_keyboard(
176
+ InlineKeyboard()
177
+ .add_callback("-", self.decrement, style="danger")
178
+ .add_callback("+", self.increment, style="success")
179
+ .row()
180
+ .add_callback("↺ Reset", self.reset)
181
+ )
182
+ ```
157
183
 
158
- ```py
159
- self.chain.set_inline_keyboard({
160
- "« Back": self.display_previous_page,
161
- "Next »": self.display_next_page,
162
- })
163
184
  ```
185
+ ╭──────────┬──────────╮
186
+ │ - │ + │
187
+ ├──────────┴──────────┤
188
+ │ ↺ Reset │
189
+ ╰─────────────────────╯
190
+ ```
191
+
192
+ > `InlineKeyboard` is built by chaining method calls. Just call `.row()` to start a new row.
193
+
194
+ **Not just callback buttons**
164
195
 
165
- Useful for pagination, navigation, or menus.
196
+ | **Method** | **Description** |
197
+ | --------------------- | ---------------------------------------------------------------- |
198
+ | `add_callback(...)` | Button that fires a callback function. |
199
+ | `add_link(...)` | Button that opens a URL. |
200
+ | `add_copy(...)` | Button that copies text to the clipboard. |
201
+ | `add_alert(...)` | Button that shows a popup alert dialog. |
202
+ | `add_notification(...)` | Button that shows a brief top-of-chat notification. |
203
+ | `add_static(...)` | Decorative button with no action. |
204
+ | `add_webapp(...)` | Button that opens a Telegram Mini App. |
205
+ | `add_suggest(...)` | Button that simulates the user sending a message. |
206
+
207
+ ### Reply Keyboards
208
+
209
+ Unlike inline keyboards, reply keyboards replace the user's system keyboard with buttons shown at the bottom of the chat. Tapping a button either sends its text as a regular message or triggers a system action, such as sharing a phone number or location.
210
+
211
+ ```python
212
+ self.chain.set_keyboard(
213
+ ReplyKeyboard(one_time_keyboard=True)
214
+ .add_text("Hello!")
215
+ .add_text("Hi")
216
+ .row()
217
+ .add_contact("📱 Share phone")
218
+ .add_location("📍 Share location")
219
+ )
220
+ ```
166
221
 
167
222
  ### Command Parameters
168
223
 
@@ -186,7 +241,7 @@ class GreetHandler(telekit.Handler):
186
241
 
187
242
  Now `/greet 64 "Alice Reingold"` or `/greet 128 Dracula` are parsed automatically.
188
243
 
189
- > [!INFO]
244
+ > [!NOTE]
190
245
  > If arguments are invalid or missing, you simply receive `None` and decide how to respond.
191
246
 
192
247
  ### Dialogue
@@ -242,7 +297,7 @@ self.chain.sender.send_chat_action(ChatAction.TYPING) # Send chat action. Use en
242
297
  ```
243
298
 
244
299
  > [!NOTE]
245
- > Telekit automatically decides whether to use 'bot.send_message' or 'bot.send_photo' based on the content
300
+ > Telekit automatically decides whether to use `bot.send_message` or `bot.send_photo` based on the content
246
301
 
247
302
  ### Styles
248
303
 
@@ -288,7 +343,7 @@ If you prefer not to write dialog logic in Python, you can use the built-in DSL
288
343
  ```py
289
344
  import telekit
290
345
 
291
- class QuizHandler(telekit.TelekitDSL.Mixin):
346
+ class QuizHandler(telekit.DSLHandler):
292
347
  @classmethod
293
348
  def init_handler(cls) -> None:
294
349
  cls.analyze_string(script)
@@ -338,7 +393,7 @@ telekit.Server(BOT_TOKEN).polling()
338
393
  - Jinja template engine
339
394
 
340
395
  <details>
341
- <summary>Click to see what you can do with the DSL</summary>
396
+ <summary>🎆 Click to see what you can do with the DSL</summary>
342
397
  <table>
343
398
  <tr>
344
399
  <td><img src="./docs/images/telekit_example_7.jpg" alt="Telekit Example 7" width="300"></td>
@@ -351,7 +406,8 @@ telekit.Server(BOT_TOKEN).polling()
351
406
  </table>
352
407
  </details>
353
408
 
354
- 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.
409
+ > [!TIP]
410
+ > 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.
355
411
 
356
412
  ### Traits
357
413
 
@@ -421,72 +477,42 @@ It tries to make Telegram bot development easier.
421
477
 
422
478
  ---
423
479
 
424
- # Changes in version 2.4.0b0
425
-
426
- ## Scheduler
427
-
428
- - Added `every` decorator for scheduling functions in a background daemon thread.
429
- - Implemented `PeriodicTask` class to manage periodic execution and error handling.
430
-
431
- ## Telekit DSL
432
-
433
- ### `InstanceDSLHandler`
434
-
435
- Instance-oriented variant of `DSLHandler` where each instance carries its own
436
- `executable_model`, `_script_data_factory`, and `_jinja_env` — allowing multiple
437
- instances to run completely independent scripts simultaneously.
438
-
439
- Use the `*_locally` instance methods instead of the class-level ones:
440
-
441
- ```python
442
- class MyHandler(telekit.InstanceDSLHandler):
443
- @classmethod
444
- def init_handler(cls) -> None:
445
- cls.on.message().invoke(cls.handle)
446
-
447
- def handle(self):
448
- script = fetch_script_from_db(self.user.id) # per-user DSL
449
- self.analyze_string_locally(script)
450
- self.start_script()
451
- ```
452
-
453
- | **Method** | **Description** |
454
- | --------------------------------------- | ---------------------------------------------------- |
455
- | `analyze_file_locally(path, encoding)` | Analyse a script file on this instance. |
456
- | `analyze_string_locally(script)` | Analyse a DSL string on this instance. |
457
- | `analyze_canvas_locally(file_path)` | Analyse an Obsidian `.canvas` file on this instance. |
458
- | `analyze_executable_model_locally(model)` | Load a pre-built model dict on this instance. |
459
-
460
- **Security**
461
-
462
- When accepting scripts from untrusted users, restrict dangerous features via the
463
- `RESTRICTED` class attribute. Set `DEFAULT_TIMEOUT` to control the fallback timeout,
464
- and `DEFAULT_CONFIG` to provide a safe base config.
465
-
466
- ```python
467
- class SafeDSL(telekit.InstanceDSLHandler):
468
- RESTRICTED: list[RestrictedToken] = ["hook", "jinja", "redirect", "handoff", "config"]
469
- DEFAULT_TIMEOUT = 120
470
- DEFAULT_CONFIG = {"template": "vars"}
471
- ```
472
-
473
- | **Token** | **Effect** |
474
- | -------------- | ----------------------------------------------------------------------------------------------- |
475
- | `"handoff"` | Disables `handoff` button type (cross-handler transitions). |
476
- | `"redirect"` | Disables `redirect` button type (simulated user messages). |
477
- | `"hook"` | Removes all `on_enter`, `on_enter_once`, `on_exit`, `on_timeout` hooks. |
478
- | `"jinja"` | Forces template engine to `"vars"`; Jinja is never executed. |
479
- | `"timeout"` | Ignores per-script `timeout_time`; uses `DEFAULT_TIMEOUT` only. |
480
- | `"config"` | Replaces script config with `DEFAULT_CONFIG`; `vars_*` keys are preserved unless `"vars"` is also set. |
481
- | `"vars"` | Removes all `vars_*` keys and disables `{{variable}}` substitution. |
482
- | `"images"` | Strips `image` field from every scene. |
483
- | `"links"` | Disables `link` button type (external URLs). |
484
- | `"suggest"` | Disables `suggest` button type (pre-filled entry suggestions). |
485
- | `"entry"` | Disables entry handlers (free-text input routing). |
486
- | `"next"` | Disables `next` magic scene navigation. |
487
- | `"back"` | Disables `back` magic scene navigation. |
488
-
489
- ## Others
490
-
491
- - Enhanced `Debug` class with callback query tracing functionality.
492
- - Refactored `BaseSender` to use `send_or_handle_error`.
480
+ # Changes in version 2.5.0
481
+
482
+ ### v2.5.0`b0`
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)
504
+
505
+ ### v2.5.0`b3`
506
+ - Added `utils.Markers` class
507
+ - Added `HTMLText` class for handling Telegram HTML strings with tag-aware indexing and slicing.
508
+ - Added `PaginatedText` trait for displaying long HTML text in a paginated format, supporting navigation and smart splitting.
509
+ - Added `__radd__` to `TextEntity`: `"Regular" + Bold(" and Bold")`
510
+ - Added `__mul__` to `TextEntity`: `Bold("Text") * 3`
511
+ - Added `enabled=` parameter to `TextEntity`: `Bold("bold text", enabled=is_text_bold)`
512
+ - Added `TextBuilder` class – a fluent message composition API mirroring `InlineKeyboard`'s builder pattern
513
+ - Added styles to `telekit.types`
514
+ - Added `utils.CyclicList`
515
+ - Fixed `_answer_callback_query` to always call `bot.answer_callback_query()`, even without a popup text
516
+
517
+ ### v2.5.0`rc2`
518
+ - Refactor `CalendarPick` trait to use `set_keyboard`
@@ -39,7 +39,7 @@ Telekit comes with a [built-in DSL](https://github.com/Romashkaa/telekit/blob/ma
39
39
  }
40
40
  ```
41
41
 
42
- > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/quiz.md)
42
+ > See the [full example](https://github.com/Romashkaa/telekit/blob/main/docs/examples/complete_hotel.md)
43
43
 
44
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
 
@@ -97,36 +97,91 @@ The `handle` method sends a message and registers `handle_name` as the next step
97
97
 
98
98
  ### Inline Keyboards
99
99
 
100
- Buttons can either **return a value** or **call a method directly**.
100
+ The fastest way to add buttons to a message. Pass a plain `dict` where each key is the button label and each value is the callback to invoke when pressed:
101
101
 
102
- **Choice keyboard** — map button labels to values. The selected value is passed straight into your handler:
103
-
104
- ```py
105
- self.chain.set_inline_choice(
106
- self.on_choice,
107
- choices={
108
- "Option 1": "Value 1",
109
- "Option 2": "Value 2",
110
- "Option 3": [3, "Yes, it's an array"],
102
+ ```python
103
+ self.chain.set_inline_keyboard(
104
+ {
105
+ "✏️ Change": self.change_name,
106
+ "❌ Delete": self.delete,
111
107
  }
112
108
  )
109
+ ```
113
110
 
114
- def on_choice(self, choice: str | list):
115
- print(f"{choice!r}") # "Value 1", "Value 2" or [3, "Yes, it's an array"]
111
+ `row_width` controls how many buttons appear per row:
112
+
113
+ ```python
114
+ self.chain.set_inline_keyboard(
115
+ {
116
+ "One": self.one,
117
+ "Two": self.two,
118
+ "Three": self.three,
119
+ "Four": self.four,
120
+ "Five": self.five,
121
+ },
122
+ row_width=(3, 2) # first row: 3 buttons, second row: 2
123
+ )
116
124
  ```
117
125
 
118
- Inside `on_choice`, you receive exactly what you defined in `choices`: a string, list, number, function — anything.
126
+ ```
127
+ ╭──────────┬──────────┬──────────╮
128
+ │ One │ Two │ Three │
129
+ ├──────────┴──┬───────┴──────────┤
130
+ │ Four │ Five │
131
+ ╰─────────────┴──────────────────╯
132
+ ```
119
133
 
120
- **Callback keyboard** — each button calls its own method:
134
+ **Need more control?**
121
135
 
122
- ```py
123
- self.chain.set_inline_keyboard({
124
- "« Back": self.display_previous_page,
125
- "Next »": self.display_next_page,
126
- })
136
+ When you need precise row layout or conditional buttons, use `InlineKeyboard` — a fluent builder that composes keyboards step by step:
137
+
138
+ ```python
139
+ self.chain.set_keyboard(
140
+ InlineKeyboard()
141
+ .add_callback("-", self.decrement, style="danger")
142
+ .add_callback("+", self.increment, style="success")
143
+ .row()
144
+ .add_callback("↺ Reset", self.reset)
145
+ )
127
146
  ```
128
147
 
129
- Useful for pagination, navigation, or menus.
148
+ ```
149
+ ╭──────────┬──────────╮
150
+ │ - │ + │
151
+ ├──────────┴──────────┤
152
+ │ ↺ Reset │
153
+ ╰─────────────────────╯
154
+ ```
155
+
156
+ > `InlineKeyboard` is built by chaining method calls. Just call `.row()` to start a new row.
157
+
158
+ **Not just callback buttons**
159
+
160
+ | **Method** | **Description** |
161
+ | --------------------- | ---------------------------------------------------------------- |
162
+ | `add_callback(...)` | Button that fires a callback function. |
163
+ | `add_link(...)` | Button that opens a URL. |
164
+ | `add_copy(...)` | Button that copies text to the clipboard. |
165
+ | `add_alert(...)` | Button that shows a popup alert dialog. |
166
+ | `add_notification(...)` | Button that shows a brief top-of-chat notification. |
167
+ | `add_static(...)` | Decorative button with no action. |
168
+ | `add_webapp(...)` | Button that opens a Telegram Mini App. |
169
+ | `add_suggest(...)` | Button that simulates the user sending a message. |
170
+
171
+ ### Reply Keyboards
172
+
173
+ Unlike inline keyboards, reply keyboards replace the user's system keyboard with buttons shown at the bottom of the chat. Tapping a button either sends its text as a regular message or triggers a system action, such as sharing a phone number or location.
174
+
175
+ ```python
176
+ self.chain.set_keyboard(
177
+ ReplyKeyboard(one_time_keyboard=True)
178
+ .add_text("Hello!")
179
+ .add_text("Hi")
180
+ .row()
181
+ .add_contact("📱 Share phone")
182
+ .add_location("📍 Share location")
183
+ )
184
+ ```
130
185
 
131
186
  ### Command Parameters
132
187
 
@@ -150,7 +205,7 @@ class GreetHandler(telekit.Handler):
150
205
 
151
206
  Now `/greet 64 "Alice Reingold"` or `/greet 128 Dracula` are parsed automatically.
152
207
 
153
- > [!INFO]
208
+ > [!NOTE]
154
209
  > If arguments are invalid or missing, you simply receive `None` and decide how to respond.
155
210
 
156
211
  ### Dialogue
@@ -206,7 +261,7 @@ self.chain.sender.send_chat_action(ChatAction.TYPING) # Send chat action. Use en
206
261
  ```
207
262
 
208
263
  > [!NOTE]
209
- > Telekit automatically decides whether to use 'bot.send_message' or 'bot.send_photo' based on the content
264
+ > Telekit automatically decides whether to use `bot.send_message` or `bot.send_photo` based on the content
210
265
 
211
266
  ### Styles
212
267
 
@@ -252,7 +307,7 @@ If you prefer not to write dialog logic in Python, you can use the built-in DSL
252
307
  ```py
253
308
  import telekit
254
309
 
255
- class QuizHandler(telekit.TelekitDSL.Mixin):
310
+ class QuizHandler(telekit.DSLHandler):
256
311
  @classmethod
257
312
  def init_handler(cls) -> None:
258
313
  cls.analyze_string(script)
@@ -302,7 +357,7 @@ telekit.Server(BOT_TOKEN).polling()
302
357
  - Jinja template engine
303
358
 
304
359
  <details>
305
- <summary>Click to see what you can do with the DSL</summary>
360
+ <summary>🎆 Click to see what you can do with the DSL</summary>
306
361
  <table>
307
362
  <tr>
308
363
  <td><img src="./docs/images/telekit_example_7.jpg" alt="Telekit Example 7" width="300"></td>
@@ -315,7 +370,8 @@ telekit.Server(BOT_TOKEN).polling()
315
370
  </table>
316
371
  </details>
317
372
 
318
- 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.
373
+ > [!TIP]
374
+ > 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.
319
375
 
320
376
  ### Traits
321
377
 
@@ -22,12 +22,16 @@ from ._trait import Trait
22
22
  from ._chain import Chain
23
23
  from ._callback_query_handler import CallbackQueryHandler
24
24
  from .server import Server, example
25
+ from ._inline_keyboard import InlineKeyboard
26
+ from ._reply_keyboard import ReplyKeyboard
27
+ from ._text_builder import TextBuilder
25
28
  from ._snapvault import Vault
26
29
  from ._chapters import chapters
27
30
  from ._user import User
28
31
  from ._telekit_dsl.telekit_dsl import TelekitDSL
29
32
  from ._telekit_dsl.mixin import DSLHandler, InstanceDSLHandler
30
33
  from ._logger import enable_file_logging
34
+ from .chat import Chat
31
35
 
32
36
  from . import senders
33
37
  from . import types
@@ -39,6 +43,8 @@ from . import utils
39
43
  from . import traits
40
44
  from . import debug
41
45
  from . import scheduler
46
+ from . import chat
47
+ from . import html_text
42
48
 
43
49
  Styles = styles.Styles
44
50
 
@@ -54,16 +60,23 @@ __all__ = [
54
60
  "inline_buttons",
55
61
  "dices",
56
62
  "scheduler",
63
+ "chat",
64
+ "html_text",
57
65
 
58
66
  "Styles",
59
67
  "User",
60
68
 
61
- "Server",
69
+ "Server",
70
+ "Chat",
62
71
  "Chain",
63
72
  "Trait",
64
73
  "Handler",
65
74
  "CallbackQueryHandler",
66
75
 
76
+ "InlineKeyboard",
77
+ "ReplyKeyboard",
78
+ "TextBuilder",
79
+
67
80
  "TelekitDSL",
68
81
  "DSLHandler",
69
82
  "InstanceDSLHandler",