Skip to content

Commit

Permalink
select menus
Browse files Browse the repository at this point in the history
  • Loading branch information
thewilloftheshadow committed Apr 26, 2024
1 parent 23f2d97 commit 98492ee
Show file tree
Hide file tree
Showing 15 changed files with 423 additions and 2 deletions.
72 changes: 72 additions & 0 deletions apps/rocko/src/commands/testing/every_select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
Command,
type CommandInteraction,
Row,
StringSelectMenu,
type StringSelectMenuInteraction,
RoleSelectMenu,
type RoleSelectMenuInteraction,
ChannelSelectMenu,
type ChannelSelectMenuInteraction,
MentionableSelectMenu,
type MentionableSelectMenuInteraction,
UserSelectMenu,
type UserSelectMenuInteraction
} from "@buape/carbon"

export default class SelectCommand extends Command {
name = "every_select"
description = "Send every select menu"
defer = true

async run(interaction: CommandInteraction) {
interaction.reply({
content: "Select menus! <:caughtIn4k:1145473115703496816>",
components: [
new Row([new StringSelect()]),
new Row([new RoleSelect()]),
new Row([new MentionableSelect()]),
new Row([new ChannelSelect()]),
new Row([new UserSelect()])
]
})
}
}

class StringSelect extends StringSelectMenu {
customId = "string-select"
placeholder = "String select menu"
options = [{ label: "Option 1", value: "option1" }]
async run(interaction: StringSelectMenuInteraction) {
interaction.reply({ content: interaction.values.join(", ") })
}
}

class RoleSelect extends RoleSelectMenu {
customId = "role-select"
placeholder = "Role select menu"
async run(interaction: RoleSelectMenuInteraction) {
interaction.reply({ content: interaction.values.join(", ") })
}
}
class MentionableSelect extends MentionableSelectMenu {
customId = "mentionable-select"
placeholder = "Mentionable select menu"
async run(interaction: MentionableSelectMenuInteraction) {
interaction.reply({ content: interaction.values.join(", ") })
}
}
class ChannelSelect extends ChannelSelectMenu {
customId = "channel-select"
placeholder = "Channel select menu"
async run(interaction: ChannelSelectMenuInteraction) {
interaction.reply({ content: interaction.values.join(", ") })
}
}
class UserSelect extends UserSelectMenu {
customId = "user-select"
placeholder = "User select menu"
async run(interaction: UserSelectMenuInteraction) {
interaction.reply({ content: interaction.values.join(", ") })
}
}
21 changes: 21 additions & 0 deletions packages/carbon/src/classes/ChannelSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {
ComponentType,
type APIChannelSelectComponent
} from "discord-api-types/v10"
import { AnySelectMenu } from "../structures/AnySelectMenu.js"
import type { ChannelSelectMenuInteraction } from "../structures/ChannelSelectMenuInteraction.js"

export abstract class ChannelSelectMenu extends AnySelectMenu {
type: ComponentType.ChannelSelect = ComponentType.ChannelSelect
channelTypes?: APIChannelSelectComponent["channel_types"]
defaultValues?: APIChannelSelectComponent["default_values"]
abstract run(interaction: ChannelSelectMenuInteraction): Promise<void>

serializeOptions() {
return {
type: this.type,
default_values: this.defaultValues,
channel_types: this.channelTypes
}
}
}
19 changes: 19 additions & 0 deletions packages/carbon/src/classes/MentionableSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
ComponentType,
type APIMentionableSelectComponent
} from "discord-api-types/v10"
import { AnySelectMenu } from "../structures/AnySelectMenu.js"
import type { MentionableSelectMenuInteraction } from "../structures/MentionableSelectMenuInteraction.js"

export abstract class MentionableSelectMenu extends AnySelectMenu {
type: ComponentType.MentionableSelect = ComponentType.MentionableSelect
defaultValues?: APIMentionableSelectComponent["default_values"]
abstract run(interaction: MentionableSelectMenuInteraction): Promise<void>

serializeOptions() {
return {
type: this.type,
default_values: this.defaultValues
}
}
}
19 changes: 19 additions & 0 deletions packages/carbon/src/classes/RoleSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
ComponentType,
type APIRoleSelectComponent
} from "discord-api-types/v10"
import { AnySelectMenu } from "../structures/AnySelectMenu.js"
import type { RoleSelectMenuInteraction } from "../structures/RoleSelectMenuInteraction.js"

export abstract class RoleSelectMenu extends AnySelectMenu {
type: ComponentType.RoleSelect = ComponentType.RoleSelect
defaultValues?: APIRoleSelectComponent["default_values"]
abstract run(interaction: RoleSelectMenuInteraction): Promise<void>

serializeOptions() {
return {
type: this.type,
default_values: this.defaultValues
}
}
}
19 changes: 19 additions & 0 deletions packages/carbon/src/classes/StringSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
type APIStringSelectComponent,
ComponentType
} from "discord-api-types/v10"
import { AnySelectMenu } from "../structures/AnySelectMenu.js"
import type { StringSelectMenuInteraction } from "../structures/StringSelectMenuInteraction.js"

