@@ -40,6 +40,9 @@ def __init__(self, bot: commands.Bot):
4040 # Initialize renderer
4141 self ._renderer = Renderer (self .lobby_service , self .game_service )
4242
43+ # Solo timer
44+ self ._solo_lobby_timers : dict [int , asyncio .Task ] = {}
45+
4346 @app_commands .command (name = "create" , description = "Create a lobby in this channel." )
4447 async def create (self , interaction : discord .Interaction ) -> None :
4548 """
@@ -69,6 +72,9 @@ async def create(self, interaction: discord.Interaction) -> None:
6972 # Silently fail if we can't ping.
7073 pass
7174
75+ lobby .channel_id = cid
76+ asyncio .create_task (self .start_solo_lobby_timer (lobby ))
77+
7278 @app_commands .command (
7379 name = "play" ,
7480 description = "Play a card from your hand on your turn (index starts at 0)." ,
@@ -184,7 +190,7 @@ async def kick(self, interaction: discord.Interaction, player: discord.Member):
184190 title = "Player Not Found" ,
185191 )
186192
187- self ._kick_player (lobby , player .id )
193+ await self ._kick_player (lobby , player .id , channel_id = cid )
188194
189195 except GameError as e :
190196 embed = self ._renderer .lobby_views .error_embed (
@@ -206,9 +212,8 @@ async def _kick_player(
206212 Helper to remove a player from the game.
207213 afk=True changes messages to AFK-specific ones
208214 """
209- cid = channel_id
215+ cid = channel_id if channel_id is not None else lobby . channel_id
210216 game = lobby .game
211- channel = self .bot .get_channel (cid )
212217
213218 try :
214219 game .kick_player (player_id )
@@ -338,6 +343,88 @@ def start_afk_timer(self, channel_id: int, lobby) -> None:
338343 self .run_afk_timer (channel_id , game .current_player (), game .turn_count ())
339344 )
340345
346+ async def start_solo_lobby_timer (self , lobby ):
347+ """
348+ Starts a solo lobby timer for a lobby with only one player.
349+ Sends a separate countdown embed. Replaces it with a "Lobby expired" embed
350+ when timer ends and deletes the main lobby embed.
351+ """
352+ channel = self .bot .get_channel (lobby .channel_id )
353+ if channel is None or not getattr (lobby , "main_message" , None ):
354+ return
355+
356+ timer_embed = discord .Embed (
357+ title = "⏳ Solo Lobby Timer" ,
358+ description = "Lobby expires in **120** seconds if nobody joins." ,
359+ color = discord .Color .orange (),
360+ )
361+
362+ try :
363+ timer_msg = await channel .send (embed = timer_embed )
364+ except discord .HTTPException :
365+ return
366+
367+ total_sec = 120
368+ interval = 1
369+
370+ try :
371+ while total_sec > 0 :
372+ if len (lobby .game .players ()) > 1 :
373+ await timer_msg .delete ()
374+ return
375+
376+ minutes , seconds = divmod (total_sec , 60 )
377+ timer_embed .description = (
378+ f"Lobby expires in **{ minutes } :{ seconds :02d} ** if nobody joins."
379+ )
380+
381+ try :
382+ await timer_msg .edit (embed = timer_embed )
383+ except discord .HTTPException :
384+ pass
385+
386+ await asyncio .sleep (interval )
387+ total_sec -= interval
388+
389+ self .lobby_service .disband_lobby (lobby .channel_id , lobby .user )
390+
391+ timer_embed .title = "🕒 Lobby Expired"
392+ timer_embed .description = "The solo lobby has expired due to inactivity."
393+ try :
394+ await timer_msg .edit (embed = timer_embed )
395+ except discord .HTTPException :
396+ pass
397+
398+ try :
399+ main_msg = await channel .fetch_message (lobby .main_message )
400+ await main_msg .delete ()
401+ except (discord .NotFound , discord .HTTPException ):
402+ pass
403+
404+ except asyncio .CancelledError :
405+ try :
406+ await timer_msg .delete ()
407+ except discord .HTTPException :
408+ pass
409+
410+ except (discord .HTTPException , discord .Forbidden , GameError ) as e :
411+ print (f"Solo lobby timer error: { e } " )
412+
413+ def restart_solo_lobby_timer (self , lobby ):
414+ """
415+ Starts the solo timer if there's 1 player, cancels it otherwise.
416+ """
417+ cid = lobby .channel_id
418+
419+ task = self ._solo_lobby_timers .get (cid )
420+ if task and not task .done ():
421+ task .cancel ()
422+
423+ if len (lobby .game .players ()) == 1 :
424+ self ._solo_lobby_timers [cid ] = asyncio .create_task (
425+ self .start_solo_lobby_timer (lobby )
426+ )
427+
341428
342429async def setup (bot : commands .Bot ) -> None :
343430 """
0 commit comments