botrs 0.2.7

A Rust QQ Bot framework based on QQ Guild Bot API
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
# Token API Reference

The `Token` struct manages authentication credentials for QQ Guild Bot API. It handles app ID and secret storage, access token generation, and automatic token refresh for secure API communication.

## Overview

```rust
use botrs::{Token, Result};

// Create token with credentials
let token = Token::new("your_app_id", "your_secret");

// Create from environment variables
let token = Token::from_env()?;

// Get authorization header for API requests
let auth_header = token.authorization_header().await?;
```

The `Token` handles the OAuth 2.0 flow with QQ's API, automatically fetching and refreshing access tokens as needed.

## Constructor Methods

### `new`

Creates a new token with the provided app ID and secret.

```rust
pub fn new(app_id: impl Into<String>, secret: impl Into<String>) -> Self
```

#### Parameters

- `app_id`: The bot's application ID from QQ Developer Portal
- `secret`: The bot's secret key from QQ Developer Portal

#### Returns

A new `Token` instance.

#### Example

```rust
let token = Token::new("123456789", "your_secret_key");
```

### `from_env`

Creates a token from environment variables.

```rust
pub fn from_env() -> Result<Self>
```

Looks for the following environment variables:
- `QQ_BOT_APP_ID`: The bot's application ID
- `QQ_BOT_SECRET`: The bot's secret key

#### Returns

A `Result` containing the token if both environment variables are found.

#### Example

```rust
// Set environment variables first:
// export QQ_BOT_APP_ID=123456789
// export QQ_BOT_SECRET=your_secret_key

let token = Token::from_env()?;
```

## Access Methods

### `app_id`

Gets the application ID.

```rust
pub fn app_id(&self) -> &str
```

#### Returns

A string slice containing the app ID.

#### Example

```rust
let token = Token::new("123456789", "secret");
assert_eq!(token.app_id(), "123456789");
```

### `secret`

Gets the secret key.

```rust
pub fn secret(&self) -> &str
```

#### Returns

A string slice containing the secret.

**Warning**: Be careful when using this method. Avoid logging or exposing the secret.

#### Example

```rust
let token = Token::new("app_id", "secret123");
assert_eq!(token.secret(), "secret123");
```

## Authentication Methods

### `authorization_header`

Generates the authorization header value for API requests.

```rust
pub async fn authorization_header(&self) -> Result<String>
```

This method automatically handles:
- Fetching access tokens from QQ's API
- Token caching and refresh
- Error handling for authentication failures

#### Returns

A `Result` containing the authorization header value in the format "QQBot {access_token}".

#### Example

```rust
let token = Token::new("valid_app_id", "valid_secret");
let auth_header = token.authorization_header().await?;
assert!(auth_header.starts_with("QQBot "));

// Use with HTTP client
let response = client
    .get("https://api.sgroup.qq.com/users/@me")
    .header("Authorization", auth_header)
    .send()
    .await?;
```

### `bot_token`

Generates the bot token for WebSocket authentication.

```rust
pub async fn bot_token(&self) -> Result<String>
```

This is an alias for `authorization_header()` that provides the same token format required for gateway connections.

#### Returns

A `Result` containing the bot token string.

#### Example

```rust
let token = Token::new("app_id", "secret");
let bot_token = token.bot_token().await?;

// Use for WebSocket authentication
let identify = Identify {
    token: bot_token,
    intents: intents.bits(),
    // ... other fields
};
```

## Validation Methods

### `validate`

Validates that the token has non-empty app ID and secret.

```rust
pub fn validate(&self) -> Result<()>
```

#### Returns

`Ok(())` if the token is valid, otherwise returns a `BotError::Auth`.

#### Example

```rust
let token = Token::new("123", "secret");
assert!(token.validate().is_ok());

let invalid_token = Token::new("", "secret");
assert!(invalid_token.validate().is_err());
```

## Utility Methods

### `safe_display`

Safely formats the token for logging purposes.

```rust
pub fn safe_display(&self) -> String
```

This method masks the secret to prevent accidental exposure in logs while keeping the app ID visible.

#### Returns

A string representation safe for logging.

#### Example

```rust
let token = Token::new("123456", "verylongsecret123");
let display = token.safe_display();
println!("{}", display); // "Token { app_id: 123456, secret: very****123 }"
```

## Error Handling

Token operations can fail for several reasons:

### Authentication Errors

```rust
match token.authorization_header().await {
    Ok(header) => {
        // Use the header for API calls
    }
    Err(BotError::Auth(msg)) => {
        eprintln!("Authentication failed: {}", msg);
        // Check app ID and secret
    }
    Err(BotError::Connection(msg)) => {
        eprintln!("Connection error: {}", msg);
        // Check network connectivity
    }
    Err(e) => {
        eprintln!("Unexpected error: {}", e);
    }
}
```

### Common Error Scenarios

**Invalid Credentials**
```rust
// Wrong app ID or secret
let token = Token::new("invalid_id", "wrong_secret");
match token.authorization_header().await {
    Err(BotError::Api { code: 401, .. }) => {
        println!("Invalid credentials");
    }
    _ => {}
}
```

**Network Issues**
```rust
// Connection timeout or network error
match token.authorization_header().await {
    Err(BotError::Connection(msg)) => {
        println!("Network error: {}", msg);
        // Implement retry logic
    }
    _ => {}
}
```

**Configuration Errors**
```rust
// Missing environment variables
match Token::from_env() {
    Err(BotError::Config(msg)) => {
        println!("Configuration error: {}", msg);
        // Check environment variables
    }
    _ => {}
}
```

## Security Considerations

### Secret Protection

The `Token` struct implements several security measures:

