This commit is contained in:
parent
90bfdb08f5
commit
12961f66da
1 changed files with 36 additions and 32 deletions
68
README.md
68
README.md
|
|
@ -1,7 +1,8 @@
|
||||||
# rustbot
|
# rustbot
|
||||||
|
|
||||||
A small, modular IRC bot in Rust — **zero dependencies**, with its own IRC
|
A small, modular IRC bot in Rust — **almost dependency-free** (only `native-tls`
|
||||||
protocol layer built to the [modern IRC client spec](https://modern.ircdocs.horse/).
|
for the TLS transport), with a hand-rolled IRC protocol layer, HTTP client, JSON
|
||||||
|
parser, and hashes, built to the [modern IRC client spec](https://modern.ircdocs.horse/).
|
||||||
|
|
||||||
## Structure
|
## Structure
|
||||||
|
|
||||||
|
|
@ -12,9 +13,11 @@ rustbot is a **library crate** (`src/lib.rs`) with a thin binary on top:
|
||||||
| `src/irc/` | protocol + I/O | `message` (parse IRCv3 wire format), `connection` (TCP/TLS transport), `encoding` (base64, server-time) |
|
| `src/irc/` | protocol + I/O | `message` (parse IRCv3 wire format), `connection` (TCP/TLS transport), `encoding` (base64, server-time) |
|
||||||
| `src/bot/` | behavior | `mod` (state + loop), `caps` (IRCv3 + SASL), `commands` (routing + help), `module` (plugin trait), `modules/` (feature modules) |
|
| `src/bot/` | behavior | `mod` (state + loop), `caps` (IRCv3 + SASL), `commands` (routing + help), `module` (plugin trait), `modules/` (feature modules) |
|
||||||
| `src/config.rs` | configuration | Layered `Config`; multi-network `[section]` parsing |
|
| `src/config.rs` | configuration | Layered `Config`; multi-network `[section]` parsing |
|
||||||
|
| `src/http.rs` | utility | Minimal HTTPS GET over `native-tls`, with retry |
|
||||||
|
| `src/json.rs` | utility | Recursive-descent JSON parser |
|
||||||
| `src/lib.rs` | crate root | Ties the modules into the reusable `rustbot` library |
|
| `src/lib.rs` | crate root | Ties the modules into the reusable `rustbot` library |
|
||||||
| `src/main.rs` | binary | Thin per-network connect/reconnect supervisor |
|
| `src/main.rs` | binary | Thin per-network connect/reconnect supervisor |
|
||||||
| `tests/` | tests | Integration tests (`config.rs`, `protocol.rs`) against the public API |
|
| `tests/` | tests | Integration tests against the public API, one file per subsystem |
|
||||||
|
|
||||||
The layers are deliberately separable: `irc` knows nothing about the bot, `bot`
|
The layers are deliberately separable: `irc` knows nothing about the bot, `bot`
|
||||||
knows nothing about how the process is launched, and `main` only supervises.
|
knows nothing about how the process is launched, and `main` only supervises.
|
||||||
|
|
@ -37,14 +40,14 @@ pub trait Module {
|
||||||
|
|
||||||
At startup the bot builds a `command → module` route table and auto-generates
|
At startup the bot builds a `command → module` route table and auto-generates
|
||||||
`help` from every module's `CommandSpec`s. Each network gets its own fresh set
|
`help` from every module's `CommandSpec`s. Each network gets its own fresh set
|
||||||
of modules, so stateful ones (like the dice roller's PRNG) keep per-network
|
of modules, so stateful ones (like the dice roller's PRNG or the weather cache)
|
||||||
state with no locking.
|
keep per-network state with no locking.
|
||||||
|
|
||||||
**To add a module**, create `src/bot/modules/<name>.rs` implementing `Module`,
|
**To add a module**, create `src/bot/modules/<name>.rs` implementing `Module`,
|
||||||
then add one line to `all()` in `src/bot/modules/mod.rs`:
|
then add one line to `all()` in `src/bot/modules/mod.rs`:
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
pub fn all() -> Vec<Box<dyn Module>> {
|
pub fn all(network: &str) -> Vec<Box<dyn Module>> {
|
||||||
vec![
|
vec![
|
||||||
Box::new(builtins::Builtins),
|
Box::new(builtins::Builtins),
|
||||||
Box::new(dice::Dice::new()),
|
Box::new(dice::Dice::new()),
|
||||||
|
|
@ -53,21 +56,31 @@ pub fn all() -> Vec<Box<dyn Module>> {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Shipped modules: **`builtins`** (`ping`, `echo`, `hello`), **`dice`**
|
Web modules fetch with the shared `http::get` and parse with `json::Json`; each
|
||||||
(`roll [NdM]`), **`hash`** (`md5`/`sha1`/`sha256`/`hash <text>`, hand-rolled),
|
has a pure formatter unit-tested against a canned API body (no network needed).
|
||||||
**`weather`** (`add <location>` then `w [location]` via wttr.in; per-user
|
|
||||||
locations saved to a per-network JSON file, path from `RUSTBOT_DATA_DIR`), and
|
## Commands
|
||||||
web lookups over the shared `http`/`json` helpers: **`crypto <sym>`** (Bitstamp),
|
|
||||||
**`wiki <term>`**, **`define <word>`**, **`urban <term>`**, **`tinyurl <url>`**,
|
Triggered by the configured prefix (default `!`, set with `prefix`); `help`
|
||||||
**`down <host>`**. Module tests live in `tests/` — construct a module (or call
|
lists them at runtime and `help <command>` describes one.
|
||||||
its pure formatter with a canned API body) and assert on the result, no network
|
|
||||||
required.
|
| Command | Does |
|
||||||
|
|---|---|
|
||||||
|
| `ping` / `echo <text>` / `hello` | basics |
|
||||||
|
| `roll [NdM]` | roll dice (default `1d6`) |
|
||||||
|
| `md5` / `sha1` / `sha256` / `hash <text>` | hex digests |
|
||||||
|
| `add <location>` then `w [location]` | weather (wttr.in), per-user location |
|
||||||
|
| `crypto <sym>` | coin price in USD (Bitstamp) |
|
||||||
|
| `wiki <term>` | Wikipedia summary |
|
||||||
|
| `define <word>` / `urban <term>` | dictionary / Urban Dictionary |
|
||||||
|
| `tinyurl <url>` | shorten a URL |
|
||||||
|
| `down <host>` | is a site reachable? |
|
||||||
|
|
||||||
## Build & run
|
## Build & run
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
cargo build # or: cargo build --release
|
cargo build # or: cargo build --release
|
||||||
cargo test # integration tests in tests/ (config + protocol)
|
cargo test # integration tests in tests/
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -89,9 +102,11 @@ RUSTBOT_CONFIG=/etc/rustbot.conf cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
Config file keys: `server`, `port`, `tls`, `nick`, `user`, `realname`,
|
Config file keys: `server`, `port`, `tls`, `nick`, `user`, `realname`,
|
||||||
`channels` (comma/space-separated), `prefix`, `password`, `sasl_user`,
|
`channels` (comma/space-separated), `prefix`, `verbose` (wire logging, default
|
||||||
`sasl_pass`, and `name` (a network's log label). Whole-line `#`/`;` comments
|
on), `password`, `sasl_user`, `sasl_pass`, and `name` (a network's log label).
|
||||||
only — inline comments would clash with channel `#`s.
|
Whole-line `#`/`;` comments only — inline comments would clash with channel `#`s.
|
||||||
|
|
||||||
|
Web modules write per-network state under `RUSTBOT_DATA_DIR` (default `.`).
|
||||||
|
|
||||||
### Multiple networks
|
### Multiple networks
|
||||||
|
|
||||||
|
|
@ -122,7 +137,7 @@ With more than one network, each network's log lines are tagged with its
|
||||||
section name (`[libera] << …`) so the interleaved output stays readable.
|
section name (`[libera] << …`) so the interleaved output stays readable.
|
||||||
|
|
||||||
Environment overrides: `IRC_SERVER`, `IRC_PORT`, `IRC_TLS`, `IRC_NICK`,
|
Environment overrides: `IRC_SERVER`, `IRC_PORT`, `IRC_TLS`, `IRC_NICK`,
|
||||||
`IRC_REALNAME`, `IRC_CHANNELS`, `IRC_PREFIX`, `IRC_PASSWORD`,
|
`IRC_REALNAME`, `IRC_CHANNELS`, `IRC_PREFIX`, `IRC_VERBOSE`, `IRC_PASSWORD`,
|
||||||
`IRC_SASL_USER`, `IRC_SASL_PASS`.
|
`IRC_SASL_USER`, `IRC_SASL_PASS`.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
|
@ -132,17 +147,6 @@ IRC_NICK=mybot IRC_CHANNELS='#test' cargo run # override the file for one run
|
||||||
> If you put a real `password` in `rustbot.conf`, add it to `.gitignore` so the
|
> If you put a real `password` in `rustbot.conf`, add it to `.gitignore` so the
|
||||||
> secret isn't committed.
|
> secret isn't committed.
|
||||||
|
|
||||||
## Built-in commands
|
|
||||||
|
|
||||||
With the default `!` prefix:
|
|
||||||
|
|
||||||
- `!ping` → `pong`
|
|
||||||
- `!echo <text>` → echoes text back
|
|
||||||
- `!hello` → greets the sender
|
|
||||||
- `!help` → lists commands
|
|
||||||
|
|
||||||
Add your own in `Bot::handle_command` in `src/bot.rs`.
|
|
||||||
|
|
||||||
## Deployment (systemd)
|
## Deployment (systemd)
|
||||||
|
|
||||||
Runs as a system service (`User=debian`), auto-restarts on crash, and starts on
|
Runs as a system service (`User=debian`), auto-restarts on crash, and starts on
|
||||||
|
|
@ -155,7 +159,7 @@ sudo systemctl daemon-reload
|
||||||
sudo systemctl enable --now rustbot # start now + on boot
|
sudo systemctl enable --now rustbot # start now + on boot
|
||||||
|
|
||||||
systemctl status rustbot # health
|
systemctl status rustbot # health
|
||||||
journalctl -u rustbot -f # live logs (<< in, >> out)
|
journalctl -u rustbot -f # live logs (<< in, >> out; verbose=false to quiet)
|
||||||
sudo systemctl restart rustbot # after a rebuild
|
sudo systemctl restart rustbot # after a rebuild
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue