feat: add missing v13 component methods (#7466)

Co-authored-by: Vlad Frangu <kingdgrizzle@gmail.com>
Co-authored-by: Rodry <38259440+ImRodry@users.noreply.github.com>
Co-authored-by: Antonio Román <kyradiscord@gmail.com>
This commit is contained in:
Suneet Tipirneni
2022-02-17 19:04:34 -05:00
committed by GitHub
parent 395a68ff49
commit f7257f0765
14 changed files with 190 additions and 76 deletions

View File

@@ -2,6 +2,7 @@ import { type APIActionRowComponent, ComponentType, APIMessageComponent } from '
import type { ButtonComponent, SelectMenuComponent } from '..';
import { Component } from './Component';
import { createComponent } from './Components';
import isEqual from 'fast-deep-equal';
export type MessageComponent = ActionRowComponent | ActionRow;
@@ -46,4 +47,14 @@ export class ActionRow<T extends ActionRowComponent = ActionRowComponent> extend
components: this.components.map((component) => component.toJSON()),
};
}
public equals(other: APIActionRowComponent<APIMessageComponent> | ActionRow) {
if (other instanceof ActionRow) {
return isEqual(other.data, this.data) && isEqual(other.components, this.components);
}
return isEqual(other, {
...this.data,
components: this.components.map((component) => component.toJSON()),
});
}
}

View File

@@ -1,5 +1,11 @@
import type { JSONEncodable } from '../util/jsonEncodable';
import type { APIBaseComponent, APIMessageComponent, ComponentType } from 'discord-api-types/v9';
import type {
APIActionRowComponentTypes,
APIBaseComponent,
APIMessageComponent,
ComponentType,
} from 'discord-api-types/v9';
import type { Equatable } from '../util/equatable';
/**
* Represents a discord component
@@ -8,18 +14,17 @@ export abstract class Component<
DataType extends Partial<APIBaseComponent<ComponentType>> & {
type: ComponentType;
} = APIBaseComponent<ComponentType>,
> implements JSONEncodable<APIMessageComponent>
> implements JSONEncodable<APIMessageComponent>, Equatable<Component | APIActionRowComponentTypes>
{
/**
* The API data associated with this component
*/
protected readonly data: DataType;
/**
* Converts this component to an API-compatible JSON object
*/
public abstract toJSON(): APIMessageComponent;
public abstract equals(other: Component | APIActionRowComponentTypes): boolean;
public constructor(data: DataType) {
this.data = data;
}

View File

@@ -7,6 +7,7 @@ import {
type APIButtonComponentWithCustomId,
} from 'discord-api-types/v9';
import { Component } from '../Component';
import isEqual from 'fast-deep-equal';
/**
* Represents a non-validated button component
@@ -118,4 +119,11 @@ export class UnsafeButtonComponent extends Component<Partial<APIButtonComponent>
...this.data,
} as APIButtonComponent;
}
public equals(other: APIButtonComponent | UnsafeButtonComponent) {
if (other instanceof UnsafeButtonComponent) {
return isEqual(other.data, this.data);
}
return isEqual(other, this.data);
}
}

View File

@@ -1,6 +1,7 @@
import { ComponentType, type APISelectMenuComponent } from 'discord-api-types/v9';
import { APISelectMenuOption, ComponentType, type APISelectMenuComponent } from 'discord-api-types/v9';
import { Component } from '../Component';
import { UnsafeSelectMenuOption } from './UnsafeSelectMenuOption';
import isEqual from 'fast-deep-equal';
/**
* Represents a non-validated select menu component
@@ -101,8 +102,12 @@ export class UnsafeSelectMenuComponent extends Component<
* @param options The options to add to this select menu
* @returns
*/
public addOptions(...options: UnsafeSelectMenuOption[]) {
this.options.push(...options);
public addOptions(...options: (UnsafeSelectMenuOption | APISelectMenuOption)[]) {
this.options.push(
...options.map((option) =>
option instanceof UnsafeSelectMenuOption ? option : new UnsafeSelectMenuOption(option),
),
);
return this;
}
@@ -110,8 +115,14 @@ export class UnsafeSelectMenuComponent extends Component<
* Sets the options on this select menu
* @param options The options to set on this select menu
*/
public setOptions(...options: UnsafeSelectMenuOption[]) {
this.options.splice(0, this.options.length, ...options);
public setOptions(...options: (UnsafeSelectMenuOption | APISelectMenuOption)[]) {
this.options.splice(
0,
this.options.length,
...options.map((option) =>
option instanceof UnsafeSelectMenuOption ? option : new UnsafeSelectMenuOption(option),
),
);
return this;
}
@@ -122,4 +133,14 @@ export class UnsafeSelectMenuComponent extends Component<
options: this.options.map((o) => o.toJSON()),
} as APISelectMenuComponent;
}
public equals(other: APISelectMenuComponent | UnsafeSelectMenuComponent): boolean {
if (other instanceof UnsafeSelectMenuComponent) {
return isEqual(other.data, this.data) && isEqual(other.options, this.options);
}
return isEqual(other, {
...this.data,
options: this.options.map((o) => o.toJSON()),
});
}
}

