1use crate::error::TitaniumError;
48use crate::framework::Framework;
49use std::sync::Arc;
50use titanium_cache::{Cache, InMemoryCache};
51use titanium_gateway::{Cluster, ClusterConfig, Event};
52use titanium_http::HttpClient;
53use titanium_model::Intents;
54
55#[derive(Clone)]
81pub struct Client {
82 pub cluster: Arc<Cluster>,
84 pub http: Arc<HttpClient>,
86 pub cache: Arc<InMemoryCache>,
88 pub framework: Option<Arc<Framework>>,
90 pub event_handler: Option<Arc<dyn EventHandler>>,
92 pub event_rx: flume::Receiver<(u16, Event<'static>)>,
94 #[allow(dead_code)]
96 token: String,
97}
98
99impl Client {
100 #[inline]
102 pub fn builder(token: impl Into<String>) -> ClientBuilder {
103 ClientBuilder::new(token)
104 }
105
106 pub async fn start(&self) -> Result<(), TitaniumError> {
108 self.cluster.start()?;
110
111 while let Ok((shard_id, event)) = self.event_rx.recv_async().await {
113 match &event {
115 Event::Ready(ready) => {
116 self.cache.insert_user(Arc::new(ready.user.clone()));
117 }
119 Event::GuildCreate(guild) => {
120 self.cache.insert_guild(Arc::clone(guild));
121 }
122 Event::GuildUpdate(guild) => {
123 self.cache.insert_guild(Arc::clone(guild));
124 }
125 Event::GuildDelete(unavailable) => {
126 if !unavailable.unavailable {
127 self.cache.remove_guild(unavailable.id);
128 }
129 }
130 Event::ChannelCreate(channel) => {
131 self.cache.insert_channel(Arc::clone(channel));
132 }
133 Event::ChannelUpdate(channel) => {
134 self.cache.insert_channel(Arc::clone(channel));
135 }
136 Event::ChannelDelete(channel) => {
137 self.cache.remove_channel(channel.id);
138 }
139 Event::GuildMemberAdd(member) => {
140 let guild_member = titanium_model::GuildMember {
142 user: member.user.clone(),
143 nick: member.nick.clone().map(Into::into),
144 avatar: member.avatar.clone().map(Into::into),
145 roles: member.roles.clone().into(),
146 joined_at: member.joined_at.clone().into(),
147 premium_since: None, deaf: member.deaf,
149 mute: member.mute,
150 flags: member.flags,
151 pending: member.pending,
152 permissions: None,
153 communication_disabled_until: None,
154 };
155 self.cache
156 .insert_member(member.guild_id, Arc::new(guild_member));
157 }
158 Event::GuildMemberUpdate(member) => {
159 if let Some(cached_member) = self.cache.member(member.guild_id, member.user.id)
161 {
162 let mut new_member = (*cached_member).clone();
164
165 new_member.roles = member.roles.clone().into();
166 new_member.nick = member.nick.clone().map(Into::into);
167 new_member.avatar = member.avatar.clone().map(Into::into);
168 if let Some(joined) = &member.joined_at {
169 new_member.joined_at = joined.clone().into();
170 }
171 new_member.deaf = member.deaf.unwrap_or(new_member.deaf);
172 new_member.mute = member.mute.unwrap_or(new_member.mute);
173 new_member.pending = member.pending;
174 new_member.communication_disabled_until =
175 member.communication_disabled_until.clone().map(Into::into);
176
177 self.cache
178 .insert_member(member.guild_id, Arc::new(new_member));
179 }
180 }
181 Event::GuildMemberRemove(event) => {
182 self.cache.remove_member(event.guild_id, event.user.id);
183 }
184 Event::GuildRoleCreate(event) => {
185 self.cache
186 .insert_role(event.role.id, Arc::new(event.role.clone()));
187 }
188 Event::GuildRoleUpdate(event) => {
189 self.cache
190 .insert_role(event.role.id, Arc::new(event.role.clone()));
191 }
192 Event::GuildRoleDelete(event) => {
193 self.cache.remove_role(event.role_id);
194 }
195 Event::UserUpdate(user) => {
196 self.cache.insert_user(user.clone());
197 }
198 _ => {}
199 }
200
201 if let Some(handler) = &self.event_handler {
203 let http = self.http.clone();
204 let cache = self.cache.clone();
205 let Some(shard) = self.cluster.shard(shard_id) else {
207 tracing::warn!(shard_id, "Shard not found for event, skipping dispatch");
208 continue;
209 };
210
211 let handler = handler.clone();
213 let event = event.clone(); tokio::spawn(async move {
217 let make_ctx = || {
219 super::context::Context::new(
220 http.clone(),
221 cache.clone(),
222 shard.clone(),
223 None,
224 )
225 };
226
227 match event {
228 Event::Ready(ready) => {
229 handler.ready(make_ctx(), (*ready).clone()).await;
230 }
231 Event::MessageCreate(msg) => {
232 handler.message_create(make_ctx(), (*msg).clone()).await;
233 }
234 Event::InteractionCreate(interaction) => {
235 let interaction_val = (*interaction).clone();
237 let ctx = super::context::Context::new(
238 http.clone(),
239 cache.clone(),
240 shard.clone(),
241 Some(interaction_val.clone()),
242 );
243 handler.interaction_create(ctx, interaction_val).await;
244 }
245 Event::MessageReactionAdd(ev) => {
246 handler.reaction_add(make_ctx(), (*ev).clone()).await;
247 }
248 Event::ThreadCreate(thread) => {
249 handler.thread_create(make_ctx(), (*thread).clone()).await;
250 }
251 Event::GuildRoleCreate(ev) => {
252 handler.role_create(make_ctx(), (*ev).clone()).await;
253 }
254 _ => {}
255 }
256 });
257 }
258 }
259
260 Ok(())
261 }
262}
263
264use crate::prelude::*;
265use async_trait::async_trait;
266
267#[async_trait]
268pub trait EventHandler: Send + Sync {
269 async fn ready(&self, _ctx: Context, _ready: ReadyEventData<'_>) {}
270 async fn message_create(&self, _ctx: Context, _msg: Message<'_>) {}
271 async fn interaction_create(&self, _ctx: Context, _interaction: Interaction<'_>) {}
272 async fn reaction_add(
273 &self,
274 _ctx: Context,
275 _add: titanium_model::MessageReactionAddEvent<'async_trait>,
276 ) {
277 }
278 async fn thread_create(&self, _ctx: Context, _thread: Channel<'async_trait>) {}
279 async fn role_create(&self, _ctx: Context, _role: GuildRoleEvent<'async_trait>) {}
280}
281
282pub struct ClientBuilder {
283 token: String,
284 intents: Intents,
285 framework: Option<Framework>,
286 event_handler: Option<Arc<dyn EventHandler>>,
287}
288
289impl ClientBuilder {
290 #[inline]
291 pub fn new(token: impl Into<String>) -> Self {
292 Self {
293 token: token.into(),
294 intents: Intents::default(),
295 framework: None,
296 event_handler: None,
297 }
298 }
299
300 #[must_use]
302 pub const fn intents(mut self, intents: Intents) -> Self {
303 self.intents = intents;
304 self
305 }
306
307 #[must_use]
309 pub fn framework(mut self, framework: Framework) -> Self {
310 self.framework = Some(framework);
311 self
312 }
313
314 pub fn event_handler<H: EventHandler + 'static>(mut self, handler: H) -> Self {
315 self.event_handler = Some(Arc::new(handler));
316 self
317 }
318
319 pub async fn build(self) -> Result<Client, TitaniumError> {
320 let http = Arc::new(HttpClient::new(self.token.clone())?);
321 let cache = Arc::new(titanium_cache::InMemoryCache::new());
322
323 let config = ClusterConfig::autoscaled(self.token.clone(), self.intents).await?;
326
327 let (cluster, rx) = Cluster::new(config);
329 let cluster = Arc::new(cluster);
330
331 Ok(Client {
332 cluster,
333 http,
334 cache,
335 framework: self.framework.map(Arc::new),
336 event_handler: self.event_handler,
337 event_rx: rx,
338 token: self.token,
339 })
340 }
341}