refactor: structured body (array|object, never string) for canonicalization

Message bodies are always arrays of strings (text lines) or objects
(structured data like PUBKEY). Never raw strings. This enables:
- Multiline messages without escape sequences
- Deterministic JSON canonicalization (RFC 8785 JCS) for signing
- Structured data where needed

Update all schemas: body fields use array type with string items.
Update message.json envelope: body is oneOf[array, object], id is UUID.
Update README: message envelope table, examples, and canonicalization docs.
Update schema/README.md: field types, examples with array bodies.
This commit is contained in:
clawbot
2026-02-10 10:36:02 -08:00
parent dfb1636be5
commit ab70f889a6
22 changed files with 446 additions and 171 deletions

139
README.md
View File

@@ -158,86 +158,109 @@ Every message is a JSON object with these fields:
| `from` | string | | Sender nick or server name |
| `to` | string | | Destination: `#channel` or nick |
| `params` | array\<string\> | | Additional IRC-style parameters |
| `body` | array \| object | | Structured body (never a raw string) |
| `meta` | object | | Extensible metadata (signatures, etc.) |
| `id` | string (uuid) | | Server-assigned message ID |
| `body` | array \| object | | Structured body (never a raw string — see below) |
| `id` | string (uuid) | | Server-assigned message UUID |
| `ts` | string | | Server-assigned ISO 8601 timestamp |
| `meta` | object | | Extensible metadata (signatures, hashes, etc.) |
**Important:** Message bodies MUST be objects or arrays, never raw strings.
This enables:
- Multiline messages (array of lines)
- Deterministic canonicalization for hashing/signing (RFC 8785 JCS)
- Structured data where needed (e.g. PUBKEY)
**Important:** Message bodies are **structured objects or arrays**, never raw
strings. This is a deliberate departure from IRC wire format that enables:
- **Multiline messages** — body is a list of lines, no escape sequences
- **Deterministic canonicalization** — for hashing and signing (see below)
- **Structured data** — commands like PUBKEY carry key material as objects
For text messages, `body` is an array of strings (one per line):
```json
{"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": ["hello world"]}
{"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": ["line one", "line two"]}
```
For numeric replies with text trailing parameters:
```json
{"command": "001", "to": "nick", "body": ["Welcome to the network, nick"]}
{"command": "353", "to": "nick", "params": ["=", "#channel"], "body": ["@op1 alice bob"]}
```
For structured data (keys, etc.), `body` is an object:
```json
{"command": "PUBKEY", "from": "nick", "body": {"alg": "ed25519", "key": "base64..."}}
```
#### IRC Command Mapping
**Client-to-Server (C2S):**
**Commands (C2S and S2C):**
| Command | Description |
|----------|-------------|
| PRIVMSG | Send message to channel or user |
| NOTICE | Send notice (no auto-reply expected) |
| JOIN | Join a channel (creates it if nonexistent) |
| PART | Leave a channel |
| QUIT | Disconnect from server |
| NICK | Change nickname |
| MODE | Set/query channel or user modes |
| TOPIC | Set/query channel topic |
| KICK | Kick a user from a channel |
| PING | Client keepalive |
| PUBKEY | Announce public signing key |
| Command | RFC | Description |
|-----------|--------------|--------------------------------------|
| `PRIVMSG` | 1459 §4.4.1 | Message to channel or user |
| `NOTICE` | 1459 §4.4.2 | Notice (must not trigger auto-reply) |
| `JOIN` | 1459 §4.2.1 | Join a channel |
| `PART` | 1459 §4.2.2 | Leave a channel |
| `QUIT` | 1459 §4.1.6 | Disconnect from server |
| `NICK` | 1459 §4.1.2 | Change nickname |
| `MODE` | 1459 §4.2.3 | Set/query channel or user modes |
| `TOPIC` | 1459 §4.2.4 | Set/query channel topic |
| `KICK` | 1459 §4.2.8 | Kick user from channel |
| `PING` | 1459 §4.6.2 | Keepalive |
| `PONG` | 1459 §4.6.3 | Keepalive response |
| `PUBKEY` | (extension) | Announce/relay signing public key |
**Server-to-Client (S2C):**
All C2S commands may be echoed back as S2C (relayed to other users), plus:
| Command | Description |
|----------|-------------|
| PONG | Server keepalive response |
| PUBKEY | Relayed public key from another user |
| ERROR | Server error message |
All C2S commands may be relayed S2C to other users (e.g. JOIN, PART, PRIVMSG).
**Numeric Reply Codes (S2C):**
| Code | Name | Description |
|------|-------------------|-------------|
| 001 | RPL_WELCOME | Welcome after session creation |
| 002 | RPL_YOURHOST | Server host information |
| 322 | RPL_LIST | Channel list entry |
| 353 | RPL_NAMREPLY | Names list for a channel |
| 366 | RPL_ENDOFNAMES | End of names list |
| 372 | RPL_MOTD | Message of the day line |
| 375 | RPL_MOTDSTART | Start of MOTD |
| 376 | RPL_ENDOFMOTD | End of MOTD |
| 401 | ERR_NOSUCHNICK | No such nick or channel |
| 403 | ERR_NOSUCHCHANNEL | No such channel |
| 433 | ERR_NICKNAMEINUSE | Nickname already in use |
| Code | Name | Description |
|------|----------------------|-------------|
| 001 | RPL_WELCOME | Welcome after session creation |
| 002 | RPL_YOURHOST | Server host information |
| 003 | RPL_CREATED | Server creation date |
| 004 | RPL_MYINFO | Server info and modes |
| 322 | RPL_LIST | Channel list entry |
| 323 | RPL_LISTEND | End of channel list |
| 332 | RPL_TOPIC | Channel topic |
| 353 | RPL_NAMREPLY | Channel member list |
| 366 | RPL_ENDOFNAMES | End of NAMES list |
| 372 | RPL_MOTD | MOTD line |
| 375 | RPL_MOTDSTART | Start of MOTD |
| 376 | RPL_ENDOFMOTD | End of MOTD |
| 401 | ERR_NOSUCHNICK | No such nick/channel |
| 403 | ERR_NOSUCHCHANNEL | No such channel |
| 433 | ERR_NICKNAMEINUSE | Nickname already in use |
| 442 | ERR_NOTONCHANNEL | Not on that channel |
| 482 | ERR_CHANOPRIVSNEEDED | Not channel operator |
**Server-to-Server (S2S):**
**Server-to-Server (Federation):**
| Command | Description |
|---------|-------------|
| RELAY | Relay message to linked server |
| LINK | Establish server link |
| UNLINK | Tear down server link |
| SYNC | Synchronize state between servers |
| PING | Server-to-server keepalive |
| PONG | Server-to-server keepalive response |
Federated servers use the same IRC commands. After link establishment, servers
exchange a burst of JOIN, NICK, TOPIC, and MODE commands to sync state.
PING/PONG serve as inter-server keepalives.
#### Message Examples
```json
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["hello world"], "meta": {"sig": "base64...", "alg": "ed25519"}}
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["hello world"]}
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["line one", "line two"]}
{"command": "PRIVMSG", "from": "alice", "to": "#general", "body": ["line one", "line two"], "meta": {"sig": "base64...", "alg": "ed25519"}}
{"command": "PRIVMSG", "from": "alice", "to": "bob", "body": ["hey, DM"]}
{"command": "JOIN", "from": "bob", "to": "#general"}
{"command": "PART", "from": "bob", "to": "#general", "body": ["later"]}
{"command": "NICK", "from": "oldnick", "body": ["newnick"]}
{"command": "001", "to": "alice", "body": ["Welcome to the network, alice"]}
{"command": "353", "to": "alice", "params": ["=", "#general"], "body": ["alice", "bob", "@charlie"]}
{"command": "353", "to": "alice", "params": ["=", "#general"], "body": ["@op1 alice bob +voiced1"]}
{"command": "JOIN", "from": "bob", "to": "#general", "body": []}
{"command": "433", "to": "*", "params": ["alice"], "body": ["Nickname is already in use"]}
{"command": "ERROR", "body": ["Closing link: connection timeout"]}
{"command": "PUBKEY", "from": "alice", "body": {"alg": "ed25519", "key": "base64..."}}
```
#### JSON Schemas

View File

@@ -11,24 +11,33 @@ to IRC wire format:
```
IRC: :nick PRIVMSG #channel :hello world
JSON: {"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": "hello world"}
JSON: {"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": ["hello world"]}
IRC: :server 353 nick = #channel :user1 @op1 +voice1
JSON: {"command": "353", "to": "nick", "params": ["=", "#channel"], "body": "user1 @op1 +voice1"}
JSON: {"command": "353", "to": "nick", "params": ["=", "#channel"], "body": ["user1 @op1 +voice1"]}
Multiline: {"command": "PRIVMSG", "to": "#ch", "body": ["line 1", "line 2"]}
Structured: {"command": "PUBKEY", "body": {"alg": "ed25519", "key": "base64..."}}
```
Common fields (see `message.json` for full schema):
| Field | Type | Description |
|-----------|----------|-------------------------------------------------------|
| `id` | integer | Server-assigned ID (monotonically increasing) |
| `command` | string | IRC command or 3-digit numeric code |
| `from` | string | Source nick or server name (IRC prefix) |
| `to` | string | Target: #channel or nick |
| `params` | string[] | Middle parameters (mainly for numerics) |
| `body` | string | Trailing parameter (message text) |
| `ts` | string | ISO 8601 timestamp (server-assigned, not in raw IRC) |
| `meta` | object | Extensible metadata (not in raw IRC) |
| Field | Type | Description |
|-----------|----------------|------------------------------------------------------|
| `id` | string (uuid) | Server-assigned message UUID |
| `command` | string | IRC command or 3-digit numeric code |
| `from` | string | Source nick or server name (IRC prefix) |
| `to` | string | Target: #channel or nick |
| `params` | string[] | Middle parameters (mainly for numerics) |
| `body` | array \| object | Structured body — never a raw string (see below) |
| `ts` | string | ISO 8601 timestamp (server-assigned, not in raw IRC) |
| `meta` | object | Extensible metadata (signatures, hashes, etc.) |
**Structured bodies:** `body` is always an array of strings (for text) or an
object (for structured data like PUBKEY). Never a raw string. This enables:
- Multiline messages without escape sequences
- Deterministic canonicalization via RFC 8785 JCS for signing
- Structured data where needed
## Commands

