Clarified body vs raw_body distinction and documented HTML escaping on send methods.

2026-03-26 19:35:58 -04:00
parent 458065420a
commit 8f83416203
3 changed files with 15 additions and 10 deletions
+1 -1
@@ -87,7 +87,7 @@ from owlbot.api import EventContext, EventType, ChatEvent, on_event
@on_event(EventType.CHAT) @on_event(EventType.CHAT)
async def on_chat(ctx: EventContext[ChatEvent]) -> None: async def on_chat(ctx: EventContext[ChatEvent]) -> None:
# The event itself: # The event itself:
message = ctx.event.body message = ctx.event.raw_body
user = ctx.event.user.display_name user = ctx.event.user.display_name
# Services (proxied from ModuleContext): # Services (proxied from ModuleContext):
+5 -5
@@ -13,7 +13,7 @@ from owlbot.api import EventContext, EventType, ChatEvent, on_event
@on_event(EventType.CHAT) @on_event(EventType.CHAT)
async def keyword_alert(ctx: EventContext[ChatEvent]) -> None: async def keyword_alert(ctx: EventContext[ChatEvent]) -> None:
if "help" in ctx.event.body.lower(): if "help" in ctx.event.raw_body.lower():
await ctx.owncast_client.send_system_message("A moderator will be with you shortly!") await ctx.owncast_client.send_system_message("A moderator will be with you shortly!")
``` ```
@@ -49,7 +49,7 @@ from owlbot.api import on_event, EventContext, EventType, ChatEvent, Priority
@on_event(EventType.CHAT, priority=Priority.HIGHEST) @on_event(EventType.CHAT, priority=Priority.HIGHEST)
async def spam_filter(ctx: EventContext[ChatEvent]) -> None: async def spam_filter(ctx: EventContext[ChatEvent]) -> None:
if "buy cheap" in ctx.event.body.lower(): if "buy cheap" in ctx.event.raw_body.lower():
await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False) await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False)
``` ```
@@ -62,7 +62,7 @@ Any event handler can stop the event from reaching remaining handlers by calling
```python ```python
@on_event(EventType.CHAT, priority=Priority.HIGHEST) @on_event(EventType.CHAT, priority=Priority.HIGHEST)
async def spam_filter(ctx: EventContext[ChatEvent]) -> None: async def spam_filter(ctx: EventContext[ChatEvent]) -> None:
if "buy cheap" in ctx.event.body.lower(): if "buy cheap" in ctx.event.raw_body.lower():
await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False) await ctx.owncast_client.set_message_visibility([ctx.event.message_id], visible=False)
ctx.stop_propagation("Spam detected") ctx.stop_propagation("Spam detected")
``` ```
@@ -123,8 +123,8 @@ Each event type maps to a dataclass:
|-------|------|-------------| |-------|------|-------------|
| `user` | `User` | The user who sent the message. | | `user` | `User` | The user who sent the message. |
| `client_id` | `int` | Numeric client ID. | | `client_id` | `int` | Numeric client ID. |
| `body` | `str` | Message text (HTML `<p>` tags stripped). | | `body` | `str` | Rendered HTML from Owncast (markdown converted, custom emotes as `<img>` tags, outer `<p>` wrapper stripped). |
| `raw_body` | `str` | Original unprocessed message body. | | `raw_body` | `str` | Original user input before Owncast rendering. |
| `message_id` | `str` | Unique message identifier. | | `message_id` | `str` | Unique message identifier. |
| `is_visible` | `bool` | Whether the message is visible. | | `is_visible` | `bool` | Whether the message is visible. |
| `timestamp` | `datetime \| None` | When the message was sent. | | `timestamp` | `datetime \| None` | When the message was sent. |
+9 -4
@@ -8,6 +8,8 @@ This is always available. It uses Bearer token authentication (the `access_token
### Sending Messages ### Sending Messages
All message-sending methods HTML-escape the body by default to prevent injection when echoing user-originated content. Markdown syntax is unaffected by escaping and will be rendered normally by Owncast. Pass `unsanitized=True` to send raw HTML intentionally.
```python ```python
# Regular chat message (visible to everyone): # Regular chat message (visible to everyone):
await ctx.owncast_client.send_message("Hello, chat!") await ctx.owncast_client.send_message("Hello, chat!")
@@ -23,6 +25,9 @@ await ctx.owncast_client.send_system_message_to_client(
client_id=event.client_id, client_id=event.client_id,
body="Only this client can see this." body="Only this client can see this."
) )
# Send raw HTML (opt out of escaping):
await ctx.owncast_client.send_message("<b>bold html</b>", unsanitized=True)
``` ```
### Moderation ### Moderation
@@ -63,10 +68,10 @@ clients = await ctx.owncast_client.get_connected_clients()
| Method | Description | | Method | Description |
|--------|-------------| |--------|-------------|
| `base_url` | Property: the Owncast server base URL. | | `base_url` | Property: the Owncast server base URL. |
| `send_message(body)` | Send a chat message visible to all. | | `send_message(body, *, unsanitized=False)` | Send a chat message visible to all. Body is HTML-escaped unless `unsanitized=True`. |
| `send_system_message(body)` | Send a system message visible to all. | | `send_system_message(body, *, unsanitized=False)` | Send a system message visible to all. Body is HTML-escaped unless `unsanitized=True`. |
| `send_action(body)` | Send an action message (like IRC `/me`). | | `send_action(body, *, unsanitized=False)` | Send an action message (like IRC `/me`). Body is HTML-escaped unless `unsanitized=True`. |
| `send_system_message_to_client(client_id, body)` | Send a private system message to one client. | | `send_system_message_to_client(client_id, body, *, unsanitized=False)` | Send a private system message to one client. Body is HTML-escaped unless `unsanitized=True`. |
| `set_message_visibility(message_ids, visible)` | Hide or show messages. | | `set_message_visibility(message_ids, visible)` | Hide or show messages. |
| `set_stream_title(title)` | Update the stream title. | | `set_stream_title(title)` | Update the stream title. |
| `get_status()` | Get public server status dict. | | `get_status()` | Get public server status dict. |