From 9a6f4ae3660405baffb023951cc3636d25ef04f0 Mon Sep 17 00:00:00 2001 From: Styler Date: Tue, 3 Nov 2026 11:54:57 +0100 Subject: [PATCH] Document the channel.chat topic Add a page for channel.chat, the topic that sends chat from every platform in one format with deletes, timeouts and bans. List it on the topics overview and in the sidebar, point to it from the Chat Message page, and add a changelog entry that also covers channel.chat.message now sending each platform's data unchanged. --- astro.config.mjs | 1 + .../2026-11-03-channel-chat-topic.mdx | 14 ++ .../topics/channel-chat-message.mdx | 4 + .../docs/websockets/topics/channel-chat.mdx | 201 ++++++++++++++++++ src/content/docs/websockets/topics/index.mdx | 3 +- 5 files changed, 222 insertions(+), 1 deletion(-) create mode 100644 src/content/changelog/websockets/2026-11-03-channel-chat-topic.mdx create mode 100644 src/content/docs/websockets/topics/channel-chat.mdx diff --git a/astro.config.mjs b/astro.config.mjs index 98bca34..05509f2 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -375,6 +375,7 @@ export default defineConfig({ collapsed: true, items: [ 'websockets/topics/channel-activities', + 'websockets/topics/channel-chat', 'websockets/topics/channel-chat-message', 'websockets/topics/channel-tips', 'websockets/topics/channel-tips-moderation', diff --git a/src/content/changelog/websockets/2026-11-03-channel-chat-topic.mdx b/src/content/changelog/websockets/2026-11-03-channel-chat-topic.mdx new file mode 100644 index 0000000..cfb9ffa --- /dev/null +++ b/src/content/changelog/websockets/2026-11-03-channel-chat-topic.mdx @@ -0,0 +1,14 @@ +--- +title: One chat format for every platform +description: The new channel.chat topic sends chat from Twitch, YouTube, Kick and X in one format, with deletes, timeouts and bans. +date: 2026-11-03 +products: [websockets] +--- + +The new [`channel.chat`](/websockets/topics/channel-chat) topic sends a channel's chat in one format, whichever platform it comes from. + +- **Messages come in fragments.** Text, emotes, mentions and cheers arrive in order, so a widget can draw chat without knowing the platform. +- **Moderation events.** Deletes, timeouts, bans and chat clears arrive on the same topic, for the platforms that send them. +- **Roles.** Authors carry a fixed list of roles, such as `moderator` and `subscriber`, so "moderators only" works the same on every platform. + +[`channel.chat.message`](/websockets/topics/channel-chat-message) now sends each platform's `data` exactly as the platform sent it. Fields we used to drop now come through, and fields we used to fill with empty values, such as an empty Kick `created_at`, no longer appear. diff --git a/src/content/docs/websockets/topics/channel-chat-message.mdx b/src/content/docs/websockets/topics/channel-chat-message.mdx index 00a0b8f..6fcd7a6 100644 --- a/src/content/docs/websockets/topics/channel-chat-message.mdx +++ b/src/content/docs/websockets/topics/channel-chat-message.mdx @@ -19,6 +19,10 @@ Subscribe to this topic to receive updates whenever a new chat message is sent. Payloads are delivered in the raw format of the originating provider, so the structure of the `data` object differs per platform. ::: +:::tip +To draw chat without handling each platform, use [`channel.chat`](/websockets/topics/channel-chat). It sends every platform's messages in one format, plus deletes, timeouts and bans. +::: + ## Examples diff --git a/src/content/docs/websockets/topics/channel-chat.mdx b/src/content/docs/websockets/topics/channel-chat.mdx new file mode 100644 index 0000000..3563dfb --- /dev/null +++ b/src/content/docs/websockets/topics/channel-chat.mdx @@ -0,0 +1,201 @@ +--- +title: Chat +description: "Chat messages and moderation events from every platform, in one format" +wsTopic: 'channel.chat' +scope: 'overlays:read' +keywords: +- chat +- chat message +- chat moderation +- emotes +- websocket events +--- + +import { Tabs, TabItem } from '@astrojs/starlight/components'; + +The `channel.chat` topic carries a channel's chat in one format, whichever platform it comes from: Twitch, YouTube, Kick or X. +Each message comes split into fragments (text, emotes, mentions, cheers), so you can draw it without knowing the platform. +The same topic tells you when messages are deleted, when a user is timed out or banned, and when the whole chat is cleared. + +:::tip +Need the platform's own payload instead? [`channel.chat.message`](/websockets/topics/channel-chat-message) sends it unchanged. +::: + +## Events + +Every event has an `event` field. A renderer needs these four to draw chat and remove what moderators remove. + +| `event` | Meaning | Its own fields | +| --- | --- | --- | +| `message` | A chat message | See [Message](#message) | +| `message_deleted` | One message was removed | `message_id` | +| `user_cleared` | All of one user's messages were removed, for a timeout or a ban | `user` (`id`, `login`, `name`), `duration_seconds` (left out when the ban is permanent or the length is unknown) | +| `chat_cleared` | All messages were removed | none | + +Every event also has these fields: + +| Field | Type | Notes | +| --- | --- | --- | +| `v` | number | Format version, `1` | +| `event` | string | One of the four above | +| `provider` | string | `twitch`, `youtube`, `kick` or `x` | +| `channel` | object | `id` (StreamElements channel ID), `provider_id` (the broadcaster's ID on the platform), `login` | +| `sent_at` | string | When the platform says the event happened, in UTC. When it doesn't say, the time StreamElements received it. | + +### Rules for readers + +- Ignore fields you don't know, and events you don't know. +- Draw a fragment of a type you don't know as its `text`. +- A field we have no value for is left out. It is never `""` or `null`. +- Events can arrive slightly out of order, so a `message_deleted` can, rarely, come before its message. Keep the last few hundred deleted IDs and drop a message whose `id` is among them. + +## Message + +| Field | Type | Notes | +| --- | --- | --- | +| `id` | string | The platform's message ID. Delete events refer to this ID. | +| `author` | object | See [Authors](#authors) | +| `text` | string | The message as shown in chat. Platform emote codes become emote names, and Twitch's `/me` wrapper and invisible padding characters are removed. | +| `fragments` | array | See [Fragments](#fragments). Joined in order, their `text` equals `text`. | +| `kind` | string | `text`, `action` (sent with `/me`), `highlighted`, `intro`, `effect` or `gigantified_emote`. Draw a kind you don't know as `text`. | +| `reply` | object | The message this one answers: `message_id`, `author` (`id`, `login`, `name`), `text` | +| `shared_from` | object | Twitch shared chat only: `provider_id` of the channel the message was sent in | + +### Fragments + +Draw a message by walking `fragments` in order. The simplest renderer joins every `text` and is done. + +| `type` | Extra field | Notes | +| --- | --- | --- | +| `text` | none | Plain text. Escape it before you put it in HTML. | +| `emote` | `emote`: `id` | The emote's name is the fragment's `text`, and its platform is the event's `provider`. | +| `mention` | `mention`: `id`, `login`, `name` | `id` is there only when the platform sends it (Twitch). | +| `cheer` | `cheer`: `prefix`, `bits`, `tier` | Twitch only. A message's total bits is the sum of its cheers. | + +### Emote images + +Emotes carry an ID, not a URL. To find the image: + +1. Fetch `GET https://api.streamelements.com/kappa/v3/emotes/{channel}` once when your widget loads, where `{channel}` is the StreamElements channel ID. It lists the channel's and the global emotes for each platform, with `urls` by size. Look the emote up by platform and `id`. +2. If it isn't there (another channel's Twitch sub emote, or one added after you loaded), build the URL from the ID: + + | Platform | URL | + | --- | --- | + | `twitch` | `https://static-cdn.jtvnw.net/emoticons/v2/{id}/default/{dark or light}/{1.0, 2.0 or 3.0}` | + | `kick` | `https://files.kick.com/emotes/{id}/fullsize` | + +3. If that fails too, draw the fragment's `text`. + +YouTube emoji such as `:face-blue-smiling:` arrive as text for now. + +## Authors + +`author` has `id`, `login`, `name`, `color`, `avatar_url`, `roles` and `badges`. +Use `roles` to decide what to do (for example, show only moderators) and `badges` to draw. + +`roles` uses this fixed list, so you can check them without knowing the platform: `broadcaster`, `moderator`, `vip`, `subscriber` (YouTube members count), `founder`, `verified`, `staff`, `bot`. + +Each badge has `id`, `version` and `label` (for example, months subscribed). YouTube and X have no badges, only roles. + +## Moderation events by platform + +| `event` | Twitch | YouTube | Kick | X | +| --- | --- | --- | --- | --- | +| `message_deleted` | Yes | No (YouTube doesn't send live deletes) | No | Yes | +| `user_cleared` | Yes, with no length | Yes, with the length of a timeout | Yes, with the length of a timeout | No | +| `chat_cleared` | Yes | No | No | No | + +## Examples + + + + +```json +{ + "id": "01JY3ZPEVXTM7RZ0X7FXP1STCT", + "ts": "2026-11-03T11:37:58Z", + "type": "message", + "topic": "channel.chat", + "room": "5f2de5dd9a474a2c2aaaaaaa", + "data": { + "v": 1, + "event": "message", + "provider": "twitch", + "channel": { "id": "5f2de5dd9a474a2c2aaaaaaa", "provider_id": "28050000", "login": "examplechannel" }, + "sent_at": "2026-11-03T11:37:58Z", + "id": "caf9fb9a-a702-486e-92f1-0c60a3a28f38", + "author": { + "id": "28057703", + "login": "exampleuser", + "name": "ExampleUser", + "color": "#8AE020", + "roles": ["moderator", "subscriber", "founder"], + "badges": [ + { "id": "moderator", "version": "1" }, + { "id": "founder", "version": "0", "label": "55" } + ] + }, + "text": "@bob nice <3 cheer100", + "fragments": [ + { "type": "mention", "text": "@bob", "mention": { "id": "123", "login": "bob", "name": "Bob" } }, + { "type": "text", "text": " nice " }, + { "type": "emote", "text": "<3", "emote": { "id": "555555584" } }, + { "type": "text", "text": " " }, + { "type": "cheer", "text": "cheer100", "cheer": { "prefix": "cheer", "bits": 100, "tier": 100 } } + ], + "kind": "text" + } +} +``` + + + + +```json +{ + "v": 1, + "event": "message_deleted", + "provider": "twitch", + "channel": { "id": "5f2de5dd9a474a2c2aaaaaaa", "provider_id": "28050000", "login": "examplechannel" }, + "sent_at": "2026-11-03T11:38:10Z", + "message_id": "caf9fb9a-a702-486e-92f1-0c60a3a28f38" +} +``` + + + + +```json +{ + "v": 1, + "event": "user_cleared", + "provider": "kick", + "channel": { "id": "67a36a1f8a2abf0b4aaaaaaa", "provider_id": "54670000", "login": "examplechannel" }, + "sent_at": "2026-11-03T11:59:00Z", + "user": { "id": "54673333", "login": "spammer", "name": "Spammer" }, + "duration_seconds": 600 +} +``` + + + + +```json +{ + "v": 1, + "event": "chat_cleared", + "provider": "twitch", + "channel": { "id": "5f2de5dd9a474a2c2aaaaaaa", "provider_id": "28050000", "login": "examplechannel" }, + "sent_at": "2026-11-03T12:00:00Z" +} +``` + + + + +The last three tabs show only the `data` field. + +## Related + +- [Chat Message](/websockets/topics/channel-chat-message) - The platform's own payload for each chat message +- [Websockets](/websockets) - General information about the Astro Websocket Gateway diff --git a/src/content/docs/websockets/topics/index.mdx b/src/content/docs/websockets/topics/index.mdx index a4c73f7..bfaea50 100644 --- a/src/content/docs/websockets/topics/index.mdx +++ b/src/content/docs/websockets/topics/index.mdx @@ -39,7 +39,8 @@ Unlinked topics are valid for subscription but are not yet documented. Topics ma | [channel.activities](/websockets/topics/channel-activities) | `activities:read` | Channel activities like follows, subscriptions, and donations | | `channel.announcements` | `channel:read` | Channel announcement events | | `channel.chatstats` | *(none)* | Chat statistics updates | -| [channel.chat.message](/websockets/topics/channel-chat-message) | `overlays:read` | Individual chat messages | +| [channel.chat](/websockets/topics/channel-chat) | `overlays:read` | Chat messages and moderation events from every platform, in one format | +| [channel.chat.message](/websockets/topics/channel-chat-message) | `overlays:read` | Individual chat messages, as the platform sent them | | [channel.tips](/websockets/topics/channel-tips) | `tips:read` | Tip/donation events | | [channel.tips.moderation](/websockets/topics/channel-tips-moderation) | `tips:moderation` | Tip moderation events | | [channel.contests](/websockets/topics/channel-contests) | *(none)* | Contest and prediction events |