botrs 0.12.1

A Rust QQ Bot framework based on QQ Guild Bot API
Documentation
# Token

`Token` carries the bot's `app_id` and `secret`, signs API requests, and caches an access token shared across `Token` clones. The cache is refreshed on demand and protected by an internal mutex, so cloning `Token` is cheap and safe across tasks.

```rust
pub struct Token { /* private fields */ }
```

`Token` implements `Clone`, `Debug`, `Serialize`, `Deserialize`. The serialized form drops the cache so credentials embedded in config files round-trip cleanly.

## Construction

- `Token::new(app_id, secret)` — explicit credentials.
- `Token::from_env()` — read `QQ_BOT_APP_ID` / `QQ_BOT_SECRET`. Returns `BotError::Config` if either variable is missing.

```rust
let token = Token::new("123456789", "abcdef...");
let token = Token::from_env()?;       // env-driven
```

## Accessors

- `app_id()` / `secret()` — borrow the credential strings (returns `&str`).
- `safe_display()` — returns a redacted human-readable form for logs (`Token { app_id: "1234***", secret: "***" }`). Use this anywhere you would otherwise be tempted to print the raw token.

## Authentication

- `authorization_header().await``Authorization: QQBot <access-token>` string suitable for HTTP requests; refreshes the cached token if it has expired.
- `bot_token().await` — just the bot token portion (without the `QQBot ` prefix), used by the gateway `Identify` payload.

Both methods share the same access-token cache. Concurrent callers will block briefly on the refresh mutex when a renewal is needed and then race-free read the freshly minted token.

## Validation

`validate()` runs lightweight syntactic checks (non-empty app id, sane lengths) and returns `BotError::Config` on failure. Use it after `Token::new(...)` to fail fast before issuing any request.

## Refresh behaviour

The framework tracks the access token's expiry and automatically renews it shortly before it lapses. If a renewal fails, the next call surfaces a `BotError::Auth` error with the upstream message. There is no exposed `refresh()` method — refresh is an implementation detail driven by `authorization_header()` / `bot_token()`.

When clones share the same internal `Arc<Mutex<TokenState>>`, a renewal triggered on one clone is immediately visible to all others, so the framework never refreshes more than once per expiry window even under heavy concurrency.

## Examples

**Build a token from the environment and validate it once at startup:**

```rust
let token = Token::from_env()?;
token.validate()?;
```

**Construct an HTTP client manually:**

```rust
let auth = token.authorization_header().await?;
let request = client
    .get("https://api.example.com/whatever")
    .header("Authorization", auth)
    .send()
    .await?;
```

**Log a token without leaking secrets:**

```rust
info!("starting bot {}", token.safe_display());
```

## Security notes

- Never `Debug`-print a `Token`; always go through `safe_display()`.
- Prefer reading credentials from env vars or a secret manager. Keep them out of source-controlled config files.
- Rotate the secret in the QQ Developer Portal if you suspect a leak; the next `authorization_header()` call after rotation will fail with `BotError::Auth`, and you can rebuild the `Token` with fresh values.

## See also

- [Bot API]./bot-api.md — every route ultimately consumes a `Token`.
- [Context]./context.md — embeds a `Token` so handlers don't pass it around manually.
- [Error types]./error-types.md`BotError::Auth` and `BotError::Config` behaviour referenced above.