Files
LogalDeveloper c93ffc551d
CI / Formatting (push) Successful in 4s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 7s
CI / Tests (Python 3.13) (push) Successful in 7s
CI / Tests (Python 3.14) (push) Successful in 6s
CI / Type Checking (push) Successful in 7s
CI / Spelling (push) Successful in 4s
Delayed chat archive finalization after stream stops.
2026-05-27 11:19:12 -04:00
..

chat_archive

This package contains an Owlbot module that writes Owncast chat activity and stream context updates to JSON files for later processing.

Behavior

An archive is the JSON file this module writes for one live stream. The module starts an archive when Owlbot sees a live stream, including streams that are already live when the module loads.

While an archive is active, the module records chat messages, user joins, user parts, name changes, stream title updates, and message visibility updates in the order Owlbot dispatches them. Events outside an active stream are ignored.

Archive files are stream-scoped. When a stream ends, the module keeps that stream's archive open for a five-minute grace period so it can capture chat that Owncast still accepts after the stream stops. If no new stream starts during that window, the archive is finalized when the grace period expires. If a new stream starts first, the stopped stream's archive is finalized immediately and a new archive is opened for the new stream. Module unload also finalizes the active archive.

Configuration

modules:
  chat_archive:
    enabled: true
    archive_dir: "data/chat_archives"
    pretty_json: true
  • archive_dir: directory where archive files are written. The module creates it if needed.
  • pretty_json: when true, write indented JSON. When false, write compact JSON.

Archive Format

Archive filenames are based on the detected stream start time when available:

chat-archive-20260526T120000Z.json

If a file already exists for that timestamp, the module adds a suffix such as chat-archive-20260526T120000Z-2.json.

Each archive is a single JSON document with these top-level fields:

  • archive_start_reason: one of module_startup or stream_started_event.
  • archive_started_at: ISO 8601 timestamp for when the archive file was started.
  • archive_updated_at: ISO 8601 timestamp for the most recent archive write.
  • archive_stopped_at: ISO 8601 timestamp for when the archive was finalized, or null while the archive is active.
  • archive_stop_reason: reason the archive was finalized, or null while the archive is active or waiting through the post-stream grace period. Current values are stream_stopped_event, module_teardown, and replaced_by_stream_started_event.
  • stream_title: stream title when the archive started, or null if unavailable.
  • stream_stopped_at: stream stop timestamp recorded when Owlbot receives a stream stop event, or null otherwise.
  • event_count: number of objects in events.
  • events: ordered list of captured activity events.

Each object in events contains:

  • event_type: one of CHAT, USER_JOINED, USER_PARTED, NAME_CHANGE, STREAM_TITLE_UPDATED, or VISIBILITY-UPDATE.
  • archived_at: ISO 8601 timestamp for when the event was written.
  • event_data: parsed Owlbot event data converted to JSON-compatible values. The exact fields depend on event_type.

Abridged example archive:

{
  "archive_start_reason": "stream_started_event",
  "archive_started_at": "2026-05-26T12:00:01.000000+00:00",
  "archive_updated_at": "2026-05-26T12:01:00.000000+00:00",
  "archive_stopped_at": null,
  "archive_stop_reason": null,
  "stream_title": "Game Night",
  "stream_stopped_at": null,
  "event_count": 1,
  "events": [
    {
      "event_type": "CHAT",
      "archived_at": "2026-05-26T12:01:00.000000+00:00",
      "event_data": {
        "message_id": "msg-1",
        "raw_body": "hello chat",
        "body": "hello chat",
        "is_visible": true
      }
    }
  ]
}

Owlbot modules receive parsed event dataclasses rather than the untouched Owncast webhook payload. event_data stores that parsed event data after converting values such as datetimes and sets into JSON-compatible values.

The archive is rewritten after each captured event through a temporary sibling file and atomic replace. For an existing archive file, readers should see either the previous complete JSON document or the next complete JSON document.