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