Add embedded web chat client (closes #7) #8

Merged
clawbot merged 22 commits from feature/web-client into main 2026-02-11 03:02:42 +01:00
72 changed files with 1166 additions and 770 deletions
Showing only changes of commit 909da3cc99 - Show all commits

View File

@@ -2,7 +2,8 @@
JSON Schema (draft 2020-12) definitions for the IRC-style message protocol. JSON Schema (draft 2020-12) definitions for the IRC-style message protocol.
All messages share a common envelope defined in [`message.schema.json`](message.schema.json). All messages share a common envelope defined in
[`message.schema.json`](message.schema.json).
## Base Envelope ## Base Envelope
@@ -30,7 +31,7 @@ All messages share a common envelope defined in [`message.schema.json`](message.
| MODE | [`c2s/mode.schema.json`](c2s/mode.schema.json) | Set/query modes | | MODE | [`c2s/mode.schema.json`](c2s/mode.schema.json) | Set/query modes |
| TOPIC | [`c2s/topic.schema.json`](c2s/topic.schema.json) | Set/query topic | | TOPIC | [`c2s/topic.schema.json`](c2s/topic.schema.json) | Set/query topic |
| KICK | [`c2s/kick.schema.json`](c2s/kick.schema.json) | Kick user | | KICK | [`c2s/kick.schema.json`](c2s/kick.schema.json) | Kick user |
| PING | [`c2s/ping.schema.json`](c2s/ping.schema.json) | Client ping | | PING | [`c2s/ping.schema.json`](c2s/ping.schema.json) | Client keepalive |
| PUBKEY | [`c2s/pubkey.schema.json`](c2s/pubkey.schema.json) | Announce public key | | PUBKEY | [`c2s/pubkey.schema.json`](c2s/pubkey.schema.json) | Announce public key |
## Server-to-Client (S2C) ## Server-to-Client (S2C)

View File

@@ -1,17 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/join.json",
"title": "C2S Join",
"description": "Join a channel. Submitted via POST /api/v1/channels/join.",
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Channel name (# prefix optional, server will add it).",
"pattern": "^#?[a-zA-Z0-9_-]+$",
"examples": ["#general", "dev"]
}
},
"required": ["channel"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/join.schema.json",
"title": "JOIN (C2S)",
"description": "Join a channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "JOIN"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Not used"
}
},
"required": [
"command",
"to"
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/kick.schema.json",
"title": "KICK (C2S)",
"description": "Kick user from channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "KICK"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Kick reason"
}
},
"required": [
"command",
"to"
]
}

View File

@@ -1,26 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/mode.json",
"title": "C2S Mode",
"description": "Set channel or user mode flags.",
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Target channel.",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"mode": {
"type": "string",
"description": "Mode string (e.g. +o, -m, +v).",
"pattern": "^[+-][a-zA-Z]+$",
"examples": ["+o", "-m", "+v", "+i"]
},
"target": {
"type": "string",
"description": "Target nick for user modes (e.g. +o alice). Omit for channel modes."
}
},
"required": ["channel", "mode"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/mode.schema.json",
"title": "MODE (C2S)",
"description": "Set/query modes",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "MODE"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Mode params"
}
},
"required": [
"command",
"to"
]
}

View File

@@ -1,18 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/nick.json",
"title": "C2S Nick",
"description": "Change the user's nickname.",
"type": "object",
"properties": {
"nick": {
"type": "string",
"description": "Desired new nickname.",
"minLength": 1,
"maxLength": 32,
"pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$"
}
},
"required": ["nick"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/nick.schema.json",
"title": "NICK (C2S)",
"description": "Request nick change",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "NICK"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Not used"
}
},
"required": [
"command",
"to"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/notice.schema.json",
"title": "NOTICE (C2S)",
"description": "Send a notice (no auto-reply expected)",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "NOTICE"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Text lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -1,22 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/part.json",
"title": "C2S Part",
"description": "Leave a channel. Submitted via DELETE /api/v1/channels/{name}.",
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Channel name to leave.",
"pattern": "^#[a-zA-Z0-9_-]+$",
"examples": ["#general"]
},
"reason": {
"type": "string",
"description": "Optional part reason message.",
"maxLength": 256
}
},
"required": ["channel"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/part.schema.json",
"title": "PART (C2S)",
"description": "Leave a channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PART"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Part message"
}
},
"required": [
"command",
"to"
]
}

View File

