docs: initialise new docs with docusaurus (#460)

* docs: initialise new docs with docusaurus

* chore: sign off
Signed-off-by: izzy <me@insrt.uk>

* chore: lock pnpm version in package.json

Signed-off-by: izzy <me@insrt.uk>

* ci: specify package json to load pnpm version from

Signed-off-by: izzy <me@insrt.uk>

* docs: update repo. lifecycle details

Signed-off-by: izzy <me@insrt.uk>

* docs: add external links to for-web, for-android
chore: add default.nix to docs

* docs: correct headings in contrib

* ci: use cache dependency path

* fix: docusaurus configuration

---------

Signed-off-by: izzy <me@insrt.uk>
This commit is contained in:
Paul Makles
2025-11-14 22:06:20 +00:00
committed by GitHub
parent e006cdd051
commit 0b7c132ace
38 changed files with 31136 additions and 57 deletions

View File

@@ -0,0 +1,10 @@
{
"label": "API",
"position": 2,
"link": {
"type": "generated-index",
"description": "Connecting and consuming the Stoat API"
},
"collapsed": false,
"collapsible": true
}

View File

@@ -0,0 +1,15 @@
# Authentication
To authenticate with the API, you must first acquire a bot token or user token:
- **Bot:** create one from user settings in the client
- **User:** copy one from client or authenticate through API
Then you may provide these through either:
| Type | Header |
| :---: | :---------------: |
| Bot | `X-Bot-Token` |
| User | `X-Session-Token` |
When dealing with an authenticated route.

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 74 KiB

View File

@@ -0,0 +1,49 @@
# Permissions
Stoat's permission system works by sequentially applying allows then denies.
## Flow Chart
Below are the high-level steps taken to determine both server and channel permissions (click to view).
<a href="./permission hierarchy.svg" target="_blank">
<img src="./permission_hierarchy.svg" style={{background: "white", padding: "1em", borderRadius: "1em"}} />
</a>
If you are looking to implement permissions in a library, I highly recommend reading either the Stoat JavaScript SDK or the backend source code since all the routines are well commented and should be relatively easy to understand.
## Values
The following permissions are currently allocated:
| Name | Value | Bitwise | Description |
| --------------------- | :-----------: | :-------: | ------------------------------------------------------- |
| `ManageChannel` | `1` | `1 << 0` | Manage the channel or channels on the server |
| `ManageServer` | `2` | `1 << 1` | Manage the server |
| `ManagePermissions` | `4` | `1 << 2` | Manage permissions on servers or channels |
| `ManageRole` | `8` | `1 << 3` | Manage roles on server |
| `ManageCustomisation` | `16` | `1 << 4` | Manage emoji on servers |
| `KickMembers` | `64` | `1 << 6` | Kick other members below their ranking |
| `BanMembers` | `128` | `1 << 7` | Ban other members below their ranking |
| `TimeoutMembers` | `256` | `1 << 8` | Timeout other members below their ranking |
| `AssignRoles` | `512` | `1 << 9` | Assign roles to members below their ranking |
| `ChangeNickname` | `1024` | `1 << 10` | Change own nickname |
| `ManageNicknames` | `2048` | `1 << 11` | Change or remove other's nicknames below their ranking |
| `ChangeAvatar` | `4096` | `1 << 12` | Change own avatar |
| `RemoveAvatars` | `8192` | `1 << 13` | Remove other's avatars below their ranking |
| `ViewChannel` | `1048576` | `1 << 20` | View a channel |
| `ReadMessageHistory` | `2097152` | `1 << 21` | Read a channel's past message history |
| `SendMessage` | `4194304` | `1 << 22` | Send a message in a channel |
| `ManageMessages` | `8388608` | `1 << 23` | Delete messages in a channel |
| `ManageWebhooks` | `16777216` | `1 << 24` | Manage webhook entries on a channel |
| `InviteOthers` | `33554432` | `1 << 25` | Create invites to this channel |
| `SendEmbeds` | `67108864` | `1 << 26` | Send embedded content in this channel |
| `UploadFiles` | `134217728` | `1 << 27` | Send attachments and media in this channel |
| `Masquerade` | `268435456` | `1 << 28` | Masquerade messages using custom nickname and avatar |
| `React` | `536870912` | `1 << 29` | React to messages with emojis |
| `Connect` | `1073741824` | `1 << 30` | Connect to a voice channel |
| `Speak` | `2147483648` | `1 << 31` | Speak in a voice call |
| `Video` | `4294967296` | `1 << 32` | Share video in a voice call |
| `MuteMembers` | `8589934592` | `1 << 33` | Mute other members with lower ranking in a voice call |
| `DeafenMembers` | `17179869184` | `1 << 34` | Deafen other members with lower ranking in a voice call |
| `MoveMembers` | `34359738368` | `1 << 35` | Move members between voice channels |

View File

@@ -0,0 +1,49 @@
# Rate Limits
Stoat uses a fixed-window ratelimiting algorithm:
- You are given a set amount of calls per each named bucket.
- Any calls past this limit will result in 429 errors.
- Buckets are replenished after 10 seconds from initial request.
## Buckets
There are distinct buckets that you may be calling against, none of these affect each other and can be used up independently of one another.
| Method | Path | Limit |
| -------: | --------------------------- | :---: |
| | `/users` | 20 |
| `PATCH` | `/users/:id` | 2 |
| | `/users/:id/default_avatar` | 255 |
| | `/bots` | 10 |
| | `/channels` | 15 |
| `POST` | `/channels/:id/messages` | 10 |
| | `/servers` | 5 |
| | `/auth` | 3 |
| `DELETE` | `/auth` | 255 |
| | `/safety` | 15 |
| | `/safety/report` | 3 |
| | `/swagger` | 100 |
| | `/*` | 20 |
## Headers
There are multiple headers you can use to figure out when you can and cannot send requests, and to determine when you can next send a request.
| Header | Type | Description |
| ------------------------- | :------: | ------------------------------------------------ |
| `X-RateLimit-Limit` | `number` | Maximum number of calls allowed for this bucket. |
| `X-RateLimit-Bucket` | `string` | Unique identifier for this bucket. |
| `X-RateLimit-Remaining` | `number` | Remaining number of calls left for this bucket. |
| `X-RateLimit-Reset-After` | `number` | Milliseconds left until calls are replenished. |
## Rate Limited Response
When you receive `429 Too Many Requests`, you will also receive a JSON body with the schema:
```typescript
interface Response {
// Milliseconds until calls are replenished
retry_after: number;
}
```

View File

@@ -0,0 +1,47 @@
# Uploading Files
File uploads work by first sending a file to the server and then using the ID provided.
You can find out what kinds of files you can upload by visiting [the API documentation](https://cdn.stoatusercontent.com/scalar).
To upload a file, pick the desired tag then send a **POST** to `{endpoint}/{tag}` along with a `multipart/form-data` body with one field `file` that contains the file you wish to upload.
You must specify session/bot authentication token as with any other API route.
You will receive the following JSON response:
```json
{
"id": "0"
}
```
You can use the ID wherever a file is required in the API.
Code sample in JavaScript using Fetch API:
```js
const body = new FormData();
body.append("file", file);
const data = await fetch(`${endpoint}/${tag}`, {
method: "POST",
body,
headers: {
"X-Session-Token": "...", // or X-Bot-Token
},
}).then((res) => res.json());
// use data.id
```
## Differences from old Autumn
If you are migrating from old Autumn, the following key points are important:
- There are only two paths that serve a unique image, the preview version of it (if available) and the original image.
- You should not specify any query parameters under any circumstance, the preview route will serve the optimal size for the content type.
- Preview routes for banners, emojis, backgrounds, and attachments will redirect to the original file where the file is not an image or the image is animated.
- If you are currently using logic to replace the URL path to start/stop animations, you should use the following templates: (NB. this only applies to avatars and icons)
- Non-animated file: `/{tag}/{file_id}`
- Animated file: `/{tag}/{file_id}/{file_name}` or `/{tag}/{file_id}/original` (if name unavailable)

View File

@@ -0,0 +1,23 @@
---
sidebar_position: 1
---
# Endpoints
**We are moving stuff around currently following the rebrand, guidance will follow soon!**
You can connect to the API on the following URLs:
| URL | Release | Description |
| ----------------------------- | :--------: | --------------------------- |
| `https://api.revolt.chat` | Production | Primary API endpoint |
| `https://app.revolt.chat/api` | Production | API endpoint for old client |
| `https://revolt.chat/api` | Staging | API endpoint for new client |
You can connect to the events server on the following URLs:
| URL | Release | Description |
| ------------------------------ | :--------: | ------------------------------ |
| `wss://ws.revolt.chat` | Production | Primary events endpoint |
| `wss://app.revolt.chat/events` | Production | Events endpoint for old client |
| `wss://revolt.chat/events` | Staging | Events endpoint for new client |

View File

@@ -0,0 +1,10 @@
{
"label": "Events",
"position": 2,
"link": {
"type": "generated-index",
"description": "Connecting and using the Stoat gateway"
},
"collapsed": false,
"collapsible": true
}

View File

@@ -0,0 +1,57 @@
# Establishing a connection
To get started, you should have:
- A WebSocket URL, which is found from the API root.
- A valid session or bot token.
You may authenticate in one of two ways:
- Include credentials in the connection URL, see [Query Parameters](#query-parameters).
- Sending an [Authenticate](./protocol.md#authenticate) event to the server.
You should listen out for [Error](./protocol.md#error) events to find out if your credentials are incorrect or if something goes wrong here.
After authenticating, the server will respond with [Authenticated](./protocol.md#authenticated) then it will send a [Ready](./protocol.md#ready) event containing useful data.
The server will now start sending relevant events as they come in.
You should [Ping](./protocol.md#ping) the server every 10 to 30 seconds to prevent your connection being dropped.
Bots receive all events, normal users do not receive UserUpdate events fanned out through servers by default, [read more here](./protocol.md#subscribe).
## Query Parameters
The Bonfire service supports additional query parameters:
| Parameter | Description | Values | Required |
| --------- | ----------------------------------------------------------------------- | -------------------------- | -------- |
| `version` | Describes the protocol version in use. | `1` | No † |
| `format` | In what format to send packets, default is JSON. | `json`, `msgpack` | No |
| `token` | token for authenticating the connecting user. | Session or bot token | No |
| `ready` | Fields to include in the `Ready` event payload, by default all are sent | [See Below](#ready-fields) | No |
`version` may become compulsary in the future, please set it to `1` if you can.
### Ready Fields
The ready query parameter can be passed multiple times to specify multiple fields, supported fields are:
| Value | Description |
| ----------------- | --------------------------------------------------------------------------------------------------------------- |
| `users` | Includes all users you have a relation with. |
| `servers` | Includes all servers you are in. |
| `channels` | Includes all channels you have access to. |
| `members` | Includes all members you have a relation with. |
| `emojis` | Includes all emojis you have access to. |
| `user_settings` | Specify which settings to pre-fetch, specify a setting by setting the value to `user_settings[<setting_name>]`. |
| `channel_unreads` | Includes all channel unreads you have. |
| `policy_changes` | Includes all new policy changes you should be aware of, this is not sent to bots. |
For example:
```
?ready=users&ready=servers&ready=user_settings[ordering]
```
You may specify these in the connection URL: `wss://stoat.chat/events?version=1&format=json`.

View File

@@ -0,0 +1,589 @@
# Events
This page documents various incoming and outgoing events.
**Help Wanted:** we should adopt [AsyncAPI](https://www.asyncapi.com) to properly document the protocol!
## Client to Server
### Authenticate
Authenticate with the server.
```json
{
"type": "Authenticate",
"token": "{token}"
}
```
### BeginTyping
Tell other users that you have begun typing in a channel.
Must be in the specified channel or nothing will happen.
```json
{
"type": "BeginTyping",
"channel": "{channel_id}"
}
```
### EndTyping
Tell other users that you have stopped typing in a channel.
Must be in the specified channel or nothing will happen.
```json
{
"type": "EndTyping",
"channel": "{channel_id}"
}
```
### Ping
Ping the server, you can specify a timestamp that you'll receive back.
```json
{
"type": "Ping",
"data": 0
}
```
### Subscribe
Subscribe to a server's UserUpdate events.
```json
{
"type": "Subscribe",
"server_id": "{server_id}"
}
```
Implementation notes:
- Subscriptions automatically expire within 15 minutes.
- A client may have up to 5 active subscriptions.
- This has no effect on bot sessions.
- This event should only be sent **iff** app/client is in focus.
- You should aim to send this event at most every 10 minutes per server.
## Server to Client
### Error
An error occurred which meant you couldn't authenticate.
```json
{
"type": "Error",
"error": "{error_id}"
}
```
The `{error_id}` can be one of the following:
- `LabelMe`: uncategorised error
- `InternalError`: the server ran into an issue
- `InvalidSession`: authentication details are incorrect
- `OnboardingNotFinished`: user has not chosen a username
- `AlreadyAuthenticated`: this connection is already authenticated
### Authenticated
The server has authenticated your connection and you will shortly start receiving data.
```json
{
"type": "Authenticated"
}
```
### Logged Out
The current user session has been invalidated or the bot token has been reset.
```json
{
"type": "Logout"
}
```
Your connection will be closed shortly after.
### Bulk
Several events have been sent, process each item of `v` as its own event.
```json
{
"type": "Bulk",
"v": [...]
}
```
### Pong
Ping response from the server.
```json
{
"type": "Pong",
"data": 0
}
```
### Ready
Data for use by client, data structures match the API specification.
```json
{
"type": "Ready",
"users"?: [{..}],
"servers"?: [{..}],
"channels"?: [{..}],
"members"?: [{..}],
"emojis"?: [{..}],
"user_settings"?: [{..}],
"channel_unreads"?: [{..}],
"policy_changes"?: [{..}],
}
```
### Message
Message received, the event object has the same schema as the Message object in the API with the addition of an event type.
```json
{
"type": "Message",
[..]
}
```
### MessageUpdate
Message edited or otherwise updated.
```json
{
"type": "MessageUpdate",
"id": "{message_id}",
"channel": "{channel_id}",
"data": {..}
}
```
- `data` field contains a partial Message object.
### MessageAppend
Message has data being appended to it.
```json
{
"type": "MessageAppend",
"id": "{message_id}",
"channel": "{channel_id}",
"append": {
"embeds"?: [...]
}
}
```
### MessageDelete
Message has been deleted.
```json
{
"type": "MessageDelete",
"id": "{message_id}",
"channel": "{channel_id}"
}
```
### MessageReact
A reaction has been added to a message.
```json
{
"type": "MessageReact",
"id": "{message_id}",
"channel_id": "{channel_id}",
"user_id": "{user_id}",
"emoji_id": "{emoji_id}"
}
```
### MessageUnreact
A reaction has been removed from a message.
```json
{
"type": "MessageUnreact",
"id": "{message_id}",
"channel_id": "{channel_id}",
"user_id": "{user_id}",
"emoji_id": "{emoji_id}"
}
```
### MessageRemoveReaction
A certain reaction has been removed from the message.
```json
{
"type": "MessageRemoveReaction",
"id": "{message_id}",
"channel_id": "{channel_id}",
"emoji_id": "{emoji_id}"
}
```
### ChannelCreate
Channel created, the event object has the same schema as the Channel object in the API with the addition of an event type.
```json
{
"type": "ChannelCreate",
[..]
}
```
### ChannelUpdate
Channel details updated.
```json
{
"type": "ChannelUpdate",
"id": "{channel_id}",
"data": {..},
"clear": ["{field}", ...]
}
```
- `data` field contains a partial Channel object.
- `{field}` is a field to remove, one of:
- `Icon`
- `Description`
### ChannelDelete
Channel has been deleted.
```json
{
"type": "ChannelDelete",
"id": "{channel_id}"
}
```
### ChannelGroupJoin
A user has joined the group.
```json
{
"type": "ChannelGroupJoin",
"id": "{channel_id}",
"user": "{user_id}"
}
```
### ChannelGroupLeave
A user has left the group.
```json
{
"type": "ChannelGroupLeave",
"id": "{channel_id}",
"user": "{user_id}"
}
```
### ChannelStartTyping
A user has started typing in this channel.
```json
{
"type": "ChannelStartTyping",
"id": "{channel_id}",
"user": "{user_id}"
}
```
### ChannelStopTyping
A user has stopped typing in this channel.
```json
{
"type": "ChannelStopTyping",
"id": "{channel_id}",
"user": "{user_id}"
}
```
### ChannelAck
You have acknowledged new messages in this channel up to this message ID.
```json
{
"type": "ChannelAck",
"id": "{channel_id}",
"user": "{user_id}",
"message_id": "{message_id}"
}
```
### ServerCreate
Server created, the event object has the same schema as the SERVER object in the API with the addition of an event type.
```json
{
"type": "ServerCreate",
[..]
}
```
### ServerUpdate
Server details updated.
```json
{
"type": "ServerUpdate",
"id": "{server_id}",
"data": {..},
"clear": ["{field}", ...]
}
```
- `data` field contains a partial Server object.
- `{field}` is a field to remove, one of:
- `Icon`
- `Banner`
- `Description`
### ServerDelete
Server has been deleted.
```json
{
"type": "ServerDelete",
"id": "{server_id}"
}
```
### ServerMemberUpdate
Server member details updated.
```json
{
"type": "ServerMemberUpdate",
"id": {
"server": "{server_id}",
"user": "{user_id}"
},
"data": {..},
"clear": ["{field}", ...]
}
```
- `data` field contains a partial Server Member object.
- `{field}` is a field to remove, one of:
- `Nickname`
- `Avatar`
### ServerMemberJoin
A user has joined the server.
```json
{
"type": "ServerMemberJoin",
"id": "{server_id}",
"user": "{user_id}",
"member": {..}
}
```
- `member` field contains a Member object.
### ServerMemberLeave
A user has left the server.
```json
{
"type": "ServerMemberLeave",
"id": "{server_id}",
"user": "{user_id}"
}
```
### ServerRoleUpdate
Server role has been updated or created.
```json
{
"type": "ServerRoleUpdate",
"id": "{server_id}",
"role_id": "{role_id}",
"data": {..},
"clear": ["{field}", ...]
}
```
- `data` field contains a partial Server Role object.
- `clear` is a field to remove, one of:
- `Colour`
### ServerRoleDelete
Server role has been deleted.
```json
{
"type": "ServerRoleDelete",
"id": "{server_id}",
"role_id": "{role_id}"
}
```
### UserUpdate
User has been updated.
```json
{
"type": "UserUpdate",
"id": "{user_id}",
"data": {..},
"clear": ["{field}", ...]
}
```
- `data` field contains a partial User object.
- `clear` is a field to remove, one of:
- `ProfileContent`
- `ProfileBackground`
- `StatusText`
- `Avatar`
- `DisplayName`
### UserRelationship
Your relationship with another user has changed.
```json
{
"type": "UserRelationship",
"id": "{your_user_id}",
"user": "{..}",
"status": "{status}"
}
```
- `user` field contains a User object.
- `status` field matches Relationship Status in API.
### UserPlatformWipe
User has been platform banned or deleted their account.
Clients should remove the following associated data:
- Messages
- DM Channels
- Relationships
- Server Memberships
User flags are specified to explain why a wipe is occurring though not all reasons will necessarily ever appear.
```json
{
"type": "UserPlatformWipe",
"user_id": "{user_id}",
"flags": "{user_flags}"
}
```
### EmojiCreate
Emoji created, the event object has the same schema as the Emoji object in the API with the addition of an event type.
```json
{
"type": "EmojiCreate",
[..]
}
```
### EmojiDelete
Emoji has been deleted.
```json
{
"type": "EmojiDelete",
"id": "{emoji_id}"
}
```
### Auth
Forwarded events from [Authifier](https://github.com/authifier/authifier), currently only session deletion events are forwarded.
```json
{
"type": "Auth",
"event_type": "{event_type}",
[..]
}
```
Event type may be defined as one of the following with the additional properties:
#### DeleteSession
A session has been deleted.
```json
{
"event_type": "DeleteSession",
"user_id": "{user_id}",
"session_id": "{session_id}"
}
```
#### DeleteAllSessions
All sessions for this account have been deleted, optionally excluding a given ID.
```json
{
"event_type": "DeleteAllSessions",
"user_id": "{user_id}",
"exclude_session_id": "{session_id}"
}
```

View File

@@ -0,0 +1,147 @@
# Plugin API
:::danger
This page documents the old Revite Plugin API (manifest v1), it will be replaced in the new client.
:::
:::warning
The Plugin API is very powerful. **Tread carefully.**
**Zero guarantees or sandboxes are provided.** Your code is run as-is.
:::
This document details the very experimental plugin API available in [Revite](https://github.com/revoltchat/revite).
This is more or less a proof of concept but can be used to achieve some simple client modifications.
## Plugin Manifest
Below is the specification for revision 1 of the plugin API. The `format` parameter is not currently enforced but you should set it to `1` to avoid future breakage.
````typescript
type Plugin = {
/**
* Plugin Format Revision
*/
format: 1;
/**
* Semver Version String
*
* This is the version of the plugin.
*/
version: string;
/**
* Plugin Namespace
*
* This will usually be the author's name.
*/
namespace: string;
/**
* Plugin Id
*
* This should be a valid URL slug, e.g. cool-plugin.
*/
id: string;
/**
* Entrypoint
*
* Valid Javascript code. It must be a function which returns a object.
*
* ```typescript
* function (state: State) {
* return {
* onUnload: () => {}
* }
* }
* ```
*/
entrypoint: string;
/**
* Whether this plugin is enabled.
*
* @default true
*/
enabled?: boolean;
};
````
An example plugin:
```javascript
{
format: 1,
version: "0.0.1",
namespace: "insert",
id: "my-plugin",
entrypoint: `(state) => {
console.log('[my-plugin] Plugin init!');
return {
onUnload: () => console.log('[my-plugin] bye!')
}
}`
}
```
## Using the Plugin API
To begin, you can load plugins using the global plugin manager at `state.plugins`.
Open the developer console and run:
```javascript
state.plugins.load({ ... });
// ...where [...] is your plugin manifest as described above.
```
## Plugin API
A plugin's entrypoint is required to return an object which is referred to as the **instance**:
```typescript
interface Instance {
onUnload?: () => void;
}
```
The Plugin API (`state.plugins`) exposes the following methods:
```typescript
interface PluginAPI {
/**
* Add a plugin
* @param plugin Plugin Manifest
*/
add(plugin: Plugin);
/**
* Remove a plugin
* @param namespace Plugin Namespace
* @param id Plugin Id
*/
remove(namespace: string, id: string);
/**
* Load a plugin
* @param namespace Plugin Namespace
* @param id Plugin Id
*/
load(namespace: string, id: string);
/**
* Unload a plugin
* @param namespace Plugin Namespace
* @param id Plugin Id
*/
unload(namespace: string, id: string);
/**
* Reset everything
*/
reset();
}
```

View File

@@ -0,0 +1,8 @@
# Libraries
The following libraries are provided by the Stoat team:
- [Javascript SDK](https://github.com/stoatchat/javascript-client-sdk)
- [Python SDK](https://github.com/stoatchat/python-client-sdk)
You can find a host of [community created libraries here](https://github.com/stoatchat/awesome-stoat#-api-libraries).

View File

@@ -0,0 +1,22 @@
# Stoat Migration Guide
:::warning
This is not yet finished.
:::
## Endpoint changes
| Service | Old URL | New URL |
| ---------- | ----------------------------------- | --------------------------------------- |
| **API** | `https://api.revolt.chat` | `https://stoat.chat/api` |
| | `https://app.revolt.chat/api` | `https://stoat.chat/api` |
| | `https://revolt.chat/api` | No equivalent |
| **Events** | `wss://ws.revolt.chat` | `wss://stoat.chat/events` |
| | `wss://app.revolt.chat/events` | `wss://stoat.chat/events` |
| | `wss://revolt.chat/events` | No equivalent |
| **Files** | `https://autumn.revolt.chat` | `https://cdn.stoatusercontent.com` |
| | `https://cdn.revoltusercontent.com` | `https://cdn.stoatusercontent.com` |
| **Proxy** | `https://jan.revolt.chat` | `https://external.stoatusercontent.com` |
| **Voice** | `https://vortex.revolt.chat` | Superseded by Voice Chats v2 |