Skip to content

Commit

Permalink
feat: default select menu values (#9867)
Browse files Browse the repository at this point in the history
* feat: default select menu values

* feat(Message): support

* fix: fix crashes when an array is supplied and remove assertion

* docs(transformResolved): `BaseChannel` is the correct type

* refactor: prefer assignment

* chore: export function again

* fix(Util): fix circular dependency

* refactor(MentionableSelectMenu): clone in method

* docs: remove semicolon

* feat(MentionableSelectMenu): add `addDefaultValues()`

* refactor: reduce overhead

* types: adjust `channel`

---------

Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com>
  • Loading branch information
jaw0r3k and Jiralite committed Nov 12, 2023
1 parent b5e23ec commit 4ff3ea4
Show file tree
Hide file tree
Showing 10 changed files with 320 additions and 73 deletions.
50 changes: 46 additions & 4 deletions packages/builders/src/components/selectMenu/ChannelSelectMenu.ts
@@ -1,7 +1,12 @@
import type { APIChannelSelectComponent, ChannelType } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import { normalizeArray, type RestOrArray } from '../../util/normalizeArray.js';
import { channelTypesValidator, customIdValidator } from '../Assertions.js';
import {
type APIChannelSelectComponent,
type ChannelType,
type Snowflake,
ComponentType,
SelectMenuDefaultValueType,
} from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { channelTypesValidator, customIdValidator, optionsLengthValidator } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';

/**
Expand Down Expand Up @@ -59,6 +64,43 @@ export class ChannelSelectMenuBuilder extends BaseSelectMenuBuilder<APIChannelSe
return this;
}

/**
* Adds default channels to this auto populated select menu.
*
* @param channels - The channels to add
*/
public addDefaultChannels(...channels: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(channels);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];

this.data.default_values.push(
...normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.Channel as const,
})),
);

return this;
}

/**
* Sets default channels to this auto populated select menu.
*
* @param channels - The channels to set
*/
public setDefaultChannels(...channels: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(channels);
optionsLengthValidator.parse(normalizedValues.length);

this.data.default_values = normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.Channel as const,
}));

return this;
}

/**
* {@inheritDoc BaseSelectMenuBuilder.toJSON}
*/
Expand Down
@@ -1,5 +1,12 @@
import type { APIMentionableSelectComponent } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import {
type APIMentionableSelectComponent,
type APISelectMenuDefaultValue,
type Snowflake,
ComponentType,
SelectMenuDefaultValueType,
} from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { optionsLengthValidator } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';

/**
Expand Down Expand Up @@ -31,4 +38,79 @@ export class MentionableSelectMenuBuilder extends BaseSelectMenuBuilder<APIMenti
public constructor(data?: Partial<APIMentionableSelectComponent>) {
super({ ...data, type: ComponentType.MentionableSelect });
}

/**
* Adds default roles to this auto populated select menu.
*
* @param roles - The roles to add
*/
public addDefaultRoles(...roles: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(roles);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];

this.data.default_values.push(
...normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.Role as const,
})),
);

return this;
}

/**
* Adds default users to this auto populated select menu.
*
* @param users - The users to add
*/
public addDefaultUsers(...users: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(users);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];

this.data.default_values.push(
...normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.User as const,
})),
);

return this;
}

/**
* Adds default values to this auto populated select menu.
*
* @param values - The values to add
*/
public addDefaultValues(
...values: RestOrArray<
| APISelectMenuDefaultValue<SelectMenuDefaultValueType.Role>
| APISelectMenuDefaultValue<SelectMenuDefaultValueType.User>
>
) {
const normalizedValues = normalizeArray(values);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];
this.data.default_values.push(...normalizedValues);
return this;
}

/**
* Sets default values to this auto populated select menu.
*
* @param values - The values to set
*/
public setDefaultValues(
...values: RestOrArray<
| APISelectMenuDefaultValue<SelectMenuDefaultValueType.Role>
| APISelectMenuDefaultValue<SelectMenuDefaultValueType.User>
>
) {
const normalizedValues = normalizeArray(values);
optionsLengthValidator.parse(normalizedValues.length);
this.data.default_values = normalizedValues.slice();
return this;
}
}
47 changes: 45 additions & 2 deletions packages/builders/src/components/selectMenu/RoleSelectMenu.ts
@@ -1,5 +1,11 @@
import type { APIRoleSelectComponent } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import {
type APIRoleSelectComponent,
type Snowflake,
ComponentType,
SelectMenuDefaultValueType,
} from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { optionsLengthValidator } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';

/**
Expand Down Expand Up @@ -31,4 +37,41 @@ export class RoleSelectMenuBuilder extends BaseSelectMenuBuilder<APIRoleSelectCo
public constructor(data?: Partial<APIRoleSelectComponent>) {
super({ ...data, type: ComponentType.RoleSelect });
}

/**
* Adds default roles to this auto populated select menu.
*
* @param roles - The roles to add
*/
public addDefaultRoles(...roles: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(roles);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];

this.data.default_values.push(
...normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.Role as const,
})),
);

return this;
}

/**
* Sets default roles to this auto populated select menu.
*
* @param roles - The roles to set
*/
public setDefaultRoles(...roles: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(roles);
optionsLengthValidator.parse(normalizedValues.length);

this.data.default_values = normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.Role as const,
}));

