Skip to content

Commit 3c63f9f

Browse files
authored
Merge pull request #126 from eunwoo1104/anothercat/prevent-uneeded-requests-autoregister
Prevent unneeded requests while using sync_commands
2 parents c107539 + 8e7e518 commit 3c63f9f

File tree

3 files changed

+144
-9
lines changed

3 files changed

+144
-9
lines changed

discord_slash/client.py

Lines changed: 46 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -296,24 +296,62 @@ async def sync_all_commands(self, delete_from_unused_guilds=False):
296296
Matches commands registered on Discord to commands registered here.
297297
Deletes any commands on Discord but not here, and registers any not on Discord.
298298
This is done with a `put` request.
299+
A PUT request will only be made if there are changes detected.
299300
If ``sync_commands`` is ``True``, then this will be automatically called.
300301
301302
:param delete_from_unused_guilds: If the bot should make a request to set no commands for guilds that haven't got any commands registered in :class:``SlashCommand``
302303
"""
303304
cmds = await self.to_dict()
304305
self.logger.info("Syncing commands...")
305-
other_guilds = [x.id for x in self._discord.guilds if x.id not in cmds["guild"]]
306-
# This is an extremely bad way to do this, because slash cmds can be in guilds the bot isn't in
307-
# But it's the only way until discord makes an endpoint to request all the guild with cmds registered.
306+
cmds_formatted = {None: cmds['global']}
307+
for guild in cmds['guild']:
308+
cmds_formatted[guild] = cmds['guild'][guild]
309+
310+
for scope in cmds_formatted:
311+
new_cmds = cmds_formatted[scope]
312+
existing_cmds = await self.req.get_all_commands(guild_id = scope)
313+
existing_by_name = {}
314+
to_send=[]
315+
changed = False
316+
for cmd in existing_cmds:
317+
existing_by_name[cmd["name"]] = model.CommandData(**cmd)
318+
319+
if len(new_cmds) != len(existing_cmds):
320+
changed = True
321+
322+
for command in new_cmds:
323+
cmd_name = command["name"]
324+
if cmd_name in existing_by_name:
325+
cmd_data = model.CommandData(**command)
326+
existing_cmd = existing_by_name[cmd_name]
327+
if cmd_data != existing_cmd:
328+
changed=True
329+
to_send.append(command)
330+
else:
331+
command_with_id = command
332+
command_with_id["id"] = existing_cmd.id
333+
to_send.append(command_with_id)
334+
else:
335+
changed=True
336+
to_send.append(command)
308337

309-
await self.req.put_slash_commands(slash_commands=cmds["global"], guild_id=None)
338+
339+
if changed:
340+
self.logger.debug(f"Detected changes on {scope if scope is not None else 'global'}, updating them")
341+
await self.req.put_slash_commands(slash_commands=to_send, guild_id=scope)
342+
else:
343+
self.logger.debug(f"Detected no changes on {scope if scope is not None else 'global'}, skipping")
310344

311-
for x in cmds["guild"]:
312-
await self.req.put_slash_commands(slash_commands=cmds["guild"][x], guild_id=x)
313345
if delete_from_unused_guilds:
314-
for x in other_guilds:
346+
other_guilds = [guild.id for guild in self._discord.guilds if guild.id not in cmds["guild"]]
347+
# This is an extremly bad way to do this, because slash cmds can be in guilds the bot isn't in
348+
# But it's the only way until discord makes an endpoint to request all the guild with cmds registered.
349+
350+
for guild in other_guilds:
315351
with suppress(discord.Forbidden):
316-
await self.req.put_slash_commands(slash_commands=[], guild_id=x)
352+
existing = self.req.get_all_commands(guild_id = guild)
353+
if len(existing) != 0:
354+
await self.req.put_slash_commands(slash_commands=[], guild_id=guild)
317355

318356
self.logger.info("Completed syncing all commands!")
319357

discord_slash/error.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,14 @@ class IncorrectType(SlashCommandError):
5555
Type passed was incorrect
5656
"""
5757

58+
59+
class IncorrectCommandData(SlashCommandError):
60+
"""
61+
Incorrect data was passed to a slash command data object
62+
"""
63+
64+
5865
class AlreadyResponded(SlashCommandError):
5966
"""
6067
The interaction was already responded to
61-
"""
68+
"""

discord_slash/model.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,96 @@
66
from . import http
77
from . import error
88

9+
class ChoiceData:
10+
"""
11+
Command choice data object
12+
13+
:ivar name: Name of the choice, this is what the user will see
14+
:ivar value: Values of the choice, this is what discord will return to you
15+
"""
16+
def __init__(self, name, value):
17+
self.name = name
18+
self.value = value
19+
20+
def __eq__(self, other):
21+
return isinstance(other, ChoiceData) and self.__dict__ == other.__dict__
22+
23+
24+
class OptionData:
25+
"""
26+
Command option data object
27+
28+
:ivar name: Name of the option.
29+
:ivar description: Description of the option.
30+
:ivar required: If the option is required.
31+
:ivar choices: A list of :class:`ChoiceData`, cannot be present on subcommand groups
32+
:ivar options: List of :class:`OptionData`, this will be present if it's a subcommand group
33+
"""
34+
def __init__(
35+
self, name, description, required=False, choices=None, options=None, **kwargs
36+
):
37+
self.name = name
38+
self.description = description
39+
self.type = kwargs.get("type")
40+
if self.type is None:
41+
raise error.IncorrectCommandData("type is required for options")
42+
self.required = required
43+
if choices is not None:
44+
self.choices = []
45+
for choice in choices:
46+
self.choices.append(ChoiceData(**choice))
47+
else:
48+
self.choices = None
49+
50+
if self.type in (1, 2):
51+
self.options = []
52+
if options is not None:
53+
for option in options:
54+
self.options.append(OptionData(**option))
55+
elif self.type == 2:
56+
raise error.IncorrectCommandData(
57+
"Options are required for subcommands / subcommand groups"
58+
)
59+
60+
def __eq__(self, other):
61+
return isinstance(other, OptionData) and self.__dict__ == other.__dict__
62+
63+
64+
class CommandData:
65+
"""
66+
Slash command data object
67+
68+
:ivar name: Name of the command.
69+
:ivar description: Description of the command.
70+
:ivar options: List of :class:`OptionData`.
71+
:ivar id: Command id, this is received from discord so may not be present
72+
"""
73+
74+
def __init__(
75+
self, name, description, options = [], id=None, application_id = None, version = None, **kwargs
76+
):
77+
self.name = name
78+
self.description = description
79+
self.id = id
80+
self.application_id = application_id
81+
self.version = version
82+
if options is not None:
83+
self.options = []
84+
for option in options:
85+
self.options.append(OptionData(**option))
86+
else:
87+
self.options = None
88+
89+
def __eq__(self, other):
90+
if isinstance(other, CommandData):
91+
return (
92+
self.name == other.name
93+
and self.description == other.description
94+
and self.options == other.options
95+
)
96+
else:
97+
return False
98+
999

10100
class CommandObject:
11101
"""

0 commit comments

Comments
 (0)