### Initialize bot for localization example
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Initializes a Telegrinder bot with imports for localization nodes (BaseTranslator, I18NConfig, KeySeparator, UserSource) and the Text rule. This is the setup for the localization example that follows.
```python
from telegrinder import API, Telegrinder, Token
from telegrinder.node import BaseTranslator, I18NConfig, KeySeparator, UserSource
from telegrinder.rules import Text
bot = Telegrinder(API(Token.from_env()))
```
--------------------------------
### Minimal handler file example
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows a minimal handler file that creates a Dispatch instance. This is the pattern for blueprint-style layouts.
```python
# handlers/start.py
from telegrinder import Dispatch
dp = Dispatch(name="start")
```
--------------------------------
### Project directory structure
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
A minimal starting point for a multi-file bot, separating handlers, keyboards, nodes, and rules into their own directories.
```text
mybot/
bot.py
handlers/
start.py
admin.py
payments.py
keyboards/
menu.py
nodes/
db.py
rules/
is_admin.py
```
--------------------------------
### Dispatch checkbox example
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Recommended usage of Checkbox via the Dispatch shortcut. It sends a message with inline buttons, tracks presses, and returns a dictionary of picked options and the message ID. The example uses the Text rule to trigger on /checkbox and edits the message with the picked items.
```python
from telegrinder import API, Message, Telegrinder, Token, configure_dotenv, setup_logger
from telegrinder.rules import Text
configure_dotenv()
setup_logger()
api = API(token=Token.from_env())
bot = Telegrinder(api)
@bot.on.message(Text("/checkbox"))
async def action(message: Message) -> None:
picked, message_id = await (
bot.dispatch.checkbox(
message.chat_id,
message="Check your checkbox",
cancel_text="Cancel",
max_in_row=2,
)
.add_option("apple", "Apple", "Apple 🍏")
.add_option("banana", "Banana", "Banana 🍌", is_picked=True)
.add_option("pear", "Pear", "Pear 🍐")
.wait(message.api)
)
await message.edit(
text="You picked: {}".format(", ".join(key for key, value in picked.items() if value)),
chat_id=message.chat.id,
message_id=message_id,
)
bot.run_forever()
```
--------------------------------
### Set, get, and delete state operations
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Shows the three core state operations: set, get, and delete. Use these to store, retrieve, and remove state for a user. The example uses a user_id and a StateEnum value with an optional payload.
```python
await states.set(user_id, StateEnum.BLESSED, {"source": "admin"})
state = await states.get(user_id)
await states.delete(user_id)
```
--------------------------------
### MemoryStateStorage example with StateEnum and handlers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Shows the simplest way to use storage with MemoryStateStorage. Defines a StateEnum, sets states on /curse and /bless commands, and reads the state in a generic handler. Uses states.State() rules to restrict commands based on current state.
```python
import enum
from telegrinder import MemoryStateStorage, Message, StateData
from telegrinder.rules import StateMeta, Text
states = MemoryStateStorage()
class StateEnum(enum.StrEnum):
CURSED = "cursed"
BLESSED = "blessed"
@bot.on.message(
Text("/curse"),
# /curse is allowed if the user is blessed
# or if they do not have any state yet.
states.State(StateEnum.BLESSED) | states.State(StateMeta.NO_STATE),
)
async def curse_handler(message: Message) -> None:
await states.set(message.from_user.id, StateEnum.CURSED, {})
await message.answer("You are now cursed.")
@bot.on.message(Text("/bless"), states.State(StateMeta.NO_STATE))
async def bless_handler(message: Message) -> None:
await states.set(message.from_user.id, StateEnum.BLESSED, {})
await message.answer("You are now blessed.")
@bot.on.message()
async def any_message_handler(message: Message) -> None:
state = await states.get(message.from_user.id)
vibe = state.unwrap_or(StateData("normal", {})).key
await message.answer(f"You are currently {vibe}.")
```
--------------------------------
### GlobalContext magic methods
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Demonstrates the magic methods __iter__, __contains__, __eq__, and __bool__. The example uses undefined DatabaseSession and WaiterMachine classes.
```python
from telegrinder.tools.global_context import GlobalContext
global_ctx = GlobalContext(db_session=DatabaseSession(...), waiter_machine=WaiterMachine())
ctx_vars = list(ctx1)
assert "db_session" in global_ctx and "http_client" not in global_ctx # ok
assert global_ctx # ok, is not empty
other_ctx = GlobalContext()
assert global_ctx == other_ctx # ok, same
```
--------------------------------
### Run the bot with run_forever
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/1_setting_up
Starts the bot asynchronously to receive updates in a loop. This line must be at the end of the file.
```python
bot.run_forever()
```
--------------------------------
### Send formatted message in handler
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Example handler that responds to the /formatting command with a formatted message using HTML and mention.
```python
@bot.on.message(Text("/formatting"))
async def formatting(message: Message) -> None:
await message.answer(
HTML << "Hello, " << mention(message.from_user.first_name, user_id=message.from_user.id),
parse_mode=HTML.PARSE_MODE,
)
```
--------------------------------
### Load a dispatch into the main bot
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows how to load a dispatch from a separate module into the main bot. The code assumes the dispatch from the previous example lives in `handlers/chat_utilities.py`.
```python
from handlers import chat_utilities
from telegrinder import API, Telegrinder, Token
api = API(Token("your-token-here"))
bot = Telegrinder(api)
```
--------------------------------
### Raw HTML string example
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
A raw HTML string example that illustrates why hand-writing HTML can be annoying: user input may not be escaped, closing tags are easy to miss, and it's harder to see what each piece does.
```python
text = "Hello world"
```
--------------------------------
### Choice waiter with options and selection
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Demonstrates using the built-in Choice waiter to let a user pick exactly one option from a set. The example shows adding options with normal and selected text, waiting for the user's reply, and editing the message with the chosen value.
```python
@bot.on.message(Text("/choice"))
async def action(message: Message) -> None:
chosen, message_id = await (
bot.dispatch.choice(message.chat.id, message="Choose something", max_in_row=1)
# First text is the normal option, second text is the selected version.
.add_option("apple", "Apple 🔴", "Apple 🟢")
.add_option("banana", "Banana 🔴", "Banana 🟢", is_picked=True)
.add_option("pear", "Pear 🔴", "Pear 🟢")
.wait(message.api)
)
await message.edit(
text=f"You chose: {chosen}",
message_id=message_id,
)
```
--------------------------------
### Register startup and shutdown hooks
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Registers startup and shutdown hooks on a standalone LoopWrapper using the lifespan decorators. The hooks run when the wrapper starts and stops.
```python
from telegrinder.tools import LoopWrapper
loop_wrapper = LoopWrapper()
@loop_wrapper.lifespan.on_startup
async def start() -> None:
await open_db_connection()
@loop_wrapper.lifespan.on_shutdown
async def stop() -> None:
await close_db_connection()
```
--------------------------------
### Adding options to Checkbox
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Shows how to add options to a Checkbox scenario. Each option has a key, default text, picked text, and optional is_picked initial state. The example adds cheese and bacon options with is_picked=True for bacon.
```python
checkbox = (
bot.dispatch.checkbox(chat_id, message="Choose toppings")
.add_option("cheese", "Cheese", "Cheese ✅")
.add_option("bacon", "Bacon", "Bacon ✅", is_picked=True)
)
```
--------------------------------
### Set context variable value
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Set context variables using attribute assignment or item assignment. The example assumes a Kitten class is defined elsewhere.
```python
from telegrinder.tools.global_context import GlobalContext
ctx = GlobalContext()
ctx.kitten = Kitten() # cool!
ctx["items"] = ["Oopa", "Loopa", "Woopa"] # very nice!
```
--------------------------------
### Shutdown the wrapper
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Stops the wrapper programmatically. The wrapper also installs signal handlers and performs safe cancellation of pending tasks during shutdown.
```python
loop_wrapper.shutdown()
```
--------------------------------
### Resolve and download a photo file
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/10_media
Resolve a photo's file_id, get the Telegram file path, and download the bytes to a local directory. Uses a custom ABCRule to detect photos and returns a confirmation string.
```python
import pathlib
from telegrinder import Message
from telegrinder.rules import ABCRule
photos_path = pathlib.Path("photos")
photos_path.mkdir(exist_ok=True)
class HasPhoto(ABCRule):
async def check(self, message: Message) -> bool:
return bool(message.photo.unwrap_or_none())
@bot.on.message(HasPhoto())
async def downloader(message: Message) -> str:
photo_file = (await message.api.get_file(file_id=message.photo.unwrap()[-1].file_id)).unwrap()
photo_path = photo_file.file_path.unwrap()
path = photos_path / pathlib.Path(photo_path.split("/")[-1])
path.write_bytes((await message.api.download_file(photo_path)).unwrap())
return "Photo downloaded!"
```
--------------------------------
### Unwrap result and use expect for detailed errors
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Shows using .unwrap() to get the message_id directly, and .expect() to raise a custom error message on failure.
```python
message_id = (
await message.api.send_message(
chat_id=message.chat_id,
text="Hi!",
)
).unwrap().message_id
# Even if we want to break the control flow, it might be much better to directly specify why the error happened (either with custom exception class or just a simple detail information). To transform the raising exception into something more detailed, we use expect
message_id = (
await message.api.send_message(...)
).expect("Could not send a message").message_id
```
--------------------------------
### Import HTML and formatting helpers
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Import the HTML entrypoint and core formatting helpers from telegrinder.tools.formatting.
```python
from telegrinder.tools.formatting import HTML, bold, italic, mention
```
--------------------------------
### IncomingMessageId node and handler
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/5_nodes
Defines a scalar node that extracts the message id and wraps it in a custom MessageId type. The handler uses this node to get the message id and respond with it. This example shows a practical use of nodes to remove duplication.
```python
from nodnod.interface.scalar import scalar_node
from telegrinder import Message
MessageId = type("MessageId", (int,), {})
@scalar_node
class IncomingMessageId:
@classmethod
def __compose__(cls, message: Message) -> MessageId:
# Tiny wrapper types can make code more expressive.
return MessageId(message.message_id)
@bot.on.message()
async def show_id(message: Message, message_id: IncomingMessageId) -> None:
await message.answer(f"Your message id is {message_id}")
```
--------------------------------
### Initialize Telegrinder bot with API and Token
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/1_setting_up
Creates the core Telegrinder instance by importing Telegrinder, API, and Token, then instantiating API with a token and passing it to Telegrinder. The token is wrapped in a Token instance for strong typing and security.
```python
from telegrinder import Telegrinder, API, Token
api = API(Token("your-token-here"))
bot = Telegrinder(api)
```
--------------------------------
### Direct Construction
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Shows how to directly instantiate a Checkbox scenario and the important detail about the WaiterMachine.
```APIDOC
## Direct Construction
### Description
Instantiates a Checkbox scenario directly without using the dispatch shortcut.
### Example
```python
from telegrinder import Checkbox
checkbox = Checkbox(
chat_id=message.chat_id,
message="Pick your options",
waiter_machine=bot.dispatch.callback_query.waiter_machine,
)
```
### Usage Notes
- After construction, call `.add_option(...)` and `.wait(api)` the same way as with the dispatch shortcut.
- The `Checkbox` requires a `WaiterMachine`. The dispatch shortcut provides the correct one automatically, which is why it is the recommended path.
```
--------------------------------
### Get context variable with type
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Retrieve a context variable with a specified type using get. If the type does not match, an AssertionError is raised. The unwrap() method returns the underlying value.
```python
from telegrinder.tools.global_context import GlobalContext, CtxVar
ctx = GlobalContext(name="Alex", URL=CtxVar("https://google.com", const=True))
URL = ctx.get("URL", int) # AssertionError: "Context variable value type of 'str' does not correspond to the expected type 'int'."
URL = ctx.get("URL", str).unwrap() #
```
--------------------------------
### Create a regular keyboard with Button and Keyboard
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Builds a keyboard with two rows: buttons '1' and '2' on the first row, and '3' on the second row. Use this to create a simple keyboard for a bot.
```python
from telegrinder.tools import Button, Keyboard
keyboard = (
Keyboard()
.add(Button("1"))
.add(Button("2"))
.row()
.add(Button("3"))
)
```
--------------------------------
### Initialize API with Token
Source: https://telegrinder.readthedocs.io/en/latest/api
Creates an API instance using a Token object. Required before making any Telegram Bot API requests.
```python
from telegrinder import API, Token
api = API(Token("token"))
```
--------------------------------
### Send a local file with InputFile
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/10_media
Build an InputFile from bytes read from a local file and send it as a photo with a caption. Works when the file already exists on disk or in memory.
```python
import pathlib
from telegrinder import Message
from telegrinder.types import InputFile
cool_bytes = pathlib.Path("examples/assets/satie.jpeg").read_bytes()
@bot.on.message(Text("/photo"))
async def start(message: Message) -> None:
await message.answer_photo(
InputFile("satie.jpeg", cool_bytes),
caption="Erik",
)
```
--------------------------------
### Direct Checkbox construction with WaiterMachine
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Directly instantiate a Checkbox scenario without using bot.dispatch.checkbox(...). Requires a WaiterMachine; the dispatch shortcut provides the correct one, so direct construction is only recommended when you need that control. After construction, call .add_option(...) and .wait(api) the same way.
```python
from telegrinder import Checkbox
checkbox = Checkbox(
chat_id=message.chat_id,
message="Pick your options",
waiter_machine=bot.dispatch.callback_query.waiter_machine,
)
```
--------------------------------
### Using bot.loop_wrapper in a bot
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows the typical way to interact with the wrapper inside a bot: adding tasks, registering startup hooks, and scheduling timers via bot.loop_wrapper.
```python
bot.loop_wrapper.add_task(...)
bot.loop_wrapper.lifespan.on_startup(...)
bot.loop_wrapper.timer(...)
```
--------------------------------
### Initialize API
Source: https://telegrinder.readthedocs.io/en/latest/api
Initializes an API instance with a token, which is required for making Telegram Bot API requests.
```APIDOC
## Initialize API
### Description
Creates an API instance with a token for making Telegram Bot API requests.
### Initialization
```python
from telegrinder import API, Token
api = API(Token("token"))
```
```
--------------------------------
### Create a minimal dispatch with handlers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Creates a local dispatch named "chat-utilities" with two handlers: one for messages from bots and one for the /repeat command with an optional times argument. The dispatch can live in its own file and be plugged into the main bot later.
```python
from telegrinder import Dispatch, Message
from telegrinder.rules import Argument, Command, IsBot
dp = Dispatch(name="chat-utilities")
@dp.message(IsBot())
async def bot_message_handler(message: Message) -> None:
# A normal handler, just registered in a local dispatch instead of bot.on.
await message.answer("Hey bot!")
@dp.message(
Command(
"repeat",
Argument("text"),
Argument("times", validators=[lambda s: int(s) if s.isdigit() else None], optional=True),
)
)
async def command_handler(message: Message, text: str, times: int = 5) -> None:
# times will contain the parsed number or the default value.
await message.answer(", ".join([text] * times))
```
--------------------------------
### Set context variables in __init__
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Initialize a GlobalContext with context variables using keyword arguments. The 'url' variable is a constant CtxVar.
```python
from telegrinder.tools.global_context import GlobalContext, CtxVar
ctx = GlobalContext(name="Alex", url=CtxVar("https://google.com", const=True))
```
--------------------------------
### Import GlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Import the GlobalContext class from the telegrinder.tools.global_context module.
```python
from telegrinder.tools.global_context import GlobalContext
```
--------------------------------
### Get context variable value
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Retrieve context variable values using attribute access, item access, or get_value with type checking. Accessing a missing variable raises NameError or UnwrapError.
```python
from telegrinder.tools.global_context import GlobalContext, CtxVar
ctx = GlobalContext(arseny="cool programmer")
ctx.arseny # 'cool programmer' (unknown type)
ctx["arseny"] # 'cool programmer' (unknown type)
ctx.get_value("arseny", str).unwrap() # 'cool programmer' (type 'str')
ctx.lul # NameError: Name 'lul' is not defined in ''.
ctx.get_value("wupi woop").unwrap() # UnwrapError: Name 'wupi woop' is not defined in ''.
```
--------------------------------
### Bot startup and shutdown hooks
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Registers startup and shutdown hooks on the bot's loop_wrapper. The startup hook sends a message, the shutdown hook prints a farewell.
```python
@bot.loop_wrapper.lifespan.on_startup
async def hello() -> None:
await bot.api.send_message(chat_id=SECRET_CHAT_ID, text="Hello!!!")
@bot.loop_wrapper.lifespan.on_shutdown
async def bye() -> None:
print("Bye!!!")
```
--------------------------------
### Import TelegrinderContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Import the built-in type-hinted global context TelegrinderContext.
```python
from telegrinder.tools.global_context import TelegrinderContext
```
--------------------------------
### Use formatting helpers
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Shows usage of several formatting helpers: blockquote, pre_code with language, mention, link, tg_emoji, and code_inline.
```python
from telegrinder.tools.formatting import (
HTML,
blockquote,
bold,
code_inline,
link,
mention,
pre_code,
tg_emoji,
)
text = HTML << blockquote("Quoted text")
code = pre_code("print('hello')", lang="python")
user = mention("arseny", user_id=549019276)
repo = link("https://github.com/timoniq/telegrinder", text="telegrinder")
emoji = tg_emoji("👍", emoji_id=5368324170671202286)
inline = code_inline("TOKEN")
```
--------------------------------
### Use .request method
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a raw request to the 'getMe' method with empty parameters, unwraps the result, and accesses the 'first_name' field.
```python
result = await api.request("getMe", {})
result.unwrap()["first_name"]
```
--------------------------------
### Load a dispatch with bot.on.load
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads an external dispatch (e.g., chat_utilities.dp) into the bot's main dispatch. This is the standard way to include routers from other files.
```python
bot.on.load(chat_utilities.dp)
bot.run_forever()
```
--------------------------------
### Access API instance from bot or event
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Shows how to access the API instance from the bot or the event object.
```python
bot.api
message.api
```
--------------------------------
### Use typed methods
Source: https://telegrinder.readthedocs.io/en/latest/api
Demonstrates using a typed method like get_me that returns a result model or APIError, and how to unwrap and access data.
```APIDOC
## Use typed methods
### Description
All methods are in snake case and return a result of model or APIError. Example shows how to call get_me and access the result.
### Example
```python
user = (await api.get_me()).unwrap()
user.first_name
```
```
--------------------------------
### Create a code block with pre_code
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Creates a proper code block using pre_code with a language hint. Gives a real block of code instead of a plain string that happens to contain indentation.
```python
from telegrinder.tools.formatting import pre_code
snippet = pre_code(
"print('Hello from telegrinder')",
lang="python",
)
```
--------------------------------
### Compose HTML with helpers
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Build a Telegram-ready HTML string by chaining the HTML object with the << operator and helper functions like mention and bold.
```python
from telegrinder.tools.formatting import HTML, bold, italic, mention
text = HTML << "Hello, " << mention("Arseny", user_id=549019276) << "! " << bold(italic("Welcome"))
```
--------------------------------
### Checkbox Constructor
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Documentation for the Checkbox constructor, including its parameters and usage notes.
```APIDOC
## Checkbox Constructor
### Description
Creates a new Checkbox scenario instance for collecting multiple options from a user in a Telegram chat.
### Constructor Signature
```python
Checkbox(
chat_id: int,
message: str,
*,
parse_mode: str | None = None,
max_in_row: int = 3,
callback_answer: str | None = None,
callback_answer_as_alert: bool | None = None,
ready_text: str = "Ready",
ready_icon_id: str | int | None = None,
ready_style: KeyboardButtonStyle | None = None,
cancel_text: str | None = None,
cancel_icon_id: str | int | None = None,
cancel_style: KeyboardButtonStyle | None = None,
waiter_machine: WaiterMachine | None = None,
)
```
### Parameters
- **chat_id** (int) - Required - The chat identifier where the checkbox will be sent.
- **message** (str) - Required - The text displayed above the checkbox.
- **parse_mode** (str | None) - Optional - Formatting mode for the message text.
- **max_in_row** (int) - Optional - Maximum number of options per row. Defaults to 3.
- **callback_answer** (str | None) - Optional - Text for the callback answer.
- **callback_answer_as_alert** (bool | None) - Optional - Whether the callback answer is shown as an alert.
- **ready_text** (str) - Optional - Text for the submit button. Defaults to "Ready".
- **ready_icon_id** (str | int | None) - Optional - Icon identifier for the ready button.
- **ready_style** (KeyboardButtonStyle | None) - Optional - Style for the ready button.
- **cancel_text** (str | None) - Optional - Text for the cancel button.
- **cancel_icon_id** (str | int | None) - Optional - Icon identifier for the cancel button.
- **cancel_style** (KeyboardButtonStyle | None) - Optional - Style for the cancel button.
- **waiter_machine** (WaiterMachine | None) - Optional - The waiter machine used for waiting for user interaction.
### Usage Notes
- The `message` parameter is used for the text above the checkbox.
- `max_in_row` controls the layout of options.
- `ready_text` customizes the submit button.
- `cancel_text` adds a cancel button.
- `parse_mode` is used if the scenario message uses formatting.
- Checkbox buttons use regular `InlineButton` under the hood, so style-aware buttons are supported through scenario parameters and option styles.
- You can visually distinguish the ready action, cancel action, and individual options via `KeyboardButtonStyle` values.
```
--------------------------------
### Import serializers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Imports the two main serializers: JSONSerializer and MsgPackSerializer.
```python
from telegrinder.tools import JSONSerializer, MsgPackSerializer
```
--------------------------------
### Import ABCGlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Import the ABCGlobalContext abstract interface from the telegrinder.tools.global_context module.
```python
from telegrinder.tools.global_context import ABCGlobalContext
```
--------------------------------
### Send a static keyboard with get_markup
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Sends a message with the static keyboard attached. The handler triggers on the /eat command.
```python
@bot.on.message(Text("/eat"))
async def eat(message: Message) -> None:
await message.answer(
"What do you want to eat?",
reply_markup=FruitsKeyboard.get_markup(),
)
```
--------------------------------
### Define MainMenuKeyboard with inline buttons
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines a static inline keyboard class with two buttons: 'Show fact' and 'Exit', using PrimaryInlineButton and DangerInlineButton. The 'new_row=True' parameter places the exit button on a new row.
```python
class MainMenuKeyboard(InlineKeyboard):
show_fact = PrimaryInlineButton("Show fact", callback_data="menu/fact", new_row=True)
exit = DangerInlineButton("Exit", callback_data="menu/exit")
```
--------------------------------
### Handler for mini player with state matching
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
A handler that uses pattern matching on the current state to perform actions. It handles play, pause, and stop commands, returning appropriate messages.
```python
@bot.on.message(Command("play", Argument("song_name", optional=True)))
async def play_song_handler(state: Stopped | Paused | Playing, song_name: str | None = None) -> str:
match state:
case Stopped():
if song_name is None:
return "You need to provide a song name"
await state.play(song_name)
return f"Started {song_name}"
case Paused():
await state.play()
return f"Continuing {state.song}"
case Playing():
return "Already playing"
```
--------------------------------
### Basic error handling with event_error
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/11_handling_errors
Shows how to register a regular handler that raises an exception and an event_error handler that catches it. The error handler receives an Error node with the exception and can reply to the user.
```python
from telegrinder import Message
from telegrinder.node import Error
from telegrinder.rules import IsUser, Text
@bot.on.message(Text("oops"))
async def oops_handler(message: Message) -> None:
await message.answer("Oh no")
raise RuntimeError("Wow")
@bot.on.message(Text("woops"))
def woops_handler() -> None:
raise ValueError("Wow oopsii!")
@bot.on.event_error(IsUser())
async def error_handler(err: Error[RuntimeError, ValueError], message: Message) -> None:
await message.answer(f"Something went wrong: {err.exception}")
```
--------------------------------
### Handle media groups with MediaGroup node
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/10_media
Handle a media group with the dedicated media_group view and MediaGroup node. Uses Option[Caption] to access an optional caption and returns a summary string.
```python
from kungfu.library.monad.option import Option
from telegrinder.node import Caption, MediaGroup
@bot.on.media_group()
async def handle_media_group(media_group: MediaGroup, caption: Option[Caption]) -> str:
return (
f"Received media group with {len(media_group.items)} items\n"
f"Caption: {caption.unwrap_or('no caption')}"
)
```
--------------------------------
### Auto-load dispatches from a directory
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads all dispatches from a directory recursively. Telegrinder imports Python files and looks for global Dispatch instances.
```python
bot.on.load_from_dir("handlers", recursive=True)
```
--------------------------------
### Template formatting with HTML
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Use template-based formatting with Python template strings and format specifiers like bold.
```python
from telegrinder.tools.formatting import HTML
text = HTML(t"Hello, {name:bold}!")
```
--------------------------------
### Import ParseMode constants
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Import ParseMode constants explicitly from telegrinder.tools.parse_mode.
```python
from telegrinder.tools.parse_mode import ParseMode
```
--------------------------------
### Build formatted text with link, spoiler, code_inline, and bold
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Builds a formatted text string using HTML helpers: link, spoiler, code_inline, and bold. Shows how to combine multiple helpers with the HTML builder. Easier to read than hand-written HTML strings.
```python
from telegrinder.tools.formatting import HTML, bold, code_inline, link, spoiler
text = (
HTML
<< "Documentation: "
<< link("https://docs.python.org/3/", text="Python docs")
<< "\n"
<< "Token: "
<< spoiler(code_inline("123:secret-token"))
<< "\n"
<< bold("Do not share this with anyone.")
)
```
--------------------------------
### Handle a regular button press with Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles a button press by matching the button text '1' with the Text rule. This is the simplest way to handle regular keyboard buttons.
```python
@bot.on.message(Text("1"))
async def handle_press_button(message: Message) -> None:
await message.answer("You pressed button 1")
```
--------------------------------
### Basic LoopWrapper usage
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Creates a standalone LoopWrapper, schedules a coroutine with add_task(), and runs it. This is the basic usage when not using Telegrinder's built-in wrapper.
```python
from telegrinder.tools import LoopWrapper
loop_wrapper = LoopWrapper()
async def main() -> None:
await open_db_connection()
await close_db_connection()
loop_wrapper.add_task(main())
loop_wrapper.run()
```
--------------------------------
### Import the Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/2_rules
Imports the prebuilt Text rule, which accepts a string or a list of strings to match against the message text.
```python
from telegrinder.rules import Text
```
--------------------------------
### Register a message handler with @bot.on.message
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Registers a handler for messages matching the Text('/start') rule. This is the typical way to add a command handler.
```python
@bot.on.message(Text("/start"))
async def start(...): ...
```
--------------------------------
### Inline Keyboard with Callback Data
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Creates an inline keyboard with three buttons, each having a callback_data payload. Uses .row() to place the third button on a new line.
```python
from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
inline_keyboard = (
InlineKeyboard()
.add(InlineButton("1", callback_data="button/1"))
.add(InlineButton("2", callback_data="button/2"))
.row()
.add(InlineButton("3", callback_data="button/3"))
)
```
--------------------------------
### InlineKeyboard.add with InlineButton
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Creates an inline keyboard with a button that carries a structured payload model as callback_data.
```APIDOC
## InlineKeyboard.add with InlineButton
### Description
Creates an inline keyboard with a button that carries a structured payload model as callback_data.
### Method
N/A (Object construction)
### Endpoint
N/A
### Parameters
#### Constructor Parameters
- **keyboard** (InlineKeyboard) - Required - The keyboard instance to which the button is added.
- **button** (InlineButton) - Required - The button to add, containing text and callback_data.
### Request Example
```python
import msgspec
from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
class ItemModel(msgspec.Struct):
item: str
amount: int
action: str
keyboard = (
InlineKeyboard()
.add(
InlineButton(
"Buy doughnut",
callback_data=ItemModel(item="doughnut", amount=100, action="buy"),
)
)
)
```
### Response
#### Success Response (200)
- **keyboard** (InlineKeyboard) - The keyboard with the button added.
#### Response Example
```python
keyboard
```
ERROR HANDLING:
- No explicit error handling is documented.
```
--------------------------------
### Create a Router for admin area
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Creates a Router named 'admin' with a handler for /ban, then wraps it in a Dispatch. This isolates admin logic.
```python
from telegrinder import Dispatch, Message, Router
from telegrinder.rules import Text
admin_router = Router(name="admin")
@admin_router.message(Text("/ban"))
async def ban_handler(message: Message) -> None:
# All admin-related logic can stay inside this router.
await message.answer("Admin action")
admin = Dispatch(router=admin_router, name="admin-dispatch")
```
--------------------------------
### InlineButton with Callback Data String
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Shows the minimal creation of an inline button with a callback_data string, which is then matched by PayloadEqRule.
```python
InlineButton("1", callback_data="button/1")
```
--------------------------------
### Create unnamed GlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Create an unnamed GlobalContext and compare it with a named one. The assertion passes because the contexts are different.
```python
from telegrinder.tools.global_context import GlobalContext
ctx = GlobalContext() # unnamed
assert ctx != GlobalContext("context")
```
--------------------------------
### Styled Keyboard with Success, Primary, Danger Buttons
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines a keyboard class with three styled buttons: SuccessButton for confirmation, PrimaryButton for main action, and DangerButton for cancel/delete/exit. Uses max_in_row=2 to control layout.
```python
from telegrinder import DangerButton, Keyboard, PrimaryButton, SuccessButton
class MenuKeyboard(Keyboard, max_in_row=2):
PROFILE = SuccessButton("Profile")
BALANCE = PrimaryButton("Balance")
EXIT = DangerButton("Exit")
```
--------------------------------
### Handle a static keyboard button press with FruitsKeyboard.APPLE
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles a button press by using the button itself as a rule. This pattern is convenient for menus.
```python
@bot.on.message(FruitsKeyboard.APPLE)
async def eat_apple(message: Message) -> None:
await message.answer(
"Good choice",
reply_markup=FruitsKeyboard.get_keyboard_remove(),
)
```
--------------------------------
### State checking rules
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Shows how to check states using the states.State() rule with StateEnum and StateMeta values. These rules are used in message handlers to filter by current state.
```python
states.State(StateEnum.CURSED)
states.State(StateMeta.NO_STATE)
states.State(StateMeta.ANY)
```
--------------------------------
### PayloadEqRule for Simple Payload Matching
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Uses PayloadEqRule to match a callback query with exact callback_data 'button/1' and respond with a confirmation message.
```python
from telegrinder import CallbackQuery
from telegrinder.rules import PayloadEqRule
@bot.on.callback_query(PayloadEqRule("button/1"))
async def handle_press_inline_button(cb: CallbackQuery) -> None:
await cb.answer("Button 1 pressed")
```
--------------------------------
### Use .request_raw method
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a raw request to the 'getMe' method, unwraps the result, and parses it as JSON to access the 'first_name' field.
```python
result = await api.request_raw("getMe", {})
json.loads(result.unwrap())["first_name"]
```
--------------------------------
### Register a message handler with bot.on.message
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows the basic syntax for registering a message handler on the bot's main dispatch. `bot.on` is the dispatch and `bot.on.message` is the message view of the main router.
```python
@bot.on.message(...)
async def handler(...): ...
```
--------------------------------
### Checkbox constructor parameters
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Constructor signature for the Checkbox scenario. The most commonly used arguments are message for the text above the checkbox, max_in_row for layout, ready_text to customize the submit button, cancel_text to add a cancel button, and parse_mode if the scenario message uses formatting.
```python
Checkbox(
chat_id: int,
message: str,
*,
parse_mode: str | None = None,
max_in_row: int = 3,
callback_answer: str | None = None,
callback_answer_as_alert: bool | None = None,
ready_text: str = "Ready",
ready_icon_id: str | int | None = None,
ready_style: KeyboardButtonStyle | None = None,
cancel_text: str | None = None,
cancel_icon_id: str | int | None = None,
cancel_style: KeyboardButtonStyle | None = None,
waiter_machine: WaiterMachine | None = None,
)
```
--------------------------------
### Define a static keyboard class with max_in_row and one_time_keyboard
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines a reusable keyboard class with two buttons per row and a one-time keyboard. This is useful for keyboards that are reused often.
```python
from telegrinder.tools.keyboard import Button, Keyboard
class FruitsKeyboard(Keyboard, max_in_row=2, one_time_keyboard=True):
APPLE = Button("Apple")
BANANA = Button("Banana")
KIWI = Button("Kiwi")
```
--------------------------------
### Send a regular keyboard with reply_markup
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Sends a message with the keyboard attached as reply_markup. The handler triggers on the /keyboard command.
```python
@bot.on.message(Text("/keyboard"))
async def handle_keyboard_command(message: Message) -> None:
await message.answer(
"Here is your keyboard",
reply_markup=keyboard.get_markup(),
)
```
--------------------------------
### state_mutator Alive/Dead state machine model
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Defines a state machine with AliveState and DeadState classes using @dataclasses and @mutation decorators. Shows how to store data in state, define transitions with return type annotations, and use a property for description. Requires telegrinder.tools.state_mutator imports.
```python
import dataclasses
import datetime
from telegrinder.tools.state_mutator import State, StateMutator, mutation
@dataclasses.dataclass
class AliveState(State):
__description__ = "alive"
# State can store useful data directly.
since: datetime.datetime = dataclasses.field(default_factory=datetime.datetime.now)
@mutation
def die(self, reason: str) -> "DeadState":
# Alive -> Dead transition
return DeadState(reason)
@dataclasses.dataclass
class DeadState(State):
reason: str
@mutation
def resurrect(self) -> "AliveState":
# Dead -> Alive transition
return AliveState()
@property
def __description__(self) -> str:
return f"dead because of {self.reason}"
```
--------------------------------
### Translate by template with parameters
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Translates a template string with parameters using the injected BaseTranslator. Convenient for quick phrases with parameters.
```python
return _("Hello, {name}!", name=user.full_name)
```
--------------------------------
### State classes for mini player
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Defines the state classes for a mini music player: Stopped, Playing, and Paused. Each state has mutation methods that return the next state, using the @mutation decorator.
```python
import datetime
from dataclasses import dataclass
from telegrinder.tools.state_mutator import State, mutation
@dataclass
class Stopped(State):
@mutation
def play(self, song: str, offset: datetime.timedelta = datetime.timedelta(0)) -> "Playing":
return Playing(song, offset, datetime.datetime.now())
@dataclass
class Playing(State):
song: str
offset: datetime.timedelta
started_at: datetime.datetime
@mutation
def stop(self) -> "Stopped":
return Stopped()
@mutation
def pause(self) -> "Paused":
offset = datetime.datetime.now() - self.started_at
return Paused(song=self.song, offset=offset + self.offset, stopped_at=datetime.datetime.now())
@dataclass
class Paused(State):
song: str
offset: datetime.timedelta
stopped_at: datetime.datetime
@mutation
def stop(self) -> "Stopped":
return Stopped()
@mutation
def play(self) -> "Playing":
return Playing(self.song, self.offset, datetime.datetime.now())
```
--------------------------------
### Combine custom and Text rules
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/2_rules
Handler that responds to '/hey' only from the user with ID 123. Multiple rules in the decorator are all required. Returning a string from the handler automatically sends it as an answer.
```python
MY_ID = 123
@bot.on.message(IsMessageFromUserId(MY_ID), Text("/hey"))
async def hey_handler():
return "Hey hey!"
```
--------------------------------
### Checkbox multi-select flow with bot.dispatch.checkbox
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Handles the /checkbox command to present a multi-select checkbox interface. Uses bot.dispatch.checkbox with options, cancel_text, and max_in_row, then waits for user interaction and edits the message with picked items. Requires a Message object and message.api for waiting.
```python
@bot.on.message(Text("/checkbox"))
async def action(message: Message) -> None:
picked, message_id = await (
bot.dispatch.checkbox(
message.chat_id,
message="Check your checkbox",
cancel_text="Cancel",
max_in_row=2,
)
.add_option("apple", "Apple", "Apple 🍏")
.add_option("banana", "Banana", "Banana 🍌", is_picked=True)
.add_option("pear", "Pear", "Pear 🍐")
.wait(message.api)
)
await message.edit(
text="You picked: {}".format(", ".join(key for key, value in picked.items() if value)),
chat_id=message.chat.id,
message_id=message_id,
)
```
--------------------------------
### Load the admin dispatch
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads the admin dispatch into the bot, making its handlers active.
```python
bot.on.load(admin)
```
--------------------------------
### Access the main router's message view
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows how to access the main router's message view directly, which is equivalent to using @bot.on.message.
```python
bot.on.main_router.message
```
--------------------------------
### Run the wrapper manually
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Runs the wrapper manually when used outside Telegrinder. By default it closes the loop after shutdown.
```python
loop_wrapper.run()
```
--------------------------------
### Define a model for callback data
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines an msgspec.Struct model for callback data and creates an inline keyboard with a button that uses this model as callback_data.
```python
import msgspec
from telegrinder.tools.keyboard import InlineButton, InlineKeyboard
class ItemModel(msgspec.Struct):
item: str
amount: int
action: str
keyboard = (
InlineKeyboard()
.add(
InlineButton(
"Buy doughnut",
callback_data=ItemModel(item="doughnut", amount=100, action="buy"),
)
)
)
```
--------------------------------
### Configure BaseTranslator and use translation handlers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Configures the BaseTranslator with an I18NConfig pointing to the translation directory and sets a key separator. Defines two message handlers that inject BaseTranslator and translate by key or with template parameters.
```python
BaseTranslator.configure(I18NConfig(domain="messages", folder="examples/assets/i18n"))
KeySeparator.set(" ")
@bot.on.message(Text("hi"))
async def hi(_: BaseTranslator) -> str:
return _.hi()
@bot.on.message(Text("hello"))
async def hello(_: BaseTranslator, user: UserSource) -> str:
return _("Hello, {name}!", name=user.full_name)
```
--------------------------------
### Delayed and periodic tasks with timer and interval
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows the timer and interval decorators for delayed and periodic tasks. timer runs once after the delay; interval re-schedules after each run. Delayed tasks expose .cancel().
```python
@bot.loop_wrapper.timer(minutes=1)
async def once_yum() -> None:
print("yum-yum!")
@bot.loop_wrapper.interval(seconds=10)
async def repeat_yum() -> None:
print("repeat yum-yum!!!")
```
--------------------------------
### Load multiple dispatches with load_many
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads several dispatches at once, useful when you've already imported the modules.
```python
bot.on.load_many(users.dp, payments.dp, admin.dp)
```
--------------------------------
### Send message using low-level API
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Demonstrates calling send_message directly via the API instance, requiring explicit chat_id.
```python
async def message_handler(message: Message):
await message.api.send_message(
chat_id=message.chat_id,
text="Hi!",
)
```
--------------------------------
### Send message using message.answer shortcut
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Shows the high-level shortcut for sending a message, which internally calls the send_message API method.
```python
async def message_handler(message: Message):
await message.answer("Hi!")
```
--------------------------------
### Handle callback query for show_fact button
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Registers a callback query handler for the 'show_fact' button. When triggered, it answers with 'Interesting fact'.
```python
@bot.on.callback_query(MainMenuKeyboard.show_fact)
async def show_fact(cb: CallbackQuery) -> None:
await cb.answer("Interesting fact")
```
--------------------------------
### Run wrapper without closing loop
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Runs the wrapper while keeping the event loop open after shutdown by passing close_loop=False.
```python
loop_wrapper.run(close_loop=False)
```
--------------------------------
### Translate by key with BaseTranslator
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Translates by key using the injected BaseTranslator instance. Useful when translations are stored in a structured way.
```python
return _.im.fine()
```