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
use super::prelude::*;
use super::{Client, Context, EventHandler};
use crate::gateway::Gateway;
use crate::http::HttpClient;
impl<H: EventHandler + 'static> Client<H> {
/// Creates a client using the crate default request timeout.
///
/// The handler is shared across all gateway shards and receives the events
/// selected by `intents`.
///
/// # Examples
///
/// ```rust,no_run
/// use botrs::{Client, Token, Intents, EventHandler, Context};
/// use tracing::info;
///
/// struct MyHandler;
///
/// #[async_trait::async_trait]
/// impl EventHandler for MyHandler {
/// async fn message_create(&self, ctx: Context, message: botrs::Message) {
/// info!("Received message: {:?}", message.content);
/// }
/// }
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let token = Token::new("app_id", "secret");
/// let intents = Intents::default();
/// let handler = MyHandler;
/// let client = Client::new(token, intents, handler, false)?;
/// Ok(())
/// }
/// ```
pub fn new(token: Token, intents: Intents, handler: H, is_sandbox: bool) -> Result<Self> {
let timeout = crate::DEFAULT_TIMEOUT;
let http = HttpClient::new(timeout, is_sandbox)?;
token.validate()?;
let api = Arc::new(BotApi::new(http, token));
Ok(Self {
intents,
api,
handler: Arc::new(handler),
})
}
/// Creates a client with an explicit HTTP request timeout.
pub fn with_config(
token: Token,
intents: Intents,
handler: H,
timeout: u64,
is_sandbox: bool,
) -> Result<Self> {
let http = HttpClient::new(timeout, is_sandbox)?;
token.validate()?;
let api = Arc::new(BotApi::new(http, token));
Ok(Self {
intents,
api,
handler: Arc::new(handler),
})
}
/// Connects to the gateway and processes events until the session manager stops.
///
/// Handler errors are passed to [`EventHandler::error`] while the event loop
/// continues to receive later gateway events.
///
/// # Examples
///
/// ```rust,no_run
/// use botrs::{Client, Token, Intents, EventHandler};
///
/// struct MyHandler;
///
/// #[async_trait::async_trait]
/// impl EventHandler for MyHandler {}
///
/// #[tokio::main]
/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let token = Token::new("app_id", "secret");
/// let intents = Intents::default();
/// let handler = MyHandler;
/// let mut client = Client::new(token, intents, handler, false)?;
/// client.start().await?;
/// Ok(())
/// }
/// ```
pub async fn start(&mut self) -> Result<()> {
info!("Starting bot client");
// Get bot information
let bot_info = self.api.get_bot_info().await?;
info!("Bot info: {} ({})", bot_info.username, bot_info.id);
// Get gateway information
let gateway_info = self.api.get_gateway().await?;
info!("Gateway URL: {}", gateway_info.url);
check_session_limit(&gateway_info)?;
// Create context
let ctx = Context::new(self.api.clone()).with_bot_info(bot_info);
// Set up event channel
let (event_sender, mut event_receiver) = mpsc::unbounded_channel();
let reconnect_interval =
Gateway::session_start_interval(gateway_info.session_start_limit.max_concurrency);
debug!(
"Gateway reconnect interval: {:?} (max_concurrency: {})",
reconnect_interval, gateway_info.session_start_limit.max_concurrency
);
info!(
"Starting {} gateway shard(s) with interval {:?}",
gateway_info.shards, reconnect_interval
);
let mut session_manager = new_session_manager();
tokio::spawn({
let gateway_info = gateway_info.clone();
let token = self.api.token().clone();
let intents = self.intents;
let event_sender = event_sender.clone();
async move {
if let Err(e) = session_manager
.start(&gateway_info, token, intents, event_sender)
.await
{
error!("Gateway session manager stopped: {}", e);
}
}
});
drop(event_sender);
// Main event processing loop - continue running even if gateway disconnects
info!("Bot client started, waiting for events...");
while let Some(event) = event_receiver.recv().await {
if let Err(e) = self.handle_event(ctx.clone(), event).await {
self.handler.error(e).await;
}
}
info!("Bot client stopped");
Ok(())
}
}