Skip to content
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
2ede85f
refactor: optimize sync behavior
EepyElvyra Apr 26, 2022
14fb6d7
ooops
EepyElvyra Apr 26, 2022
0256ebd
Update interactions/api/http/interaction.py
EepyElvyra Apr 27, 2022
c447bea
Update interactions/client/bot.pyi
EepyElvyra Apr 27, 2022
dba98de
Update interactions/api/http/scheduledEvent.py
EepyElvyra Apr 27, 2022
7e72baa
Update interactions/client/bot.py
EepyElvyra Apr 27, 2022
11acf4b
Update bot.py
EepyElvyra Apr 27, 2022
7426776
ci: correct from checks.
pre-commit-ci[bot] Apr 27, 2022
a749a83
fix: Fix command check for user and member decorator
EepyElvyra Apr 28, 2022
a328711
Merge remote-tracking branch 'origin/rework_cmd_sync' into rework_cmd…
EepyElvyra Apr 28, 2022
998c0b3
Update interactions/client/bot.py
EepyElvyra Apr 29, 2022
518aad7
fix!: Fix autocomplete when sync is disabled
EepyElvyra Apr 30, 2022
622f3e2
Merge remote-tracking branch 'origin/rework_cmd_sync' into rework_cmd…
EepyElvyra Apr 30, 2022
951208d
Update bot.py
EepyElvyra May 1, 2022
3bd3820
Update bot.py
EepyElvyra May 1, 2022
8e8d59d
Update bot.pyi
EepyElvyra May 1, 2022
71e1aad
ci: correct from checks.
pre-commit-ci[bot] May 1, 2022
2548d63
Update bot.py
EepyElvyra May 1, 2022
a7dcf86
fix: fix option checks and autocomplete dispatch with command names
EepyElvyra May 2, 2022
f79c93a
ci
EepyElvyra May 2, 2022
f08a3d9
refactor: unnecessary if checks
EepyElvyra May 2, 2022
39cbf95
fix!: Fix synchronisation by properly checking attributes and their l…
FayeDel May 2, 2022
8f7b21a
refactor: Remove print statements.
FayeDel May 2, 2022
04ed0a7
refactor: move sync and autocomplete into _ready
EepyElvyra May 2, 2022
c91d8da
doc: add warning
EepyElvyra May 2, 2022
baaeb0d
Update interactions/client/bot.py
EepyElvyra May 3, 2022
95e5f6b
Merge branch 'unstable' into rework_cmd_sync
EepyElvyra May 5, 2022
c8076da
refactor!: Consider extensions in the sync process
EepyElvyra May 5, 2022
75e6db9
Merge branch 'unstable' into rework_cmd_sync
EepyElvyra May 5, 2022
3b57ee8
ci: correct from checks.
pre-commit-ci[bot] May 5, 2022
9694436
i hate merge conflicts
EepyElvyra May 5, 2022
97a926f
purge: remove debugging changes
EepyElvyra May 5, 2022
8697ce9
Update interactions/client/bot.py
EepyElvyra May 6, 2022
cc53d66
Update interactions/client/bot.py
EepyElvyra May 6, 2022
2405d79
fix: consider loading after bot start
EepyElvyra May 6, 2022
08f733e
ci: correct from checks.
pre-commit-ci[bot] May 6, 2022
00f3284
refactor: make compare sync static
EepyElvyra May 6, 2022
cfd2b4e
Merge remote-tracking branch 'origin/rework_cmd_sync' into rework_cmd…
EepyElvyra May 6, 2022
fbf5c72
ci: correct from checks.
pre-commit-ci[bot] May 6, 2022
08623f5
fix: consider sync toggle on load
EepyElvyra May 6, 2022
ca551f8
fix: Fix attribute reference typo.
FayeDel May 6, 2022
253dbe4
fix!: Attempt to fix critical error on startup with JSONException
EepyElvyra May 8, 2022
7ecb6f0
add check
EepyElvyra May 8, 2022
ea7d7bf
fix
EepyElvyra May 8, 2022
b379d46
refactor: change raise to warning if sync is off
EepyElvyra May 8, 2022
8f39278
ci: correct from checks.
pre-commit-ci[bot] May 8, 2022
a3b9c47
fix!: continue
EepyElvyra May 8, 2022
7630a81
fix!: fix snyc bug if the bot does not have the `application.commands…
EepyElvyra May 9, 2022
22ae9b4
fix: fix permission overwrite serialization and allow modifying chann…
EepyElvyra May 9, 2022
ba3d111
ci
EepyElvyra May 9, 2022
0dac1cc
Merge branch 'unstable' into rework_cmd_sync
EepyElvyra May 9, 2022
2fa7f6c
fix: Add missing `_`
EepyElvyra May 9, 2022
77ed70d
Merge remote-tracking branch 'origin/rework_cmd_sync' into rework_cmd…
EepyElvyra May 9, 2022
ca1e724
refactor: change critical to exception to provide a better DX
EepyElvyra May 9, 2022
b1fcebb
remove f-string
EepyElvyra May 9, 2022
05c6e57
fix
EepyElvyra May 10, 2022
1bb6bb1
Update interactions/api/models/channel.py
EepyElvyra May 11, 2022
b584b26
Update interactions/api/models/guild.py
EepyElvyra May 11, 2022
848e7f7
Update interactions/client/bot.py
EepyElvyra May 11, 2022
6a1cc28
Update interactions/client/bot.py
EepyElvyra May 11, 2022
d214655
Update interactions/client/bot.py
EepyElvyra May 11, 2022
c9ff68e
Update interactions/client/bot.py
EepyElvyra May 11, 2022
80e723e
ci: correct from checks.
pre-commit-ci[bot] May 11, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions interactions/api/models/role.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Role(DictSerializerMixin):
"managed",
"mentionable",
"tags",
"flags",
"permissions",
"_client",
)
Expand Down
111 changes: 80 additions & 31 deletions interactions/client/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,10 @@ async def __register_name_autocomplete(self) -> None:
name=f"autocomplete_{_command}_{self.__name_autocomplete[key]['name']}",
)

async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict]:
@staticmethod
async def __compare_sync(
data: dict, pool: List[dict]
) -> Tuple[bool, dict]: # sourcery no-metrics
"""
Compares an application command during the synchronization process.

Expand All @@ -150,10 +153,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
:return: Whether the command has changed or not.
:rtype: bool
"""

# sourcery skip: none-compare
attrs: List[str] = [
name
for name in ApplicationCommand.__slots__
if not name.startswith("_") and not name.endswith("id") and name != "version"
if not name.startswith("_")
and not name.endswith("id")
and name not in {"version", "default_permission"}
]

option_attrs: List[str] = [name for name in Option.__slots__ if not name.startswith("_")]
Expand Down Expand Up @@ -252,6 +259,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
return clean, _command
else:
continue
elif option_attr == "required":
if (
option.get(option_attr) == None # noqa: E711
and _option.get(option_attr)
== False # noqa: E712
):
# API not including if False
continue
elif option.get(option_attr) != _option.get(
option_attr
):
Expand All @@ -271,6 +286,14 @@ async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict
# This is an API/Version difference.
continue

elif (
attr == "dm_permission"
and data.get(attr) == True # noqa: E712
and command.get(attr) == None # noqa: E711
):
# idk, it encountered me and synced unintentionally
continue

# elif data.get(attr, None) and command.get(attr) == data.get(attr):
elif command.get(attr, None) == data.get(attr, None):
# hasattr checks `dict.attr` not `dict[attr]`
Expand Down Expand Up @@ -375,6 +398,17 @@ async def __get_all_commands(self) -> None:
application_id=self.me.id, guild_id=_id, with_localizations=True
)

if isinstance(_cmds, dict) and _cmds.get("code"):
if int(_cmds.get("code")) != 50001:
raise JSONException(_cmds["code"], message=f'{_cmds["message"]} |')

log.warning(
f"Your bot is missing access to guild with corresponding id {_id}! "
"Syncing commands will not be possible until it is invited with "
"`application.commands` scope!"
)
continue

for command in _cmds:
if command.get("code"):
# Error exists.
Expand Down Expand Up @@ -416,6 +450,10 @@ async def __sync(self) -> None: # sourcery no-metrics
application_id=self.me.id, guild_id=_id, with_localizations=True
)

if isinstance(_cmds, dict) and _cmds.get("code"):
# Error exists.
raise JSONException(_cmds["code"], message=f'{_cmds["message"]} |')

for command in _cmds:
if command.get("code"):
# Error exists.
Expand Down Expand Up @@ -708,22 +746,27 @@ def __check_options(_option: Option, _names: list, _sub_command: Option = MISSIN
def __check_coro():
__indent = 4
log.debug(f"{' ' * __indent}Checking coroutine: '{coro.__name__}'")
if not len(coro.__code__.co_varnames):
_ismethod = hasattr(coro, "__func__")
if not len(coro.__code__.co_varnames) ^ (
_ismethod and len(coro.__code__.co_varnames) == 1
):
raise InteractionException(
11, message="Your command needs at least one argument to return context."
)
elif "kwargs" in coro.__code__.co_varnames:
return
elif _sub_cmds_present and len(coro.__code__.co_varnames) < 2:
elif _sub_cmds_present and len(coro.__code__.co_varnames) < (3 if _ismethod else 2):
raise InteractionException(
11, message="Your command needs one argument for the sub_command."
)
elif _sub_groups_present and len(coro.__code__.co_varnames) < 3:
elif _sub_groups_present and len(coro.__code__.co_varnames) < (4 if _ismethod else 3):
raise InteractionException(
11,
message="Your command needs one argument for the sub_command and one for the sub_command_group.",
)
add: int = 1 + abs(_sub_cmds_present) + abs(_sub_groups_present)
add: int = (
1 + abs(_sub_cmds_present) + abs(_sub_groups_present) + 1 if _ismethod else +0
)

if len(coro.__code__.co_varnames) - add < len(set(_options_names)):
log.debug(
Expand Down Expand Up @@ -896,7 +939,11 @@ def decorator(coro: Coroutine) -> Callable[..., Any]:
coro=coro,
)

coro._command_data = commands
if hasattr(coro, "__func__"):
coro.__func__._command_data = commands
else:
coro._command_data = commands

self.__command_coroutines.append(coro)

if scope is not MISSING:
Expand Down Expand Up @@ -1268,19 +1315,28 @@ def remove(self, name: str, package: Optional[str] = None) -> None:
log.error(f"Extension {name} has not been loaded before. Skipping.")
return

try:
extension.teardown() # made for Extension, usable by others
except AttributeError:
pass

if isinstance(extension, ModuleType): # loaded as a module
for ext_name, ext in getmembers(
extension, lambda x: isinstance(x, type) and issubclass(x, Extension)
):
self.remove(ext_name)

if ext_name != "Extension":
_extension = self._extensions.get(ext_name)
try:
self._loop.create_task(
_extension.teardown()
) # made for Extension, usable by others
except AttributeError:
pass

del sys.modules[_name]

else:
try:
self._loop.create_task(extension.teardown()) # made for Extension, usable by others
except AttributeError:
pass

del self._extensions[_name]

log.debug(f"Removed extension {name}.")
Expand All @@ -1291,6 +1347,9 @@ def reload(
r"""
"Reloads" an extension off of current client from an import resolve.

.. warning::
This will remove and re-add application commands, counting towards your daily application command creation limit.

:param name: The name of the extension.
:type name: str
:param package?: The package of the extension.
Expand All @@ -1307,8 +1366,7 @@ def reload(

if extension is None:
log.warning(f"Extension {name} could not be reloaded because it was never loaded.")
self.load(name, package)
return
return self.load(name, package)

self.remove(name, package)
return self.load(name, package, *args, **kwargs)
Expand Down Expand Up @@ -1429,7 +1487,6 @@ def __new__(cls, client: Client, *args, **kwargs) -> "Extension":
# This gets every coroutine in a way that we can easily change them
# cls
for name, func in getmembers(self, predicate=iscoroutinefunction):

# TODO we can make these all share the same list, might make it easier to load/unload
if hasattr(func, "__listener_name__"): # set by extension_listener
func = client.event(
Expand Down Expand Up @@ -1497,32 +1554,24 @@ def __new__(cls, client: Client, *args, **kwargs) -> "Extension":

client._extensions[cls.__name__] = self

if client._websocket.ready.is_set() and client._automate_sync:
client._loop.create_task(client._Client__sync())

return self

def teardown(self):
async def teardown(self):
for event, funcs in self._listeners.items():
for func in funcs:
self.client._websocket._dispatch.events[event].remove(func)

for cmd, funcs in self._commands.items():
for func in funcs:
_index = self.client._Client__command_coroutines.index(func)
self.client._Client__command_coroutines.pop(_index)
self.client._websocket._dispatch.events[cmd].remove(func)

clean_cmd_names = [cmd[7:] for cmd in self._commands.keys()]
cmds = filter(
lambda cmd_data: cmd_data["name"] in clean_cmd_names,
self.client._http.cache.interactions.view,
)

if self.client._automate_sync:
[
self.client._loop.create_task(
self.client._http.delete_application_command(
cmd["application_id"], cmd["id"], cmd["guild_id"]
)
)
for cmd in cmds
]
await self.client._Client__sync()


@wraps(command)
Expand Down
5 changes: 3 additions & 2 deletions interactions/client/bot.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class Client:
def start(self) -> None: ...
def __register_events(self) -> None: ...
async def __register_name_autocomplete(self) -> None: ...
async def __compare_sync(self, data: dict, pool: List[dict]) -> Tuple[bool, dict]: ...
@staticmethod
async def __compare_sync(data: dict, pool: List[dict]) -> Tuple[bool, dict]: ...
async def _ready(self) -> None: ...
async def _login(self) -> None: ...
async def wait_until_ready(self) -> None: ...
Expand Down Expand Up @@ -120,7 +121,7 @@ class Extension:
_commands: dict
_listeners: dict
def __new__(cls, client: Client, *args, **kwargs) -> Extension: ...
def teardown(self) -> None: ...
async def teardown(self) -> None: ...

def extension_command(
*,
Expand Down