@@ -1,14 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/ping.json",
"title": "C2S Ping",
"description": "Client keepalive. Server responds with a pong.",
"type": "object",
"properties": {
"token": {
"type": "string",
"description": "Optional opaque token echoed back in the pong response."
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/ping.schema.json",
"title": "PING (C2S)",
"description": "Client keepalive",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PING"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ping token"
}
},
"required": [
"command"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/privmsg.schema.json",
"title": "PRIVMSG (C2S)",
"description": "Send message to channel or user",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PRIVMSG"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Text lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/pubkey.schema.json",
"title": "PUBKEY (C2S)",
"description": "Announce public signing key",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PUBKEY"
},
"body": {
"type": "object",
"required": [
"alg",
"key"
],
"properties": {
"alg": {
"type": "string",
"description": "Key algorithm (e.g. ed25519)"
},
"key": {
"type": "string",
"description": "Base64-encoded public key"
}
}
}
},
"required": [
"command",
"body"
]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/quit.schema.json",
"title": "QUIT (C2S)",
"description": "Disconnect from server",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "QUIT"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Quit message"
}
},
"required": [
"command"
]
}

View File

@@ -1,22 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/send.json",
"title": "C2S Send",
"description": "Send a message to a channel or user. Submitted via POST /api/v1/messages.",
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Target: channel name (prefixed with #) or nick for DM.",
"examples": ["#general", "alice"]
},
"content": {
"type": "string",
"description": "Message body (UTF-8 text).",
"minLength": 1,
"maxLength": 4096
}
},
"required": ["to", "content"],
"additionalProperties": false
}

View File

@@ -1,21 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/topic.json",
"title": "C2S Topic",
"description": "Set a channel's topic.",
"type": "object",
"properties": {
"channel": {
"type": "string",
"description": "Target channel.",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"topic": {
"type": "string",
"description": "New topic text. Empty string clears the topic.",
"maxLength": 512
}
},
"required": ["channel", "topic"],
"additionalProperties": false
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/c2s/topic.schema.json",
"title": "TOPIC (C2S)",
"description": "Set/query topic",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "TOPIC"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Topic lines"
}
},
"required": [
"command",
"to"
]
}

23
schema/commands/JOIN.json Normal file
View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/commands/JOIN.json",
"title": "JOIN",
"description": "Join a channel. C2S: request to join. S2C: notification that a user joined. RFC 1459 §4.2.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "JOIN" },
"from": {
"type": "string",
"description": "Nick that joined (S2C only)."
},
"to": {
"type": "string",
"description": "Channel name.",
"pattern": "^#[a-zA-Z0-9_-]+$"
}
},
"required": ["command", "to"],
"examples": [
{ "command": "JOIN", "from": "alice", "to": "#general" }
]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/commands/NOTICE.json",
"title": "NOTICE",
"description": "Send a notice. Like PRIVMSG but must not trigger automatic replies. RFC 1459 §4.4.2.",
"$ref": "../message.json",
"properties": {
"command": { "const": "NOTICE" },
"from": { "type": "string" },
"to": {
"type": "string",
"description": "Target: #channel, nick, or * (global)."
},
"body": {
"type": "string",
"description": "Notice text."
}
},
"required": ["command", "to", "body"],
"examples": [
{ "command": "NOTICE", "from": "server.example.com", "to": "*", "body": "Server restarting in 5 minutes" }
]
}

27
schema/commands/PART.json Normal file
View File

@@ -0,0 +1,27 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/commands/PART.json",
"title": "PART",
"description": "Leave a channel. C2S: request to leave. S2C: notification that a user left. RFC 1459 §4.2.2.",
"$ref": "../message.json",
"properties": {
"command": { "const": "PART" },
"from": {
"type": "string",
"description": "Nick that left (S2C only)."
},
"to": {
"type": "string",
"description": "Channel name.",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"body": {
"type": "string",
"description": "Optional part reason."
}
},
"required": ["command", "to"],
"examples": [
{ "command": "PART", "from": "alice", "to": "#general", "body": "later" }
]
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/commands/PRIVMSG.json",
"title": "PRIVMSG",
"description": "Send a message to a channel or user. C2S: client sends to server. S2C: server relays to recipients. RFC 1459 §4.4.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "PRIVMSG" },
"from": {
"type": "string",
"description": "Sender nick (set by server on S2C relay)."
},
"to": {
"type": "string",
"description": "Target: #channel or nick.",
"examples": ["#general", "alice"]
},
"body": {
"type": "string",
"description": "Message text.",
"minLength": 1
}
},
"required": ["command", "to", "body"],
"examples": [
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": "hello world" },
{ "command": "PRIVMSG", "from": "bob", "to": "alice", "body": "hey" }
]
}