View File

@@ -25,7 +25,14 @@ export const authorNamePredicate = fieldNamePredicate.nullable();
export const urlPredicate = z.string().url().nullish();
export const colorPredicate = z.number().gte(0).lte(0xffffff).nullable();
export const RGBPredicate = z.number().int().gte(0).lte(255);
export const colorPredicate = z
.number()
.int()
.gte(0)
.lte(0xffffff)
.nullable()
.or(z.tuple([RGBPredicate, RGBPredicate, RGBPredicate]));
export const descriptionPredicate = z.string().min(1).max(4096).nullable();

View File

@@ -13,7 +13,7 @@ import {
urlPredicate,
validateFieldLength,
} from './Assertions';
import { EmbedAuthorOptions, EmbedFooterOptions, UnsafeEmbed } from './UnsafeEmbed';
import { EmbedAuthorOptions, EmbedFooterOptions, RGBTuple, UnsafeEmbed } from './UnsafeEmbed';
/**
* Represents a validated embed in a message (image/video preview, rich embed, etc.)
@@ -48,7 +48,7 @@ export class Embed extends UnsafeEmbed {
return super.setAuthor(options);
}
public override setColor(color: number | null): this {
public override setColor(color: number | RGBTuple | null): this {
// Data assertions
return super.setColor(colorPredicate.parse(color));
}

View File

@@ -6,6 +6,10 @@ import type {
APIEmbedImage,
APIEmbedVideo,
} from 'discord-api-types/v9';
import type { Equatable } from '../../util/equatable';
import isEqual from 'fast-deep-equal';
export type RGBTuple = [red: number, green: number, blue: number];
export interface IconData {
/**
@@ -36,7 +40,7 @@ export interface EmbedImageData extends Omit<APIEmbedImage, 'proxy_url'> {
/**
* Represents a non-validated embed in a message (image/video preview, rich embed, etc.)
*/
export class UnsafeEmbed {
export class UnsafeEmbed implements Equatable<APIEmbed | UnsafeEmbed> {
protected data: APIEmbed;
public constructor(data: APIEmbed = {}) {
@@ -164,6 +168,13 @@ export class UnsafeEmbed {
);
}
/**
* The hex color of the current color of the embed
*/
public get hexColor() {
return typeof this.data.color === 'number' ? `#${this.data.color.toString(16).padStart(6, '0')}` : this.data.color;
}
/**
* Adds a field to the embed (max 25)
*
@@ -228,7 +239,12 @@ export class UnsafeEmbed {
*
* @param color The color of the embed
*/
public setColor(color: number | null): this {
public setColor(color: number | RGBTuple | null): this {
if (Array.isArray(color)) {
const [red, green, blue] = color;
this.data.color = (red << 16) + (green << 8) + blue;
return this;
}
this.data.color = color ?? undefined;
return this;
}
@@ -315,6 +331,13 @@ export class UnsafeEmbed {
return { ...this.data };
}
public equals(other: UnsafeEmbed | APIEmbed) {
const { image: thisImage, thumbnail: thisThumbnail, ...thisData } = this.data;
const data = other instanceof UnsafeEmbed ? other.data : other;
const { image, thumbnail, ...otherData } = data;
return isEqual(otherData, thisData) && image?.url === thisImage?.url && thumbnail?.url === thisThumbnail?.url;
}
/**
* Normalizes field input and resolves strings
*

View File

@@ -0,0 +1,14 @@
export interface Equatable<T> {
/**
* Whether or not this is equal to another structure
*/
equals: (other: T) => boolean;
}
/**
* Indicates if an object is equatable or not.
* @param maybeEquatable The object to check against
*/
export function isEquatable(maybeEquatable: unknown): maybeEquatable is Equatable<unknown> {
return maybeEquatable !== null && typeof maybeEquatable === 'object' && 'equals' in maybeEquatable;
}