8
Modules Context
Logan Fick edited this page 2026-05-04 21:48:06 -04:00

Modules - Context

Every handler receives a context object as its only argument. There are four context types, but they all provide access to the same core services. The difference is what triggered the handler and what extra data is carried alongside those services.

ModuleContext

ModuleContext is the root context. It is created once per module at load time and holds all shared services. @on_setup and @on_teardown functions receive this directly:

from owlbot.api import ModuleContext, on_setup

@on_setup
async def setup(ctx: ModuleContext) -> None:
    ctx.logger.info("Module loaded!")

Fields

Field Type Description
module_name str The module's name (filename without .py for single-file modules, or directory name for packages).
config ModuleConfig Module-scoped configuration (details).
owncast_client OwncastClient Owncast Integration API client (details).
storage ModuleStorage Per-module SQLite storage (details).
commands ModuleCommands Dynamic command registration and lookup (details).
events ModuleEvents Dynamic event handler registration (details).
routes ModuleRoutes HTTP route registration and URL building (details).
http HttpClient Shared HTTP client for external requests (details).
admin_client OwncastAdminClient | None Owncast Admin API client, or None if admin is not enabled in config (details).
state dict[str, Any] Per-instance state storage for runtime objects. See Per-Instance State below.
logger logging.Logger Logger named owlbot.modules.<module_name>. Auto-derived from module_name.

ModuleContext is a dataclass. All fields except logger are set during construction. logger is derived from module_name in __post_init__.

The service wrapper types (ModuleCommands, ModuleEvents, ModuleRoutes) are importable from owlbot.api for use in type annotations on helper functions:

Per-Instance State

Modules that need to hold in-memory runtime objects (managers, schedulers, caches) across handler invocations should use the state dict instead of module-level globals. This ensures multiple bot instances running in the same Python process maintain fully independent state.

Store objects during setup using string keys, and retrieve them in handlers via ctx.module.state:

from owlbot.api import ModuleContext, on_setup, on_teardown

class MyManager:
    """Example stateful manager."""
    def __init__(self, greeting: str) -> None:
        self.greeting = greeting

@on_setup
async def setup(ctx: ModuleContext) -> None:
    ctx.state["manager"] = MyManager(greeting="hello")

@on_teardown
async def teardown(ctx: ModuleContext) -> None:
    ctx.state.pop("manager", None)

For type safety, wrap access in a typed helper function. The isinstance check narrows the return type so callers get full type information:

from owlbot.api import CommandContext, ModuleContext, on_command


def get_manager(ctx: ModuleContext) -> MyManager:
    """Return the MyManager for this module instance."""
    manager = ctx.state.get("manager")
    if not isinstance(manager, MyManager):
        raise RuntimeError("MyManager is not initialized.")
    return manager


@on_command("greet")
async def greet(ctx: CommandContext) -> None:
    manager = get_manager(ctx.module)  # Fully typed as MyManager
    await ctx.owncast_client.send_message(manager.greeting)

EventContext[E]

Event handlers receive an EventContext parameterized by the event type. It wraps the event data and provides access to all services from ModuleContext:

from owlbot.api import EventContext, EventType, ChatEvent, on_event

@on_event(EventType.CHAT)
async def on_chat(ctx: EventContext[ChatEvent]) -> None:
    # The event itself:
    message = ctx.event.raw_body
    user = ctx.event.user.display_name

    # Services (proxied from ModuleContext):
    await ctx.owncast_client.send_message(f"{user} said: {message}")

Attributes

Attribute Type Description
event E The event that triggered this handler (e.g., ChatEvent, UserJoinedEvent).
module ModuleContext The full module context with all services.
propagation_stopped bool Whether propagation has been stopped for this event.

Methods

Method Description
stop_propagation(reason=None) Stops the event from reaching remaining handlers. See Event System.
session_url_for(path) Build a one-time connect URL for one of this module's routes, tied to ctx.event.user. Available only when the event has a user field.

All ModuleContext services are available directly on ctx (see Proxied Properties below).

CommandContext

Command handlers receive a CommandContext with parsed command data, access to the underlying chat event, and all module services:

from owlbot.api import CommandContext, on_command

@on_command("greet", aliases=["hello", "hi"])
async def greet(ctx: CommandContext) -> None:
    name = ctx.user.display_name
    args = ctx.args  # Everything after the command name
    await ctx.owncast_client.send_message(f"Hey {name}! Args: {args}")

Fields

Field Type Description
command_event CommandEvent The parsed command data (command name, args, prefix, original chat event).
event_context EventContext[ChatEvent] The event context from the CHAT event that triggered this command.
module ModuleContext The full module context with all services.

Convenience Properties

CommandContext provides shortcuts into command_event for common data:

Property Type Description
command str The canonical command name (not the alias used to invoke it).
args str Raw argument string after the command name.
args_list list[str] Arguments split into a list by whitespace.
prefix str The command prefix (e.g., "!").
chat_event ChatEvent The original chat event.
user User The user who invoked the command (shortcut to chat_event.user).

Methods

Method Description
session_url_for(path) Build a one-time connect URL for one of this module's routes, tied to the command user. Use this when sending links to protected routes.

All ModuleContext services are available directly on ctx (see Proxied Properties below).

RouteContext

Route handlers receive a RouteContext with the aiohttp request and module services:

from owlbot.api import RouteContext, on_route

@on_route("/stats")
async def stats_page(ctx: RouteContext) -> dict:
    query = ctx.request.query.get("format", "json")
    return {"viewers": 42, "format": query}

Fields

Field Type Description
request aiohttp.web.Request The aiohttp request object. Access query params, body, headers, path info, etc.
match_info dict[str, str] Captured path parameters from {name} patterns. Empty dict for plain routes.
session BrowserSession | None Connected browser session for this request, if one was provided. Protected routes can use this to read the linked Owncast user.
module ModuleContext The full module context with all services.

All ModuleContext services are available directly on ctx (see Proxied Properties below).

BrowserSession

ctx.session is set when the browser has an active Owlbot session. Routes that use requires_session=True, requires_authenticated=True, or requires_moderator=True only run after the required session check passes, so ctx.session is available inside those handlers.

Attribute Type Description
user User Owncast user linked to this browser session.
expires_at datetime When the session expires.
is_authenticated bool Whether the linked user is authenticated.
is_moderator bool Whether the linked user has the MODERATOR scope.

For public routes, ctx.session may be None. Check it before reading session fields unless the route is protected. See Protected Routes for the route flags and session caveats.

Proxied Properties

EventContext, CommandContext, and RouteContext all proxy the services from ModuleContext directly onto ctx. This means ctx.owncast_client and ctx.module.owncast_client return the same object. The full set of proxied properties:

module_name, config, owncast_client, storage, commands, events, routes, logger, http, admin_client

The only differences between the three context types are the handler-specific fields:

  • EventContext adds .event (the triggering event), propagation control, and .session_url_for() for events with a user field.
  • CommandContext adds .command_event (parsed command data), convenience properties for command/args/user, and .session_url_for() through the underlying chat event.
  • RouteContext adds .request (the HTTP request) and .session (the connected browser session, if any).

Utility functions that need to work across handler types can accept ModuleContext directly, since all three contexts expose it via .module.