46
schema/message.json Normal file
View File

@@ -0,0 +1,46 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/message.json",
"title": "IRC Message Envelope",
"description": "Base envelope for all messages. Mirrors IRC wire format (RFC 1459/2812) encoded as JSON over HTTP. The 'command' field carries either an IRC command name (PRIVMSG, JOIN, etc.) or a three-digit numeric reply code (001, 353, 433, etc.).",
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Server-assigned message ID, monotonically increasing. Present on all server-originated messages."
},
"command": {
"type": "string",
"description": "IRC command name (PRIVMSG, JOIN, NICK, etc.) or three-digit numeric reply code (001, 353, 433, etc.).",
"examples": ["PRIVMSG", "JOIN", "001", "353", "433"]
},
"from": {
"type": "string",
"description": "Source — nick for user messages, server name for server messages. Equivalent to IRC prefix."
},
"to": {
"type": "string",
"description": "Target — channel (#name) or nick. Equivalent to first IRC parameter for most commands."
},
"params": {
"type": "array",
"items": { "type": "string" },
"description": "Additional parameters (used primarily by numeric replies). Equivalent to IRC middle parameters."
},
"body": {
"type": "string",
"description": "Message body / trailing parameter. Equivalent to IRC trailing parameter (after the colon)."
},
"ts": {
"type": "string",
"format": "date-time",
"description": "Server-assigned timestamp (ISO 8601). Not present in original IRC; added for HTTP transport."
},
"meta": {
"type": "object",
"description": "Extensible metadata (signatures, rich content hints, etc.). Not present in original IRC.",
"additionalProperties": true
}
},
"required": ["command"]
}

View File

@@ -0,0 +1,67 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/message.schema.json",
"title": "Chat Message Envelope",
"description": "Base message envelope. Bodies MUST be objects or arrays (never raw strings) for deterministic canonicalization (RFC 8785 JCS) and signing.",
"type": "object",
"required": [
"command"
],
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Server-assigned UUID"
},
"ts": {
"type": "string",
"format": "date-time",
"description": "Server-assigned timestamp (ISO 8601)"
},
"command": {
"type": "string",
"description": "IRC command name or numeric reply code"
},
"from": {
"type": "string",
"description": "Sender nick or server name"
},
"to": {
"type": "string",
"description": "Destination: channel (#foo) or nick"
},
"params": {
"type": "array",
"items": {
"type": "string"
},
"description": "Additional IRC-style parameters"
},
"body": {
"oneOf": [
{
"type": "array"
},
{
"type": "object"
}
],
"description": "Message body (array or object, never raw string)"
},
"meta": {
"type": "object",
"description": "Extensible metadata",
"properties": {
"sig": {
"type": "string",
"description": "Cryptographic signature (base64)"
},
"alg": {
"type": "string",
"description": "Signature algorithm (e.g. ed25519)"
}
}
}
},
"additionalProperties": false
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/001.schema.json",
"title": "001 RPL_WELCOME (S2C)",
"description": "Welcome message after registration",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "001"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/002.schema.json",
"title": "002 RPL_YOURHOST (S2C)",
"description": "Server host information",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "002"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/322.schema.json",
"title": "322 RPL_LIST (S2C)",
"description": "Channel list entry",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "322"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/353.schema.json",
"title": "353 RPL_NAMREPLY (S2C)",
"description": "Names list for a channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "353"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/366.schema.json",
"title": "366 RPL_ENDOFNAMES (S2C)",
"description": "End of names list",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "366"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/372.schema.json",
"title": "372 RPL_MOTD (S2C)",
"description": "Message of the day line",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "372"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/375.schema.json",
"title": "375 RPL_MOTDSTART (S2C)",
"description": "Start of MOTD",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "375"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/376.schema.json",
"title": "376 RPL_ENDOFMOTD (S2C)",
"description": "End of MOTD",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "376"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/401.schema.json",
"title": "401 ERR_NOSUCHNICK (S2C)",
"description": "No such nick or channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "401"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/403.schema.json",
"title": "403 ERR_NOSUCHCHANNEL (S2C)",
"description": "No such channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "403"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/433.schema.json",
"title": "433 ERR_NICKNAMEINUSE (S2C)",
"description": "Nickname already in use",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "433"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Response lines"
}
},
"required": [
"command",
"to",
"body"
]
}