View File

@@ -6,15 +6,8 @@
"$ref": "../message.json",
"properties": {
"command": { "const": "KICK" },
"from": {
"type": "string",
"description": "Nick that performed the kick."
},
"to": {
"type": "string",
"description": "Channel name.",
"pattern": "^#[a-zA-Z0-9_-]+$"
},
"from": { "type": "string", "description": "Nick that performed the kick." },
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
"params": {
"type": "array",
"items": { "type": "string" },
@@ -23,12 +16,14 @@
"maxItems": 1
},
"body": {
"type": "string",
"description": "Optional kick reason."
"type": "array",
"items": { "type": "string" },
"description": "Optional kick reason.",
"maxItems": 1
}
},
"required": ["command", "to", "params"],
"examples": [
{ "command": "KICK", "from": "op1", "to": "#general", "params": ["troll"], "body": "Behave" }
{ "command": "KICK", "from": "op1", "to": "#general", "params": ["troll"], "body": ["Behave"] }
]
}

View File

@@ -7,10 +7,16 @@
"properties": {
"command": { "const": "NICK" },
"from": { "type": "string", "description": "Old nick (S2C)." },
"body": { "type": "string", "description": "New nick.", "minLength": 1, "maxLength": 32, "pattern": "^[a-zA-Z][a-zA-Z0-9_-]*$" }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "New nick (single-element array).",
"minItems": 1,
"maxItems": 1
}
},
"required": ["command", "body"],
"examples": [
{ "command": "NICK", "from": "oldnick", "body": "newnick" }
{ "command": "NICK", "from": "oldnick", "body": ["newnick"] }
]
}

View File

@@ -8,10 +8,14 @@
"command": { "const": "NOTICE" },
"from": { "type": "string" },
"to": { "type": "string", "description": "Target: #channel, nick, or * (global)." },
"body": { "type": "string", "description": "Notice text." }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "Notice text lines."
}
},
"required": ["command", "to", "body"],
"examples": [
{ "command": "NOTICE", "from": "server.example.com", "to": "*", "body": "Server restarting in 5 minutes" }
{ "command": "NOTICE", "from": "server.example.com", "to": "*", "body": ["Server restarting in 5 minutes"] }
]
}

View File

@@ -8,10 +8,15 @@
"command": { "const": "PART" },
"from": { "type": "string", "description": "Nick that left (S2C)." },
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
"body": { "type": "string", "description": "Optional part reason." }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "Optional part reason.",
"maxItems": 1
}
},
"required": ["command", "to"],
"examples": [
{ "command": "PART", "from": "alice", "to": "#general", "body": "later" }
{ "command": "PART", "from": "alice", "to": "#general", "body": ["later"] }
]
}

View File

@@ -7,12 +7,14 @@
"properties": {
"command": { "const": "PING" },
"body": {
"type": "string",
"description": "Opaque token to be echoed in PONG."
"type": "array",
"items": { "type": "string" },
"description": "Opaque token to be echoed in PONG (single-element array).",
"maxItems": 1
}
},
"required": ["command"],
"examples": [
{ "command": "PING", "body": "1707580000" }
{ "command": "PING", "body": ["1707580000"] }
]
}

View File

@@ -6,17 +6,16 @@
"$ref": "../message.json",
"properties": {
"command": { "const": "PONG" },
"from": {
"type": "string",
"description": "Responding server name."
},
"from": { "type": "string", "description": "Responding server name." },
"body": {
"type": "string",
"description": "Echoed token from PING."
"type": "array",
"items": { "type": "string" },
"description": "Echoed token from PING (single-element array).",
"maxItems": 1
}
},
"required": ["command"],
"examples": [
{ "command": "PONG", "from": "server.example.com", "body": "1707580000" }
{ "command": "PONG", "from": "server.example.com", "body": ["1707580000"] }
]
}

View File

@@ -8,11 +8,17 @@
"command": { "const": "PRIVMSG" },
"from": { "type": "string", "description": "Sender nick (set by server on relay)." },
"to": { "type": "string", "description": "Target: #channel or nick.", "examples": ["#general", "alice"] },
"body": { "type": "string", "description": "Message text.", "minLength": 1 }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "Message lines. One string per line.",
"minItems": 1
}
},
"required": ["command", "to", "body"],
"examples": [
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": "hello world" },
{ "command": "PRIVMSG", "from": "bob", "to": "alice", "body": "hey" }
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": ["hello world"] },
{ "command": "PRIVMSG", "from": "bob", "to": "#general", "body": ["line one", "line two"] },
{ "command": "PRIVMSG", "from": "bob", "to": "alice", "body": ["hey"], "meta": { "sig": "base64...", "alg": "ed25519" } }
]
}

View File

@@ -7,10 +7,15 @@
"properties": {
"command": { "const": "QUIT" },
"from": { "type": "string", "description": "Nick that quit." },
"body": { "type": "string", "description": "Optional quit reason." }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "Optional quit reason.",
"maxItems": 1
}
},
"required": ["command", "from"],
"examples": [
{ "command": "QUIT", "from": "alice", "body": "Connection reset" }
{ "command": "QUIT", "from": "alice", "body": ["Connection reset"] }
]
}

View File

@@ -8,10 +8,15 @@
"command": { "const": "TOPIC" },
"from": { "type": "string", "description": "Nick that changed the topic (S2C)." },
"to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" },
"body": { "type": "string", "description": "New topic text. Empty string clears the topic.", "maxLength": 512 }
"body": {
"type": "array",
"items": { "type": "string" },
"description": "New topic text (single-element array). Empty array clears the topic.",
"maxItems": 1
}
},
"required": ["command", "to"],
"examples": [
{ "command": "TOPIC", "from": "alice", "to": "#general", "body": "Welcome to the chat" }
{ "command": "TOPIC", "from": "alice", "to": "#general", "body": ["Welcome to the chat"] }
]
}

View File

