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.
Changes
@@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) thfrom `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).@@@ -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",nnew file mode 100644@@@ -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."++ }++ }++ }++ }++}nnew file mode 100644@@@ -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"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -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"++ }++ }++ }++ ]++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "settings": {}, "moderator_ids": [] }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "settings": { "channel_id": 41, "slow_mode_seconds": 0, "followers_only": 2 }, "moderator_ids": [] }nnew file mode 100644@@@ -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": []++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "error": "channel not found" }nnew file mode 100644@@@ -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]++}nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": false, "error": "chat store unavailable" }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "count": 12 }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "channels": [{ "title": "Late Night D&D" }] }nnew file mode 100644@@@ -0,0 +1 @@++{ "ok": true, "channels": [] }nnew file mode 100644@@@ -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 }] }@@@ -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",@@@ -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"@@@ -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"@@@ -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"@@@ -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.",@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -26,8 +26,8 @@"service": "chat","name": "OpenVibe.Chat","origin": "https://openvibe.chat",-- "operations": 1,-- "capabilities": 1++ "operations": 4,++ "capabilities": 2},{"service": "codes",@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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"@@@ -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).nnew file mode 100644@@@ -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"++}@@@ -14,6 +14,7 @@"capabilities": ["chat.live_bridge.write","chat.message.send",++ "chat.moderation.read","chat.presence.read"],"eventsProduced": [@@@ -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",
@@ -4,6 +4,16 @@ All notable changes to `openvibe-contracts`. Releases are git tags (`vX.Y.Z`) th
@@ -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,
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`).
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
## 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).
**Service-token capabilities for every internal call that still used X-Internal-Key** (plan T2, register C-50–C-58).
@@ -4484,6 +4484,33 @@
@@ -4484,6 +4484,33 @@
"compatibility": "backward",
"compatibility": "backward",
"status": "active"
"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",
"id": "live.moderation.action",
"version": "1.0.0",
"version": "1.0.0",
new file mode 100644
new file mode 100644
@@ -0,0 +1,227 @@
@@ -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."
+ }
+ }
+ }
+ }
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,39 @@
@@ -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"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -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"
+ }
+ }
+ }
+ ]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "settings": {}, "moderator_ids": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "settings": { "channel_id": 41, "slow_mode_seconds": 0, "followers_only": 2 }, "moderator_ids": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1,45 @@
@@ -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": []
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "error": "channel not found" }
new file mode 100644
new file mode 100644
@@ -0,0 +1,50 @@
@@ -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]
+}
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": false, "error": "chat store unavailable" }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "count": 12 }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "channels": [{ "title": "Late Night D&D" }] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -0,0 +1 @@
+{ "ok": true, "channels": [] }
new file mode 100644
new file mode 100644
@@ -0,0 +1 @@
@@ -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 }] }
@@ -48023,6 +48023,412 @@
@@ -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": {
"live.moderation.action": {
"version": "1.0.0",
"version": "1.0.0",
"owner": "live",
"owner": "live",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.AI API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Billing API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Blog API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Chat API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -71,10 +71,456 @@
@@ -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": {
"components": {
"schemas": {
"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": {
"chat.send-request.v1": {
"title": "ChatSendRequest",
"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.",
"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.",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Codes API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Community API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Coupons API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Deals API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Events API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Games API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Host API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -26,8 +26,8 @@
@@ -26,8 +26,8 @@
"service": "chat",
"service": "chat",
"name": "OpenVibe.Chat",
"name": "OpenVibe.Chat",
"origin": "https://openvibe.chat",
"origin": "https://openvibe.chat",
- "operations": 1,
- "capabilities": 1
+ "operations": 4,
+ "capabilities": 2
},
},
{
{
"service": "codes",
"service": "codes",
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Live API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Media API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Network API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.News API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenRe.Stream API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Reviews API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Search API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Sources API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tips API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Tools API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Trade API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.VIP API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -3,8 +3,8 @@
@@ -3,8 +3,8 @@
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
"info": {
"info": {
"title": "OpenVibe.Wiki API",
"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": {
"license": {
"name": "MIT",
"name": "MIT",
"identifier": "MIT"
"identifier": "MIT"
@@ -43710,6 +43710,66 @@ export type ChatSendResult =
@@ -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) */
/** [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).
* 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).
new file mode 100644
new file mode 100644
@@ -0,0 +1,23 @@
@@ -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"
+}
@@ -14,6 +14,7 @@
@@ -14,6 +14,7 @@
"capabilities": [
"capabilities": [
"chat.live_bridge.write",
"chat.live_bridge.write",
"chat.message.send",
"chat.message.send",
+ "chat.moderation.read",
"chat.presence.read"
"chat.presence.read"
],
],
"eventsProduced": [
"eventsProduced": [
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-contracts",
"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.",
"description": "Machine-readable contracts for the OpenVibe network: schemas, manifests, ids, error envelope, capability checks.",
"main": "index.js",
"main": "index.js",
"types": "generated/typescript/index.d.ts",
"types": "generated/typescript/index.d.ts",