Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type Work #106

Draft
wants to merge 6 commits into
base: autocomplete
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions packages/interaction-kit/src/application.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import type { FastifyRequest, FastifyReply } from "fastify";

import type { FastifyReply, FastifyRequest } from "fastify";
import fs from "node:fs";
import path from "node:path";
import SlashCommand from "./commands/slash-command";
import ContextMenu from "./commands/context-menu";
import Config from "./api/config";
import ContextMenu from "./commands/context-menu";
import SlashCommand from "./commands/slash-command";
import { ExecutableComponent, isExecutableComponent } from "./components";
import {
ApplicationCommandType,
Interaction as InteractionDefinition,
Snowflake,
ApplicationCommandType,
} from "./definitions";
import * as Interaction from "./interactions";
import ApplicationCommandInteraction from "./interactions/application-commands/application-command-interaction";
import { InteractionKitCommand, SerializableComponent } from "./interfaces";
import startInteractionKitServer from "./server";
import ApplicationCommandInteraction from "./interactions/application-commands/application-command-interaction";
import { ExecutableComponent, isExecutableComponent } from "./components";

type ApplicationArgs = {
applicationID: string;
Expand Down
53 changes: 24 additions & 29 deletions packages/interaction-kit/src/commands/slash-command.ts
Original file line number Diff line number Diff line change
@@ -1,69 +1,61 @@
import { ApplicationCommand, ApplicationCommandType } from "../definitions";
import Application from "../application";
import { Input } from "../components/inputs";
import { Optional, InteractionKitCommand } from "../interfaces";
import type { InputKey } from "../components/inputs";
import { ApplicationCommand, ApplicationCommandType } from "../definitions";
import SlashCommandInteraction from "../interactions/application-commands/slash-command-interaction";
import SlashCommandAutocompleteInteraction from "../interactions/autcomplete/application-command-autocomplete";
import SlashCommandAutocompleteInteraction from "../interactions/autocomplete/application-command-autocomplete";
import { InteractionKitCommand, Optional } from "../interfaces";

// TODO: options OR autocomplete
type CommandArgs = {
type CommandArgs<V extends InputKey, T extends readonly [V, ...V[]] | []> = {
name: string;
description: string;
defaultPermission?: boolean;
options?: Input[];
options?: T;
onAutocomplete?: (
interaction: SlashCommandAutocompleteInteraction,
application: Application
) => void;
handler: (interaction: SlashCommandInteraction) => void;
handler: (interaction: SlashCommandInteraction<T>) => void;
};

export default class SlashCommand
implements InteractionKitCommand<SlashCommandInteraction>
export default class SlashCommand<
V extends InputKey,
T extends readonly [V, ...V[]] | []
> implements InteractionKitCommand<SlashCommandInteraction<T>>
{
public readonly type = ApplicationCommandType.CHAT_INPUT;

name: string;
#description: string;
#defaultPermission: boolean;
#options: Map<string, Input>;

onAutocomplete?: (
interaction: SlashCommandAutocompleteInteraction,
application: Application
) => void;

handler: (
interaction: SlashCommandInteraction,
interaction: SlashCommandInteraction<T>,
application: Application
) => void;

private readonly options: T;

constructor({
name,
description,
options,
onAutocomplete,
handler,
defaultPermission = true,
}: CommandArgs) {
}: CommandArgs<V, T>) {
// TODO: Validate: 1-32 lowercase character name matching ^[\w-]{1,32}$
this.name = name;
this.#description = description;
this.#defaultPermission = defaultPermission;
this.handler = handler;
this.onAutocomplete = onAutocomplete;
this.#options = new Map();

options?.forEach((option) => {
const key = option.name.toLowerCase();
if (this.#options.has(key)) {
throw new Error(
`Option names must be unique (case insensitive). Duplicate name detected: ${key}`
);
}

this.#options.set(key, option);
});

this.options = options ?? ([] as T);
}

group() {
Expand All @@ -83,13 +75,16 @@ export default class SlashCommand
return false;
}

if (this.#options.size !== (schema.options?.length ?? 0)) {
if (this.options.length !== (schema.options?.length ?? 0)) {
return false;
}

return (
schema.options?.every(
(option) => this.#options.get(option.name)?.equals(option) ?? false
(option) =>
this.options
.find((opt) => opt.name === option.name)
?.equals(option) ?? false
) ?? true
);
}
Expand All @@ -105,10 +100,10 @@ export default class SlashCommand
}

// TODO: Sort these so that required options come first
if (this.#options.size > 0) {
if (this.options.length > 0) {
payload.options = [];

Array.from(this.#options.entries()).forEach(([_, value]) => {
this.options.forEach((value) => {
payload.options?.push(value.serialize());
});
}
Expand Down
5 changes: 5 additions & 0 deletions packages/interaction-kit/src/components/choices.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,8 @@ export class SlashChoiceList<
});
}
}

/**
* Choices.createSelectOptionList()
* Choices.createSlashChoiceList()
*/
141 changes: 108 additions & 33 deletions packages/interaction-kit/src/components/inputs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,6 @@ import { SlashChoiceList } from "./choices";

type InputChoiceValue = ApplicationCommandOptionChoice["value"];

type InputArgs = {
type: ApplicationCommandOptionType;
name: string;
description: string;
required?: boolean;
choices?: SlashChoiceList<InputChoiceValue>;
options?: ApplicationCommandOption[];
};

