use crate::cache::{Cache, CacheConfig};
use crate::client::{ClientBuilder, Context, DispatchEvent, DispatchEventType, EventHandler};
use crate::error::{CaptchaInfo, Result};
use crate::gateway::Gateway;
use crate::http::HttpClient;
use crate::model::{Message, PassiveUpdateV1, ReadySupplemental, User};
use serde_json::Value;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use tokio::sync::Notify;
pub struct Client {
token: String,
handler: Arc<dyn EventHandler>,
http: HttpClient,
cache: Cache,
shutdown_requested: Arc<AtomicBool>,
shutdown_notify: Arc<Notify>,
}
impl Client {
pub fn builder<H>(token: impl Into<String>, handler: H) -> Result<ClientBuilder<H>>
where
H: EventHandler + 'static,
{
ClientBuilder::new(token, handler)
}
pub fn new(token: impl Into<String>, handler: impl EventHandler + 'static) -> Result<Self> {
let token = token.into();
let http = HttpClient::new(token.clone())?;
let cache = Cache::new();
Ok(Self::from_parts(token, Arc::new(handler), http, cache))
}
pub(crate) fn from_parts(
token: String,
handler: Arc<dyn EventHandler>,
http: HttpClient,
cache: Cache,
) -> Self {
Self {
token,
handler,
http,
cache,
shutdown_requested: Arc::new(AtomicBool::new(false)),
shutdown_notify: Arc::new(Notify::new()),
}
}
pub fn with_cache_config(mut self, config: CacheConfig) -> Self {
self.cache = Cache::with_config(config);
self
}
pub fn without_cache(mut self) -> Self {
self.cache = Cache::with_config(CacheConfig {
cache_users: false,
cache_channels: false,
cache_guilds: false,
cache_relationships: false,
});
self
}
pub fn with_captcha_handler<F, Fut>(mut self, handler: F) -> Self
where
F: Fn(CaptchaInfo) -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = Result<String>> + Send + 'static,
{
self.http = self.http.with_captcha_handler(handler);
self
}
pub fn http(&self) -> &HttpClient {
&self.http
}
pub fn cache(&self) -> &Cache {
&self.cache
}
pub async fn start(&self) -> Result<()> {
self.shutdown_requested.store(false, Ordering::SeqCst);
tracing::info!("Starting Discord client...");
let mut gateway = Gateway::connect(&self.token).await?;
tracing::info!("Client connected, listening for events...");
let ctx = Context::create(self.http.clone(), self.cache.clone()).await?;
loop {
if self.shutdown_requested.load(Ordering::SeqCst) {
tracing::info!("Shutdown requested, stopping client loop");
gateway.shutdown().await?;
break;
}
let next_event = tokio::select! {
event = gateway.next_event() => Some(event?),
_ = self.shutdown_notify.notified() => None,
};
match next_event {
Some(event) => {
if let Some(event) = event {
if let Err(e) = self.handle_event(&ctx, event).await {
tracing::error!("Error handling event: {}", e);
}
} else {
tracing::warn!("Gateway connection closed");
gateway.shutdown().await?;
break;
}
}
None => {
tracing::info!("Shutdown signal received, closing gateway");
gateway.shutdown().await?;
break;
}
}
}
Ok(())
}
pub fn shutdown(&self) {
self.shutdown_requested.store(true, Ordering::SeqCst);
self.shutdown_notify.notify_waiters();
}
async fn handle_event(&self, ctx: &Context, event: Value) -> Result<()> {
self.handler.on_gateway_payload(ctx, &event).await;
let op = event.get("op").and_then(|v| v.as_u64());
if op == Some(0) {
if let Some(event_type) = event.get("t").and_then(|v| v.as_str()) {
let sequence = event.get("s").and_then(|v| v.as_u64());
let data = event.get("d").cloned().unwrap_or(Value::Null);
let dispatch = DispatchEvent::from_gateway_payload(event_type, sequence, data);
let dispatch_kind = dispatch.kind.clone();
let dispatch_name = dispatch.name().to_string();
let maybe_old_user = if matches!(dispatch_kind, DispatchEventType::UserUpdate) {
dispatch
.data
.get("id")
.and_then(|v| v.as_str())
.and_then(|id| ctx.cache.user(id))
} else {
None
};
ctx.cache.update_from_dispatch(&dispatch_name, &dispatch.data);
ctx.collectors.dispatch(dispatch.clone());
self.handler.on_dispatch(ctx, dispatch.clone()).await;
self.dispatch_raw_event(ctx, &dispatch).await;
match dispatch_kind {
DispatchEventType::Ready => {
if let Some(user) = ctx.cache.current_user() {
self.handler.on_ready(ctx, user).await;
}
}
DispatchEventType::ReadySupplemental => {
self.handler
.on_ready_supplemental(ctx, ctx.user.clone(), dispatch.data.clone())
.await;
if let Ok(data) =
serde_json::from_value::<ReadySupplemental>(dispatch.data.clone())
{
self.handler
.on_ready_supplemental_typed(ctx, ctx.user.clone(), data)
.await;
}
}
DispatchEventType::MessageCreate => {
if let Ok(message) = serde_json::from_value::<Message>(dispatch.data) {
self.handler.on_message_create(ctx, message).await;
}
}
DispatchEventType::MessageUpdate => {
if let Ok(message) = serde_json::from_value::<Message>(dispatch.data) {
self.handler.on_message_update(ctx, message).await;
}
}
DispatchEventType::MessageDelete => {
let data = dispatch.data;
if let (Some(channel_id), Some(message_id)) =
(data["channel_id"].as_str(), data["id"].as_str())
{
self.handler
.on_message_delete(
ctx,
channel_id.to_string(),
message_id.to_string(),
)
.await;
}
}
DispatchEventType::UserUpdate => {
if let Ok(new_user) = serde_json::from_value::<User>(dispatch.data) {
let old_user = maybe_old_user.unwrap_or_else(|| new_user.clone());
self.handler.on_user_update(ctx, old_user, new_user).await;
}
}
DispatchEventType::Unknown(name) => {
tracing::trace!("Unhandled dispatch event: {}", name);
}
_ => {}
}
}
}
Ok(())
}
async fn dispatch_raw_event(&self, ctx: &Context, dispatch: &DispatchEvent) {
match dispatch.kind {
DispatchEventType::Ready => self.handler.on_ready_event(ctx, dispatch.data.clone()).await,
DispatchEventType::ReadySupplemental => self
.handler
.on_ready_supplemental_event(ctx, dispatch.data.clone())
.await,
DispatchEventType::Resumed => self.handler.on_resumed_event(ctx, dispatch.data.clone()).await,
DispatchEventType::ApplicationCommandPermissionsUpdate => self
.handler
.on_application_command_permissions_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::AutoModerationRuleCreate => self
.handler
.on_auto_moderation_rule_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::AutoModerationRuleUpdate => self
.handler
.on_auto_moderation_rule_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::AutoModerationRuleDelete => self
.handler
.on_auto_moderation_rule_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::AutoModerationActionExecution => self
.handler
.on_auto_moderation_action_execution(ctx, dispatch.data.clone())
.await,
DispatchEventType::ChannelCreate => self.handler.on_channel_create(ctx, dispatch.data.clone()).await,
DispatchEventType::ChannelUpdate => self.handler.on_channel_update(ctx, dispatch.data.clone()).await,
DispatchEventType::ChannelDelete => self.handler.on_channel_delete(ctx, dispatch.data.clone()).await,
DispatchEventType::ChannelPinsUpdate => self
.handler
.on_channel_pins_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::ThreadCreate => self.handler.on_thread_create(ctx, dispatch.data.clone()).await,
DispatchEventType::ThreadUpdate => self.handler.on_thread_update(ctx, dispatch.data.clone()).await,
DispatchEventType::ThreadDelete => self.handler.on_thread_delete(ctx, dispatch.data.clone()).await,
DispatchEventType::ThreadListSync => self.handler.on_thread_list_sync(ctx, dispatch.data.clone()).await,
DispatchEventType::ThreadMemberUpdate => self
.handler
.on_thread_member_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::ThreadMembersUpdate => self
.handler
.on_thread_members_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::EntitlementCreate => self
.handler
.on_entitlement_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::EntitlementUpdate => self
.handler
.on_entitlement_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::EntitlementDelete => self
.handler
.on_entitlement_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildCreate => self.handler.on_guild_create(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildUpdate => self.handler.on_guild_update(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildDelete => self.handler.on_guild_delete(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildAuditLogEntryCreate => self
.handler
.on_guild_audit_log_entry_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildBanAdd => self.handler.on_guild_ban_add(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildBanRemove => self
.handler
.on_guild_ban_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildEmojisUpdate => self
.handler
.on_guild_emojis_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildStickersUpdate => self
.handler
.on_guild_stickers_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildIntegrationsUpdate => self
.handler
.on_guild_integrations_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildMemberAdd => self.handler.on_guild_member_add(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildMemberRemove => self
.handler
.on_guild_member_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildMemberUpdate => self
.handler
.on_guild_member_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildMembersChunk => self
.handler
.on_guild_members_chunk(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildRoleCreate => self.handler.on_guild_role_create(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildRoleUpdate => self.handler.on_guild_role_update(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildRoleDelete => self.handler.on_guild_role_delete(ctx, dispatch.data.clone()).await,
DispatchEventType::GuildScheduledEventCreate => self
.handler
.on_guild_scheduled_event_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildScheduledEventUpdate => self
.handler
.on_guild_scheduled_event_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildScheduledEventDelete => self
.handler
.on_guild_scheduled_event_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildScheduledEventUserAdd => self
.handler
.on_guild_scheduled_event_user_add(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildScheduledEventUserRemove => self
.handler
.on_guild_scheduled_event_user_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildSoundboardSoundCreate => self
.handler
.on_guild_soundboard_sound_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildSoundboardSoundUpdate => self
.handler
.on_guild_soundboard_sound_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildSoundboardSoundDelete => self
.handler
.on_guild_soundboard_sound_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::GuildSoundboardSoundsUpdate => self
.handler
.on_guild_soundboard_sounds_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::IntegrationCreate => self
.handler
.on_integration_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::IntegrationUpdate => self
.handler
.on_integration_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::IntegrationDelete => self
.handler
.on_integration_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::InteractionCreate => self
.handler
.on_interaction_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::InviteCreate => self.handler.on_invite_create(ctx, dispatch.data.clone()).await,
DispatchEventType::InviteDelete => self.handler.on_invite_delete(ctx, dispatch.data.clone()).await,
DispatchEventType::MessageCreate => self
.handler
.on_message_create_event(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageUpdate => self
.handler
.on_message_update_event(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageDelete => self
.handler
.on_message_delete_event(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageDeleteBulk => self
.handler
.on_message_delete_bulk(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageReactionAdd => self
.handler
.on_message_reaction_add(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageReactionRemove => self
.handler
.on_message_reaction_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageReactionRemoveAll => self
.handler
.on_message_reaction_remove_all(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessageReactionRemoveEmoji => self
.handler
.on_message_reaction_remove_emoji(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessagePollVoteAdd => self
.handler
.on_message_poll_vote_add(ctx, dispatch.data.clone())
.await,
DispatchEventType::MessagePollVoteRemove => self
.handler
.on_message_poll_vote_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::PresenceUpdate => self
.handler
.on_presence_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::PassiveUpdateV1 => self
.handler
.on_passive_update_v1(ctx, dispatch.data.clone())
.await,
DispatchEventType::StageInstanceCreate => self
.handler
.on_stage_instance_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::StageInstanceUpdate => self
.handler
.on_stage_instance_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::StageInstanceDelete => self
.handler
.on_stage_instance_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::SubscriptionCreate => self
.handler
.on_subscription_create(ctx, dispatch.data.clone())
.await,
DispatchEventType::SubscriptionUpdate => self
.handler
.on_subscription_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::SubscriptionDelete => self
.handler
.on_subscription_delete(ctx, dispatch.data.clone())
.await,
DispatchEventType::TypingStart => self.handler.on_typing_start(ctx, dispatch.data.clone()).await,
DispatchEventType::UserUpdate => self.handler.on_user_update_event(ctx, dispatch.data.clone()).await,
DispatchEventType::VoiceChannelEffectSend => self
.handler
.on_voice_channel_effect_send(ctx, dispatch.data.clone())
.await,
DispatchEventType::VoiceStateUpdate => self
.handler
.on_voice_state_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::VoiceServerUpdate => self
.handler
.on_voice_server_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::WebhooksUpdate => self
.handler
.on_webhooks_update(ctx, dispatch.data.clone())
.await,
DispatchEventType::RelationshipAdd => self
.handler
.on_relationship_add(ctx, dispatch.data.clone())
.await,
DispatchEventType::RelationshipRemove => self
.handler
.on_relationship_remove(ctx, dispatch.data.clone())
.await,
DispatchEventType::Unknown(_) => {}
}
if let DispatchEventType::PassiveUpdateV1 = dispatch.kind {
if let Ok(data) = serde_json::from_value::<PassiveUpdateV1>(dispatch.data.clone()) {
self.handler.on_passive_update_v1_typed(ctx, data).await;
}
}
}
}