mirror of
https://github.com/discordjs/discord.js.git
synced 2026-03-09 16:13:31 +01:00
feat: Add Modals and Text Inputs (#7023)
Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com> Co-authored-by: Jiralite <33201955+Jiralite@users.noreply.github.com> Co-authored-by: Ryan Munro <monbrey@gmail.com> Co-authored-by: Vitor <milagre.vitor@gmail.com>
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { APIActionRowComponent, APIMessageComponent, ButtonStyle, ComponentType } from 'discord-api-types/v9';
|
||||
import {
|
||||
APIActionRowComponent,
|
||||
APIActionRowComponentTypes,
|
||||
APIMessageActionRowComponent,
|
||||
ButtonStyle,
|
||||
ComponentType,
|
||||
} from 'discord-api-types/v9';
|
||||
import { ActionRow, ButtonComponent, createComponent, SelectMenuComponent, SelectMenuOption } from '../../src';
|
||||
|
||||
const rowWithButtonData: APIActionRowComponent<APIMessageComponent> = {
|
||||
@@ -43,7 +49,7 @@ describe('Action Row Components', () => {
|
||||
});
|
||||
|
||||
test('GIVEN valid JSON input THEN valid JSON output is given', () => {
|
||||
const actionRowData: APIActionRowComponent<APIMessageComponent> = {
|
||||
const actionRowData: APIActionRowComponent<APIMessageActionRowComponent> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
@@ -75,10 +81,43 @@ describe('Action Row Components', () => {
|
||||
expect(new ActionRow(actionRowData).toJSON()).toEqual(actionRowData);
|
||||
expect(new ActionRow().toJSON()).toEqual({ type: ComponentType.ActionRow, components: [] });
|
||||
expect(() => createComponent({ type: ComponentType.ActionRow, components: [] })).not.toThrowError();
|
||||
// @ts-expect-error
|
||||
expect(() => createComponent({ type: 42, components: [] })).toThrowError();
|
||||
});
|
||||
test('GIVEN valid builder options THEN valid JSON output is given', () => {
|
||||
const rowWithButtonData: APIActionRowComponent<APIActionRowComponentTypes> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.Button,
|
||||
label: 'test',
|
||||
custom_id: '123',
|
||||
style: ButtonStyle.Primary,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const rowWithSelectMenuData: APIActionRowComponent<APIActionRowComponentTypes> = {
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.SelectMenu,
|
||||
custom_id: '1234',
|
||||
options: [
|
||||
{
|
||||
label: 'one',
|
||||
value: 'one',
|
||||
},
|
||||
{
|
||||
label: 'two',
|
||||
value: 'two',
|
||||
},
|
||||
],
|
||||
max_values: 10,
|
||||
min_values: 12,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const button = new ButtonComponent().setLabel('test').setStyle(ButtonStyle.Primary).setCustomId('123');
|
||||
const selectMenu = new SelectMenuComponent()
|
||||
.setCustomId('1234')
|
||||
|
||||
126
packages/builders/__tests__/components/textInput.test.ts
Normal file
126
packages/builders/__tests__/components/textInput.test.ts
Normal file
@@ -0,0 +1,126 @@
|
||||
import { APITextInputComponent, ComponentType, TextInputStyle } from 'discord-api-types/v9';
|
||||
import {
|
||||
labelValidator,
|
||||
maxLengthValidator,
|
||||
minLengthValidator,
|
||||
placeholderValidator,
|
||||
valueValidator,
|
||||
textInputStyleValidator,
|
||||
} from '../../src/components/textInput/Assertions';
|
||||
import { TextInputComponent } from '../../src/components/textInput/TextInput';
|
||||
|
||||
const superLongStr = 'a'.repeat(5000);
|
||||
|
||||
const textInputComponent = () => new TextInputComponent();
|
||||
|
||||
describe('Text Input Components', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid label THEN validator does not throw', () => {
|
||||
expect(() => labelValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid label THEN validator does throw', () => {
|
||||
expect(() => labelValidator.parse(24)).toThrowError();
|
||||
expect(() => labelValidator.parse(undefined)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid style THEN validator does not throw', () => {
|
||||
expect(() => textInputStyleValidator.parse(TextInputStyle.Paragraph)).not.toThrowError();
|
||||
expect(() => textInputStyleValidator.parse(TextInputStyle.Short)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid style THEN validator does throw', () => {
|
||||
expect(() => textInputStyleValidator.parse(24)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid min length THEN validator does not throw', () => {
|
||||
expect(() => minLengthValidator.parse(10)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid min length THEN validator does throw', () => {
|
||||
expect(() => minLengthValidator.parse(-1)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid max length THEN validator does not throw', () => {
|
||||
expect(() => maxLengthValidator.parse(10)).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid min length THEN validator does throw', () => {
|
||||
expect(() => maxLengthValidator.parse(4001)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid value THEN validator does not throw', () => {
|
||||
expect(() => valueValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid value THEN validator does throw', () => {
|
||||
expect(() => valueValidator.parse(superLongStr)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid placeholder THEN validator does not throw', () => {
|
||||
expect(() => placeholderValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid value THEN validator does throw', () => {
|
||||
expect(() => placeholderValidator.parse(superLongStr)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid fields THEN builder does not throw', () => {
|
||||
expect(() => {
|
||||
textInputComponent().setCustomId('foobar').setLabel('test').setStyle(TextInputStyle.Paragraph).toJSON();
|
||||
}).not.toThrowError();
|
||||
|
||||
expect(() => {
|
||||
textInputComponent()
|
||||
.setCustomId('foobar')
|
||||
.setLabel('test')
|
||||
.setMaxLength(100)
|
||||
.setMinLength(1)
|
||||
.setPlaceholder('bar')
|
||||
.setRequired(true)
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.toJSON();
|
||||
}).not.toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN invalid fields THEN builder throws', () => {
|
||||
expect(() => textInputComponent().toJSON()).toThrowError();
|
||||
expect(() => {
|
||||
textInputComponent()
|
||||
.setCustomId('test')
|
||||
.setMaxLength(100)
|
||||
.setPlaceholder('hello')
|
||||
.setStyle(TextInputStyle.Paragraph)
|
||||
.toJSON();
|
||||
}).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid input THEN valid JSON outputs are given', () => {
|
||||
const textInputData: APITextInputComponent = {
|
||||
type: ComponentType.TextInput,
|
||||
label: 'label',
|
||||
custom_id: 'custom id',
|
||||
placeholder: 'placeholder',
|
||||
max_length: 100,
|
||||
min_length: 10,
|
||||
value: 'value',
|
||||
required: false,
|
||||
style: TextInputStyle.Paragraph,
|
||||
};
|
||||
|
||||
expect(new TextInputComponent(textInputData).toJSON()).toEqual(textInputData);
|
||||
expect(
|
||||
textInputComponent()
|
||||
.setCustomId(textInputData.custom_id)
|
||||
.setLabel(textInputData.label)
|
||||
.setPlaceholder(textInputData.placeholder)
|
||||
.setMaxLength(textInputData.max_length)
|
||||
.setMinLength(textInputData.min_length)
|
||||
.setValue(textInputData.value)
|
||||
.setRequired(textInputData.required)
|
||||
.setStyle(textInputData.style)
|
||||
.toJSON(),
|
||||
).toEqual(textInputData);
|
||||
});
|
||||
});
|
||||
88
packages/builders/__tests__/interactions/modal.test.ts
Normal file
88
packages/builders/__tests__/interactions/modal.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { APIModalInteractionResponseCallbackData, ComponentType, TextInputStyle } from 'discord-api-types/v9';
|
||||
import { ActionRow, ButtonComponent, Modal, ModalActionRowComponent, TextInputComponent } from '../../src';
|
||||
import {
|
||||
componentsValidator,
|
||||
titleValidator,
|
||||
validateRequiredParameters,
|
||||
} from '../../src/interactions/modals/Assertions';
|
||||
|
||||
const modal = () => new Modal();
|
||||
|
||||
describe('Modals', () => {
|
||||
describe('Assertion Tests', () => {
|
||||
test('GIVEN valid title THEN validator does not throw', () => {
|
||||
expect(() => titleValidator.parse('foobar')).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid title THEN validator does throw', () => {
|
||||
expect(() => titleValidator.parse(42)).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid components THEN validator does not throw', () => {
|
||||
expect(() => componentsValidator.parse([new ActionRow(), new ActionRow()])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid components THEN validator does throw', () => {
|
||||
expect(() => componentsValidator.parse([new ButtonComponent(), new TextInputComponent()])).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid required parameters THEN validator does not throw', () => {
|
||||
expect(() => validateRequiredParameters('123', 'title', [new ActionRow(), new ActionRow()])).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid required parameters THEN validator does throw', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
validateRequiredParameters('123', undefined, [new ActionRow(), new ButtonComponent()]),
|
||||
).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
test('GIVEN valid fields THEN builder does not throw', () => {
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').setComponents(new ActionRow())).not.toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN invalid fields THEN builder does throw', () => {
|
||||
expect(() =>
|
||||
// @ts-expect-error
|
||||
modal().setTitle('test').setCustomId('foobar').setComponents([new ActionRow()]).toJSON(),
|
||||
).toThrowError();
|
||||
expect(() => modal().setTitle('test').setCustomId('foobar').toJSON()).toThrowError();
|
||||
// @ts-expect-error
|
||||
expect(() => modal().setTitle('test').setCustomId(42).toJSON()).toThrowError();
|
||||
});
|
||||
|
||||
test('GIVEN valid input THEN valid JSON outputs are given', () => {
|
||||
const modalData: APIModalInteractionResponseCallbackData = {
|
||||
title: 'title',
|
||||
custom_id: 'custom id',
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
type: ComponentType.TextInput,
|
||||
label: 'label',
|
||||
style: TextInputStyle.Paragraph,
|
||||
custom_id: 'custom id',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
expect(new Modal(modalData).toJSON()).toEqual(modalData);
|
||||
|
||||
expect(
|
||||
modal()
|
||||
.setTitle(modalData.title)
|
||||
.setCustomId('custom id')
|
||||
.setComponents(
|
||||
new ActionRow<ModalActionRowComponent>().addComponents(
|
||||
new TextInputComponent().setCustomId('custom id').setLabel('label').setStyle(TextInputStyle.Paragraph),
|
||||
),
|
||||
)
|
||||
.toJSON(),
|
||||
).toEqual(modalData);
|
||||
});
|
||||
});
|
||||
@@ -52,7 +52,7 @@
|
||||
"homepage": "https://discord.js.org",
|
||||
"dependencies": {
|
||||
"@sindresorhus/is": "^4.4.0",
|
||||
"discord-api-types": "^0.27.0",
|
||||
"discord-api-types": "^0.27.3",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"ts-mixer": "^6.0.0",
|
||||
"tslib": "^2.3.1",
|
||||
|
||||
@@ -1,26 +1,42 @@
|
||||
import { type APIActionRowComponent, ComponentType, APIMessageComponent } from 'discord-api-types/v9';
|
||||
import type { ButtonComponent, SelectMenuComponent } from '..';
|
||||
import {
|
||||
APIActionRowComponent,
|
||||
APIMessageActionRowComponent,
|
||||
APIModalActionRowComponent,
|
||||
ComponentType,
|
||||
} from 'discord-api-types/v9';
|
||||
import type { ButtonComponent, SelectMenuComponent, TextInputComponent } from '../index';
|
||||
import { Component } from './Component';
|
||||
import { createComponent } from './Components';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
|
||||
export type MessageComponent = ActionRowComponent | ActionRow;
|
||||
export type MessageComponent = MessageActionRowComponent | ActionRow<MessageActionRowComponent>;
|
||||
export type ModalComponent = ModalActionRowComponent | ActionRow<ModalActionRowComponent>;
|
||||
|
||||
export type ActionRowComponent = ButtonComponent | SelectMenuComponent;
|
||||
export type MessageActionRowComponent = ButtonComponent | SelectMenuComponent;
|
||||
export type ModalActionRowComponent = TextInputComponent;
|
||||
|
||||
// TODO: Add valid form component types
|
||||
/**
|
||||
* Represents an action row component
|
||||
*/
|
||||
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extends Component<
|
||||
Omit<Partial<APIActionRowComponent<APIMessageComponent>> & { type: ComponentType.ActionRow }, 'components'>
|
||||
export class ActionRow<
|
||||
T extends ModalActionRowComponent | MessageActionRowComponent = ModalActionRowComponent | MessageActionRowComponent,
|
||||
> extends Component<
|
||||
Omit<
|
||||
Partial<APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>> & {
|
||||
type: ComponentType.ActionRow;
|
||||
},
|
||||
'components'
|
||||
>
|
||||
> {
|
||||
/**
|
||||
* The components within this action row
|
||||
*/
|
||||
public readonly components: T[];
|
||||
|
||||
public constructor({ components, ...data }: Partial<APIActionRowComponent<APIMessageComponent>> = {}) {
|
||||
public constructor({
|
||||
components,
|
||||
...data
|
||||
}: Partial<APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>> = {}) {
|
||||
super({ type: ComponentType.ActionRow, ...data });
|
||||
this.components = (components?.map((c) => createComponent(c)) ?? []) as T[];
|
||||
}
|
||||
@@ -44,14 +60,14 @@ export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extend
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIActionRowComponent<APIMessageComponent> {
|
||||
public toJSON(): APIActionRowComponent<ReturnType<T['toJSON']>> {
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
components: this.components.map((component) => component.toJSON()) as ReturnType<T['toJSON']>[],
|
||||
};
|
||||
}
|
||||
|
||||
public equals(other: APIActionRowComponent<APIMessageComponent> | ActionRow) {
|
||||
public equals(other: APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent> | ActionRow) {
|
||||
if (other instanceof ActionRow) {
|
||||
return isEqual(other.data, this.data) && isEqual(other.components, this.components);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { JSONEncodable } from '../util/jsonEncodable';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIActionRowComponentTypes,
|
||||
APIBaseComponent,
|
||||
APIMessageActionRowComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIMessageComponent,
|
||||
ComponentType,
|
||||
APIModalComponent,
|
||||
} from 'discord-api-types/v9';
|
||||
import type { Equatable } from '../util/equatable';
|
||||
|
||||
@@ -14,16 +18,33 @@ export abstract class Component<
|
||||
DataType extends Partial<APIBaseComponent<ComponentType>> & {
|
||||
type: ComponentType;
|
||||
} = APIBaseComponent<ComponentType>,
|
||||
> implements JSONEncodable<APIMessageComponent>, Equatable<Component | APIActionRowComponentTypes>
|
||||
> implements
|
||||
JSONEncodable<
|
||||
| APIModalComponent
|
||||
| APIMessageComponent
|
||||
| APIActionRowComponent<APIModalActionRowComponent | APIMessageActionRowComponent>
|
||||
>,
|
||||
Equatable<
|
||||
| Component
|
||||
| APIActionRowComponentTypes
|
||||
| APIActionRowComponent<APIModalActionRowComponent | APIMessageActionRowComponent>
|
||||
>
|
||||
{
|
||||
/**
|
||||
* The API data associated with this component
|
||||
*/
|
||||
public readonly data: DataType;
|
||||
|
||||
public abstract toJSON(): APIMessageComponent;
|
||||
public abstract toJSON():
|
||||
| APIActionRowComponentTypes
|
||||
| APIActionRowComponent<APIModalActionRowComponent | APIMessageActionRowComponent>;
|
||||
|
||||
public abstract equals(other: Component | APIActionRowComponentTypes): boolean;
|
||||
public abstract equals(
|
||||
other:
|
||||
| Component
|
||||
| APIActionRowComponentTypes
|
||||
| APIActionRowComponent<APIModalActionRowComponent | APIMessageActionRowComponent>,
|
||||
): boolean;
|
||||
|
||||
public constructor(data: DataType) {
|
||||
this.data = data;
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { APIMessageComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import { ActionRow, ButtonComponent, Component, SelectMenuComponent } from '../index';
|
||||
import type { MessageComponent } from './ActionRow';
|
||||
import { APIBaseComponent, APIMessageComponent, APIModalComponent, ComponentType } from 'discord-api-types/v9';
|
||||
import { ActionRow, ButtonComponent, Component, SelectMenuComponent, TextInputComponent } from '../index';
|
||||
import type { MessageComponent, ModalActionRowComponent } from './ActionRow';
|
||||
|
||||
export interface MappedComponentTypes {
|
||||
[ComponentType.ActionRow]: ActionRow;
|
||||
[ComponentType.Button]: ButtonComponent;
|
||||
[ComponentType.SelectMenu]: SelectMenuComponent;
|
||||
[ComponentType.TextInput]: TextInputComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -13,10 +14,10 @@ export interface MappedComponentTypes {
|
||||
* @param data The api data to transform to a component class
|
||||
*/
|
||||
export function createComponent<T extends keyof MappedComponentTypes>(
|
||||
data: APIMessageComponent & { type: T },
|
||||
data: (APIMessageComponent | APIModalComponent) & { type: T },
|
||||
): MappedComponentTypes[T];
|
||||
export function createComponent<C extends MessageComponent>(data: C): C;
|
||||
export function createComponent(data: APIMessageComponent | MessageComponent): Component {
|
||||
export function createComponent<C extends MessageComponent | ModalActionRowComponent>(data: C): C;
|
||||
export function createComponent(data: APIModalComponent | APIMessageComponent | Component): Component {
|
||||
if (data instanceof Component) {
|
||||
return data;
|
||||
}
|
||||
@@ -28,8 +29,9 @@ export function createComponent(data: APIMessageComponent | MessageComponent): C
|
||||
return new ButtonComponent(data);
|
||||
case ComponentType.SelectMenu:
|
||||
return new SelectMenuComponent(data);
|
||||
case ComponentType.TextInput:
|
||||
return new TextInputComponent(data);
|
||||
default:
|
||||
// @ts-expect-error
|
||||
throw new Error(`Cannot serialize component type: ${data.type as number}`);
|
||||
throw new Error(`Cannot serialize component type: ${(data as APIBaseComponent<ComponentType>).type}`);
|
||||
}
|
||||
}
|
||||
|
||||
17
packages/builders/src/components/textInput/Assertions.ts
Normal file
17
packages/builders/src/components/textInput/Assertions.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { TextInputStyle } from 'discord-api-types/v9';
|
||||
import { z } from 'zod';
|
||||
import { customIdValidator } from '../Assertions';
|
||||
|
||||
export const textInputStyleValidator = z.nativeEnum(TextInputStyle);
|
||||
export const minLengthValidator = z.number().int().min(0).max(4000);
|
||||
export const maxLengthValidator = z.number().int().min(1).max(4000);
|
||||
export const requiredValidator = z.boolean();
|
||||
export const valueValidator = z.string().max(4000);
|
||||
export const placeholderValidator = z.string().max(100);
|
||||
export const labelValidator = z.string().min(1).max(45);
|
||||
|
||||
export function validateRequiredParameters(customId?: string, style?: TextInputStyle, label?: string) {
|
||||
customIdValidator.parse(customId);
|
||||
textInputStyleValidator.parse(style);
|
||||
labelValidator.parse(label);
|
||||
}
|
||||
37
packages/builders/src/components/textInput/TextInput.ts
Normal file
37
packages/builders/src/components/textInput/TextInput.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import type { APITextInputComponent } from 'discord-api-types/v9';
|
||||
import {
|
||||
maxLengthValidator,
|
||||
minLengthValidator,
|
||||
placeholderValidator,
|
||||
requiredValidator,
|
||||
valueValidator,
|
||||
validateRequiredParameters,
|
||||
} from './Assertions';
|
||||
import { UnsafeTextInputComponent } from './UnsafeTextInput';
|
||||
|
||||
export class TextInputComponent extends UnsafeTextInputComponent {
|
||||
public override setMinLength(minLength: number) {
|
||||
return super.setMinLength(minLengthValidator.parse(minLength));
|
||||
}
|
||||
|
||||
public override setMaxLength(maxLength: number) {
|
||||
return super.setMaxLength(maxLengthValidator.parse(maxLength));
|
||||
}
|
||||
|
||||
public override setRequired(required = true) {
|
||||
return super.setRequired(requiredValidator.parse(required));
|
||||
}
|
||||
|
||||
public override setValue(value: string) {
|
||||
return super.setValue(valueValidator.parse(value));
|
||||
}
|
||||
|
||||
public override setPlaceholder(placeholder: string) {
|
||||
return super.setPlaceholder(placeholderValidator.parse(placeholder));
|
||||
}
|
||||
|
||||
public override toJSON(): APITextInputComponent {
|
||||
validateRequiredParameters(this.data.custom_id, this.data.style, this.data.label);
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
154
packages/builders/src/components/textInput/UnsafeTextInput.ts
Normal file
154
packages/builders/src/components/textInput/UnsafeTextInput.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
import { ComponentType, type TextInputStyle, type APITextInputComponent } from 'discord-api-types/v9';
|
||||
import { Component } from '../../index';
|
||||
import isEqual from 'fast-deep-equal';
|
||||
|
||||
export class UnsafeTextInputComponent extends Component<
|
||||
Partial<APITextInputComponent> & { type: ComponentType.TextInput }
|
||||
> {
|
||||
public constructor(data?: APITextInputComponent & { type?: ComponentType.TextInput }) {
|
||||
super({ type: ComponentType.TextInput, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* The style of this text input
|
||||
*/
|
||||
public get style() {
|
||||
return this.data.style;
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom id of this text input
|
||||
*/
|
||||
public get customId() {
|
||||
return this.data.custom_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The label for this text input
|
||||
*/
|
||||
public get label() {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* The placeholder text for this text input
|
||||
*/
|
||||
public get placeholder() {
|
||||
return this.data.placeholder;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default value for this text input
|
||||
*/
|
||||
public get value() {
|
||||
return this.data.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The minimum length of this text input
|
||||
*/
|
||||
public get minLength() {
|
||||
return this.data.min_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* The maximum length of this text input
|
||||
*/
|
||||
public get maxLength() {
|
||||
return this.data.max_length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this text input is required
|
||||
*/
|
||||
public get required() {
|
||||
return this.data.required;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id for this text input
|
||||
* @param customId The custom id of this text input
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the label for this text input
|
||||
* @param label The label for this text input
|
||||
*/
|
||||
public setLabel(label: string) {
|
||||
this.data.label = label;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the style for this text input
|
||||
* @param style The style for this text input
|
||||
*/
|
||||
public setStyle(style: TextInputStyle) {
|
||||
this.data.style = style;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum length of text for this text input
|
||||
* @param minLength The minimum length of text for this text input
|
||||
*/
|
||||
public setMinLength(minLength: number) {
|
||||
this.data.min_length = minLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the maximum length of text for this text input
|
||||
* @param maxLength The maximum length of text for this text input
|
||||
*/
|
||||
public setMaxLength(maxLength: number) {
|
||||
this.data.max_length = maxLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the placeholder of this text input
|
||||
* @param placeholder The placeholder of this text input
|
||||
*/
|
||||
public setPlaceholder(placeholder: string) {
|
||||
this.data.placeholder = placeholder;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the value of this text input
|
||||
* @param value The value for this text input
|
||||
*/
|
||||
public setValue(value: string) {
|
||||
this.data.value = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this text input is required or not
|
||||
* @param required Whether this text input is required or not
|
||||
*/
|
||||
public setRequired(required = true) {
|
||||
this.data.required = required;
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APITextInputComponent {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
} as APITextInputComponent;
|
||||
}
|
||||
|
||||
public equals(other: UnsafeTextInputComponent | APITextInputComponent): boolean {
|
||||
if (other instanceof UnsafeTextInputComponent) {
|
||||
return isEqual(other.data, this.data);
|
||||
}
|
||||
|
||||
return isEqual(other, this.data);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,12 @@ export * from './components/ActionRow';
|
||||
export * from './components/button/Button';
|
||||
export * from './components/Component';
|
||||
export * from './components/Components';
|
||||
export * from './components/textInput/TextInput';
|
||||
export * as TextInputAssertions from './components/textInput/Assertions';
|
||||
export * from './components/textInput/UnsafeTextInput';
|
||||
export * from './interactions/modals/UnsafeModal';
|
||||
export * from './interactions/modals/Modal';
|
||||
export * as ModalAssertions from './interactions/modals/Assertions';
|
||||
export * from './components/selectMenu/SelectMenu';
|
||||
export * from './components/selectMenu/SelectMenuOption';
|
||||
export * from './components/button/UnsafeButton';
|
||||
|
||||
16
packages/builders/src/interactions/modals/Assertions.ts
Normal file
16
packages/builders/src/interactions/modals/Assertions.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { z } from 'zod';
|
||||
import { ActionRow, type ModalActionRowComponent } from '../..';
|
||||
import { customIdValidator } from '../../components/Assertions';
|
||||
|
||||
export const titleValidator = z.string().min(1).max(45);
|
||||
export const componentsValidator = z.array(z.instanceof(ActionRow)).min(1);
|
||||
|
||||
export function validateRequiredParameters(
|
||||
customId?: string,
|
||||
title?: string,
|
||||
components?: ActionRow<ModalActionRowComponent>[],
|
||||
) {
|
||||
customIdValidator.parse(customId);
|
||||
titleValidator.parse(title);
|
||||
componentsValidator.parse(components);
|
||||
}
|
||||
19
packages/builders/src/interactions/modals/Modal.ts
Normal file
19
packages/builders/src/interactions/modals/Modal.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { APIModalInteractionResponseCallbackData } from 'discord-api-types/v9';
|
||||
import { customIdValidator } from '../../components/Assertions';
|
||||
import { titleValidator, validateRequiredParameters } from './Assertions';
|
||||
import { UnsafeModal } from './UnsafeModal';
|
||||
|
||||
export class Modal extends UnsafeModal {
|
||||
public override setCustomId(customId: string): this {
|
||||
return super.setCustomId(customIdValidator.parse(customId));
|
||||
}
|
||||
|
||||
public override setTitle(title: string) {
|
||||
return super.setTitle(titleValidator.parse(title));
|
||||
}
|
||||
|
||||
public override toJSON(): APIModalInteractionResponseCallbackData {
|
||||
validateRequiredParameters(this.data.custom_id, this.data.title, this.components);
|
||||
return super.toJSON();
|
||||
}
|
||||
}
|
||||
80
packages/builders/src/interactions/modals/UnsafeModal.ts
Normal file
80
packages/builders/src/interactions/modals/UnsafeModal.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
} from 'discord-api-types/v9';
|
||||
import { ActionRow, createComponent, JSONEncodable, ModalActionRowComponent } from '../../index';
|
||||
|
||||
export class UnsafeModal implements JSONEncodable<APIModalInteractionResponseCallbackData> {
|
||||
protected readonly data: Partial<Omit<APIModalInteractionResponseCallbackData, 'components'>>;
|
||||
public readonly components: ActionRow<ModalActionRowComponent>[] = [];
|
||||
|
||||
public constructor({ components, ...data }: Partial<APIModalInteractionResponseCallbackData> = {}) {
|
||||
this.data = { ...data };
|
||||
this.components = (components?.map((c) => createComponent(c)) ?? []) as ActionRow<ModalActionRowComponent>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* The custom id of this modal
|
||||
*/
|
||||
public get customId() {
|
||||
return this.data.custom_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The title of this modal
|
||||
*/
|
||||
public get title() {
|
||||
return this.data.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the title of the modal
|
||||
* @param title The title of the modal
|
||||
*/
|
||||
public setTitle(title: string) {
|
||||
this.data.title = title;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the custom id of the modal
|
||||
* @param customId The custom id of this modal
|
||||
*/
|
||||
public setCustomId(customId: string) {
|
||||
this.data.custom_id = customId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to this modal
|
||||
* @param components The components to add to this modal
|
||||
*/
|
||||
public addComponents(
|
||||
...components: (ActionRow<ModalActionRowComponent> | APIActionRowComponent<APIModalActionRowComponent>)[]
|
||||
) {
|
||||
this.components.push(
|
||||
...components.map((component) =>
|
||||
component instanceof ActionRow ? component : new ActionRow<ModalActionRowComponent>(component),
|
||||
),
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components in this modal
|
||||
* @param components The components to set this modal to
|
||||
*/
|
||||
public setComponents(...components: ActionRow<ModalActionRowComponent>[]) {
|
||||
this.components.splice(0, this.components.length, ...components);
|
||||
return this;
|
||||
}
|
||||
|
||||
public toJSON(): APIModalInteractionResponseCallbackData {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map((component) => component.toJSON()),
|
||||
} as APIModalInteractionResponseCallbackData;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@
|
||||
"@discordjs/rest": "workspace:^",
|
||||
"@sapphire/snowflake": "^3.1.0",
|
||||
"@types/ws": "^8.2.2",
|
||||
"discord-api-types": "^0.27.0",
|
||||
"discord-api-types": "^0.27.3",
|
||||
"lodash.snakecase": "^4.1.1",
|
||||
"undici": "^4.14.1",
|
||||
"ws": "^8.5.0"
|
||||
|
||||
@@ -6,6 +6,7 @@ const AutocompleteInteraction = require('../../structures/AutocompleteInteractio
|
||||
const ButtonInteraction = require('../../structures/ButtonInteraction');
|
||||
const ChatInputCommandInteraction = require('../../structures/ChatInputCommandInteraction');
|
||||
const MessageContextMenuCommandInteraction = require('../../structures/MessageContextMenuCommandInteraction');
|
||||
const ModalSubmitInteraction = require('../../structures/ModalSubmitInteraction');
|
||||
const SelectMenuInteraction = require('../../structures/SelectMenuInteraction');
|
||||
const UserContextMenuCommandInteraction = require('../../structures/UserContextMenuCommandInteraction');
|
||||
const Events = require('../../util/Events');
|
||||
@@ -57,6 +58,9 @@ class InteractionCreateAction extends Action {
|
||||
case InteractionType.ApplicationCommandAutocomplete:
|
||||
InteractionClass = AutocompleteInteraction;
|
||||
break;
|
||||
case InteractionType.ModalSubmit:
|
||||
InteractionClass = ModalSubmitInteraction;
|
||||
break;
|
||||
default:
|
||||
client.emit(Events.Debug, `[INTERACTION] Received interaction with unknown type: ${data.type}`);
|
||||
return;
|
||||
|
||||
@@ -141,6 +141,10 @@ const Messages = {
|
||||
COMMAND_INTERACTION_OPTION_NO_SUB_COMMAND_GROUP: 'No subcommand group specified for interaction.',
|
||||
AUTOCOMPLETE_INTERACTION_OPTION_NO_FOCUSED_OPTION: 'No focused option for autocomplete interaction.',
|
||||
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND: customId => `Required field with custom id "${customId}" not found.`,
|
||||
MODAL_SUBMIT_INTERACTION_FIELD_TYPE: (customId, type, expected) =>
|
||||
`Field with custom id "${customId}" is of type: ${type}; expected ${expected}.`,
|
||||
|
||||
INVITE_MISSING_SCOPES: 'At least one valid scope must be provided for the invite',
|
||||
|
||||
NOT_IMPLEMENTED: (what, name) => `Method ${what} not implemented on ${name}.`,
|
||||
|
||||
@@ -124,6 +124,9 @@ exports.MessageContextMenuCommandInteraction = require('./structures/MessageCont
|
||||
exports.MessageMentions = require('./structures/MessageMentions');
|
||||
exports.MessagePayload = require('./structures/MessagePayload');
|
||||
exports.MessageReaction = require('./structures/MessageReaction');
|
||||
exports.Modal = require('./structures/Modal');
|
||||
exports.ModalSubmitInteraction = require('./structures/ModalSubmitInteraction');
|
||||
exports.ModalSubmitFieldsResolver = require('./structures/ModalSubmitFieldsResolver');
|
||||
exports.NewsChannel = require('./structures/NewsChannel');
|
||||
exports.OAuth2Guild = require('./structures/OAuth2Guild');
|
||||
exports.PartialGroupDMChannel = require('./structures/PartialGroupDMChannel');
|
||||
@@ -143,6 +146,7 @@ exports.StoreChannel = require('./structures/StoreChannel');
|
||||
exports.Team = require('./structures/Team');
|
||||
exports.TeamMember = require('./structures/TeamMember');
|
||||
exports.TextChannel = require('./structures/TextChannel');
|
||||
exports.TextInputComponent = require('./structures/TextInputComponent');
|
||||
exports.ThreadChannel = require('./structures/ThreadChannel');
|
||||
exports.ThreadMember = require('./structures/ThreadMember');
|
||||
exports.Typing = require('./structures/Typing');
|
||||
@@ -193,6 +197,7 @@ exports.RESTJSONErrorCodes = require('discord-api-types/v9').RESTJSONErrorCodes;
|
||||
exports.StageInstancePrivacyLevel = require('discord-api-types/v9').StageInstancePrivacyLevel;
|
||||
exports.StickerType = require('discord-api-types/v9').StickerType;
|
||||
exports.StickerFormatType = require('discord-api-types/v9').StickerFormatType;
|
||||
exports.TextInputStyle = require('discord-api-types/v9').TextInputStyle;
|
||||
exports.UserFlags = require('discord-api-types/v9').UserFlags;
|
||||
exports.WebhookType = require('discord-api-types/v9').WebhookType;
|
||||
exports.UnsafeButtonComponent = require('@discordjs/builders').UnsafeButtonComponent;
|
||||
|
||||
@@ -203,6 +203,7 @@ class CommandInteraction extends Interaction {
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
showModal() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(CommandInteraction, ['deferUpdate', 'update']);
|
||||
|
||||
@@ -191,6 +191,14 @@ class Interaction extends Base {
|
||||
return this.isContextMenuCommand() && this.commandType === ApplicationCommandType.Message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is a {@link ModalSubmitInteraction}
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isModalSubmit() {
|
||||
return this.type === InteractionType.ModalSubmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates whether this interaction is an {@link AutocompleteInteraction}
|
||||
* @returns {boolean}
|
||||
|
||||
@@ -90,6 +90,7 @@ class MessageComponentInteraction extends Interaction {
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
showModal() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(MessageComponentInteraction);
|
||||
|
||||
12
packages/discord.js/src/structures/Modal.js
Normal file
12
packages/discord.js/src/structures/Modal.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { Modal: BuildersModal } = require('@discordjs/builders');
|
||||
const Transformers = require('../util/Transformers');
|
||||
|
||||
class Modal extends BuildersModal {
|
||||
constructor(data) {
|
||||
super(Transformers.toSnakeCase(data));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Modal;
|
||||
@@ -0,0 +1,54 @@
|
||||
'use strict';
|
||||
|
||||
const { Collection } = require('@discordjs/collection');
|
||||
const { ComponentType } = require('discord-api-types/v9');
|
||||
const { TypeError } = require('../errors');
|
||||
|
||||
/**
|
||||
* Represents the serialized fields from a modal submit interaction
|
||||
*/
|
||||
class ModalSubmitFieldsResolver {
|
||||
constructor(components) {
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {Array<ActionRow<ModalFieldData>>} The components in the modal
|
||||
*/
|
||||
this.components = components;
|
||||
|
||||
/**
|
||||
* The extracted fields from the modal
|
||||
* @type {Collection<string, ModalFieldData>} The fields in the modal
|
||||
*/
|
||||
this.fields = components.reduce((accumulator, next) => {
|
||||
next.components.forEach(c => accumulator.set(c.customId, c));
|
||||
return accumulator;
|
||||
}, new Collection());
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a field given a custom id from a component
|
||||
* @param {string} customId The custom id of the component
|
||||
* @returns {ModalFieldData}
|
||||
*/
|
||||
getField(customId) {
|
||||
const field = this.fields.get(customId);
|
||||
if (!field) throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_NOT_FOUND', customId);
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of a text input component given a custom id
|
||||
* @param {string} customId The custom id of the text input component
|
||||
* @returns {string}
|
||||
*/
|
||||
getTextInputValue(customId) {
|
||||
const field = this.getField(customId);
|
||||
const expectedType = ComponentType.TextInput;
|
||||
if (field.type !== expectedType) {
|
||||
throw new TypeError('MODAL_SUBMIT_INTERACTION_FIELD_TYPE', customId, field.type, expectedType);
|
||||
}
|
||||
return field.value;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ModalSubmitFieldsResolver;
|
||||
93
packages/discord.js/src/structures/ModalSubmitInteraction.js
Normal file
93
packages/discord.js/src/structures/ModalSubmitInteraction.js
Normal file
@@ -0,0 +1,93 @@
|
||||
'use strict';
|
||||
|
||||
const { createComponent } = require('@discordjs/builders');
|
||||
const Interaction = require('./Interaction');
|
||||
const InteractionWebhook = require('./InteractionWebhook');
|
||||
const ModalSubmitFieldsResolver = require('./ModalSubmitFieldsResolver');
|
||||
const InteractionResponses = require('./interfaces/InteractionResponses');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalFieldData
|
||||
* @property {string} value The value of the field
|
||||
* @property {ComponentType} type The component type of the field
|
||||
* @property {string} customId The custom id of the field
|
||||
*/
|
||||
|
||||
/**
|
||||
* Represents a modal interaction
|
||||
* @implements {InteractionResponses}
|
||||
*/
|
||||
class ModalSubmitInteraction extends Interaction {
|
||||
constructor(client, data) {
|
||||
super(client, data);
|
||||
/**
|
||||
* The custom id of the modal.
|
||||
* @type {string}
|
||||
*/
|
||||
this.customId = data.data.custom_id;
|
||||
|
||||
if ('message' in data) {
|
||||
/**
|
||||
* The message associated with this interaction
|
||||
* @type {?(Message|APIMessage)}
|
||||
*/
|
||||
this.message = this.channel?.messages._add(data.message) ?? data.message;
|
||||
} else {
|
||||
this.message = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The components within the modal
|
||||
* @type {ActionRow[]}
|
||||
*/
|
||||
this.components = data.data.components?.map(c => createComponent(c)) ?? [];
|
||||
|
||||
/**
|
||||
* The fields within the modal
|
||||
* @type {ModalSubmitFieldsResolver}
|
||||
*/
|
||||
this.fields = new ModalSubmitFieldsResolver(this.components);
|
||||
|
||||
/**
|
||||
* An associated interaction webhook, can be used to further interact with this interaction
|
||||
* @type {InteractionWebhook}
|
||||
*/
|
||||
this.webhook = new InteractionWebhook(this.client, this.applicationId, this.token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms component data to discord.js-compatible data
|
||||
* @param {*} rawComponent The data to transform
|
||||
* @returns {ModalFieldData[]}
|
||||
*/
|
||||
static transformComponent(rawComponent) {
|
||||
return {
|
||||
value: rawComponent.value,
|
||||
type: rawComponent.type,
|
||||
customId: rawComponent.custom_id,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this is from a {@link MessageComponentInteraction}.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isFromMessage() {
|
||||
return Boolean(this.message);
|
||||
}
|
||||
|
||||
// These are here only for documentation purposes - they are implemented by InteractionResponses
|
||||
/* eslint-disable no-empty-function */
|
||||
deferReply() {}
|
||||
reply() {}
|
||||
fetchReply() {}
|
||||
editReply() {}
|
||||
deleteReply() {}
|
||||
followUp() {}
|
||||
deferUpdate() {}
|
||||
update() {}
|
||||
}
|
||||
|
||||
InteractionResponses.applyToClass(ModalSubmitInteraction, 'showModal');
|
||||
|
||||
module.exports = ModalSubmitInteraction;
|
||||
12
packages/discord.js/src/structures/TextInputComponent.js
Normal file
12
packages/discord.js/src/structures/TextInputComponent.js
Normal file
@@ -0,0 +1,12 @@
|
||||
'use strict';
|
||||
|
||||
const { TextInputComponent: BuildersTextInputComponent } = require('@discordjs/builders');
|
||||
const Transformers = require('../util/Transformers');
|
||||
|
||||
class TextInputComponent extends BuildersTextInputComponent {
|
||||
constructor(data) {
|
||||
super(Transformers.toSnakeCase(data));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextInputComponent;
|
||||
@@ -1,9 +1,18 @@
|
||||
'use strict';
|
||||
|
||||
const { isJSONEncodable } = require('@discordjs/builders');
|
||||
const { InteractionResponseType, MessageFlags, Routes } = require('discord-api-types/v9');
|
||||
const { Error } = require('../../errors');
|
||||
const Transformers = require('../../util/Transformers');
|
||||
const MessagePayload = require('../MessagePayload');
|
||||
|
||||
/**
|
||||
* @typedef {Object} ModalData
|
||||
* @property {string} title The title of the modal
|
||||
* @property {string} customId The custom id of the modal
|
||||
* @property {ActionRowData[]} components The components within this modal
|
||||
*/
|
||||
|
||||
/**
|
||||
* Interface for classes that support shared interaction response types.
|
||||
* @interface
|
||||
@@ -225,6 +234,21 @@ class InteractionResponses {
|
||||
return options.fetchReply ? this.fetchReply() : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a modal component
|
||||
* @param {APIModal|ModalData|Modal} modal The modal to show
|
||||
*/
|
||||
async showModal(modal) {
|
||||
if (this.deferred || this.replied) throw new Error('INTERACTION_ALREADY_REPLIED');
|
||||
await this.client.rest.post(Routes.interactionCallback(this.id, this.token), {
|
||||
body: {
|
||||
type: InteractionResponseType.Modal,
|
||||
data: isJSONEncodable(modal) ? modal.toJSON() : Transformers.toSnakeCase(modal),
|
||||
},
|
||||
});
|
||||
this.replied = true;
|
||||
}
|
||||
|
||||
static applyToClass(structure, ignore = []) {
|
||||
const props = [
|
||||
'deferReply',
|
||||
@@ -235,6 +259,7 @@ class InteractionResponses {
|
||||
'followUp',
|
||||
'deferUpdate',
|
||||
'update',
|
||||
'showModal',
|
||||
];
|
||||
|
||||
for (const prop of props) {
|
||||
|
||||
@@ -1,44 +1,46 @@
|
||||
'use strict';
|
||||
|
||||
// This file contains the typedefs for camel-cased json data
|
||||
|
||||
/**
|
||||
* @typedef {Object} BaseComponentData
|
||||
* @property {ComponentType} type
|
||||
* @property {ComponentType} type The type of component
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ActionRowData
|
||||
* @property {ComponentData[]} components
|
||||
* @property {ComponentData[]} components The components in this action row
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} ButtonComponentData
|
||||
* @property {ButtonStyle} style
|
||||
* @property {?boolean} disabled
|
||||
* @property {string} label
|
||||
* @property {?APIComponentEmoji} emoji
|
||||
* @property {?string} customId
|
||||
* @property {?string} url
|
||||
* @property {ButtonStyle} style The style of the button
|
||||
* @property {?boolean} disabled Whether this button is disabled
|
||||
* @property {string} label The label of this button
|
||||
* @property {?APIComponentEmoji} emoji The emoji on this button
|
||||
* @property {?string} customId The custom id of the button
|
||||
* @property {?string} url The URL of the button
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} SelectMenuComponentOptionData
|
||||
* @property {string} label
|
||||
* @property {string} value
|
||||
* @property {?string} description
|
||||
* @property {?APIComponentEmoji} emoji
|
||||
* @property {?boolean} default
|
||||
* @property {string} label The label of the option
|
||||
* @property {string} value The value of the option
|
||||
* @property {?string} description The description of the option
|
||||
* @property {?APIComponentEmoji} emoji The emoji on the option
|
||||
* @property {?boolean} default Whether this option is selected by default
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {BaseComponentData} SelectMenuComponentData
|
||||
* @property {string} customId
|
||||
* @property {?boolean} disabled
|
||||
* @property {?number} maxValues
|
||||
* @property {?number} minValues
|
||||
* @property {?SelectMenuComponentOptionData[]} options
|
||||
* @property {?string} placeholder
|
||||
* @property {string} customId The custom id of the select menu
|
||||
* @property {?boolean} disabled Whether the select menu is disabled or not
|
||||
* @property {?number} maxValues The maximum amount of options that can be selected
|
||||
* @property {?number} minValues The minimum amount of options that can be selected
|
||||
* @property {?SelectMenuComponentOptionData[]} options The options in this select menu
|
||||
* @property {?string} placeholder The placeholder of the select menu
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} ComponentData
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData} MessageComponentData
|
||||
/
|
||||
|
||||
/**
|
||||
* @typedef {ActionRowData|ButtonComponentData|SelectMenuComponentData|TextInputComponentData} ComponentData
|
||||
*/
|
||||
|
||||
@@ -2,47 +2,47 @@
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedData
|
||||
* @property {?string} title
|
||||
* @property {?EmbedType} type
|
||||
* @property {?string} description
|
||||
* @property {?string} url
|
||||
* @property {?string} timestamp
|
||||
* @property {?number} color
|
||||
* @property {?EmbedFooterData} footer
|
||||
* @property {?EmbedImageData} image
|
||||
* @property {?EmbedImageData} thumbnail
|
||||
* @property {?EmbedProviderData} provider
|
||||
* @property {?EmbedAuthorData} author
|
||||
* @property {?EmbedFieldData[]} fields
|
||||
* @property {?string} title The title of the embed
|
||||
* @property {?EmbedType} type The type of the embed
|
||||
* @property {?string} description The description of the embed
|
||||
* @property {?string} url The URL of the embed
|
||||
* @property {?string} timestamp The timestamp on the embed
|
||||
* @property {?number} color The color of the embed
|
||||
* @property {?EmbedFooterData} footer The footer of the embed
|
||||
* @property {?EmbedImageData} image The image of the embed
|
||||
* @property {?EmbedImageData} thumbnail The thumbnail of the embed
|
||||
* @property {?EmbedProviderData} provider The provider of the embed
|
||||
* @property {?EmbedAuthorData} author The author in the embed
|
||||
* @property {?EmbedFieldData[]} fields The fields in this embed
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedFooterData
|
||||
* @property {string} text
|
||||
* @property {?string} iconURL
|
||||
* @property {string} text The text of the footer
|
||||
* @property {?string} iconURL The URL of the icon
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedImageData
|
||||
* @property {?string} url
|
||||
* @property {?string} url The URL of the image
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedProviderData
|
||||
* @property {?string} name
|
||||
* @property {?string} url
|
||||
* @property {?string} name The name of the provider
|
||||
* @property {?string} url The URL of the provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedAuthorData
|
||||
* @property {string} name
|
||||
* @property {?string} url
|
||||
* @property {?string} iconURL
|
||||
* @property {string} name The name of the author
|
||||
* @property {?string} url The URL of the author
|
||||
* @property {?string} iconURL The icon URL of the author
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} EmbedFieldData
|
||||
* @property {string} name
|
||||
* @property {string} value
|
||||
* @property {?boolean} inline
|
||||
* @property {string} name The name of the field
|
||||
* @property {string} value The value of the field
|
||||
* @property {?boolean} inline Whether to inline this field
|
||||
*/
|
||||
|
||||
165
packages/discord.js/typings/index.d.ts
vendored
165
packages/discord.js/typings/index.d.ts
vendored
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
ActionRow as BuilderActionRow,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
blockQuote,
|
||||
bold,
|
||||
ButtonComponent as BuilderButtonComponent,
|
||||
@@ -14,9 +14,11 @@ import {
|
||||
inlineCode,
|
||||
italic,
|
||||
memberNicknameMention,
|
||||
Modal as BuilderModal,
|
||||
quote,
|
||||
roleMention,
|
||||
SelectMenuComponent as BuilderSelectMenuComponent,
|
||||
TextInputComponent as BuilderTextInputComponent,
|
||||
spoiler,
|
||||
strikethrough,
|
||||
time,
|
||||
@@ -24,6 +26,7 @@ import {
|
||||
TimestampStylesString,
|
||||
underscore,
|
||||
userMention,
|
||||
ModalActionRowComponent,
|
||||
} from '@discordjs/builders';
|
||||
import { Collection } from '@discordjs/collection';
|
||||
import { BaseImageURLOptions, ImageURLOptions, RawFile, REST, RESTOptions } from '@discordjs/rest';
|
||||
@@ -95,6 +98,13 @@ import {
|
||||
APIMessageComponentEmoji,
|
||||
EmbedType,
|
||||
APIActionRowComponentTypes,
|
||||
APIModalInteractionResponseCallbackData,
|
||||
APIModalSubmitInteraction,
|
||||
APIMessageActionRowComponent,
|
||||
TextInputStyle,
|
||||
APITextInputComponent,
|
||||
APIModalActionRowComponent,
|
||||
APIModalComponent,
|
||||
} from 'discord-api-types/v9';
|
||||
import { ChildProcess } from 'node:child_process';
|
||||
import { EventEmitter } from 'node:events';
|
||||
@@ -198,17 +208,23 @@ export interface BaseComponentData {
|
||||
type?: ComponentType;
|
||||
}
|
||||
|
||||
export type ActionRowComponentData = ButtonComponentData | SelectMenuComponentData;
|
||||
export type MessageActionRowComponentData = ButtonComponentData | SelectMenuComponentData;
|
||||
export type ModalActionRowComponentData = TextInputComponentData;
|
||||
|
||||
export interface ActionRowData extends BaseComponentData {
|
||||
components: ActionRowComponentData[];
|
||||
export interface ActionRowData<T extends MessageActionRowComponentData | ModalActionRowComponentData>
|
||||
extends BaseComponentData {
|
||||
components: T[];
|
||||
}
|
||||
|
||||
export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extends BuilderActionRow<T> {
|
||||
export class ActionRow<
|
||||
T extends MessageActionRowComponent | ModalActionRowComponent = MessageActionRowComponent,
|
||||
> extends BuilderActionRow<T> {
|
||||
constructor(
|
||||
data?:
|
||||
| ActionRowData
|
||||
| (Omit<APIActionRowComponent<APIMessageComponent>, 'type'> & { type?: ComponentType.ActionRow }),
|
||||
| ActionRowData<MessageActionRowComponentData | ModalActionRowComponentData>
|
||||
| (Omit<APIActionRowComponent<APIMessageActionRowComponent | APIModalActionRowComponent>, 'type'> & {
|
||||
type?: ComponentType.ActionRow;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -336,6 +352,7 @@ export interface InteractionResponseFields<Cached extends CacheType = CacheType>
|
||||
deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
|
||||
fetchReply(): Promise<GuildCacheMessage<Cached>>;
|
||||
followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
showModal(modal: Modal): Promise<void>;
|
||||
}
|
||||
|
||||
export abstract class CommandInteraction<Cached extends CacheType = CacheType> extends Interaction<Cached> {
|
||||
@@ -374,6 +391,7 @@ export abstract class CommandInteraction<Cached extends CacheType = CacheType> e
|
||||
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public showModal(modal: Modal): Promise<void>;
|
||||
private transformOption(
|
||||
option: APIApplicationCommandOption,
|
||||
resolved: APIApplicationCommandInteractionData['resolved'],
|
||||
@@ -490,6 +508,14 @@ export class SelectMenuComponent extends BuilderSelectMenuComponent {
|
||||
);
|
||||
}
|
||||
|
||||
export class TextInputComponent extends BuilderTextInputComponent {
|
||||
public constructor(data?: TextInputComponentData | APITextInputComponent);
|
||||
}
|
||||
|
||||
export class Modal extends BuilderModal {
|
||||
public constructor(data?: ModalData | APIModalActionRowComponent);
|
||||
}
|
||||
|
||||
export interface EmbedData {
|
||||
title?: string;
|
||||
type?: EmbedType;
|
||||
@@ -1355,6 +1381,7 @@ export class Interaction<Cached extends CacheType = CacheType> extends Base {
|
||||
public isMessageComponent(): this is MessageComponentInteraction<Cached>;
|
||||
public isSelectMenu(): this is SelectMenuInteraction<Cached>;
|
||||
public isRepliable(): this is this & InteractionResponseFields<Cached>;
|
||||
public isModalSubmit(): this is ModalSubmitInteraction<Cached>;
|
||||
}
|
||||
|
||||
export class InteractionCollector<T extends Interaction> extends Collector<Snowflake, T> {
|
||||
@@ -1447,17 +1474,19 @@ export class LimitedCollection<K, V> extends Collection<K, V> {
|
||||
public keepOverLimit: ((value: V, key: K, collection: this) => boolean) | null;
|
||||
}
|
||||
|
||||
export type MessageCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type MessageComponentType = Exclude<ComponentType, ComponentType.TextInput>;
|
||||
|
||||
export type MessageCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| {
|
||||
componentType?: T;
|
||||
} & MessageComponentCollectorOptions<MappedInteractionTypes<Cached>[T]>;
|
||||
|
||||
export type MessageChannelCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type MessageChannelCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| {
|
||||
componentType?: T;
|
||||
} & MessageChannelComponentCollectorOptions<MappedInteractionTypes<Cached>[T]>;
|
||||
|
||||
export type AwaitMessageCollectorOptionsParams<T extends ComponentType, Cached extends boolean = boolean> =
|
||||
export type AwaitMessageCollectorOptionsParams<T extends MessageComponentType, Cached extends boolean = boolean> =
|
||||
| { componentType?: T } & Pick<
|
||||
InteractionCollectorOptions<MappedInteractionTypes<Cached>[T]>,
|
||||
keyof AwaitMessageComponentOptions<any>
|
||||
@@ -1490,7 +1519,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public get channel(): If<Cached, GuildTextBasedChannel, TextBasedChannel>;
|
||||
public channelId: Snowflake;
|
||||
public get cleanContent(): string;
|
||||
public components: ActionRow<ActionRowComponent>[];
|
||||
public components: ActionRow<MessageActionRowComponent>[];
|
||||
public content: string;
|
||||
public get createdAt(): Date;
|
||||
public createdTimestamp: number;
|
||||
@@ -1522,12 +1551,12 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public webhookId: Snowflake | null;
|
||||
public flags: Readonly<MessageFlagsBitField>;
|
||||
public reference: MessageReference | null;
|
||||
public awaitMessageComponent<T extends ComponentType = ComponentType.ActionRow>(
|
||||
public awaitMessageComponent<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, Cached>,
|
||||
): Promise<MappedInteractionTypes<Cached>[T]>;
|
||||
public awaitReactions(options?: AwaitReactionsOptions): Promise<Collection<Snowflake | string, MessageReaction>>;
|
||||
public createReactionCollector(options?: ReactionCollectorOptions): ReactionCollector;
|
||||
public createMessageComponentCollector<T extends ComponentType = ComponentType.ActionRow>(
|
||||
public createMessageComponentCollector<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: MessageCollectorOptionsParams<T, Cached>,
|
||||
): InteractionCollector<MappedInteractionTypes<Cached>[T]>;
|
||||
public delete(): Promise<Message>;
|
||||
@@ -1541,7 +1570,7 @@ export class Message<Cached extends boolean = boolean> extends Base {
|
||||
public react(emoji: EmojiIdentifierResolvable): Promise<MessageReaction>;
|
||||
public removeAttachments(): Promise<Message>;
|
||||
public reply(options: string | MessagePayload | ReplyMessageOptions): Promise<Message>;
|
||||
public resolveComponent(customId: string): ActionRowComponent | null;
|
||||
public resolveComponent(customId: string): MessageActionRowComponent | null;
|
||||
public startThread(options: StartThreadOptions): Promise<ThreadChannel>;
|
||||
public suppressEmbeds(suppress?: boolean): Promise<Message>;
|
||||
public toJSON(): unknown;
|
||||
@@ -1590,10 +1619,10 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
|
||||
protected constructor(client: Client, data: RawMessageComponentInteractionData);
|
||||
public get component(): CacheTypeReducer<
|
||||
Cached,
|
||||
ActionRowComponent,
|
||||
Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>,
|
||||
ActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>,
|
||||
ActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageComponent>>
|
||||
MessageActionRowComponent,
|
||||
Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>,
|
||||
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>,
|
||||
MessageActionRowComponent | Exclude<APIMessageComponent, APIActionRowComponent<APIMessageActionRowComponent>>
|
||||
>;
|
||||
public componentType: Exclude<ComponentType, ComponentType.ActionRow>;
|
||||
public customId: string;
|
||||
@@ -1618,6 +1647,7 @@ export class MessageComponentInteraction<Cached extends CacheType = CacheType> e
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
|
||||
public showModal(modal: Modal): Promise<void>;
|
||||
}
|
||||
|
||||
export class MessageContextMenuCommandInteraction<
|
||||
@@ -1706,6 +1736,57 @@ export class MessageReaction {
|
||||
public toJSON(): unknown;
|
||||
}
|
||||
|
||||
export interface ModalFieldData {
|
||||
value: string;
|
||||
type: ComponentType;
|
||||
customId: string;
|
||||
}
|
||||
|
||||
export class ModalSubmitFieldsResolver {
|
||||
constructor(components: ModalFieldData[][]);
|
||||
public fields: Collection<string, ModalFieldData>;
|
||||
public getField(customId: string): ModalFieldData;
|
||||
public getTextInputValue(customId: string): string;
|
||||
}
|
||||
|
||||
export interface ModalMessageModalSubmitInteraction<Cached extends CacheType = CacheType>
|
||||
extends ModalSubmitInteraction<Cached> {
|
||||
message: GuildCacheMessage<Cached> | null;
|
||||
update(options: InteractionUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
update(options: string | MessagePayload | InteractionUpdateOptions): Promise<void>;
|
||||
deferUpdate(options: InteractionDeferUpdateOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
deferUpdate(options?: InteractionDeferUpdateOptions): Promise<void>;
|
||||
inGuild(): this is ModalMessageModalSubmitInteraction<'raw' | 'cached'>;
|
||||
inCachedGuild(): this is ModalMessageModalSubmitInteraction<'cached'>;
|
||||
inRawGuild(): this is ModalMessageModalSubmitInteraction<'raw'>;
|
||||
}
|
||||
|
||||
export interface ModalSubmitActionRow {
|
||||
type: ComponentType.ActionRow;
|
||||
components: ModalFieldData[];
|
||||
}
|
||||
|
||||
export class ModalSubmitInteraction<Cached extends CacheType = CacheType> extends Interaction<Cached> {
|
||||
private constructor(client: Client, data: APIModalSubmitInteraction);
|
||||
public readonly customId: string;
|
||||
// TODO: fix this type when #7517 is implemented
|
||||
public readonly components: ModalSubmitActionRow[];
|
||||
public readonly fields: ModalSubmitFieldsResolver;
|
||||
public readonly webhook: InteractionWebhook;
|
||||
public reply(options: InteractionReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public reply(options: string | MessagePayload | InteractionReplyOptions): Promise<void>;
|
||||
public deleteReply(): Promise<void>;
|
||||
public editReply(options: string | MessagePayload | WebhookEditMessageOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public deferReply(options: InteractionDeferReplyOptions & { fetchReply: true }): Promise<GuildCacheMessage<Cached>>;
|
||||
public deferReply(options?: InteractionDeferReplyOptions): Promise<void>;
|
||||
public fetchReply(): Promise<GuildCacheMessage<Cached>>;
|
||||
public followUp(options: string | MessagePayload | InteractionReplyOptions): Promise<GuildCacheMessage<Cached>>;
|
||||
public inGuild(): this is ModalSubmitInteraction<'raw' | 'cached'>;
|
||||
public inCachedGuild(): this is ModalSubmitInteraction<'cached'>;
|
||||
public inRawGuild(): this is ModalSubmitInteraction<'raw'>;
|
||||
public isFromMessage(): this is ModalMessageModalSubmitInteraction<Cached>;
|
||||
}
|
||||
|
||||
export class NewsChannel extends BaseGuildTextChannel {
|
||||
public threads: ThreadManager<AllowedThreadTypeForNewsChannel>;
|
||||
public type: ChannelType.GuildNews;
|
||||
@@ -2381,7 +2462,10 @@ export class Formatters extends null {
|
||||
public static userMention: typeof userMention;
|
||||
}
|
||||
|
||||
export type ComponentData = ActionRowComponentData | ButtonComponentData | SelectMenuComponentData;
|
||||
export type ComponentData =
|
||||
| MessageActionRowComponentData
|
||||
| ModalActionRowComponentData
|
||||
| ActionRowData<MessageActionRowComponentData | ModalActionRowComponentData>;
|
||||
|
||||
export class VoiceChannel extends BaseGuildVoiceChannel {
|
||||
public get speakable(): boolean;
|
||||
@@ -3132,8 +3216,8 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
lastMessageId: Snowflake | null;
|
||||
get lastMessage(): Message | null;
|
||||
lastPinTimestamp: number | null;
|
||||
get lastPinAt(): Date | null;
|
||||
awaitMessageComponent<T extends ComponentType = ComponentType.ActionRow>(
|
||||
readonly lastPinAt: Date | null;
|
||||
awaitMessageComponent<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: AwaitMessageCollectorOptionsParams<T, true>,
|
||||
): Promise<MappedInteractionTypes[T]>;
|
||||
awaitMessages(options?: AwaitMessagesOptions): Promise<Collection<Snowflake, Message>>;
|
||||
@@ -3141,7 +3225,7 @@ export interface TextBasedChannelFields extends PartialTextBasedChannelFields {
|
||||
messages: Collection<Snowflake, Message> | readonly MessageResolvable[] | number,
|
||||
filterOld?: boolean,
|
||||
): Promise<Collection<Snowflake, Message>>;
|
||||
createMessageComponentCollector<T extends ComponentType = ComponentType.ActionRow>(
|
||||
createMessageComponentCollector<T extends MessageComponentType = ComponentType.ActionRow>(
|
||||
options?: MessageChannelCollectorOptionsParams<T, true>,
|
||||
): InteractionCollector<MappedInteractionTypes[T]>;
|
||||
createMessageCollector(options?: MessageCollectorOptions): MessageCollector;
|
||||
@@ -4518,7 +4602,7 @@ export type ActionRowComponentOptions =
|
||||
| (Required<BaseComponentData> & ButtonComponentData)
|
||||
| (Required<BaseComponentData> & SelectMenuComponentData);
|
||||
|
||||
export type MessageActionRowComponentResolvable = ActionRowComponent | ActionRowComponentOptions;
|
||||
export type MessageActionRowComponentResolvable = MessageActionRowComponent | ActionRowComponentOptions;
|
||||
|
||||
export interface MessageActivity {
|
||||
partyId: string;
|
||||
@@ -4548,7 +4632,7 @@ export interface MessageCollectorOptions extends CollectorOptions<[Message]> {
|
||||
maxProcessed?: number;
|
||||
}
|
||||
|
||||
export type MessageComponent = Component | ActionRow<ActionRowComponent> | ButtonComponent | SelectMenuComponent;
|
||||
export type MessageComponent = Component | ActionRow<MessageActionRowComponent> | ButtonComponent | SelectMenuComponent;
|
||||
|
||||
export type MessageComponentCollectorOptions<T extends MessageComponentInteraction> = Omit<
|
||||
InteractionCollectorOptions<T>,
|
||||
@@ -4567,7 +4651,11 @@ export interface MessageEditOptions {
|
||||
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
|
||||
flags?: BitFieldResolvable<MessageFlagsString, number>;
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
components?: (ActionRow<ActionRowComponent> | (Required<BaseComponentData> & ActionRowData))[];
|
||||
components?: (
|
||||
| ActionRow<MessageActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData<MessageActionRowComponentData>)
|
||||
| APIActionRowComponent<APIMessageActionRowComponent>
|
||||
)[];
|
||||
}
|
||||
|
||||
export interface MessageEvent {
|
||||
@@ -4605,9 +4693,9 @@ export interface MessageOptions {
|
||||
content?: string | null;
|
||||
embeds?: (Embed | APIEmbed)[];
|
||||
components?: (
|
||||
| ActionRow<ActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData)
|
||||
| APIActionRowComponent<APIActionRowComponentTypes>
|
||||
| ActionRow<MessageActionRowComponent>
|
||||
| (Required<BaseComponentData> & ActionRowData<MessageActionRowComponentData>)
|
||||
| APIActionRowComponent<APIMessageActionRowComponent>
|
||||
)[];
|
||||
allowedMentions?: MessageMentionOptions;
|
||||
files?: (FileOptions | BufferResolvable | Stream | MessageAttachment)[];
|
||||
@@ -4658,6 +4746,23 @@ export interface SelectMenuComponentOptionData {
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface TextInputComponentData extends BaseComponentData {
|
||||
customId: string;
|
||||
style: TextInputStyle;
|
||||
label: string;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
required?: boolean;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export interface ModalData {
|
||||
customId: string;
|
||||
title: string;
|
||||
components: ActionRowData<ModalActionRowComponentData>[];
|
||||
}
|
||||
|
||||
export type MessageTarget =
|
||||
| Interaction
|
||||
| InteractionWebhook
|
||||
@@ -5156,6 +5261,7 @@ export {
|
||||
StageInstancePrivacyLevel,
|
||||
StickerType,
|
||||
StickerFormatType,
|
||||
TextInputStyle,
|
||||
GuildSystemChannelFlags,
|
||||
ThreadMemberFlags,
|
||||
UserFlags,
|
||||
@@ -5166,7 +5272,8 @@ export {
|
||||
UnsafeSelectMenuComponent,
|
||||
SelectMenuOption,
|
||||
UnsafeSelectMenuOption,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
UnsafeEmbed,
|
||||
ModalActionRowComponent,
|
||||
} from '@discordjs/builders';
|
||||
export { DiscordAPIError, HTTPError, RateLimitError } from '@discordjs/rest';
|
||||
|
||||
@@ -96,7 +96,7 @@ import {
|
||||
ActionRow,
|
||||
ButtonComponent,
|
||||
SelectMenuComponent,
|
||||
ActionRowComponent,
|
||||
MessageActionRowComponent,
|
||||
InteractionResponseFields,
|
||||
ThreadChannelType,
|
||||
Events,
|
||||
@@ -104,6 +104,7 @@ import {
|
||||
Status,
|
||||
CategoryChannelChildManager,
|
||||
ActionRowData,
|
||||
MessageActionRowComponentData,
|
||||
} from '.';
|
||||
import { expectAssignable, expectDeprecated, expectNotAssignable, expectNotType, expectType } from 'tsd';
|
||||
import { Embed } from '@discordjs/builders';
|
||||
@@ -723,11 +724,14 @@ client.on('interactionCreate', async interaction => {
|
||||
|
||||
if (!interaction.isCommand()) return;
|
||||
|
||||
void new ActionRow<ActionRowComponent>();
|
||||
void new ActionRow<MessageActionRowComponent>();
|
||||
|
||||
const button = new ButtonComponent();
|
||||
|
||||
const actionRow = new ActionRow<ActionRowComponent>({ type: ComponentType.ActionRow, components: [button.toJSON()] });
|
||||
const actionRow = new ActionRow<MessageActionRowComponent>({
|
||||
type: ComponentType.ActionRow,
|
||||
components: [button.toJSON()],
|
||||
});
|
||||
|
||||
await interaction.reply({ content: 'Hi!', components: [actionRow] });
|
||||
|
||||
@@ -1092,11 +1096,11 @@ client.on('interactionCreate', async interaction => {
|
||||
|
||||
if (interaction.isMessageComponent()) {
|
||||
expectType<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<Message | APIMessage>(interaction.message);
|
||||
if (interaction.inCachedGuild()) {
|
||||
expectAssignable<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent>(interaction.component);
|
||||
expectType<Message<true>>(interaction.message);
|
||||
expectType<Guild>(interaction.guild);
|
||||
expectAssignable<Promise<Message>>(interaction.reply({ fetchReply: true }));
|
||||
@@ -1108,7 +1112,7 @@ client.on('interactionCreate', async interaction => {
|
||||
expectType<Promise<APIMessage>>(interaction.reply({ fetchReply: true }));
|
||||
} else if (interaction.inGuild()) {
|
||||
expectAssignable<MessageComponentInteraction>(interaction);
|
||||
expectType<ActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<MessageActionRowComponent | APIButtonComponent | APISelectMenuComponent>(interaction.component);
|
||||
expectType<Message | APIMessage>(interaction.message);
|
||||
expectType<Guild | null>(interaction.guild);
|
||||
expectType<Promise<APIMessage | Message>>(interaction.reply({ fetchReply: true }));
|
||||
@@ -1336,7 +1340,7 @@ new ButtonComponent({
|
||||
style: ButtonStyle.Danger,
|
||||
});
|
||||
|
||||
expectNotAssignable<ActionRowData>({
|
||||
expectNotAssignable<ActionRowData<MessageActionRowComponentData>>({
|
||||
type: ComponentType.ActionRow,
|
||||
components: [
|
||||
{
|
||||
|
||||
17
yarn.lock
17
yarn.lock
@@ -1764,7 +1764,7 @@ __metadata:
|
||||
"@typescript-eslint/eslint-plugin": ^5.11.0
|
||||
"@typescript-eslint/parser": ^5.11.0
|
||||
babel-plugin-transform-typescript-metadata: ^0.3.2
|
||||
discord-api-types: ^0.27.0
|
||||
discord-api-types: ^0.27.3
|
||||
eslint: ^8.9.0
|
||||
eslint-config-marine: ^9.3.2
|
||||
eslint-config-prettier: ^8.3.0
|
||||
@@ -4416,9 +4416,16 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"discord-api-types@npm:^0.27.0":
|
||||
version: 0.27.0
|
||||
resolution: "discord-api-types@npm:0.27.0"
|
||||
checksum: 5a74a49ad7e57ea24e67d431de30cc7056d6d422b607c7d5a7dd35c683c8b87d70ec35a0d3929971adb411acc3df2bd6a77c1401ce30b29690bd1305e427265c
|
||||
version: 0.27.1
|
||||
resolution: "discord-api-types@npm:0.27.1"
|
||||
checksum: 5e3473eb01eb3e7ed2b1313513f165644dc70f1f64fb130a50b40394b41c97b1202f4de00b17df34a9f0916269595a091421955bb1e8dbd8e0475637512f2057
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"discord-api-types@npm:^0.27.3":
|
||||
version: 0.27.3
|
||||
resolution: "discord-api-types@npm:0.27.3"
|
||||
checksum: c22d87e787fae6cffd9d23972a3d196d4b43f2fb6deeed50181e7c9d4e823a4fd30a3e1d0e0b3b48a7c284ae2b39fbe960dee988375c7d4072df445f30ac440e
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -4433,7 +4440,7 @@ __metadata:
|
||||
"@sapphire/snowflake": ^3.1.0
|
||||
"@types/node": ^16.11.24
|
||||
"@types/ws": ^8.2.2
|
||||
discord-api-types: ^0.27.0
|
||||
discord-api-types: ^0.27.3
|
||||
dtslint: ^4.2.1
|
||||
eslint: ^8.9.0
|
||||
eslint-config-prettier: ^8.3.0
|
||||
|
||||
Reference in New Issue
Block a user