Compare commits
28
Commits
90335ce1bd
...
b89edfafa9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b89edfafa9 | ||
|
|
f1e5148480 | ||
|
|
1a28d120c2 | ||
|
|
e4e4337852 | ||
|
|
687c6b7cc6 | ||
|
|
20086b7776 | ||
|
|
29c63121f4 | ||
|
|
65018af7a0 | ||
|
|
d28b289048 | ||
|
|
4e8ca67ccc | ||
|
|
a0d54b426d | ||
|
|
5564a30bf9 | ||
|
|
2856d04396 | ||
|
|
868e56e5e8 | ||
|
|
2e386c1e63 | ||
|
|
39844e6e51 | ||
|
|
1e58ecd7f0 | ||
|
|
eb2be975c1 | ||
|
|
8c84e45bf9 | ||
|
|
c63806b094 | ||
|
|
06192f4717 | ||
|
|
5f09ad23ee | ||
|
|
5b42580245 | ||
|
|
b22ee5eeab | ||
|
|
c7e57aa015 | ||
|
|
2760f0234f | ||
|
|
d5a4027708 | ||
|
|
d555bc0727 |
@@ -0,0 +1,19 @@
|
|||||||
|
name: Bug report
|
||||||
|
description: Report a reproducible defect.
|
||||||
|
title: 'bug: '
|
||||||
|
labels: [bug]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: reproduction
|
||||||
|
attributes:
|
||||||
|
label: Reproduction
|
||||||
|
description: List the smallest deterministic reproduction and expected behavior.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: verification
|
||||||
|
attributes:
|
||||||
|
label: Verification context
|
||||||
|
description: Include repository, branch, version, and relevant checks. Never include secrets.
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Feature request
|
||||||
|
description: Propose a change that is consistent with PLAN.md.
|
||||||
|
title: 'feature: '
|
||||||
|
labels: [enhancement]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: problem
|
||||||
|
attributes:
|
||||||
|
label: Problem and proposed behavior
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: checkboxes
|
||||||
|
id: plan
|
||||||
|
attributes:
|
||||||
|
label: Planning
|
||||||
|
options:
|
||||||
|
- label: I checked PLAN.md and this does not silently change an existing decision.
|
||||||
|
required: true
|
||||||
@@ -9,4 +9,19 @@ jobs:
|
|||||||
runs-on: debian-latest
|
runs-on: debian-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- run: test -f README.md && test -f AGENTS.md && test -f docs/versioning.md
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
- name: Validate repository documentation
|
||||||
|
run: test -f README.md && test -f AGENTS.md && test -f docs/versioning.md
|
||||||
|
- name: Validate JSON contracts and fixtures
|
||||||
|
run: |
|
||||||
|
node --input-type=module <<'NODE'
|
||||||
|
import { readdir, readFile } from 'node:fs/promises';
|
||||||
|
for (const directory of ['contracts/v1', 'fixtures/v1']) {
|
||||||
|
for (const name of await readdir(directory)) {
|
||||||
|
if (!name.endsWith('.json')) continue;
|
||||||
|
JSON.parse(await readFile(`${directory}/${name}`, 'utf8'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NODE
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
name: API release check
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags: ['v*.*.*']
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release-check:
|
||||||
|
runs-on: debian-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 22
|
||||||
|
- name: Validate release metadata
|
||||||
|
run: test -s CHANGELOG.md && test -s README.md && test -s docs/versioning.md
|
||||||
|
- name: Validate JSON
|
||||||
|
run: node --input-type=module -e "import { readdir, readFile } from 'node:fs/promises'; for (const d of ['contracts/v1','fixtures/v1']) for (const f of await readdir(d)) if (f.endsWith('.json')) JSON.parse(await readFile(d + '/' + f, 'utf8'));"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable API contract changes are recorded here. The format follows
|
||||||
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Development work is
|
||||||
|
performed on `dev`; stable release entries are added when the user authorizes a
|
||||||
|
release.
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
- Add versioned backup inventory and manual-backup response contracts.
|
||||||
@@ -4,5 +4,17 @@ Shared versioned HTTP and authenticated WebSocket contracts for `trixie-server`
|
|||||||
|
|
||||||
This repository contains schemas, shared enums, error codes, compatibility fixtures, contract tests, and API documentation only.
|
This repository contains schemas, shared enums, error codes, compatibility fixtures, contract tests, and API documentation only.
|
||||||
|
|
||||||
The first contract slice is under `contracts/v1/` and `fixtures/v1/`. It covers server readiness and health so the client and server can be developed and tested together before Twitch integration.
|
The first contract slice is under `contracts/v1/` and `fixtures/v1/`. It covers
|
||||||
|
server readiness, health, errors, and the transport-neutral realtime event
|
||||||
|
envelope so the client and server can be developed and tested together before
|
||||||
|
Twitch integration. It also defines the first administrator authentication
|
||||||
|
request and response envelopes; bearer values in fixtures are synthetic only.
|
||||||
|
Twitch OAuth link and completion response envelopes are also versioned here;
|
||||||
|
fixtures contain no real credentials or tokens.
|
||||||
|
|
||||||
|
The shared boundary vocabulary includes the ordered permission levels in
|
||||||
|
`contracts/v1/permissions.json` and the client/server compatibility result in
|
||||||
|
`contracts/v1/version-negotiation.json`. Server-owned state is delivered as
|
||||||
|
the authenticated `realtime-event.json` envelope; clients must ignore event
|
||||||
|
types they do not understand.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/activity-list-response.json",
|
||||||
|
"title": "Trixie activity feed response",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["event_id", "event_type", "occurred_at", "payload"],
|
||||||
|
"properties": {
|
||||||
|
"event_id": { "type": "string", "minLength": 1 },
|
||||||
|
"event_type": { "type": "string", "pattern": "^[a-z0-9_.-]+$" },
|
||||||
|
"occurred_at": { "type": "string", "format": "date-time" },
|
||||||
|
"payload": { "type": "object" }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"maxItems": 100
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "severity", "title", "message", "seen_at", "resolved_at", "created_at"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string", "format": "uuid" },
|
||||||
|
"severity": { "type": "string", "enum": ["info", "warning", "error"] },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"message": { "type": "string" },
|
||||||
|
"seen_at": { "type": ["string", "null"], "format": "date-time" },
|
||||||
|
"resolved_at": { "type": ["string", "null"], "format": "date-time" },
|
||||||
|
"created_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/auth-login-request.json",
|
||||||
|
"title": "Trixie Login Request",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["username", "password"],
|
||||||
|
"properties": {
|
||||||
|
"username": { "type": "string", "minLength": 1 },
|
||||||
|
"password": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/auth-login-response.json",
|
||||||
|
"title": "Trixie Login Response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["session_token", "must_replace_credentials"],
|
||||||
|
"properties": {
|
||||||
|
"session_token": { "type": "string", "minLength": 1 },
|
||||||
|
"must_replace_credentials": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/auth-session-response.json",
|
||||||
|
"title": "Trixie Session Response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["authenticated"],
|
||||||
|
"properties": {
|
||||||
|
"authenticated": { "const": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["filename", "size_bytes", "modified_at"],
|
||||||
|
"properties": {
|
||||||
|
"filename": { "type": "string" },
|
||||||
|
"size_bytes": { "type": "integer", "minimum": 0 },
|
||||||
|
"modified_at": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["status", "filename", "reason"],
|
||||||
|
"properties": {
|
||||||
|
"status": { "type": "string", "enum": ["ok", "failed"] },
|
||||||
|
"filename": { "type": ["string", "null"] },
|
||||||
|
"reason": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/bot-command-invoke-request.json",
|
||||||
|
"title": "Trixie bot command invocation request",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "user_id", "permission_level"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "minLength": 1, "maxLength": 64 },
|
||||||
|
"user_id": { "type": "string", "minLength": 1, "maxLength": 64 },
|
||||||
|
"permission_level": { "enum": ["everyone", "subscriber", "moderator", "broadcaster"] },
|
||||||
|
"variables": { "type": "object", "additionalProperties": { "type": "string" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/bot-command-invoke-response.json",
|
||||||
|
"title": "Trixie bot command invocation response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["status", "response", "remaining_cooldown_seconds", "error"],
|
||||||
|
"properties": {
|
||||||
|
"status": { "enum": ["ok", "cooldown", "not_found", "permission_denied", "error"] },
|
||||||
|
"response": { "type": ["string", "null"] },
|
||||||
|
"remaining_cooldown_seconds": { "type": ["integer", "null"], "minimum": 0 },
|
||||||
|
"error": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/bot-commands-response.json",
|
||||||
|
"title": "Trixie bot command list",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "name", "description", "permission_level", "aliases", "tags", "cooldown_seconds", "cooldown_scope", "response_template", "error_template", "enabled", "failure_count", "disabled_reason"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string", "format": "uuid" },
|
||||||
|
"name": { "type": "string", "pattern": "^[A-Za-z0-9_-]+$" },
|
||||||
|
"description": { "type": "string" },
|
||||||
|
"permission_level": { "enum": ["everyone", "subscriber", "moderator", "broadcaster"] },
|
||||||
|
"aliases": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||||
|
"tags": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||||
|
"cooldown_seconds": { "type": "integer", "minimum": 0, "maximum": 86400 },
|
||||||
|
"cooldown_scope": { "enum": ["global", "user", "role"] },
|
||||||
|
"response_template": { "type": "string" },
|
||||||
|
"error_template": { "type": ["string", "null"] },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"failure_count": { "type": "integer", "minimum": 0 },
|
||||||
|
"disabled_reason": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/bot-secrets-response.json",
|
||||||
|
"title": "Trixie bot secret metadata",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "name", "description", "used_by", "created_at", "updated_at"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string", "format": "uuid" },
|
||||||
|
"name": { "type": "string", "pattern": "^[A-Za-z0-9_]+$" },
|
||||||
|
"description": { "type": "string" },
|
||||||
|
"used_by": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"created_at": { "type": "string", "format": "date-time" },
|
||||||
|
"updated_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 50,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["name", "url", "provider"],
|
||||||
|
"properties": {
|
||||||
|
"name": { "type": "string", "minLength": 1 },
|
||||||
|
"url": { "type": "string", "format": "uri", "pattern": "^https://" },
|
||||||
|
"provider": { "type": "string", "enum": ["twitch", "7tv"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/chat-send-request.json",
|
||||||
|
"title": "Twitch chat send request",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["identity", "message"],
|
||||||
|
"properties": {
|
||||||
|
"identity": { "enum": ["streamer", "bot"] },
|
||||||
|
"message": { "type": "string", "minLength": 1, "maxLength": 500 },
|
||||||
|
"reply_parent_message_id": { "type": "string", "maxLength": 128 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/chat-send-response.json",
|
||||||
|
"title": "Twitch chat send response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["message_id", "is_sent"],
|
||||||
|
"properties": {
|
||||||
|
"message_id": { "type": "string", "minLength": 1 },
|
||||||
|
"is_sent": { "type": "boolean" },
|
||||||
|
"drop_reason": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "guests"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"guests": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["user_id"],
|
||||||
|
"properties": {
|
||||||
|
"user_id": { "type": "string" },
|
||||||
|
"user_display_name": { "type": ["string", "null"] },
|
||||||
|
"slot_id": { "type": ["string", "null"] },
|
||||||
|
"is_live": { "type": ["boolean", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "login", "display_name"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string" },
|
||||||
|
"login": { "type": "string" },
|
||||||
|
"display_name": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["twitch_user_id", "display_name", "favorite", "last_invited_at"],
|
||||||
|
"properties": {
|
||||||
|
"twitch_user_id": { "type": "string" },
|
||||||
|
"display_name": { "type": "string" },
|
||||||
|
"favorite": { "type": "boolean" },
|
||||||
|
"last_invited_at": { "type": ["string", "null"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/error.json",
|
||||||
|
"title": "Trixie API Error",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["code", "message", "request_id"],
|
||||||
|
"properties": {
|
||||||
|
"code": { "type": "string", "pattern": "^[a-z0-9_]+$" },
|
||||||
|
"message": { "type": "string", "minLength": 1 },
|
||||||
|
"request_id": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/game-search-response.json",
|
||||||
|
"title": "Twitch category search response",
|
||||||
|
"type": "array",
|
||||||
|
"maxItems": 25,
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["id", "name", "box_art_url"],
|
||||||
|
"properties": {
|
||||||
|
"id": { "type": "string", "minLength": 1 },
|
||||||
|
"name": { "type": "string", "minLength": 1 },
|
||||||
|
"box_art_url": { "type": "string", "format": "uri" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/moderation-action-request.json",
|
||||||
|
"title": "Twitch moderation action",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["action", "user_id"],
|
||||||
|
"properties": {
|
||||||
|
"action": { "enum": ["ban", "timeout", "unban", "delete_message"] },
|
||||||
|
"user_id": { "type": "string", "minLength": 1, "maxLength": 64 },
|
||||||
|
"duration_seconds": { "type": "integer", "minimum": 1, "maximum": 1209600 },
|
||||||
|
"reason": { "type": "string", "maxLength": 500 },
|
||||||
|
"message_id": { "type": "string", "maxLength": 128 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/moderation-action-response.json",
|
||||||
|
"title": "Twitch moderation action response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["action", "applied"],
|
||||||
|
"properties": {
|
||||||
|
"action": { "type": "string" },
|
||||||
|
"applied": { "const": true }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/overlay-response.json",
|
||||||
|
"title": "Trixie public overlay",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["slug", "html", "css", "javascript", "enabled", "disabled_design"],
|
||||||
|
"properties": {
|
||||||
|
"slug": { "type": "string", "pattern": "^[a-z0-9-]+$" },
|
||||||
|
"html": { "type": "string" },
|
||||||
|
"css": { "type": "string" },
|
||||||
|
"javascript": { "type": "string" },
|
||||||
|
"enabled": { "type": "boolean" },
|
||||||
|
"disabled_design": { "type": "string" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/permissions.json",
|
||||||
|
"title": "Trixie Permission Level",
|
||||||
|
"description": "The ordered permission levels used by chat commands and moderation controls.",
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["everyone", "subscriber", "moderator", "broadcaster"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/readiness.json",
|
||||||
|
"title": "Trixie readiness response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["status"],
|
||||||
|
"properties": {
|
||||||
|
"status": { "enum": ["ready", "database_unavailable"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/realtime-auth-frame.json",
|
||||||
|
"title": "Trixie realtime authentication frame",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["frame_type", "token"],
|
||||||
|
"properties": {
|
||||||
|
"frame_type": { "const": "authenticate" },
|
||||||
|
"token": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/realtime-event.json",
|
||||||
|
"title": "Trixie Realtime Event",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["event_id", "event_type", "occurred_at", "payload"],
|
||||||
|
"properties": {
|
||||||
|
"event_id": { "type": "string", "minLength": 1 },
|
||||||
|
"event_type": { "type": "string", "pattern": "^[a-z0-9_.-]+$" },
|
||||||
|
"occurred_at": { "type": "string", "format": "date-time" },
|
||||||
|
"payload": { "type": "object" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["status", "database", "twitch_identity_count", "unseen_notification_count", "realtime"],
|
||||||
|
"properties": {
|
||||||
|
"status": { "type": "string", "enum": ["healthy"] },
|
||||||
|
"database": { "type": "string", "enum": ["connected"] },
|
||||||
|
"twitch_identity_count": { "type": "integer", "minimum": 0 },
|
||||||
|
"unseen_notification_count": { "type": "integer", "minimum": 0 },
|
||||||
|
"realtime": { "type": "string", "enum": ["available"] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,12 @@
|
|||||||
"title": "Trixie Server Info",
|
"title": "Trixie Server Info",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"required": ["api_version", "server_version", "status"],
|
"required": ["api_version", "server_version", "minimum_client_version", "maximum_client_version", "status"],
|
||||||
"properties": {
|
"properties": {
|
||||||
"api_version": { "type": "string", "const": "v1" },
|
"api_version": { "type": "string", "const": "v1" },
|
||||||
"server_version": { "type": "string", "minLength": 1 },
|
"server_version": { "type": "string", "minLength": 1 },
|
||||||
|
"minimum_client_version": { "type": "string", "minLength": 1 },
|
||||||
|
"maximum_client_version": { "type": "string", "minLength": 1 },
|
||||||
"status": { "type": "string", "enum": ["ready"] }
|
"status": { "type": "string", "enum": ["ready"] }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/shared-chat-session-response.json",
|
||||||
|
"title": "Twitch Shared Chat session response",
|
||||||
|
"type": ["object", "null"],
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["session_id", "host_broadcaster_id", "participants", "created_at", "updated_at"],
|
||||||
|
"properties": {
|
||||||
|
"session_id": { "type": "string", "minLength": 1 },
|
||||||
|
"host_broadcaster_id": { "type": "string", "minLength": 1 },
|
||||||
|
"participants": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["broadcaster_id"],
|
||||||
|
"properties": { "broadcaster_id": { "type": "string", "minLength": 1 } }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"created_at": { "type": "string", "format": "date-time" },
|
||||||
|
"updated_at": { "type": "string", "format": "date-time" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/stream-info-request.json",
|
||||||
|
"title": "Twitch stream information update",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"minProperties": 1,
|
||||||
|
"properties": {
|
||||||
|
"title": { "type": "string", "maxLength": 140 },
|
||||||
|
"game_id": { "type": "string", "maxLength": 64 },
|
||||||
|
"language": { "type": "string", "maxLength": 10 },
|
||||||
|
"tags": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 25 } },
|
||||||
|
"content_classification_labels": { "type": "array", "maxItems": 10, "items": { "type": "string", "maxLength": 64 } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/stream-info-response.json",
|
||||||
|
"title": "Twitch stream information",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["broadcaster_id", "broadcaster_name", "game_id", "game_name", "title", "language", "tags", "content_classification_labels"],
|
||||||
|
"properties": {
|
||||||
|
"broadcaster_id": { "type": "string", "minLength": 1 },
|
||||||
|
"broadcaster_name": { "type": "string", "minLength": 1 },
|
||||||
|
"game_id": { "type": "string" },
|
||||||
|
"game_name": { "type": "string" },
|
||||||
|
"title": { "type": "string" },
|
||||||
|
"language": { "type": "string" },
|
||||||
|
"tags": { "type": "array", "items": { "type": "string" } },
|
||||||
|
"content_classification_labels": { "type": "array", "items": { "type": "string" } }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-link-response.json",
|
||||||
|
"title": "Twitch OAuth authorization link response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["identity", "scopes", "authorization_url", "expires_in_seconds"],
|
||||||
|
"properties": {
|
||||||
|
"identity": { "enum": ["streamer", "bot", "moderator"] },
|
||||||
|
"scopes": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string", "minLength": 1 },
|
||||||
|
"minItems": 1,
|
||||||
|
"uniqueItems": true
|
||||||
|
},
|
||||||
|
"authorization_url": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "uri",
|
||||||
|
"pattern": "^https://id\\.twitch\\.tv/oauth2/authorize\\?"
|
||||||
|
},
|
||||||
|
"expires_in_seconds": { "type": "integer", "const": 600 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-result.json",
|
||||||
|
"title": "Twitch OAuth completion response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["identity", "twitch_user_id", "display_name"],
|
||||||
|
"properties": {
|
||||||
|
"identity": { "enum": ["streamer", "bot", "moderator"] },
|
||||||
|
"twitch_user_id": { "type": "string", "minLength": 1 },
|
||||||
|
"display_name": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.reaperofveriod.nl/contracts/v1/twitch-oauth-status-response.json",
|
||||||
|
"title": "Twitch OAuth scope status response",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["identity", "connected", "required_scopes", "granted_scopes", "missing_scopes", "reauthorization_required"],
|
||||||
|
"properties": {
|
||||||
|
"identity": { "enum": ["streamer", "bot", "moderator"] },
|
||||||
|
"connected": { "type": "boolean" },
|
||||||
|
"required_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||||
|
"granted_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||||
|
"missing_scopes": { "type": "array", "items": { "type": "string" }, "uniqueItems": true },
|
||||||
|
"reauthorization_required": { "type": "boolean" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://trixie.invalid/contracts/v1/version-negotiation.json",
|
||||||
|
"title": "Trixie Version Negotiation",
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": ["api_version", "client_version", "server_version", "compatible"],
|
||||||
|
"properties": {
|
||||||
|
"api_version": { "type": "string", "const": "v1" },
|
||||||
|
"client_version": { "type": "string", "minLength": 1 },
|
||||||
|
"server_version": { "type": "string", "minLength": 1 },
|
||||||
|
"compatible": { "type": "boolean" },
|
||||||
|
"reason": { "type": "string", "minLength": 1 }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,18 @@
|
|||||||
# Contract Testing
|
# Contract Testing
|
||||||
|
|
||||||
Fixtures cover HTTP responses, authenticated WebSocket events, errors, permissions, version negotiation, reconnects, and server-owned state transitions. Fixtures are deterministic, synthetic, and secret-free.
|
Fixtures cover HTTP responses, authenticated WebSocket events, errors, permissions,
|
||||||
|
version negotiation, reconnects, and server-owned state transitions. Fixtures are
|
||||||
|
deterministic, synthetic, and secret-free. Every new contract must include at
|
||||||
|
least one valid fixture and a negative case where validation rules are important.
|
||||||
|
|
||||||
|
The current realtime envelope is deliberately transport-neutral: the server may
|
||||||
|
deliver it over an authenticated WebSocket or a future compatible transport, but
|
||||||
|
the event identity and payload rules remain the same. `event_id` is the stable
|
||||||
|
deduplication key for at-least-once delivery.
|
||||||
|
|
||||||
|
Browser clients authenticate the WebSocket with the first text frame described
|
||||||
|
by `realtime-auth-frame.json`. Session tokens are sent in that frame rather
|
||||||
|
than in a URL query parameter. The server closes the connection when the frame
|
||||||
|
is missing, malformed, or invalid, and sends `connection.ready` only after
|
||||||
|
successful validation.
|
||||||
|
|
||||||
|
|||||||
@@ -5,4 +5,13 @@
|
|||||||
- Clients must tolerate unknown fields and events.
|
- Clients must tolerate unknown fields and events.
|
||||||
- Breaking changes require a new major version, fixtures, migration notes, and coordinated releases.
|
- Breaking changes require a new major version, fixtures, migration notes, and coordinated releases.
|
||||||
- Each release records minimum compatible client and server versions.
|
- Each release records minimum compatible client and server versions.
|
||||||
|
- `GET /api/v1/server/info` publishes `minimum_client_version` and
|
||||||
|
`maximum_client_version`; clients must use these fields to detect an
|
||||||
|
incompatible pairing before enabling authenticated controls.
|
||||||
|
- Permission values are shared through `contracts/v1/permissions.json`.
|
||||||
|
They are ordered from least to most privileged: `everyone`, `subscriber`,
|
||||||
|
`moderator`, and `broadcaster`.
|
||||||
|
- The normalized result of a compatibility check is described by
|
||||||
|
`contracts/v1/version-negotiation.json`; an incompatible result includes a
|
||||||
|
human-readable `reason` while a compatible result may omit it.
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"event_id": "synthetic-event-1",
|
||||||
|
"event_type": "channel.follow",
|
||||||
|
"occurred_at": "2026-10-25T12:00:00Z",
|
||||||
|
"payload": { "user_name": "SyntheticViewer" }
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-0000-0000-000000000001",
|
||||||
|
"severity": "warning",
|
||||||
|
"title": "IP permanently rate-limited",
|
||||||
|
"message": "A source IP reached the login failure limit and requires review.",
|
||||||
|
"seen_at": null,
|
||||||
|
"resolved_at": null,
|
||||||
|
"created_at": "2026-10-25T12:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"session_token": "fixture-session-token-never-use",
|
||||||
|
"must_replace_credentials": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"authenticated": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "trixie-20261025T030000Z.dump",
|
||||||
|
"size_bytes": 4096,
|
||||||
|
"modified_at": "2026-10-25T03:00:01Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"filename": "trixie-20261025T030000Z.dump",
|
||||||
|
"reason": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"status": "ok",
|
||||||
|
"response": "Hello Alice",
|
||||||
|
"remaining_cooldown_seconds": null,
|
||||||
|
"error": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-4000-8000-000000000001",
|
||||||
|
"name": "uptime",
|
||||||
|
"description": "Show uptime",
|
||||||
|
"permission_level": "everyone",
|
||||||
|
"aliases": [],
|
||||||
|
"tags": ["utility"],
|
||||||
|
"cooldown_seconds": 2,
|
||||||
|
"cooldown_scope": "user",
|
||||||
|
"response_template": "Online",
|
||||||
|
"error_template": null,
|
||||||
|
"enabled": true,
|
||||||
|
"failure_count": 0,
|
||||||
|
"disabled_reason": null
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "00000000-0000-4000-8000-000000000002",
|
||||||
|
"name": "API_Key",
|
||||||
|
"description": "External API key",
|
||||||
|
"used_by": [],
|
||||||
|
"created_at": "2026-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Kappa",
|
||||||
|
"url": "https://static-cdn.jtvnw.net/emoticons/v2/25/static/dark/2.0",
|
||||||
|
"provider": "twitch"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "catJAM",
|
||||||
|
"url": "https://cdn.7tv.app/emote/60ae8f6f625f5a2a4d4e2b6f/2x.webp",
|
||||||
|
"provider": "7tv"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"message_id": "synthetic-message-id",
|
||||||
|
"is_sent": true,
|
||||||
|
"drop_reason": null
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"id": "guest-star-session-1",
|
||||||
|
"guests": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "123456",
|
||||||
|
"login": "example_streamer",
|
||||||
|
"display_name": "ExampleStreamer"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"twitch_user_id": "123456",
|
||||||
|
"display_name": "ExampleStreamer",
|
||||||
|
"favorite": true,
|
||||||
|
"last_invited_at": "2026-10-25T12:00:00Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"code": "unauthorized",
|
||||||
|
"message": "Authentication is required.",
|
||||||
|
"request_id": "req_fixture_unauthorized"
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"id": "123",
|
||||||
|
"name": "Synthetic Game",
|
||||||
|
"box_art_url": "https://static-cdn.jtvnw.net/ttv-boxart/123-52x72.jpg"
|
||||||
|
}
|
||||||
|
]
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"action": "timeout",
|
||||||
|
"applied": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"slug": "followers",
|
||||||
|
"html": "<div id=\"alert\"></div>",
|
||||||
|
"css": "#alert { color: white; }",
|
||||||
|
"javascript": "",
|
||||||
|
"enabled": true,
|
||||||
|
"disabled_design": "transparent"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"status": "ready"
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"frame_type": "authenticate",
|
||||||
|
"token": "synthetic-session-token"
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"event_id": "evt_fixture_activity_001",
|
||||||
|
"event_type": "activity.follow",
|
||||||
|
"occurred_at": "2026-01-01T00:00:00Z",
|
||||||
|
"payload": {
|
||||||
|
"username": "fixture_viewer",
|
||||||
|
"display_name": "Fixture Viewer"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"status": "healthy",
|
||||||
|
"database": "connected",
|
||||||
|
"twitch_identity_count": 2,
|
||||||
|
"unseen_notification_count": 0,
|
||||||
|
"realtime": "available"
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
{
|
{
|
||||||
"api_version": "v1",
|
"api_version": "v1",
|
||||||
"server_version": "0.1.0-dev",
|
"server_version": "0.1.0-dev",
|
||||||
|
"minimum_client_version": "0.1.0",
|
||||||
|
"maximum_client_version": "0.x",
|
||||||
"status": "ready"
|
"status": "ready"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"session_id": "shared-session-1",
|
||||||
|
"host_broadcaster_id": "1234",
|
||||||
|
"participants": [{ "broadcaster_id": "1234" }, { "broadcaster_id": "5678" }],
|
||||||
|
"created_at": "2026-01-01T00:00:00Z",
|
||||||
|
"updated_at": "2026-01-01T00:00:00Z"
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"broadcaster_id": "synthetic-broadcaster-id",
|
||||||
|
"broadcaster_name": "SyntheticStreamer",
|
||||||
|
"game_id": "123",
|
||||||
|
"game_name": "Synthetic Game",
|
||||||
|
"title": "A synthetic stream",
|
||||||
|
"language": "en",
|
||||||
|
"tags": ["Synthetic"],
|
||||||
|
"content_classification_labels": []
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"identity": "bot",
|
||||||
|
"scopes": ["channel:bot", "user:read:chat", "user:write:chat"],
|
||||||
|
"authorization_url": "https://id.twitch.tv/oauth2/authorize?client_id=public-example-client&redirect_uri=http%3A%2F%2Flocalhost%3A8787%2Fapi%2Fv1%2Ftwitch%2Foauth%2Fcallback&response_type=code&scope=channel%3Abot+user%3Aread%3Achat+user%3Awrite%3Achat&state=synthetic-state",
|
||||||
|
"expires_in_seconds": 600
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"identity": "streamer",
|
||||||
|
"twitch_user_id": "synthetic-twitch-user-id",
|
||||||
|
"display_name": "SyntheticStreamer"
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"identity": "streamer",
|
||||||
|
"connected": false,
|
||||||
|
"required_scopes": ["user:read:chat"],
|
||||||
|
"granted_scopes": [],
|
||||||
|
"missing_scopes": ["user:read:chat"],
|
||||||
|
"reauthorization_required": true
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"api_version": "v1",
|
||||||
|
"client_version": "0.1.0",
|
||||||
|
"server_version": "0.1.0",
|
||||||
|
"compatible": true
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user