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