Created
June 5, 2020 09:36
-
-
Save SebbyLaw/b1f999b2cb172688d7a209a52483c950 to your computer and use it in GitHub Desktop.
Command suffix proof of concept implementation for discord.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import collections | |
import discord | |
from discord.ext import commands | |
from discord.ext.commands.view import StringView | |
def _suffix_used(suffix, content): | |
space_index = content.find(' ') | |
suffix_index = content.find(suffix) | |
return suffix_index > 0 and (space_index == -1 or suffix_index < space_index) | |
class SuffixContext(commands.Context): | |
def __init__(self, **attrs): | |
super().__init__(**attrs) | |
self.suffix = attrs.pop('suffix') | |
@property | |
def valid(self): | |
return (self.suffix is not None or self.prefix is not None) and self.command is not None | |
async def reinvoke(self, *, call_hooks=False, restart=True): | |
if self.suffix is not None: | |
# since the command was invoked with a suffix, | |
# we need to make sure the view doesn't try to skip a nonexistent prefix | |
original_prefix = self.prefix | |
self.prefix = '' | |
await super().reinvoke(call_hooks=call_hooks, restart=restart) | |
try: | |
self.prefix = original_prefix | |
except NameError: | |
pass | |
class SuffixBot(commands.Bot): | |
def __init__(self, command_prefix=None, command_suffix=None, **options): | |
if command_prefix is None and command_suffix is None: | |
raise ValueError('Bot must have a prefix or suffix') | |
super().__init__(command_prefix=command_prefix, **options) | |
self.command_suffix = command_suffix | |
async def get_prefix(self, message): | |
if self.command_prefix is None: | |
return None | |
return await super().get_prefix(message) | |
async def get_suffix(self, message): | |
"""|coro| | |
Retrieves the prefix the bot is listening to | |
with the message as a context. | |
Parameters | |
----------- | |
message: :class:`discord.Message` | |
The message context to get the prefix of. | |
Returns | |
-------- | |
Optional[Union[List[:class:`str`], :class:`str`]] | |
A list of prefixes or a single prefix that the bot is | |
listening for. | |
""" | |
if self.command_suffix is None: | |
return None | |
suffix = ret = self.command_suffix | |
if callable(suffix): | |
ret = await discord.utils.maybe_coroutine(suffix, self, message) | |
if not isinstance(ret, str): | |
try: | |
ret = list(ret) | |
except TypeError: | |
# It's possible that a generator raised this exception. Don't | |
# replace it with our own error if that's the case. | |
if isinstance(ret, collections.abc.Iterable): | |
raise | |
raise TypeError("command_suffix must be plain string, iterable of strings, or callable " | |
"returning either of these, not {}".format(ret.__class__.__name__)) | |
if not ret: | |
raise ValueError("Iterable command_prefix must contain at least one suffix") | |
return ret | |
async def get_context(self, message, *, cls=SuffixContext): | |
"""Defaults to check for prefix first.""" | |
view = StringView(message.content) | |
ctx = cls(prefix=None, suffix=None, view=view, bot=self, message=message) | |
if self._skip_check(message.author.id, self.user.id): | |
return ctx | |
prefix = await self.get_prefix(message) | |
suffix = await self.get_suffix(message) | |
if prefix is not None: | |
if isinstance(prefix, str): | |
if view.skip_string(prefix): | |
invoked_prefix = prefix | |
else: | |
try: | |
if message.content.startswith(tuple(prefix)): | |
invoked_prefix = discord.utils.find(view.skip_string, prefix) | |
except TypeError: | |
if not isinstance(prefix, list): | |
raise TypeError("get_prefix must return either a string or a list of string, " | |
"not {}".format(prefix.__class__.__name__)) | |
for value in prefix: | |
if not isinstance(value, str): | |
raise TypeError("Iterable command_prefix or list returned from get_prefix must " | |
"contain only strings, not {}".format(value.__class__.__name__)) | |
raise | |
else: | |
if isinstance(suffix, str): | |
if _suffix_used(suffix, message.content): | |
invoked_suffix = suffix | |
else: | |
return ctx | |
else: | |
try: | |
invoked_suffixes = [s for s in suffix if _suffix_used(s, message.content)] | |
if not invoked_suffixes: | |
return ctx | |
for suf in invoked_suffixes: | |
invoker = view.get_word()[:-len(suf)] | |
command = self.all_commands.get(invoker) | |
if command is not None: | |
view.undo() | |
invoked_suffix = suf | |
break | |
else: | |
return ctx | |
except TypeError: | |
if not isinstance(suffix, list): | |
raise TypeError("get_suffix must return either a string or a list of string, " | |
"not {}".format(suffix.__class__.__name__)) | |
for value in suffix: | |
if not isinstance(value, str): | |
raise TypeError("Iterable command_suffix or list returned from get_suffix must " | |
"contain only strings, not {}".format(value.__class__.__name__)) | |
raise | |
invoker = view.get_word() | |
try: | |
ctx.suffix = invoked_suffix | |
except NameError: | |
try: | |
ctx.prefix = invoked_prefix | |
except NameError: | |
pass | |
else: | |
invoker = invoker[:-len(invoked_suffix)] | |
ctx.invoked_with = invoker | |
ctx.command = self.all_commands.get(invoker) | |
return ctx | |
bot = SuffixBot(command_suffix='!', command_prefix='!') # both the prefix and the suffix will work when invoking commands | |
@bot.command() | |
async def say(ctx, *, words): | |
ret = 'You said \"{}\" with the {} `{}`!' | |
if ctx.prefix is not None: | |
ret = ret.format(words, 'prefix', ctx.prefix) | |
else: | |
ret = ret.format(words, 'suffix', ctx.prefix) | |
await ctx.send(ret) | |
@bot.command() | |
async def ping(ctx): | |
await ctx.send('Pong! {}ms'.format(ctx.bot.latency * 1000)) | |
@bot.command() | |
async def hello(ctx): | |
await ctx.send('Hello! I\'m a bot with command suffixes!') | |
bot.run('token') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment