Skip to content

Commit 677a425

Browse files
committed
BUGFIX:
- discord command names might differ from allowed python function names
1 parent 9a2c373 commit 677a425

File tree

2 files changed

+34
-5
lines changed

2 files changed

+34
-5
lines changed

core/utils/helper.py

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import importlib
99
import inspect
1010
import json
11+
import keyword
1112
import logging
1213
import luadata
1314
import os
@@ -79,7 +80,8 @@
7980
"YAMLError",
8081
"DictWrapper",
8182
"format_dict_pretty",
82-
"show_dict_diff"
83+
"show_dict_diff",
84+
"to_valid_pyfunc_name"
8385
]
8486

8587
logger = logging.getLogger(__name__)
@@ -1170,3 +1172,29 @@ def show_dict_diff(old_dict: dict[str, Any], new_dict: dict[str, Any], context_l
11701172
result.append("```")
11711173

11721174
return '\n'.join(result)
1175+
1176+
1177+
def to_valid_pyfunc_name(raw_name: str) -> str:
1178+
"""
1179+
Convert an arbitrary name (e.g. 'test-1') into a legal Python identifier.
1180+
1181+
Rules applied (in order):
1182+
1183+
1. Replace every character that is **not** `[A-Za-z0-9_]` with an underscore.
1184+
2. If the resulting string starts with a digit, prepend an underscore.
1185+
3. If the result is a Python keyword (`def`, `class`, …), prefix it with an underscore as well.
1186+
1187+
The function returns the sanitized name; the original name is kept unchanged.
1188+
"""
1189+
# 1️⃣ Replace everything that is not a word character
1190+
cleaned = re.sub(r'\W', '_', raw_name)
1191+
1192+
# 2️⃣ If it starts with a digit, add a leading underscore
1193+
if re.match(r'^\d', cleaned):
1194+
cleaned = '_' + cleaned
1195+
1196+
# 3️⃣ Avoid Python keywords
1197+
if keyword.iskeyword(cleaned):
1198+
cleaned = '_' + cleaned
1199+
1200+
return cleaned

plugins/commands/commands.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,7 @@ def register_commands(self):
330330
return
331331

332332
for name, cmd in self.locals["commands"].items():
333+
sanitized_name = utils.to_valid_pyfunc_name(name)
333334
try:
334335
checks: list = []
335336
if "roles" in cmd:
@@ -343,14 +344,14 @@ def register_commands(self):
343344
kw_only = self.annotated_params(params)
344345
kw_as_args = ", ".join(f"{n}={n}" for n in params.keys())
345346
src = f"""
346-
async def __{name}_callback(interaction: discord.Interaction, {kw_only}):
347+
async def __{sanitized_name}_callback(interaction: discord.Interaction, {kw_only}):
347348
await interaction.response.defer()
348349
await self.exec_slash_command(interaction, {kw_as_args})
349350
"""
350351
else:
351352
# no options – only interaction
352353
src = f"""
353-
async def __{name}_callback(interaction: discord.Interaction):
354+
async def __{sanitized_name}_callback(interaction: discord.Interaction):
354355
await interaction.response.defer()
355356
await self.exec_slash_command(interaction)
356357
"""
@@ -372,7 +373,7 @@ async def __{name}_callback(interaction: discord.Interaction):
372373
},
373374
local_ns,
374375
)
375-
_callback = local_ns[f"__{name}_callback"]
376+
_callback = local_ns[f"__{sanitized_name}_callback"]
376377
_callback.__module__ = self.__module__
377378
_callback.__discord_app_commands_guild_only__ = True
378379
_callback.__discord_app_commands_contexts__ = app_commands.AppCommandContext(guild=True)
@@ -417,7 +418,7 @@ async def __{name}_callback(interaction: discord.Interaction):
417418
self.log.info(f" - Custom command /{name} added.")
418419

419420
except Exception as ex:
420-
self.log.error(f"Failed to register command `{name}`: {ex}", exc_info=True)
421+
self.log.error(f"Failed to register command `{name}`: {ex}")
421422

422423
def _unregister_commands(self):
423424
for name in list(self.commands.keys()):

0 commit comments

Comments
 (0)