### Helper examples
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Shows usage of core helpers: blockquote, pre_code with lang, 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")
```
--------------------------------
### Minimal multi-file bot project layout
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
A minimal starting point for a multi-file bot project layout. Scales more calmly than a single large file.
```text
mybot/
bot.py
handlers/
start.py
admin.py
payments.py
keyboards/
menu.py
nodes/
db.py
rules/
is_admin.py
```
--------------------------------
### Formatting handler example
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Example handler that replies with a formatted mention using the user's first name and ID, sending with HTML.PARSE_MODE.
```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,
)
```
--------------------------------
### Minimal handler module with Dispatch
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Minimal example of a handler module that defines a Dispatch instance for blueprint-style project layouts.
```python
# handlers/start.py
from telegrinder import Dispatch
dp = Dispatch(name="start")
```
--------------------------------
### Build a regular keyboard with Button and Keyboard
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Creates a keyboard with three buttons, placing '1' and '2' on the first row and '3' on a new row. Use .add() to add buttons and .row() to start a new row.
```python
from telegrinder.tools import Button, Keyboard
keyboard = (
Keyboard()
.add(Button("1"))
.add(Button("2"))
.row()
.add(Button("3"))
)
```
--------------------------------
### MemoryStateStorage example with StateEnum
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Demonstrates setting and checking user states with MemoryStateStorage. Use this to remember a user's current mode or step in a flow. The state rule allows /curse only if user is blessed or has no state, and /bless only if no state exists. StateData contains the key and optional payload; the get() returns an optional that can be unwrapped with a default.
```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}.")
```
--------------------------------
### Load external dispatch and run bot
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads an external dispatch and starts the bot. This is the final step in assembling a bot from multiple dispatch modules.
```python
bot.on.load(chat_utilities.dp)
bot.run_forever()
```
--------------------------------
### 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 (handlers/chat_utilities.py) into the main bot. The example is truncated in the source; it only shows the imports and the creation of the API and bot instances.
```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
Shows raw HTML string formatting. This approach is error-prone because user input is not escaped and closing tags are easy to miss.
```python
text = "Hello world"
```
--------------------------------
### Adding options to Checkbox
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Shows how to add options with keys, display texts, and initial picked state. The example uses a chat_id and message text, and sets is_picked for the banana option.
```python
checkbox = (
bot.dispatch.checkbox(chat_id, message="Choose toppings")
.add_option("cheese", "Cheese", "Cheese ✅")
.add_option("bacon", "Bacon", "Bacon ✅", is_picked=True)
)
```
--------------------------------
### Set, get, and delete state
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Shows the three core state operations: set, get, and delete. Use with a state storage instance (e.g., MemoryStateStorage) to persist per-user data.
```python
await states.set(user_id, StateEnum.BLESSED, {"source": "admin"})
state = await states.get(user_id)
await states.delete(user_id)
```
--------------------------------
### Dispatch shortcut usage for Checkbox
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Complete example of using bot.dispatch.checkbox with options and editing the message with the picked items. Requires a bot token from environment and a /checkbox command handler.
```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()
```
--------------------------------
### Get context variable with type
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Get a context variable with a provided type. If the type does not match, an AssertionError is raised.
```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() #
```
--------------------------------
### Make request with .request_raw
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a request using .request_raw, which returns a result with msgspec.Raw data or APIError. The example calls getMe and parses the raw JSON to access first_name.
```python
result = await api.request_raw("getMe", {})
json.loads(result.unwrap())["first_name"]
```
--------------------------------
### Make request with .request
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a request using the .request method, which returns a result with parsed data (list, dict, or boolean) or APIError. The example calls getMe and accesses the first_name field.
```python
result = await api.request("getMe", {})
result.unwrap()["first_name"]
```
--------------------------------
### Shutdown LoopWrapper programmatically
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows calling shutdown() to stop the wrapper. The wrapper also installs signal handlers where possible and performs safe cancellation of pending tasks during shutdown.
```python
loop_wrapper.shutdown()
```
--------------------------------
### Unwrap or expect send_message result
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Shows two ways to break the control flow: using `.unwrap()` to get the message ID directly, and using `.expect("Could not send a message")` to provide a custom error message when the call fails.
```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
```
--------------------------------
### Using bot.loop_wrapper for tasks, startup, and timers
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows the typical usage inside a bot: adding tasks, registering startup hooks, and setting timers via bot.loop_wrapper.
```python
bot.loop_wrapper.add_task(...)
bot.loop_wrapper.lifespan.on_startup(...)
bot.loop_wrapper.timer(...)
```
--------------------------------
### Get context variable values
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Get context variable values using attribute access, item access, or get_value() with type. Missing names raise 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 ''.
```
--------------------------------
### Import HTML formatting toolkit
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Import the HTML entrypoint and helper functions from telegrinder.tools.formatting.
```python
from telegrinder.tools.formatting import HTML, bold, italic, mention
```
--------------------------------
### API Initialization
Source: https://telegrinder.readthedocs.io/en/latest/api
Initialization of the API instance with a token.
```APIDOC
## API Initialization
### Description
Creates an API instance to make Telegram Bot API requests.
### Method
Initialization
### Endpoint
N/A
### Parameters
- **token** (Token) - Required - The bot token.
### Request Example
```python
from telegrinder import API, Token
api = API(Token("token"))
```
```
--------------------------------
### Register startup and shutdown hooks with LoopWrapper
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Demonstrates registering startup and shutdown hooks using the lifespan decorators on a standalone LoopWrapper instance.
```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()
```
--------------------------------
### Send a static keyboard with FruitsKeyboard.get_markup()
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles the /eat command and sends a message with the FruitsKeyboard attached. Uses the class method get_markup() to generate the reply markup.
```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(),
)
```
--------------------------------
### Direct Checkbox instantiation
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Shows how to create a Checkbox instance directly without the dispatch shortcut. Requires a WaiterMachine; the dispatch shortcut provides the correct one automatically.
```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 Typed Methods
Source: https://telegrinder.readthedocs.io/en/latest/api
Demonstrates calling typed methods on the API instance.
```APIDOC
## Using Typed Methods
### Description
All methods are in snake case and return a result of model or APIError.
### Method
N/A
### Endpoint
N/A
### Parameters
- **method** (string) - Required - The method name in snake case.
### Request Example
```python
user = (await api.get_me()).unwrap()
user.first_name
```
```
--------------------------------
### Register startup and shutdown hooks in a bot
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows registering startup and shutdown hooks on the bot's loop_wrapper, sending a message on startup and printing on shutdown.
```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!!!")
```
--------------------------------
### Define a model and create an inline keyboard with callback_data
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Shows how to define a msgspec.Struct model and use it as callback_data for an InlineButton, then build an InlineKeyboard.
```APIDOC
## Define a model and create an inline keyboard with callback_data
### Description
Defines a msgspec.Struct model and uses it as callback_data for an InlineButton, then builds an InlineKeyboard.
### Method
N/A
### Endpoint
N/A
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
- **item** (str) - Required - The item name.
- **amount** (int) - Required - The item price.
- **action** (str) - Required - The action to perform.
### 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 constructed keyboard object.
#### Response Example
```python
keyboard = InlineKeyboard().add(InlineButton("Buy doughnut", callback_data=ItemModel(...)))
```
### Error Handling
No explicit error handling is documented.
```
--------------------------------
### Create code block with pre_code()
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Creates a proper code block with the pre_code helper. Use this when you need to display code to the user.
```python
from telegrinder.tools.formatting import pre_code
snippet = pre_code(
"print('Hello from telegrinder')",
lang="python",
)
```
--------------------------------
### Template formatting with HTML
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Template-based formatting using Python template strings. Format specifiers map to helper names like bold.
```python
from telegrinder.tools.formatting import HTML
text = HTML(t"Hello, {name:bold}!")
```
--------------------------------
### Register message handler with Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Registers a message handler for the /start command. This is equivalent to registering on bot.on.main_router.message.
```python
@bot.on.message(Text("/start"))
async def start(...): ...
```
--------------------------------
### Initialize API with Token
Source: https://telegrinder.readthedocs.io/en/latest/api
Initializes the telegrinder API instance with a token. The API instance is fully typed and implements all Telegram Bot API methods.
```python
from telegrinder import API, Token
api = API(Token("token"))
```
--------------------------------
### Compose HTML with helpers
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Build an HTML string by chaining text and helper outputs with the << operator. The result is an HTML string object.
```python
from telegrinder.tools.formatting import HTML, bold, italic, mention
text = HTML << "Hello, " << mention("Arseny", user_id=549019276) << "! " << bold(italic("Welcome"))
```
--------------------------------
### Basic LoopWrapper usage: create wrapper, add task, run
Source: https://telegrinder.readthedocs.io/en/latest/tools/loop_wrapper
Shows the minimal standalone usage of LoopWrapper: create an instance, add a coroutine as a task, and run the wrapper. In actual bot code you usually do not create and run a separate wrapper manually.
```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 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
```
--------------------------------
### Build inline keyboard with InlineButton and InlineKeyboard
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Builds an inline keyboard with three buttons, placing the third on a new row using .row(). Each button carries a callback_data payload.
```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"))
)
```
--------------------------------
### Initialize Telegrinder bot with API and Token
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/1_setting_up
Initializes the core Telegrinder bot instance. Requires a token from BotFather. The Token class is used for strong typing and security, API provides access to the Telegram API, and Telegrinder collects components to run the bot.
```python
from telegrinder import Telegrinder, API, Token
api = API(Token("your-token-here"))
bot = Telegrinder(api)
```
--------------------------------
### Create a minimal dispatch with Dispatch and Command
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 the bot itself (IsBot) and one for a /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))
```
--------------------------------
### Checkbox constructor signature
Source: https://telegrinder.readthedocs.io/en/latest/tools/checkbox
Shows the full constructor signature with all parameters and defaults. Use this when you need to know the exact parameter names and default values.
```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,
)
```
--------------------------------
### Run bot with bot.run_forever()
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/1_setting_up
Runs the bot asynchronously to receive updates in a loop. Must be added at the end of the file.
```python
bot.run_forever()
```
--------------------------------
### Create admin router and dispatch
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Creates a dedicated admin router and dispatch. Useful for separating a clear logical area like an admin zone.
```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")
```
--------------------------------
### Configure BaseTranslator and use in handlers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Configure BaseTranslator with I18NConfig specifying the domain and folder. Use KeySeparator to set the separator for nested keys. Inject BaseTranslator into handlers to translate by key or use a template with parameters.
```python
# Point telegrinder at your translation directory.
BaseTranslator.configure(I18NConfig(domain="messages", folder="examples/assets/i18n"))
# Separator for nested keys.
KeySeparator.set(" ")
@bot.on.message(Text("hi"))
async def hi(_: BaseTranslator) -> str:
# Translate by key.
return _.hi()
@bot.on.message(Text("hello"))
async def hello(_: BaseTranslator, user: UserSource) -> str:
# Or translate with parameters.
return _("Hello, {name}!", name=user.full_name)
```
--------------------------------
### Import serializers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Shows how to import the built-in payload serializers.
```APIDOC
## Import serializers
### Description
Imports the built-in payload serializers JSONSerializer and MsgPackSerializer.
### Method
N/A
### Endpoint
N/A
### Parameters
None
### Request Example
```python
from telegrinder.tools import JSONSerializer, MsgPackSerializer
```
### Response
#### Success Response (200)
- **JSONSerializer** (class) - The JSON serializer class.
- **MsgPackSerializer** (class) - The MessagePack serializer class.
#### Response Example
```python
from telegrinder.tools import JSONSerializer, MsgPackSerializer
```
### Error Handling
No explicit error handling is documented.
```
--------------------------------
### Send inline keyboard with get_markup()
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Sends the previously built inline keyboard as reply markup when the user sends /inline_keyboard. Uses the get_markup() method to obtain the keyboard markup.
```python
@bot.on.message(Text("/inline_keyboard"))
async def handle_inline_keyboard_command(message: Message) -> None:
await message.answer(
"Here is your inline keyboard",
reply_markup=inline_keyboard.get_markup(),
)
```
--------------------------------
### Import JSONSerializer and MsgPackSerializer
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Imports the two main payload serializers provided by Telegrinder: JSONSerializer and MsgPackSerializer.
```python
from telegrinder.tools import JSONSerializer, MsgPackSerializer
```
--------------------------------
### Date and time formatting
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Create a date/time entity. Accepts a datetime or Unix timestamp.
```python
import datetime
from telegrinder.tools.formatting import date_time
text = date_time("tomorrow", datetime.datetime.now() + datetime.timedelta(days=1))
```
--------------------------------
### Auto-load dispatches from directory
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Auto-loads dispatches from a directory. Telegrinder walks over Python files, imports them, looks for global Dispatch instances, and loads them into the main dispatch.
```python
bot.on.load_from_dir("handlers", recursive=True)
```
--------------------------------
### Import GlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Import the base GlobalContext class, which inherits from dict and stores values by name.
```python
from telegrinder.tools.global_context import GlobalContext
```
--------------------------------
### Load admin dispatch
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads the admin dispatch into the main bot, following the creation of the admin router and dispatch.
```python
bot.on.load(admin)
```
--------------------------------
### Define a static keyboard class with FruitsKeyboard
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines a reusable keyboard class with max_in_row=2 and one_time_keyboard=True. Buttons are class attributes and can be used as rules.
```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")
```
--------------------------------
### Handlers for music player with state matching
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Handlers that use pattern matching on the current state to handle play, pause, and stop commands. The state parameter type is a union of possible states.
```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"
```
--------------------------------
### Import ParseMode constants
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Import ParseMode for explicit parse mode constants like ParseMode.HTML and ParseMode.MARKDOWNV2.
```python
from telegrinder.tools.parse_mode import ParseMode
```
--------------------------------
### Send a local file with InputFile
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/10_media
Use this snippet to send a local image file as a photo with a caption. It reads the file bytes from disk and passes them to InputFile along with a filename.
```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",
)
```
--------------------------------
### Send a regular keyboard with get_markup()
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles the /keyboard command and sends a message with the keyboard attached. get_markup() converts the Telegrinder keyboard object into the Telegram API markup object.
```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 classes with mutations for music player
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Defines Stopped, Playing, and Paused state classes with mutations for play, pause, and stop transitions. Each mutation returns a new state object.
```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())
```
--------------------------------
### Set context variables in __init__
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Set context variables in __init__() by kwargs. CtxVar with const=True makes the variable constant.
```python
from telegrinder.tools.global_context import GlobalContext, CtxVar
ctx = GlobalContext(name="Alex", url=CtxVar("https://google.com", const=True))
```
--------------------------------
### Translation by template
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Returns a translation using a template with placeholders and parameters. This is convenient for quick phrases with parameters.
```python
return _("Hello, {name}!", name=user.full_name)
```
--------------------------------
### Use message shortcuts: answer, edit, delete
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Demonstrates the built-in shortcuts `answer`, `edit`, and `delete` on a message. The message object is obtained by unwrapping the result of `answer`, then edited and deleted after delays.
```python
m = (await message.answer("Happy birthday")).unwrap()
await asyncio.sleep(1)
await m.edit("Oops wrong chat")
await asyncio.sleep(1)
await m.delete()
```
--------------------------------
### Formatting with HTML helpers
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Shows how to compose HTML formatting helpers like bold, italic, mention, and date_time in a message handler. Use this when you need to build formatted text with multiple elements. The date_time helper renders a proper date/time entity.
```python
import datetime
from telegrinder import Message
from telegrinder.tools.formatting.html import HTML, bold, date_time, italic, mention
@bot.on.message(Text("/formatting"))
async def formatting(message: Message) -> None:
# Compose helpers like building blocks.
await message.answer(bold(italic("Bold and italic text")))
await message.answer(
HTML
<< "Hello, "
<< mention(message.from_user.first_name, user_id=message.from_user.id)
<< "!"
)
await message.answer(
# Telegram will render this as a proper date/time entity.
date_time("Tomorrow", datetime.datetime.now() + datetime.timedelta(days=1))
)
```
--------------------------------
### Create unnamed GlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Create an unnamed global context and compare it with a named one. The assertion shows they are not equal.
```python
from telegrinder.tools.global_context import GlobalContext
ctx = GlobalContext() # unnamed
assert ctx != GlobalContext("context")
```
--------------------------------
### Compose text with link, spoiler, code_inline, bold
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Builds a formatted text string using link, spoiler, code_inline, and bold helpers. This is 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 callback with PayloadEqRule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles a callback query when the payload equals "button/1" using PayloadEqRule. The callback answer sends a notification to the user.
```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")
```
--------------------------------
### Import Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/2_rules
Imports the prebuilt Text rule for filtering messages by text content.
```python
from telegrinder.rules import Text
```
--------------------------------
### Styled keyboard with DangerButton, PrimaryButton, SuccessButton
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Defines a keyboard with styled buttons: SuccessButton for confirmation, PrimaryButton for the main action, and DangerButton for cancel/delete/exit. The max_in_row=2 parameter limits buttons per row.
```python
from telegrinder import DangerButton, Keyboard, PrimaryButton, SuccessButton
class MenuKeyboard(Keyboard, max_in_row=2):
PROFILE = SuccessButton("Profile")
BALANCE = PrimaryButton("Balance")
EXIT = DangerButton("Exit")
```
--------------------------------
### Use HTML.PARSE_MODE for sending
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Pass HTML.PARSE_MODE as the parse_mode argument when sending the formatted HTML string.
```python
parse_mode=HTML.PARSE_MODE
```
--------------------------------
### Combine custom rule with Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/2_rules
Handler that responds to '/hey' only from the user with ID MY_ID. Multiple rules are ANDed. Returning a string is syntactic sugar for an answer.
```python
MY_ID = 123
@bot.on.message(IsMessageFromUserId(MY_ID), Text("/hey"))
async def hey_handler():
return "Hey hey!"
```
--------------------------------
### Import ABCGlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Import the abstract interface ABCGlobalContext for global context.
```python
from telegrinder.tools.global_context import ABCGlobalContext
```
--------------------------------
### Use a static keyboard button as a rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles a message that matches the APPLE button and replies with a confirmation, removing the keyboard with get_keyboard_remove(). The button acts both as a button and as a rule.
```python
@bot.on.message(FruitsKeyboard.APPLE)
async def eat_apple(message: Message) -> None:
await message.answer(
"Good choice",
reply_markup=FruitsKeyboard.get_keyboard_remove(),
)
```
--------------------------------
### Access the low-level API instance
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Access the low-level API instance from the bot or from an event, since the event is bound to the API it was received from.
```python
bot.api
```
```python
message.api
```
--------------------------------
### Pattern match on Result
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/3_functional_bits
Using match-case to handle Ok and Err states explicitly.
```python
match result:
case Ok(value):
print("Value is", value)
case Err(err):
print("Something is wrong:", err)
```
--------------------------------
### Load multiple dispatches with load_many
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Loads multiple dispatches at once. Useful when you have already imported the modules and want to combine them in one place.
```python
bot.on.load_many(users.dp, payments.dp, admin.dp)
```
--------------------------------
### Basic error handling with event_error
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/11_handling_errors
Shows how to define regular handlers that raise exceptions and an error handler registered with @bot.on.event_error(IsUser()) that receives an Error[RuntimeError, ValueError] node. The error handler can access the exception via err.exception and 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}")
```
--------------------------------
### Checkbox multi-select flow
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Handles a /checkbox command, showing a multi-select keyboard with options. Waits for user selection and edits the message with the picked items. Uses bot.dispatch.checkbox with options apple, banana, pear, and cancel_text.
```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,
)
```
--------------------------------
### GLOBAL nodes for configuration and secrets
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/5_nodes
Shows GLOBAL nodes for configuration and clients. Settings is a DataNode built once from environment variables, and Secret is a scalar node that generates a secret once per application.
```python
from telegrinder.node import DataNode, global_node, scalar_node
@global_node
class Settings(DataNode):
api_url: str
secret: str
@classmethod
def __compose__(cls) -> "Settings":
# No need to rebuild this on every update.
return cls(
api_url=env["API_URL"],
secret=env["APP_SECRET"],
)
@global_node
@scalar_node
class Secret:
@classmethod
def __compose__(cls) -> str:
return generate_secret(16)
```
--------------------------------
### Send a message using the answer shortcut
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Shows the high-level shortcut `answer` on a message, which internally calls the `send_message` API method.
```python
async def message_handler(message: Message):
await message.answer("Hi!")
```
--------------------------------
### Handle callback with PayloadMarkupRule template
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles callback queries with a template payload using PayloadMarkupRule. The placeholder captures an integer automatically, which is passed as the index parameter.
```python
from telegrinder.rules import PayloadMarkupRule
@bot.on.callback_query(PayloadMarkupRule("button/"))
async def handle_press_inline_button(cb: CallbackQuery, index: int) -> None:
# index arrives as an int automatically.
await cb.answer(f"Button {index} pressed")
```
--------------------------------
### Register a message handler with bot.on.message
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows the basic usage of bot.on.message to register a message handler. bot.on is the main dispatch and bot.on.message is the message view of the main router.
```python
@bot.on.message(...)
async def handler(...): ...
```
--------------------------------
### GlobalContext magic methods
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Use magic methods __iter__, __contains__, __eq__, and __bool__ on GlobalContext.
```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
```
--------------------------------
### Call send_message via the API instance
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/4_api
Calls `send_message` directly via the API instance, specifying the chat ID and text. This is the low-level equivalent of the `answer` shortcut.
```python
async def message_handler(message: Message):
await message.api.send_message(
chat_id=message.chat_id,
text="Hi!",
)
```
--------------------------------
### Set context variable values
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Set context variables using attribute assignment or item assignment.
```python
from telegrinder.tools.global_context import GlobalContext
ctx = GlobalContext()
ctx.kitten = Kitten() # cool!
ctx["items"] = ["Oopa", "Loopa", "Woopa"] # very nice!
```
--------------------------------
### .request
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a request to the Telegram Bot API and returns parsed data.
```APIDOC
## .request
### Description
Makes a request with `request_raw`, returns result with parsed data (list, dict or boolean) or APIError.
### Method
POST
### Endpoint
N/A
### Parameters
- **method** (string) - Required - The Telegram Bot API method name.
- **data** (dict) - Required - The request parameters.
### Request Example
```python
result = await api.request("getMe", {})
result.unwrap()["first_name"]
```
```
--------------------------------
### .request_raw
Source: https://telegrinder.readthedocs.io/en/latest/api
Makes a raw request to the Telegram Bot API and returns raw data.
```APIDOC
## .request_raw
### Description
Makes a request to telegram instance serving at `.request_url`. Returns result with `msgspec.Raw` data or `APIError`.
### Method
POST
### Endpoint
N/A
### Parameters
- **method** (string) - Required - The Telegram Bot API method name.
- **data** (dict) - Required - The request parameters.
### Request Example
```python
result = await api.request_raw("getMe", {})
json.loads(result.unwrap())["first_name"]
```
```
--------------------------------
### StateMutator model with AliveState and DeadState
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/9_states
Defines a state machine with AliveState and DeadState classes. Uses @mutation to define transitions: die() moves from Alive to Dead, resurrect() moves from Dead to Alive. The state stores data (since, reason) and provides a description property.
```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}"
```
--------------------------------
### Handle callback with PayloadModelRule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Shows how to handle a callback query using PayloadModelRule to automatically deserialize the payload into the model.
```APIDOC
## Handle callback with PayloadModelRule
### Description
Handles a callback query by using PayloadModelRule to automatically deserialize the callback data into the specified model.
### Method
N/A
### Endpoint
N/A
### Parameters
#### Path Parameters
None
#### Query Parameters
None
#### Request Body
- **data** (ItemModel) - Required - The deserialized model instance.
### Request Example
```python
from telegrinder.rules import PayloadModelRule
@bot.on.callback_query(PayloadModelRule(ItemModel, alias="data"))
async def buy(cb: CallbackQuery, data: ItemModel) -> None:
# data is already a ready model object.
await cb.edit_text(f"You bought {data.item} for {data.amount}")
```
### Response
#### Success Response (200)
- **None** - The handler edits the message text.
#### Response Example
```python
await cb.edit_text(f"You bought {data.item} for {data.amount}")
```
### Error Handling
No explicit error handling is documented.
```
--------------------------------
### Handle media groups with MediaGroup node
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/10_media
Use this snippet to handle media groups sent by users. It uses the MediaGroup and Caption nodes to access the group items and caption.
```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')}"
)
```
--------------------------------
### Handle a regular button press with Text rule
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Handles a message that equals '1' and replies with a confirmation. For regular keyboards, button presses arrive as normal text messages.
```python
@bot.on.message(Text("1"))
async def handle_press_button(message: Message) -> None:
await message.answer("You pressed button 1")
```
--------------------------------
### Translation by key
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Returns a translation using the key directly. This is useful when translations are stored in a structured way.
```python
return _.im.fine()
```
--------------------------------
### Access main router message view
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/6_dispatch
Shows the underlying main router view that the previous handler registration targets.
```python
bot.on.main_router.message
```
--------------------------------
### Use typed method get_me
Source: https://telegrinder.readthedocs.io/en/latest/api
Calls a typed method (get_me) and unwraps the result to access the model's attributes. Methods are in snake case and return a result of model or APIError.
```python
user = (await api.get_me()).unwrap()
user.first_name
```
--------------------------------
### InlineButton with callback_data
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/7_keyboard
Creates an inline button with the callback_data payload "button/1". This payload is matched by PayloadEqRule in the handler.
```python
InlineButton("1", callback_data="button/1")
```
--------------------------------
### Set default parse mode to HTML
Source: https://telegrinder.readthedocs.io/en/latest/tutorial/en/8_text
Sets the default parse mode to HTML so you don't need to pass parse_mode in every message. Recommended during initialization for bots that mostly send HTML-formatted text.
```python
from telegrinder.tools.formatting.html import HTML
api.default_params["parse_mode"] = HTML.PARSE_MODE
```
--------------------------------
### Type-hinted GlobalContext
Source: https://telegrinder.readthedocs.io/en/latest/tools/global_context
Define a type-hinted global context by subclassing GlobalContext and using ctx_var for constants.
```python
import typing
from telegrinder.tools.global_context import GlobalContext, ctx_var
class MyGlobalContext(GlobalContext):
__ctx_name__ = "context"
supplier: str
COOKIES: typing.Final[set[str]] = ctx_var(default={"chocolate", "chip", "cracker"}, const=True, init=False)
ctx = MyGlobalContext(supplier="Brooklyn Born Chocolate") # )>, )>>
```
--------------------------------
### Escape user input
Source: https://telegrinder.readthedocs.io/en/latest/tools/formatting
Use escape() on user-controlled text before inserting it into formatted output. Helpers like bold() already escape, but explicit escape is recommended when mixing plain strings and external input.
```python
from telegrinder.tools.formatting import HTML, bold, escape
safe = HTML << "User said: " << bold(escape(user_input))
```