Skip to content

Commit

Permalink
subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
thewilloftheshadow committed Apr 19, 2024
1 parent 29e0aee commit 6bd60f1
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 47 deletions.
10 changes: 7 additions & 3 deletions apps/rocko/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { serve } from "@carbonjs/nodejs"
import { Client, Command, type CommandInteraction } from "carbon"
import { Subc } from "./subcommand.js"
import { inspect } from "node:util"

class PingCommand extends Command {
name = "ping"
description = "A simple ping command"
defer = true

async run(interaction: CommandInteraction) {
await sleep(7500)
await sleep(3000)
interaction.reply({ content: "Pong" })
}
}
Expand All @@ -18,11 +20,13 @@ const client = new Client(
publicKey: process.env.PUBLIC_KEY!,
token: process.env.DISCORD_TOKEN!
},
[new PingCommand()]
[new PingCommand(), new Subc()]
)

serve(client, { port: 3000 })

const sleep = async (ms: number) => {
console.log(inspect(client.commands.map((x) => x.serialize()), false, null, true))

export const sleep = async (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}
33 changes: 33 additions & 0 deletions apps/rocko/src/subcommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Command, Subcommand, type CommandInteraction } from "carbon";
import { sleep } from "./index.js";

class Sub1 extends Command {
name = "sub1"
description = "Subcommand 1"
defer = true

async run(interaction: CommandInteraction) {
await sleep(3000)
interaction.reply({ content: "Subcommand 1" })
}
}

class Sub2 extends Command {
name = "sub2"
description = "Subcommand 2"
defer = true

async run(interaction: CommandInteraction) {
await sleep(3000)
interaction.reply({ content: "Subcommand 2" })
}
}


export class Subc extends Subcommand {
name = "subc"
description = "Subcommands!"
defer = true

subcommands = [new Sub1(), new Sub2()]
}
2 changes: 1 addition & 1 deletion packages/carbon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "./dist/src/index.js",
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"dev": "tsc -w -preserveWatchOutput",
"typecheck": "tsc --noEmit"
},
"license": "MIT",
Expand Down
57 changes: 45 additions & 12 deletions packages/carbon/src/classes/Client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@ import {
InteractionType,
MessageFlags,
RouteBases,
Routes
Routes,
ApplicationCommandType
} from "discord-api-types/v10"
import { PlatformAlgorithm, isValidRequest } from "discord-verify"
import { AutoRouter, type IRequestStrict, StatusError, json } from "itty-router"
import { CommandInteraction } from "../structures/CommandInteraction.js"
import { RestClient } from "../structures/RestClient.js"
import type { Command } from "./Command.js"
import { Command } from "./Command.js"
import pkg from "../../package.json" assert { type: "json" }
import type { BaseCommand } from "../structures/_BaseCommand.js"
import { Subcommand } from "./Subcommand.js"