1. **Debug Safety**: The `Debug` implementation redacts the secret
2. **Safe Display**: The `safe_display()` method masks sensitive parts
3. **No Secret Exposure**: Avoid calling `secret()` unless absolutely necessary

```rust
let token = Token::new("app_id", "secret123");

// Safe for logging
println!("{:?}", token); // Shows [REDACTED] for secret
println!("{}", token.safe_display()); // Shows masked secret

// Avoid this in production logs
println!("Secret: {}", token.secret()); // Exposes full secret
```

### Environment Variables

When using `from_env()`, ensure environment variables are secure:

```bash
# Good: Set in secure environment
export QQ_BOT_APP_ID=123456789
export QQ_BOT_SECRET=your_secret_key

# Bad: Don't put in shell history or scripts
echo "QQ_BOT_SECRET=secret123" >> ~/.bashrc
```

### Token Storage

```rust
// Good: Create token when needed
async fn create_bot() -> Result<()> {
    let token = Token::from_env()?;
    let client = Client::new(app_id, handler)
        .token(token)
        .build()
        .await?;
    Ok(())
}

// Avoid: Storing tokens in plain text files
// Don't serialize tokens to JSON/YAML configuration files
```

## Advanced Usage

### Custom Token Refresh

The token automatically refreshes access tokens, but you can observe the process:

```rust
use tracing::{info, warn};

let token = Token::new("app_id", "secret");

// This will trigger token fetch on first call
match token.authorization_header().await {
    Ok(header) => {
        info!("Successfully obtained access token");
        // Token is cached for subsequent calls
    }
    Err(e) => {
        warn!("Token fetch failed: {}", e);
    }
}

// Subsequent calls use cached token (if not expired)
let header2 = token.authorization_header().await?;
```

### Token Validation in Production

```rust
async fn validate_bot_config() -> Result<()> {
    let token = Token::from_env()?;
    
    // Validate credentials format
    token.validate()?;
    
    // Test actual authentication
    match token.authorization_header().await {
        Ok(_) => {
            println!("Bot credentials verified");
            Ok(())
        }
        Err(e) => {
            eprintln!("Credential verification failed: {}", e);
            Err(e)
        }
    }
}
```

### Multiple Bot Support

```rust
struct MultiBotManager {
    bots: HashMap<String, Token>,
}

impl MultiBotManager {
    fn new() -> Self {
        Self {
            bots: HashMap::new(),
        }
    }
    
    fn add_bot(&mut self, name: String, app_id: String, secret: String) {
        let token = Token::new(app_id, secret);
        self.bots.insert(name, token);
    }
    
    async fn get_auth_header(&self, bot_name: &str) -> Result<String> {
        let token = self.bots.get(bot_name)
            .ok_or_else(|| BotError::config("Bot not found"))?;
        token.authorization_header().await
    }
}
```

## Integration Examples

### With HTTP Client

```rust
use reqwest::Client;

async fn make_api_call(token: &Token) -> Result<serde_json::Value> {
    let client = Client::new();
    let auth_header = token.authorization_header().await?;
    
    let response = client
        .get("https://api.sgroup.qq.com/users/@me")
        .header("Authorization", auth_header)
        .send()
        .await?;
    
    if response.status().is_success() {
        Ok(response.json().await?)
    } else {
        Err(BotError::api(
            response.status().as_u16() as u32,
            "API call failed".to_string()
        ))
    }
}
```

### With BotRS Client

```rust
use botrs::{Client, EventHandler};

struct MyBot;

#[async_trait::async_trait]
impl EventHandler for MyBot {
    async fn ready(&self, ctx: Context, ready: Ready) {
        println!("Bot {} is ready!", ready.user.username);
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let token = Token::from_env()?;
    let bot = MyBot;
    
    let mut client = Client::new(token.app_id(), bot)
        .token(token)
        .build()
        .await?;
    
    client.start().await?;
    Ok(())
}
```

## Best Practices

### Development Environment

```rust
// Use environment variables for development
let token = match Token::from_env() {
    Ok(token) => token,
    Err(_) => {
        eprintln!("Please set QQ_BOT_APP_ID and QQ_BOT_SECRET");
        std::process::exit(1);
    }
};
```

### Production Deployment

```rust
// Validate configuration at startup
async fn initialize_bot() -> Result<()> {
    let token = Token::from_env()
        .map_err(|e| {
            eprintln!("Configuration error: {}", e);
            e
        })?;
    
    // Test credentials before starting
    token.authorization_header().await
        .map_err(|e| {
            eprintln!("Authentication test failed: {}", e);
            e
        })?;
    
    println!("Bot configuration validated");
    Ok(())
}
```

### Error Recovery

```rust
async fn robust_api_call(token: &Token) -> Result<serde_json::Value> {
    const MAX_RETRIES: u32 = 3;
    
    for attempt in 1..=MAX_RETRIES {
        match token.authorization_header().await {
            Ok(auth_header) => {
                // Make API call with header
                return make_request_with_auth(auth_header).await;
            }
            Err(BotError::Auth(_)) => {
                // Authentication failed, don't retry
                return Err(BotError::auth("Authentication failed permanently"));
            }
            Err(e) if attempt < MAX_RETRIES => {
                eprintln!("Attempt {} failed: {}, retrying...", attempt, e);
                tokio::time::sleep(Duration::from_secs(1)).await;
            }
            Err(e) => {
                return Err(e);
            }
        }
    }
    
    unreachable!()
}
```

## See Also

- [`Client`]./client.md - Bot client that uses tokens
- [`Context`]./context.md - Context provides token access in handlers
- [`BotApi`]./bot-api.md - API client that requires authentication
- [Error Types]./error-types.md - Authentication error handling