From dfb1636be5d9ad2d5d1c0c65b0b7648d9def162d Mon Sep 17 00:00:00 2001 From: clawbot Date: Tue, 10 Feb 2026 10:31:26 -0800 Subject: [PATCH] refactor: model message schemas after IRC RFC 1459/2812 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace c2s/s2c/s2s taxonomy with IRC-native structure: - schema/commands/ — IRC command schemas (PRIVMSG, NOTICE, JOIN, PART, QUIT, NICK, TOPIC, MODE, KICK, PING, PONG) - schema/numerics/ — IRC numeric reply codes (001-004, 322-323, 332, 353, 366, 372-376, 401, 403, 433, 442, 482) - schema/message.json — base envelope mapping IRC wire format to JSON Messages use 'command' field with IRC command names or 3-digit numeric codes. 'body' is a string (IRC trailing parameter), not object/array. 'from'/'to' map to IRC prefix and first parameter. Federation uses the same IRC commands (no custom RELAY/LINK/SYNC). Update README message format, command tables, and examples to match. --- README.md | 227 +++++++++++++++++++++++---------- schema/README.md | 142 +++++++++++---------- schema/c2s/join.schema.json | 23 ---- schema/c2s/kick.schema.json | 23 ---- schema/c2s/mode.schema.json | 23 ---- schema/c2s/nick.schema.json | 23 ---- schema/c2s/notice.schema.json | 24 ---- schema/c2s/part.schema.json | 23 ---- schema/c2s/ping.schema.json | 22 ---- schema/c2s/privmsg.schema.json | 24 ---- schema/c2s/pubkey.schema.json | 33 ----- schema/c2s/quit.schema.json | 22 ---- schema/c2s/topic.schema.json | 23 ---- schema/commands/JOIN.json | 16 +++ schema/commands/KICK.json | 34 +++++ schema/commands/MODE.json | 29 +++++ schema/commands/NICK.json | 16 +++ schema/commands/NOTICE.json | 17 +++ schema/commands/PART.json | 17 +++ schema/commands/PING.json | 18 +++ schema/commands/PONG.json | 22 ++++ schema/commands/PRIVMSG.json | 18 +++ schema/commands/QUIT.json | 16 +++ schema/commands/TOPIC.json | 17 +++ schema/message.json | 46 +++++++ schema/message.schema.json | 67 ---------- schema/numerics/001.json | 20 +++ schema/numerics/002.json | 20 +++ schema/numerics/003.json | 36 ++++++ schema/numerics/004.json | 39 ++++++ schema/numerics/322.json | 47 +++++++ schema/numerics/323.json | 13 ++ schema/numerics/332.json | 47 +++++++ schema/numerics/353.json | 48 +++++++ schema/numerics/366.json | 18 +++ schema/numerics/372.json | 36 ++++++ schema/numerics/375.json | 26 ++++ schema/numerics/376.json | 13 ++ schema/numerics/401.json | 21 +++ schema/numerics/403.json | 21 +++ schema/numerics/433.json | 21 +++ schema/numerics/442.json | 18 +++ schema/numerics/482.json | 18 +++ schema/s2c/001.schema.json | 24 ---- schema/s2c/002.schema.json | 24 ---- schema/s2c/322.schema.json | 24 ---- schema/s2c/353.schema.json | 24 ---- schema/s2c/366.schema.json | 24 ---- schema/s2c/372.schema.json | 24 ---- schema/s2c/375.schema.json | 24 ---- schema/s2c/376.schema.json | 24 ---- schema/s2c/401.schema.json | 24 ---- schema/s2c/403.schema.json | 24 ---- schema/s2c/433.schema.json | 24 ---- schema/s2c/error.schema.json | 23 ---- schema/s2c/join.schema.json | 24 ---- schema/s2c/kick.schema.json | 24 ---- schema/s2c/mode.schema.json | 24 ---- schema/s2c/nick.schema.json | 24 ---- schema/s2c/notice.schema.json | 23 ---- schema/s2c/part.schema.json | 24 ---- schema/s2c/pong.schema.json | 22 ---- schema/s2c/privmsg.schema.json | 25 ---- schema/s2c/pubkey.schema.json | 34 ----- schema/s2c/quit.schema.json | 23 ---- schema/s2c/topic.schema.json | 25 ---- schema/s2s/link.schema.json | 20 --- schema/s2s/ping.schema.json | 22 ---- schema/s2s/pong.schema.json | 22 ---- schema/s2s/relay.schema.json | 20 --- schema/s2s/sync.schema.json | 20 --- schema/s2s/unlink.schema.json | 22 ---- 72 files changed, 963 insertions(+), 1149 deletions(-) delete mode 100644 schema/c2s/join.schema.json delete mode 100644 schema/c2s/kick.schema.json delete mode 100644 schema/c2s/mode.schema.json delete mode 100644 schema/c2s/nick.schema.json delete mode 100644 schema/c2s/notice.schema.json delete mode 100644 schema/c2s/part.schema.json delete mode 100644 schema/c2s/ping.schema.json delete mode 100644 schema/c2s/privmsg.schema.json delete mode 100644 schema/c2s/pubkey.schema.json delete mode 100644 schema/c2s/quit.schema.json delete mode 100644 schema/c2s/topic.schema.json create mode 100644 schema/commands/JOIN.json create mode 100644 schema/commands/KICK.json create mode 100644 schema/commands/MODE.json create mode 100644 schema/commands/NICK.json create mode 100644 schema/commands/NOTICE.json create mode 100644 schema/commands/PART.json create mode 100644 schema/commands/PING.json create mode 100644 schema/commands/PONG.json create mode 100644 schema/commands/PRIVMSG.json create mode 100644 schema/commands/QUIT.json create mode 100644 schema/commands/TOPIC.json create mode 100644 schema/message.json delete mode 100644 schema/message.schema.json create mode 100644 schema/numerics/001.json create mode 100644 schema/numerics/002.json create mode 100644 schema/numerics/003.json create mode 100644 schema/numerics/004.json create mode 100644 schema/numerics/322.json create mode 100644 schema/numerics/323.json create mode 100644 schema/numerics/332.json create mode 100644 schema/numerics/353.json create mode 100644 schema/numerics/366.json create mode 100644 schema/numerics/372.json create mode 100644 schema/numerics/375.json create mode 100644 schema/numerics/376.json create mode 100644 schema/numerics/401.json create mode 100644 schema/numerics/403.json create mode 100644 schema/numerics/433.json create mode 100644 schema/numerics/442.json create mode 100644 schema/numerics/482.json delete mode 100644 schema/s2c/001.schema.json delete mode 100644 schema/s2c/002.schema.json delete mode 100644 schema/s2c/322.schema.json delete mode 100644 schema/s2c/353.schema.json delete mode 100644 schema/s2c/366.schema.json delete mode 100644 schema/s2c/372.schema.json delete mode 100644 schema/s2c/375.schema.json delete mode 100644 schema/s2c/376.schema.json delete mode 100644 schema/s2c/401.schema.json delete mode 100644 schema/s2c/403.schema.json delete mode 100644 schema/s2c/433.schema.json delete mode 100644 schema/s2c/error.schema.json delete mode 100644 schema/s2c/join.schema.json delete mode 100644 schema/s2c/kick.schema.json delete mode 100644 schema/s2c/mode.schema.json delete mode 100644 schema/s2c/nick.schema.json delete mode 100644 schema/s2c/notice.schema.json delete mode 100644 schema/s2c/part.schema.json delete mode 100644 schema/s2c/pong.schema.json delete mode 100644 schema/s2c/privmsg.schema.json delete mode 100644 schema/s2c/pubkey.schema.json delete mode 100644 schema/s2c/quit.schema.json delete mode 100644 schema/s2c/topic.schema.json delete mode 100644 schema/s2s/link.schema.json delete mode 100644 schema/s2s/ping.schema.json delete mode 100644 schema/s2s/pong.schema.json delete mode 100644 schema/s2s/relay.schema.json delete mode 100644 schema/s2s/sync.schema.json delete mode 100644 schema/s2s/unlink.schema.json diff --git a/README.md b/README.md index 9a941eb..638213e 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,87 @@ This project builds a chat server that: - Holds session state server-side (message queues, presence, channel membership) - Exposes a minimal, clean HTTP+JSON API — easy to build clients against -- Supports multiple concurrent connections per user session +- Supports multiple concurrent clients per user session - Provides IRC-like semantics: channels, nicks, topics, modes - Uses structured JSON messages with IRC command names and numeric reply codes +## Design Decisions + +### Identity & Sessions — No Accounts + +There are no accounts, no registration, no passwords. Identity is a signing +key; a nick is just a display name. The two are decoupled. + +- **Session creation**: client connects → server assigns a **session UUID** + (user identity for this server), a **client UUID** (this specific device), + and an **opaque auth token** (random bytes, not JWT). +- The auth token implicitly identifies the client. Clients present it via + `Authorization: Bearer `. +- Nicks are changeable; the session UUID is the stable identity. +- Server-assigned UUIDs — clients do not choose their own IDs. + +### Multi-Client Model + +A single user session can have multiple clients (phone, laptop, terminal). + +- Each client gets a **separate server-to-client (S2C) message queue**. +- The server fans out all S2C messages to every active client queue for that + user session. +- `GET /api/v1/messages` delivers from the calling client's specific queue, + identified by the auth token. +- Client queues have **independent expiry/pruning** — one client going offline + doesn't affect others. + +``` +User (session UUID) +├── Client A (client UUID, token, queue) +├── Client B (client UUID, token, queue) +└── Client C (client UUID, token, queue) +``` + +### Message Immutability + +Messages are **immutable** — no editing, no deletion by clients. This is a +deliberate design choice that enables cryptographic signing: if a message could +be modified after signing, signatures would be meaningless. + +### Message Delivery + +- **Long-poll timeout**: 15 seconds +- **Queue depth**: server-configurable, default at least 48 hours worth of + messages +- **No delivery/read receipts** except in DMs +- **Bodies are structured** objects or arrays (never raw strings) — enables + deterministic canonicalization via RFC 8785 JCS for signing + +### Crypto & Signing + +- Servers **relay signatures verbatim** — signatures are key/value metadata on + message objects (`meta.sig`, `meta.alg`). Servers do not verify them. +- Clients handle key authentication via **TOFU** (trust on first use). +- **No key revocation mechanism** — keep your keys safe. +- **PUBKEY** message type for distributing signing keys to channel members. +- **E2E encryption for DMs** is planned for 1.0. + +### Channels + +- **Any user can create channels** — joining a nonexistent channel creates it, + like IRC. +- **Ephemeral** — channels disappear when the last member leaves. +- No channel size limits. +- No channel-level encryption. + +### Federation + +- **Manual server linking only** — no autodiscovery, no mesh. Operators + explicitly configure server links. +- Servers relay messages (including signatures) verbatim. + +### Web Client + +The SPA web client is a **convenience UI**. The primary interface is IRC-style +client apps talking directly to the HTTP API. + ## Architecture ### Transport: HTTP only @@ -30,7 +107,8 @@ All client↔server and server↔server communication uses HTTP/1.1+ with JSON request/response bodies. No WebSockets, no raw TCP, no gRPC — just plain HTTP. - **Client polling**: Clients long-poll `GET /api/v1/messages` — server holds - the connection until messages arrive or timeout. One endpoint for everything. + the connection for up to 15 seconds until messages arrive or timeout. + One endpoint for everything. - **Client sending**: `POST /api/v1/messages` with a `to` field. That's it. - **Server federation**: Servers exchange messages via HTTP to enable multi-server networks (like IRC server linking) @@ -38,6 +116,33 @@ request/response bodies. No WebSockets, no raw TCP, no gRPC — just plain HTTP. The entire read/write loop for a client is two endpoints. Everything else is channel management and history. +### Session Model + +``` +┌─────────────────────────────────┐ +│ User Session (UUID) │ +│ nick: "alice" │ +│ signing key: ed25519:... │ +│ │ +│ ┌──────────┐ ┌──────────┐ │ +│ │ Client A │ │ Client B │ ... │ +│ │ UUID │ │ UUID │ │ +│ │ token │ │ token │ │ +│ │ queue │ │ queue │ │ +│ └──────────┘ └──────────┘ │ +└─────────────────────────────────┘ +``` + +- **User session**: server-assigned UUID. Represents a user on this server. + Has a nick (changeable, unique per server at any point in time). +- **Client**: each device/connection gets its own UUID and opaque auth token. + The token is the credential — present it to authenticate. +- **Queue**: each client has an independent S2C message queue. The server fans + out messages to all active client queues for the session. + +Sessions persist across disconnects. Messages queue until retrieved. Client +queues expire independently after a configurable idle timeout. + ### Message Protocol All messages use **IRC command names and numeric reply codes** from RFC 1459/2812. @@ -72,7 +177,7 @@ This enables: |----------|-------------| | PRIVMSG | Send message to channel or user | | NOTICE | Send notice (no auto-reply expected) | -| JOIN | Join a channel | +| JOIN | Join a channel (creates it if nonexistent) | | PART | Leave a channel | | QUIT | Disconnect from server | | NICK | Change nickname | @@ -96,7 +201,7 @@ All C2S commands may be echoed back as S2C (relayed to other users), plus: | Code | Name | Description | |------|-------------------|-------------| -| 001 | RPL_WELCOME | Welcome after registration | +| 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 | @@ -144,6 +249,8 @@ complete index. ### Canonicalization and Signing Messages support optional cryptographic signatures for integrity verification. +Servers relay signatures verbatim without verifying them — verification is +purely a client-side concern. #### Canonicalization (RFC 8785 JCS) @@ -179,34 +286,9 @@ under canonicalization. {"command": "PUBKEY", "from": "alice", "body": {"alg": "ed25519", "key": "base64-encoded-pubkey"}} ``` -Servers SHOULD relay PUBKEY messages to all channel members. Clients SHOULD -cache public keys and use them to verify `meta.sig` on incoming messages. -Key distribution is trust-on-first-use (TOFU) by default. - -### Core Concepts - -#### Users - -- Identified by a unique user ID (UUID) -- Authenticate via token (issued at registration or login) -- Have a nick (changeable, unique per server at any point in time) -- Maintain a persistent message queue on the server - -#### Sessions - -- A session represents an authenticated user's connection context -- Session state is **server-held**, not connection-bound -- Multiple devices can share a session (messages delivered to all) -- Sessions persist across disconnects — messages queue until retrieved -- Sessions expire after a configurable idle timeout (default 24h) - -#### Channels - -- Named with `#` prefix (e.g. `#general`) -- Have a topic, mode flags, and member list -- Messages to a channel are queued for all members -- Channel history is stored server-side (configurable depth) -- No eternal logging by default — history rotates +Servers relay PUBKEY messages to all channel members. Clients cache public keys +and use them to verify `meta.sig` on incoming messages. Key distribution is +trust-on-first-use (TOFU). There is no key revocation mechanism. ### API Endpoints @@ -216,9 +298,9 @@ require `Authorization: Bearer ` header. The API is the primary interface — designed for IRC-style clients. The entire client loop is: -1. `POST /api/v1/register` — get a token +1. `POST /api/v1/session` — create a session, get a token 2. `GET /api/v1/state` — see who you are and what channels you're in -3. `GET /api/v1/messages?after=0` — long-poll for all messages (channel, DM, system) +3. `GET /api/v1/messages?timeout=15` — long-poll for all messages (channel, DM, system) 4. `POST /api/v1/messages` — send to `"#channel"` or `"nick"` That's the core. Everything else (join, part, history, members) is ancillary. @@ -226,11 +308,11 @@ That's the core. Everything else (join, part, history, members) is ancillary. #### Quick example (curl) ```bash -# Register -TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/register \ +# Create a session (get session UUID, client UUID, and auth token) +TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/session \ -d '{"nick":"alice"}' | jq -r .token) -# Join a channel +# Join a channel (creates it if it doesn't exist) curl -s -X POST http://localhost:8080/api/v1/messages \ -H "Authorization: Bearer $TOKEN" \ -d '{"command":"JOIN","to":"#general"}' @@ -240,34 +322,41 @@ curl -s -X POST http://localhost:8080/api/v1/messages \ -H "Authorization: Bearer $TOKEN" \ -d '{"command":"PRIVMSG","to":"#general","body":["hello world"]}' -# Poll for messages (long-poll) -curl -s http://localhost:8080/api/v1/messages?after=0 \ +# Poll for messages (long-poll, 15s timeout) +curl -s "http://localhost:8080/api/v1/messages?timeout=15" \ -H "Authorization: Bearer $TOKEN" ``` -#### Registration +#### Session ``` -POST /api/v1/register — Create account { "nick": "..." } → { id, nick, token } +POST /api/v1/session — Create session { "nick": "..." } + → { session_id, client_id, nick, token } + Token is opaque (random), not JWT. + Token implicitly identifies the client. ``` #### State ``` -GET /api/v1/state — User state: nick, id, and list of joined channels - Replaces separate /me and /channels endpoints +GET /api/v1/state — User state: nick, session_id, client_id, + and list of joined channels ``` #### Messages (unified stream) ``` -GET /api/v1/messages — Single message stream (long-poll supported) +GET /api/v1/messages — Single message stream (long-poll, 15s timeout) All message types: channel, DM, notices, events - Query params: ?after=&timeout=30 + Delivers from the calling client's queue + (identified by auth token) + Query params: ?after=&timeout=15 POST /api/v1/messages — Send a message (IRC command in body) Body: { "command": "PRIVMSG", "to": "#channel", "body": ["..."] } ``` +Messages are immutable — no edit or delete endpoints. + #### History ``` @@ -281,7 +370,9 @@ GET /api/v1/history — Fetch history for a target (channel or DM) ``` GET /api/v1/channels/all — List all server channels POST /api/v1/channels/join — Join a channel { "channel": "#name" } + Creates the channel if it doesn't exist. DELETE /api/v1/channels/{name} — Part (leave) a channel + Channel is destroyed when last member leaves. GET /api/v1/channels/{name}/members — Channel member list ``` @@ -294,7 +385,8 @@ GET /.well-known/healthcheck.json — Health check ### Federation (Server-to-Server) -Servers can link to form a network, similar to IRC server linking: +Servers can link to form a network, similar to IRC server linking. Links are +**manually configured** — there is no autodiscovery. ``` POST /api/v1/federation/link — Establish server link (mutual auth via shared key) @@ -303,8 +395,8 @@ GET /api/v1/federation/status — Link status ``` Federation uses the same HTTP+JSON transport. S2S messages use the RELAY, LINK, -UNLINK, SYNC, PING, and PONG commands. Messages are relayed between servers so -users on different servers can share channels. +UNLINK, SYNC, PING, and PONG commands. Messages (including signatures) are +relayed verbatim between servers so users on different servers can share channels. ### Channel Modes @@ -331,7 +423,9 @@ Via environment variables (Viper), following gohttpserver conventions: | `DEBUG` | `false` | Debug mode | | `MAX_HISTORY` | `10000` | Max messages per channel history | | `SESSION_TIMEOUT` | `86400` | Session idle timeout (seconds) | +| `QUEUE_MAX_AGE` | `172800` | Max client queue age in seconds (default 48h) | | `MAX_MESSAGE_SIZE` | `4096` | Max message body size (bytes) | +| `LONG_POLL_TIMEOUT` | `15` | Long-poll timeout in seconds | | `MOTD` | `""` | Message of the day | | `SERVER_NAME` | hostname | Server display name | | `FEDERATION_KEY` | `""` | Shared key for server linking | @@ -341,11 +435,12 @@ Via environment variables (Viper), following gohttpserver conventions: SQLite by default (single-file, zero-config), with Postgres support for larger deployments. Tables: -- `users` — accounts and auth tokens +- `sessions` — user sessions (UUID, nick, created_at) +- `clients` — client records (UUID, session_id, token_hash, last_seen) - `channels` — channel metadata and modes - `channel_members` — membership and user modes - `messages` — message history (rotated per `MAX_HISTORY`) -- `message_queue` — per-user pending delivery queue +- `client_queues` — per-client pending delivery queues - `server_links` — federation peer configuration ### Project Structure @@ -398,30 +493,30 @@ Per gohttpserver conventions: | Metrics | `github.com/prometheus/client_golang` | | DB | `modernc.org/sqlite` + `database/sql` | -### Web Client - -The server embeds a single-page web client (Preact) served at `/`. This is a -**convenience/reference implementation** — not the primary interface. The -primary intended clients are IRC-style terminal applications, bots, and custom -clients talking directly to the HTTP API. - ### Design Principles 1. **API-first** — the HTTP API is the product. Clients are thin. If you can't build a working IRC-style TUI client in an afternoon, the API is too complex. -2. **IRC semantics over HTTP** — command names and numeric codes from RFC 1459/2812. +2. **No accounts** — identity is a signing key, nick is a display name. No + registration, no passwords. Session creation is instant. +3. **IRC semantics over HTTP** — command names and numeric codes from RFC 1459/2812. Familiar to anyone who's built IRC clients or bots. -3. **HTTP is the only transport** — no WebSockets, no raw TCP, no protocol +4. **HTTP is the only transport** — no WebSockets, no raw TCP, no protocol negotiation. HTTP is universal, proxy-friendly, and works everywhere. -4. **Server holds state** — clients are stateless. Reconnect, switch devices, - lose connectivity — your messages are waiting. -5. **Structured messages** — JSON with extensible metadata. Bodies are always +5. **Server holds state** — clients are stateless. Reconnect, switch devices, + lose connectivity — your messages are waiting in your client queue. +6. **Structured messages** — JSON with extensible metadata. Bodies are always objects or arrays for deterministic canonicalization (JCS) and signing. -6. **Simple deployment** — single binary, SQLite default, zero mandatory +7. **Immutable messages** — no editing, no deletion. Fits naturally with + cryptographic signatures. +8. **Simple deployment** — single binary, SQLite default, zero mandatory external dependencies. -7. **No eternal logs** — history rotates. Chat should be ephemeral by default. -8. **Federation optional** — single server works standalone. Linking is opt-in. -9. **Signable messages** — optional Ed25519 signatures with TOFU key distribution. +9. **No eternal logs** — history rotates. Chat should be ephemeral by default. + Channels disappear when empty. +10. **Federation optional** — single server works standalone. Linking is manual + and opt-in. +11. **Signable messages** — optional Ed25519 signatures with TOFU key + distribution. Servers relay signatures without verification. ## Status diff --git a/schema/README.md b/schema/README.md index c40c61a..529c331 100644 --- a/schema/README.md +++ b/schema/README.md @@ -1,81 +1,87 @@ -# Message Schema Index +# Message Schemas -JSON Schema (draft 2020-12) definitions for the IRC-style message protocol. +JSON Schema definitions (draft 2020-12) for the chat protocol. Messages use +**IRC command names and numeric reply codes** (RFC 1459/2812) encoded as JSON +over HTTP. -All messages share a common envelope defined in -[`message.schema.json`](message.schema.json). +## Envelope -## Base Envelope +Every message is a JSON object with a `command` field. The format maps directly +to IRC wire format: -| Field | Type | Required | Description | -|-----------|-----------------|----------|-------------| -| `command` | string | ✓ | IRC command name or numeric reply code | -| `from` | string | | Sender nick or server name | -| `to` | string | | Destination channel or nick | -| `params` | array\ | | Additional IRC-style parameters | -| `body` | array \| object | varies | Message body (never a raw string) | -| `meta` | object | | Extensible metadata (signatures, etc.) | -| `id` | string (uuid) | | Server-assigned message ID | -| `ts` | string (date-time) | | Server-assigned timestamp | +``` +IRC: :nick PRIVMSG #channel :hello world +JSON: {"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": "hello world"} -## Client-to-Server (C2S) +IRC: :server 353 nick = #channel :user1 @op1 +voice1 +JSON: {"command": "353", "to": "nick", "params": ["=", "#channel"], "body": "user1 @op1 +voice1"} +``` -| Command | Schema | Description | -|----------|--------|-------------| -| PRIVMSG | [`c2s/privmsg.schema.json`](c2s/privmsg.schema.json) | Send message to channel or user | -| NOTICE | [`c2s/notice.schema.json`](c2s/notice.schema.json) | Send a notice | -| JOIN | [`c2s/join.schema.json`](c2s/join.schema.json) | Join a channel | -| PART | [`c2s/part.schema.json`](c2s/part.schema.json) | Leave a channel | -| QUIT | [`c2s/quit.schema.json`](c2s/quit.schema.json) | Disconnect | -| NICK | [`c2s/nick.schema.json`](c2s/nick.schema.json) | Change nick | -| MODE | [`c2s/mode.schema.json`](c2s/mode.schema.json) | Set/query modes | -| TOPIC | [`c2s/topic.schema.json`](c2s/topic.schema.json) | Set/query topic | -| KICK | [`c2s/kick.schema.json`](c2s/kick.schema.json) | Kick user | -| PING | [`c2s/ping.schema.json`](c2s/ping.schema.json) | Client keepalive | -| PUBKEY | [`c2s/pubkey.schema.json`](c2s/pubkey.schema.json) | Announce public key | +Common fields (see `message.json` for full schema): -## Server-to-Client (S2C) +| 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) | -### Named Commands +## Commands -| Command | Schema | Description | -|----------|--------|-------------| -| PRIVMSG | [`s2c/privmsg.schema.json`](s2c/privmsg.schema.json) | Relayed message | -| NOTICE | [`s2c/notice.schema.json`](s2c/notice.schema.json) | Server or user notice | -| JOIN | [`s2c/join.schema.json`](s2c/join.schema.json) | User joined channel | -| PART | [`s2c/part.schema.json`](s2c/part.schema.json) | User left channel | -| QUIT | [`s2c/quit.schema.json`](s2c/quit.schema.json) | User disconnected | -| NICK | [`s2c/nick.schema.json`](s2c/nick.schema.json) | Nick change | -| MODE | [`s2c/mode.schema.json`](s2c/mode.schema.json) | Mode change | -| TOPIC | [`s2c/topic.schema.json`](s2c/topic.schema.json) | Topic change | -| KICK | [`s2c/kick.schema.json`](s2c/kick.schema.json) | User kicked | -| PONG | [`s2c/pong.schema.json`](s2c/pong.schema.json) | Server pong | -| PUBKEY | [`s2c/pubkey.schema.json`](s2c/pubkey.schema.json) | Relayed public key | -| ERROR | [`s2c/error.schema.json`](s2c/error.schema.json) | Server error | +IRC commands used for client↔server and server↔server communication. -### Numeric Replies +| Command | File | RFC | Description | +|-----------|---------------------------|-----------|--------------------------------| +| `PRIVMSG` | `commands/PRIVMSG.json` | 1459 §4.4.1 | Message to channel or user | +| `NOTICE` | `commands/NOTICE.json` | 1459 §4.4.2 | Notice (no auto-reply) | +| `JOIN` | `commands/JOIN.json` | 1459 §4.2.1 | Join a channel | +| `PART` | `commands/PART.json` | 1459 §4.2.2 | Leave a channel | +| `QUIT` | `commands/QUIT.json` | 1459 §4.1.6 | User disconnected | +| `NICK` | `commands/NICK.json` | 1459 §4.1.2 | Change nickname | +| `TOPIC` | `commands/TOPIC.json` | 1459 §4.2.4 | Get/set channel topic | +| `MODE` | `commands/MODE.json` | 1459 §4.2.3 | Set channel/user modes | +| `KICK` | `commands/KICK.json` | 1459 §4.2.8 | Kick user from channel | +| `PING` | `commands/PING.json` | 1459 §4.6.2 | Keepalive | +| `PONG` | `commands/PONG.json` | 1459 §4.6.3 | Keepalive response | -| Code | Name | Schema | Description | -|------|--------------------|--------|-------------| -| 001 | RPL_WELCOME | [`s2c/001.schema.json`](s2c/001.schema.json) | Welcome after registration | -| 002 | RPL_YOURHOST | [`s2c/002.schema.json`](s2c/002.schema.json) | Server host info | -| 322 | RPL_LIST | [`s2c/322.schema.json`](s2c/322.schema.json) | Channel list entry | -| 353 | RPL_NAMREPLY | [`s2c/353.schema.json`](s2c/353.schema.json) | Names list | -| 366 | RPL_ENDOFNAMES | [`s2c/366.schema.json`](s2c/366.schema.json) | End of names list | -| 372 | RPL_MOTD | [`s2c/372.schema.json`](s2c/372.schema.json) | MOTD line | -| 375 | RPL_MOTDSTART | [`s2c/375.schema.json`](s2c/375.schema.json) | Start of MOTD | -| 376 | RPL_ENDOFMOTD | [`s2c/376.schema.json`](s2c/376.schema.json) | End of MOTD | -| 401 | ERR_NOSUCHNICK | [`s2c/401.schema.json`](s2c/401.schema.json) | No such nick/channel | -| 403 | ERR_NOSUCHCHANNEL | [`s2c/403.schema.json`](s2c/403.schema.json) | No such channel | -| 433 | ERR_NICKNAMEINUSE | [`s2c/433.schema.json`](s2c/433.schema.json) | Nick in use | +## Numeric Replies -## Server-to-Server (S2S) +Three-digit codes for server responses, per IRC convention. -| Command | Schema | Description | -|---------|--------|-------------| -| RELAY | [`s2s/relay.schema.json`](s2s/relay.schema.json) | Relay message to linked server | -| LINK | [`s2s/link.schema.json`](s2s/link.schema.json) | Establish server link | -| UNLINK | [`s2s/unlink.schema.json`](s2s/unlink.schema.json) | Tear down link | -| SYNC | [`s2s/sync.schema.json`](s2s/sync.schema.json) | Synchronize state | -| PING | [`s2s/ping.schema.json`](s2s/ping.schema.json) | Server ping | -| PONG | [`s2s/pong.schema.json`](s2s/pong.schema.json) | Server pong | +### Success / Informational (0xx–3xx) + +| Code | Name | File | Description | +|-------|-------------------|-----------------------|--------------------------------| +| `001` | RPL_WELCOME | `numerics/001.json` | Welcome after session creation | +| `002` | RPL_YOURHOST | `numerics/002.json` | Server host info | +| `003` | RPL_CREATED | `numerics/003.json` | Server creation date | +| `004` | RPL_MYINFO | `numerics/004.json` | Server info and modes | +| `322` | RPL_LIST | `numerics/322.json` | Channel list entry | +| `323` | RPL_LISTEND | `numerics/323.json` | End of channel list | +| `332` | RPL_TOPIC | `numerics/332.json` | Channel topic | +| `353` | RPL_NAMREPLY | `numerics/353.json` | Channel member list | +| `366` | RPL_ENDOFNAMES | `numerics/366.json` | End of NAMES list | +| `372` | RPL_MOTD | `numerics/372.json` | MOTD line | +| `375` | RPL_MOTDSTART | `numerics/375.json` | Start of MOTD | +| `376` | RPL_ENDOFMOTD | `numerics/376.json` | End of MOTD | + +### Errors (4xx) + +| Code | Name | File | Description | +|-------|----------------------|-----------------------|--------------------------------| +| `401` | ERR_NOSUCHNICK | `numerics/401.json` | No such nick/channel | +| `403` | ERR_NOSUCHCHANNEL | `numerics/403.json` | No such channel | +| `433` | ERR_NICKNAMEINUSE | `numerics/433.json` | Nickname already in use | +| `442` | ERR_NOTONCHANNEL | `numerics/442.json` | Not on that channel | +| `482` | ERR_CHANOPRIVSNEEDED | `numerics/482.json` | Not channel operator | + +## Federation (S2S) + +Server-to-server messages use the same command format. Federated servers relay +messages with an additional `origin` field in `meta` to track the source server. +The PING/PONG commands serve as inter-server keepalives. State sync after link +establishment uses a burst of JOIN, NICK, TOPIC, and MODE commands. diff --git a/schema/c2s/join.schema.json b/schema/c2s/join.schema.json deleted file mode 100644 index aab9a7c..0000000 --- a/schema/c2s/join.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/kick.schema.json b/schema/c2s/kick.schema.json deleted file mode 100644 index 7042bd6..0000000 --- a/schema/c2s/kick.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/mode.schema.json b/schema/c2s/mode.schema.json deleted file mode 100644 index 3f8cc8a..0000000 --- a/schema/c2s/mode.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/nick.schema.json b/schema/c2s/nick.schema.json deleted file mode 100644 index 0341173..0000000 --- a/schema/c2s/nick.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/notice.schema.json b/schema/c2s/notice.schema.json deleted file mode 100644 index 89cb838..0000000 --- a/schema/c2s/notice.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/part.schema.json b/schema/c2s/part.schema.json deleted file mode 100644 index c572558..0000000 --- a/schema/c2s/part.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/ping.schema.json b/schema/c2s/ping.schema.json deleted file mode 100644 index 6f6b5fe..0000000 --- a/schema/c2s/ping.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/privmsg.schema.json b/schema/c2s/privmsg.schema.json deleted file mode 100644 index a8032b4..0000000 --- a/schema/c2s/privmsg.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/pubkey.schema.json b/schema/c2s/pubkey.schema.json deleted file mode 100644 index 47093f5..0000000 --- a/schema/c2s/pubkey.schema.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/quit.schema.json b/schema/c2s/quit.schema.json deleted file mode 100644 index 9fd70e4..0000000 --- a/schema/c2s/quit.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/c2s/topic.schema.json b/schema/c2s/topic.schema.json deleted file mode 100644 index 5df67cd..0000000 --- a/schema/c2s/topic.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/commands/JOIN.json b/schema/commands/JOIN.json new file mode 100644 index 0000000..ca7f6c4 --- /dev/null +++ b/schema/commands/JOIN.json @@ -0,0 +1,16 @@ +{ + "$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)." }, + "to": { "type": "string", "description": "Channel name.", "pattern": "^#[a-zA-Z0-9_-]+$" } + }, + "required": ["command", "to"], + "examples": [ + { "command": "JOIN", "from": "alice", "to": "#general" } + ] +} diff --git a/schema/commands/KICK.json b/schema/commands/KICK.json new file mode 100644 index 0000000..8380f31 --- /dev/null +++ b/schema/commands/KICK.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/KICK.json", + "title": "KICK", + "description": "Kick a user from a channel. RFC 1459 §4.2.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_-]+$" + }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "Kicked nick. e.g. [\"alice\"].", + "minItems": 1, + "maxItems": 1 + }, + "body": { + "type": "string", + "description": "Optional kick reason." + } + }, + "required": ["command", "to", "params"], + "examples": [ + { "command": "KICK", "from": "op1", "to": "#general", "params": ["troll"], "body": "Behave" } + ] +} diff --git a/schema/commands/MODE.json b/schema/commands/MODE.json new file mode 100644 index 0000000..e2da7f0 --- /dev/null +++ b/schema/commands/MODE.json @@ -0,0 +1,29 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/MODE.json", + "title": "MODE", + "description": "Set or query channel/user modes. RFC 1459 §4.2.3.", + "$ref": "../message.json", + "properties": { + "command": { "const": "MODE" }, + "from": { + "type": "string", + "description": "Nick that set the mode (S2C only)." + }, + "to": { + "type": "string", + "description": "Channel name.", + "pattern": "^#[a-zA-Z0-9_-]+$" + }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "Mode string and optional target nick. e.g. [\"+o\", \"alice\"].", + "examples": [["+o", "alice"], ["-m"], ["+v", "bob"]] + } + }, + "required": ["command", "to", "params"], + "examples": [ + { "command": "MODE", "from": "op1", "to": "#general", "params": ["+o", "alice"] } + ] +} diff --git a/schema/commands/NICK.json b/schema/commands/NICK.json new file mode 100644 index 0000000..49cf83f --- /dev/null +++ b/schema/commands/NICK.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/NICK.json", + "title": "NICK", + "description": "Change nickname. C2S: request new nick. S2C: notification of nick change. RFC 1459 §4.1.2.", + "$ref": "../message.json", + "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_-]*$" } + }, + "required": ["command", "body"], + "examples": [ + { "command": "NICK", "from": "oldnick", "body": "newnick" } + ] +} diff --git a/schema/commands/NOTICE.json b/schema/commands/NOTICE.json new file mode 100644 index 0000000..1ee166d --- /dev/null +++ b/schema/commands/NOTICE.json @@ -0,0 +1,17 @@ +{ + "$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" } + ] +} diff --git a/schema/commands/PART.json b/schema/commands/PART.json new file mode 100644 index 0000000..d82eb94 --- /dev/null +++ b/schema/commands/PART.json @@ -0,0 +1,17 @@ +{ + "$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)." }, + "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" } + ] +} diff --git a/schema/commands/PING.json b/schema/commands/PING.json new file mode 100644 index 0000000..4bb4493 --- /dev/null +++ b/schema/commands/PING.json @@ -0,0 +1,18 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/PING.json", + "title": "PING", + "description": "Keepalive. C2S or S2S. Server responds with PONG. RFC 1459 §4.6.2.", + "$ref": "../message.json", + "properties": { + "command": { "const": "PING" }, + "body": { + "type": "string", + "description": "Opaque token to be echoed in PONG." + } + }, + "required": ["command"], + "examples": [ + { "command": "PING", "body": "1707580000" } + ] +} diff --git a/schema/commands/PONG.json b/schema/commands/PONG.json new file mode 100644 index 0000000..400fc0c --- /dev/null +++ b/schema/commands/PONG.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/PONG.json", + "title": "PONG", + "description": "Keepalive response. S2C or S2S. RFC 1459 §4.6.3.", + "$ref": "../message.json", + "properties": { + "command": { "const": "PONG" }, + "from": { + "type": "string", + "description": "Responding server name." + }, + "body": { + "type": "string", + "description": "Echoed token from PING." + } + }, + "required": ["command"], + "examples": [ + { "command": "PONG", "from": "server.example.com", "body": "1707580000" } + ] +} diff --git a/schema/commands/PRIVMSG.json b/schema/commands/PRIVMSG.json new file mode 100644 index 0000000..877e004 --- /dev/null +++ b/schema/commands/PRIVMSG.json @@ -0,0 +1,18 @@ +{ + "$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 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" } + ] +} diff --git a/schema/commands/QUIT.json b/schema/commands/QUIT.json new file mode 100644 index 0000000..9b66f20 --- /dev/null +++ b/schema/commands/QUIT.json @@ -0,0 +1,16 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/QUIT.json", + "title": "QUIT", + "description": "User disconnected. S2C only. RFC 1459 §4.1.6.", + "$ref": "../message.json", + "properties": { + "command": { "const": "QUIT" }, + "from": { "type": "string", "description": "Nick that quit." }, + "body": { "type": "string", "description": "Optional quit reason." } + }, + "required": ["command", "from"], + "examples": [ + { "command": "QUIT", "from": "alice", "body": "Connection reset" } + ] +} diff --git a/schema/commands/TOPIC.json b/schema/commands/TOPIC.json new file mode 100644 index 0000000..6ab998c --- /dev/null +++ b/schema/commands/TOPIC.json @@ -0,0 +1,17 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/commands/TOPIC.json", + "title": "TOPIC", + "description": "Get or set channel topic. C2S: set topic (body present) or query (body absent). S2C: topic change notification. RFC 1459 §4.2.4.", + "$ref": "../message.json", + "properties": { + "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 } + }, + "required": ["command", "to"], + "examples": [ + { "command": "TOPIC", "from": "alice", "to": "#general", "body": "Welcome to the chat" } + ] +} diff --git a/schema/message.json b/schema/message.json new file mode 100644 index 0000000..286d0ea --- /dev/null +++ b/schema/message.json @@ -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"] +} diff --git a/schema/message.schema.json b/schema/message.schema.json deleted file mode 100644 index 40d2dae..0000000 --- a/schema/message.schema.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$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 -} diff --git a/schema/numerics/001.json b/schema/numerics/001.json new file mode 100644 index 0000000..499d686 --- /dev/null +++ b/schema/numerics/001.json @@ -0,0 +1,20 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "001" }, + "to": { "type": "string", "description": "Target nick." }, + "body": { + "type": "array", + "items": { "type": "string" }, + "description": "Welcome text lines." + } + }, + "required": ["command", "to", "body"], + "examples": [ + { "command": "001", "to": "alice", "body": ["Welcome to the network, alice"] } + ] +} diff --git a/schema/numerics/002.json b/schema/numerics/002.json new file mode 100644 index 0000000..22166a9 --- /dev/null +++ b/schema/numerics/002.json @@ -0,0 +1,20 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "002" }, + "to": { "type": "string" }, + "body": { + "type": "array", + "items": { "type": "string" }, + "description": "Host info lines." + } + }, + "required": ["command", "to", "body"], + "examples": [ + { "command": "002", "to": "alice", "body": ["Your host is chat.example.com, running version 0.1.0"] } + ] +} diff --git a/schema/numerics/003.json b/schema/numerics/003.json new file mode 100644 index 0000000..d448a8c --- /dev/null +++ b/schema/numerics/003.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/003.json", + "title": "003 RPL_CREATED", + "description": "Server creation date. RFC 2812 \u00a75.1.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "003" + }, + "to": { + "type": "string" + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Text lines." + } + }, + "required": [ + "command", + "to", + "body" + ], + "examples": [ + { + "command": "003", + "to": "alice", + "body": [ + "This server was created 2026-02-01" + ] + } + ] +} diff --git a/schema/numerics/004.json b/schema/numerics/004.json new file mode 100644 index 0000000..2aaf116 --- /dev/null +++ b/schema/numerics/004.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/004.json", + "title": "004 RPL_MYINFO", + "description": "Server info (name, version, available modes). RFC 2812 \u00a75.1.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "004" + }, + "to": { + "type": "string" + }, + "params": { + "type": "array", + "items": { + "type": "string" + }, + "description": "[server_name, version, user_modes, channel_modes]." + } + }, + "required": [ + "command", + "to", + "params" + ], + "examples": [ + { + "command": "004", + "to": "alice", + "params": [ + "chat.example.com", + "0.1.0", + "o", + "imnst+ov" + ] + } + ] +} diff --git a/schema/numerics/322.json b/schema/numerics/322.json new file mode 100644 index 0000000..3f4d288 --- /dev/null +++ b/schema/numerics/322.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/322.json", + "title": "322 RPL_LIST", + "description": "Channel list entry. One per channel in response to LIST. RFC 1459 \u00a76.2.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "322" + }, + "to": { + "type": "string" + }, + "params": { + "type": "array", + "items": { + "type": "string" + }, + "description": "[channel, visible_count]." + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Channel topic." + } + }, + "required": [ + "command", + "to", + "params" + ], + "examples": [ + { + "command": "322", + "to": "alice", + "params": [ + "#general", + "12" + ], + "body": [ + "General discussion" + ] + } + ] +} diff --git a/schema/numerics/323.json b/schema/numerics/323.json new file mode 100644 index 0000000..92734b8 --- /dev/null +++ b/schema/numerics/323.json @@ -0,0 +1,13 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "323" }, + "to": { "type": "string" }, + "body": { "const": "End of /LIST" } + }, + "required": ["command", "to"] +} diff --git a/schema/numerics/332.json b/schema/numerics/332.json new file mode 100644 index 0000000..aebb9c6 --- /dev/null +++ b/schema/numerics/332.json @@ -0,0 +1,47 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/332.json", + "title": "332 RPL_TOPIC", + "description": "Channel topic (sent on JOIN or TOPIC query). RFC 1459 \u00a76.2.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "332" + }, + "to": { + "type": "string" + }, + "params": { + "type": "array", + "items": { + "type": "string" + }, + "description": "[channel]." + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Topic text." + } + }, + "required": [ + "command", + "to", + "params", + "body" + ], + "examples": [ + { + "command": "332", + "to": "alice", + "params": [ + "#general" + ], + "body": [ + "Welcome to the chat" + ] + } + ] +} diff --git a/schema/numerics/353.json b/schema/numerics/353.json new file mode 100644 index 0000000..ddf7f4e --- /dev/null +++ b/schema/numerics/353.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/353.json", + "title": "353 RPL_NAMREPLY", + "description": "Channel member list. Sent on JOIN or NAMES query. RFC 1459 \u00a76.2.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "353" + }, + "to": { + "type": "string" + }, + "params": { + "type": "array", + "items": { + "type": "string" + }, + "description": "[channel_type, channel]. channel_type: = (public), * (private), @ (secret)." + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Space-separated list of nicks. Prefixed with @ for ops, + for voiced." + } + }, + "required": [ + "command", + "to", + "params", + "body" + ], + "examples": [ + { + "command": "353", + "to": "alice", + "params": [ + "=", + "#general" + ], + "body": [ + "@op1 alice bob +voiced1" + ] + } + ] +} diff --git a/schema/numerics/366.json b/schema/numerics/366.json new file mode 100644 index 0000000..a4309c7 --- /dev/null +++ b/schema/numerics/366.json @@ -0,0 +1,18 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "366" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[channel]." + }, + "body": { "const": "End of /NAMES list" } + }, + "required": ["command", "to", "params"] +} diff --git a/schema/numerics/372.json b/schema/numerics/372.json new file mode 100644 index 0000000..58ddb05 --- /dev/null +++ b/schema/numerics/372.json @@ -0,0 +1,36 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/372.json", + "title": "372 RPL_MOTD", + "description": "MOTD line. One message per line of the MOTD. RFC 2812 \u00a75.1.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "372" + }, + "to": { + "type": "string" + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "MOTD line text (prefixed with '- ')." + } + }, + "required": [ + "command", + "to", + "body" + ], + "examples": [ + { + "command": "372", + "to": "alice", + "body": [ + "- Welcome to our server!" + ] + } + ] +} diff --git a/schema/numerics/375.json b/schema/numerics/375.json new file mode 100644 index 0000000..4fbe45a --- /dev/null +++ b/schema/numerics/375.json @@ -0,0 +1,26 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://git.eeqj.de/sneak/chat/schema/numerics/375.json", + "title": "375 RPL_MOTDSTART", + "description": "Start of MOTD. RFC 2812 \u00a75.1.", + "$ref": "../message.json", + "properties": { + "command": { + "const": "375" + }, + "to": { + "type": "string" + }, + "body": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Text lines." + } + }, + "required": [ + "command", + "to" + ] +} diff --git a/schema/numerics/376.json b/schema/numerics/376.json new file mode 100644 index 0000000..7a2ca35 --- /dev/null +++ b/schema/numerics/376.json @@ -0,0 +1,13 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "376" }, + "to": { "type": "string" }, + "body": { "const": "End of /MOTD command" } + }, + "required": ["command", "to"] +} diff --git a/schema/numerics/401.json b/schema/numerics/401.json new file mode 100644 index 0000000..be77370 --- /dev/null +++ b/schema/numerics/401.json @@ -0,0 +1,21 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "401" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[target_nick]." + }, + "body": { "const": "No such nick/channel" } + }, + "required": ["command", "to", "params"], + "examples": [ + { "command": "401", "to": "alice", "params": ["bob"], "body": "No such nick/channel" } + ] +} diff --git a/schema/numerics/403.json b/schema/numerics/403.json new file mode 100644 index 0000000..f3bd5c2 --- /dev/null +++ b/schema/numerics/403.json @@ -0,0 +1,21 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "403" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[channel_name]." + }, + "body": { "const": "No such channel" } + }, + "required": ["command", "to", "params"], + "examples": [ + { "command": "403", "to": "alice", "params": ["#nonexistent"], "body": "No such channel" } + ] +} diff --git a/schema/numerics/433.json b/schema/numerics/433.json new file mode 100644 index 0000000..6d910af --- /dev/null +++ b/schema/numerics/433.json @@ -0,0 +1,21 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "433" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[requested_nick]." + }, + "body": { "const": "Nickname is already in use" } + }, + "required": ["command", "to", "params"], + "examples": [ + { "command": "433", "to": "*", "params": ["alice"], "body": "Nickname is already in use" } + ] +} diff --git a/schema/numerics/442.json b/schema/numerics/442.json new file mode 100644 index 0000000..9fe8db0 --- /dev/null +++ b/schema/numerics/442.json @@ -0,0 +1,18 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "442" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[channel]." + }, + "body": { "const": "You're not on that channel" } + }, + "required": ["command", "to", "params"] +} diff --git a/schema/numerics/482.json b/schema/numerics/482.json new file mode 100644 index 0000000..1f0ad75 --- /dev/null +++ b/schema/numerics/482.json @@ -0,0 +1,18 @@ +{ + "$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.", + "$ref": "../message.json", + "properties": { + "command": { "const": "482" }, + "to": { "type": "string" }, + "params": { + "type": "array", + "items": { "type": "string" }, + "description": "[channel]." + }, + "body": { "const": "You're not channel operator" } + }, + "required": ["command", "to", "params"] +} diff --git a/schema/s2c/001.schema.json b/schema/s2c/001.schema.json deleted file mode 100644 index 5786bdd..0000000 --- a/schema/s2c/001.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/002.schema.json b/schema/s2c/002.schema.json deleted file mode 100644 index ef8571f..0000000 --- a/schema/s2c/002.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/322.schema.json b/schema/s2c/322.schema.json deleted file mode 100644 index f704405..0000000 --- a/schema/s2c/322.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/353.schema.json b/schema/s2c/353.schema.json deleted file mode 100644 index d28371f..0000000 --- a/schema/s2c/353.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/366.schema.json b/schema/s2c/366.schema.json deleted file mode 100644 index 522a411..0000000 --- a/schema/s2c/366.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/372.schema.json b/schema/s2c/372.schema.json deleted file mode 100644 index d4469cd..0000000 --- a/schema/s2c/372.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/375.schema.json b/schema/s2c/375.schema.json deleted file mode 100644 index bfa7d88..0000000 --- a/schema/s2c/375.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/376.schema.json b/schema/s2c/376.schema.json deleted file mode 100644 index f5d34bb..0000000 --- a/schema/s2c/376.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/401.schema.json b/schema/s2c/401.schema.json deleted file mode 100644 index 1278cba..0000000 --- a/schema/s2c/401.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/403.schema.json b/schema/s2c/403.schema.json deleted file mode 100644 index 2e0870d..0000000 --- a/schema/s2c/403.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/433.schema.json b/schema/s2c/433.schema.json deleted file mode 100644 index cef8c52..0000000 --- a/schema/s2c/433.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/error.schema.json b/schema/s2c/error.schema.json deleted file mode 100644 index fd4a92d..0000000 --- a/schema/s2c/error.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/join.schema.json b/schema/s2c/join.schema.json deleted file mode 100644 index 10a0ff9..0000000 --- a/schema/s2c/join.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/kick.schema.json b/schema/s2c/kick.schema.json deleted file mode 100644 index b4503a6..0000000 --- a/schema/s2c/kick.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/mode.schema.json b/schema/s2c/mode.schema.json deleted file mode 100644 index be04679..0000000 --- a/schema/s2c/mode.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/nick.schema.json b/schema/s2c/nick.schema.json deleted file mode 100644 index 8b0cce0..0000000 --- a/schema/s2c/nick.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/notice.schema.json b/schema/s2c/notice.schema.json deleted file mode 100644 index 738719a..0000000 --- a/schema/s2c/notice.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/part.schema.json b/schema/s2c/part.schema.json deleted file mode 100644 index 9c05a99..0000000 --- a/schema/s2c/part.schema.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/pong.schema.json b/schema/s2c/pong.schema.json deleted file mode 100644 index a184887..0000000 --- a/schema/s2c/pong.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/privmsg.schema.json b/schema/s2c/privmsg.schema.json deleted file mode 100644 index 3e397d6..0000000 --- a/schema/s2c/privmsg.schema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/pubkey.schema.json b/schema/s2c/pubkey.schema.json deleted file mode 100644 index f720b0c..0000000 --- a/schema/s2c/pubkey.schema.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/quit.schema.json b/schema/s2c/quit.schema.json deleted file mode 100644 index edb2395..0000000 --- a/schema/s2c/quit.schema.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2c/topic.schema.json b/schema/s2c/topic.schema.json deleted file mode 100644 index 144b89c..0000000 --- a/schema/s2c/topic.schema.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/link.schema.json b/schema/s2s/link.schema.json deleted file mode 100644 index 1e07586..0000000 --- a/schema/s2s/link.schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/ping.schema.json b/schema/s2s/ping.schema.json deleted file mode 100644 index 14911ed..0000000 --- a/schema/s2s/ping.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/pong.schema.json b/schema/s2s/pong.schema.json deleted file mode 100644 index ae8c628..0000000 --- a/schema/s2s/pong.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/relay.schema.json b/schema/s2s/relay.schema.json deleted file mode 100644 index aa6b833..0000000 --- a/schema/s2s/relay.schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/sync.schema.json b/schema/s2s/sync.schema.json deleted file mode 100644 index 9218714..0000000 --- a/schema/s2s/sync.schema.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "$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" - ] -} diff --git a/schema/s2s/unlink.schema.json b/schema/s2s/unlink.schema.json deleted file mode 100644 index dc181d3..0000000 --- a/schema/s2s/unlink.schema.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "$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" - ] -}