export abstract class StringSelectMenu extends AnySelectMenu {
type: ComponentType.StringSelect = ComponentType.StringSelect
abstract options: APIStringSelectComponent["options"]
abstract run(interaction: StringSelectMenuInteraction): Promise<void>

serializeOptions() {
return {
type: this.type,
options: this.options
}
}
}
19 changes: 19 additions & 0 deletions packages/carbon/src/classes/UserSelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import {
ComponentType,
type APIUserSelectComponent
} from "discord-api-types/v10"
import { AnySelectMenu } from "../structures/AnySelectMenu.js"
import type { UserSelectMenuInteraction } from "../structures/UserSelectMenuInteraction.js"

export abstract class UserSelectMenu extends AnySelectMenu {
type: ComponentType.UserSelect = ComponentType.UserSelect
defaultValues?: APIUserSelectComponent["default_values"]
abstract run(interaction: UserSelectMenuInteraction): Promise<void>

serializeOptions() {
return {
type: this.type,
default_values: this.defaultValues
}
}
}
12 changes: 12 additions & 0 deletions packages/carbon/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
// ----- Classes -----
export * from "./classes/Button.js"
export * from "./classes/ChannelSelectMenu.js"
export * from "./classes/Client.js"
export * from "./classes/Command.js"
export * from "./classes/CommandWithSubcommands.js"
export * from "./classes/CommandWithSubcommandGroups.js"
export * from "./classes/MentionableSelectMenu.js"
export * from "./classes/RoleSelectMenu.js"
export * from "./classes/Row.js"
export * from "./classes/StringSelectMenu.js"
export * from "./classes/UserSelectMenu.js"

// ----- Structures -----
export * from "./structures/AnySelectMenu.js"
export * from "./structures/AnySelectMenuInteraction.js"
export * from "./structures/Base.js"
export * from "./structures/BaseCommand.js"
export * from "./structures/BaseComponent.js"
export * from "./structures/BaseComponentInteraction.js"
export * from "./structures/BaseInteraction.js"
export * from "./structures/ButtonInteraction.js"
export * from "./structures/ChannelSelectMenuInteraction.js"
export * from "./structures/Collector.js"
export * from "./structures/CommandInteraction.js"
export * from "./structures/ComponentHandler.js"
export * from "./structures/MentionableSelectMenuInteraction.js"
export * from "./structures/RoleSelectMenuInteraction.js"
export * from "./structures/StringSelectMenuInteraction.js"
export * from "./structures/UserSelectMenuInteraction.js"

// ----- Types -----
export * from "discord-api-types/v10"
Expand Down
63 changes: 63 additions & 0 deletions packages/carbon/src/structures/AnySelectMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type {
APIChannelSelectComponent,
APIMentionableSelectComponent,
APIRoleSelectComponent,
APISelectMenuComponent,
APIStringSelectComponent,
APIUserSelectComponent,
ComponentType
} from "discord-api-types/v10"
import type { AnySelectMenuInteraction } from "./AnySelectMenuInteraction.js"
import { BaseComponent } from "./BaseComponent.js"

export type AnySelectMenuComponentType =
| ComponentType.ChannelSelect
| ComponentType.RoleSelect
| ComponentType.StringSelect
| ComponentType.UserSelect
| ComponentType.MentionableSelect

export abstract class AnySelectMenu extends BaseComponent {
abstract type: AnySelectMenuComponentType
abstract run(interaction: AnySelectMenuInteraction): Promise<void>

minValues?: number
maxValues?: number
disabled?: boolean
placeholder?: string

serialize = (): APISelectMenuComponent => {
const options = this.serializeOptions()
return {
...options,
custom_id: this.customId,
disabled: this.disabled,
placeholder: this.placeholder,
min_values: this.minValues,
max_values: this.maxValues
}
}

abstract serializeOptions():
| {
type: ComponentType.ChannelSelect
channel_types: APIChannelSelectComponent["channel_types"]
default_values: APIChannelSelectComponent["default_values"]
}
| {
type: ComponentType.StringSelect
options: APIStringSelectComponent["options"]
}
| {
type: ComponentType.RoleSelect
default_values: APIRoleSelectComponent["default_values"]
}
| {
type: ComponentType.UserSelect
default_values: APIUserSelectComponent["default_values"]
}
| {
type: ComponentType.MentionableSelect
default_values: APIMentionableSelectComponent["default_values"]
}
}
29 changes: 29 additions & 0 deletions packages/carbon/src/structures/AnySelectMenuInteraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {
InteractionType,
type APIMessageSelectMenuInteractionData,
type APIMessageComponentSelectMenuInteraction
} from "discord-api-types/v10"
import type { Client } from "../classes/Client.js"
import { BaseComponentInteraction } from "./BaseComponentInteraction.js"
import { splitCustomId } from "../utils.js"

export abstract class AnySelectMenuInteraction extends BaseComponentInteraction {
customId: string = splitCustomId(
(this.rawData.data as APIMessageSelectMenuInteractionData).custom_id
)[0]
constructor(client: Client, data: APIMessageComponentSelectMenuInteraction) {
super(client, data)
if (!data.data)
throw new Error("Invalid interaction data was used to create this class")
if (data.type !== InteractionType.MessageComponent) {
throw new Error("Invalid interaction type was used to create this class")
}
}

/**
* The raw IDs of the selected options (either role/string/channel IDs or the IDs you provided in your options)
*/
get values(): string[] {
return (this.rawData.data as APIMessageSelectMenuInteractionData).values
}
}
15 changes: 15 additions & 0 deletions packages/carbon/src/structures/ChannelSelectMenuInteraction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import {
type APIMessageComponentSelectMenuInteraction,
ComponentType
} from "discord-api-types/v10"
import type { Client } from "../classes/Client.js"
import { AnySelectMenuInteraction } from "./AnySelectMenuInteraction.js"

export class ChannelSelectMenuInteraction extends AnySelectMenuInteraction {
constructor(client: Client, data: APIMessageComponentSelectMenuInteraction) {
super(client, data)
if (data.data.component_type !== ComponentType.ChannelSelect) {
throw new Error("Invalid component type was used to create this class")
}
}
}
56 changes: 54 additions & 2 deletions packages/carbon/src/structures/ComponentHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,21 @@ import { Button } from "../classes/Button.js"
import { ButtonInteraction } from "./ButtonInteraction.js"
import type {
APIMessageComponentButtonInteraction,
APIMessageComponentInteraction
APIMessageComponentInteraction,
APIMessageComponentSelectMenuInteraction
} from "discord-api-types/v10"
import { BaseComponentInteraction } from "./BaseComponentInteraction.js"
import type { BaseComponent } from "./BaseComponent.js"
import { RoleSelectMenu } from "../classes/RoleSelectMenu.js"
import { ChannelSelectMenu } from "../classes/ChannelSelectMenu.js"
import { MentionableSelectMenu } from "../classes/MentionableSelectMenu.js"
import { StringSelectMenu } from "../classes/StringSelectMenu.js"
import { UserSelectMenu } from "../classes/UserSelectMenu.js"
import { RoleSelectMenuInteraction } from "./RoleSelectMenuInteraction.js"
import { UserSelectMenuInteraction } from "./UserSelectMenuInteraction.js"
import { StringSelectMenuInteraction } from "./StringSelectMenuInteraction.js"
import { MentionableSelectMenuInteraction } from "./MentionableSelectMenuInteraction.js"
import { ChannelSelectMenuInteraction } from "./ChannelSelectMenuInteraction.js"

export class ComponentHandler extends Base {
collectors: Collector[] = []
Expand All @@ -30,7 +41,13 @@ export class ComponentHandler extends Base {
rawInteraction
)
const passed = this.collectors.filter((c) => c.check(interaction))
if (!passed.length) return
if (!passed.length)
return interaction.reply({
content: `Component is not cached: ${JSON.stringify({
componentType: interaction.componentType,
customId: interaction.customId
})}`
})
for (const collector of passed) {
if (collector.component instanceof Button) {
collector.component.run(
Expand All @@ -39,6 +56,41 @@ export class ComponentHandler extends Base {
interaction.rawData as APIMessageComponentButtonInteraction
)
)
} else if (collector.component instanceof RoleSelectMenu) {
collector.component.run(
new RoleSelectMenuInteraction(
this.client,
interaction.rawData as APIMessageComponentSelectMenuInteraction
)
)
} else if (collector.component instanceof ChannelSelectMenu) {
collector.component.run(
new ChannelSelectMenuInteraction(
this.client,
interaction.rawData as APIMessageComponentSelectMenuInteraction
)
)
} else if (collector.component instanceof MentionableSelectMenu) {
collector.component.run(
new MentionableSelectMenuInteraction(
this.client,
interaction.rawData as APIMessageComponentSelectMenuInteraction
)
)
} else if (collector.component instanceof StringSelectMenu) {
collector.component.run(
new StringSelectMenuInteraction(
this.client,
interaction.rawData as APIMessageComponentSelectMenuInteraction
)
)
} else if (collector.component instanceof UserSelectMenu) {
collector.component.run(
new UserSelectMenuInteraction(
this.client,
interaction.rawData as APIMessageComponentSelectMenuInteraction
)
)
}
}
}
Expand Down
Loading

0 comments on commit 98492ee

Please sign in to comment.