View File

@@ -1,37 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/dm.json",
"title": "S2C Direct Message",
"description": "A direct message delivered via the unified message stream.",
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Server-assigned message ID."
},
"type": {
"const": "dm"
},
"ts": {
"type": "string",
"format": "date-time"
},
"from": {
"type": "string",
"description": "Sender nick."
},
"to": {
"type": "string",
"description": "Recipient nick."
},
"content": {
"type": "string",
"description": "Message body."
},
"meta": {
"type": "object",
"additionalProperties": true
}
},
"required": ["id", "type", "ts", "from", "to", "content"]
}

View File

@@ -1,33 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/error.json",
"title": "S2C Error",
"description": "Error message delivered via the message stream.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "error"
},
"ts": {
"type": "string",
"format": "date-time"
},
"code": {
"type": "string",
"description": "Machine-readable error code.",
"examples": ["nick_in_use", "no_such_channel", "not_on_channel", "permission_denied"]
},
"content": {
"type": "string",
"description": "Human-readable error message."
},
"channel": {
"type": "string",
"description": "Related channel, if applicable."
}
},
"required": ["id", "type", "ts", "code", "content"]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/error.schema.json",
"title": "ERROR (S2C)",
"description": "Server error",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "ERROR"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Error lines"
}
},
"required": [
"command",
"body"
]
}

View File

@@ -1,29 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/join.json",
"title": "S2C Join",
"description": "A user joined a channel.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "join"
},
"ts": {
"type": "string",
"format": "date-time"
},
"nick": {
"type": "string",
"description": "The nick that joined."
},
"channel": {
"type": "string",
"description": "The channel joined.",
"pattern": "^#[a-zA-Z0-9_-]+$"
}
},
"required": ["id", "type", "ts", "nick", "channel"]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/join.schema.json",
"title": "JOIN (S2C)",
"description": "User joined a channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "JOIN"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Not used"
}
},
"required": [
"command",
"from",
"to"
]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/kick.schema.json",
"title": "KICK (S2C)",
"description": "User kicked from channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "KICK"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Kick reason"
}
},
"required": [
"command",
"from",
"to"
]
}

View File

@@ -1,40 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/message.json",
"title": "S2C Message",
"description": "A channel message delivered via the unified message stream.",
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Server-assigned message ID, monotonically increasing."
},
"type": {
"const": "message"
},
"ts": {
"type": "string",
"format": "date-time",
"description": "Server-assigned timestamp (ISO 8601)."
},
"from": {
"type": "string",
"description": "Sender nick."
},
"channel": {
"type": "string",
"description": "Channel the message was sent to.",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"content": {
"type": "string",
"description": "Message body."
},
"meta": {
"type": "object",
"description": "Extensible metadata (signatures, rich content, etc.).",
"additionalProperties": true
}
},
"required": ["id", "type", "ts", "from", "channel", "content"]
}

View File

@@ -1,37 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/mode.json",
"title": "S2C Mode",
"description": "A channel or user mode was changed.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "mode"
},
"ts": {
"type": "string",
"format": "date-time"
},
"nick": {
"type": "string",
"description": "The nick that set the mode."
},
"channel": {
"type": "string",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"mode": {
"type": "string",
"description": "Mode string applied (e.g. +o, -m).",
"pattern": "^[+-][a-zA-Z]+$"
},
"target": {
"type": "string",
"description": "Target nick for user modes. Absent for channel modes."
}
},
"required": ["id", "type", "ts", "nick", "channel", "mode"]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/mode.schema.json",
"title": "MODE (S2C)",
"description": "Mode change notification",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "MODE"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Mode params"
}
},
"required": [
"command",
"from",
"to"
]
}

View File

@@ -1,28 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/nick.json",
"title": "S2C Nick",
"description": "A user changed their nickname.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "nick"
},
"ts": {
"type": "string",
"format": "date-time"
},
"oldNick": {
"type": "string",
"description": "Previous nickname."
},
"newNick": {
"type": "string",
"description": "New nickname."
}
},
"required": ["id", "type", "ts", "oldNick", "newNick"]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/nick.schema.json",
"title": "NICK (S2C)",
"description": "User changed nick",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "NICK"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Not used"
}
},
"required": [
"command",
"from",
"to"
]
}

View File

@@ -1,32 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/notice.json",
"title": "S2C Notice",
"description": "A server notice. May be targeted to a channel or user, or global.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "notice"
},
"ts": {
"type": "string",
"format": "date-time"
},
"from": {
"type": "string",
"description": "Origin (server name or nick)."
},
"channel": {
"type": "string",
"description": "Target channel, if channel-scoped."
},
"content": {
"type": "string",
"description": "Notice text."
}
},
"required": ["id", "type", "ts", "content"]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/notice.schema.json",
"title": "NOTICE (S2C)",
"description": "Server or user notice",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "NOTICE"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Text lines"
}
},
"required": [
"command",
"body"
]
}

View File

@@ -1,32 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/part.json",
"title": "S2C Part",
"description": "A user left a channel.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "part"
},
"ts": {
"type": "string",
"format": "date-time"
},
"nick": {
"type": "string",
"description": "The nick that left."
},
"channel": {
"type": "string",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"reason": {
"type": "string",
"description": "Optional part reason."
}
},
"required": ["id", "type", "ts", "nick", "channel"]
}

View File

@@ -0,0 +1,24 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/part.schema.json",
"title": "PART (S2C)",
"description": "User left a channel",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PART"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Part message"
}
},
"required": [
"command",
"from",
"to"
]
}

View File

@@ -1,24 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/pong.json",
"title": "S2C Pong",
"description": "Keepalive response to a client ping.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "pong"
},
"ts": {
"type": "string",
"format": "date-time"
},
"token": {
"type": "string",
"description": "Echoed token from the client's ping, if provided."
}
},
"required": ["id", "type", "ts"]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/pong.schema.json",
"title": "PONG (S2C)",
"description": "Server keepalive response",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PONG"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Pong token"
}
},
"required": [
"command"
]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/privmsg.schema.json",
"title": "PRIVMSG (S2C)",
"description": "Relayed message from a user",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PRIVMSG"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Text lines"
}
},
"required": [
"command",
"from",
"to",
"body"
]
}

View File

@@ -0,0 +1,34 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/pubkey.schema.json",
"title": "PUBKEY (S2C)",
"description": "Relayed public key announcement",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PUBKEY"
},
"body": {
"type": "object",
"required": [
"alg",
"key"
],
"properties": {
"alg": {
"type": "string",
"description": "Key algorithm (e.g. ed25519)"
},
"key": {
"type": "string",
"description": "Base64-encoded public key"
}
}
}
},
"required": [
"command",
"from",
"body"
]
}

View File

@@ -1,28 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/quit.json",
"title": "S2C Quit",
"description": "A user disconnected from the server.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "quit"
},
"ts": {
"type": "string",
"format": "date-time"
},
"nick": {
"type": "string",
"description": "The nick that quit."
},
"reason": {
"type": "string",
"description": "Optional quit reason."
}
},
"required": ["id", "type", "ts", "nick"]
}

View File

@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/quit.schema.json",
"title": "QUIT (S2C)",
"description": "User disconnected",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "QUIT"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Quit message"
}
},
"required": [
"command",
"from"
]
}

View File

@@ -1,29 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/system.json",
"title": "S2C System",
"description": "Server system message (MOTD, maintenance notices, etc.).",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "system"
},
"ts": {
"type": "string",
"format": "date-time"
},
"content": {
"type": "string",
"description": "System message text."
},
"code": {
"type": "string",
"description": "Optional machine-readable system message code.",
"examples": ["motd", "maintenance", "server_restart"]
}
},
"required": ["id", "type", "ts", "content"]
}

View File

@@ -1,32 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/topic.json",
"title": "S2C Topic",
"description": "A channel topic was changed.",
"type": "object",
"properties": {
"id": {
"type": "integer"
},
"type": {
"const": "topic"
},
"ts": {
"type": "string",
"format": "date-time"
},
"nick": {
"type": "string",
"description": "The nick that changed the topic."
},
"channel": {
"type": "string",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"topic": {
"type": "string",
"description": "New topic text."
}
},
"required": ["id", "type", "ts", "nick", "channel", "topic"]
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2c/topic.schema.json",
"title": "TOPIC (S2C)",
"description": "Topic change notification",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "TOPIC"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Topic lines"
}
},
"required": [
"command",
"from",
"to",
"body"
]
}

View File

