### FastAPI Webhook Setup with Maxo Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/event-handling/webhooks.rst This code demonstrates how to initialize a Maxo engine with a FastAPI web adapter and configure the application lifespan to handle startup and shutdown events for the webhook engine. ```python def main() -> FastAPI: engine = SimpleEngine( dp, bot, web_adapter=FastApiWebAdapter(), # Укажите путь, по которому к вам будут приходить апдейты из Макса routing=StaticRouting(url="https://example.com/webhook"), # security можно оставить None, если не используете секретный токен security=Security(secret_token=StaticSecretToken("pepapig")), ) # В реализации FastApiWebAdapter в register игнорируются переданные # on_startup и on_shutdown. Разработчик должен сам определить lifespan, # в котором вызовет engine.on_startup и engine.on_shutdown для корректной работы @asynccontextmanager async def lifespan(app: FastAPI) -> AsyncGenerator[None, None]: engine.register(app) await engine.on_startup(app) yield await engine.on_shutdown(app) return FastAPI(lifespan=lifespan) logging.basicConfig(level=logging.DEBUG) app = main() # TOKEN=f9LHod fastapi dev ./examples/webhook_fastapi.py ``` -------------------------------- ### Setting Webhook in After Startup Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/event-handling/webhooks.rst This handler is called after the application has started. It's used to set up the webhook with the Maxo framework, specifying the types of updates to receive. ```python @dp.after_startup() async def on_startup(dispatcher: Dispatcher, webhook_engine: WebhookEngine) -> None: await webhook_engine.set_webhook(update_types=collect_used_updates(dispatcher)) ``` -------------------------------- ### Security Token Configuration Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/event-handling/webhooks.rst Example of configuring a security token for webhook requests using StaticSecretToken. This ensures that only requests from Max.ru servers are processed. ```python from maxo.transport.webhook.security import Security, StaticSecretToken security = Security(secret_token=StaticSecretToken("your-super-secret-token")) ``` -------------------------------- ### Maxo Dialog Templating - Navigation and Dialog Loop Source: https://github.com/k1rl3s/maxo/blob/master/src/maxo/dialogs/tools/templates/message.html This snippet demonstrates the main loop for rendering dialogs and windows, including navigation links based on dialog states. It iterates through dialogs and their respective windows, creating links for state transitions. ```html {% for dialog in dialogs %} {{ dialog.state_group }} {% for window in dialog.windows %} [{{ window.state_name }}](#{{ window.state|replace (':', '--') }}) {% endfor %} {% endfor %} {% for dialog in dialogs %} {% for window in dialog.windows %} B {% if window.photo %} ![{{ window.photo }}]({{ window.photo }}) {% endif %} Bot {{ window.message|safe }} {{ window.state_name }} {% if window.keyboard %} {% for row in window.keyboard %} {% for button in row %} {% if button.state == window.state %} {{ button.title }} {% else %} [{{ button.title }}](#{{ button.state|replace (':', '--') }}) {% endif %} {% endfor %} {% endfor %} {% endif %} {% if window.reply_keyboard %} Reply keyboard {% for row in window.reply_keyboard %} {% for button in row %} [{{ button.title }}](#{{ button.state }}) {% endfor %} {% endfor %} {% endif %} {% if window.text_input or window.attachment_input %} {% if window.text_input %} ⌨️ TextInput {% endif %} {% if window.attachment_input %} 📎 MessageInput {% endif %} {% endif %} {{ window.state }} {% endfor %} {% endfor %} ``` -------------------------------- ### Using Facades with Omittable Parameters Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/botapi/omitted.rst Shows how facades wrap API methods and handle `Omittable` parameters. If a parameter like `notify` is omitted, it defaults to `true` on the server. Setting `notify=False` explicitly sends the parameter. ```python from maxo.routing.updates import MessageCreated @dispatcher.message_created() async def handler(message: MessageCreated) -> None: # notify не указан -> Omitted -> не попадёт в запрос -> сервер использует значение по умолчанию (true) await message.answer_text("Привет!") # notify=False -> попадёт в запрос -> участники чата НЕ получат уведомление await message.answer_text("Тихое сообщение", notify=False) ``` -------------------------------- ### Link Utility Module Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/utils/link.rst This section provides documentation for the functions within the maxo.utils.link module. ```APIDOC ## Link Utility Module ### Description This module contains utility functions for handling links. ### Members - **create_link**(url, text, new_tab=False) Creates an HTML link. - **get_domain**(url) Extracts the domain name from a URL. - **is_valid_url**(url) Checks if a given string is a valid URL. ### Undocumented Members This module may contain undocumented members not listed here. ### Inheritance Information about class inheritance is not available in this documentation. ``` -------------------------------- ### Building Greetings with Omittable Fields Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/botapi/omitted.rst Illustrates how to build a greeting string using `Omittable` fields for `name` and `greeting`. The `is_defined` function checks if these fields have been provided. ```python from maxo.omit import Omittable, Omitted, is_defined def build_greeting( name: Omittable[str] = Omitted(), greeting: Omittable[str] = Omitted(), ) -> str: parts = [] if is_defined(greeting): parts.append(greeting) else: parts.append("Привет") if is_defined(name): parts.append(name) return ", ".join(parts) + "!" build_greeting() # "Привет!" build_greeting(name="Кирилл") # "Привет, Кирилл!" build_greeting(greeting="Здравствуйте") # "Здравствуйте!" ``` -------------------------------- ### maxo.routing.updates Module Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/botapi/updates.rst Documentation for the routing updates module members and inheritance. ```APIDOC ## Module: maxo.routing.updates ### Description This module handles routing updates within the Maxo project. It includes members and inheritance structures for managing routing logic. ### Members - The module includes all members defined within `maxo.routing.updates`. - Includes undocumented members. - Shows class inheritance. ``` -------------------------------- ### Safe vs. Unsafe Field Access in Maxo Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/botapi/omitted.rst Demonstrates safe access to potentially omitted fields using `is_defined` and unsafe access with `unsafe_` which may raise `AttributeIsEmptyError`. ```python from maxo.omit import is_defined from maxo.errors import AttributeIsEmptyError @dispatcher.message_created() async def handler(update: MessageCreated) -> None: # Безопасный доступ - проверяйте сами: if is_defined(update.message.sender): name = update.message.sender.first_name # Или используйте unsafe_ - короче, но бросит исключение, # если поля нет: try: name = update.message.unsafe_sender.first_name except AttributeIsEmptyError: name = "Аноним" ``` -------------------------------- ### Basic Message Handler Source: https://github.com/k1rl3s/maxo/blob/master/docs/pages/event-handling/webhooks.rst A simple handler that echoes the received HTML text back to the user, preserving the HTML format. ```python async def echo_handler(message: MessageCreated) -> None: await message.answer_text( text=message.message.body.html_text, format=TextFormat.HTML, ) ``` -------------------------------- ### Maxo CSS Styling Source: https://github.com/k1rl3s/maxo/blob/master/src/maxo/dialogs/tools/templates/message.html This CSS code defines the styling for the Maxo Dialog Preview, including layout, colors, typography, and interactive element appearances. ```css * { font-family: 'Roboto', sans-serif; margin: 0; padding: 0; box-sizing: border-box; } html { height: 100%; } body { background-color: #d6e6f2; font-size: 14px; color: #1a1a1a; padding: 16px; } .nav { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; align-items: center; } .nav-group { display: inline-flex; align-items: center; gap: 4px; background-color: rgba(255, 255, 255, 0.7); border-radius: 12px; padding: 6px 10px; } .nav-group .group-name { font-weight: 500; color: #2c3e50; font-size: 13px; padding-right: 4px; } .nav-link { display: inline-block; background-color: #4a9fe5; color: #ffffff; text-decoration: none; padding: 6px 14px; border-radius: 8px; font-size: 13px; font-weight: 500; transition: background-color 200ms; } .nav-link:hover { background-color: #3a8bd4; } .message { max-width: 520px; margin-left: 56px; margin-top: 12px; position: relative; } .avatar { position: absolute; bottom: 0; left: -52px; width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, #4a9fe5, #6bb5f0); color: #ffffff; font-size: 18px; font-weight: 700; text-align: center; line-height: 40px; } .bubble { position: relative; background-color: #ffffff; border-radius: 12px; box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08); overflow: hidden; } .bubble-content { padding: 10px 14px 6px 14px; } .author { font-weight: 700; color: #4a9fe5; font-size: 13px; margin-bottom: 4px; } .text { line-height: 1.45; padding-bottom: 4px; word-wrap: break-word; } .time { text-align: right; color: #e07060; font-size: 12px; padding: 0 14px 8px 0; } .bubble > img { width: 100%; max-height: 256px; object-fit: cover; } .keyboard { border-top: 1px solid #eef2f5; } .keyboard-row { display: flex; gap: 4px; padding: 4px 6px 0 6px; } .keyboard-row:last-of-type { padding-bottom: 6px; } .keyboard-button { display: block; flex: 1; text-align: center; padding: 10px 8px; background-color: #dcedf8; color: #2c3e50; border-radius: 10px; text-decoration: none; font-size: 14px; font-weight: 400; transition: background-color 200ms; min-width: 0; } .keyboard-button:hover { background-color: #c5dff0; } .keyboard-button-disabled { opacity: 0.45; cursor: default; } .reply-keyboard { background-color: rgba(255, 255, 255, 0.5); padding: 6px; margin-top: 8px; border-radius: 12px; } .reply-keyboard .title { background-color: rgba(255, 255, 255, 0.5); padding: 8px; text-align: center; border-radius: 10px; font-weight: 500; color: #2c3e50; margin-bottom: 4px; } .reply-keyboard .keyboard-button { background-color: #dcedf8; } .reply-keyboard .keyboard-button:hover { background-color: #c5dff0; } .inputs { display: flex; gap: 6px; justify-content: center; padding: 6px; border-top: 1px solid #eef2f5; } .input-badge { display: inline-block; color: #7a8fa0; background-color: #f0f4f8; padding: 6px 12px; border-radius: 8px; font-size: 12px; } .state-label { font-size: 11px; color: #8899aa; margin-top: 2px; margin-left: 4px; } .message:target { display: block; } .message:not(:target) { display: none; } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.