### 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() ```