maxapi

repository·main·Indexed 42 days ago

https://github.com/love-apples/maxapi

An asynchronous Python framework for building bots for the MAX platform, providing a high-level API inspired by aiogram. Version 1.2.2 supports FSM, inline keyboards, middleware, and webhook integration via FastAPI.

Tokens
50.4K
Snippets
170
Records
241
Agent score
52%

What's inside maxapi

  1. Core features of maxapi

    main

    maxapi is an asynchronous framework for building MAX bots with features inspired by aiogram:

    • Asynchronous Bot: Thin wrappers around MAX Bot API methods.
    • Dispatcher & Router: Support for Dispatcher, Router, filters, F (magic filter), and middleware.
    • FSM (Finite State Machine): Context management via MemoryContext and RedisContext.
    • Update Methods: Supports both Polling and Webhooks.
    • Webhook Frameworks: Integration with aiohttp, FastAPI, or Litestar.
    • Media Handling: Uploading, sending, and downloading files.
    • Interactive UI: Support for inline buttons, callbacks, and typed callback payloads.
  2. How middleware inheritance works in the router tree

    main

    Inner middleware accumulates down the router hierarchy. When a handler is executed, its middleware chain (mw_chain) is composed of:

    1. Global inner_middlewares from the Dispatcher.
    2. inner_middlewares from all parent Routers in the hierarchy.
    3. inner_middlewares from the specific Router containing the handler.
    4. The handler's own local middlewares.

    Example Hierarchy: If a parent router has DbTransactionMiddleware and a child router has ChildAuditMiddleware, a handler inside child will execute both in that order before calling the handler itself.

    parent = Router(router_id="parent")
    parent.register_inner_middleware(DbTransactionMiddleware())
    
    child = Router(router_id="child")
    child.register_inner_middleware(ChildAuditMiddleware())
    
    parent.include_routers(child)
    # Handlers in 'child' will run: [DbTransactionMiddleware, ChildAuditMiddleware] + local_middlewares
    parent = Router(router_id="parent")
    parent.register_inner_middleware(DbTransactionMiddleware())
    
    child = Router(router_id="child")
    child.register_inner_middleware(ChildAuditMiddleware())
    
    parent.include_routers(child)
  3. Understand the middleware execution flow

    main

    Middleware execution follows a hierarchical structure. The 'outer' chain is built from the top down, while the 'inner' chain is baked into the handler's execution chain and runs from global to local levels.

    Execution Order:

    1. dp.outer_middlewares (Global Outer)
    2. router.outer_middlewares (Router Outer)
    3. [Filter Check: State + Filters]
    4. dp.inner_middlewares (Global Inner)
    5. router.inner_middlewares (Router Inner)
    6. handler.middlewares (Handler-level Inner)
    7. call_handler (The actual handler function)

    If the filters in step 3 fail, the execution stops, and no 'inner' middleware is ever invoked.

  4. Implement Middleware patterns

    main

    Middleware allows you to intercept events before they reach handlers. You can implement various patterns by subclassing BaseMiddleware.

    Common Patterns:

    • Logging: Log all incoming events.
    • Throttling: Limit request frequency (e.g., 1 message/sec per user).
    • Authorization: Implement a whitelist of allowed users.
    • Error Handling: Catch exceptions to prevent the bot from crashing.

    Note on Order:

    • dp.outer_middleware(): Runs before the router/handler logic.
    • dp.middleware(): Runs within the handler chain.
    • Data can be passed from middleware to handlers via the data dictionary.
  5. How routers, context, and states work together

    main

    You can organize bot logic using Router objects and manage user flow using MemoryContext with StatesGroup.

    • Routers: Use Router() to create modular handlers that can be included in a main Dispatcher via dp.include_routers(router).
    • States: Define a class inheriting from StatesGroup with State() members to represent different steps in a conversation.
    • Context: Use MemoryContext to persist data (update_data, get_data) and manage the current state (set_state, get_state, clear).
    • Filtering: Use the F magic filter to trigger handlers based on specific conditions, such as F.callback.payload == '...' or combining a message filter with a specific state (e.g., @dp.message_created(F.message.body.text, Form.name)).
    from maxapi import Bot, Dispatcher, F
    from maxapi.context import MemoryContext, State, StatesGroup
    from maxapi.types import MessageCreated, CallbackButton
    from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
    from maxapi import Router
    
    # 1. Define States
    class Form(StatesGroup):
        name = State()
        age = State()
    
    router = Router()
    
    # 2. Use Router and Context to manage state transitions
    @router.message_callback(F.callback.payload == "btn_1")
    async def handle_btn_1(event: MessageCallback, context: MemoryContext):
        await context.set_state(Form.name)
        await event.message.answer("Отправьте свое имя:")
    
    # 3. Handle input within a specific state
    @router.message_created(F.message.body.text, Form.name)
    async def handle_name_input(event: MessageCreated, context: MemoryContext):
        await context.update_data(name=event.message.body.text)
        await event.message.answer(f"Hello, {event.message.body.text}!")
  6. Compare Outer and Inner Middleware exception handling

    main

    The type of exception received depends on where the middleware is registered:

    Middleware TypeReceivesAccess to Original Exception
    Inner (in handler decorator)RAW exceptionDirectly
    Router outerHandlerExceptionvia e.cause
    Global outerHandlerExceptionvia e.cause

    Warning: If an outer middleware catches a HandlerException and does not raise it, the dispatcher considers the event successfully handled and will not log the error. In this case, the middleware is entirely responsible for error notification.

  7. Use MagicFilter (F) for message filtering

    main

    The F object (MagicFilter) provides a powerful way to filter incoming messages based on their content and attributes. Supported operations include:

    • Equality: F.message.body.text == "value"
    • String methods: .lower().contains("substring")
    • Regex: .regexp(r"pattern")
    • Length checks: .len() > 20
    • Existence: F.message.body.attachments (checks if attachments exist)
    • Empty checks: .len() > 0
    @dp.message_created(F.message.body.text == "привет")
    async def on_hello(event: MessageCreated):
        await event.message.answer("Привет!")
    
    @dp.message_created(F.message.body.text.lower().contains("помощь"))
    async def on_help(event: MessageCreated):
        await event.message.answer("Чем могу помочь?")
    
    @dp.message_created(F.message.body.text.regexp(r"^\d{4}$"))
    async def on_code(event: MessageCreated):
        await event.message.answer("Принят 4-значный код")
    
    @dp.message_created(F.message.body.attachments)
    async def on_attachment(event: MessageCreated):
        await event.message.answer("Получено вложение")
    
    @dp.message_created(F.message.body.text.len() > 20)
    async def on_long_text(event: MessageCreated):
        await event.message.answer("Слишком длинное сообщение")
  8. How outer and inner middleware work together

    main

    The Dispatcher and Router distinguish between two types of middleware based on when they are executed in the event lifecycle:

    1. Outer Middleware (register_outer_middleware(mw)): Called before any filters or state checks for a specific handler are evaluated. It triggers for every event that reaches the dispatcher/router, even if the handler is eventually skipped. Use this for logging, tracing, or generating request IDs.
    2. Inner Middleware (register_inner_middleware(mw)): Called only after a specific handler has passed all its filters and state requirements. It is guaranteed that the handler will be executed. Use this for database transactions, timing handler execution, or acquiring distributed locks.

    Note: The methods dp.middleware(), router.middleware(), dp.outer_middleware(), and router.outer_middleware() are deprecated. Always use register_outer_middleware() and register_inner_middleware() instead.

    # Registering on a Dispatcher
    dp.register_outer_middleware(LoggingMiddleware())  # Every update
    dp.register_inner_middleware(DbTransactionMiddleware())  # Only under handler
    
    # Registering on a Router
    router.register_outer_middleware(AuditMiddleware())
    router.register_inner_middleware(LockMiddleware())
  9. Use `event.message.answer()` vs `bot.send_message()`

    main

    When responding to an incoming event, choose the method based on your target:

    • event.message.answer(): A convenience wrapper that automatically uses the chat_id and user_id from the incoming message. Use this for direct replies.
    • bot.send_message(): Requires you to explicitly provide the chat_id. Use this when sending messages to arbitrary chats or users that did not trigger the current event.
  10. Implement FSM (Finite State Machine) for step-by-step forms

    main

    Use FSM to collect user data across multiple steps (e.g., a registration form).

    Core Components:

    • StatesGroup and State: Define the sequence of steps.
    • BaseContext: The storage for data between steps.

    Context Operations:

    • context.set_state(State): Transition to the next state.
    • context.update_data(key=value): Save data to the context.
    • context.get_data(): Retrieve accumulated data.
    • context.clear(): Reset the state and data (e.g., on a /cancel command).
  11. Understand the difference between outer and inner middleware

    main

    The maxapi framework distinguishes between two types of middleware to optimize performance and control execution flow:

    Outer Middleware

    • When it runs: Executes for every incoming event/update, even if no handler is matched or if filters (like IsAdmin) reject the request.
    • Best use cases:
      • Tracing and Request IDs (RequestIdMiddleware).
      • Global logging of all incoming updates (LoggingMiddleware).
      • Security auditing (e.g., logging attempts to access admin routes before authorization checks occur).
    • Registration: Use register_outer_middleware().

    Inner Middleware

    • When it runs: Executes only if a handler is actually matched and is about to be executed.
    • Best use cases:
      • Database transactions (DbTransactionMiddleware) that should only start when a write/read is certain.
      • Performance metrics (HandlerLatencyMetricMiddleware) that only care about handler execution time.
      • Resource locks (BroadcastLockMiddleware) that should only be held during the actual operation to avoid unnecessary overhead.
    • Registration: Use register_inner_middleware().
    # Global Outer: runs for everything
    dp.register_outer_middleware(LoggingMiddleware())
    
    # Global Inner: runs only if a handler is found
    dp.register_inner_middleware(DbTransactionMiddleware())
    
    # Router Outer: runs for any event targeting this router
    admin_router.register_outer_middleware(AdminAccessLogMiddleware())
    
    # Router Inner: runs only when an admin handler is called
    admin_router.register_inner_middleware(BroadcastLockMiddleware())