return this;
}
}
47 changes: 45 additions & 2 deletions packages/builders/src/components/selectMenu/UserSelectMenu.ts
@@ -1,5 +1,11 @@
import type { APIUserSelectComponent } from 'discord-api-types/v10';
import { ComponentType } from 'discord-api-types/v10';
import {
type APIUserSelectComponent,
type Snowflake,
ComponentType,
SelectMenuDefaultValueType,
} from 'discord-api-types/v10';
import { type RestOrArray, normalizeArray } from '../../util/normalizeArray.js';
import { optionsLengthValidator } from '../Assertions.js';
import { BaseSelectMenuBuilder } from './BaseSelectMenu.js';

/**
Expand Down Expand Up @@ -31,4 +37,41 @@ export class UserSelectMenuBuilder extends BaseSelectMenuBuilder<APIUserSelectCo
public constructor(data?: Partial<APIUserSelectComponent>) {
super({ ...data, type: ComponentType.UserSelect });
}

/**
* Adds default users to this auto populated select menu.
*
* @param users - The users to add
*/
public addDefaultUsers(...users: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(users);
optionsLengthValidator.parse((this.data.default_values?.length ?? 0) + normalizedValues.length);
this.data.default_values ??= [];

this.data.default_values.push(
...normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.User as const,
})),
);

return this;
}

/**
* Sets default users to this auto populated select menu.
*
* @param users - The users to set
*/
public setDefaultUsers(...users: RestOrArray<Snowflake>) {
const normalizedValues = normalizeArray(users);
optionsLengthValidator.parse(normalizedValues.length);

this.data.default_values = normalizedValues.map((id) => ({
id,
type: SelectMenuDefaultValueType.User as const,
}));

return this;
}
}
Expand Up @@ -2,6 +2,7 @@

const CommandInteraction = require('./CommandInteraction');
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
const { transformResolved } = require('../util/Util');

/**
* Represents a command interaction.
Expand All @@ -18,7 +19,7 @@ class ChatInputCommandInteraction extends CommandInteraction {
this.options = new CommandInteractionOptionResolver(
this.client,
data.data.options?.map(option => this.transformOption(option, data.data.resolved)) ?? [],
this.transformResolved(data.data.resolved ?? {}),
transformResolved({ client: this.client, guild: this.guild, channel: this.channel }, data.data.resolved),
);
}

Expand Down
57 changes: 0 additions & 57 deletions packages/discord.js/src/structures/CommandInteraction.js
@@ -1,6 +1,5 @@
'use strict';

const { Collection } = require('@discordjs/collection');
const Attachment = require('./Attachment');
const BaseInteraction = require('./BaseInteraction');
const InteractionWebhook = require('./InteractionWebhook');
Expand Down Expand Up @@ -91,62 +90,6 @@ class CommandInteraction extends BaseInteraction {
* @property {Collection<Snowflake, Attachment>} [attachments] The resolved attachments
*/

/**
* Transforms the resolved received from the API.
* @param {APIInteractionDataResolved} resolved The received resolved objects
* @returns {CommandInteractionResolvedData}
* @private
*/
transformResolved({ members, users, channels, roles, messages, attachments }) {
const result = {};

if (members) {
result.members = new Collection();
for (const [id, member] of Object.entries(members)) {
const user = users[id];
result.members.set(id, this.guild?.members._add({ user, ...member }) ?? member);
}
}

if (users) {
result.users = new Collection();
for (const user of Object.values(users)) {
result.users.set(user.id, this.client.users._add(user));
}
}

if (roles) {
result.roles = new Collection();
for (const role of Object.values(roles)) {
result.roles.set(role.id, this.guild?.roles._add(role) ?? role);
}
}

if (channels) {
result.channels = new Collection();
for (const channel of Object.values(channels)) {
result.channels.set(channel.id, this.client.channels._add(channel, this.guild) ?? channel);
}
}

if (messages) {
result.messages = new Collection();
for (const message of Object.values(messages)) {
result.messages.set(message.id, this.channel?.messages?._add(message) ?? message);
}
}

if (attachments) {
result.attachments = new Collection();
for (const attachment of Object.values(attachments)) {
const patched = new Attachment(attachment);
result.attachments.set(attachment.id, patched);
}
}

return result;
}

/**
* Represents an option of a received command interaction.
* @typedef {Object} CommandInteractionOption
Expand Down
Expand Up @@ -4,6 +4,7 @@ const { lazy } = require('@discordjs/util');
const { ApplicationCommandOptionType } = require('discord-api-types/v10');
const CommandInteraction = require('./CommandInteraction');
const CommandInteractionOptionResolver = require('./CommandInteractionOptionResolver');
const { transformResolved } = require('../util/Util');

const getMessage = lazy(() => require('./Message').Message);

Expand All @@ -21,7 +22,7 @@ class ContextMenuCommandInteraction extends CommandInteraction {
this.options = new CommandInteractionOptionResolver(
this.client,
this.resolveContextMenuOptions(data.data),
this.transformResolved(data.data.resolved),
transformResolved({ client: this.client, guild: this.guild, channel: this.channel }, data.data.resolved),
);

/**
Expand Down

0 comments on commit 4ff3ea4

Please sign in to comment.