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 auserfield. - 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.
Built-in Modules
Creating New Modules