export function isChoiceType(
input: ApplicationCommandOption
): input is ApplicationCommandOptionWithChoice {
Expand All @@ -31,11 +22,31 @@ export function isChoiceType(
}
}

export class Input
implements Serializable, Comparable<ApplicationCommandOption>
export interface InputKey
extends Serializable<ApplicationCommandOption>,
Comparable<ApplicationCommandOption> {
readonly name: string;
readonly type: ApplicationCommandOptionType;
}

type InputArgs<T extends string, U extends ApplicationCommandOptionType> = {
type: U;
name: T;
description: string;
required?: boolean;
choices?: SlashChoiceList<InputChoiceValue>;
options?: ApplicationCommandOption[];
};

export class Input<
Name extends string,
OptionType extends ApplicationCommandOptionType
> implements
Serializable<ApplicationCommandOption>,
Comparable<ApplicationCommandOption>
{
public readonly type;
public readonly name;
public readonly name: Name;
public readonly type: OptionType;
public readonly description;
public readonly required;
public readonly options;
Expand All @@ -48,7 +59,7 @@ export class Input
choices,
options,
required = false,
}: InputArgs) {
}: InputArgs<Name, OptionType>) {
this.type = type;
this.name = name;
this.description = description;
Expand Down Expand Up @@ -121,62 +132,126 @@ export class Input
}
}

interface StringInputArgs extends Omit<InputArgs, "type" | "options"> {
interface StringInputArgs<
Name extends string,
OptionType extends ApplicationCommandOptionType
> extends Omit<InputArgs<Name, OptionType>, "type" | "options"> {
choices?: SlashChoiceList<string>;
}

export class StringInput extends Input {
constructor(args: StringInputArgs) {
export class StringInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.STRING
> {
constructor(
args: StringInputArgs<Name, ApplicationCommandOptionType.STRING>
) {
super({ type: ApplicationCommandOptionType.STRING, ...args });
}
}

interface IntegerInputArgs extends Omit<InputArgs, "type" | "options"> {
interface IntegerInputArgs<
Name extends string,
OptionType extends ApplicationCommandOptionType
> extends Omit<InputArgs<Name, OptionType>, "type" | "options"> {
choices?: SlashChoiceList<number>;
}

export class IntegerInput extends Input {
constructor(args: IntegerInputArgs) {
export class IntegerInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.INTEGER
> {
constructor(
args: IntegerInputArgs<Name, ApplicationCommandOptionType.INTEGER>
) {
super({ type: ApplicationCommandOptionType.INTEGER, ...args });
}
}

interface NumberInputArgs extends Omit<InputArgs, "type" | "options"> {
interface NumberInputArgs<
Name extends string,
OptionType extends ApplicationCommandOptionType
> extends Omit<InputArgs<Name, OptionType>, "type" | "options"> {
choices?: SlashChoiceList<number>;
}

export class NumberInput extends Input {
constructor(args: NumberInputArgs) {
export class NumberInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.NUMBER
> {
constructor(
args: NumberInputArgs<Name, ApplicationCommandOptionType.NUMBER>
) {
super({ type: ApplicationCommandOptionType.NUMBER, ...args });
}
}

export class BooleanInput extends Input {
constructor(args: Omit<InputArgs, "type" | "choices" | "options">) {
export class BooleanInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.BOOLEAN
> {
constructor(
args: Omit<
InputArgs<Name, ApplicationCommandOptionType.BOOLEAN>,
"type" | "choices" | "options"
>
) {
super({ type: ApplicationCommandOptionType.BOOLEAN, ...args });
}
}

export class UserInput extends Input {
constructor(args: Omit<InputArgs, "type" | "choices" | "options">) {
export class UserInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.USER
> {
constructor(
args: Omit<
InputArgs<Name, ApplicationCommandOptionType.USER>,
"type" | "choices" | "options"
>
) {
super({ type: ApplicationCommandOptionType.USER, ...args });
}
}

export class ChannelInput extends Input {
constructor(args: Omit<InputArgs, "type" | "choices" | "options">) {
export class ChannelInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.CHANNEL
> {
constructor(
args: Omit<
InputArgs<Name, ApplicationCommandOptionType.CHANNEL>,
"type" | "choices" | "options"
>
) {
super({ type: ApplicationCommandOptionType.CHANNEL, ...args });
}
}

export class RoleInput extends Input {
constructor(args: Omit<InputArgs, "type" | "choices" | "options">) {
export class RoleInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.ROLE
> {
constructor(
args: Omit<
InputArgs<Name, ApplicationCommandOptionType.ROLE>,
"type" | "choices" | "options"
>
) {
super({ type: ApplicationCommandOptionType.ROLE, ...args });
}
}

export class MentionableInput extends Input {
constructor(args: Omit<InputArgs, "type" | "choices" | "options">) {
export class MentionableInput<Name extends string> extends Input<
Name,
ApplicationCommandOptionType.MENTIONABLE
> {
constructor(
args: Omit<
InputArgs<Name, ApplicationCommandOptionType.MENTIONABLE>,
"type" | "choices" | "options"
>
) {
super({ type: ApplicationCommandOptionType.MENTIONABLE, ...args });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ export type ApplicationCommandInteractionData = {
custom_id?: string;
component_type?: ComponentType;
target_id?: Snowflake;
values?: Array<SelectOption["value"]>;
value?: Array<SelectOption["value"]>;
};

/** @link https://discord.com/developers/docs/interactions/receiving-and-responding#interaction-object-resolved-data-structure */
Expand Down
Loading