@@ -110,6 +110,48 @@ pub async fn voice_state_update(ctx: &Context, new: &VoiceState) {
110110 return ;
111111 }
112112
113+ // is auto join temporarily disabled?
114+ let auto_join_key_name = format ! ( "guild_{{{}}}_auto_join_disabled" , guild_id. get( ) ) ;
115+ let r = scripty_redis:: run_transaction :: < i64 > ( "PEXPIRETIME" , |cmd| {
116+ cmd. arg ( & auto_join_key_name) ;
117+ } )
118+ . await ;
119+ match r {
120+ Ok ( -1 ) => {
121+ // key exists without expiration time: delete and continue because this is bad
122+ if let Err ( e) = scripty_redis:: run_transaction :: < i64 > ( "DEL" , |cmd| {
123+ cmd. arg ( & auto_join_key_name) ;
124+ } )
125+ . await
126+ {
127+ error ! ( %guild_id, "failed to delete key that never expires: {}" , e) ;
128+ }
129+ }
130+ Ok ( -2 ) => {
131+ // key doesn't exist so continue normally
132+ }
133+ Ok ( disabled_until) => {
134+ // temporarily disabled
135+ let disabled_until =
136+ SystemTime :: UNIX_EPOCH + Duration :: from_millis ( disabled_until as u64 ) ;
137+ let delta_time = disabled_until. duration_since ( SystemTime :: now ( ) ) ;
138+ match delta_time {
139+ Ok ( time_left) => {
140+ debug ! ( %guild_id, "guild auto join disabled for another {:?}" , time_left) ;
141+ return ;
142+ }
143+ Err ( time_since) => {
144+ let time_since = time_since. duration ( ) ;
145+ warn ! ( %guild_id, "guild auto join was disabled, but it's been {:?} since auto join timeout expired" , time_since) ;
146+ }
147+ }
148+ }
149+ Err ( e) => {
150+ error ! ( %guild_id, "error fetching temporarily disabled state: {}" , e) ;
151+ return ;
152+ }
153+ }
154+
113155 // now we need to check the voice channel the user is joining
114156 // discord doesn't give us the channel id, so we need to get it from the guild's voice states
115157 let vs = {
@@ -194,6 +236,22 @@ pub async fn voice_state_update(ctx: &Context, new: &VoiceState) {
194236 {
195237 error ! ( %guild_id, "error joining voice channel: {:?}" , e) ;
196238
239+ if e. is_dropped ( ) || e. is_timed_out ( ) {
240+ debug ! ( %guild_id, "got a Dropped/TimedOut error, disabling auto join for five minutes" ) ;
241+
242+ // set a key that expires after 5 minutes to disable auto join temporarily
243+ if let Err ( e) = scripty_redis:: run_transaction :: < ( ) > ( "SETEX" , |cmd| {
244+ cmd. arg ( format ! ( "guild_{{{}}}_auto_join_disabled" , guild_id. get( ) ) )
245+ . arg ( true )
246+ . arg ( "EX" )
247+ . arg ( 5 * 60 ) ;
248+ } )
249+ . await
250+ {
251+ error ! ( %guild_id, "failed to set auto_join disable key: {}" , e) ;
252+ }
253+ }
254+
197255 let target_channel = match sqlx:: query!(
198256 r#"SELECT target_channel AS "target_channel!"
199257 FROM default_join_settings
@@ -217,8 +275,15 @@ pub async fn voice_state_update(ctx: &Context, new: &VoiceState) {
217275 & ctx. http ,
218276 format ! (
219277 "Failed to join voice channel due to auto-join error: {}\n You may want to \
220- report this in our support server.",
221- e
278+ report this in our support server.{}",
279+ e,
280+ if e. is_dropped( ) || e. is_timed_out( ) {
281+ "Because of the nature of this error, auto-join has been disabled for \
282+ five minutes. If you want Scripty to join anyway, run `/leave` and \
283+ `/join` manually."
284+ } else {
285+ ""
286+ }
222287 ) ,
223288 )
224289 . await ;
0 commit comments