@@ -106,7 +106,10 @@ async def on_ready():
106106 asyncio .create_task (_stdin_reader ())
107107 # Sync slash commands (Discord API call, can take seconds)
108108 if slash_defs :
109- await _register_slash_commands ()
109+ try :
110+ await _register_slash_commands ()
111+ except Exception as e :
112+ emit ({"event" : "error" , "message" : f"Slash command registration failed: { e } " })
110113
111114 @client .event
112115 async def on_message (message ):
@@ -239,54 +242,60 @@ async def on_member_remove(member):
239242 # ── Slash Commands ──────────────────────────────────────────────
240243
241244 async def _register_slash_commands ():
245+ import inspect
246+
247+ _type_map = {"string" : str , "integer" : int , "number" : float , "boolean" : bool }
248+
249+ def _make_slash_callback (param_defs ):
250+ """Create a callback whose __signature__ exposes params to discord.py."""
251+ async def _callback (interaction : discord .Interaction , ** kwargs ):
252+ cmd_name = interaction .command .name
253+ itk = str (uuid .uuid4 ())
254+ interactions [itk ] = interaction
255+ await interaction .response .defer (thinking = True )
256+ emit ({
257+ "event" : "slash_command" ,
258+ "command" : cmd_name ,
259+ "args" : {k : str (v ) for k , v in kwargs .items () if v is not None },
260+ "channel_id" : str (interaction .channel_id ),
261+ "user" : str (interaction .user ),
262+ "user_id" : str (interaction .user .id ),
263+ "guild_id" : str (interaction .guild_id ) if interaction .guild_id else None ,
264+ "interaction_token" : itk ,
265+ })
266+
267+ # Build a proper signature so discord.py registers slash options
268+ sig_params = [
269+ inspect .Parameter ("interaction" , inspect .Parameter .POSITIONAL_OR_KEYWORD ,
270+ annotation = discord .Interaction ),
271+ ]
272+ for p in param_defs :
273+ annotation = _type_map .get (p .get ("type" , "string" ), str )
274+ required = p .get ("required" , True )
275+ default = inspect .Parameter .empty if required else None
276+ sig_params .append (
277+ inspect .Parameter (p ["name" ], inspect .Parameter .POSITIONAL_OR_KEYWORD ,
278+ annotation = annotation , default = default ),
279+ )
280+ _callback .__signature__ = inspect .Signature (sig_params )
281+ return _callback
282+
242283 for cmd_def in slash_defs :
243284 name = cmd_def ["name" ]
244285 desc = cmd_def .get ("description" , name )
245286 params = cmd_def .get ("params" , [])
246287
288+ callback = _make_slash_callback (params )
247289 if params :
248- # Command with a single string parameter
249- param = params [0 ]
250-
251- @tree .command (name = name , description = desc )
252- @app_commands .describe (** {param ["name" ]: param .get ("description" , param ["name" ])})
253- async def slash_handler (interaction : discord .Interaction , ** kwargs ):
254- cmd_name = interaction .command .name
255- itk = str (uuid .uuid4 ())
256- interactions [itk ] = interaction
257- await interaction .response .defer (thinking = True )
258- emit ({
259- "event" : "slash_command" ,
260- "command" : cmd_name ,
261- "args" : {k : str (v ) for k , v in kwargs .items ()},
262- "channel_id" : str (interaction .channel_id ),
263- "user" : str (interaction .user ),
264- "user_id" : str (interaction .user .id ),
265- "guild_id" : str (interaction .guild_id ) if interaction .guild_id else None ,
266- "interaction_token" : itk ,
267- })
268- else :
269- @tree .command (name = name , description = desc )
270- async def slash_handler_no_args (interaction : discord .Interaction ):
271- cmd_name = interaction .command .name
272- itk = str (uuid .uuid4 ())
273- interactions [itk ] = interaction
274- await interaction .response .defer (thinking = True )
275- emit ({
276- "event" : "slash_command" ,
277- "command" : cmd_name ,
278- "args" : {},
279- "channel_id" : str (interaction .channel_id ),
280- "user" : str (interaction .user ),
281- "user_id" : str (interaction .user .id ),
282- "guild_id" : str (interaction .guild_id ) if interaction .guild_id else None ,
283- "interaction_token" : itk ,
284- })
285-
286- # Sync per guild for instant registration (global sync can take hours)
290+ descriptions = {p ["name" ]: p .get ("description" , p ["name" ]) for p in params }
291+ callback = app_commands .describe (** descriptions )(callback )
292+ tree .command (name = name , description = desc )(callback )
293+
294+ # Copy global commands to each guild for instant availability, then sync
287295 synced_guilds = 0
288296 for guild in client .guilds :
289297 try :
298+ tree .copy_global_to (guild = guild )
290299 await tree .sync (guild = guild )
291300 synced_guilds += 1
292301 except Exception as e :
0 commit comments