feacture: Builders

fix: fmt
This commit is contained in:
socram03 2022-07-04 19:34:55 -04:00
parent 9a5a25c2d5
commit 35ec425916
18 changed files with 296 additions and 64 deletions

View File

@ -1,7 +1,7 @@
{ {
"fmt": { "fmt": {
"files": { "files": {
"exclude": "vendor" "exclude": ["vendor"]
}, },
"options": { "options": {
"indentWidth": 4, "indentWidth": 4,

View File

@ -1,17 +1,16 @@
import type { import type {
DiscordChannel, DiscordChannel,
DiscordChannelPinsUpdate, DiscordChannelPinsUpdate,
DiscordEmoji,
DiscordGuild, DiscordGuild,
DiscordGuildBanAddRemove,
DiscordGuildEmojisUpdate,
DiscordGuildMemberAdd, DiscordGuildMemberAdd,
DiscordGuildMemberRemove, DiscordGuildMemberRemove,
DiscordGuildMemberUpdate, DiscordGuildMemberUpdate,
DiscordGuildBanAddRemove,
DiscordGuildEmojisUpdate,
DiscordGuildRoleCreate, DiscordGuildRoleCreate,
DiscordGuildRoleUpdate,
DiscordGuildRoleDelete, DiscordGuildRoleDelete,
DiscordUser, DiscordGuildRoleUpdate,
DiscordEmoji,
DiscordInteraction, DiscordInteraction,
DiscordMemberWithUser, DiscordMemberWithUser,
DiscordMessage, DiscordMessage,
@ -25,7 +24,8 @@ import type {
// DiscordThreadMemberUpdate, // DiscordThreadMemberUpdate,
// DiscordThreadMembersUpdate, // DiscordThreadMembersUpdate,
DiscordThreadListSync, DiscordThreadListSync,
DiscordWebhookUpdate DiscordUser,
DiscordWebhookUpdate,
} from "../vendor/external.ts"; } from "../vendor/external.ts";
import type { Snowflake } from "../util/Snowflake.ts"; import type { Snowflake } from "../util/Snowflake.ts";
import type { Session } from "../session/Session.ts"; import type { Session } from "../session/Session.ts";
@ -37,7 +37,7 @@ import ThreadMember from "../structures/ThreadMember.ts";
import Member from "../structures/Member.ts"; import Member from "../structures/Member.ts";
import Message from "../structures/Message.ts"; import Message from "../structures/Message.ts";
import User from "../structures/User.ts"; import User from "../structures/User.ts";
import Guild from "../structures/guilds/Guild.ts"; import Guild from "../structures/guilds/Guild.ts";
import Interaction from "../structures/interactions/Interaction.ts"; import Interaction from "../structures/interactions/Interaction.ts";
export type RawHandler<T> = (...args: [Session, number, T]) => void; export type RawHandler<T> = (...args: [Session, number, T]) => void;
@ -90,20 +90,20 @@ export const GUILD_BAN_REMOVE: RawHandler<DiscordGuildBanAddRemove> = (session,
}; };
export const GUILD_EMOJIS_UPDATE: RawHandler<DiscordGuildEmojisUpdate> = (session, _shardId, data) => { export const GUILD_EMOJIS_UPDATE: RawHandler<DiscordGuildEmojisUpdate> = (session, _shardId, data) => {
session.emit("guildEmojisUpdate", { guildId: data.guild_id, emojis: data.emojis}) session.emit("guildEmojisUpdate", { guildId: data.guild_id, emojis: data.emojis });
}; };
export const GUILD_ROLE_CREATE: RawHandler<DiscordGuildRoleCreate> = (session, _shardId, data) => { export const GUILD_ROLE_CREATE: RawHandler<DiscordGuildRoleCreate> = (session, _shardId, data) => {
session.emit("guildRoleCreate", { guildId: data.guild_id, role: data.role }); session.emit("guildRoleCreate", { guildId: data.guild_id, role: data.role });
} };
export const GUILD_ROLE_UPDATE: RawHandler<DiscordGuildRoleUpdate> = (session, _shardId, data) => { export const GUILD_ROLE_UPDATE: RawHandler<DiscordGuildRoleUpdate> = (session, _shardId, data) => {
session.emit("guildRoleUpdate", { guildId: data.guild_id, role: data.role }); session.emit("guildRoleUpdate", { guildId: data.guild_id, role: data.role });
} };
export const GUILD_ROLE_DELETE: RawHandler<DiscordGuildRoleDelete> = (session, _shardId, data) => { export const GUILD_ROLE_DELETE: RawHandler<DiscordGuildRoleDelete> = (session, _shardId, data) => {
session.emit("guildRoleDelete", { guildId: data.guild_id, roleId: data.role_id }); session.emit("guildRoleDelete", { guildId: data.guild_id, roleId: data.role_id });
} };
export const INTERACTION_CREATE: RawHandler<DiscordInteraction> = (session, _shardId, interaction) => { export const INTERACTION_CREATE: RawHandler<DiscordInteraction> = (session, _shardId, interaction) => {
session.unrepliedInteractions.add(BigInt(interaction.id)); session.unrepliedInteractions.add(BigInt(interaction.id));
@ -164,7 +164,7 @@ export const CHANNEL_PINS_UPDATE: RawHandler<DiscordChannelPinsUpdate> = (sessio
}; };
export const WEBHOOKS_UPDATE: RawHandler<DiscordWebhookUpdate> = (session, _shardId, webhook) => { export const WEBHOOKS_UPDATE: RawHandler<DiscordWebhookUpdate> = (session, _shardId, webhook) => {
session.emit("webhooksUpdate", { guildId: webhook.guild_id, channelId: webhook.channel_id }) session.emit("webhooksUpdate", { guildId: webhook.guild_id, channelId: webhook.channel_id });
}; };
/* /*

5
mod.ts
View File

@ -36,6 +36,11 @@ export * from "./structures/guilds/Guild.ts";
export * from "./structures/guilds/InviteGuild.ts"; export * from "./structures/guilds/InviteGuild.ts";
export * from "./structures/builders/EmbedBuilder.ts"; export * from "./structures/builders/EmbedBuilder.ts";
export * from "./structures/builders/InputTextComponentBuilder.ts";
export * from "./structures/builders/MessageActionRow.ts";
export * from "./structures/builders/MessageButton.ts";
export * from "./structures/builders/MessageSelectMenu.ts";
export * from "./structures/builders/SelectMenuOptionBuilder.ts";
export * from "./structures/interactions/Interaction.ts"; export * from "./structures/interactions/Interaction.ts";

View File

@ -6,8 +6,8 @@ import type {
DiscordMessage, DiscordMessage,
DiscordUser, DiscordUser,
FileContent, FileContent,
MessageTypes,
MessageActivityTypes, MessageActivityTypes,
MessageTypes,
} from "../vendor/external.ts"; } from "../vendor/external.ts";
import type { Component } from "./components/Component.ts"; import type { Component } from "./components/Component.ts";
import type { GetReactions } from "../util/Routes.ts"; import type { GetReactions } from "../util/Routes.ts";
@ -181,7 +181,6 @@ export class Message implements Model {
return this.editedTimestamp; return this.editedTimestamp;
} }
get url() { get url() {
return `https://discord.com/channels/${this.guildId ?? "@me"}/${this.channelId}/${this.id}`; return `https://discord.com/channels/${this.guildId ?? "@me"}/${this.channelId}/${this.id}`;
} }

View File

@ -5,7 +5,7 @@ import Emoji from "./Emoji.ts";
/** /**
* Represents a reaction * Represents a reaction
* @link https://discord.com/developers/docs/resources/channel#reaction-object * @link https://discord.com/developers/docs/resources/channel#reaction-object
* */ */
export class MessageReaction { export class MessageReaction {
constructor(session: Session, data: DiscordReaction) { constructor(session: Session, data: DiscordReaction) {
this.session = session; this.session = session;

View File

@ -39,15 +39,15 @@ export class StageInstance implements Model {
discoverableDisabled: boolean; discoverableDisabled: boolean;
guildScheduledEventId: Snowflake; guildScheduledEventId: Snowflake;
async edit(options: { topic?: string, privacyLevel?: PrivacyLevels }) { async edit(options: { topic?: string; privacyLevel?: PrivacyLevels }) {
const stageInstance = await this.session.rest.runMethod<DiscordStageInstance>( const stageInstance = await this.session.rest.runMethod<DiscordStageInstance>(
this.session.rest, this.session.rest,
"PATCH", "PATCH",
Routes.STAGE_INSTANCE(this.id), Routes.STAGE_INSTANCE(this.id),
{ {
topic: options.topic, topic: options.topic,
privacy_level: options.privacyLevel privacy_level: options.privacyLevel,
} },
); );
return new StageInstance(this.session, stageInstance); return new StageInstance(this.session, stageInstance);

View File

@ -0,0 +1,49 @@
import { DiscordInputTextComponent, MessageComponentTypes, TextStyles } from "../../vendor/external.ts";
export class InputTextBuilder {
constructor() {
this.#data = {} as DiscordInputTextComponent;
this.type = 4;
}
#data: DiscordInputTextComponent;
type: MessageComponentTypes.InputText;
setStyle(style: TextStyles) {
this.#data.style = style;
return this;
}
setLabel(label: string) {
this.#data.label = label;
return this;
}
setPlaceholder(placeholder: string) {
this.#data.placeholder = placeholder;
return this;
}
setLength(max?: number, min?: number) {
this.#data.max_length = max;
this.#data.min_length = min;
return this;
}
setCustomId(id: string) {
this.#data.custom_id = id;
return this;
}
setValue(value: string) {
this.#data.value = value;
return this;
}
setRequired(required = true) {
this.#data.required = required;
return this;
}
toJSON() {
return { ...this.#data };
}
}

View File

@ -0,0 +1,29 @@
import { MessageComponentTypes } from "../../vendor/external.ts";
import { AnyComponentBuilder } from "../../util/builders.ts";
export class ActionRowBuilder<T extends AnyComponentBuilder> {
constructor() {
this.components = [] as T[];
this.type = 1;
}
components: T[];
type: MessageComponentTypes.ActionRow;
addComponents(...components: T[]) {
this.components.push(...components);
return this;
}
setComponents(...components: T[]) {
this.components.splice(
0,
this.components.length,
...components,
);
return this;
}
toJSON() {
return { type: this.type, components: this.components.map((c) => c.toJSON()) };
}
}

View File

@ -0,0 +1,44 @@
import { ButtonStyles, type DiscordButtonComponent, MessageComponentTypes } from "../../vendor/external.ts";
import { ComponentEmoji } from "../../util/builders.ts";
export class ButtonBuilder {
constructor() {
this.#data = {} as DiscordButtonComponent;
this.type = 2;
}
#data: DiscordButtonComponent;
type: MessageComponentTypes.Button;
setStyle(style: ButtonStyles) {
this.#data.style = style;
return this;
}
setLabel(label: string) {
this.#data.label = label;
return this;
}
setCustomId(id: string) {
this.#data.custom_id = id;
return this;
}
setEmoji(emoji: ComponentEmoji) {
this.#data.emoji = emoji;
return this;
}
setDisabled(disabled = true) {
this.#data.disabled = disabled;
return this;
}
setURL(url: string) {
this.#data.url = url;
return this;
}
toJSON(): DiscordButtonComponent {
return { ...this.#data };
}
}

View File

@ -0,0 +1,53 @@
import { type DiscordSelectMenuComponent, MessageComponentTypes } from "../../vendor/external.ts";
import { SelectMenuOptionBuilder } from "./SelectMenuOptionBuilder.ts";
export class SelectMenuBuilder {
constructor() {
this.#data = {} as DiscordSelectMenuComponent;
this.type = 3;
this.options = [];
}
#data: DiscordSelectMenuComponent;
type: MessageComponentTypes.SelectMenu;
options: SelectMenuOptionBuilder[];
setPlaceholder(placeholder: string) {
this.#data.placeholder = placeholder;
return this;
}
setValues(max?: number, min?: number) {
this.#data.max_values = max;
this.#data.min_values = min;
return this;
}
setDisabled(disabled = true) {
this.#data.disabled = disabled;
return this;
}
setCustomId(id: string) {
this.#data.custom_id = id;
return this;
}
setOptions(...options: SelectMenuOptionBuilder[]) {
this.options.splice(
0,
this.options.length,
...options,
);
return this;
}
addOptions(...options: SelectMenuOptionBuilder[]) {
this.options.push(
...options,
);
}
toJSON() {
return { ...this.#data, options: this.options.map((option) => option.toJSON()) };
}
}

View File

@ -0,0 +1,38 @@
import type { DiscordSelectOption } from "../../vendor/external.ts";
import type { ComponentEmoji } from "../../util/builders.ts";
export class SelectMenuOptionBuilder {
constructor() {
this.#data = {} as DiscordSelectOption;
}
#data: DiscordSelectOption;
setLabel(label: string) {
this.#data.label = label;
return this;
}
setValue(value: string) {
this.#data.value = value;
return this;
}
setDescription(description: string) {
this.#data.description = description;
return this;
}
setDefault(Default = true) {
this.#data.default = Default;
return this;
}
setEmoji(emoji: ComponentEmoji) {
this.#data.emoji = emoji;
return this;
}
toJSON() {
return { ...this.#data };
}
}

View File

@ -1,7 +1,12 @@
import type { Model } from "../Base.ts"; import type { Model } from "../Base.ts";
import type { Snowflake } from "../../util/Snowflake.ts"; import type { Snowflake } from "../../util/Snowflake.ts";
import type { Session } from "../../session/Session.ts"; import type { Session } from "../../session/Session.ts";
import type { ChannelTypes, DiscordChannel, DiscordInviteMetadata, DiscordListArchivedThreads } from "../../vendor/external.ts"; import type {
ChannelTypes,
DiscordChannel,
DiscordInviteMetadata,
DiscordListArchivedThreads,
} from "../../vendor/external.ts";
import type { ListArchivedThreads } from "../../util/Routes.ts"; import type { ListArchivedThreads } from "../../util/Routes.ts";
import BaseChannel from "./BaseChannel.ts"; import BaseChannel from "./BaseChannel.ts";
import ThreadChannel from "./ThreadChannel.ts"; import ThreadChannel from "./ThreadChannel.ts";
@ -25,7 +30,7 @@ export interface ThreadCreateOptions {
/** /**
* Represents the option object to create a thread channel from a message * Represents the option object to create a thread channel from a message
* @link https://discord.com/developers/docs/resources/channel#start-thread-from-message * @link https://discord.com/developers/docs/resources/channel#start-thread-from-message
* */ */
export interface ThreadCreateOptions { export interface ThreadCreateOptions {
name: string; name: string;
autoArchiveDuration?: 60 | 1440 | 4320 | 10080; autoArchiveDuration?: 60 | 1440 | 4320 | 10080;
@ -59,7 +64,6 @@ export class GuildChannel extends BaseChannel implements Model {
return invites.map((invite) => new Invite(this.session, invite)); return invites.map((invite) => new Invite(this.session, invite));
} }
async getArchivedThreads(options: ListArchivedThreads & { type: "public" | "private" | "privateJoinedThreads" }) { async getArchivedThreads(options: ListArchivedThreads & { type: "public" | "private" | "privateJoinedThreads" }) {
let func: (channelId: Snowflake, options: ListArchivedThreads) => string; let func: (channelId: Snowflake, options: ListArchivedThreads) => string;
@ -78,10 +82,10 @@ export class GuildChannel extends BaseChannel implements Model {
const { threads, members, has_more } = await this.session.rest.runMethod<DiscordListArchivedThreads>( const { threads, members, has_more } = await this.session.rest.runMethod<DiscordListArchivedThreads>(
this.session.rest, this.session.rest,
"GET", "GET",
func(this.id, options) func(this.id, options),
); );
return { return {
threads: Object.fromEntries( threads: Object.fromEntries(
threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]), threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]),
) as Record<Snowflake, ThreadChannel>, ) as Record<Snowflake, ThreadChannel>,

View File

@ -88,27 +88,27 @@ export class TextChannel {
* Mixin * Mixin
*/ */
static applyTo(klass: Function, ignore: Array<keyof TextChannel> = []) { static applyTo(klass: Function, ignore: Array<keyof TextChannel> = []) {
const methods: Array<keyof TextChannel> = [ const methods: Array<keyof TextChannel> = [
"fetchPins", "fetchPins",
"createInvite", "createInvite",
"fetchMessages", "fetchMessages",
"sendTyping", "sendTyping",
"pinMessage", "pinMessage",
"unpinMessage", "unpinMessage",
"addReaction", "addReaction",
"removeReaction", "removeReaction",
"nukeReactions", "nukeReactions",
"fetchPins", "fetchPins",
"sendMessage", "sendMessage",
"editMessage", "editMessage",
"createWebhook", "createWebhook",
]; ];
for (const method of methods) { for (const method of methods) {
if (ignore.includes(method)) continue; if (ignore.includes(method)) continue;
klass.prototype[method] = TextChannel.prototype[method]; klass.prototype[method] = TextChannel.prototype[method];
} }
} }
async fetchPins(): Promise<Message[] | []> { async fetchPins(): Promise<Message[] | []> {

View File

@ -6,14 +6,14 @@ import type {
DiscordEmoji, DiscordEmoji,
DiscordGuild, DiscordGuild,
DiscordInviteMetadata, DiscordInviteMetadata,
DiscordMemberWithUser,
DiscordRole,
DiscordListActiveThreads, DiscordListActiveThreads,
GuildFeatures, DiscordMemberWithUser,
SystemChannelFlags,
MakeRequired,
VideoQualityModes,
DiscordOverwrite, DiscordOverwrite,
DiscordRole,
GuildFeatures,
MakeRequired,
SystemChannelFlags,
VideoQualityModes,
} from "../../vendor/external.ts"; } from "../../vendor/external.ts";
import type { GetInvite } from "../../util/Routes.ts"; import type { GetInvite } from "../../util/Routes.ts";
import { import {
@ -136,7 +136,7 @@ export interface GuildCreateOptionsChannel {
/** /**
* @link https://discord.com/developers/docs/resources/guild#create-guild * @link https://discord.com/developers/docs/resources/guild#create-guild
* */ */
export interface GuildCreateOptions { export interface GuildCreateOptions {
name: string; name: string;
afkChannelId?: Snowflake; afkChannelId?: Snowflake;
@ -167,7 +167,7 @@ export interface GuildCreateOptions {
/** /**
* @link https://discord.com/developers/docs/resources/guild#modify-guild-json-params * @link https://discord.com/developers/docs/resources/guild#modify-guild-json-params
* */ */
export interface GuildEditOptions extends Omit<GuildCreateOptions, "roles" | "channels"> { export interface GuildEditOptions extends Omit<GuildCreateOptions, "roles" | "channels"> {
ownerId?: Snowflake; ownerId?: Snowflake;
splashURL?: string; splashURL?: string;
@ -473,10 +473,10 @@ export class Guild extends BaseGuild implements Model {
const { threads, members } = await this.session.rest.runMethod<DiscordListActiveThreads>( const { threads, members } = await this.session.rest.runMethod<DiscordListActiveThreads>(
this.session.rest, this.session.rest,
"GET", "GET",
Routes.THREAD_ACTIVE(this.id) Routes.THREAD_ACTIVE(this.id),
); );
return { return {
threads: Object.fromEntries( threads: Object.fromEntries(
threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]), threads.map((thread) => [thread.id, new ThreadChannel(this.session, thread, this.id)]),
) as Record<Snowflake, ThreadChannel>, ) as Record<Snowflake, ThreadChannel>,
@ -488,13 +488,13 @@ export class Guild extends BaseGuild implements Model {
/*** /***
* Makes the bot leave the guild * Makes the bot leave the guild
* */ */
async leave() { async leave() {
} }
/*** /***
* Deletes a guild * Deletes a guild
* */ */
async delete() { async delete() {
await this.session.rest.runMethod<undefined>( await this.session.rest.runMethod<undefined>(
this.session.rest, this.session.rest,
@ -507,7 +507,7 @@ export class Guild extends BaseGuild implements Model {
* Creates a guild and returns its data, the bot joins the guild * Creates a guild and returns its data, the bot joins the guild
* This was modified from discord.js to make it compatible * This was modified from discord.js to make it compatible
* precondition: Bot should be in less than 10 servers * precondition: Bot should be in less than 10 servers
* */ */
static async create(session: Session, options: GuildCreateOptions) { static async create(session: Session, options: GuildCreateOptions) {
const guild = await session.rest.runMethod<DiscordGuild>(session.rest, "POST", Routes.GUILDS(), { const guild = await session.rest.runMethod<DiscordGuild>(session.rest, "POST", Routes.GUILDS(), {
name: options.name, name: options.name,
@ -540,7 +540,7 @@ export class Guild extends BaseGuild implements Model {
hoist: role.hoist, hoist: role.hoist,
position: role.position, position: role.position,
unicode_emoji: role.unicodeEmoji, unicode_emoji: role.unicodeEmoji,
icon: options.iconURL || urlToBase64(options.iconURL!), icon: options.iconURL || urlToBase64(options.iconURL!),
})), })),
}); });
@ -549,7 +549,7 @@ export class Guild extends BaseGuild implements Model {
/** /**
* Edits a guild and returns its data * Edits a guild and returns its data
* */ */
async edit(session: Session, options: GuildEditOptions) { async edit(session: Session, options: GuildEditOptions) {
const guild = await session.rest.runMethod<DiscordGuild>(session.rest, "PATCH", Routes.GUILDS(), { const guild = await session.rest.runMethod<DiscordGuild>(session.rest, "PATCH", Routes.GUILDS(), {
name: options.name, name: options.name,

View File

@ -7,7 +7,8 @@ if (!token) {
throw new Error("Please provide a token"); throw new Error("Please provide a token");
} }
const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages | GatewayIntents.GuildMembers | GatewayIntents.GuildBans const intents = GatewayIntents.MessageContent | GatewayIntents.Guilds | GatewayIntents.GuildMessages |
GatewayIntents.GuildMembers | GatewayIntents.GuildBans;
const session = new Session({ token, intents }); const session = new Session({ token, intents });
session.on("ready", (payload) => { session.on("ready", (payload) => {

View File

@ -233,7 +233,6 @@ export function CHANNEL_WEBHOOKS(channelId: Snowflake) {
return `/channels/${channelId}/webhooks`; return `/channels/${channelId}/webhooks`;
} }
export function THREAD_START_PUBLIC(channelId: Snowflake, messageId: Snowflake) { export function THREAD_START_PUBLIC(channelId: Snowflake, messageId: Snowflake) {
return `/channels/${channelId}/messages/${messageId}/threads`; return `/channels/${channelId}/messages/${messageId}/threads`;
} }
@ -271,8 +270,8 @@ export function THREAD_ARCHIVED_PUBLIC(channelId: Snowflake, options?: ListArchi
let url = `/channels/${channelId}/threads/archived/public?`; let url = `/channels/${channelId}/threads/archived/public?`;
if (options) { if (options) {
if (options.before) url += `before=${new Date(options.before).toISOString()}`; if (options.before) url += `before=${new Date(options.before).toISOString()}`;
if (options.limit) url += `&limit=${options.limit}`; if (options.limit) url += `&limit=${options.limit}`;
} }
return url; return url;
@ -293,8 +292,8 @@ export function THREAD_ARCHIVED_PRIVATE_JOINED(channelId: Snowflake, options?: L
let url = `/channels/${channelId}/users/@me/threads/archived/private?`; let url = `/channels/${channelId}/users/@me/threads/archived/private?`;
if (options) { if (options) {
if (options.before) url += `before=${new Date(options.before).toISOString()}`; if (options.before) url += `before=${new Date(options.before).toISOString()}`;
if (options.limit) url += `&limit=${options.limit}`; if (options.limit) url += `&limit=${options.limit}`;
} }
return url; return url;

9
util/builders.ts Normal file
View File

@ -0,0 +1,9 @@
import { ButtonBuilder, InputTextBuilder, SelectMenuBuilder } from "../mod.ts";
import { Snowflake } from "./Snowflake.ts";
export type AnyComponentBuilder = InputTextBuilder | SelectMenuBuilder | ButtonBuilder;
export type ComponentEmoji = {
id: Snowflake;
name: string;
animated?: boolean;
};

View File

@ -1171,6 +1171,8 @@ export interface DiscordSelectMenuComponent {
max_values?: number; max_values?: number;
/** The choices! Maximum of 25 items. */ /** The choices! Maximum of 25 items. */
options: DiscordSelectOption[]; options: DiscordSelectOption[];
/** Whether or not this select menu is disabled */
disabled?: boolean;
} }
export interface DiscordSelectOption { export interface DiscordSelectOption {