docs: add a module-developer API reference (docs/api/) — Command/Module/ChanMode/UserMode traits, the Server API surface, per-entity Extensible state, and a first-module tutorial
This commit is contained in:
parent
bd054f7721
commit
d795d1a59c
7 changed files with 569 additions and 1 deletions
138
docs/api/server.md
Normal file
138
docs/api/server.md
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
# The `Server` API
|
||||
|
||||
`Server` is the whole server state, passed as `&mut Server` to every command,
|
||||
mode, and module hook. This is the surface you call from a module. Signatures are
|
||||
abbreviated; see `src/server.rs` for the exact ones.
|
||||
|
||||
## The data model
|
||||
|
||||
- **`Uid`** — an opaque handle for a user (not a pointer). Look users up with it;
|
||||
it can't dangle.
|
||||
- **`srv.users: HashMap<Uid, User>`** — every local and remote user. A `User` has
|
||||
`nick`, `ident`, `host`, `realname`, `account`, `flags`, `ext`, and more.
|
||||
- **`srv.channels: HashMap<String, Channel>`** — keyed by the **lower-cased** name.
|
||||
A `Channel` has its members, `modes` (a `ChanModes` struct), topic, and ban
|
||||
lists.
|
||||
- **`srv.name`, `srv.network`** — this server's name and the network name.
|
||||
- **`srv.conf_path`** — the path to the loaded config file (useful for sibling
|
||||
data files).
|
||||
|
||||
```rust
|
||||
let nick = srv.users.get(&uid).map(|u| u.nick.clone());
|
||||
let count = srv.channels.get(&key).map(|c| c.members.len());
|
||||
```
|
||||
|
||||
## Per-entity state
|
||||
|
||||
Attach your own typed state through the `Extensible` typemap instead of adding
|
||||
struct fields. It is keyed by Rust type and dropped automatically with its owner.
|
||||
|
||||
- **`srv.ext`** — per-server state. For **per-channel** state, key your value by
|
||||
channel name inside a map stored here (channels themselves have no `ext`).
|
||||
- **`srv.users[&uid].ext`** — per-user state.
|
||||
|
||||
```rust
|
||||
#[derive(Default)]
|
||||
struct Counter(u32);
|
||||
|
||||
let c = srv.ext.get_or_insert_with::<Counter>(Counter::default);
|
||||
c.0 += 1;
|
||||
|
||||
if let Some(u) = srv.users.get_mut(&uid) {
|
||||
u.ext.set(MyUserState { /* … */ });
|
||||
}
|
||||
```
|
||||
|
||||
`Extensible` methods: `get::<T>()`, `get_mut::<T>()`, `set::<T>(v)`,
|
||||
`get_or_insert_with::<T>(f)`, `take::<T>()`.
|
||||
|
||||
## Configuration
|
||||
|
||||
Never hardcode a tunable — read it, with a literal default:
|
||||
|
||||
| Method | Returns |
|
||||
|--------|---------|
|
||||
| `srv.conf(key)` | `Option<&str>` — the single value, if set |
|
||||
| `srv.conf_all(key)` | all values for a repeatable key |
|
||||
| `srv.conf_bool(key, default)` | a `yes`/`on`/`true` flag |
|
||||
| `srv.conf_num(key, default)` | any `FromStr` number |
|
||||
|
||||
```rust
|
||||
let threshold = srv.conf_num("mymod_threshold", 8u32);
|
||||
let enabled = srv.conf_bool("mymod", false);
|
||||
for line in srv.conf_all("mymod_rule") { /* … */ }
|
||||
```
|
||||
|
||||
## Sending
|
||||
|
||||
| Method | Sends |
|
||||
|--------|-------|
|
||||
| `srv.send(uid, line)` | a fully-formed protocol line to one user |
|
||||
| `srv.numeric(uid, code, rest)` | a numeric reply (`005`, `461`, …) |
|
||||
| `srv.fail(uid, cmd, code, desc)` / `warn(...)` / `note(...)` | IRCv3 standard replies |
|
||||
| `srv.to_channel(key, line, except)` | a line to every member (optionally excluding one) |
|
||||
| `srv.snotice(msg)` | a server notice to subscribed opers |
|
||||
| `srv.announce(msg)` | a global notice to all users |
|
||||
| `srv.notify_peers(uid, line, want)` | send to a user's common-channel peers whose caps match |
|
||||
|
||||
```rust
|
||||
srv.numeric(uid, 481, ":Permission Denied- You're not an IRC operator");
|
||||
srv.to_channel(&key, format!(":{} NOTICE {} :hi", srv.name, chan), Some(uid));
|
||||
```
|
||||
|
||||
## Lookups & permissions
|
||||
|
||||
| Method | Result |
|
||||
|--------|--------|
|
||||
| `srv.is_oper(uid)` | is the user an IRC operator? |
|
||||
| `srv.rank(uid, key)` | the user's channel rank (compare to `RANK_*`) |
|
||||
| `srv.is_member(uid, key)` | is the user in the channel? |
|
||||
| `srv.extban_active(uid, key, kind)` | does an acting extban of `kind` apply to them here? |
|
||||
|
||||
Rank constants (from `crate::channels`): `RANK_OWNER`, `RANK_ADMIN`, `RANK_OP`,
|
||||
`RANK_HALFOP`, `RANK_VOICE`.
|
||||
|
||||
```rust
|
||||
if srv.rank(uid, &key) >= crate::channels::RANK_OP { /* ops-only */ }
|
||||
```
|
||||
|
||||
## Mutating users
|
||||
|
||||
| Method | Effect |
|
||||
|--------|--------|
|
||||
| `srv.change_host_ident(uid, new_ident, new_host)` | change a user's displayed ident/host (drives `chghost`) |
|
||||
| `srv.oper_up(uid)` | mark a user as an operator |
|
||||
| `srv.remove_user(uid, reason)` | disconnect a user cleanly |
|
||||
|
||||
## Off-core work
|
||||
|
||||
The core thread must never block. Offload slow work:
|
||||
|
||||
- **`srv.disk_write(path, contents)`** — fire-and-forget, coalescing, atomic
|
||||
(temp + rename) file write. Use this for saving state; a slow disk can't stall
|
||||
the core. **No result to handle** — the simplest offload.
|
||||
- **`srv.spawn_crypto(closure)`** — run a bounded, CPU-heavy job (a KDF hash) on a
|
||||
worker thread. Returns `false` if at capacity.
|
||||
- **`srv.spawn_http(...)`** — an outbound HTTP request on a worker thread.
|
||||
|
||||
`spawn_crypto` / `spawn_http` deliver their result back as a core `Event`, so
|
||||
wiring a *new* async result type touches the core event enum in `src/ircd.rs`;
|
||||
`disk_write` needs nothing extra. Prefer `disk_write` for persistence and
|
||||
`on_tick` for periodic jobs.
|
||||
|
||||
```rust
|
||||
// good: persist off-core from a hook
|
||||
srv.disk_write(format!("{}.mystore", srv.conf_path), store.serialize());
|
||||
```
|
||||
|
||||
## Time
|
||||
|
||||
`crate::server::now()` → unix seconds. `iso_time(secs)` / `parse_iso(s)` convert to
|
||||
and from ISO-8601 (used for `server-time` tags and timestamps).
|
||||
|
||||
## Rules of the road
|
||||
|
||||
- Do the work synchronously and quickly; **never block** — offload instead.
|
||||
- Reference users/channels by `Uid` / key; don't cache references across events.
|
||||
- Keep state in `ext`, read settings via `conf*`, and register in the tables. See
|
||||
the [conventions](README.md#project-conventions).
|
||||
Loading…
Add table
Add a link
Reference in a new issue