Add embedded web chat client (closes #7) #8
227
README.md
227
README.md
@@ -18,10 +18,87 @@ This project builds a chat server that:
|
|||||||
|
|
||||||
- Holds session state server-side (message queues, presence, channel membership)
|
- Holds session state server-side (message queues, presence, channel membership)
|
||||||
- Exposes a minimal, clean HTTP+JSON API — easy to build clients against
|
- 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
|
- Provides IRC-like semantics: channels, nicks, topics, modes
|
||||||
- Uses structured JSON messages with IRC command names and numeric reply codes
|
- 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 <token>`.
|
||||||
|
- 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
|
## Architecture
|
||||||
|
|
||||||
### Transport: HTTP only
|
### 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.
|
request/response bodies. No WebSockets, no raw TCP, no gRPC — just plain HTTP.
|
||||||
|
|
||||||
- **Client polling**: Clients long-poll `GET /api/v1/messages` — server holds
|
- **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.
|
- **Client sending**: `POST /api/v1/messages` with a `to` field. That's it.
|
||||||
- **Server federation**: Servers exchange messages via HTTP to enable multi-server
|
- **Server federation**: Servers exchange messages via HTTP to enable multi-server
|
||||||
networks (like IRC server linking)
|
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
|
The entire read/write loop for a client is two endpoints. Everything else is
|
||||||
channel management and history.
|
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
|
### Message Protocol
|
||||||
|
|
||||||
All messages use **IRC command names and numeric reply codes** from RFC 1459/2812.
|
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 |
|
| PRIVMSG | Send message to channel or user |
|
||||||
| NOTICE | Send notice (no auto-reply expected) |
|
| NOTICE | Send notice (no auto-reply expected) |
|
||||||
| JOIN | Join a channel |
|
| JOIN | Join a channel (creates it if nonexistent) |
|
||||||
| PART | Leave a channel |
|
| PART | Leave a channel |
|
||||||
| QUIT | Disconnect from server |
|
| QUIT | Disconnect from server |
|
||||||
| NICK | Change nickname |
|
| NICK | Change nickname |
|
||||||
@@ -96,7 +201,7 @@ All C2S commands may be echoed back as S2C (relayed to other users), plus:
|
|||||||
|
|
||||||
| Code | Name | Description |
|
| Code | Name | Description |
|
||||||
|------|-------------------|-------------|
|
|------|-------------------|-------------|
|
||||||
| 001 | RPL_WELCOME | Welcome after registration |
|
| 001 | RPL_WELCOME | Welcome after session creation |
|
||||||
| 002 | RPL_YOURHOST | Server host information |
|
| 002 | RPL_YOURHOST | Server host information |
|
||||||
| 322 | RPL_LIST | Channel list entry |
|
| 322 | RPL_LIST | Channel list entry |
|
||||||
| 353 | RPL_NAMREPLY | Names list for a channel |
|
| 353 | RPL_NAMREPLY | Names list for a channel |
|
||||||
@@ -144,6 +249,8 @@ complete index.
|
|||||||
### Canonicalization and Signing
|
### Canonicalization and Signing
|
||||||
|
|
||||||
Messages support optional cryptographic signatures for integrity verification.
|
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)
|
#### Canonicalization (RFC 8785 JCS)
|
||||||
|
|
||||||
@@ -179,34 +286,9 @@ under canonicalization.
|
|||||||
{"command": "PUBKEY", "from": "alice", "body": {"alg": "ed25519", "key": "base64-encoded-pubkey"}}
|
{"command": "PUBKEY", "from": "alice", "body": {"alg": "ed25519", "key": "base64-encoded-pubkey"}}
|
||||||
```
|
```
|
||||||
|
|
||||||
Servers SHOULD relay PUBKEY messages to all channel members. Clients SHOULD
|
Servers relay PUBKEY messages to all channel members. Clients cache public keys
|
||||||
cache public keys and use them to verify `meta.sig` on incoming messages.
|
and use them to verify `meta.sig` on incoming messages. Key distribution is
|
||||||
Key distribution is trust-on-first-use (TOFU) by default.
|
trust-on-first-use (TOFU). There is no key revocation mechanism.
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
### API Endpoints
|
### API Endpoints
|
||||||
|
|
||||||
@@ -216,9 +298,9 @@ require `Authorization: Bearer <token>` header.
|
|||||||
The API is the primary interface — designed for IRC-style clients. The entire
|
The API is the primary interface — designed for IRC-style clients. The entire
|
||||||
client loop is:
|
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
|
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"`
|
4. `POST /api/v1/messages` — send to `"#channel"` or `"nick"`
|
||||||
|
|
||||||
That's the core. Everything else (join, part, history, members) is ancillary.
|
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)
|
#### Quick example (curl)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Register
|
# Create a session (get session UUID, client UUID, and auth token)
|
||||||
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/register \
|
TOKEN=$(curl -s -X POST http://localhost:8080/api/v1/session \
|
||||||
-d '{"nick":"alice"}' | jq -r .token)
|
-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 \
|
curl -s -X POST http://localhost:8080/api/v1/messages \
|
||||||
-H "Authorization: Bearer $TOKEN" \
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
-d '{"command":"JOIN","to":"#general"}'
|
-d '{"command":"JOIN","to":"#general"}'
|
||||||
@@ -240,34 +322,41 @@ curl -s -X POST http://localhost:8080/api/v1/messages \
|
|||||||
-H "Authorization: Bearer $TOKEN" \
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
-d '{"command":"PRIVMSG","to":"#general","body":["hello world"]}'
|
-d '{"command":"PRIVMSG","to":"#general","body":["hello world"]}'
|
||||||
|
|
||||||
# Poll for messages (long-poll)
|
# Poll for messages (long-poll, 15s timeout)
|
||||||
curl -s http://localhost:8080/api/v1/messages?after=0 \
|
curl -s "http://localhost:8080/api/v1/messages?timeout=15" \
|
||||||
-H "Authorization: Bearer $TOKEN"
|
-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
|
#### State
|
||||||
|
|
||||||
```
|
```
|
||||||
GET /api/v1/state — User state: nick, id, and list of joined channels
|
GET /api/v1/state — User state: nick, session_id, client_id,
|
||||||
Replaces separate /me and /channels endpoints
|
and list of joined channels
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Messages (unified stream)
|
#### 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
|
All message types: channel, DM, notices, events
|
||||||
Query params: ?after=<message-id>&timeout=30
|
Delivers from the calling client's queue
|
||||||
|
(identified by auth token)
|
||||||
|
Query params: ?after=<message-id>&timeout=15
|
||||||
POST /api/v1/messages — Send a message (IRC command in body)
|
POST /api/v1/messages — Send a message (IRC command in body)
|
||||||
Body: { "command": "PRIVMSG", "to": "#channel", "body": ["..."] }
|
Body: { "command": "PRIVMSG", "to": "#channel", "body": ["..."] }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Messages are immutable — no edit or delete endpoints.
|
||||||
|
|
||||||
#### History
|
#### 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
|
GET /api/v1/channels/all — List all server channels
|
||||||
POST /api/v1/channels/join — Join a channel { "channel": "#name" }
|
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
|
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
|
GET /api/v1/channels/{name}/members — Channel member list
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -294,7 +385,8 @@ GET /.well-known/healthcheck.json — Health check
|
|||||||
|
|
||||||
### Federation (Server-to-Server)
|
### 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)
|
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,
|
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
|
UNLINK, SYNC, PING, and PONG commands. Messages (including signatures) are
|
||||||
users on different servers can share channels.
|
relayed verbatim between servers so users on different servers can share channels.
|
||||||
|
|
||||||
### Channel Modes
|
### Channel Modes
|
||||||
|
|
||||||
@@ -331,7 +423,9 @@ Via environment variables (Viper), following gohttpserver conventions:
|
|||||||
| `DEBUG` | `false` | Debug mode |
|
| `DEBUG` | `false` | Debug mode |
|
||||||
| `MAX_HISTORY` | `10000` | Max messages per channel history |
|
| `MAX_HISTORY` | `10000` | Max messages per channel history |
|
||||||
| `SESSION_TIMEOUT` | `86400` | Session idle timeout (seconds) |
|
| `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) |
|
| `MAX_MESSAGE_SIZE` | `4096` | Max message body size (bytes) |
|
||||||
|
| `LONG_POLL_TIMEOUT` | `15` | Long-poll timeout in seconds |
|
||||||
| `MOTD` | `""` | Message of the day |
|
| `MOTD` | `""` | Message of the day |
|
||||||
| `SERVER_NAME` | hostname | Server display name |
|
| `SERVER_NAME` | hostname | Server display name |
|
||||||
| `FEDERATION_KEY` | `""` | Shared key for server linking |
|
| `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
|
SQLite by default (single-file, zero-config), with Postgres support for
|
||||||
larger deployments. Tables:
|
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
|
- `channels` — channel metadata and modes
|
||||||
- `channel_members` — membership and user modes
|
- `channel_members` — membership and user modes
|
||||||
- `messages` — message history (rotated per `MAX_HISTORY`)
|
- `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
|
- `server_links` — federation peer configuration
|
||||||
|
|
||||||
### Project Structure
|
### Project Structure
|
||||||
@@ -398,30 +493,30 @@ Per gohttpserver conventions:
|
|||||||
| Metrics | `github.com/prometheus/client_golang` |
|
| Metrics | `github.com/prometheus/client_golang` |
|
||||||
| DB | `modernc.org/sqlite` + `database/sql` |
|
| 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
|
### Design Principles
|
||||||
|
|
||||||
1. **API-first** — the HTTP API is the product. Clients are thin. If you can't
|
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.
|
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.
|
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.
|
negotiation. HTTP is universal, proxy-friendly, and works everywhere.
|
||||||
4. **Server holds state** — clients are stateless. Reconnect, switch devices,
|
5. **Server holds state** — clients are stateless. Reconnect, switch devices,
|
||||||
lose connectivity — your messages are waiting.
|
lose connectivity — your messages are waiting in your client queue.
|
||||||
5. **Structured messages** — JSON with extensible metadata. Bodies are always
|
6. **Structured messages** — JSON with extensible metadata. Bodies are always
|
||||||
objects or arrays for deterministic canonicalization (JCS) and signing.
|
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.
|
external dependencies.
|
||||||
7. **No eternal logs** — history rotates. Chat should be ephemeral by default.
|
9. **No eternal logs** — history rotates. Chat should be ephemeral by default.
|
||||||
8. **Federation optional** — single server works standalone. Linking is opt-in.
|
Channels disappear when empty.
|
||||||
9. **Signable messages** — optional Ed25519 signatures with TOFU key distribution.
|
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
|
## Status
|
||||||
|
|
||||||
|
|||||||
142
schema/README.md
142
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
|
## Envelope
|
||||||
[`message.schema.json`](message.schema.json).
|
|
||||||
|
|
||||||
## Base Envelope
|
Every message is a JSON object with a `command` field. The format maps directly
|
||||||
|
to IRC wire format:
|
||||||
|
|
||||||
| Field | Type | Required | Description |
|
```
|
||||||
|-----------|-----------------|----------|-------------|
|
IRC: :nick PRIVMSG #channel :hello world
|
||||||
| `command` | string | ✓ | IRC command name or numeric reply code |
|
JSON: {"command": "PRIVMSG", "from": "nick", "to": "#channel", "body": "hello world"}
|
||||||
| `from` | string | | Sender nick or server name |
|
|
||||||
| `to` | string | | Destination channel or nick |
|
|
||||||
| `params` | array\<string\> | | 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 |
|
|
||||||
|
|
||||||
## 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 |
|
Common fields (see `message.json` for full schema):
|
||||||
|----------|--------|-------------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
## 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 |
|
IRC commands used for client↔server and server↔server communication.
|
||||||
|----------|--------|-------------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
### 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 |
|
## Numeric Replies
|
||||||
|------|--------------------|--------|-------------|
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
## Server-to-Server (S2S)
|
Three-digit codes for server responses, per IRC convention.
|
||||||
|
|
||||||
| Command | Schema | Description |
|
### Success / Informational (0xx–3xx)
|
||||||
|---------|--------|-------------|
|
|
||||||
| RELAY | [`s2s/relay.schema.json`](s2s/relay.schema.json) | Relay message to linked server |
|
| Code | Name | File | Description |
|
||||||
| LINK | [`s2s/link.schema.json`](s2s/link.schema.json) | Establish server link |
|
|-------|-------------------|-----------------------|--------------------------------|
|
||||||
| UNLINK | [`s2s/unlink.schema.json`](s2s/unlink.schema.json) | Tear down link |
|
| `001` | RPL_WELCOME | `numerics/001.json` | Welcome after session creation |
|
||||||
| SYNC | [`s2s/sync.schema.json`](s2s/sync.schema.json) | Synchronize state |
|
| `002` | RPL_YOURHOST | `numerics/002.json` | Server host info |
|
||||||
| PING | [`s2s/ping.schema.json`](s2s/ping.schema.json) | Server ping |
|
| `003` | RPL_CREATED | `numerics/003.json` | Server creation date |
|
||||||
| PONG | [`s2s/pong.schema.json`](s2s/pong.schema.json) | Server pong |
|
| `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.
|
||||||
|
|||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
16
schema/commands/JOIN.json
Normal file
16
schema/commands/JOIN.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
34
schema/commands/KICK.json
Normal file
34
schema/commands/KICK.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
29
schema/commands/MODE.json
Normal file
29
schema/commands/MODE.json
Normal file
@@ -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"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
16
schema/commands/NICK.json
Normal file
16
schema/commands/NICK.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
17
schema/commands/NOTICE.json
Normal file
17
schema/commands/NOTICE.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
17
schema/commands/PART.json
Normal file
17
schema/commands/PART.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
18
schema/commands/PING.json
Normal file
18
schema/commands/PING.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
22
schema/commands/PONG.json
Normal file
22
schema/commands/PONG.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
18
schema/commands/PRIVMSG.json
Normal file
18
schema/commands/PRIVMSG.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
16
schema/commands/QUIT.json
Normal file
16
schema/commands/QUIT.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
17
schema/commands/TOPIC.json
Normal file
17
schema/commands/TOPIC.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
46
schema/message.json
Normal file
46
schema/message.json
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "https://git.eeqj.de/sneak/chat/schema/message.json",
|
||||||
|
"title": "IRC Message Envelope",
|
||||||
|
"description": "Base envelope for all messages. Mirrors IRC wire format (RFC 1459/2812) encoded as JSON over HTTP. The 'command' field carries either an IRC command name (PRIVMSG, JOIN, etc.) or a three-digit numeric reply code (001, 353, 433, etc.).",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"description": "Server-assigned message ID, monotonically increasing. Present on all server-originated messages."
|
||||||
|
},
|
||||||
|
"command": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "IRC command name (PRIVMSG, JOIN, NICK, etc.) or three-digit numeric reply code (001, 353, 433, etc.).",
|
||||||
|
"examples": ["PRIVMSG", "JOIN", "001", "353", "433"]
|
||||||
|
},
|
||||||
|
"from": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Source — nick for user messages, server name for server messages. Equivalent to IRC prefix."
|
||||||
|
},
|
||||||
|
"to": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Target — channel (#name) or nick. Equivalent to first IRC parameter for most commands."
|
||||||
|
},
|
||||||
|
"params": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "Additional parameters (used primarily by numeric replies). Equivalent to IRC middle parameters."
|
||||||
|
},
|
||||||
|
"body": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Message body / trailing parameter. Equivalent to IRC trailing parameter (after the colon)."
|
||||||
|
},
|
||||||
|
"ts": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time",
|
||||||
|
"description": "Server-assigned timestamp (ISO 8601). Not present in original IRC; added for HTTP transport."
|
||||||
|
},
|
||||||
|
"meta": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "Extensible metadata (signatures, rich content hints, etc.). Not present in original IRC.",
|
||||||
|
"additionalProperties": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["command"]
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
20
schema/numerics/001.json
Normal file
20
schema/numerics/001.json
Normal file
@@ -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"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
20
schema/numerics/002.json
Normal file
20
schema/numerics/002.json
Normal file
@@ -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"] }
|
||||||
|
]
|
||||||
|
}
|
||||||
36
schema/numerics/003.json
Normal file
36
schema/numerics/003.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
39
schema/numerics/004.json
Normal file
39
schema/numerics/004.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
47
schema/numerics/322.json
Normal file
47
schema/numerics/322.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
13
schema/numerics/323.json
Normal file
13
schema/numerics/323.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
47
schema/numerics/332.json
Normal file
47
schema/numerics/332.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
48
schema/numerics/353.json
Normal file
48
schema/numerics/353.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
18
schema/numerics/366.json
Normal file
18
schema/numerics/366.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
36
schema/numerics/372.json
Normal file
36
schema/numerics/372.json
Normal file
@@ -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!"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
26
schema/numerics/375.json
Normal file
26
schema/numerics/375.json
Normal file
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
schema/numerics/376.json
Normal file
13
schema/numerics/376.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
21
schema/numerics/401.json
Normal file
21
schema/numerics/401.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
21
schema/numerics/403.json
Normal file
21
schema/numerics/403.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
21
schema/numerics/433.json
Normal file
21
schema/numerics/433.json
Normal file
@@ -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" }
|
||||||
|
]
|
||||||
|
}
|
||||||
18
schema/numerics/442.json
Normal file
18
schema/numerics/442.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
18
schema/numerics/482.json
Normal file
18
schema/numerics/482.json
Normal file
@@ -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"]
|
||||||
|
}
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user