Closed MaidThatPrograms closed 2 years ago
The only issue I can envisage personally here is how it would appear on the client. From the internal implementation you've described, it sounds like the command would be flooded with optional args of very similar names (I don't believe two can use the same name).
This honestly just sounds like something Discord would need to implement themselves into slash commands.
So, I did a lot more programming since I posted this and have created a fairly duct-tape, but effective method of setting up simple slash only bots. It doesn't have every feature you could possibly want, but it is good for simple bots at least. I put it into a helper file for loading in a few bots I have. Default command values could probably be implemented without too much work. I'm sure this whole thing could be done much better as well.
Bots can be created like bot = create_bot(default_enabled_guilds=GUILD_NUMBER, cache_settings=CacheSettings(max_messages=10000))
for instance.
Slash commands are registered as follows. Returning an object will respond to the context.
@slasher('Does some random thing!')
async def foo(context, message: ('Say what?', str), *users: ('About whom?', User)):
content = ''
for user in users:
content += f'{user.user.username} is {message}!\n'
return content
from collections import defaultdict
from getpass import getpass
from inspect import signature
from lightbulb import BotApp, command, CommandErrorEvent, implements, option
from lightbulb.commands import SlashCommand
from os import name
def slasher(description):
def decorator(function):
@bot.command
@command(function.__name__, description, auto_defer=True)
@implements(SlashCommand)
async def callback(context):
content = await function(context, *filter(None, context.options,_options.values()))
await context.respond(content)
parameters = signature(function).parameters
for name, parameter in parameters.items():
if parameter.annotation != parameter.empty:
option(name, *parameter.annotation)(callback)
if parameter.kind == parameter.VAR_POSITIONAL:
for i in range(2, 27 - len(callback.options)):
option(f'{name}{i}', *parameter.annotation, required=False)(callback)
return decorator
def create_bot(**kwargs):
global bot
bot = BotApp(getpass(), **kwargs)
if name == 'posix':
from uvloop import install
install()
@bot.listen(CommandErrorEvent)
async def on_error(event):
await event.context.respond(event.exception.__cause__)
raise event.exception
return bot
bot = None
It looks like this which can be a little weird, but just pressing enter/tab will automatically select the next user option.
I'd say this is probably more in the scope of Filament. It's essentially a bunch of utility functions and decorators, and already has a load of hella botched solutions in it, so perhaps could be @tandemdude's next illegal coding job d:
In my personal opinion this is too much of a botchy hackjob for Lightbulb, despite it's uses insofar as features that really should be part of slash commands are concerned.
Well, I have an implementation of BotApp that probably doesn't fit into Lightbulb, but I will link it here for potential solutions. https://github.com/ItsPonks/DiscordBots/blob/main/utils.py
Summary
A way to specify variable arguments for slash commands.
Why is this needed?
While splitting a string argument and parsing it manually is not too difficult, using a string argument removes some really nice features of slash commands like auto-complete for most types. It also makes it much harder for a user to specify things like users with names that are not one word or have unusual characters.
Alternatively, manually adding the max number of options to a command and treating it like varargs takes a lot of lines of code and makes it much harder to read. Not to mention, actually collecting every option into a list or tuple for iteration is also very hard to read and tedious.
Ideal implementation
With a decorator like
@lightbulb.option('users', 'A bunch of users', List[hikari.User])
or@lightbulb.option('users', 'A bunch of users', hikari.User, variable_args=True)
. Thencontext.options.users
would have a list of the specified users.Internally, I believe it could function like this. Register the slash command as having 25 optional options of the correct type (or less if other non-variable arguments are given), and collect as many that were input into a list or tuple in the context.
A not very internal workaround I am currently using is as follows for a command
foo
to have variable numbers ofUser
arguments. It would have to be adjusted when collected arguments to only get the ones of typeUser
.Checklist