@@ -6,8 +6,9 @@
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "Server-assigned message ID, monotonically increasing. Present on all server-originated messages."
"type": "string",
"format": "uuid",
"description": "Server-assigned message UUID. Present on all server-originated messages."
},
"command": {
"type": "string",
@@ -28,8 +29,19 @@
"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)."
"oneOf": [
{
"type": "array",
"items": { "type": "string" },
"description": "Array of strings (one per line for text messages)."
},
{
"type": "object",
"description": "Structured data (e.g. PUBKEY key material).",
"additionalProperties": true
}
],
"description": "Message body. MUST be an array or object, never a raw string. Arrays represent lines of text; objects carry structured data. This enables deterministic canonicalization (RFC 8785 JCS) for signing."
},
"ts": {
"type": "string",
@@ -38,7 +50,21 @@
},
"meta": {
"type": "object",
"description": "Extensible metadata (signatures, rich content hints, etc.). Not present in original IRC.",
"description": "Extensible metadata. Used for signatures (meta.sig, meta.alg), hashes (meta.hash), and client extensions.",
"properties": {
"sig": {
"type": "string",
"description": "Base64-encoded cryptographic signature over the canonical message form."
},
"alg": {
"type": "string",
"description": "Signature algorithm (e.g. 'ed25519')."
},
"hash": {
"type": "string",
"description": "Hash of the canonical message form (e.g. 'sha256:base64...')."
}
},
"additionalProperties": true
}
},

View File

@@ -2,19 +2,36 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/001.json",
"title": "001 RPL_WELCOME",
"description": "Welcome message sent after successful session creation. RFC 2812 §5.1.",
"description": "Welcome message sent after successful session creation. RFC 2812 \u00a75.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "001" },
"to": { "type": "string", "description": "Target nick." },
"command": {
"const": "001"
},
"to": {
"type": "string",
"description": "Target nick."
},
"body": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "Welcome text lines."
}
},
"required": ["command", "to", "body"],
"required": [
"command",
"to",
"body"
],
"examples": [
{ "command": "001", "to": "alice", "body": ["Welcome to the network, alice"] }
{
"command": "001",
"to": "alice",
"body": [
"Welcome to the network, alice"
]
}
]
}

View File

@@ -2,19 +2,35 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/002.json",
"title": "002 RPL_YOURHOST",
"description": "Server host info sent after session creation. RFC 2812 §5.1.",
"description": "Server host info sent after session creation. RFC 2812 \u00a75.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "002" },
"to": { "type": "string" },
"command": {
"const": "002"
},
"to": {
"type": "string"
},
"body": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "Host info lines."
}
},
"required": ["command", "to", "body"],
"required": [
"command",
"to",
"body"
],
"examples": [
{ "command": "002", "to": "alice", "body": ["Your host is chat.example.com, running version 0.1.0"] }
{
"command": "002",
"to": "alice",
"body": [
"Your host is chat.example.com, running version 0.1.0"
]
}
]
}

View File

@@ -2,12 +2,26 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/323.json",
"title": "323 RPL_LISTEND",
"description": "End of channel list. RFC 1459 §6.2.",
"description": "End of channel list. RFC 1459 \u00a76.2.",
"$ref": "../message.json",
"properties": {
"command": { "const": "323" },
"to": { "type": "string" },
"body": { "const": "End of /LIST" }
"command": {
"const": "323"
},
"to": {
"type": "string"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "End of /LIST",
"maxItems": 1
}
},
"required": ["command", "to"]
"required": [
"command",
"to"
]
}

View File

@@ -2,17 +2,34 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/366.json",
"title": "366 RPL_ENDOFNAMES",
"description": "End of NAMES list. RFC 1459 §6.2.",
"description": "End of NAMES list. RFC 1459 \u00a76.2.",
"$ref": "../message.json",
"properties": {
"command": { "const": "366" },
"to": { "type": "string" },
"command": {
"const": "366"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[channel]."
},
"body": { "const": "End of /NAMES list" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "End of /NAMES list",
"maxItems": 1
}
},
"required": ["command", "to", "params"]
"required": [
"command",
"to",
"params"
]
}

View File

