openvibers/openvibe.contracts Commits

Contracts 0.80.0: chat.moderation.read (plan T3), how Live reads the six chat tables Chat owns: a channel's moderation settings (every channel_moderation_settings column, Live's defaults without a row) and moderator ids, the channels a person moderates, a channel's emote count. Internal, loopback, service token. Result contracts chat.channel-moderation-result@1, chat.moderated-channels-result@1, chat.emote-count-result@1 with fixtures; Chat's manifest lists the capability. 14355 checks; compat vs v0.79.0: no breaking changes.

OP OpenVibers committed 29 Sept 2026 · 5dd7a18aa3 · 46 files, +1457 −51


Changes

ddiff --git a/CHANGELOG.md b/CHANGELOG.md
iindex 199021f9..586227fe 100644
---- a/CHANGELOG.md
++++ b/CHANGELOG.md
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
from `https://codeload.github.com/OpenVibers/OpenVibe.Contracts/tar.gz/refs/tags/<tag>`. Before v0.30.0,
the notes were in the tag and commit messages (`git tag -n1`).
++## 0.80.0 — 2026-09-29
++
++**`chat.moderation.read`: how Live reads the chat tables Chat owns** (plan T3, the six staged tables).
++- New internal capability (owner `chat`, active): `GET /internal/moderation/channels/:channelId` (a channel's moderation
++ settings, every `channel_moderation_settings` column with Live's defaults when there is no row, and its moderator ids),
++ `GET /internal/moderation/users/:userId/channels` (the channels a person moderates) and
++ `GET /internal/moderation/channels/:channelId/emote-count`. Loopback only, service token.
++- Result contracts with fixtures: `chat.channel-moderation-result@1`, `chat.moderated-channels-result@1`,
++ `chat.emote-count-result@1`. Chat's manifest lists the capability.
++
## 0.79.0 — 2026-09-29
**Service-token capabilities for every internal call that still used X-Internal-Key** (plan T2, register C-50–C-58).
ddiff --git a/contracts/catalog.json b/contracts/catalog.json
iindex b92b1095..c1528887 100644
---- a/contracts/catalog.json
++++ b/contracts/catalog.json
@@@ -4484,6 +4484,33 @@
"compatibility": "backward",
"status": "active"
},
++ {
++ "id": "chat.channel-moderation-result",
++ "version": "1.0.0",
++ "owner": "chat",
++ "schema": "chat/channel-moderation-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "chat.moderated-channels-result",
++ "version": "1.0.0",
++ "owner": "chat",
++ "schema": "chat/moderated-channels-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
++ {
++ "id": "chat.emote-count-result",
++ "version": "1.0.0",
++ "owner": "chat",
++ "schema": "chat/emote-count-result.v1.json",
++ "visibility": "first-party",
++ "compatibility": "backward",
++ "status": "active"
++ },
{
"id": "live.moderation.action",
"version": "1.0.0",
ddiff --git a/contracts/chat/channel-moderation-result.v1.json b/contracts/chat/channel-moderation-result.v1.json
nnew file mode 100644
iindex 00000000..fc5fda8d
---- /dev/null
++++ b/contracts/chat/channel-moderation-result.v1.json
@@@ -0,0 +1,227 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/channel-moderation-result.v1.json",
++ "title": "ChatChannelModerationResult",
++ "description": "chat.channel-moderation-result@1: the answer of GET /internal/moderation/channels/:channelId on OpenVibe.Chat (capability chat.moderation.read): { ok: true, settings, moderator_ids } — one channel's moderation settings and the ids of the people who moderate it, for OpenVibe.Live's stream chat. settings carries every channel_moderation_settings column (Live's own defaults when the channel has no row yet, database.js getChannelModerationSettings); the switches are 0/1 as the table stores them. moderator_ids are Live user ids (channel_moderators.user_id), never names. A failure is { ok: false, error }; a caller without the grant gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "settings",
++ "moderator_ids"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "settings": {
++ "$ref": "#/$defs/settings"
++ },
++ "moderator_ids": {
++ "type": "array",
++ "description": "The channel's moderators, oldest first (channel_moderators.created_at); empty when it has none.",
++ "items": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ],
++ "$defs": {
++ "flag": {
++ "type": "integer",
++ "enum": [
++ 0,
++ 1
++ ],
++ "description": "A switch as the SQLite table stores it."
++ },
++ "settings": {
++ "type": "object",
++ "description": "One row of channel_moderation_settings, or Live's defaults for a channel that has no row. Every column is present; the switches are 0/1.",
++ "required": [
++ "channel_id",
++ "slow_mode_seconds",
++ "followers_only",
++ "emote_only",
++ "allow_anonymous",
++ "links_allowed",
++ "gifs_enabled",
++ "account_age_gate_hours",
++ "caps_percentage_limit",
++ "aggressive_filter",
++ "max_message_length",
++ "tts_max_length",
++ "slur_filter_enabled",
++ "slur_filter_use_builtin",
++ "slur_filter_terms",
++ "slur_filter_regexes",
++ "slur_filter_nudge_message",
++ "slur_filter_disabled_categories",
++ "ip_approval_mode",
++ "soundboard_enabled",
++ "soundboard_allow_pitch",
++ "soundboard_allow_speed",
++ "soundboard_banned_ids",
++ "viewer_auto_delete_enabled",
++ "viewer_delete_all_enabled",
++ "custom_emotes_enabled",
++ "custom_sounds_enabled",
++ "max_sound_seconds",
++ "uploads_mods_only",
++ "mods_can_edit_about",
++ "emote_scale",
++ "emote_size_min",
++ "emote_size_max",
++ "sounds_mods_only",
++ "sound_min_speed",
++ "sound_max_speed",
++ "sound_min_pitch_cents",
++ "sound_max_pitch_cents",
++ "sub_only"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "channel_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slow_mode_seconds": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "followers_only": { "$ref": "#/$defs/flag" },
++ "emote_only": { "$ref": "#/$defs/flag" },
++ "allow_anonymous": { "$ref": "#/$defs/flag" },
++ "links_allowed": { "$ref": "#/$defs/flag" },
++ "gifs_enabled": { "$ref": "#/$defs/flag" },
++ "account_age_gate_hours": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "caps_percentage_limit": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Percent of a message that may be capitals; 0 means off."
++ },
++ "aggressive_filter": { "$ref": "#/$defs/flag" },
++ "max_message_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "tts_max_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slur_filter_enabled": { "$ref": "#/$defs/flag" },
++ "slur_filter_use_builtin": { "$ref": "#/$defs/flag" },
++ "slur_filter_terms": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra terms, comma separated; a channel's own filter list, never a network-wide secret."
++ },
++ "slur_filter_regexes": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra patterns, one per line."
++ },
++ "slur_filter_nudge_message": {
++ "type": "string",
++ "maxLength": 512
++ },
++ "slur_filter_disabled_categories": {
++ "type": "string",
++ "description": "A JSON array string of disabled built-in categories ('[]' when none)."
++ },
++ "ip_approval_mode": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 2,
++ "description": "0 off, 1 approve before chatting, 2 approved addresses only."
++ },
++ "soundboard_enabled": { "$ref": "#/$defs/flag" },
++ "soundboard_allow_pitch": { "$ref": "#/$defs/flag" },
++ "soundboard_allow_speed": { "$ref": "#/$defs/flag" },
++ "soundboard_banned_ids": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Banned channel sound ids, comma separated."
++ },
++ "viewer_auto_delete_enabled": { "$ref": "#/$defs/flag" },
++ "viewer_delete_all_enabled": { "$ref": "#/$defs/flag" },
++ "custom_emotes_enabled": { "$ref": "#/$defs/flag" },
++ "custom_sounds_enabled": { "$ref": "#/$defs/flag" },
++ "max_sound_seconds": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "uploads_mods_only": { "$ref": "#/$defs/flag" },
++ "mods_can_edit_about": { "$ref": "#/$defs/flag" },
++ "emote_scale": {
++ "type": "integer",
++ "minimum": 50,
++ "maximum": 300,
++ "description": "Emote display size in chat, percent of the base height (100 is the default)."
++ },
++ "emote_size_min": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "emote_size_max": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "sounds_mods_only": { "$ref": "#/$defs/flag" },
++ "sound_min_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_max_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_min_pitch_cents": {
++ "type": "integer"
++ },
++ "sound_max_pitch_cents": {
++ "type": "integer"
++ },
++ "sub_only": { "$ref": "#/$defs/flag" },
++ "donation_sound_url": {
++ "type": ["string", "null"],
++ "description": "Optional alert sounds set by the streamer; absent or null when unset."
++ },
++ "donation_sound_mime": {
++ "type": ["string", "null"]
++ },
++ "goal_sound_url": {
++ "type": ["string", "null"]
++ },
++ "goal_sound_mime": {
++ "type": ["string", "null"]
++ },
++ "updated_at": {
++ "type": ["string", "null"],
++ "description": "When the row was last written; absent or null when the channel still has its defaults."
++ }
++ }
++ }
++ }
++}
ddiff --git a/contracts/chat/emote-count-result.v1.json b/contracts/chat/emote-count-result.v1.json
nnew file mode 100644
iindex 00000000..e7c97e88
---- /dev/null
++++ b/contracts/chat/emote-count-result.v1.json
@@@ -0,0 +1,39 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/emote-count-result.v1.json",
++ "title": "ChatEmoteCountResult",
++ "description": "chat.emote-count-result@1: the answer of GET /internal/moderation/channels/:channelId/emote-count on OpenVibe.Chat (capability chat.moderation.read): { ok: true, count } — how many custom emotes a channel holds (the emotes rows with that channel as their owner), so a stream chat can tell an owner whether their uploads landed and show a mod panel a count without shipping every emote. Counts only, never the emotes themselves. A failure is { ok: false, error }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "count"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/contracts/chat/moderated-channels-result.v1.json b/contracts/chat/moderated-channels-result.v1.json
nnew file mode 100644
iindex 00000000..45252e95
---- /dev/null
++++ b/contracts/chat/moderated-channels-result.v1.json
@@@ -0,0 +1,63 @@
++{
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/moderated-channels-result.v1.json",
++ "title": "ChatModeratedChannelsResult",
++ "description": "chat.moderated-channels-result@1: the answer of GET /internal/moderation/users/:userId/channels on OpenVibe.Chat (capability chat.moderation.read): { ok: true, channels } — the channels one Live user moderates (channel_moderators rows joined with their channels, database.js getChannelsByModerator), so a stream chat can answer 'may I moderate' without asking per message. Empty when the person moderates none. A failure is { ok: false, error }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "channels"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "channels": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "channel_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "channel_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "title": {
++ "type": ["string", "null"],
++ "description": "The channel's title, for a label; never required to decide."
++ },
++ "owner_user_id": {
++ "type": ["integer", "null"],
++ "description": "The channel owner's Live user id (channels.user_id)."
++ },
++ "owner_username": {
++ "type": ["string", "null"],
++ "description": "The owner's handle; a public name, never a profile."
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++}
ddiff --git a/fixtures/chat.channel-moderation-result/invalid/no-columns.json b/fixtures/chat.channel-moderation-result/invalid/no-columns.json
nnew file mode 100644
iindex 00000000..9dc756bb
---- /dev/null
++++ b/fixtures/chat.channel-moderation-result/invalid/no-columns.json
@@@ -0,0 +1 @@
++{ "ok": true, "settings": {}, "moderator_ids": [] }
ddiff --git a/fixtures/chat.channel-moderation-result/invalid/not-a-switch.json b/fixtures/chat.channel-moderation-result/invalid/not-a-switch.json
nnew file mode 100644
iindex 00000000..ff6a3dd1
---- /dev/null
++++ b/fixtures/chat.channel-moderation-result/invalid/not-a-switch.json
@@@ -0,0 +1 @@
++{ "ok": true, "settings": { "channel_id": 41, "slow_mode_seconds": 0, "followers_only": 2 }, "moderator_ids": [] }
ddiff --git a/fixtures/chat.channel-moderation-result/valid/defaults.json b/fixtures/chat.channel-moderation-result/valid/defaults.json
nnew file mode 100644
iindex 00000000..71f627c0
---- /dev/null
++++ b/fixtures/chat.channel-moderation-result/valid/defaults.json
@@@ -0,0 +1,45 @@
++{
++ "ok": true,
++ "settings": {
++ "channel_id": 88,
++ "slow_mode_seconds": 0,
++ "followers_only": 0,
++ "emote_only": 0,
++ "allow_anonymous": 1,
++ "links_allowed": 1,
++ "gifs_enabled": 1,
++ "account_age_gate_hours": 0,
++ "caps_percentage_limit": 0,
++ "aggressive_filter": 0,
++ "max_message_length": 500,
++ "tts_max_length": 200,
++ "slur_filter_enabled": 0,
++ "slur_filter_use_builtin": 1,
++ "slur_filter_terms": "",
++ "slur_filter_regexes": "",
++ "slur_filter_nudge_message": "",
++ "slur_filter_disabled_categories": "[]",
++ "ip_approval_mode": 0,
++ "soundboard_enabled": 1,
++ "soundboard_allow_pitch": 1,
++ "soundboard_allow_speed": 1,
++ "soundboard_banned_ids": "",
++ "viewer_auto_delete_enabled": 1,
++ "viewer_delete_all_enabled": 1,
++ "custom_emotes_enabled": 1,
++ "custom_sounds_enabled": 1,
++ "max_sound_seconds": 10,
++ "uploads_mods_only": 0,
++ "mods_can_edit_about": 0,
++ "emote_scale": 100,
++ "emote_size_min": 50,
++ "emote_size_max": 200,
++ "sounds_mods_only": 0,
++ "sound_min_speed": 0.5,
++ "sound_max_speed": 3.0,
++ "sound_min_pitch_cents": -1200,
++ "sound_max_pitch_cents": 1200,
++ "sub_only": 0
++ },
++ "moderator_ids": []
++}
ddiff --git a/fixtures/chat.channel-moderation-result/valid/error.json b/fixtures/chat.channel-moderation-result/valid/error.json
nnew file mode 100644
iindex 00000000..8a2208ac
---- /dev/null
++++ b/fixtures/chat.channel-moderation-result/valid/error.json
@@@ -0,0 +1 @@
++{ "ok": false, "error": "channel not found" }
ddiff --git a/fixtures/chat.channel-moderation-result/valid/tuned.json b/fixtures/chat.channel-moderation-result/valid/tuned.json
nnew file mode 100644
iindex 00000000..609c081b
---- /dev/null
++++ b/fixtures/chat.channel-moderation-result/valid/tuned.json
@@@ -0,0 +1,50 @@
++{
++ "ok": true,
++ "settings": {
++ "channel_id": 41,
++ "slow_mode_seconds": 30,
++ "followers_only": 1,
++ "emote_only": 0,
++ "allow_anonymous": 1,
++ "links_allowed": 0,
++ "gifs_enabled": 1,
++ "account_age_gate_hours": 24,
++ "caps_percentage_limit": 40,
++ "aggressive_filter": 0,
++ "max_message_length": 400,
++ "tts_max_length": 200,
++ "slur_filter_enabled": 1,
++ "slur_filter_use_builtin": 1,
++ "slur_filter_terms": "badword,otherword",
++ "slur_filter_regexes": "^https?://",
++ "slur_filter_nudge_message": "Keep it kind here.",
++ "slur_filter_disabled_categories": "[\"slurs\"]",
++ "ip_approval_mode": 1,
++ "soundboard_enabled": 1,
++ "soundboard_allow_pitch": 1,
++ "soundboard_allow_speed": 1,
++ "soundboard_banned_ids": "3,9",
++ "viewer_auto_delete_enabled": 1,
++ "viewer_delete_all_enabled": 0,
++ "custom_emotes_enabled": 1,
++ "custom_sounds_enabled": 1,
++ "max_sound_seconds": 10,
++ "uploads_mods_only": 1,
++ "mods_can_edit_about": 0,
++ "emote_scale": 120,
++ "emote_size_min": 50,
++ "emote_size_max": 200,
++ "sounds_mods_only": 0,
++ "sound_min_speed": 0.5,
++ "sound_max_speed": 3.0,
++ "sound_min_pitch_cents": -1200,
++ "sound_max_pitch_cents": 1200,
++ "sub_only": 0,
++ "donation_sound_url": "sounds/don.mp3",
++ "donation_sound_mime": "audio/mpeg",
++ "goal_sound_url": null,
++ "goal_sound_mime": null,
++ "updated_at": "2026-09-27T18:04:11Z"
++ },
++ "moderator_ids": [12, 19]
++}
ddiff --git a/fixtures/chat.emote-count-result/invalid/no-count.json b/fixtures/chat.emote-count-result/invalid/no-count.json
nnew file mode 100644
iindex 00000000..ba5677b8
---- /dev/null
++++ b/fixtures/chat.emote-count-result/invalid/no-count.json
@@@ -0,0 +1 @@
++{ "ok": true }
ddiff --git a/fixtures/chat.emote-count-result/valid/error.json b/fixtures/chat.emote-count-result/valid/error.json
nnew file mode 100644
iindex 00000000..a7ac7028
---- /dev/null
++++ b/fixtures/chat.emote-count-result/valid/error.json
@@@ -0,0 +1 @@
++{ "ok": false, "error": "chat store unavailable" }
ddiff --git a/fixtures/chat.emote-count-result/valid/many.json b/fixtures/chat.emote-count-result/valid/many.json
nnew file mode 100644
iindex 00000000..cd7b514f
---- /dev/null
++++ b/fixtures/chat.emote-count-result/valid/many.json
@@@ -0,0 +1 @@
++{ "ok": true, "count": 12 }
ddiff --git a/fixtures/chat.moderated-channels-result/invalid/no-channel-id.json b/fixtures/chat.moderated-channels-result/invalid/no-channel-id.json
nnew file mode 100644
iindex 00000000..cf709e2e
---- /dev/null
++++ b/fixtures/chat.moderated-channels-result/invalid/no-channel-id.json
@@@ -0,0 +1 @@
++{ "ok": true, "channels": [{ "title": "Late Night D&D" }] }
ddiff --git a/fixtures/chat.moderated-channels-result/valid/none.json b/fixtures/chat.moderated-channels-result/valid/none.json
nnew file mode 100644
iindex 00000000..b3b57578
---- /dev/null
++++ b/fixtures/chat.moderated-channels-result/valid/none.json
@@@ -0,0 +1 @@
++{ "ok": true, "channels": [] }
ddiff --git a/fixtures/chat.moderated-channels-result/valid/two-channels.json b/fixtures/chat.moderated-channels-result/valid/two-channels.json
nnew file mode 100644
iindex 00000000..5b0af376
---- /dev/null
++++ b/fixtures/chat.moderated-channels-result/valid/two-channels.json
@@@ -0,0 +1 @@
++{ "ok": true, "channels": [{ "channel_id": 41, "title": "Late Night D&D", "owner_user_id": 7, "owner_username": "mapmaker" }, { "channel_id": 88, "title": null, "owner_user_id": 7, "owner_username": null }] }
ddiff --git a/generated/json-schema/bundle.json b/generated/json-schema/bundle.json
iindex be85a1dd..aaf8c332 100644
---- a/generated/json-schema/bundle.json
++++ b/generated/json-schema/bundle.json
@@@ -48023,6 +48023,412 @@
]
}
},
++ "chat.channel-moderation-result": {
++ "version": "1.0.0",
++ "owner": "chat",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/channel-moderation-result.v1.json",
++ "title": "ChatChannelModerationResult",
++ "description": "chat.channel-moderation-result@1: the answer of GET /internal/moderation/channels/:channelId on OpenVibe.Chat (capability chat.moderation.read): { ok: true, settings, moderator_ids } — one channel's moderation settings and the ids of the people who moderate it, for OpenVibe.Live's stream chat. settings carries every channel_moderation_settings column (Live's own defaults when the channel has no row yet, database.js getChannelModerationSettings); the switches are 0/1 as the table stores them. moderator_ids are Live user ids (channel_moderators.user_id), never names. A failure is { ok: false, error }; a caller without the grant gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "settings",
++ "moderator_ids"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "settings": {
++ "$ref": "#/$defs/settings"
++ },
++ "moderator_ids": {
++ "type": "array",
++ "description": "The channel's moderators, oldest first (channel_moderators.created_at); empty when it has none.",
++ "items": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ],
++ "$defs": {
++ "flag": {
++ "type": "integer",
++ "enum": [
++ 0,
++ 1
++ ],
++ "description": "A switch as the SQLite table stores it."
++ },
++ "settings": {
++ "type": "object",
++ "description": "One row of channel_moderation_settings, or Live's defaults for a channel that has no row. Every column is present; the switches are 0/1.",
++ "required": [
++ "channel_id",
++ "slow_mode_seconds",
++ "followers_only",
++ "emote_only",
++ "allow_anonymous",
++ "links_allowed",
++ "gifs_enabled",
++ "account_age_gate_hours",
++ "caps_percentage_limit",
++ "aggressive_filter",
++ "max_message_length",
++ "tts_max_length",
++ "slur_filter_enabled",
++ "slur_filter_use_builtin",
++ "slur_filter_terms",
++ "slur_filter_regexes",
++ "slur_filter_nudge_message",
++ "slur_filter_disabled_categories",
++ "ip_approval_mode",
++ "soundboard_enabled",
++ "soundboard_allow_pitch",
++ "soundboard_allow_speed",
++ "soundboard_banned_ids",
++ "viewer_auto_delete_enabled",
++ "viewer_delete_all_enabled",
++ "custom_emotes_enabled",
++ "custom_sounds_enabled",
++ "max_sound_seconds",
++ "uploads_mods_only",
++ "mods_can_edit_about",
++ "emote_scale",
++ "emote_size_min",
++ "emote_size_max",
++ "sounds_mods_only",
++ "sound_min_speed",
++ "sound_max_speed",
++ "sound_min_pitch_cents",
++ "sound_max_pitch_cents",
++ "sub_only"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "channel_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slow_mode_seconds": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "followers_only": {
++ "$ref": "#/$defs/flag"
++ },
++ "emote_only": {
++ "$ref": "#/$defs/flag"
++ },
++ "allow_anonymous": {
++ "$ref": "#/$defs/flag"
++ },
++ "links_allowed": {
++ "$ref": "#/$defs/flag"
++ },
++ "gifs_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "account_age_gate_hours": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "caps_percentage_limit": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Percent of a message that may be capitals; 0 means off."
++ },
++ "aggressive_filter": {
++ "$ref": "#/$defs/flag"
++ },
++ "max_message_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "tts_max_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slur_filter_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "slur_filter_use_builtin": {
++ "$ref": "#/$defs/flag"
++ },
++ "slur_filter_terms": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra terms, comma separated; a channel's own filter list, never a network-wide secret."
++ },
++ "slur_filter_regexes": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra patterns, one per line."
++ },
++ "slur_filter_nudge_message": {
++ "type": "string",
++ "maxLength": 512
++ },
++ "slur_filter_disabled_categories": {
++ "type": "string",
++ "description": "A JSON array string of disabled built-in categories ('[]' when none)."
++ },
++ "ip_approval_mode": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 2,
++ "description": "0 off, 1 approve before chatting, 2 approved addresses only."
++ },
++ "soundboard_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "soundboard_allow_pitch": {
++ "$ref": "#/$defs/flag"
++ },
++ "soundboard_allow_speed": {
++ "$ref": "#/$defs/flag"
++ },
++ "soundboard_banned_ids": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Banned channel sound ids, comma separated."
++ },
++ "viewer_auto_delete_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "viewer_delete_all_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "custom_emotes_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "custom_sounds_enabled": {
++ "$ref": "#/$defs/flag"
++ },
++ "max_sound_seconds": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "uploads_mods_only": {
++ "$ref": "#/$defs/flag"
++ },
++ "mods_can_edit_about": {
++ "$ref": "#/$defs/flag"
++ },
++ "emote_scale": {
++ "type": "integer",
++ "minimum": 50,
++ "maximum": 300,
++ "description": "Emote display size in chat, percent of the base height (100 is the default)."
++ },
++ "emote_size_min": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "emote_size_max": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "sounds_mods_only": {
++ "$ref": "#/$defs/flag"
++ },
++ "sound_min_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_max_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_min_pitch_cents": {
++ "type": "integer"
++ },
++ "sound_max_pitch_cents": {
++ "type": "integer"
++ },
++ "sub_only": {
++ "$ref": "#/$defs/flag"
++ },
++ "donation_sound_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Optional alert sounds set by the streamer; absent or null when unset."
++ },
++ "donation_sound_mime": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "goal_sound_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "goal_sound_mime": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "When the row was last written; absent or null when the channel still has its defaults."
++ }
++ }
++ }
++ }
++ }
++ },
++ "chat.moderated-channels-result": {
++ "version": "1.0.0",
++ "owner": "chat",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/moderated-channels-result.v1.json",
++ "title": "ChatModeratedChannelsResult",
++ "description": "chat.moderated-channels-result@1: the answer of GET /internal/moderation/users/:userId/channels on OpenVibe.Chat (capability chat.moderation.read): { ok: true, channels } — the channels one Live user moderates (channel_moderators rows joined with their channels, database.js getChannelsByModerator), so a stream chat can answer 'may I moderate' without asking per message. Empty when the person moderates none. A failure is { ok: false, error }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "channels"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "channels": {
++ "type": "array",
++ "items": {
++ "type": "object",
++ "required": [
++ "channel_id"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "channel_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "title": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The channel's title, for a label; never required to decide."
++ },
++ "owner_user_id": {
++ "type": [
++ "integer",
++ "null"
++ ],
++ "description": "The channel owner's Live user id (channels.user_id)."
++ },
++ "owner_username": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "The owner's handle; a public name, never a profile."
++ }
++ }
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++ }
++ },
++ "chat.emote-count-result": {
++ "version": "1.0.0",
++ "owner": "chat",
++ "status": "active",
++ "schema": {
++ "$schema": "https://json-schema.org/draft/2020-12/schema",
++ "$id": "https://openvibe.network/contracts/chat/emote-count-result.v1.json",
++ "title": "ChatEmoteCountResult",
++ "description": "chat.emote-count-result@1: the answer of GET /internal/moderation/channels/:channelId/emote-count on OpenVibe.Chat (capability chat.moderation.read): { ok: true, count } — how many custom emotes a channel holds (the emotes rows with that channel as their owner), so a stream chat can tell an owner whether their uploads landed and show a mod panel a count without shipping every emote. Counts only, never the emotes themselves. A failure is { ok: false, error }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "count"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "count": {
++ "type": "integer",
++ "minimum": 0
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ]
++ }
++ },
"live.moderation.action": {
"version": "1.0.0",
"owner": "live",
ddiff --git a/generated/openapi/ai.json b/generated/openapi/ai.json
iindex 3204ff9b..ca9ffa16 100644
---- a/generated/openapi/ai.json
++++ b/generated/openapi/ai.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.AI API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.AI as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/billing.json b/generated/openapi/billing.json
iindex a744118b..86ca9e38 100644
---- a/generated/openapi/billing.json
++++ b/generated/openapi/billing.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Billing API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Billing as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/blog.json b/generated/openapi/blog.json
iindex 17f327a1..b9edd5c4 100644
---- a/generated/openapi/blog.json
++++ b/generated/openapi/blog.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Blog API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Blog as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/chat.json b/generated/openapi/chat.json
iindex 7f49a700..e96d351b 100644
---- a/generated/openapi/chat.json
++++ b/generated/openapi/chat.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Chat API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Chat as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
@@@ -71,10 +71,456 @@
}
}
}
++ },
++ "/internal/moderation/channels/{channelId}": {
++ "get": {
++ "operationId": "get_internal_moderation_channels_channelId",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "channelId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/moderation/channels/{channelId}/emote-count": {
++ "get": {
++ "operationId": "get_internal_moderation_channels_channelId_emote_count",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "channelId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
++ },
++ "/internal/moderation/users/{userId}/channels": {
++ "get": {
++ "operationId": "get_internal_moderation_users_userId_channels",
++ "summary": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.C",
++ "description": "**chat.moderation.read** (internal): Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "tags": [
++ "chat.moderation"
++ ],
++ "x-openvibe-capabilities": [
++ "chat.moderation.read"
++ ],
++ "x-openvibe-visibility": [
++ "internal"
++ ],
++ "x-openvibe-quota-class": "internal-read",
++ "security": [
++ {
++ "openvibeToken": [
++ "chat.moderation.read"
++ ]
++ }
++ ],
++ "parameters": [
++ {
++ "name": "userId",
++ "in": "path",
++ "required": true,
++ "schema": {
++ "type": "string"
++ }
++ }
++ ],
++ "responses": {
++ "2XX": {
++ "description": "Success",
++ "content": {
++ "application/json": {
++ "schema": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1"
++ }
++ }
++ }
++ },
++ "default": {
++ "description": "An error (RFC 9457 problem details)",
++ "content": {
++ "application/problem+json": {
++ "schema": {
++ "$ref": "#/components/schemas/errors.problem.v1"
++ }
++ }
++ }
++ }
++ }
++ }
}
},
"components": {
"schemas": {
++ "chat.channel-moderation-result.v1": {
++ "title": "ChatChannelModerationResult",
++ "description": "chat.channel-moderation-result@1: the answer of GET /internal/moderation/channels/:channelId on OpenVibe.Chat (capability chat.moderation.read): { ok: true, settings, moderator_ids } — one channel's moderation settings and the ids of the people who moderate it, for OpenVibe.Live's stream chat. settings carries every channel_moderation_settings column (Live's own defaults when the channel has no row yet, database.js getChannelModerationSettings); the switches are 0/1 as the table stores them. moderator_ids are Live user ids (channel_moderators.user_id), never names. A failure is { ok: false, error }; a caller without the grant gets 401 { ok: false }.",
++ "anyOf": [
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "settings",
++ "moderator_ids"
++ ],
++ "properties": {
++ "ok": {
++ "const": true
++ },
++ "settings": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/settings"
++ },
++ "moderator_ids": {
++ "type": "array",
++ "description": "The channel's moderators, oldest first (channel_moderators.created_at); empty when it has none.",
++ "items": {
++ "type": "integer",
++ "minimum": 1
++ }
++ }
++ }
++ },
++ {
++ "type": "object",
++ "required": [
++ "ok",
++ "error"
++ ],
++ "properties": {
++ "ok": {
++ "const": false
++ },
++ "error": {
++ "type": "string"
++ }
++ }
++ }
++ ],
++ "$defs": {
++ "flag": {
++ "type": "integer",
++ "enum": [
++ 0,
++ 1
++ ],
++ "description": "A switch as the SQLite table stores it."
++ },
++ "settings": {
++ "type": "object",
++ "description": "One row of channel_moderation_settings, or Live's defaults for a channel that has no row. Every column is present; the switches are 0/1.",
++ "required": [
++ "channel_id",
++ "slow_mode_seconds",
++ "followers_only",
++ "emote_only",
++ "allow_anonymous",
++ "links_allowed",
++ "gifs_enabled",
++ "account_age_gate_hours",
++ "caps_percentage_limit",
++ "aggressive_filter",
++ "max_message_length",
++ "tts_max_length",
++ "slur_filter_enabled",
++ "slur_filter_use_builtin",
++ "slur_filter_terms",
++ "slur_filter_regexes",
++ "slur_filter_nudge_message",
++ "slur_filter_disabled_categories",
++ "ip_approval_mode",
++ "soundboard_enabled",
++ "soundboard_allow_pitch",
++ "soundboard_allow_speed",
++ "soundboard_banned_ids",
++ "viewer_auto_delete_enabled",
++ "viewer_delete_all_enabled",
++ "custom_emotes_enabled",
++ "custom_sounds_enabled",
++ "max_sound_seconds",
++ "uploads_mods_only",
++ "mods_can_edit_about",
++ "emote_scale",
++ "emote_size_min",
++ "emote_size_max",
++ "sounds_mods_only",
++ "sound_min_speed",
++ "sound_max_speed",
++ "sound_min_pitch_cents",
++ "sound_max_pitch_cents",
++ "sub_only"
++ ],
++ "additionalProperties": false,
++ "properties": {
++ "channel_id": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slow_mode_seconds": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "followers_only": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "emote_only": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "allow_anonymous": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "links_allowed": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "gifs_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "account_age_gate_hours": {
++ "type": "integer",
++ "minimum": 0
++ },
++ "caps_percentage_limit": {
++ "type": "integer",
++ "minimum": 0,
++ "description": "Percent of a message that may be capitals; 0 means off."
++ },
++ "aggressive_filter": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "max_message_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "tts_max_length": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "slur_filter_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "slur_filter_use_builtin": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "slur_filter_terms": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra terms, comma separated; a channel's own filter list, never a network-wide secret."
++ },
++ "slur_filter_regexes": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Extra patterns, one per line."
++ },
++ "slur_filter_nudge_message": {
++ "type": "string",
++ "maxLength": 512
++ },
++ "slur_filter_disabled_categories": {
++ "type": "string",
++ "description": "A JSON array string of disabled built-in categories ('[]' when none)."
++ },
++ "ip_approval_mode": {
++ "type": "integer",
++ "minimum": 0,
++ "maximum": 2,
++ "description": "0 off, 1 approve before chatting, 2 approved addresses only."
++ },
++ "soundboard_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "soundboard_allow_pitch": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "soundboard_allow_speed": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "soundboard_banned_ids": {
++ "type": "string",
++ "maxLength": 8192,
++ "description": "Banned channel sound ids, comma separated."
++ },
++ "viewer_auto_delete_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "viewer_delete_all_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "custom_emotes_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "custom_sounds_enabled": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "max_sound_seconds": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "uploads_mods_only": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "mods_can_edit_about": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "emote_scale": {
++ "type": "integer",
++ "minimum": 50,
++ "maximum": 300,
++ "description": "Emote display size in chat, percent of the base height (100 is the default)."
++ },
++ "emote_size_min": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "emote_size_max": {
++ "type": "integer",
++ "minimum": 1
++ },
++ "sounds_mods_only": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "sound_min_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_max_speed": {
++ "type": "number",
++ "exclusiveMinimum": 0
++ },
++ "sound_min_pitch_cents": {
++ "type": "integer"
++ },
++ "sound_max_pitch_cents": {
++ "type": "integer"
++ },
++ "sub_only": {
++ "$ref": "#/components/schemas/chat.channel-moderation-result.v1/$defs/flag"
++ },
++ "donation_sound_url": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "Optional alert sounds set by the streamer; absent or null when unset."
++ },
++ "donation_sound_mime": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "goal_sound_url": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "goal_sound_mime": {
++ "type": [
++ "string",
++ "null"
++ ]
++ },
++ "updated_at": {
++ "type": [
++ "string",
++ "null"
++ ],
++ "description": "When the row was last written; absent or null when the channel still has its defaults."
++ }
++ }
++ }
++ }
++ },
"chat.send-request.v1": {
"title": "ChatSendRequest",
"description": "chat.send-request@1: what a sender hands OpenVibe.Chat to post a chat line (chat.message.send). From a browser, a frame on WS /ws/chat of type chat in the room the socket joined (the text is trimmed; empty or longer than 6000 characters is dropped, the channel's own max_message_length is enforced after that; a line starting with ! or / is a command, not a message). From Live's bridge, POST /internal/live/calls: a chat.live_bridge.write batch in which every op that sends a message (a chat or dm frame, db saveChatMessage, deployNotice) also needs chat.message.send; without it that op alone is refused.",
ddiff --git a/generated/openapi/codes.json b/generated/openapi/codes.json
iindex 9c2d6e27..bc8e019c 100644
---- a/generated/openapi/codes.json
++++ b/generated/openapi/codes.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Codes API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Codes as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/community.json b/generated/openapi/community.json
iindex 83ecd06b..0abc78bf 100644
---- a/generated/openapi/community.json
++++ b/generated/openapi/community.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Community API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Community as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/coupons.json b/generated/openapi/coupons.json
iindex a6b00783..adc7ed86 100644
---- a/generated/openapi/coupons.json
++++ b/generated/openapi/coupons.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Coupons API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Coupons as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/deals.json b/generated/openapi/deals.json
iindex 19675577..796b9642 100644
---- a/generated/openapi/deals.json
++++ b/generated/openapi/deals.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Deals API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Deals as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/events.json b/generated/openapi/events.json
iindex 8018970e..fd0c8a03 100644
---- a/generated/openapi/events.json
++++ b/generated/openapi/events.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Events API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Events as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/games.json b/generated/openapi/games.json
iindex 36468db0..3b04320d 100644
---- a/generated/openapi/games.json
++++ b/generated/openapi/games.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Games API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Games as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/host.json b/generated/openapi/host.json
iindex c1b596d9..b704fa97 100644
---- a/generated/openapi/host.json
++++ b/generated/openapi/host.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Host API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Host as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/index.json b/generated/openapi/index.json
iindex a81fe8f2..ffed804c 100644
---- a/generated/openapi/index.json
++++ b/generated/openapi/index.json
@@@ -26,8 +26,8 @@
"service": "chat",
"name": "OpenVibe.Chat",
"origin": "https://openvibe.chat",
-- "operations": 1,
-- "capabilities": 1
++ "operations": 4,
++ "capabilities": 2
},
{
"service": "codes",
ddiff --git a/generated/openapi/live.json b/generated/openapi/live.json
iindex fd97741c..bf50cc17 100644
---- a/generated/openapi/live.json
++++ b/generated/openapi/live.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Live API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Live as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/media.json b/generated/openapi/media.json
iindex e29437a8..796d57a2 100644
---- a/generated/openapi/media.json
++++ b/generated/openapi/media.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Media API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Media as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/network.json b/generated/openapi/network.json
iindex 9691e7f6..95bb7070 100644
---- a/generated/openapi/network.json
++++ b/generated/openapi/network.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Network API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Network as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/news.json b/generated/openapi/news.json
iindex bec75583..f55e4ce7 100644
---- a/generated/openapi/news.json
++++ b/generated/openapi/news.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.News API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.News as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/openre.json b/generated/openapi/openre.json
iindex b0631d5e..c7001fc8 100644
---- a/generated/openapi/openre.json
++++ b/generated/openapi/openre.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenRe.Stream API",
-- "version": "0.79.0",
-- "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenRe.Stream as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/reviews.json b/generated/openapi/reviews.json
iindex 56fe5cf4..f5cf617b 100644
---- a/generated/openapi/reviews.json
++++ b/generated/openapi/reviews.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Reviews API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Reviews as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/search.json b/generated/openapi/search.json
iindex 8213e26d..c742cc80 100644
---- a/generated/openapi/search.json
++++ b/generated/openapi/search.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Search API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Search as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/sources.json b/generated/openapi/sources.json
iindex f1baf44a..844a1446 100644
---- a/generated/openapi/sources.json
++++ b/generated/openapi/sources.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Sources API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Sources as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tips.json b/generated/openapi/tips.json
iindex 8448643a..170626d6 100644
---- a/generated/openapi/tips.json
++++ b/generated/openapi/tips.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tips API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Tips as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/tools.json b/generated/openapi/tools.json
iindex 534abca1..29df8311 100644
---- a/generated/openapi/tools.json
++++ b/generated/openapi/tools.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Tools API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Tools as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/trade.json b/generated/openapi/trade.json
iindex 7b16b234..7bcdb24a 100644
---- a/generated/openapi/trade.json
++++ b/generated/openapi/trade.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Trade API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Trade as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/vip.json b/generated/openapi/vip.json
iindex 8093ae08..9bc6b868 100644
---- a/generated/openapi/vip.json
++++ b/generated/openapi/vip.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.VIP API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.VIP as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/openapi/wiki.json b/generated/openapi/wiki.json
iindex 1e0e33a0..39d96089 100644
---- a/generated/openapi/wiki.json
++++ b/generated/openapi/wiki.json
@@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"title": "OpenVibe.Wiki API",
-- "version": "0.79.0",
-- "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.79.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
++ "version": "0.80.0",
++ "description": "Routes of OpenVibe.Wiki as the OpenVibe contracts describe them (openvibe-contracts 0.80.0): each operation lists the capabilities it performs; a token for the service's audience must carry one of them unless the route is open. Generated by openvibe-contracts lib/openapi.js; do not edit.",
"license": {
"name": "MIT",
"identifier": "MIT"
ddiff --git a/generated/typescript/index.d.ts b/generated/typescript/index.d.ts
iindex 96ba5355..34734afd 100644
---- a/generated/typescript/index.d.ts
++++ b/generated/typescript/index.d.ts
@@@ -43710,6 +43710,66 @@ export type ChatSendResult =
}[];
};
++/** [email protected] (owner: chat) */
++/**
++ * chat.channel-moderation-result@1: the answer of GET /internal/moderation/channels/:channelId on OpenVibe.Chat (capability chat.moderation.read): { ok: true, settings, moderator_ids } — one channel's moderation settings and the ids of the people who moderate it, for OpenVibe.Live's stream chat. settings carries every channel_moderation_settings column (Live's own defaults when the channel has no row yet, database.js getChannelModerationSettings); the switches are 0/1 as the table stores them. moderator_ids are Live user ids (channel_moderators.user_id), never names. A failure is { ok: false, error }; a caller without the grant gets 401 { ok: false }.
++ */
++export type ChatChannelModerationResult =
++ | {
++ ok: true;
++ settings: Settings;
++ /**
++ * The channel's moderators, oldest first (channel_moderators.created_at); empty when it has none.
++ */
++ moderator_ids: number[];
++ }
++ | {
++ ok: false;
++ error: string;
++ };
++
++/** [email protected] (owner: chat) */
++/**
++ * chat.moderated-channels-result@1: the answer of GET /internal/moderation/users/:userId/channels on OpenVibe.Chat (capability chat.moderation.read): { ok: true, channels } — the channels one Live user moderates (channel_moderators rows joined with their channels, database.js getChannelsByModerator), so a stream chat can answer 'may I moderate' without asking per message. Empty when the person moderates none. A failure is { ok: false, error }.
++ */
++export type ChatModeratedChannelsResult =
++ | {
++ ok: true;
++ channels: {
++ channel_id: number;
++ /**
++ * The channel's title, for a label; never required to decide.
++ */
++ title?: string | null;
++ /**
++ * The channel owner's Live user id (channels.user_id).
++ */
++ owner_user_id?: number | null;
++ /**
++ * The owner's handle; a public name, never a profile.
++ */
++ owner_username?: string | null;
++ }[];
++ }
++ | {
++ ok: false;
++ error: string;
++ };
++
++/** [email protected] (owner: chat) */
++/**
++ * chat.emote-count-result@1: the answer of GET /internal/moderation/channels/:channelId/emote-count on OpenVibe.Chat (capability chat.moderation.read): { ok: true, count } — how many custom emotes a channel holds (the emotes rows with that channel as their owner), so a stream chat can tell an owner whether their uploads landed and show a mod panel a count without shipping every emote. Counts only, never the emotes themselves. A failure is { ok: false, error }.
++ */
++export type ChatEmoteCountResult =
++ | {
++ ok: true;
++ count: number;
++ }
++ | {
++ ok: false;
++ error: string;
++ };
++
/** [email protected] (owner: live) */
/**
* live.moderation.action v1 (OpenVibe.Live server/db/database.js logModerationAction; ADR-022). A staff or channel moderator action taken on OpenVibe.Live outside chat: site and global bans, IP bans, message deletes and purges from Live's admin panel, a stream force-ended, relay users hidden, channel moderators added or removed. Not a person tidying their own messages or configuring their own channel. Written to Live's outbox in the transaction that records the action. OpenVibe.Network keeps it in the moderation audit log. Envelope: subject { type: moderation_action, id: <action_id> }, visibility internal, actor the acting person when known. details is free-form per action (never secrets or message text beyond what moderators saw).
ddiff --git a/manifests/capabilities/chat.moderation.read.json b/manifests/capabilities/chat.moderation.read.json
nnew file mode 100644
iindex 00000000..688adc20
---- /dev/null
++++ b/manifests/capabilities/chat.moderation.read.json
@@@ -0,0 +1,23 @@
++{
++ "id": "chat.moderation.read",
++ "version": "1.0",
++ "owner": "chat",
++ "status": "active",
++ "visibility": "internal",
++ "description": "Read one channel's moderation settings and moderator ids, the channels a person moderates, and a channel's custom emote count: how OpenVibe.Live reads what it needs from the six chat tables OpenVibe.Chat owns (plan T3). Loopback only, service token.",
++ "permissions": [
++ "chat:read"
++ ],
++ "resourceConstraints": [
++ "none"
++ ],
++ "quotaClass": "internal-read",
++ "events": [],
++ "implementedBy": [
++ "GET /internal/moderation/channels/:channelId",
++ "GET /internal/moderation/users/:userId/channels",
++ "GET /internal/moderation/channels/:channelId/emote-count"
++ ],
++ "inputSchema": "common.no-body@1",
++ "outputSchema": "chat.channel-moderation-result@1"
++}
ddiff --git a/manifests/services/chat.json b/manifests/services/chat.json
iindex f720b113..1d3fe778 100644
---- a/manifests/services/chat.json
++++ b/manifests/services/chat.json
@@@ -14,6 +14,7 @@
"capabilities": [
"chat.live_bridge.write",
"chat.message.send",
++ "chat.moderation.read",
"chat.presence.read"
],
"eventsProduced": [
ddiff --git a/package.json b/package.json
iindex ffb380ea..33daea14 100644
---- a/package.json
++++ b/package.json
@@@ -1,6 +1,6 @@
{
"name": "openvibe-contracts",
-- "version": "0.79.0",
++ "version": "0.80.0",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"types": "generated/typescript/index.d.ts",