Skip to content

Commit 41f0633

Browse files
committed
2 parents 4a39cbf + 7fa12d8 commit 41f0633

File tree

6 files changed

+229
-9
lines changed

6 files changed

+229
-9
lines changed

discommand/entry/inject.py

+8
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
from ..ext.cog_manager import CogManager
22

3+
from discord import (
4+
Client,
5+
AutoShardedClient
6+
)
7+
38
def inject_into_bot(bot):
9+
if not issubclass(bot.__class__, Client) or not issubclass(bot.__class__, AutoShardedClient):
10+
raise TypeError("Bot is not of type discord.Client or discord.AutoShardedClient (ext.commands.Bot or ext.commands.AutoShardedBot are not supported as they implement commands themselves.)")
11+
412
cog_manager = CogManager(bot)
513
bot.cog_manager = cog_manager

discommand/ext/cog_manager.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ def __init__(self, bot) -> None:
1313
# FIXME: make internal only and have staticmethod to retrieve
1414
self._cogs: dict[str, cogs.Cog] = {}
1515

16-
self.all_commands: dict[str, Command] = {}
16+
self.all_commands: dict[str, Command | CommandGroup] = {}
17+
self.sub_commands: dict[str, Command] = {}
1718

1819

1920
def update_all_commands(self) -> None:
@@ -22,11 +23,12 @@ def update_all_commands(self) -> None:
2223
print(f"Updating: {name}")
2324
if type(command) == CommandGroup: # Add all the groups sub-commands so discord.py recognizes them too.
2425
print("Found Group while updating...")
25-
print(command.commands)
2626
for _name, _command in command.commands.items():
27-
self.all_commands[_name] = _command
27+
self.sub_commands[_name] = _command
2828
self.all_commands[name] = command
29+
2930
self.client.all_commands = self.all_commands
31+
self.client.sub_commands = self.sub_commands
3032

3133
async def start_load_cogs(self, path: str) -> None:
3234
for file in listdir(path):

discommand/ext/cogs.py

-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22
import importlib.util
33
import importlib.machinery
44