@@ -2,12 +2,26 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/376.json",
"title": "376 RPL_ENDOFMOTD",
"description": "End of MOTD. RFC 2812 §5.1.",
"description": "End of MOTD. RFC 2812 \u00a75.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "376" },
"to": { "type": "string" },
"body": { "const": "End of /MOTD command" }
"command": {
"const": "376"
},
"to": {
"type": "string"
},
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "End of /MOTD command",
"maxItems": 1
}
},
"required": ["command", "to"]
"required": [
"command",
"to"
]
}

View File

@@ -2,20 +2,46 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/401.json",
"title": "401 ERR_NOSUCHNICK",
"description": "No such nick/channel. RFC 1459 §6.1.",
"description": "No such nick/channel. RFC 1459 \u00a76.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "401" },
"to": { "type": "string" },
"command": {
"const": "401"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[target_nick]."
},
"body": { "const": "No such nick/channel" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "No such nick/channel",
"maxItems": 1
}
},
"required": ["command", "to", "params"],
"required": [
"command",
"to",
"params"
],
"examples": [
{ "command": "401", "to": "alice", "params": ["bob"], "body": "No such nick/channel" }
{
"command": "401",
"to": "alice",
"params": [
"bob"
],
"body": [
"No such nick/channel"
]
}
]
}

View File

@@ -2,20 +2,46 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/403.json",
"title": "403 ERR_NOSUCHCHANNEL",
"description": "No such channel. RFC 1459 §6.1.",
"description": "No such channel. RFC 1459 \u00a76.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "403" },
"to": { "type": "string" },
"command": {
"const": "403"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[channel_name]."
},
"body": { "const": "No such channel" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "No such channel",
"maxItems": 1
}
},
"required": ["command", "to", "params"],
"required": [
"command",
"to",
"params"
],
"examples": [
{ "command": "403", "to": "alice", "params": ["#nonexistent"], "body": "No such channel" }
{
"command": "403",
"to": "alice",
"params": [
"#nonexistent"
],
"body": [
"No such channel"
]
}
]
}

View File

@@ -2,20 +2,46 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/433.json",
"title": "433 ERR_NICKNAMEINUSE",
"description": "Nickname is already in use. RFC 1459 §6.1.",
"description": "Nickname is already in use. RFC 1459 \u00a76.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "433" },
"to": { "type": "string" },
"command": {
"const": "433"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[requested_nick]."
},
"body": { "const": "Nickname is already in use" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "Nickname is already in use",
"maxItems": 1
}
},
"required": ["command", "to", "params"],
"required": [
"command",
"to",
"params"
],
"examples": [
{ "command": "433", "to": "*", "params": ["alice"], "body": "Nickname is already in use" }
{
"command": "433",
"to": "*",
"params": [
"alice"
],
"body": [
"Nickname is already in use"
]
}
]
}

View File

@@ -2,17 +2,34 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/442.json",
"title": "442 ERR_NOTONCHANNEL",
"description": "You're not on that channel. RFC 1459 §6.1.",
"description": "You're not on that channel. RFC 1459 \u00a76.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "442" },
"to": { "type": "string" },
"command": {
"const": "442"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[channel]."
},
"body": { "const": "You're not on that channel" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "You're not on that channel",
"maxItems": 1
}
},
"required": ["command", "to", "params"]
"required": [
"command",
"to",
"params"
]
}

View File

@@ -2,17 +2,34 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://git.eeqj.de/sneak/chat/schema/numerics/482.json",
"title": "482 ERR_CHANOPRIVSNEEDED",
"description": "You're not channel operator. RFC 1459 §6.1.",
"description": "You're not channel operator. RFC 1459 \u00a76.1.",
"$ref": "../message.json",
"properties": {
"command": { "const": "482" },
"to": { "type": "string" },
"command": {
"const": "482"
},
"to": {
"type": "string"
},
"params": {
"type": "array",
"items": { "type": "string" },
"items": {
"type": "string"
},
"description": "[channel]."
},
"body": { "const": "You're not channel operator" }
"body": {
"type": "array",
"items": {
"type": "string"
},
"description": "You're not channel operator",
"maxItems": 1
}
},
"required": ["command", "to", "params"]
"required": [
"command",
"to",
"params"
]
}