### Telegrinder demo bot: /start handler and run loop Source: https://luwqz1.github.io/telegrinder-docs-website The README example. Creates a Telegrinder bot that answers /start with a greeting built from the sender's and bot's full names. Requires a valid bot token in place of "123:token" and calls bot.run_forever() to start polling. ```python from telegrinder import API, Message, Telegrinder, Token from telegrinder.modules import setup_logger from telegrinder.rules import Text setup_logger(level="INFO") api = API(token=Token("123:token")) bot = Telegrinder(api) @bot.on.message(Text("/start")) async def start(message: Message) -> None: me = (await api.get_me()).unwrap() await message.answer(f"Hello, {message.from_user.full_name}! I'm {me.full_name}.") bot.run_forever() ``` -------------------------------- ### Checkbox waiter example Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Use bot.dispatch.checkbox for multi-select flows. The example shows adding options with normal and selected text, and editing the message with the picked keys. ```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, ) ``` -------------------------------- ### Choice waiter example Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Use bot.dispatch.choice to let the user pick exactly one option. The example shows adding options with normal and selected text, 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, ) ``` -------------------------------- ### Create a regular keyboard with buttons Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Builds a keyboard with buttons '1', '2', and '3', using .add() to add buttons to the current row 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")) ) ``` -------------------------------- ### Run the bot with bot.run_forever() Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/setting-up Add this line at the end of the file to start polling and receive updates asynchronously in a loop. ```python bot.run_forever() ``` -------------------------------- ### MemoryStateStorage with StateEnum Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Creates a memory-backed state storage and a StrEnum of state keys. Use this as the starting point for long-lived user state in Telegrinder. ```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" ``` -------------------------------- ### Set, get, and delete state with states API Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Shows the three most common state operations: set, get, and delete. MemoryStateStorage does not survive process restarts, so use external storage like Redis when state must persist. ```python await states.set(user_id, StateEnum.BLESSED, {"source": "admin"}) state = await states.get(user_id) await states.delete(user_id) ``` -------------------------------- ### Create named and unnamed global contexts Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context Shows how to instantiate GlobalContext with or without a name. An unnamed context is created with no arguments, and a named context is created by passing a string; the example asserts that an unnamed context is not equal to a named one. ```python from telegrinder.tools.global_context import GlobalContext ctx = GlobalContext() # unnamed assert ctx != GlobalContext("context") ``` -------------------------------- ### Use unwrap to get value or raise exception Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/functional-bits Demonstrates the unwrap method: returns the value on success, raises UnwrapError on error. The second line will raise an exception. ```python divide(6, 3).unwrap() == 2 divide(3, 0).unwrap() # Exception UnwrapError("Cannot divide by zero") ``` -------------------------------- ### Format code block with pre_code Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/text Use pre_code from telegrinder.tools.formatting to render a code block with a specified language. Pass the code string and the lang parameter to get a properly formatted block instead of a plain string with indentation. ```python from telegrinder.tools.formatting import pre_code snippet = pre_code( "print('Hello from telegrinder')", lang="python", ) ``` -------------------------------- ### Payload as a model with msgspec.Struct Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Use a model for more serious menus where callback_data may be a str, dict, dataclass, or msgspec.Struct. The example defines an ItemModel(msgspec.Struct) and builds an InlineKeyboard with an InlineButton whose callback_data is that model. ```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"), ) ) ) ``` -------------------------------- ### Format a message with HTML helper and formatting functions Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/text Builds a formatted Telegram message using the HTML helper and formatting functions. The example shows how to combine links, spoilers, inline code, and bold text with the << operator. The rendered output is shown in the documentation: 'Documentation: Python docs', 'Token: `123:secret-token`', and '**Do not share this with anyone.**'. ```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.") ) ``` -------------------------------- ### Import HTML formatting helpers Source: https://luwqz1.github.io/telegrinder-docs-website/tools/formatting Imports the HTML entrypoint and formatting helpers from telegrinder.tools.formatting. ```python from telegrinder.tools.formatting import HTML, bold, italic, mention ``` -------------------------------- ### Send a local file with InputFile Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/media Builds an InputFile from bytes read from a local path and sends it as a photo with a caption. Use 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", ) ``` -------------------------------- ### GlobalContext Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context The GlobalContext class stores values for the entire program by name. It inherits from dict and supports named contexts, setting and getting values, deletion, clearing, renaming, and type-hinting. ```APIDOC ## GlobalContext ### Description The global context stores values for the entire program that can be set by name. The base global context inherits from the `dict` class, so it works like a `dict` object. ### Import ```python from telegrinder.tools.global_context import GlobalContext ``` ### Abstract Interface ```python from telegrinder.tools.global_context import ABCGlobalContext ``` ### Constructor ```python GlobalContext(*args, **kwargs) ``` - `*args` - Optional positional arguments for dict initialization. - `**kwargs` - Optional keyword arguments to set context variables. Values can be `CtxVar` instances. ### Methods #### `get_value(name, type=None)` - **name** (str) - Required - The name of the context variable. - **type** (type) - Optional - The expected type of the value. - Returns a `Result` that unwraps to the value if found, or an error if not found. #### `get(name, type)` - **name** (str) - Required - The name of the context variable. - **type** (type) - Required - The expected type of the value. - Returns a `GlobalCtxVar` if the type matches, otherwise raises `AssertionError`. #### `delete_ctx()` - Deletes the context. Returns a `Result` that unwraps to `None` on success, or an error if the context is unnamed. #### `rename(old_name, new_name)` - **old_name** (str) - Required - The current name of the variable. - **new_name** (str) - Required - The new name for the variable. - Returns a `Result` that unwraps to `None` on success, or an error if the variable is const. #### `clear(include_consts=False)` - **include_consts** (bool) - Optional - If `True`, also removes constant variables. Defaults to `False`. ### Magic Methods - `__iter__` - Iterates over context variables. - `__contains__` - Checks if a variable name exists. - `__eq__` - Compares two contexts for equality. - `__bool__` - Returns `True` if the context is not empty. ### Examples #### Setting and Getting Values ```python from telegrinder.tools.global_context import GlobalContext, CtxVar ctx = GlobalContext(name="Alex", url=CtxVar("https://google.com", const=True)) 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 ''. ``` #### Setting Values ```python from telegrinder.tools.global_context import GlobalContext ctx = GlobalContext() ctx.kitten = Kitten() # cool! ctx["items"] = ["Oopa", "Loopa", "Woopa"] # very nice! ``` #### Deleting Values ```python from telegrinder.tools.global_context import GlobalContext, CtxVar ctx = GlobalContext(first="woop", second=CtxVar("wop", const=True)) del ctx.woop # cool! del ctx["wop"] # cannot delete const context variable! ``` #### Getting with Type ```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() # ``` #### Clearing ```python from telegrinder.tools.global_context import GlobalContext, CtxVar ctx = GlobalContext(name="Alex", URL=CtxVar("https://google.com", const=True)) ctx.clear() # exclude constants ctx.clear(include_consts=True) ``` #### Deleting Context ```python from telegrinder.tools.global_context import GlobalContext ctx = GlobalContext("context") ctx.delete_ctx().unwrap() # ok! ctx = GlobalContext() ctx.delete_ctx().unwrap() # error: cannot delete unnamed context! ``` #### Renaming ```python from telegrinder.tools.global_context import GlobalContext, CtxVar ctx = GlobalContext(georgy="cool", telegrinder=CtxVar("cool", const=True)) ctx.rename("georgy", "arseny").unwrap() ctx.arseny # 'cool' ctx.georgy # is not defined ctx.rename("telegrinder", "other framework").unwrap() # error: cannot rename const context variable! ``` #### Type-Hinting ```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") # )>, )>> ``` #### Magic Methods ```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 ``` ### Built-in Type-Hinted Context ```python from telegrinder.tools.global_context import TelegrinderContext ``` ``` -------------------------------- ### Import GlobalContext Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context Import the GlobalContext class from the telegrinder.tools.global_context module. ```python from telegrinder.tools.global_context import GlobalContext ``` -------------------------------- ### Create an inline keyboard with InlineKeyboard and InlineButton Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Builds a two-row inline keyboard: buttons 1 and 2 on the first row, then a new row with button 3. Each button carries a `callback_data` payload. Use this when you need buttons attached to a message that send callback data instead of text. ```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")) ) ``` -------------------------------- ### Blueprint-style handler module and bot entry point Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch The handlers/start.py module defines a global Dispatch instance named dp. The bot entry point loads all handlers from the directory recursively. This pairs the module definition with the loader call for a blueprint-style layout. ```python # handlers/start.py from telegrinder import Dispatch dp = Dispatch(name="start") ``` ```python bot.on.load_from_dir("handlers", recursive=True) ``` -------------------------------- ### Send formatted message with parse_mode Source: https://luwqz1.github.io/telegrinder-docs-website/tools/formatting Uses the HTML string object with parse_mode=HTML.PARSE_MODE to send a formatted message. The example shows a handler that mentions the sender by their first name and user ID. ```python parse_mode=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, ) ``` -------------------------------- ### Initialize Telegrinder bot with API and Token Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/setting-up Creates the core bot instance. Requires a token from BotFather. The Token class is used for strong typing and security, and API is the instance for accessing the Telegram API. ```python from telegrinder import Telegrinder, API, Token api = API(Token("your-token-here")) bot = Telegrinder(api) ``` -------------------------------- ### Create admin router and dispatch Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch Creates a separate router for an admin zone, registers a handler for the /ban command, and creates a Dispatch that uses this router. This helps organize code by logical domains. ```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") ``` -------------------------------- ### Basic error handling with event_error Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/handling-errors Registers two message handlers that raise different exceptions and an event_error handler that catches them. The error handler receives an Error[RuntimeError, ValueError] node and can access the exception via err.exception. The example shows the expected chat output for both the ```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}") ``` -------------------------------- ### Basic LoopWrapper usage Source: https://luwqz1.github.io/telegrinder-docs-website/tools/loop-wrapper Creates a LoopWrapper, schedules a background task with add_task, and runs the wrapper. Use this when you need a standalone event-loop orchestration outside of a bot instance. ```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() ``` -------------------------------- ### Send static keyboard with FruitsKeyboard.get_markup() Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Sends the keyboard by passing FruitsKeyboard.get_markup() to reply_markup in a message answer. Use this when a handler should present the static keyboard to the user. ```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(), ) ``` -------------------------------- ### Escape user input with escape() before formatting Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/text Escapes user-controlled text before formatting it as bold in a Telegram message. The example uses the escape() helper to prevent markup injection from user names. The documentation shows that a first_name like 'George' is rendered as '**< i>George**'. ```python from telegrinder.tools.formatting import HTML, bold, escape @bot.on.message() async def echo_name(message: Message) -> None: unsafe_name = message.from_user.first_name await message.answer( HTML << "Your nickname: " << bold(escape(unsafe_name)) ) ``` -------------------------------- ### Import JSONSerializer and MsgPackSerializer Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Import the two main payload serializers provided by Telegrinder: JSONSerializer and MsgPackSerializer. ```python from telegrinder.tools import JSONSerializer, MsgPackSerializer ``` -------------------------------- ### Get context variable values with type checking Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context Retrieves a context variable value. `ctx.arseny` and `ctx["arseny"]` return the value with an unknown type, while `ctx.get_value("arseny", str).unwrap()` returns it typed as `str`. Accessing a missing name raises `NameError: Name 'lul' is not defined`. ```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' not defined ``` -------------------------------- ### Using bot.loop_wrapper for tasks, lifespan hooks, and timers Source: https://luwqz1.github.io/telegrinder-docs-website/tools/loop-wrapper In actual bot code you usually do not create and run a separate wrapper manually. Instead, use the shared wrapper that Telegrinder already creates internally. ```python bot.loop_wrapper.add_task(...) bot.loop_wrapper.lifespan.on_startup(...) bot.loop_wrapper.timer(...) ``` -------------------------------- ### Auto-load dispatches from a directory with load_from_dir Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch Walks over Python files in the given directory, imports them, looks for global Dispatch instances, and loads them into the main dispatch. Useful for blueprint-style project layouts. ```python bot.on.load_from_dir("handlers", recursive=True) ``` -------------------------------- ### Send a regular keyboard with a message Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Handles the /keyboard command and sends a message with the keyboard markup using keyboard.get_markup(). ```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(), ) ``` -------------------------------- ### Mini player state machine with Telegrinder State and mutation Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Defines three state classes (Stopped, Playing, Paused) with mutation methods that transition between states, and a handler that uses pattern matching to process the play command. The handler expects a state of type Stopped | Paused | Playing and an optional song name; it returns a string message based on the current state. ```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()) ``` ```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" ``` -------------------------------- ### Set context variables with CtxVar in GlobalContext __init__ Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context Creates a GlobalContext and sets context variables by keyword arguments in `__init__()`. The `CtxVar` wrapper can mark a value as constant with `const=True`. ```python from telegrinder.tools.global_context import GlobalContext, CtxVar ctx = GlobalContext(name="Alex", url=CtxVar("https://google.com", const=True)) ``` -------------------------------- ### Using StateMutator in handlers with state type hints Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Shows how to use StateMutator with state classes in bot handlers. Handlers accept the current state object and call transition methods on it; the handler only runs when the state matches the type hint. The third handler accepts a union of states. ```python @bot.on.message(Text("/die")) async def die_handler(alive: AliveState) -> str: # If this handler ran, the current state is already AliveState. new_state = await alive.die("sadness") return f"You are now {new_state.__description__}" @bot.on.message(Text("/resurrect")) async def resurrect_handler(dead: DeadState) -> str: await dead.resurrect() return "You resurrected" @bot.on.message() async def in_state_handler(state: AliveState | DeadState) -> str: # You can accept a union if several states are valid here. return f"You are currently {state.__description__}" ``` -------------------------------- ### Loading the dispatch into the main bot Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch Shows how to load an external dispatch into the main bot using bot.on.load(). The dispatch is imported from a handlers module and loaded before running the bot. ```python from handlers import chat_utilities from telegrinder import API, Telegrinder, Token api = API(Token("your-token-here")) bot = Telegrinder(api) # Load routers and error handlers from the external dispatch. bot.on.load(chat_utilities.dp) bot.run_forever() ``` -------------------------------- ### Create global nodes for configuration and secrets Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/nodes Shows two global nodes: a DataNode for configuration and a scalar node for a secret value. They are built once for the whole application, avoiding rebuilds on every update. ```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) ``` -------------------------------- ### Import ABCGlobalContext abstract interface Source: https://luwqz1.github.io/telegrinder-docs-website/tools/global-context Import the ABCGlobalContext abstract interface from the telegrinder.tools.global_context module. ```python from telegrinder.tools.global_context import ABCGlobalContext ``` -------------------------------- ### Minimal dispatch with bot message and repeat command handlers Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch Defines a local dispatch with two handlers: one for bot messages and one for the /repeat command. The repeat command parses text and an optional times argument, defaulting to 5. The dispatch can be stored in its own file and 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)) ``` -------------------------------- ### Formatting message with HTML helpers Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/text Shows how to compose formatted messages using HTML helpers like bold, italic, mention, and date_time. The handler responds to /formatting and sends three messages: bold+italic text, a greeting with a user mention, and a date/time entity. Requires a bot instance and a Message object. ```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)) ) ``` -------------------------------- ### Define a static keyboard class with FruitsKeyboard Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Defines a keyboard as a class with max_in_row=2 and one_time_keyboard=True. Class attributes declare buttons that can also be reused 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") ``` -------------------------------- ### Pattern match on Result with match statement Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/functional-bits Shows how to process a Result using Python's match statement, handling Ok and Err cases separately. ```python match result: case Ok(value): print("Value is", value) case Err(err): print("Something is wrong:", err) ``` -------------------------------- ### Load multiple dispatches with bot.on.load_many Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/dispatch Combines several already-imported dispatcher modules into the main bot dispatch in one place. Useful when modules are imported manually and need to be registered together. ```python bot.on.load_many(users.dp, payments.dp, admin.dp) ``` -------------------------------- ### Handle a regular button press Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/keyboard Handles a button press by matching the button text '1' with a Text rule and replying with a confirmation message. ```python @bot.on.message(Text("1")) async def handle_press_button(message: Message) -> None: await message.answer("You pressed button 1") ``` -------------------------------- ### Using external mutations in handlers with StateMutator Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/states Shows how to use external mutations in handlers. Handlers receive a StateMutator instance and pass it to the mutation functions. The first triggers be_born, the second triggers login_as_ghost with silently=True. ```python @bot.on.message(Text("/be_born")) async def be_born_handler(mutator: StateMutator) -> str: await be_born(mutator) return "You were born" @bot.on.message(Text("Gh0$T_рa$$w0rd")) async def ghost_handler(mutator: StateMutator) -> str: await login_as_ghost(mutator, silently=True) return "You are now a ghost" ``` -------------------------------- ### Localization with BaseTranslator and I18NConfig Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/text Configures telegrinder localization with a translation directory and nested-key separator. Uses the BaseTranslator node to translate by key or with parameters in message handlers. ```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())) # 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) ``` -------------------------------- ### Define a function returning Result with kungfu Source: https://luwqz1.github.io/telegrinder-docs-website/tutorial/functional-bits Shows how to create a safe division function using kungfu's Result, Ok, and Error. Returns Error for division by zero, otherwise Ok with the result. ```python from kungfu import Result, Ok, Error def divide(a: int, b: int) -> Result[float, str]: if b == 0: return Error("Cannot divide by zero") return Ok(a / b) ``` -------------------------------- ### Build formatted text with HTML, mention, bold, italic Source: https://luwqz1.github.io/telegrinder-docs-website/tools/formatting Composes an HTML string object by chaining text and formatting helpers. The result is passed to `parse_mode=HTML.PARSE_MODE` when sending. ```python from telegrinder.tools.formatting import HTML, bold, italic, mention text = HTML << "Hello, " << mention("Arseny", user_id=549019276) << "! " << bold(italic("Welcome")) ```