@@ -1,41 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/link.json",
"title": "S2S Link",
"description": "Server link establishment request/response.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"const": "link"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Requesting server name."
},
"version": {
"type": "string",
"description": "Protocol version of the requesting server."
},
"auth": {
"type": "string",
"description": "HMAC signature over the link request using the shared federation key."
},
"capabilities": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of supported protocol capabilities.",
"examples": [["relay", "sync", "presence"]]
}
},
"required": ["id", "type", "ts", "origin", "auth"]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/link.schema.json",
"title": "LINK (S2S)",
"description": "Establish server link",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "LINK"
},
"body": {
"type": "object",
"description": "Link parameters"
}
},
"required": [
"command",
"body"
]
}

View File

@@ -1,29 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/ping.json",
"title": "S2S Ping",
"description": "Inter-server keepalive.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"const": "ping"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Pinging server."
},
"token": {
"type": "string",
"description": "Opaque token to be echoed in pong."
}
},
"required": ["id", "type", "ts", "origin"]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/ping.schema.json",
"title": "PING (S2S)",
"description": "Server-to-server keepalive",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PING"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Ping token"
}
},
"required": [
"command"
]
}

View File

@@ -1,29 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/pong.json",
"title": "S2S Pong",
"description": "Inter-server keepalive response.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"const": "pong"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Responding server."
},
"token": {
"type": "string",
"description": "Echoed token from the ping."
}
},
"required": ["id", "type", "ts", "origin"]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/pong.schema.json",
"title": "PONG (S2S)",
"description": "Server-to-server keepalive response",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "PONG"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Pong token"
}
},
"required": [
"command"
]
}

View File

@@ -1,49 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/relay.json",
"title": "S2S Relay",
"description": "A message relayed from a remote server in the federation.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid",
"description": "Message UUID, globally unique across the federation."
},
"type": {
"const": "relay"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Originating server name."
},
"message": {
"type": "object",
"description": "The original S2C message being relayed. Preserves the original type, from, channel, content, etc.",
"properties": {
"type": {
"type": "string"
},
"from": {
"type": "string"
},
"channel": {
"type": "string"
},
"content": {
"type": "string"
},
"ts": {
"type": "string",
"format": "date-time"
}
},
"required": ["type", "from"]
}
},
"required": ["id", "type", "ts", "origin", "message"]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/relay.schema.json",
"title": "RELAY (S2S)",
"description": "Relay message to linked server",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "RELAY"
},
"body": {
"type": "object",
"description": "Wrapped message"
}
},
"required": [
"command",
"body"
]
}

View File

@@ -1,69 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/sync.json",
"title": "S2S Sync",
"description": "State synchronization between federated servers. Sent after link establishment to share channel and user state.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"const": "sync"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Server sending the sync."
},
"channels": {
"type": "array",
"description": "Channels on the origin server.",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"topic": {
"type": "string"
},
"modes": {
"type": "string"
},
"members": {
"type": "array",
"items": {
"type": "string"
},
"description": "List of nicks in the channel."
}
},
"required": ["name"]
}
},
"users": {
"type": "array",
"description": "Users on the origin server.",
"items": {
"type": "object",
"properties": {
"nick": {
"type": "string"
},
"server": {
"type": "string",
"description": "Home server for this user."
}
},
"required": ["nick"]
}
}
},
"required": ["id", "type", "ts", "origin"]
}

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/sync.schema.json",
"title": "SYNC (S2S)",
"description": "Synchronize state between servers",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "SYNC"
},
"body": {
"type": "object",
"description": "State data"
}
},
"required": [
"command",
"body"
]
}

View File

@@ -1,30 +0,0 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/unlink.json",
"title": "S2S Unlink",
"description": "Server link teardown notification.",
"type": "object",
"properties": {
"id": {
"type": "string",
"format": "uuid"
},
"type": {
"const": "unlink"
},
"ts": {
"type": "string",
"format": "date-time"
},
"origin": {
"type": "string",
"description": "Server initiating the unlink."
},
"reason": {
"type": "string",
"description": "Optional reason for the unlink.",
"examples": ["shutdown", "configuration change", "timeout"]
}
},
"required": ["id", "type", "ts", "origin"]
}

View File

@@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/s2s/unlink.schema.json",
"title": "UNLINK (S2S)",
"description": "Tear down server link",
"$ref": "../message.schema.json",
"properties": {
"command": {
"const": "UNLINK"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Unlink reason"
}
},
"required": [
"command"
]
}