-
-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: SlashCommandOption#setType and SharedSlashCommandOptions#addOption #10491
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎ 2 Skipped Deployments
|
You should use the callback syntax if importing the classes is too "tedious" |
I think that the callback syntax is too confusing. I like to create all the options separate and attach them to their subcommand. |
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #10491 +/- ##
==========================================
- Coverage 35.45% 35.39% -0.06%
==========================================
Files 228 229 +1
Lines 14319 14404 +85
Branches 1254 1255 +1
==========================================
+ Hits 5077 5099 +22
- Misses 9198 9261 +63
Partials 44 44
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
Could you elaborate on why is that? Maybe we could look into addressing this pain point instead. Here's a slightly cut-down snippet from my current code. There's a single import from import { SlashCommandBuilder } from 'discord.js';
const deployData = new SlashCommandBuilder()
.setName(commandName)
.setDescription('Fix something')
.addSubcommandGroup(group => { // member
return group
.setName('member')
.setDescription('Fix Member')
.addSubcommand(sub => { // separator-roles
return sub
.setName('separator-roles')
.setDescription('Fix member\'s separator roles')
.addUserOption(option => { // member
return option
.setName('member')
.setDescription('Member whose roles to fix')
.setRequired(true);
})
.addBooleanOption(option => { // show
return option
.setName('show')
.setDescription('Should the response be visible?');
});
});
}); Admittedly, I agree that I could switch this to implicit return instead, but using explicit one makes this look better for me. Not like it matters overall though, this code was written once and collapsed both within the blocks (that's why there are name comments on the block starts), as well as with a If you're still curious about what else this 537 LOC file imports from discord.js, here:import {
ButtonInteraction,
ChannelType,
ChatInputCommandInteraction,
Collection,
EmbedBuilder,
GuildBasedChannel,
GuildMember,
PermissionFlagsBits,
PermissionsString,
PrivateThreadChannel,
PublicThreadChannel,
RoleMention,
SlashCommandBuilder,
Snowflake,
} from 'discord.js'; As far as I'm aware, you would not be cutting anything out of this list regardless of how much you'd change up the Builder. By using JSON object directly instead of |
My issue with that is mainly the same reasons I prefer using async/await over callbacks. It results in a giant "pyramid" that becomes very hard to read the more subcommands/options you add. The way I do it by separating the options and attaching them later is much easier to read at a glance for me. It's easier to change/remove individual options without going into the pyramid and trying to figure out which block corresponds to which option. const showOption = new SlashCommandOption()
.setType("boolean")
.setName('show')
.setDescription('Should the response be visible?');
const memberOption = new SlashCommandOption()
.setType("user")
.setName('member')
.setDescription('Member whose roles to fix')
.setRequired(true)
const sub = new SlashCommandSubcommandBuilder()
.setName('separator-roles')
.setDescription('Fix member\'s separator roles')
.addOption(memberOption)
.addOption(showOption)
const group = new SlashCommandSubcommandGroupBuilder()
.setName('member')
.setDescription('Fix Member')
.addSubcommand(sub)
const deployData = new SlashCommandBuilder()
.setName(commandName)
.setDescription('Fix something')
.addSubcommandGroup(group) |
This seems like an awfully convoluted PR to support a single use case that's based on style preference. Builders exist to ease people into the concept, provide autocomplete and optionally validation. |
You can still achieve something similar tho. Consider the following snippet; const command = new SlashCommandBuilder()
.setName("name")
.setDescription("description");
// add a boolean option
command.addBooleanOption(option => option.setName("bool").setDescription("desc"))
// add string option
command.addStringOption(...)
// or for a subcommand
const sub = new SlashCommandSubcommnadBuilder()
.setName(...)
.setDescription(...);
// add an option to it
sub.addX(...)
sub.addY(...)
// add subcommand to the command
command.addSubcommand(sub);
// similary for subcommand group There's nothing "pyramind" going here |
Souji basically said my feelings about this PR in its current form. I also don't understand why exactly you have an extra function to do something that can be done by just exporting an object with all builders in it and using that (not that it'd make my feelings towards this much better) Ultimately, if this is something others want I will fold, but imo this is one of those abstractions better fit in your own code. Or just split your declarations up like shown in the previous comment by @imnaiyar. |
Ok I found the interface I am happy with which is to export a This is what usage looks like: const questionOption = new SlashCommandOption()
.setType("string")
.setName("question")
.setDescription("question")
.setRequired(true) I should note that the linter formats the code very weirdly and ruins the spacing on the conditional types. I tried to fix the spacing but it made the test fail so I just left it that way. If you want me to be honest, I don't really get the point of having so many type classes when the code between them is 90% similar and you could easily combine them into one class. I think there's a lot of unnecessary duplication in the code. However, I got very lukewarm responses to combining them in the discord so this is basically my way of still getting the functionality I wanted without a breaking change. Yes, I could just implement this into my code. I already did that. But by opening this PR, I hoped that my solution could be helpful to anyone else in the community by being integrated into the library. Am I really the only one that found importing all the option type classes a bit tedious?.. |
It was already explained to you multiple times. On top of the maintenance concerns, you legitimately wouldn't even be able to combine them completely because type-guarding away methods based off of a generic is a nightmare-ish task that probably makes you create separate classes internally anyway, even if to the user it seems like one.
Yes, use a modern code editor that imports for you automatically, or, for the 10th time, use the callback API. You're not fixing a legitimate problem with this PR or improving DX so much to the point where it's worth the tech debt you're introducing, which, might I add, is more than builders has seen in its entire lifetime. I, much like @vladfrangu and @almostSouji am against this. |
The library provides two separate methods of adding slash command options: 1) importing the type classes and 2) using the My main question is... why do I have to import so many different types? Exporting a simple helper to get any of them is a basic functionality (that should preferably be implemented by the library, not by me). I don't feel like the issue is so much maintainability, but a clash of style preference. I don't like to use the callback syntax or import so many classes in my code, and this is a suitable solution for me. But it seems like no one else likes my style so they don't want to add this. Claiming that the issue is maintainability when I only added one more file you have to modify just doesn't seem right to me. Is it really that difficult to add another slot to the conditional types when (once every blue moon) discord adds a new option type? And eventually they will stop adding more option types, there are only so many that they can add. Interface changes like adding a new method to an option type will not break my code. |
Builders is opinionated. Wherever we provide multiple ways to do something it's because we think it's an improvement to the developer experience that doesn't compromise the set of patterns we've adopted. Your proposed changes do not fit the patterns in question at all, so it's not even an issue of "style", but rather consistency. Even if this wasn't a consideration, your DX improvement here is "you don't have to import classes anymore", which we already provide a solution to. "I don't like your solution" is not a good enough reason to add another one, especially with how convoluted it is. I'm not even certain why you're using builders, it seems like you would benefit from raw objects a lot more. As per your comment on maintainability, I've raised multiple points that you haven't addressed & there's more to it than that anyway. |
If complexity is really your concern then I can export a simple object as someone mentioned before. I prefer the call interface less, but on your end it does remove the need for conditional types. export const SlashCommandOption = {
attachment: SlashCommandAttachmentOption,
boolean: SlashCommandBooleanOption,
channel: SlashCommandChannelOption,
integer: SlashCommandIntegerOption,
mentionable: SlashCommandMentionableOption,
number: SlashCommandNumberOption,
role: SlashCommandRoleOption,
string: SlashCommandStringOption,
user: SlashCommandUserOption
} But something tells me that it wouldn't make you happy because maintainability wasn't the real issue you had. You mentioned consistency but I looked at the rest of the builders and I just find the consistency to be... kinda off? I expect each of the builders to each follow the same interface designs but it seems like every single builder is doing things their own way. This is how you add things to an const buttonRow = new ActionRowBuilder<ButtonBuilder>()
buttonRow.addComponents(button1, button2, button3) The const option1 = new StringSelectMenuOptionBuilder()
.setLabel("label")
.setValue("value")
const select = new StringSelectMenuBuilder()
.addOptions(option1) Then there is a weird choice of renaming The To me it looks like adding a |
You're now back-pedaling and looking for straws to pick. I thought I was having a good faith conversation & went out of my way to tell you why your changes are not in-line with our vision for the package.
Because that'd make for a bad API that encourages more complex code updates when, say, changing the type of an option. When I cite "consistency" this doesn't mean everything is going to be the exact same without regard for other factors. For example, if we were to introduce more builders, one which takes a bunch of "options" of the same type (e.g. a bunch of strings), and one which is similar to the slash command options, then the former would be consistent with the string select menu builder, while the latter would be consistent with the slash command options.
Quote from the package description:
Not sure where else you'd want this to be? Of course all the other builders are related to interactions, because the API doesn't really have any other constructs that mandate a builder... The rename is not at all a "weird choice".
Yes, it's not really a builder, it's poorly named, and quite frankly, there are issues with it, we're already aware of this and addressing it after #10448 Multiple maintainers have come out of their way to provide you with solutions and explanations for why we're not happy with this pull request. We tried having a conversation (as opposed to simply closing the PR with a single-sentence response), but we were met the idea that this is some sort of egoistical behavior on our part. Also worth quoting this part that you edited it out
This is honestly deeply inappropriate and bordering a violation of our code of conduct. Your arguments have left me unconvinced about these changes and I believe at this point we'll just be going in circles if we continue, so I'll be resigning from this conversation. If other maintainers wish to go further with this, great. |
Thanks for the suggestion. As various people have iterated on above we do not think this is feasible to introduce on the library level and are not going forward with a merge. |
I expressed my dissatisfaction using all the import classes and the callback interface, but if you don't want to add my solution there isn't much to be helped. I will implement it into my own code. |
Please describe the changes this PR makes and why it should be merged:
The problem:
It has been tedious for me to add SlashCommandOption's with every type being it's own class. This usually results in bloated imports that must be changed between commands
To improve ease of use, I propose adding two changes:
SlashCommandOption
helper class that can be set to any type in.setType()
SharedSlashCommandOptions#addOption
that supports adding option of any typeNow with my proposed changes, the code will look like this:
SlashCommandOption#setType
supports passing a string orApplicationCommandOptionType
.This also works:
No breaking changes
This PR does not rename or remove any old methods so it doesn't have breaking changes. If people prefer the old method they can continue to use it.
Use cases
I believe this makes it easier for me to create SlashCommandOptions and quickly copy code over to a different command without having to deal with changing imports and methods.
Status and versioning classification: