5
Modules Events
Logan Fick edited this page 2026-04-26 18:17:46 -04:00

Modules - Events

The event system is how modules react to things happening on the Owncast server: chat messages, users joining or leaving, the stream going live, etc. Webhook payloads from Owncast are parsed into typed dataclasses and dispatched to registered handlers.

Handlers that exceed the configured handler_timeout (default 30 seconds) are cancelled. An exception or timeout in one handler does not affect other handlers for the same event; each runs independently, and failures are logged without taking down the bot.

@on_event Decorator

The @on_event decorator registers a function as an event handler. Basic usage:

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

@on_event(EventType.CHAT)
async def keyword_alert(ctx: EventContext[ChatEvent]) -> None:
    if "help" in ctx.event.raw_body.lower():
        await ctx.owncast_client.send_system_message("A moderator will be with you shortly!")

A single handler can listen to multiple event types:

from owlbot.api import EventContext, EventType, Event, StreamStartedEvent, on_event

@on_event(EventType.STREAM_STARTED, EventType.STREAM_STOPPED)
async def stream_status(ctx: EventContext[Event]) -> None:
    if isinstance(ctx.event, StreamStartedEvent):
        await ctx.owncast_client.send_system_message("We're live!")
    else:
        await ctx.owncast_client.send_system_message("Stream ended.")

When listening to a single event type, use the specific type in the annotation (e.g., EventContext[ChatEvent]). When listening to multiple types, use the Event union type instead (e.g., EventContext[Event]).

Priority

Handlers execute sequentially in priority order. Higher priority values run first:

Constant Value Intended Use
Priority.HIGHEST 100 Filters, rate limiting, authentication.
Priority.HIGH 75 Moderation, logging.
Priority.NORMAL 50 Default. Most handlers.
Priority.LOW 25 Reactions, notifications.
Priority.LOWEST 0 Stats collection, cleanup.
from owlbot.api import on_event, EventContext, EventType, ChatEvent, Priority

@on_event(EventType.CHAT, priority=Priority.HIGHEST)
async def spam_filter(ctx: EventContext[ChatEvent]) -> None:
    if "buy cheap" in ctx.event.raw_body.lower():
        await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False)

Any integer value works for fine-grained control; the named constants are just convenient landmarks. Handlers at the same priority run in registration order.

Propagation Control

Any event handler can stop the event from reaching remaining handlers by calling ctx.stop_propagation(). Building on the spam filter example above, hiding the message alone still allows other handlers and commands to process it. Adding stop_propagation() prevents that:

@on_event(EventType.CHAT, priority=Priority.HIGHEST)
async def spam_filter(ctx: EventContext[ChatEvent]) -> None:
    if "buy cheap" in ctx.event.raw_body.lower():
        await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False)
        ctx.stop_propagation("Spam detected")

Once called:

  • No further event handlers will be invoked for this event.
  • For CHAT events, command dispatch is also skipped, so if the spam message happened to start with !, no command will be executed.

The current state is available via ctx.propagation_stopped (returns bool). The reason string is optional and only used for debug logging.

Dynamic Registration

Handlers can be registered and unregistered at runtime through ctx.events:

from owlbot.api import ModuleContext, EventContext, EventType, ChatEvent, Priority, on_setup

async def my_dynamic_handler(ctx: EventContext[ChatEvent]) -> None:
    await ctx.owncast_client.send_message("Dynamic handler fired!")

@on_setup
async def setup(ctx: ModuleContext) -> None:
    ctx.events.register(
        handler=my_dynamic_handler,
        event_types=(EventType.CHAT,),
        priority=Priority.NORMAL,
    )

Available Methods

Method Description
ctx.events.register(handler, event_types, priority=...) Register a handler for one or more event types. The module name is automatically supplied.
ctx.events.unregister(handler) Remove a handler from all event types. Only works for handlers owned by the calling module.
ctx.events.get(event_type) Get handlers registered by this module for a specific event type.
ctx.events.module_events Property: all handlers registered by this module, grouped by event type.

Event Types

Each event type maps to a dataclass:

EventType Dataclass Description
EventType.CHAT ChatEvent A chat message was sent.
EventType.USER_JOINED UserJoinedEvent A user connected to chat.
EventType.USER_PARTED UserPartedEvent A user disconnected from chat.
EventType.NAME_CHANGE NameChangedEvent A user changed their display name.
EventType.STREAM_STARTED StreamStartedEvent The stream went live.
EventType.STREAM_STOPPED StreamStoppedEvent The stream went offline.
EventType.STREAM_TITLE_UPDATED StreamTitleUpdatedEvent The stream title was changed.
EventType.VISIBILITY_UPDATE VisibilityUpdateEvent Message visibility changed (moderation action).

Event Dataclass Fields

ChatEvent

Field Type Description
user User The user who sent the message.
client_id int Numeric client ID.
body str Rendered HTML from Owncast (markdown converted, custom emotes as <img> tags, outer <p> wrapper stripped).
raw_body str Original user input before Owncast rendering.
message_id str Unique message identifier.
is_visible bool Whether the message is visible.
timestamp datetime | None When the message was sent.

UserJoinedEvent / UserPartedEvent

Field Type Description
user User The user who joined/parted.
client_id int Numeric client ID.
event_id str Unique event identifier.
timestamp datetime | None When the event occurred.

NameChangedEvent

Field Type Description
user User The user after the rename. user.display_name already reflects the new display name.
client_id int Numeric client ID.
new_name str The new display name.
event_id str Unique event identifier.
timestamp datetime | None When the name change occurred.

StreamStartedEvent / StreamStoppedEvent

Field Type Description
server_id str Server identifier.
server_name str Server name.
stream_title str Current stream title.
summary str Server summary.
timestamp datetime | None When the event occurred.

StreamTitleUpdatedEvent

Field Type Description
server_id str Server identifier.
server_name str Server name.
stream_title str The new stream title.
summary str Server summary.
status StreamStatus | None Current stream status snapshot.
timestamp datetime | None When the title was updated.

VisibilityUpdateEvent

Field Type Description
event_id str Unique event identifier.
message_ids list[str] IDs of the affected messages.
is_visible bool New visibility state.
timestamp datetime | None When the visibility change occurred.

User

User appears in most events and carries identity and permission data:

Attribute Type Description
id str Unique user identifier.
display_name str Current display name.
display_color int User's chat color.
created_at datetime | None Account creation time.
previous_names list[str] Historical display names.
name_changed_at datetime | None When the name was last changed.
is_bot bool Whether this is a bot account.
is_authenticated bool Whether the user is authenticated.
scopes list[str] Permission scopes (e.g., ["MODERATOR"]).
is_moderator bool Whether the user has moderator privileges (derived from scopes).

StreamStatus

StreamStatus is included in StreamTitleUpdatedEvent and provides a snapshot of the stream state:

Field Type Description
last_connect_time datetime | None When the stream last connected.
last_disconnect_time datetime | None When the stream last disconnected.
version_number str Owncast server version.
stream_title str Current stream title.
viewer_count int Current number of viewers.
overall_max_viewer_count int All-time peak viewer count.
session_max_viewer_count int Peak viewer count for the current session.
is_online bool Whether the stream is currently live.