mirror of
https://github.com/tiramisulabs/seyfert.git
synced 2025-07-01 20:46:08 +00:00
chore: 3.1.0 (#339)
* perf: optimize members cache * feat: components V2 (#337) * feat: components v2 * fix: build * chore: apply formatting * refactor(components): some types * refactor(types): replace TopLevelComponents with APITopLevelComponent in REST * fix: unify components * refactor(TextDisplay): rename content method to setContent for clarity * refactor(builders): add missing builder from component * fix: touche * feat(webhook): webhook params for components v2 --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> * fix: use protected instead of private * fix(editOrReply): accept flags when editing message * feat: add onBeforeMiddlewares and onBeforeOptions (#338) * chore: package version --------- Co-authored-by: MARCROCK22 <marcos22dev@gmail.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: MARCROCK22 <57925328+MARCROCK22@users.noreply.github.com>
This commit is contained in:
parent
ce3d75121d
commit
e3b6f57741
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "seyfert",
|
||||
"version": "3.0.0",
|
||||
"version": "3.1.0",
|
||||
"description": "The most advanced framework for discord bots",
|
||||
"main": "./lib/index.js",
|
||||
"module": "./lib/index.js",
|
||||
|
@ -9,6 +9,7 @@ import type {
|
||||
RESTPatchAPIWebhookResult,
|
||||
RESTPatchAPIWebhookWithTokenJSONBody,
|
||||
RESTPatchAPIWebhookWithTokenMessageJSONBody,
|
||||
RESTPatchAPIWebhookWithTokenMessageQuery,
|
||||
RESTPatchAPIWebhookWithTokenMessageResult,
|
||||
RESTPatchAPIWebhookWithTokenResult,
|
||||
RESTPostAPIWebhookWithTokenGitHubQuery,
|
||||
@ -33,7 +34,9 @@ export interface WebhookRoutes {
|
||||
token: string,
|
||||
): {
|
||||
get(args?: RestArgumentsNoBody): Promise<RESTGetAPIWebhookWithTokenResult>;
|
||||
patch(args: RestArguments<RESTPatchAPIWebhookWithTokenJSONBody>): Promise<RESTPatchAPIWebhookWithTokenResult>;
|
||||
patch(
|
||||
args: RestArguments<RESTPatchAPIWebhookWithTokenJSONBody, RESTPatchAPIWebhookWithTokenMessageQuery>,
|
||||
): Promise<RESTPatchAPIWebhookWithTokenResult>;
|
||||
delete(args?: RestArgumentsNoBody): Promise<RESTDeleteAPIWebhookWithTokenResult>;
|
||||
post(
|
||||
args: RestArguments<RESTPostAPIWebhookWithTokenJSONBody, RESTPostAPIWebhookWithTokenQuery>,
|
||||
|
@ -98,13 +98,13 @@ export class ApiHandler {
|
||||
}
|
||||
}
|
||||
|
||||
#randomUUID(): UUID {
|
||||
randomUUID(): UUID {
|
||||
const uuid = randomUUID();
|
||||
if (this.workerPromises!.has(uuid)) return this.#randomUUID();
|
||||
if (this.workerPromises!.has(uuid)) return this.randomUUID();
|
||||
return uuid;
|
||||
}
|
||||
|
||||
private sendMessage(_body: WorkerSendApiRequest) {
|
||||
protected sendMessage(_body: WorkerSendApiRequest) {
|
||||
throw new Error('Function not implemented');
|
||||
}
|
||||
|
||||
@ -121,7 +121,7 @@ export class ApiHandler {
|
||||
{ auth = true, ...request }: ApiRequestOptions = {},
|
||||
): Promise<T> {
|
||||
if (this.options.workerProxy) {
|
||||
const nonce = this.#randomUUID();
|
||||
const nonce = this.randomUUID();
|
||||
return this.postMessage<T>({
|
||||
method,
|
||||
url,
|
||||
|
@ -7,13 +7,13 @@ import {
|
||||
} from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
import { fromComponent } from './index';
|
||||
import type { BuilderComponents, FixedComponents } from './types';
|
||||
import type { ActionBuilderComponents, FixedComponents } from './types';
|
||||
|
||||
/**
|
||||
* Represents an Action Row component in a message.
|
||||
* @template T - The type of components in the Action Row.
|
||||
*/
|
||||
export class ActionRow<T extends BuilderComponents> extends BaseComponentBuilder<
|
||||
export class ActionRow<T extends ActionBuilderComponents = ActionBuilderComponents> extends BaseComponentBuilder<
|
||||
APIActionRowComponent<APIActionRowComponentTypes>
|
||||
> {
|
||||
/**
|
||||
@ -50,8 +50,8 @@ export class ActionRow<T extends BuilderComponents> extends BaseComponentBuilder
|
||||
* @example
|
||||
* actionRow.setComponents([buttonComponent1, buttonComponent2]);
|
||||
*/
|
||||
setComponents(component: FixedComponents<T>[]): this {
|
||||
this.components = [...component];
|
||||
setComponents(...component: RestOrArray<FixedComponents<T>>): this {
|
||||
this.components = component.flat() as FixedComponents<T>[];
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,14 @@
|
||||
import { type EmojiResolvable, resolvePartialEmoji } from '../common';
|
||||
import { type APIButtonComponent, type APIMessageComponentEmoji, type ButtonStyle, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a button component.
|
||||
* @template Type - The type of the button component.
|
||||
*/
|
||||
export class Button {
|
||||
/**
|
||||
* Creates a new Button instance.
|
||||
* @param data - The initial data for the button.
|
||||
*/
|
||||
constructor(public data: Partial<APIButtonComponent> = {}) {
|
||||
this.data.type = ComponentType.Button;
|
||||
export class Button extends BaseComponentBuilder<APIButtonComponent> {
|
||||
constructor(data: Partial<APIButtonComponent> = {}) {
|
||||
super({ type: ComponentType.Button, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -76,12 +73,4 @@ export class Button {
|
||||
(this.data as Extract<APIButtonComponent, { sku_id?: string }>).sku_id = skuId;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the Button instance to its JSON representation.
|
||||
* @returns The JSON representation of the Button instance.
|
||||
*/
|
||||
toJSON() {
|
||||
return { ...this.data } as Partial<APIButtonComponent>;
|
||||
}
|
||||
}
|
||||
|
100
src/builders/Container.ts
Normal file
100
src/builders/Container.ts
Normal file
@ -0,0 +1,100 @@
|
||||
import { type ActionRow, fromComponent } from '.';
|
||||
import { type ColorResolvable, type RestOrArray, resolveColor } from '../common';
|
||||
import { type APIContainerComponent, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
import type { File } from './File';
|
||||
import type { MediaGallery } from './MediaGallery';
|
||||
import type { Section } from './Section';
|
||||
import type { Separator } from './Separator';
|
||||
import type { TextDisplay } from './TextDisplay';
|
||||
|
||||
/**
|
||||
* Represents the possible component types that can be added to a Container.
|
||||
*/
|
||||
export type ContainerBuilderComponents = ActionRow | TextDisplay | Section | MediaGallery | Separator | File;
|
||||
|
||||
/**
|
||||
* Represents a container component builder.
|
||||
* Containers group other components together.
|
||||
* @example
|
||||
* ```ts
|
||||
* const container = new Container()
|
||||
* .addComponents(
|
||||
* new TextDisplay('This is text inside a container!'),
|
||||
* new ActionRow().addComponents(new Button().setLabel('Click me!'))
|
||||
* )
|
||||
* .setColor('Blue');
|
||||
* ```
|
||||
*/
|
||||
export class Container extends BaseComponentBuilder<APIContainerComponent> {
|
||||
/**
|
||||
* The components held within this container.
|
||||
*/
|
||||
components: ContainerBuilderComponents[];
|
||||
|
||||
/**
|
||||
* Constructs a new Container.
|
||||
* @param data Optional initial data for the container.
|
||||
*/
|
||||
constructor({ components, ...data }: Partial<APIContainerComponent> = {}) {
|
||||
super({ ...data, type: ComponentType.Container });
|
||||
this.components = (components?.map(fromComponent) ?? []) as ContainerBuilderComponents[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to the container.
|
||||
* @param components The components to add. Can be a single component, an array of components, or multiple components as arguments.
|
||||
* @returns The updated Container instance.
|
||||
*/
|
||||
addComponents(...components: RestOrArray<ContainerBuilderComponents>) {
|
||||
this.components = this.components.concat(components.flat());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components for the container, replacing any existing components.
|
||||
* @param components The components to set. Can be a single component, an array of components, or multiple components as arguments.
|
||||
* @returns The updated Container instance.
|
||||
*/
|
||||
setComponents(...components: RestOrArray<ContainerBuilderComponents>) {
|
||||
this.components = components.flat();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the container's content should be visually marked as a spoiler.
|
||||
* @param spoiler Whether the content is a spoiler (defaults to true).
|
||||
* @returns The updated Container instance.
|
||||
*/
|
||||
setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the accent color for the container.
|
||||
* @param color The color resolvable (e.g., hex code, color name, integer).
|
||||
* @returns The updated Container instance.
|
||||
*/
|
||||
setColor(color: ColorResolvable) {
|
||||
this.data.accent_color = resolveColor(color);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID for the container.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated Container instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map(c => c.toJSON()),
|
||||
} as APIContainerComponent;
|
||||
}
|
||||
}
|
52
src/builders/File.ts
Normal file
52
src/builders/File.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { type APIFileComponent, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a file component builder.
|
||||
* Used to display files within containers.
|
||||
* @example
|
||||
* ```ts
|
||||
* const file = new File()
|
||||
* .setMedia('https://example.com/image.png')
|
||||
* .setSpoiler();
|
||||
* ```
|
||||
*/
|
||||
export class File extends BaseComponentBuilder<APIFileComponent> {
|
||||
/**
|
||||
* Constructs a new File component.
|
||||
* @param data Optional initial data for the file component.
|
||||
*/
|
||||
constructor(data: Partial<APIFileComponent> = {}) {
|
||||
super({ type: ComponentType.File, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID for the file component.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated File instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media URL for the file.
|
||||
* @param url The URL of the file to display.
|
||||
* @returns The updated File instance.
|
||||
*/
|
||||
setMedia(url: string) {
|
||||
this.data.file = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the file should be visually marked as a spoiler.
|
||||
* @param spoiler Whether the file is a spoiler (defaults to true).
|
||||
* @returns The updated File instance.
|
||||
*/
|
||||
setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoiler;
|
||||
return this;
|
||||
}
|
||||
}
|
112
src/builders/MediaGallery.ts
Normal file
112
src/builders/MediaGallery.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import type { RestOrArray } from '../common';
|
||||
import { type APIMediaGalleryComponent, type APIMediaGalleryItems, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a media gallery component builder.
|
||||
* Used to display a collection of media items.
|
||||
* @example
|
||||
* ```ts
|
||||
* const gallery = new MediaGallery()
|
||||
* .addItems(
|
||||
* new MediaGalleryItem().setMedia('https://example.com/image1.png').setDescription('Image 1'),
|
||||
* new MediaGalleryItem().setMedia('https://example.com/image2.jpg').setSpoiler()
|
||||
* );
|
||||
* ```
|
||||
*/
|
||||
export class MediaGallery extends BaseComponentBuilder<APIMediaGalleryComponent> {
|
||||
items: MediaGalleryItem[];
|
||||
/**
|
||||
* Constructs a new MediaGallery.
|
||||
* @param data Optional initial data for the media gallery.
|
||||
*/
|
||||
constructor({ items, ...data }: Partial<APIMediaGalleryComponent> = {}) {
|
||||
super({ type: ComponentType.MediaGallery, ...data });
|
||||
this.items = (items?.map(i => new MediaGalleryItem(i)) ?? []) as MediaGalleryItem[];
|
||||
}
|
||||
/**
|
||||
* Sets the ID for the media gallery component.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated MediaGallery instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds items to the media gallery.
|
||||
* @param items The items to add. Can be a single item, an array of items, or multiple items as arguments.
|
||||
* @returns The updated MediaGallery instance.
|
||||
*/
|
||||
addItems(...items: RestOrArray<MediaGalleryItem>) {
|
||||
this.items = this.items.concat(items.flat());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the items for the media gallery, replacing any existing items.
|
||||
* @param items The items to set. Can be a single item, an array of items, or multiple items as arguments.
|
||||
* @returns The updated MediaGallery instance.
|
||||
*/
|
||||
setItems(...items: RestOrArray<MediaGalleryItem>) {
|
||||
this.items = items.flat();
|
||||
return this;
|
||||
}
|
||||
|
||||
toJSON() {
|
||||
return {
|
||||
...this.data,
|
||||
items: this.items.map(i => i.toJSON()),
|
||||
} as APIMediaGalleryComponent;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an item within a MediaGallery.
|
||||
*/
|
||||
export class MediaGalleryItem {
|
||||
/**
|
||||
* Constructs a new MediaGalleryItem.
|
||||
* @param data Optional initial data for the media gallery item.
|
||||
*/
|
||||
constructor(public data: Partial<APIMediaGalleryItems> = {}) {}
|
||||
|
||||
/**
|
||||
* Sets the media URL for this gallery item.
|
||||
* @param url The URL of the media.
|
||||
* @returns The updated MediaGalleryItem instance.
|
||||
*/
|
||||
setMedia(url: string) {
|
||||
this.data.media = { url };
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description for this gallery item.
|
||||
* @param desc The description text.
|
||||
* @returns The updated MediaGalleryItem instance.
|
||||
*/
|
||||
setDescription(desc: string) {
|
||||
this.data.description = desc;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this gallery item should be visually marked as a spoiler.
|
||||
* @param spoiler Whether the item is a spoiler (defaults to true).
|
||||
* @returns The updated MediaGalleryItem instance.
|
||||
*/
|
||||
setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this MediaGalleryItem instance to its JSON representation.
|
||||
* @returns The JSON representation of the item data.
|
||||
*/
|
||||
toJSON() {
|
||||
return { ...this.data };
|
||||
}
|
||||
}
|
55
src/builders/Section.ts
Normal file
55
src/builders/Section.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import { type Button, fromComponent } from '.';
|
||||
import type { RestOrArray } from '../common';
|
||||
import { type APISectionComponent, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
import type { TextDisplay } from './TextDisplay';
|
||||
import type { Thumbnail } from './Thumbnail';
|
||||
|
||||
export class Section<
|
||||
Ac extends Button | Thumbnail = Button | Thumbnail,
|
||||
> extends BaseComponentBuilder<APISectionComponent> {
|
||||
components: TextDisplay[];
|
||||
accessory!: Ac;
|
||||
constructor({ components, accessory, ...data }: Partial<APISectionComponent> = {}) {
|
||||
super({ type: ComponentType.Section, ...data });
|
||||
this.components = (components?.map(component => fromComponent(component)) ?? []) as TextDisplay[];
|
||||
if (accessory) this.accessory = fromComponent(accessory) as Ac;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds components to this section.
|
||||
* @param components The components to add
|
||||
* @example section.addComponents(new TextDisplay().content('Hello'));
|
||||
*/
|
||||
addComponents(...components: RestOrArray<TextDisplay>) {
|
||||
this.components = this.components.concat(components.flat());
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the components for this section.
|
||||
* @param components The components to set
|
||||
* @example section.setComponents(new TextDisplay().content('Hello'));
|
||||
*/
|
||||
setComponents(...components: RestOrArray<TextDisplay>) {
|
||||
this.components = components.flat();
|
||||
return this;
|
||||
}
|
||||
|
||||
setAccessory(accessory: Ac) {
|
||||
this.accessory = accessory;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this section to JSON.
|
||||
* @returns The JSON representation of this section
|
||||
*/
|
||||
toJSON() {
|
||||
return {
|
||||
...this.data,
|
||||
components: this.components.map(component => component.toJSON()),
|
||||
accessory: this.accessory.toJSON(),
|
||||
} as APISectionComponent;
|
||||
}
|
||||
}
|
54
src/builders/Separator.ts
Normal file
54
src/builders/Separator.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { type APISeparatorComponent, ComponentType, type Spacing } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a separator component builder.
|
||||
* Used to add visual spacing or dividers between components.
|
||||
* @example
|
||||
* ```ts
|
||||
* // A simple separator for spacing
|
||||
* const spacingSeparator = new Separator().setSpacing(Spacing.Small);
|
||||
*
|
||||
* // A separator acting as a visual divider
|
||||
* const dividerSeparator = new Separator().setDivider(true);
|
||||
* ```
|
||||
*/
|
||||
export class Separator extends BaseComponentBuilder<APISeparatorComponent> {
|
||||
/**
|
||||
* Constructs a new Separator component.
|
||||
* @param data Optional initial data for the separator component.
|
||||
*/
|
||||
constructor(data: Partial<APISeparatorComponent> = {}) {
|
||||
super({ type: ComponentType.Separator, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID for the separator component.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated Separator instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether this separator should act as a visual divider.
|
||||
* @param divider Whether to render as a divider (defaults to false).
|
||||
* @returns The updated Separator instance.
|
||||
*/
|
||||
setDivider(divider = false) {
|
||||
this.data.divider = divider;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the amount of spacing this separator provides.
|
||||
* @param spacing The desired spacing level ('None', 'Small', 'Medium', 'Large').
|
||||
* @returns The updated Separator instance.
|
||||
*/
|
||||
setSpacing(spacing: Spacing) {
|
||||
this.data.spacing = spacing;
|
||||
return this;
|
||||
}
|
||||
}
|
40
src/builders/TextDisplay.ts
Normal file
40
src/builders/TextDisplay.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { type APITextDispalyComponent, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a text display component builder.
|
||||
* Used to display simple text content.
|
||||
* @example
|
||||
* ```ts
|
||||
* const text = new TextDisplay().content('Hello, world!');
|
||||
* ```
|
||||
*/
|
||||
export class TextDisplay extends BaseComponentBuilder<APITextDispalyComponent> {
|
||||
/**
|
||||
* Constructs a new TextDisplay component.
|
||||
* @param data Optional initial data for the text display component.
|
||||
*/
|
||||
constructor(data: Partial<APITextDispalyComponent> = {}) {
|
||||
super({ type: ComponentType.TextDisplay, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID for the text display component.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated TextDisplay instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the text content to display.
|
||||
* @param content The text content.
|
||||
* @returns The updated TextDisplay instance.
|
||||
*/
|
||||
setContent(content: string) {
|
||||
this.data.content = content;
|
||||
return this;
|
||||
}
|
||||
}
|
62
src/builders/Thumbnail.ts
Normal file
62
src/builders/Thumbnail.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { type APIThumbnailComponent, ComponentType } from '../types';
|
||||
import { BaseComponentBuilder } from './Base';
|
||||
|
||||
/**
|
||||
* Represents a thumbnail component builder.
|
||||
* Used to display a small image preview, often alongside other content.
|
||||
* @example
|
||||
* ```ts
|
||||
* const thumbnail = new Thumbnail()
|
||||
* .setMedia('https://example.com/thumbnail.jpg')
|
||||
* .setDescription('A cool thumbnail');
|
||||
* ```
|
||||
*/
|
||||
export class Thumbnail extends BaseComponentBuilder<APIThumbnailComponent> {
|
||||
/**
|
||||
* Constructs a new Thumbnail component.
|
||||
* @param data Optional initial data for the thumbnail component.
|
||||
*/
|
||||
constructor(data: Partial<APIThumbnailComponent> = {}) {
|
||||
super({ type: ComponentType.Thumbnail, ...data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the thumbnail should be visually marked as a spoiler.
|
||||
* @param spoiler Whether the thumbnail is a spoiler (defaults to true).
|
||||
* @returns The updated Thumbnail instance.
|
||||
*/
|
||||
setSpoiler(spoiler = true) {
|
||||
this.data.spoiler = spoiler;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the description for the thumbnail.
|
||||
* @param description The description text. Can be undefined to remove the description.
|
||||
* @returns The updated Thumbnail instance.
|
||||
*/
|
||||
setDescription(description: string | undefined) {
|
||||
this.data.description = description;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the ID for the thumbnail component.
|
||||
* @param id The ID to set.
|
||||
* @returns The updated Thumbnail instance.
|
||||
*/
|
||||
setId(id: number) {
|
||||
this.data.id = id;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the media URL for the thumbnail.
|
||||
* @param url The URL of the image to display as a thumbnail.
|
||||
* @returns The updated Thumbnail instance.
|
||||
*/
|
||||
setMedia(url: string) {
|
||||
this.data.media = { url };
|
||||
return this;
|
||||
}
|
||||
}
|
@ -1,7 +1,11 @@
|
||||
import { type APIActionRowComponent, type APIActionRowComponentTypes, ComponentType } from '../types';
|
||||
import { type APIComponents, ComponentType } from '../types';
|
||||
import { ActionRow } from './ActionRow';
|
||||
import { Button } from './Button';
|
||||
import { Container } from './Container';
|
||||
import { File } from './File';
|
||||
import { MediaGallery } from './MediaGallery';
|
||||
import { TextInput } from './Modal';
|
||||
import { Section } from './Section';
|
||||
import {
|
||||
ChannelSelectMenu,
|
||||
MentionableSelectMenu,
|
||||
@ -9,29 +13,32 @@ import {
|
||||
StringSelectMenu,
|
||||
UserSelectMenu,
|
||||
} from './SelectMenu';
|
||||
import { Separator } from './Separator';
|
||||
import { TextDisplay } from './TextDisplay';
|
||||
import { Thumbnail } from './Thumbnail';
|
||||
import type { BuilderComponents } from './types';
|
||||
|
||||
export * from './ActionRow';
|
||||
export * from './Attachment';
|
||||
export * from './Base';
|
||||
export * from './Button';
|
||||
export * from './Container';
|
||||
export * from './Embed';
|
||||
export * from './File';
|
||||
export * from './MediaGallery';
|
||||
export * from './Modal';
|
||||
export * from './SelectMenu';
|
||||
export * from './Poll';
|
||||
export * from './Section';
|
||||
export * from './SelectMenu';
|
||||
export * from './Separator';
|
||||
export * from './TextDisplay';
|
||||
export * from './Thumbnail';
|
||||
export * from './types';
|
||||
|
||||
export function fromComponent(
|
||||
data:
|
||||
| BuilderComponents
|
||||
| APIActionRowComponentTypes
|
||||
| APIActionRowComponent<APIActionRowComponentTypes>
|
||||
| ActionRow<BuilderComponents>,
|
||||
): BuilderComponents | ActionRow<BuilderComponents> {
|
||||
export function fromComponent(data: BuilderComponents | APIComponents): BuilderComponents {
|
||||
if ('toJSON' in data) {
|
||||
return data;
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case ComponentType.Button:
|
||||
return new Button(data);
|
||||
@ -49,5 +56,19 @@ export function fromComponent(
|
||||
return new ChannelSelectMenu(data);
|
||||
case ComponentType.ActionRow:
|
||||
return new ActionRow(data);
|
||||
case ComponentType.Section:
|
||||
return new Section(data);
|
||||
case ComponentType.TextDisplay:
|
||||
return new TextDisplay(data);
|
||||
case ComponentType.Thumbnail:
|
||||
return new Thumbnail(data);
|
||||
case ComponentType.Container:
|
||||
return new Container(data);
|
||||
case ComponentType.MediaGallery:
|
||||
return new MediaGallery(data);
|
||||
case ComponentType.Separator:
|
||||
return new Separator(data);
|
||||
case ComponentType.File:
|
||||
return new File(data);
|
||||
}
|
||||
}
|
||||
|
@ -3,9 +3,17 @@ import type {
|
||||
ModalSubmitInteraction,
|
||||
StringSelectMenuInteraction,
|
||||
} from '../structures/Interaction';
|
||||
import type { ActionRow } from './ActionRow';
|
||||
import type { Button } from './Button';
|
||||
import type { Container } from './Container';
|
||||
import type { File } from './File';
|
||||
import type { MediaGallery } from './MediaGallery';
|
||||
import type { TextInput } from './Modal';
|
||||
import type { Section } from './Section';
|
||||
import type { BuilderSelectMenus } from './SelectMenu';
|
||||
import type { Separator } from './Separator';
|
||||
import type { TextDisplay } from './TextDisplay';
|
||||
import type { Thumbnail } from './Thumbnail';
|
||||
|
||||
export type ComponentCallback<
|
||||
T extends ComponentInteraction | StringSelectMenuInteraction = ComponentInteraction | StringSelectMenuInteraction,
|
||||
@ -25,7 +33,22 @@ export type ButtonID = Omit<Button, 'setURL'>;
|
||||
|
||||
export type MessageBuilderComponents = FixedComponents<Button> | BuilderSelectMenus;
|
||||
export type ModalBuilderComponents = TextInput;
|
||||
export type BuilderComponents = MessageBuilderComponents | TextInput;
|
||||
export type ActionBuilderComponents = MessageBuilderComponents | TextInput;
|
||||
|
||||
export type BuilderComponents =
|
||||
| ActionRow
|
||||
| ActionBuilderComponents
|
||||
| Section<Button | Thumbnail>
|
||||
| Thumbnail
|
||||
| TextDisplay
|
||||
| Container
|
||||
| Separator
|
||||
| MediaGallery
|
||||
| File
|
||||
| TextInput;
|
||||
|
||||
export type TopLevelBuilders = Exclude<BuilderComponents, Thumbnail | TextInput>;
|
||||
|
||||
export type FixedComponents<T = Button> = T extends Button ? ButtonLink | ButtonID : T;
|
||||
export interface ListenerOptions {
|
||||
timeout?: number;
|
||||
|
6
src/cache/resources/guilds.ts
vendored
6
src/cache/resources/guilds.ts
vendored
@ -18,7 +18,7 @@ export class Guilds extends BaseResource<any, APIGuild | GatewayGuildCreateDispa
|
||||
);
|
||||
}
|
||||
|
||||
raw(id: string): ReturnCache<APIGuild | undefined> {
|
||||
raw(id: string): ReturnCache<(APIGuild & { member_count?: number }) | undefined> {
|
||||
return super.get(id);
|
||||
}
|
||||
|
||||
@ -28,7 +28,7 @@ export class Guilds extends BaseResource<any, APIGuild | GatewayGuildCreateDispa
|
||||
);
|
||||
}
|
||||
|
||||
bulkRaw(ids: string[]): ReturnCache<APIGuild[]> {
|
||||
bulkRaw(ids: string[]): ReturnCache<(APIGuild & { member_count?: number })[]> {
|
||||
return super.bulk(ids);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ export class Guilds extends BaseResource<any, APIGuild | GatewayGuildCreateDispa
|
||||
);
|
||||
}
|
||||
|
||||
valuesRaw(): ReturnCache<APIGuild[]> {
|
||||
valuesRaw(): ReturnCache<(APIGuild & { member_count?: number })[]> {
|
||||
return super.values();
|
||||
}
|
||||
|
||||
|
36
src/cache/resources/members.ts
vendored
36
src/cache/resources/members.ts
vendored
@ -1,7 +1,7 @@
|
||||
import type { CacheFrom, ReturnCache } from '../..';
|
||||
import { type GuildMemberStructure, Transformers } from '../../client/transformers';
|
||||
import { fakePromise } from '../../common';
|
||||
import type { APIGuildMember } from '../../types';
|
||||
import type { APIGuildMember, APIUser } from '../../types';
|
||||
import { GuildBasedResource } from './default/guild-based';
|
||||
export class Members extends GuildBasedResource<any, APIGuildMember> {
|
||||
namespace = 'member';
|
||||
@ -39,14 +39,21 @@ export class Members extends GuildBasedResource<any, APIGuildMember> {
|
||||
|
||||
override bulk(ids: string[], guild: string): ReturnCache<GuildMemberStructure[]> {
|
||||
return fakePromise(super.bulk(ids, guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.bulkRaw(ids)).then(users =>
|
||||
members
|
||||
fakePromise(this.client.cache.users?.bulkRaw(ids)).then(users => {
|
||||
if (!users) return [];
|
||||
let usersRecord: null | Partial<Record<string, APIUser>> = {};
|
||||
for (const user of users) {
|
||||
usersRecord[user.id] = user;
|
||||
}
|
||||
const result = members
|
||||
.map(rawMember => {
|
||||
const user = users?.find(x => x.id === rawMember.id);
|
||||
const user = usersRecord![rawMember.id];
|
||||
return user ? Transformers.GuildMember(this.client, rawMember, user, guild) : undefined;
|
||||
})
|
||||
.filter(x => x !== undefined),
|
||||
),
|
||||
.filter(x => x !== undefined);
|
||||
usersRecord = null;
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@ -56,14 +63,21 @@ export class Members extends GuildBasedResource<any, APIGuildMember> {
|
||||
|
||||
override values(guild: string): ReturnCache<GuildMemberStructure[]> {
|
||||
return fakePromise(super.values(guild)).then(members =>
|
||||
fakePromise(this.client.cache.users?.valuesRaw()).then(users =>
|
||||
members
|
||||
fakePromise(this.client.cache.users?.bulkRaw(members.map(member => member.id))).then(users => {
|
||||
if (!users) return [];
|
||||
let usersRecord: null | Partial<Record<string, APIUser>> = {};
|
||||
for (const user of users) {
|
||||
usersRecord[user.id] = user;
|
||||
}
|
||||
const result = members
|
||||
.map(rawMember => {
|
||||
const user = users?.find(x => x.id === rawMember.id);
|
||||
const user = usersRecord![rawMember.id];
|
||||
return user ? Transformers.GuildMember(this.client, rawMember, user, rawMember.guild_id) : undefined;
|
||||
})
|
||||
.filter(x => x !== undefined),
|
||||
),
|
||||
.filter(x => x !== undefined);
|
||||
usersRecord = null;
|
||||
return result;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -470,6 +470,8 @@ export interface BaseClientOptions {
|
||||
globalMiddlewares?: readonly (keyof RegisteredMiddlewares)[];
|
||||
commands?: {
|
||||
defaults?: {
|
||||
onBeforeMiddlewares?: (context: CommandContext | MenuCommandContext<any, never>) => unknown;
|
||||
onBeforeOptions?: Command['onBeforeOptions'];
|
||||
onRunError?: (context: MenuCommandContext<any, never> | CommandContext, error: unknown) => unknown;
|
||||
onPermissionsFail?: Command['onPermissionsFail'];
|
||||
onBotPermissionsFail?: (
|
||||
@ -489,6 +491,7 @@ export interface BaseClientOptions {
|
||||
};
|
||||
components?: {
|
||||
defaults?: {
|
||||
onBeforeMiddlewares?: ComponentCommand['onBeforeMiddlewares'];
|
||||
onRunError?: ComponentCommand['onRunError'];
|
||||
onInternalError?: ComponentCommand['onInternalError'];
|
||||
onMiddlewaresError?: ComponentCommand['onMiddlewaresError'];
|
||||
@ -497,6 +500,7 @@ export interface BaseClientOptions {
|
||||
};
|
||||
modals?: {
|
||||
defaults?: {
|
||||
onBeforeMiddlewares?: ModalCommand['onBeforeMiddlewares'];
|
||||
onRunError?: ModalCommand['onRunError'];
|
||||
onInternalError?: ModalCommand['onInternalError'];
|
||||
onMiddlewaresError?: ModalCommand['onMiddlewaresError'];
|
||||
|
@ -285,6 +285,8 @@ export class BaseCommand {
|
||||
Object.setPrototypeOf(this, __tempCommand.prototype);
|
||||
}
|
||||
|
||||
onBeforeMiddlewares?(context: CommandContext): any;
|
||||
onBeforeOptions?(context: CommandContext): any;
|
||||
run?(context: CommandContext): any;
|
||||
onAfterRun?(context: CommandContext, error: unknown | undefined): any;
|
||||
onRunError?(context: CommandContext, error: unknown): any;
|
||||
|
@ -54,6 +54,7 @@ export abstract class EntryPointCommand {
|
||||
Object.setPrototypeOf(this, __tempCommand.prototype);
|
||||
}
|
||||
|
||||
onBeforeMiddlewares?(context: EntryPointContext): any;
|
||||
abstract run?(context: EntryPointContext): any;
|
||||
onAfterRun?(context: EntryPointContext, error: unknown | undefined): any;
|
||||
onRunError(context: EntryPointContext<never>, error: unknown): any {
|
||||
|
@ -53,6 +53,7 @@ export abstract class ContextMenuCommand {
|
||||
Object.setPrototypeOf(this, __tempCommand.prototype);
|
||||
}
|
||||
|
||||
onBeforeMiddlewares?(context: MenuCommandContext<any>): any;
|
||||
abstract run?(context: MenuCommandContext<any>): any;
|
||||
onAfterRun?(context: MenuCommandContext<any>, error: unknown | undefined): any;
|
||||
onRunError?(context: MenuCommandContext<any, never>, error: unknown): any;
|
||||
|
@ -91,7 +91,7 @@ export class HandleCommand {
|
||||
await optionsResolver.getCommand()?.onInternalError?.(this.client, optionsResolver.getCommand()!, error);
|
||||
}
|
||||
} catch (error) {
|
||||
// pass
|
||||
this.client.logger.error(`[${optionsResolver.fullCommandName}] Internal error:`, error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,17 +100,18 @@ export class HandleCommand {
|
||||
interaction: MessageCommandInteraction | UserCommandInteraction,
|
||||
context: MenuCommandContext<MessageCommandInteraction | UserCommandInteraction>,
|
||||
) {
|
||||
if (context.guildId && command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return command.onBotPermissionsFail?.(context, permissions);
|
||||
}
|
||||
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
if (context.guildId && command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return await command.onBotPermissionsFail?.(context, permissions);
|
||||
}
|
||||
|
||||
await command.onBeforeMiddlewares?.(context);
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
await command.run!(context);
|
||||
await command.onAfterRun?.(context, undefined);
|
||||
@ -121,8 +122,8 @@ export class HandleCommand {
|
||||
} catch (error) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command, error);
|
||||
} catch {
|
||||
// pass
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -144,17 +145,18 @@ export class HandleCommand {
|
||||
}
|
||||
|
||||
async entryPoint(command: EntryPointCommand, interaction: EntryPointInteraction, context: EntryPointContext) {
|
||||
if (context.guildId && command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return command.onBotPermissionsFail(context, permissions);
|
||||
}
|
||||
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
if (context.guildId && command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return await command.onBotPermissionsFail(context, permissions);
|
||||
}
|
||||
|
||||
await command.onBeforeMiddlewares?.(context);
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
await command.run!(context);
|
||||
await command.onAfterRun?.(context, undefined);
|
||||
@ -165,8 +167,8 @@ export class HandleCommand {
|
||||
} catch (error) {
|
||||
try {
|
||||
await command.onInternalError(this.client, command, error);
|
||||
} catch {
|
||||
// pass
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -177,26 +179,28 @@ export class HandleCommand {
|
||||
resolver: OptionResolverStructure,
|
||||
context: CommandContext,
|
||||
) {
|
||||
if (context.guildId) {
|
||||
if (command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return command.onBotPermissionsFail?.(context, permissions);
|
||||
}
|
||||
|
||||
if (command.defaultMemberPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.member!.permissions, command.defaultMemberPermissions);
|
||||
if (permissions) return command.onPermissionsFail?.(context, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
if (!(await this.runOptions(command, context, resolver))) return;
|
||||
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
if (context.guildId) {
|
||||
if (command.botPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.appPermissions, command.botPermissions);
|
||||
if (permissions) return await command.onBotPermissionsFail?.(context, permissions);
|
||||
}
|
||||
|
||||
if (command.defaultMemberPermissions) {
|
||||
const permissions = this.checkPermissions(interaction.member!.permissions, command.defaultMemberPermissions);
|
||||
if (permissions) return await command.onPermissionsFail?.(context, permissions);
|
||||
}
|
||||
}
|
||||
|
||||
await command.onBeforeOptions?.(context);
|
||||
if (!(await this.runOptions(command, context, resolver))) return;
|
||||
|
||||
await command.onBeforeMiddlewares?.(context);
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
if (typeof resultMiddle === 'boolean') return;
|
||||
|
||||
try {
|
||||
await command.run!(context);
|
||||
await command.onAfterRun?.(context, undefined);
|
||||
@ -207,8 +211,8 @@ export class HandleCommand {
|
||||
} catch (error) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command, error);
|
||||
} catch {
|
||||
// pass
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -350,17 +354,17 @@ export class HandleCommand {
|
||||
attachments: {},
|
||||
};
|
||||
|
||||
const args = this.argsParser(argsContent, command, message);
|
||||
const { options, errors } = await this.argsOptionsParser(command, rawMessage, args, resolved);
|
||||
const optionsResolver = this.makeResolver(self, options, parent as Command, rawMessage.guild_id, resolved);
|
||||
const context = new CommandContext(self, message, optionsResolver, shardId, command);
|
||||
//@ts-expect-error
|
||||
const extendContext = self.options?.context?.(message) ?? {};
|
||||
Object.assign(context, extendContext);
|
||||
|
||||
try {
|
||||
const args = this.argsParser(argsContent, command, message);
|
||||
const { options, errors } = await this.argsOptionsParser(command, rawMessage, args, resolved);
|
||||
const optionsResolver = this.makeResolver(self, options, parent as Command, rawMessage.guild_id, resolved);
|
||||
const context = new CommandContext(self, message, optionsResolver, shardId, command);
|
||||
//@ts-expect-error
|
||||
const extendContext = self.options?.context?.(message) ?? {};
|
||||
Object.assign(context, extendContext);
|
||||
|
||||
if (errors.length) {
|
||||
return command.onOptionsError?.(
|
||||
return await command.onOptionsError?.(
|
||||
context,
|
||||
Object.fromEntries(
|
||||
errors.map(x => {
|
||||
@ -383,7 +387,7 @@ export class HandleCommand {
|
||||
const permissions = this.checkPermissions(memberPermissions, command.defaultMemberPermissions);
|
||||
const guild = await this.client.guilds.raw(rawMessage.guild_id);
|
||||
if (permissions && guild.owner_id !== rawMessage.author.id) {
|
||||
return command.onPermissionsFail?.(context, memberPermissions.keys(permissions));
|
||||
return await command.onPermissionsFail?.(context, memberPermissions.keys(permissions));
|
||||
}
|
||||
}
|
||||
|
||||
@ -391,13 +395,15 @@ export class HandleCommand {
|
||||
const appPermissions = await self.members.permissions(rawMessage.guild_id, self.botId);
|
||||
const permissions = this.checkPermissions(appPermissions, command.botPermissions);
|
||||
if (permissions) {
|
||||
return command.onBotPermissionsFail?.(context, permissions);
|
||||
return await command.onBotPermissionsFail?.(context, permissions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await command.onBeforeOptions?.(context);
|
||||
if (!(await this.runOptions(command, context, optionsResolver))) return;
|
||||
|
||||
await command.onBeforeMiddlewares?.(context);
|
||||
const resultGlobal = await this.runGlobalMiddlewares(command, context);
|
||||
if (typeof resultGlobal === 'boolean') return;
|
||||
const resultMiddle = await this.runMiddlewares(command, context);
|
||||
@ -412,8 +418,8 @@ export class HandleCommand {
|
||||
} catch (error) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command, error);
|
||||
} catch {
|
||||
// http 418
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -574,8 +580,8 @@ export class HandleCommand {
|
||||
} catch (e) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command as never, e);
|
||||
} catch {
|
||||
// http 418
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -602,8 +608,8 @@ export class HandleCommand {
|
||||
} catch (e) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command as never, e);
|
||||
} catch {
|
||||
// http 418
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${command.name}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -627,15 +633,7 @@ export class HandleCommand {
|
||||
async runOptions(command: Command | SubCommand, context: CommandContext, resolver: OptionResolverStructure) {
|
||||
const [erroredOptions, result] = await command.__runOptions(context, resolver);
|
||||
if (erroredOptions) {
|
||||
try {
|
||||
await command.onOptionsError?.(context, result);
|
||||
} catch (e) {
|
||||
try {
|
||||
await command.onInternalError?.(this.client, command, e);
|
||||
} catch {
|
||||
// http 418
|
||||
}
|
||||
}
|
||||
await command.onOptionsError?.(context, result);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -466,6 +466,8 @@ export class CommandHandler extends BaseHandler {
|
||||
|
||||
stablishContextCommandDefaults(commandInstance: InstanceType<HandleableCommand>): ContextMenuCommand | false {
|
||||
if (!(commandInstance instanceof ContextMenuCommand)) return false;
|
||||
commandInstance.onBeforeMiddlewares ??= this.client.options.commands?.defaults?.onBeforeMiddlewares;
|
||||
|
||||
commandInstance.onAfterRun ??= this.client.options.commands?.defaults?.onAfterRun;
|
||||
|
||||
commandInstance.onBotPermissionsFail ??= this.client.options.commands?.defaults?.onBotPermissionsFail;
|
||||
@ -482,6 +484,8 @@ export class CommandHandler extends BaseHandler {
|
||||
commandInstance: InstanceType<HandleableCommand>,
|
||||
): OmitInsert<Command, 'options', { options: NonNullable<Command['options']> }> | false {
|
||||
if (!(commandInstance instanceof Command)) return false;
|
||||
commandInstance.onBeforeMiddlewares ??= this.client.options.commands?.defaults?.onBeforeMiddlewares;
|
||||
commandInstance.onBeforeOptions ??= this.client.options.commands?.defaults?.onBeforeOptions;
|
||||
commandInstance.onAfterRun ??= this.client.options.commands?.defaults?.onAfterRun;
|
||||
commandInstance.onBotPermissionsFail ??= this.client.options.commands?.defaults?.onBotPermissionsFail;
|
||||
commandInstance.onInternalError ??= this.client.options.commands?.defaults?.onInternalError;
|
||||
@ -495,6 +499,14 @@ export class CommandHandler extends BaseHandler {
|
||||
|
||||
stablishSubCommandDefaults(commandInstance: Command, option: SubCommand): SubCommand {
|
||||
option.middlewares = (commandInstance.middlewares ?? []).concat(option.middlewares ?? []);
|
||||
option.onBeforeMiddlewares =
|
||||
option.onBeforeMiddlewares?.bind(option) ??
|
||||
commandInstance.onBeforeMiddlewares?.bind(commandInstance) ??
|
||||
this.client.options.commands?.defaults?.onBeforeMiddlewares;
|
||||
option.onBeforeOptions =
|
||||
option.onBeforeOptions?.bind(option) ??
|
||||
commandInstance.onBeforeOptions?.bind(commandInstance) ??
|
||||
this.client.options.commands?.defaults?.onBeforeOptions;
|
||||
option.onMiddlewaresError =
|
||||
option.onMiddlewaresError?.bind(option) ??
|
||||
commandInstance.onMiddlewaresError?.bind(commandInstance) ??
|
||||
|
@ -1,20 +1,11 @@
|
||||
import type { RawFile } from '../../api';
|
||||
import type { Attachment, AttachmentBuilder, Embed, Modal, PollBuilder, TopLevelBuilders } from '../../builders';
|
||||
import type {
|
||||
ActionRow,
|
||||
Attachment,
|
||||
AttachmentBuilder,
|
||||
BuilderComponents,
|
||||
Embed,
|
||||
Modal,
|
||||
PollBuilder,
|
||||
} from '../../builders';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIEmbed,
|
||||
APIInteractionResponseCallbackData,
|
||||
APIInteractionResponseChannelMessageWithSource,
|
||||
APIMessageActionRowComponent,
|
||||
APIModalInteractionResponse,
|
||||
MessageFlags,
|
||||
RESTAPIPollCreate,
|
||||
RESTPatchAPIChannelMessageJSONBody,
|
||||
RESTPatchAPIWebhookWithTokenMessageJSONBody,
|
||||
@ -26,7 +17,7 @@ import type { OmitInsert } from './util';
|
||||
|
||||
export interface ResolverProps {
|
||||
embeds?: Embed[] | APIEmbed[] | undefined;
|
||||
components?: APIActionRowComponent<APIMessageActionRowComponent>[] | ActionRow<BuilderComponents>[] | undefined;
|
||||
components?: TopLevelBuilders[];
|
||||
files?: AttachmentBuilder[] | Attachment[] | RawFile[] | undefined;
|
||||
}
|
||||
|
||||
@ -62,7 +53,9 @@ export type InteractionMessageUpdateBodyRequest = OmitInsert<
|
||||
RESTPatchAPIWebhookWithTokenMessageJSONBody,
|
||||
'components' | 'embeds' | 'poll',
|
||||
SendResolverProps
|
||||
>;
|
||||
> & {
|
||||
flags?: MessageFlags;
|
||||
};
|
||||
|
||||
export type ComponentInteractionMessageUpdate = OmitInsert<
|
||||
APIInteractionResponseCallbackData,
|
||||
|
@ -4,7 +4,7 @@ import type { ActionRowMessageComponents } from './index';
|
||||
import { componentFactory } from './index';
|
||||
|
||||
export class MessageActionRowComponent<
|
||||
T extends ActionRowMessageComponents,
|
||||
T extends ActionRowMessageComponents = ActionRowMessageComponents,
|
||||
> extends BaseComponent<ComponentType.ActionRow> {
|
||||
private ComponentsFactory: T[];
|
||||
constructor(data: {
|
||||
|
@ -1,13 +1,37 @@
|
||||
import { fromComponent } from '../builders';
|
||||
import {
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type Container,
|
||||
type File,
|
||||
type MediaGallery,
|
||||
type MentionableSelectMenu,
|
||||
type RoleSelectMenu,
|
||||
type Section,
|
||||
type Separator,
|
||||
type StringSelectMenu,
|
||||
type TextDisplay,
|
||||
type TextInput,
|
||||
type Thumbnail,
|
||||
type UserSelectMenu,
|
||||
fromComponent,
|
||||
} from '../builders';
|
||||
import {
|
||||
type APIActionRowComponent,
|
||||
type APIActionRowComponentTypes,
|
||||
type APIButtonComponent,
|
||||
type APIChannelSelectComponent,
|
||||
type APIContainerComponent,
|
||||
type APIFileComponent,
|
||||
type APIMediaGalleryComponent,
|
||||
type APIMentionableSelectComponent,
|
||||
type APIRoleSelectComponent,
|
||||
type APISectionComponent,
|
||||
type APISeparatorComponent,
|
||||
type APIStringSelectComponent,
|
||||
type APITextDispalyComponent,
|
||||
type APITextInputComponent,
|
||||
type APIThumbnailComponent,
|
||||
type APIUserSelectComponent,
|
||||
ComponentType,
|
||||
} from '../types';
|
||||
@ -24,7 +48,7 @@ export class BaseComponent<T extends ComponentType> {
|
||||
}
|
||||
|
||||
toBuilder() {
|
||||
return fromComponent(this.data);
|
||||
return fromComponent(this.data) as BuilderComponentsMap[T];
|
||||
}
|
||||
}
|
||||
export interface APIComponentsMap {
|
||||
@ -36,4 +60,29 @@ export interface APIComponentsMap {
|
||||
[ComponentType.StringSelect]: APIStringSelectComponent;
|
||||
[ComponentType.UserSelect]: APIUserSelectComponent;
|
||||
[ComponentType.TextInput]: APITextInputComponent;
|
||||
[ComponentType.File]: APIFileComponent;
|
||||
[ComponentType.Thumbnail]: APIThumbnailComponent;
|
||||
[ComponentType.Section]: APISectionComponent;
|
||||
[ComponentType.Container]: APIContainerComponent;
|
||||
[ComponentType.MediaGallery]: APIMediaGalleryComponent;
|
||||
[ComponentType.Separator]: APISeparatorComponent;
|
||||
[ComponentType.TextDisplay]: APITextDispalyComponent;
|
||||
}
|
||||
|
||||
export interface BuilderComponentsMap {
|
||||
[ComponentType.ActionRow]: ActionRow;
|
||||
[ComponentType.Button]: Button;
|
||||
[ComponentType.ChannelSelect]: ChannelSelectMenu;
|
||||
[ComponentType.MentionableSelect]: MentionableSelectMenu;
|
||||
[ComponentType.RoleSelect]: RoleSelectMenu;
|
||||
[ComponentType.StringSelect]: StringSelectMenu;
|
||||
[ComponentType.UserSelect]: UserSelectMenu;
|
||||
[ComponentType.TextInput]: TextInput;
|
||||
[ComponentType.File]: File;
|
||||
[ComponentType.Thumbnail]: Thumbnail;
|
||||
[ComponentType.Section]: Section;
|
||||
[ComponentType.Container]: Container;
|
||||
[ComponentType.MediaGallery]: MediaGallery;
|
||||
[ComponentType.Separator]: Separator;
|
||||
[ComponentType.TextDisplay]: TextDisplay;
|
||||
}
|
||||
|
23
src/components/Container.ts
Normal file
23
src/components/Container.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { type ContainerComponents, componentFactory } from '.';
|
||||
import type { APIContainerComponent, ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class ContainerComponent extends BaseComponent<ComponentType.Container> {
|
||||
_components: ContainerComponents[];
|
||||
constructor(data: APIContainerComponent) {
|
||||
super(data);
|
||||
this._components = this.data.components.map(componentFactory) as ContainerComponents[];
|
||||
}
|
||||
|
||||
get components() {
|
||||
return this.data.components;
|
||||
}
|
||||
|
||||
get accentColor() {
|
||||
return this.data.accent_color;
|
||||
}
|
||||
|
||||
get spoiler() {
|
||||
return this.data.spoiler;
|
||||
}
|
||||
}
|
16
src/components/File.ts
Normal file
16
src/components/File.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class FileComponent extends BaseComponent<ComponentType.File> {
|
||||
get spoiler() {
|
||||
return this.data.spoiler;
|
||||
}
|
||||
|
||||
get file() {
|
||||
return this.data.file;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.data.id;
|
||||
}
|
||||
}
|
12
src/components/MediaGallery.ts
Normal file
12
src/components/MediaGallery.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import type { ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class MediaGalleryComponent extends BaseComponent<ComponentType.MediaGallery> {
|
||||
get items() {
|
||||
return this.data.items;
|
||||
}
|
||||
|
||||
get id() {
|
||||
return this.data.id;
|
||||
}
|
||||
}
|
23
src/components/Section.ts
Normal file
23
src/components/Section.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { componentFactory } from '.';
|
||||
import type { APISectionComponent, ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import type { ButtonComponent } from './ButtonComponent';
|
||||
import type { TextDisplayComponent } from './TextDisplay';
|
||||
import type { ThumbnailComponent } from './Thumbnail';
|
||||
|
||||
export class SectionComponent extends BaseComponent<ComponentType.Section> {
|
||||
protected _components: TextDisplayComponent[];
|
||||
protected _accessory: ThumbnailComponent | ButtonComponent;
|
||||
constructor(data: APISectionComponent) {
|
||||
super(data);
|
||||
this._components = data.components?.map(componentFactory) as TextDisplayComponent[];
|
||||
this._accessory = componentFactory(data.accessory) as ThumbnailComponent | ButtonComponent;
|
||||
}
|
||||
get components() {
|
||||
return this._components;
|
||||
}
|
||||
|
||||
get accessory() {
|
||||
return this._accessory;
|
||||
}
|
||||
}
|
16
src/components/Separator.ts
Normal file
16
src/components/Separator.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class SeparatorComponent extends BaseComponent<ComponentType.Separator> {
|
||||
get id() {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
get spacing() {
|
||||
return this.data.spacing;
|
||||
}
|
||||
|
||||
get divider() {
|
||||
return this.data.divider;
|
||||
}
|
||||
}
|
8
src/components/TextDisplay.ts
Normal file
8
src/components/TextDisplay.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class TextDisplayComponent extends BaseComponent<ComponentType.TextDisplay> {
|
||||
get content() {
|
||||
return this.data.content;
|
||||
}
|
||||
}
|
20
src/components/Thumbnail.ts
Normal file
20
src/components/Thumbnail.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import type { ComponentType } from '../types';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
|
||||
export class ThumbnailComponent extends BaseComponent<ComponentType.Thumbnail> {
|
||||
get id() {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
get media() {
|
||||
return this.data.media;
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.data.description;
|
||||
}
|
||||
|
||||
get spoiler() {
|
||||
return this.data.spoiler;
|
||||
}
|
||||
}
|
@ -33,6 +33,7 @@ export abstract class ComponentCommand {
|
||||
return ComponentType[this.componentType];
|
||||
}
|
||||
|
||||
onBeforeMiddlewares?(context: ComponentContext): any;
|
||||
onAfterRun?(context: ComponentContext, error: unknown | undefined): any;
|
||||
onRunError?(context: ComponentContext, error: unknown): any;
|
||||
onMiddlewaresError?(context: ComponentContext, error: string): any;
|
||||
|
@ -204,6 +204,7 @@ export class ComponentHandler extends BaseHandler {
|
||||
component.onMiddlewaresError ??= this.client.options?.[is]?.defaults?.onMiddlewaresError;
|
||||
component.onRunError ??= this.client.options?.[is]?.defaults?.onRunError;
|
||||
component.onAfterRun ??= this.client.options?.[is]?.defaults?.onAfterRun;
|
||||
component.onBeforeMiddlewares ??= this.client.options?.[is]?.defaults?.onBeforeMiddlewares;
|
||||
}
|
||||
|
||||
set(instances: (new () => ComponentCommands)[]) {
|
||||
@ -289,6 +290,7 @@ export class ComponentHandler extends BaseHandler {
|
||||
|
||||
async execute(i: ComponentCommands, context: ComponentContext | ModalContext) {
|
||||
try {
|
||||
await i.onBeforeMiddlewares?.(context as never);
|
||||
const resultRunGlobalMiddlewares = await BaseCommand.__runMiddlewares(
|
||||
context,
|
||||
(context.client.options?.globalMiddlewares ?? []) as keyof RegisteredMiddlewares,
|
||||
@ -298,7 +300,7 @@ export class ComponentHandler extends BaseHandler {
|
||||
return;
|
||||
}
|
||||
if ('error' in resultRunGlobalMiddlewares) {
|
||||
return i.onMiddlewaresError?.(context as never, resultRunGlobalMiddlewares.error ?? 'Unknown error');
|
||||
return await i.onMiddlewaresError?.(context as never, resultRunGlobalMiddlewares.error ?? 'Unknown error');
|
||||
}
|
||||
|
||||
const resultRunMiddlewares = await BaseCommand.__runMiddlewares(context, i.middlewares, false);
|
||||
@ -306,7 +308,7 @@ export class ComponentHandler extends BaseHandler {
|
||||
return;
|
||||
}
|
||||
if ('error' in resultRunMiddlewares) {
|
||||
return i.onMiddlewaresError?.(context as never, resultRunMiddlewares.error ?? 'Unknown error');
|
||||
return await i.onMiddlewaresError?.(context as never, resultRunMiddlewares.error ?? 'Unknown error');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -319,9 +321,8 @@ export class ComponentHandler extends BaseHandler {
|
||||
} catch (error) {
|
||||
try {
|
||||
await i.onInternalError?.(this.client, error);
|
||||
} catch (e) {
|
||||
// supress error
|
||||
this.logger.error(e);
|
||||
} catch (err) {
|
||||
this.client.logger.error(`[${i.customId ?? 'Component/Modal command'}] Internal error:`, err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,19 @@
|
||||
import { type APIMessageActionRowComponent, ButtonStyle, ComponentType } from '../types';
|
||||
import { type APIComponents, type APITopLevelComponent, ButtonStyle, ComponentType } from '../types';
|
||||
import { MessageActionRowComponent } from './ActionRow';
|
||||
import { BaseComponent } from './BaseComponent';
|
||||
import { ButtonComponent, LinkButtonComponent, SKUButtonComponent } from './ButtonComponent';
|
||||
import { ChannelSelectMenuComponent } from './ChannelSelectMenuComponent';
|
||||
import { ContainerComponent } from './Container';
|
||||
import { FileComponent } from './File';
|
||||
import { MediaGalleryComponent } from './MediaGallery';
|
||||
import { MentionableSelectMenuComponent } from './MentionableSelectMenuComponent';
|
||||
import { RoleSelectMenuComponent } from './RoleSelectMenuComponent';
|
||||
import { SectionComponent } from './Section';
|
||||
import { SeparatorComponent } from './Separator';
|
||||
import { StringSelectMenuComponent } from './StringSelectMenuComponent';
|
||||
import type { TextInputComponent } from './TextInputComponent';
|
||||
import { TextDisplayComponent } from './TextDisplay';
|
||||
import { TextInputComponent } from './TextInputComponent';
|
||||
import { ThumbnailComponent } from './Thumbnail';
|
||||
import { UserSelectMenuComponent } from './UserSelectMenuComponent';
|
||||
|
||||
export type MessageComponents =
|
||||
@ -21,20 +29,37 @@ export type MessageComponents =
|
||||
|
||||
export type ActionRowMessageComponents = Exclude<MessageComponents, TextInputComponent>;
|
||||
|
||||
export type AllComponents = MessageComponents | TopLevelComponents | ContainerComponents | BaseComponent<ComponentType>;
|
||||
|
||||
export * from './componentcommand';
|
||||
export * from './componentcontext';
|
||||
export * from './modalcommand';
|
||||
export * from './modalcontext';
|
||||
|
||||
export type TopLevelComponents =
|
||||
| SectionComponent
|
||||
| ActionRowMessageComponents
|
||||
| TextDisplayComponent
|
||||
| ContainerComponent
|
||||
| FileComponent
|
||||
| MediaGalleryComponent
|
||||
| BaseComponent<APITopLevelComponent['type']>;
|
||||
|
||||
export type ContainerComponents =
|
||||
| MessageActionRowComponent
|
||||
| TextDisplayComponent
|
||||
| MediaGalleryComponent
|
||||
| SectionComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent;
|
||||
|
||||
/**
|
||||
* Return a new component instance based on the component type.
|
||||
*
|
||||
* @param component The component to create.
|
||||
* @returns The component instance.
|
||||
*/
|
||||
export function componentFactory(
|
||||
component: APIMessageActionRowComponent,
|
||||
): ActionRowMessageComponents | BaseComponent<ActionRowMessageComponents['type']> {
|
||||
export function componentFactory(component: APIComponents): AllComponents {
|
||||
switch (component.type) {
|
||||
case ComponentType.Button: {
|
||||
if (component.style === ButtonStyle.Link) {
|
||||
@ -55,7 +80,25 @@ export function componentFactory(
|
||||
return new UserSelectMenuComponent(component);
|
||||
case ComponentType.MentionableSelect:
|
||||
return new MentionableSelectMenuComponent(component);
|
||||
case ComponentType.ActionRow:
|
||||
return new MessageActionRowComponent(component as any);
|
||||
case ComponentType.Container:
|
||||
return new ContainerComponent(component);
|
||||
case ComponentType.File:
|
||||
return new FileComponent(component);
|
||||
case ComponentType.MediaGallery:
|
||||
return new MediaGalleryComponent(component);
|
||||
case ComponentType.Section:
|
||||
return new SectionComponent(component);
|
||||
case ComponentType.TextDisplay:
|
||||
return new TextDisplayComponent(component);
|
||||
case ComponentType.Separator:
|
||||
return new SeparatorComponent(component);
|
||||
case ComponentType.Thumbnail:
|
||||
return new ThumbnailComponent(component);
|
||||
case ComponentType.TextInput:
|
||||
return new TextInputComponent(component);
|
||||
default:
|
||||
return new BaseComponent<ActionRowMessageComponents['type']>(component);
|
||||
return new BaseComponent<ComponentType>(component);
|
||||
}
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ export abstract class ModalCommand {
|
||||
|
||||
props!: ExtraProps;
|
||||
|
||||
onBeforeMiddlewares?(context: ModalContext): any;
|
||||
onAfterRun?(context: ModalContext, error: unknown | undefined): any;
|
||||
onRunError?(context: ModalContext, error: unknown): any;
|
||||
onMiddlewaresError?(context: ModalContext, error: string): any;
|
||||
|
@ -148,6 +148,7 @@ export class BaseInteraction<
|
||||
//@ts-ignore
|
||||
return {
|
||||
type: body.type,
|
||||
// @ts-expect-error
|
||||
data: BaseInteraction.transformBody(body.data ?? {}, files, self),
|
||||
};
|
||||
}
|
||||
@ -330,7 +331,6 @@ export class BaseInteraction<
|
||||
case ApplicationCommandType.PrimaryEntryPoint:
|
||||
return new EntryPointInteraction(client, gateway as APIEntryPointCommandInteraction, __reply);
|
||||
}
|
||||
// biome-ignore lint/suspicious/noFallthroughSwitchClause: bad interaction between biome and ts-server
|
||||
case InteractionType.MessageComponent:
|
||||
switch (gateway.data.component_type) {
|
||||
case ComponentType.Button:
|
||||
@ -357,6 +357,8 @@ export class BaseInteraction<
|
||||
gateway as APIMessageComponentSelectMenuInteraction,
|
||||
__reply,
|
||||
);
|
||||
default:
|
||||
return;
|
||||
}
|
||||
case InteractionType.ModalSubmit:
|
||||
return new ModalSubmitInteraction(client, gateway);
|
||||
@ -488,8 +490,8 @@ export class Interaction<
|
||||
fetchReply?: FR,
|
||||
): Promise<WebhookMessageStructure> {
|
||||
if (await this.replied) {
|
||||
const { content, embeds, allowed_mentions, components, files, attachments, poll } = body;
|
||||
return this.editResponse({ content, embeds, allowed_mentions, components, files, attachments, poll });
|
||||
const { content, embeds, allowed_mentions, components, files, attachments, poll, flags } = body;
|
||||
return this.editResponse({ content, embeds, allowed_mentions, components, files, attachments, poll, flags });
|
||||
}
|
||||
return this.write(body as InteractionCreateBodyRequest, fetchReply);
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { type AllChannels, Embed, type ReturnCache } from '..';
|
||||
import { type AllChannels, Embed, type ReturnCache, componentFactory } from '..';
|
||||
import type { ListenerOptions } from '../builders';
|
||||
import {
|
||||
type GuildMemberStructure,
|
||||
@ -15,8 +15,7 @@ import { type ObjectToLower, toCamelCase } from '../common';
|
||||
import { Formatter } from '../common';
|
||||
import type { EmojiResolvable } from '../common/types/resolvables';
|
||||
import type { MessageCreateBodyRequest, MessageUpdateBodyRequest } from '../common/types/write';
|
||||
import type { ActionRowMessageComponents } from '../components';
|
||||
import { MessageActionRowComponent } from '../components/ActionRow';
|
||||
import type { TopLevelComponents } from '../components';
|
||||
import type {
|
||||
APIChannelMention,
|
||||
APIEmbed,
|
||||
@ -37,7 +36,7 @@ export interface BaseMessage
|
||||
guildId?: string;
|
||||
author: UserStructure;
|
||||
member?: GuildMemberStructure;
|
||||
components: MessageActionRowComponent<ActionRowMessageComponents>[];
|
||||
components: TopLevelComponents[];
|
||||
poll?: PollStructure;
|
||||
mentions: {
|
||||
roles: string[];
|
||||
@ -55,7 +54,7 @@ export class BaseMessage extends DiscordBase {
|
||||
channels: data.mention_channels ?? [],
|
||||
users: [],
|
||||
};
|
||||
this.components = data.components?.map(x => new MessageActionRowComponent(x)) ?? [];
|
||||
this.components = (data.components?.map(componentFactory) as TopLevelComponents[]) ?? [];
|
||||
this.embeds = data.embeds.map(embed => new InMessageEmbed(embed));
|
||||
this.patch(data);
|
||||
}
|
||||
|
@ -21,9 +21,9 @@ import type {
|
||||
*/
|
||||
import type {
|
||||
APIWebhook,
|
||||
RESTGetAPIWebhookWithTokenMessageQuery,
|
||||
RESTPatchAPIWebhookJSONBody,
|
||||
RESTPatchAPIWebhookWithTokenJSONBody,
|
||||
RESTPatchAPIWebhookWithTokenMessageQuery,
|
||||
RESTPostAPIWebhookWithTokenQuery,
|
||||
} from '../types';
|
||||
import type { AllChannels } from './channels';
|
||||
@ -181,7 +181,7 @@ export class Webhook extends DiscordBase {
|
||||
/** Type definition for parameters of editing a message through a webhook. */
|
||||
export type MessageWebhookMethodEditParams = MessageWebhookPayload<
|
||||
MessageWebhookUpdateBodyRequest,
|
||||
{ messageId: string; query?: RESTGetAPIWebhookWithTokenMessageQuery }
|
||||
{ messageId: string; query?: RESTPatchAPIWebhookWithTokenMessageQuery }
|
||||
>;
|
||||
/** Type definition for parameters of writing a message through a webhook. */
|
||||
export type MessageWebhookMethodWriteParams = MessageWebhookPayload<
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Collection, Formatter, type RawFile, type ReturnCache } from '..';
|
||||
import { ActionRow, Embed, PollBuilder, resolveAttachment } from '../builders';
|
||||
import { Embed, PollBuilder, resolveAttachment } from '../builders';
|
||||
import type { Overwrites } from '../cache/resources/overwrites';
|
||||
import {
|
||||
type BaseChannelStructure,
|
||||
@ -353,8 +353,8 @@ export class MessagesMethods extends DiscordBase {
|
||||
const payload = {
|
||||
allowed_mentions: self.options?.allowedMentions,
|
||||
...body,
|
||||
components: body.components?.map(x => (x instanceof ActionRow ? x.toJSON() : x)) ?? undefined,
|
||||
embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)) ?? undefined,
|
||||
embeds: body.embeds?.map(x => (x instanceof Embed ? x.toJSON() : x)),
|
||||
components: body.components?.map(x => ('toJSON' in x ? x.toJSON() : x)) ?? undefined,
|
||||
poll: poll ? (poll instanceof PollBuilder ? poll.toJSON() : poll) : undefined,
|
||||
};
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { Snowflake } from '../../index';
|
||||
import type { ComponentType } from '../channel';
|
||||
import type { ComponentType, Snowflake } from '../../index';
|
||||
|
||||
import type { APIBaseInteraction, InteractionType } from '../interactions';
|
||||
import type {
|
||||
APIDMInteractionWrapper,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import type { APIActionRowComponent, APIModalActionRowComponent } from '../channel';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIBaseInteraction,
|
||||
APIDMInteractionWrapper,
|
||||
APIGuildInteractionWrapper,
|
||||
APIModalActionRowComponent,
|
||||
ComponentType,
|
||||
InteractionType,
|
||||
} from '../index';
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { MakeRequired } from '../../../common';
|
||||
import type { RESTPostAPIWebhookWithTokenJSONBody } from '../../index';
|
||||
import type { APIActionRowComponent, APIModalActionRowComponent } from '../channel';
|
||||
import type { MessageFlags } from '../index';
|
||||
|
||||
import type { APIActionRowComponent, APIModalActionRowComponent, MessageFlags } from '../index';
|
||||
import type { APIApplicationCommandOptionChoice } from './applicationCommands';
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,15 @@
|
||||
*/
|
||||
|
||||
import type { PickRequired } from '../../common';
|
||||
import type { ChannelType, OverwriteType, Permissions, Snowflake, VideoQualityMode } from '../index';
|
||||
import type {
|
||||
APITopLevelComponent,
|
||||
ChannelFlags,
|
||||
ChannelType,
|
||||
OverwriteType,
|
||||
Permissions,
|
||||
Snowflake,
|
||||
VideoQualityMode,
|
||||
} from '../index';
|
||||
import type { APIApplication } from './application';
|
||||
import type { APIPartialEmoji } from './emoji';
|
||||
import type { APIGuildMember } from './guild';
|
||||
@ -579,7 +587,7 @@ export interface APIMessage {
|
||||
*
|
||||
* See https://support-dev.discord.com/hc/articles/4404772028055
|
||||
*/
|
||||
components?: APIActionRowComponent<APIMessageActionRowComponent>[];
|
||||
components?: APITopLevelComponent[];
|
||||
/**
|
||||
* Sent if the message contains stickers
|
||||
*
|
||||
@ -813,6 +821,14 @@ export enum MessageFlags {
|
||||
* This message is a voice message
|
||||
*/
|
||||
IsVoiceMessage = 1 << 13,
|
||||
/**
|
||||
* This message has a snapshot (via Message Forwarding)
|
||||
*/
|
||||
HasSnapshot = 1 << 14,
|
||||
/**
|
||||
* This message allows you to create fully component driven messages
|
||||
*/
|
||||
IsComponentsV2 = 1 << 15,
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1498,403 +1514,3 @@ export interface APIAllowedMentions {
|
||||
*/
|
||||
replied_user?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#component-object
|
||||
*/
|
||||
export interface APIBaseComponent<T extends ComponentType> {
|
||||
/**
|
||||
* The type of the component
|
||||
*/
|
||||
type: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#component-object-component-types
|
||||
*/
|
||||
export enum ComponentType {
|
||||
/**
|
||||
* Action Row component
|
||||
*/
|
||||
ActionRow = 1,
|
||||
/**
|
||||
* Button component
|
||||
*/
|
||||
Button,
|
||||
/**
|
||||
* Select menu for picking from defined text options
|
||||
*/
|
||||
StringSelect,
|
||||
/**
|
||||
* Text Input component
|
||||
*/
|
||||
TextInput,
|
||||
/**
|
||||
* Select menu for users
|
||||
*/
|
||||
UserSelect,
|
||||
/**
|
||||
* Select menu for roles
|
||||
*/
|
||||
RoleSelect,
|
||||
/**
|
||||
* Select menu for users and roles
|
||||
*/
|
||||
MentionableSelect,
|
||||
/**
|
||||
* Select menu for channels
|
||||
*/
|
||||
ChannelSelect,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#action-rows
|
||||
*/
|
||||
export interface APIActionRowComponent<T extends APIActionRowComponentTypes>
|
||||
extends APIBaseComponent<ComponentType.ActionRow> {
|
||||
/**
|
||||
* The components in the ActionRow
|
||||
*/
|
||||
components: T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#buttons
|
||||
*/
|
||||
export interface APIButtonComponentBase<Style extends ButtonStyle> extends APIBaseComponent<ComponentType.Button> {
|
||||
/**
|
||||
* The label to be displayed on the button
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The style of the button
|
||||
*/
|
||||
style: Style;
|
||||
/**
|
||||
* The emoji to display to the left of the text
|
||||
*/
|
||||
emoji?: APIMessageComponentEmoji;
|
||||
/**
|
||||
* The status of the button
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface APIMessageComponentEmoji {
|
||||
/**
|
||||
* Emoji id
|
||||
*/
|
||||
id?: Snowflake;
|
||||
/**
|
||||
* Emoji name
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Whether this emoji is animated
|
||||
*/
|
||||
animated?: boolean;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithCustomId
|
||||
extends APIButtonComponentBase<
|
||||
ButtonStyle.Danger | ButtonStyle.Primary | ButtonStyle.Secondary | ButtonStyle.Success
|
||||
> {
|
||||
/**
|
||||
* The custom_id to be sent in the interaction when clicked
|
||||
*/
|
||||
custom_id: string;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithURL extends APIButtonComponentBase<ButtonStyle.Link> {
|
||||
/**
|
||||
* The URL to direct users to when clicked for Link buttons
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithSKUId
|
||||
extends Omit<APIButtonComponentBase<ButtonStyle.Premium>, 'custom_id' | 'emoji' | 'label'> {
|
||||
/**
|
||||
* The id for a purchasable SKU
|
||||
*/
|
||||
sku_id: Snowflake;
|
||||
}
|
||||
|
||||
export type APIButtonComponent =
|
||||
| APIButtonComponentWithCustomId
|
||||
| APIButtonComponentWithSKUId
|
||||
| APIButtonComponentWithURL;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
|
||||
*/
|
||||
export enum ButtonStyle {
|
||||
Primary = 1,
|
||||
Secondary,
|
||||
Success,
|
||||
Danger,
|
||||
Link,
|
||||
Premium,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-styles
|
||||
*/
|
||||
export enum TextInputStyle {
|
||||
Short = 1,
|
||||
Paragraph,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIBaseSelectMenuComponent<
|
||||
T extends
|
||||
| ComponentType.ChannelSelect
|
||||
| ComponentType.MentionableSelect
|
||||
| ComponentType.RoleSelect
|
||||
| ComponentType.StringSelect
|
||||
| ComponentType.UserSelect,
|
||||
> extends APIBaseComponent<T> {
|
||||
/**
|
||||
* A developer-defined identifier for the select menu, max 100 characters
|
||||
*/
|
||||
custom_id: string;
|
||||
/**
|
||||
* Custom placeholder text if nothing is selected, max 150 characters
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* The minimum number of items that must be chosen; min 0, max 25
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
min_values?: number;
|
||||
/**
|
||||
* The maximum number of items that can be chosen; max 25
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
max_values?: number;
|
||||
/**
|
||||
* Disable the select
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface APIBaseAutoPopulatedSelectMenuComponent<
|
||||
T extends
|
||||
| ComponentType.ChannelSelect
|
||||
| ComponentType.MentionableSelect
|
||||
| ComponentType.RoleSelect
|
||||
| ComponentType.UserSelect,
|
||||
D extends SelectMenuDefaultValueType,
|
||||
> extends APIBaseSelectMenuComponent<T> {
|
||||
/**
|
||||
* List of default values for auto-populated select menu components
|
||||
*/
|
||||
default_values?: APISelectMenuDefaultValue<D>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIStringSelectComponent extends APIBaseSelectMenuComponent<ComponentType.StringSelect> {
|
||||
/**
|
||||
* Specified choices in a select menu; max 25
|
||||
*/
|
||||
options: APISelectMenuOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIUserSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.UserSelect,
|
||||
SelectMenuDefaultValueType.User
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIRoleSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.RoleSelect,
|
||||
SelectMenuDefaultValueType.Role
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIMentionableSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.MentionableSelect,
|
||||
SelectMenuDefaultValueType.Role | SelectMenuDefaultValueType.User
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIChannelSelectComponent
|
||||
extends APIBaseAutoPopulatedSelectMenuComponent<ComponentType.ChannelSelect, SelectMenuDefaultValueType.Channel> {
|
||||
/**
|
||||
* List of channel types to include in the ChannelSelect component
|
||||
*/
|
||||
channel_types?: ChannelType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure
|
||||
*/
|
||||
export enum SelectMenuDefaultValueType {
|
||||
Channel = 'channel',
|
||||
Role = 'role',
|
||||
User = 'user',
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure
|
||||
*/
|
||||
export interface APISelectMenuDefaultValue<T extends SelectMenuDefaultValueType> {
|
||||
type: T;
|
||||
id: Snowflake;
|
||||
}
|
||||
|
||||
export type APIAutoPopulatedSelectMenuComponent =
|
||||
| APIChannelSelectComponent
|
||||
| APIMentionableSelectComponent
|
||||
| APIRoleSelectComponent
|
||||
| APIUserSelectComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APISelectMenuComponent =
|
||||
| APIChannelSelectComponent
|
||||
| APIMentionableSelectComponent
|
||||
| APIRoleSelectComponent
|
||||
| APIStringSelectComponent
|
||||
| APIUserSelectComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
|
||||
*/
|
||||
export interface APISelectMenuOption {
|
||||
/**
|
||||
* The user-facing name of the option (max 100 chars)
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The dev-defined value of the option (max 100 chars)
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* An additional description of the option (max 100 chars)
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The emoji to display to the left of the option
|
||||
*/
|
||||
emoji?: APIMessageComponentEmoji;
|
||||
/**
|
||||
* Whether this option should be already-selected by default
|
||||
*/
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
|
||||
*/
|
||||
export interface APITextInputComponent extends APIBaseComponent<ComponentType.TextInput> {
|
||||
/**
|
||||
* One of text input styles
|
||||
*/
|
||||
style: TextInputStyle;
|
||||
/**
|
||||
* The custom id for the text input
|
||||
*/
|
||||
custom_id: string;
|
||||
/**
|
||||
* Text that appears on top of the text input field, max 45 characters
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* The pre-filled text in the text input
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* Minimal length of text input
|
||||
*/
|
||||
min_length?: number;
|
||||
/**
|
||||
* Maximal length of text input
|
||||
*/
|
||||
max_length?: number;
|
||||
/**
|
||||
* Whether or not this text input is required or not
|
||||
*/
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/channel#channel-object-channel-flags
|
||||
*/
|
||||
export enum ChannelFlags {
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
GuildFeedRemoved = 1 << 0,
|
||||
/**
|
||||
* This thread is pinned to the top of its parent forum channel
|
||||
*/
|
||||
Pinned = 1 << 1,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
ActiveChannelsRemoved = 1 << 2,
|
||||
/**
|
||||
* Whether a tag is required to be specified when creating a thread in a forum channel.
|
||||
* Tags are specified in the `applied_tags` field
|
||||
*/
|
||||
RequireTag = 1 << 4,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsSpam = 1 << 5,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsGuildResourceChannel = 1 << 7,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
ClydeAI = 1 << 8,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsScheduledForDeletion = 1 << 9,
|
||||
/**
|
||||
* Whether media download options are hidden.
|
||||
*/
|
||||
HideMediaDownloadOptions = 1 << 15,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#message-components
|
||||
*/
|
||||
export type APIMessageComponent = APIActionRowComponent<APIMessageActionRowComponent> | APIMessageActionRowComponent;
|
||||
export type APIModalComponent = APIActionRowComponent<APIModalActionRowComponent> | APIModalActionRowComponent;
|
||||
|
||||
export type APIActionRowComponentTypes = APIMessageActionRowComponent | APIModalActionRowComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#message-components
|
||||
*/
|
||||
export type APIMessageActionRowComponent = APIButtonComponent | APISelectMenuComponent;
|
||||
|
||||
// Modal components
|
||||
export type APIModalActionRowComponent = APITextInputComponent;
|
||||
|
594
src/types/payloads/components.ts
Normal file
594
src/types/payloads/components.ts
Normal file
@ -0,0 +1,594 @@
|
||||
import type { APIAttachment, Snowflake } from '..';
|
||||
import type { Identify, MakeRequired } from '../../common';
|
||||
import type { ChannelType } from '../utils';
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#component-object
|
||||
*/
|
||||
export interface APIBaseComponent<T extends ComponentType> {
|
||||
/**
|
||||
* The type of the component
|
||||
*/
|
||||
type: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#component-object-component-types
|
||||
*/
|
||||
export enum ComponentType {
|
||||
/**
|
||||
* Action Row component
|
||||
*/
|
||||
ActionRow = 1,
|
||||
/**
|
||||
* Button component
|
||||
*/
|
||||
Button,
|
||||
/**
|
||||
* Select menu for picking from defined text options
|
||||
*/
|
||||
StringSelect,
|
||||
/**
|
||||
* Text Input component
|
||||
*/
|
||||
TextInput,
|
||||
/**
|
||||
* Select menu for users
|
||||
*/
|
||||
UserSelect,
|
||||
/**
|
||||
* Select menu for roles
|
||||
*/
|
||||
RoleSelect,
|
||||
/**
|
||||
* Select menu for users and roles
|
||||
*/
|
||||
MentionableSelect,
|
||||
/**
|
||||
* Select menu for channels
|
||||
*/
|
||||
ChannelSelect,
|
||||
/**
|
||||
* Section for accessory
|
||||
*/
|
||||
Section,
|
||||
/**
|
||||
* Text display component
|
||||
*/
|
||||
TextDisplay,
|
||||
/**
|
||||
* Thumbnail component
|
||||
*/
|
||||
Thumbnail,
|
||||
/**
|
||||
* Media Gallery component
|
||||
*/
|
||||
MediaGallery,
|
||||
/**
|
||||
* File component
|
||||
*/
|
||||
File,
|
||||
/**
|
||||
* Separator component
|
||||
*/
|
||||
Separator,
|
||||
/**
|
||||
* Container component
|
||||
*/
|
||||
Container = 17,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#action-rows
|
||||
*/
|
||||
export interface APIActionRowComponent<T extends APIActionRowComponentTypes>
|
||||
extends APIBaseComponent<ComponentType.ActionRow> {
|
||||
/**
|
||||
* The components in the ActionRow
|
||||
*/
|
||||
components: T[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#buttons
|
||||
*/
|
||||
export interface APIButtonComponentBase<Style extends ButtonStyle> extends APIBaseComponent<ComponentType.Button> {
|
||||
/**
|
||||
* The label to be displayed on the button
|
||||
*/
|
||||
label?: string;
|
||||
/**
|
||||
* The style of the button
|
||||
*/
|
||||
style: Style;
|
||||
/**
|
||||
* The emoji to display to the left of the text
|
||||
*/
|
||||
emoji?: APIMessageComponentEmoji;
|
||||
/**
|
||||
* The status of the button
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface APIMessageComponentEmoji {
|
||||
/**
|
||||
* Emoji id
|
||||
*/
|
||||
id?: Snowflake;
|
||||
/**
|
||||
* Emoji name
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* Whether this emoji is animated
|
||||
*/
|
||||
animated?: boolean;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithCustomId
|
||||
extends APIButtonComponentBase<
|
||||
ButtonStyle.Danger | ButtonStyle.Primary | ButtonStyle.Secondary | ButtonStyle.Success
|
||||
> {
|
||||
/**
|
||||
* The custom_id to be sent in the interaction when clicked
|
||||
*/
|
||||
custom_id: string;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithURL extends APIButtonComponentBase<ButtonStyle.Link> {
|
||||
/**
|
||||
* The URL to direct users to when clicked for Link buttons
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface APIButtonComponentWithSKUId
|
||||
extends Omit<APIButtonComponentBase<ButtonStyle.Premium>, 'custom_id' | 'emoji' | 'label'> {
|
||||
/**
|
||||
* The id for a purchasable SKU
|
||||
*/
|
||||
sku_id: Snowflake;
|
||||
}
|
||||
|
||||
export type APIButtonComponent =
|
||||
| APIButtonComponentWithCustomId
|
||||
| APIButtonComponentWithSKUId
|
||||
| APIButtonComponentWithURL;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#button-object-button-styles
|
||||
*/
|
||||
export enum ButtonStyle {
|
||||
Primary = 1,
|
||||
Secondary,
|
||||
Success,
|
||||
Danger,
|
||||
Link,
|
||||
Premium,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-styles
|
||||
*/
|
||||
export enum TextInputStyle {
|
||||
Short = 1,
|
||||
Paragraph,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIBaseSelectMenuComponent<
|
||||
T extends
|
||||
| ComponentType.ChannelSelect
|
||||
| ComponentType.MentionableSelect
|
||||
| ComponentType.RoleSelect
|
||||
| ComponentType.StringSelect
|
||||
| ComponentType.UserSelect,
|
||||
> extends APIBaseComponent<T> {
|
||||
/**
|
||||
* A developer-defined identifier for the select menu, max 100 characters
|
||||
*/
|
||||
custom_id: string;
|
||||
/**
|
||||
* Custom placeholder text if nothing is selected, max 150 characters
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* The minimum number of items that must be chosen; min 0, max 25
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
min_values?: number;
|
||||
/**
|
||||
* The maximum number of items that can be chosen; max 25
|
||||
*
|
||||
* @default 1
|
||||
*/
|
||||
max_values?: number;
|
||||
/**
|
||||
* Disable the select
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface APIBaseAutoPopulatedSelectMenuComponent<
|
||||
T extends
|
||||
| ComponentType.ChannelSelect
|
||||
| ComponentType.MentionableSelect
|
||||
| ComponentType.RoleSelect
|
||||
| ComponentType.UserSelect,
|
||||
D extends SelectMenuDefaultValueType,
|
||||
> extends APIBaseSelectMenuComponent<T> {
|
||||
/**
|
||||
* List of default values for auto-populated select menu components
|
||||
*/
|
||||
default_values?: APISelectMenuDefaultValue<D>[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIStringSelectComponent extends APIBaseSelectMenuComponent<ComponentType.StringSelect> {
|
||||
/**
|
||||
* Specified choices in a select menu; max 25
|
||||
*/
|
||||
options: APISelectMenuOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIUserSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.UserSelect,
|
||||
SelectMenuDefaultValueType.User
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIRoleSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.RoleSelect,
|
||||
SelectMenuDefaultValueType.Role
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APIMentionableSelectComponent = APIBaseAutoPopulatedSelectMenuComponent<
|
||||
ComponentType.MentionableSelect,
|
||||
SelectMenuDefaultValueType.Role | SelectMenuDefaultValueType.User
|
||||
>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export interface APIChannelSelectComponent
|
||||
extends APIBaseAutoPopulatedSelectMenuComponent<ComponentType.ChannelSelect, SelectMenuDefaultValueType.Channel> {
|
||||
/**
|
||||
* List of channel types to include in the ChannelSelect component
|
||||
*/
|
||||
channel_types?: ChannelType[];
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure
|
||||
*/
|
||||
export enum SelectMenuDefaultValueType {
|
||||
Channel = 'channel',
|
||||
Role = 'role',
|
||||
User = 'user',
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-default-value-structure
|
||||
*/
|
||||
export interface APISelectMenuDefaultValue<T extends SelectMenuDefaultValueType> {
|
||||
type: T;
|
||||
id: Snowflake;
|
||||
}
|
||||
|
||||
export type APIAutoPopulatedSelectMenuComponent =
|
||||
| APIChannelSelectComponent
|
||||
| APIMentionableSelectComponent
|
||||
| APIRoleSelectComponent
|
||||
| APIUserSelectComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menus
|
||||
*/
|
||||
export type APISelectMenuComponent =
|
||||
| APIChannelSelectComponent
|
||||
| APIMentionableSelectComponent
|
||||
| APIRoleSelectComponent
|
||||
| APIStringSelectComponent
|
||||
| APIUserSelectComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
|
||||
*/
|
||||
export interface APISelectMenuOption {
|
||||
/**
|
||||
* The user-facing name of the option (max 100 chars)
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* The dev-defined value of the option (max 100 chars)
|
||||
*/
|
||||
value: string;
|
||||
/**
|
||||
* An additional description of the option (max 100 chars)
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* The emoji to display to the left of the option
|
||||
*/
|
||||
emoji?: APIMessageComponentEmoji;
|
||||
/**
|
||||
* Whether this option should be already-selected by default
|
||||
*/
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
|
||||
*/
|
||||
export interface APITextInputComponent extends APIBaseComponent<ComponentType.TextInput> {
|
||||
/**
|
||||
* One of text input styles
|
||||
*/
|
||||
style: TextInputStyle;
|
||||
/**
|
||||
* The custom id for the text input
|
||||
*/
|
||||
custom_id: string;
|
||||
/**
|
||||
* Text that appears on top of the text input field, max 45 characters
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Placeholder for the text input
|
||||
*/
|
||||
placeholder?: string;
|
||||
/**
|
||||
* The pre-filled text in the text input
|
||||
*/
|
||||
value?: string;
|
||||
/**
|
||||
* Minimal length of text input
|
||||
*/
|
||||
min_length?: number;
|
||||
/**
|
||||
* Maximal length of text input
|
||||
*/
|
||||
max_length?: number;
|
||||
/**
|
||||
* Whether or not this text input is required or not
|
||||
*/
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/channel#channel-object-channel-flags
|
||||
*/
|
||||
export enum ChannelFlags {
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
GuildFeedRemoved = 1 << 0,
|
||||
/**
|
||||
* This thread is pinned to the top of its parent forum channel
|
||||
*/
|
||||
Pinned = 1 << 1,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
ActiveChannelsRemoved = 1 << 2,
|
||||
/**
|
||||
* Whether a tag is required to be specified when creating a thread in a forum channel.
|
||||
* Tags are specified in the `applied_tags` field
|
||||
*/
|
||||
RequireTag = 1 << 4,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsSpam = 1 << 5,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsGuildResourceChannel = 1 << 7,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
ClydeAI = 1 << 8,
|
||||
/**
|
||||
* @unstable This channel flag is currently not documented by Discord but has a known value which we will try to keep up to date.
|
||||
*/
|
||||
IsScheduledForDeletion = 1 << 9,
|
||||
/**
|
||||
* Whether media download options are hidden.
|
||||
*/
|
||||
HideMediaDownloadOptions = 1 << 15,
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#message-components
|
||||
*/
|
||||
export type APIMessageComponent = APIActionRowComponent<APIMessageActionRowComponent> | APIMessageActionRowComponent;
|
||||
export type APIModalComponent = APIActionRowComponent<APIModalActionRowComponent> | APIModalActionRowComponent;
|
||||
|
||||
export type APIActionRowComponentTypes = APIMessageActionRowComponent | APIModalActionRowComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/interactions/message-components#message-components
|
||||
*/
|
||||
export type APIMessageActionRowComponent = APIButtonComponent | APISelectMenuComponent;
|
||||
|
||||
export type APIComponents =
|
||||
| APIMessageActionRowComponent
|
||||
| APIModalActionRowComponent
|
||||
| APIContainerComponent
|
||||
| APIContainerComponents
|
||||
| APITopLevelComponent;
|
||||
|
||||
// Modal components
|
||||
export type APIModalActionRowComponent = APITextInputComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#section
|
||||
*
|
||||
* A Section is a top-level layout component that allows you to join text contextually with an accessory.
|
||||
*/
|
||||
export interface APISectionComponent {
|
||||
/** 9 for section component */
|
||||
type: ComponentType.Section;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** One to three text components */
|
||||
components: APITextDispalyComponent[];
|
||||
/** A thumbnail or a button component, with a future possibility of adding more compatible components */
|
||||
accessory: APIButtonComponent | APIThumbnailComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#text-display
|
||||
*
|
||||
* A Text Display is a top-level content component that allows you to add text to your message formatted with markdown and mention users and roles. This is similar to the content field of a message, but allows you to add multiple text components, controlling the layout of your message.
|
||||
* Text Displays are only available in messages.
|
||||
*/
|
||||
export interface APITextDispalyComponent {
|
||||
/** 10 for text display */
|
||||
type: ComponentType.TextDisplay;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** Text that will be displayed similar to a message */
|
||||
content: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#thumbnail
|
||||
*
|
||||
* A Thumbnail is a content component that is a small image only usable as an accessory in a section. The preview comes from an url or attachment through the unfurled media item structure.
|
||||
* Thumbnails are only available in messages as an accessory in a section.
|
||||
*/
|
||||
export interface APIThumbnailComponent {
|
||||
/** 11 for thumbnail */
|
||||
type: ComponentType.Thumbnail;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** A url or attachment */
|
||||
media: APIUnfurledMediaItem;
|
||||
/** Alt text for the media */
|
||||
description?: string;
|
||||
/** Whether the thumbnail should be a spoiler (or blurred out). Defaults to false */
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#media-gallery
|
||||
*
|
||||
* A Media Gallery is a top-level content component that allows you to display 1-10 media attachments in an organized gallery format. Each item can have optional descriptions and can be marked as spoilers.
|
||||
* Media Galleries are only available in messages.
|
||||
*/
|
||||
export interface APIMediaGalleryComponent {
|
||||
/** 12 for media gallery */
|
||||
type: ComponentType.MediaGallery;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** 1 to 10 media gallery items */
|
||||
items: APIMediaGalleryItems[];
|
||||
}
|
||||
|
||||
export interface APIMediaGalleryItems {
|
||||
/** A url or attachment */
|
||||
media: APIUnfurledMediaItem;
|
||||
/** Alt text for the media */
|
||||
description?: string;
|
||||
/** Whether the thumbnail should be a spoiler (or blurred out). Defaults to false */
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#file
|
||||
*
|
||||
* A File is a top-level component that allows you to display an uploaded file as an attachment to the message and reference it in the component. Each file component can only display 1 attached file, but you can upload multiple files and add them to different file components within your payload. This is similar to the embeds field of a message but allows you to control the layout of your message by using this anywhere as a component.
|
||||
* Files are only available in messages.
|
||||
*/
|
||||
export interface APIFileComponent {
|
||||
/** 13 for file */
|
||||
type: ComponentType.File;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** This unfurled media item is unique in that it only supports attachment references using the attachment://<filename> syntax */
|
||||
file: APIUnfurledMediaItem;
|
||||
/** Whether the media should be a spoiler (or blurred out). Defaults to false */
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#separator
|
||||
*
|
||||
* A Separator is a top-level layout component that adds vertical padding and visual division between other components.
|
||||
*/
|
||||
export interface APISeparatorComponent {
|
||||
/** 14 for separator */
|
||||
type: ComponentType.Separator;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** Whether a visual divider should be displayed in the component. Defaults to true */
|
||||
divider?: boolean;
|
||||
/** Size of separator padding—1 for small padding, 2 for large padding. Defaults to 1 */
|
||||
spacing?: Spacing;
|
||||
}
|
||||
|
||||
export enum Spacing {
|
||||
/** For small padding */
|
||||
Small = 1,
|
||||
/** For large padding */
|
||||
Large,
|
||||
}
|
||||
|
||||
export type APIContainerComponents =
|
||||
| APIActionRowComponent<APIActionRowComponentTypes>
|
||||
| APITextDispalyComponent
|
||||
| APISectionComponent
|
||||
| APIMediaGalleryComponent
|
||||
| APIFileComponent
|
||||
| APISeparatorComponent
|
||||
| APIThumbnailComponent;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#container
|
||||
*/
|
||||
export interface APIContainerComponent {
|
||||
/** 15 for container */
|
||||
type: ComponentType.Container;
|
||||
/** Optional identifier for component */
|
||||
id?: number;
|
||||
/** Up to 10 components of the type action row, text display, section, media gallery, separator, or file */
|
||||
components: APIContainerComponents[];
|
||||
/** Color for the accent on the container as RGB from 0x000000 to 0xFFFFFF */
|
||||
accent_color?: number;
|
||||
/** Whether the container should be a spoiler (or blurred out). Defaults to false. */
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/components/reference#unfurled-media-item-structure
|
||||
*/
|
||||
export interface APIUnfurledMediaItem
|
||||
extends Identify<
|
||||
MakeRequired<Partial<Pick<APIAttachment, 'url' | 'proxy_url' | 'height' | 'width' | 'content_type'>>, 'url'>
|
||||
> {}
|
||||
|
||||
export type APITopLevelComponent =
|
||||
| APIContainerComponent
|
||||
| APIActionRowComponent<APIActionRowComponentTypes>
|
||||
| APIFileComponent
|
||||
| APIMediaGalleryComponent
|
||||
| APISectionComponent
|
||||
| APISeparatorComponent
|
||||
| APITextDispalyComponent;
|
@ -20,6 +20,7 @@ export * from './voice';
|
||||
export * from './webhook';
|
||||
export * from './monetization';
|
||||
export * from './soundboard';
|
||||
export * from './components';
|
||||
|
||||
import type { LocaleString } from '../rest';
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import type { ChannelType, OverwriteType, Permissions, Snowflake, VideoQualityMode } from '..';
|
||||
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIAllowedMentions,
|
||||
APIAttachment,
|
||||
APIChannel,
|
||||
@ -11,10 +10,10 @@ import type {
|
||||
APIGuildForumDefaultReactionEmoji,
|
||||
APIGuildForumTag,
|
||||
APIMessage,
|
||||
APIMessageActionRowComponent,
|
||||
APIMessageReference,
|
||||
APIThreadList,
|
||||
APIThreadMember,
|
||||
APITopLevelComponent,
|
||||
APIUser,
|
||||
ChannelFlags,
|
||||
ForumLayoutType,
|
||||
@ -294,7 +293,7 @@ export interface RESTPostAPIChannelMessageJSONBody {
|
||||
*
|
||||
* See https://discord.com/developers/docs/interactions/message-components#component-object
|
||||
*/
|
||||
components?: APIActionRowComponent<APIMessageActionRowComponent>[] | undefined;
|
||||
components?: APITopLevelComponent[] | undefined;
|
||||
/**
|
||||
* IDs of up to 3 stickers in the server to send in the message
|
||||
*
|
||||
@ -442,7 +441,7 @@ export interface RESTPatchAPIChannelMessageJSONBody {
|
||||
*
|
||||
* See https://discord.com/developers/docs/interactions/message-components#component-object
|
||||
*/
|
||||
components?: APIActionRowComponent<APIMessageActionRowComponent>[] | null | undefined;
|
||||
components?: APITopLevelComponent[] | null | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,10 +1,9 @@
|
||||
import type { Snowflake } from '..';
|
||||
import type {
|
||||
APIActionRowComponent,
|
||||
APIAllowedMentions,
|
||||
APIEmbed,
|
||||
APIMessage,
|
||||
APIMessageActionRowComponent,
|
||||
APITopLevelComponent,
|
||||
APIWebhook,
|
||||
MessageFlags,
|
||||
} from '../payloads';
|
||||
@ -136,7 +135,7 @@ export interface RESTPostAPIWebhookWithTokenJSONBody {
|
||||
*
|
||||
* See https://discord.com/developers/docs/interactions/message-components#component-object
|
||||
*/
|
||||
components?: APIActionRowComponent<APIMessageActionRowComponent>[] | undefined;
|
||||
components?: APITopLevelComponent[] | undefined;
|
||||
/**
|
||||
* Attachment objects with filename and description
|
||||
*/
|
||||
@ -190,6 +189,14 @@ export interface RESTPostAPIWebhookWithTokenQuery {
|
||||
* Available only if the {@link RESTPostAPIWebhookWithTokenJSONBody.thread_name} JSON body property is not specified
|
||||
*/
|
||||
thread_id?: Snowflake;
|
||||
|
||||
/**
|
||||
* Whether to respect the components field of the request. When enabled, allows application-owned webhooks to use all components and
|
||||
* non-owned webhooks to use non-interactive components.
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
with_components?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -208,7 +215,7 @@ export type RESTPostAPIWebhookWithTokenWaitResult = APIMessage;
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook-query-string-params
|
||||
*/
|
||||
export type RESTPostAPIWebhookWithTokenSlackQuery = RESTPostAPIWebhookWithTokenQuery;
|
||||
export type RESTPostAPIWebhookWithTokenSlackQuery = Omit<RESTPostAPIWebhookWithTokenQuery, 'with_components'>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/webhook#execute-slackcompatible-webhook
|
||||
@ -226,7 +233,7 @@ export type RESTPostAPIWebhookWithTokenSlackWaitResult = APIMessage;
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook-query-string-params
|
||||
*/
|
||||
export type RESTPostAPIWebhookWithTokenGitHubQuery = RESTPostAPIWebhookWithTokenQuery;
|
||||
export type RESTPostAPIWebhookWithTokenGitHubQuery = Omit<RESTPostAPIWebhookWithTokenQuery, 'with_components'>;
|
||||
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/webhook#execute-githubcompatible-webhook
|
||||
@ -269,6 +276,7 @@ export type RESTPatchAPIWebhookWithTokenMessageJSONBody = AddUndefinedToPossibly
|
||||
attachments?: RESTAPIAttachment[] | undefined;
|
||||
};
|
||||
|
||||
export type RESTPatchAPIWebhookWithTokenMessageQuery = Omit<RESTPostAPIWebhookWithTokenQuery, 'wait'>;
|
||||
/**
|
||||
* https://discord.com/developers/docs/resources/webhook#edit-webhook-message
|
||||
*/
|
||||
|
Loading…
x
Reference in New Issue
Block a user