5-
from functools import wraps
6-
75
from typing_extensions import (
86
Self,
97
Any

discommand/ext/commands.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async def invoke(self, bot, context): # FIXME: type check context
125125
raise CommandCheckError("One of the commands checks failed.")
126126

127127
injected = hooked_wrapped_callback(self, bot, context, self.callback)
128-
await injected(*context.args, **context.kwargs)
128+
await injected(**context.args)
129129

130130
def add_check(self, check: Callable) -> None:
131131
"""Adds a check to the checks list.
@@ -137,12 +137,13 @@ def add_check(self, check: Callable) -> None:
137137

138138
def hooked_wrapped_callback(command: Command, bot, context, coro: Callable) -> Callable:
139139
@wraps(coro)
140-
async def wrapped(*args, **kwargs):
140+
async def wrapped(**args):
141141
try:
142-
ret = await coro(command, bot, context, *args, **kwargs)
142+
ret = await coro(command, bot, context, **args)
143143
except asyncio.CancelledError:
144144
return
145145
except Exception as exc:
146+
print(exc)
146147
raise CommandInvokeError(exc) from exc
147148
return ret
148149

@@ -212,7 +213,7 @@ async def invoke(self, bot, context):
212213
if _do_checks(self.checks, context) != True:
213214
raise CommandCheckError("One of the commands checks failed.")
214215
injected = hooked_wrapped_callback(self, bot, context, self.callback)
215-
await injected(*context.args, **context.kwargs)
216+
await injected(**context.args)
216217

217218

218219
def add_command(self, command: Command) -> None:

discommand/ext/context.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from discord import (
2+
Message,
3+
Client,
4+
AutoShardedClient
5+
)
6+
from discord.channel import DMChannel, GroupChannel, PartialMessageable, StageChannel, TextChannel, VoiceChannel
7+
from discord.threads import Thread
8+
from .commands import (
9+
Command,
10+
CommandGroup
11+
)
12+
from typing import (
13+
Any,
14+
Callable,
15+
Coroutine
16+
)
17+
from discord import (
18+
TextChannel,
19+
VoiceChannel,
20+
StageChannel
21+
)
22+
23+
from discord.embeds import Embed
24+
from discord.abc import Messageable
25+
26+
class Context(Messageable):
27+
def __init__(
28+
self,
29+
*,
30+
message: Message,
31+
bot: Client | AutoShardedClient,
32+
args: list[Any] | str,
33+
kwargs: dict[str, Any],
34+
prefix: str | Callable | Coroutine,
35+
command: Command | CommandGroup,
36+
invoked_with: Command | CommandGroup,
37+
invoked_parent: CommandGroup,
38+
invoked_subcommand: Command
39+
) -> None:
40+
self._message = message
41+
self.bot = bot
42+
self.args = args if args else {}
43+
self.prefix = prefix
44+
self.command = command
45+
self.invoked_with = invoked_with
46+
self.invoked_parent = invoked_parent
47+
self.invoked_subcommand = invoked_subcommand
48+
49+
50+
async def invoke(self, command: Command | CommandGroup):
51+
return await command.invoke(self.bot, self)
52+
53+
@property
54+
def message(self):
55+
return self._message
56+
57+
@property
58+
def guild(self):
59+
return self.message.guild
60+
61+
@property
62+
def channel(self):
63+
return self.message.channel
64+
65+
@property
66+
def author(self):
67+
return self.message.author
68+
69+
@property
70+
def me(self):
71+
return self.guild.me if self.guild else self.bot.user
72+
73+
74+
@property
75+
def permissions(self):
76+
_type = type(self.channel)
77+
#if _type == DMChannel:
78+
# return None # FIXME: return DM permissions.
79+
return self.channel.permissions_for(self.author)
80+
# FIXME: interactions not implemented
81+
82+
@property
83+
def bot_permissions(self):
84+
return self.channel.permissions_for(self.bot)
85+
86+
@property
87+
def valid(self) -> bool:
88+
return self.prefix is not None and self.command is not None
89+
90+
91+
92+
async def send(self, content: str, tts: bool = False, embed: Embed = None, mention_author: bool = False, delete_after: int = None):
93+
return await self.channel.send(
94+
content=content,
95+
tts=tts,
96+
embed=embed,
97+
embeds=None, #FIXME: support multiple embeds, files, stickers
98+
file=None,
99+
files=None,
100+
stickers=None,
101+
delete_after=delete_after,
102+
nonce=None, # FIXME: Support none, mentions, reference
103+
allowed_mentions=None,
104+
reference=None,
105+
mention_author=mention_author,
106+
view=None, # FIXME: support view, embed supression, and silent flag
107+
suppress_embeds=None,
108+
silent=False,
109+
)
110+

discommand/ext/events.py

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import inspect
2+
3+
from discord import (
4+
Message,
5+
Client,
6+
AutoShardedClient
7+
)
8+
from typing import (
9+
Any,
10+
Callable,
11+
Coroutine
12+
)
13+
from .commands import (
14+
Command,
15+
CommandGroup
16+
)
17+
18+
from .context import Context
19+
20+
def _create_context(
21+
message: Message,
22+
bot: Client | AutoShardedClient,
23+
args: list[Any],
24+
prefix: str | Callable | Coroutine,
25+
command: Command | CommandGroup,
26+
invoked_with: Command | CommandGroup,
27+
kwargs: dict[str, Any] = {},
28+
invoked_parent: CommandGroup = None,
29+
invoked_subcommand: Command = None,
30+
cls: Context = None): # FIXME: typing
31+
_cls = cls or Context
32+
33+
return _cls(
34+
message=message,
35+
bot=bot,
36+
args=args,
37+
kwargs=kwargs,
38+
prefix=prefix,
39+
command=command,
40+
invoked_with=invoked_with,
41+
invoked_parent=invoked_parent,
42+
invoked_subcommand=invoked_subcommand
43+
)
44+
45+
def _has_prefix(prefixes: list, message: Message) -> str:
46+
for prefix in prefixes:
47+
if len(message.content.split(prefix))==2:
48+
return prefix
49+
50+
def _find_command(bot: Client | AutoShardedClient, prefix: str, content: str) -> Command | None:
51+
name = content.split(prefix, 1)[1].split(" ")
52+
if len(name)>=2:
53+
if bot.all_commands[name[0]] == bot.sub_commands[name[1]].parent:
54+
return bot.sub_commands[name[1]]
55+
elif name[0] in bot.all_commands:
56+
return bot.all_commands[name[0]]
57+
else:
58+
return bot.all_commands[name[0]] if name[0] in bot.all_commands else None
59+
60+
def _process_arguments(content: str):
61+
pass
62+
63+
def _find_args(command: Command, message: Message) -> dict[str, str]:
64+
try:
65+
args = inspect.getfullargspec(command.callback)[0]
66+
except ValueError:
67+
args = inspect.signature(command.callback)
68+
69+
given_args = {}
70+
if not len(args)>3:
71+
return None
72+
del args[:3]
73+
iteration = 0
74+
_temp = " "
75+
for arg in message.content.split(command.name, 1)[1].strip().split(" "):
76+
if iteration>=len(args):
77+
_temp += arg
78+
else:
79+
given_args[args[iteration]] = arg
80+
iteration += 1
81+
given_args[args[-1]] += _temp
82+
83+
return given_args
84+
85+
def process_message(bot: Client | AutoShardedClient, prefixes: str | list, message: Message, context = None) -> Command:
86+
"""Processes a message, for retrieving a command if the message contains the prefix+command combination.
87+
88+
Args:
89+
bot (Client | AutoShardedClient): Initialized discord.py bot.
90+
prefixes (str | list): either a string or a list of prefixes.
91+
message (Message): The message processed for a command.
92+
93+
Returns:
94+
Command: The command found
95+
Args (dict): All found args
96+
"""
97+
if (prefix := _has_prefix(prefixes, message)):
98+
if (command := _find_command(bot, prefix, message.content)):
99+
args = _find_args(command, message)
100+
context = _create_context(message, bot, args=args, kwargs={}, prefix=prefixes, command=command, invoked_with=command, cls=context)
101+
return command, context

0 commit comments

Comments
 (0)