/**
* The options used for initializing the client
Expand All @@ -34,7 +37,7 @@ export class Client {
/**
* The commands that the client has registered
*/
commands: Command[]
commands: BaseCommand[]
/**
* The router used to handle requests
*/
Expand All @@ -49,7 +52,7 @@ export class Client {
* @param options The options used to initialize the client
* @param commands The commands that the client has registered
*/
constructor(options: ClientOptions, commands: Command[]) {
constructor(options: ClientOptions, commands: BaseCommand[]) {
this.options = options
this.commands = commands
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
Expand All @@ -67,6 +70,7 @@ export class Client {
const commands = this.commands.map((command) => {
return command.serialize()
})
console.log(commands)
await fetch(
RouteBases.api + Routes.applicationCommands(this.options.clientId),
{
Expand Down Expand Up @@ -119,17 +123,46 @@ export class Client {

const interaction = new CommandInteraction(this, rawInteraction)

if (command.defer) {
command.run(interaction)
if (command instanceof Command) {
if (command.defer) {
command.run(interaction)
return json({
type: InteractionResponseType.DeferredChannelMessageWithSource,
flags: command.ephemeral ? MessageFlags.Ephemeral : 0
})
}
return json({
type: InteractionResponseType.DeferredChannelMessageWithSource,
flags: command.ephemeral ? MessageFlags.Ephemeral : 0
type: InteractionResponseType.ChannelMessageWithSource,
content: "Man someone should really implement non-deferred replies huh"
})
}

if (command instanceof Subcommand) {
if (rawInteraction.data.type !== ApplicationCommandType.ChatInput) {
return json({
type: InteractionResponseType.ChannelMessageWithSource,
data: {
content: "Subcommands must be used with ChatInput"
}
})
}
const data = rawInteraction.data
const subcommand = command.subcommands.find(
(x) => x.name === data.options?.[0]?.name
)
if (!subcommand) return new Response(null, { status: 400 })

if (subcommand.defer) {
subcommand.run(interaction)
return json({
type: InteractionResponseType.DeferredChannelMessageWithSource,
flags: subcommand.ephemeral ? MessageFlags.Ephemeral : 0
})
}
return json({
type: InteractionResponseType.ChannelMessageWithSource
})
}
return json({
type: InteractionResponseType.ChannelMessageWithSource,
content: "Man someone should really implement non-deferred replies huh"
})
})
}

Expand Down
35 changes: 7 additions & 28 deletions packages/carbon/src/classes/Command.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,19 @@
import type { RESTPostAPIChatInputApplicationCommandsJSONBody } from "discord-api-types/v10"
import { ApplicationCommandType } from "discord-api-types/v10"
import type { CommandInteraction } from "../structures/CommandInteraction.js"
import { BaseCommand } from "../structures/_BaseCommand.js"

/**
* Represents a command that the user creates
* Represents a standard command that the user creates
*/
export abstract class Command {
/**
* The name of the command (e.g. "ping" for /ping)
*/
abstract name: string
/**
* A description of the command
*/
abstract description: string
/**
* Whether the command response should be automatically deferred
*/
defer = false
/**
* Whether the command response should be ephemeral
*/
ephemeral = false

export abstract class Command extends BaseCommand {
type = ApplicationCommandType.ChatInput
/**
* The function that is called when the command is ran
* @param interaction The interaction that triggered the command
*/
abstract run(interaction: CommandInteraction): Promise<void>

/**
* Serializes the command into a JSON object that can be sent to Discord
*/
serialize() {
return {
name: this.name,
description: this.description
} satisfies RESTPostAPIChatInputApplicationCommandsJSONBody
serializeOptions() {
return []
}
}
16 changes: 16 additions & 0 deletions packages/carbon/src/classes/Subcommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { type APIApplicationCommandBasicOption, ApplicationCommandType } from "discord-api-types/v10";
import { BaseCommand } from "../structures/_BaseCommand.js";
import type { Command } from "./Command.js";

/**
* Represents a subcommand command that the user creates.
* You make this instead of a {@link Command} class when you want to have subcommands in your options.
*/
export abstract class Subcommand extends BaseCommand {
type = ApplicationCommandType.ChatInput
abstract subcommands: Command[]

serializeOptions(): APIApplicationCommandBasicOption[] {
return this.subcommands.map((subcommand) => subcommand.serialize()) as unknown as APIApplicationCommandBasicOption[];
}
}
1 change: 1 addition & 0 deletions packages/carbon/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// ----- Classes -----
export * from "./classes/Client.js"
export * from "./classes/Command.js"
export * from "./classes/Subcommand.js"

// ----- Structures -----
export * from "./structures/Base.js"
Expand Down
4 changes: 3 additions & 1 deletion packages/carbon/src/structures/CommandInteraction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,15 @@ export class CommandInteraction extends BaseInteraction {
*/
async reply(data: RESTPostAPIChannelMessageJSONBody) {
// TODO: Handle non-deferred

console.log(JSON.stringify(data, null, 2))
this.client.rest.patch(
Routes.webhookMessage(
this.rawData.application_id,
this.rawData.token,
"@original"
),
{ body: JSON.stringify(data) }
{ body: data }
)
}
}
43 changes: 43 additions & 0 deletions packages/carbon/src/structures/_BaseCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import type { ApplicationCommandType, RESTPostAPIApplicationCommandsJSONBody } from "discord-api-types/v10"

/**
* Represents the base data of a command that the user creates
*/
export abstract class BaseCommand {
/**
* The name of the command (e.g. "ping" for /ping)
*/
abstract name: string
/**
* A description of the command
*/
abstract description: string
/**
* Whether the command response should be automatically deferred
*/
defer = false
/**
* Whether the command response should be ephemeral
*/
ephemeral = false
/**
* The type of the command
*/
abstract type: ApplicationCommandType

/**
* Serializes the command into a JSON object that can be sent to Discord
*/
serialize() {
const data: RESTPostAPIApplicationCommandsJSONBody = {
name: this.name,
description: this.description,
type: this.type,
options: this.serializeOptions()
}

return data
}

abstract serializeOptions(): RESTPostAPIApplicationCommandsJSONBody["options"]
}
1 change: 1 addition & 0 deletions packages/carbon/src/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const Omit = <T, K extends keyof T>(Class: new () => T, keys: K[]): new () => Omit<T, typeof keys[number]> => Class;
2 changes: 1 addition & 1 deletion packages/nodejs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "./dist/src/index.js",
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"dev": "tsc -w -preserveWatchOutput",
"typecheck": "tsc --noEmit"
},
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion turbo/generators/templates/package.json.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "./dist/src/index.js",
"scripts": {
"build": "tsc",
"dev": "tsc -w",
"dev": "tsc -w -preserveWatchOutput",
"typecheck": "tsc --noEmit"
},
"license": "MIT",
Expand Down

0 comments on commit 6bd60f1

Please sign in to comment.