JSX component support and time command
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -4,3 +4,4 @@ node_modules
|
||||
data
|
||||
db
|
||||
coverage
|
||||
*.tsbuildinfo
|
||||
|
||||
1
bun.lock
1
bun.lock
@@ -23,6 +23,7 @@
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"cron-parser": "^5.3.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fp-filters": "^0.5.4",
|
||||
"html-dom-parser": "^5.1.1",
|
||||
|
||||
@@ -14,12 +14,10 @@
|
||||
},
|
||||
"author": "JB <j-b-3.deviate267@passmail.net>",
|
||||
"files": [
|
||||
"dist",
|
||||
"src"
|
||||
"dist"
|
||||
],
|
||||
"main": "./dist/index.js",
|
||||
"module": "./dist/index.js",
|
||||
"types": "./dist/index*.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
@@ -62,10 +60,10 @@
|
||||
"types": "./src/eve/ref/index*.d.ts",
|
||||
"require": "./src/eve/ref/index.js"
|
||||
},
|
||||
"./eve/third-party": {
|
||||
"import": "./dist/eve/third-party/index.js",
|
||||
"types": "./src/eve/third-party/index*.d.ts",
|
||||
"require": "./src/eve/third-party/index.js"
|
||||
"./eve/third-party/janice.js": {
|
||||
"import": "./dist/eve/third-party/janice.js",
|
||||
"types": "./dist/types/eve/third-party/janice.d.ts",
|
||||
"require": "./src/eve/third-party/janice.js"
|
||||
},
|
||||
"./eve/models": {
|
||||
"import": "./dist/eve/models/index.js",
|
||||
@@ -75,26 +73,25 @@
|
||||
"./eve/data/*": "./data/*",
|
||||
"./discord": {
|
||||
"import": "./dist/discord/index.js",
|
||||
"types": "./src/discord/index*.d.ts",
|
||||
"require": "./src/discord/index.js"
|
||||
"require": "./src/discord/index.js",
|
||||
"types": "./dist/types/discord/index.d.ts"
|
||||
},
|
||||
"./discord/commands": {
|
||||
"types": "./src/discord/commands/index*.d.ts",
|
||||
"require": "./src/discord/commands/index.js",
|
||||
"import": "./dist/discord/commands/index.js"
|
||||
"import": "./dist/discord/commands/index.js",
|
||||
"types": "./dist/types/discord/commands/index.d.ts"
|
||||
},
|
||||
"./discord/components": {
|
||||
"types": "./src/discord/components/index*.d.ts",
|
||||
"types": "./dist/types/discord/components/index.d.ts",
|
||||
"require": "./src/discord/components/index.js",
|
||||
"import": "./dist/discord/components/index.js"
|
||||
},
|
||||
"./discord/pages": {
|
||||
"types": "./src/discord/pages/index*.d.ts",
|
||||
"require": "./src/discord/pages/index.js",
|
||||
"import": "./dist/discord/pages/index.js"
|
||||
"import": "./dist/discord/pages/index.js",
|
||||
"types": "./dist/types/discord/pages/index.d.ts"
|
||||
},
|
||||
"./discord/common": {
|
||||
"types": "./src/discord/common/index*.d.ts",
|
||||
"require": "./src/discord/common/index.js",
|
||||
"import": "./dist/discord/common/index.js"
|
||||
},
|
||||
@@ -144,23 +141,24 @@
|
||||
"prettier-plugin-multiline-arrays": "^4.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@orama/orama": "^3.1.13",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
|
||||
"acorn": "^8.14.0",
|
||||
"acorn-jsx": "^5.3.2",
|
||||
"html-dom-parser": "^5.1.1",
|
||||
"cron-parser": "^5.3.1",
|
||||
"date-fns": "^4.1.0",
|
||||
"domhandler": "^5.0.3",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fp-filters": "^0.5.4",
|
||||
"html-dom-parser": "^5.1.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.7",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"node-cache": "^5.1.2",
|
||||
"stream-chain": "^3.4.0",
|
||||
"stream-json": "^1.9.1",
|
||||
"winston": "^3.17.0",
|
||||
"@orama/orama": "^3.1.13",
|
||||
"@oslojs/encoding": "^1.1.0",
|
||||
"drizzle-orm": "^0.44.5",
|
||||
"fp-filters": "^0.5.4",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"jwk-to-pem": "^2.0.7",
|
||||
"jwt-decode": "^4.0.0"
|
||||
"winston": "^3.17.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import type { Cache } from '@/discord/core/cache.type';
|
||||
import type { KVStore } from '@/discord/core/kv-store.type.ts';
|
||||
import type { Client } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandState } from './command-state';
|
||||
|
||||
export interface PartialContext<T = any> {
|
||||
client: Client;
|
||||
cache: Cache;
|
||||
kv: KVStore;
|
||||
id?: string; // unique id for this command instance
|
||||
state?: CommandState<T>; // state associated with this command instance
|
||||
}
|
||||
|
||||
export type CommandContext<T = any> = Required<PartialContext<T>>;
|
||||
@@ -1,27 +1,10 @@
|
||||
import {
|
||||
AutocompleteInteraction,
|
||||
CommandInteraction,
|
||||
ComponentInteraction,
|
||||
Constants,
|
||||
ModalSubmitInteraction,
|
||||
type ApplicationCommandOptionAutocomplete,
|
||||
type ApplicationCommandOptions,
|
||||
type ApplicationCommandStructure,
|
||||
type ChatInputApplicationCommandStructure,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
import type { CommandContext, PartialContext } from './command-context.type';
|
||||
import { type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import type { ExecutableInteraction } from '../types/interaction.type';
|
||||
import type { ChatCommandDefinition, CommandContext, CommandHandler } from '../types';
|
||||
|
||||
export interface CommandHandler<T extends ApplicationCommandStructure> {
|
||||
definition: T;
|
||||
execute: (interaction: ExecutableInteraction, ctx: CommandContext) => Promise<void>;
|
||||
}
|
||||
|
||||
export type ExecutableInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction | AutocompleteInteraction;
|
||||
|
||||
export type ChatCommandDefinition = Omit<ChatInputApplicationCommandStructure, 'type'>;
|
||||
export function createChatCommand(
|
||||
definition: ChatCommandDefinition,
|
||||
execute: (interaction: CommandInteraction, ctx: CommandContext) => Promise<void>,
|
||||
execute: (interaction: ExecutableInteraction, ctx: CommandContext) => Promise<void>,
|
||||
): CommandHandler<ChatInputApplicationCommandStructure> {
|
||||
const def = definition as ChatInputApplicationCommandStructure;
|
||||
def.type = 1; // CHAT_INPUT
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {
|
||||
Interaction,
|
||||
import { Constants } from '@projectdysnomia/dysnomia';
|
||||
import type {
|
||||
CommandInteraction,
|
||||
Constants,
|
||||
ModalSubmitInteraction,
|
||||
ComponentInteraction,
|
||||
ExecutableInteraction,
|
||||
Interaction,
|
||||
AutocompleteInteraction,
|
||||
ComponentInteraction,
|
||||
ModalSubmitInteraction,
|
||||
PingInteraction,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
} from '../types';
|
||||
|
||||
export function isApplicationCommand(interaction: Interaction): interaction is CommandInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND;
|
||||
@@ -43,3 +43,30 @@ export function getCommandName(interaction: ExecutableInteraction): string | und
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function augmentInteraction(interaction: Interaction): Interaction {
|
||||
interaction.isApplicationCommand = function (): this is CommandInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND;
|
||||
};
|
||||
interaction.isModalSubmit = function (): this is ModalSubmitInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.MODAL_SUBMIT;
|
||||
};
|
||||
interaction.isMessageComponent = function (): this is ComponentInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.MESSAGE_COMPONENT;
|
||||
};
|
||||
interaction.isAutocomplete = function (): this is AutocompleteInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE;
|
||||
};
|
||||
interaction.isPing = function (): this is PingInteraction {
|
||||
return interaction.type === Constants.InteractionTypes.PING;
|
||||
};
|
||||
interaction.isExecutable = function (): this is ExecutableInteraction {
|
||||
return (
|
||||
interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND ||
|
||||
interaction.type === Constants.InteractionTypes.MODAL_SUBMIT ||
|
||||
interaction.type === Constants.InteractionTypes.MESSAGE_COMPONENT ||
|
||||
interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE
|
||||
);
|
||||
};
|
||||
return interaction;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { type InteractionModalContent, type Component } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandContext, PartialContext } from './command-context.type';
|
||||
import { isApplicationCommand, isMessageComponent } from './command-helpers';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
import type { CommandContext, PartialContext, ExecutableInteraction } from '../types';
|
||||
|
||||
export function injectInteraction(interaction: ExecutableInteraction, ctx: PartialContext): [ExecutableInteraction, CommandContext] {
|
||||
// Wrap the interaction methods to inject command tracking ids into all custom_ids for modals and components.
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import { createReactiveState } from '@/util/reactive-state.js';
|
||||
import type { PartialContext } from './command-context.type';
|
||||
import { isApplicationCommand, isAutocomplete } from './command-helpers';
|
||||
import type { ExecutableInteraction } from './command-handler';
|
||||
|
||||
export interface CommandState<T = any> {
|
||||
id: string; // unique id for this command instance
|
||||
name: string; // command name
|
||||
data: T; // internal data storage
|
||||
}
|
||||
import type { CommandState, ExecutableInteraction, PartialContext } from '../types';
|
||||
|
||||
export async function getCommandState<T>(interaction: ExecutableInteraction, ctx: PartialContext): Promise<CommandState<T>> {
|
||||
const id = instanceIdFromInteraction(interaction);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { expect, test, mock, beforeEach, afterEach } from 'bun:test';
|
||||
import { handleCommands } from './handle-commands';
|
||||
import { CommandInteraction, Constants, ModalSubmitInteraction, type ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandHandler } from './command-handler';
|
||||
import { CommandHandler } from '../types';
|
||||
|
||||
let commands: Record<string, CommandHandler<ApplicationCommandStructure>>;
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { type ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { getCommandName, isApplicationCommand, isAutocomplete, isMessageComponent, isModalSubmit } from './command-helpers';
|
||||
import type { PartialContext } from './command-context.type';
|
||||
import type { CommandHandler, ExecutableInteraction } from './command-handler';
|
||||
import { augmentInteraction, getCommandName } from './command-helpers';
|
||||
import { injectInteraction } from './command-injection';
|
||||
import { getCommandState } from './command-state';
|
||||
import { type ExecutableInteraction } from '../types/interaction.type';
|
||||
import type { CommandHandler, PartialContext } from '../types';
|
||||
|
||||
export async function handleCommands(
|
||||
interaction: ExecutableInteraction,
|
||||
@@ -15,7 +15,7 @@ export async function handleCommands(
|
||||
ctx.state.name = getCommandName(interaction);
|
||||
}
|
||||
|
||||
if (isAutocomplete(interaction) && ctx.state.name) {
|
||||
if (interaction.isAutocomplete() && ctx.state.name) {
|
||||
const acCommand = commands[ctx.state.name];
|
||||
return acCommand.execute(interaction, ctx as any);
|
||||
}
|
||||
@@ -36,8 +36,9 @@ export async function handleCommands(
|
||||
}
|
||||
|
||||
export function initializeCommandHandling(commands: Record<string, CommandHandler<ApplicationCommandStructure>>, ctx: PartialContext) {
|
||||
ctx.client.on('interactionCreate', async (interaction) => {
|
||||
if (isApplicationCommand(interaction) || isModalSubmit(interaction) || isMessageComponent(interaction) || isAutocomplete(interaction)) {
|
||||
ctx.client.on('interactionCreate', async (_interaction) => {
|
||||
const interaction = augmentInteraction(_interaction as any);
|
||||
if (interaction.isExecutable()) {
|
||||
handleCommands(interaction, commands, ctx);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Glob } from 'bun';
|
||||
import { join } from 'node:path';
|
||||
import type { CommandHandler } from './command-handler';
|
||||
import type { ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import type { CommandHandler } from '../types';
|
||||
|
||||
export async function importCommands(
|
||||
pattern: string = '**/*.command.{js,ts}',
|
||||
pattern: string = '**/*.command.{js,ts,jsx,tsx}',
|
||||
baseDir: string = join(process.cwd(), 'src'),
|
||||
commandRegistry: Record<string, CommandHandler<ApplicationCommandStructure>> = {},
|
||||
): Promise<Record<string, CommandHandler<ApplicationCommandStructure>>> {
|
||||
|
||||
@@ -3,6 +3,5 @@ export * from './import-commands';
|
||||
export * from './handle-commands';
|
||||
export * from './command-helpers';
|
||||
export * from './register-commands';
|
||||
export * from './command-context.type';
|
||||
export * from './command-state';
|
||||
export * from './option-builders';
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
export * from './text';
|
||||
@@ -21,10 +21,12 @@ import {
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
type ModalSubmitInteractionData,
|
||||
type FileUploadComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
|
||||
export const createActionRow = (...components: ActionRowItem[]): ActionRow => ({
|
||||
export const actionRow = (...components: ActionRowItem[]): ActionRow => ({
|
||||
type: Constants.ComponentTypes.ACTION_ROW,
|
||||
components,
|
||||
});
|
||||
@@ -42,7 +44,7 @@ export interface ButtonOptions {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createButton = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
|
||||
export const button = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
|
||||
label,
|
||||
@@ -55,7 +57,7 @@ export interface URLButtonOptions {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createURLButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
|
||||
export const urlButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: Constants.ButtonStyles.LINK,
|
||||
label,
|
||||
@@ -68,7 +70,7 @@ export interface PremiumButtonOptions {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export const createPremiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
|
||||
export const premiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
|
||||
type: Constants.ComponentTypes.BUTTON,
|
||||
style: Constants.ButtonStyles.PREMIUM,
|
||||
sku_id,
|
||||
@@ -87,30 +89,30 @@ export interface StringSelectOption {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: {
|
||||
name?: string;
|
||||
id?: string;
|
||||
animated?: boolean;
|
||||
};
|
||||
emoji?: PartialEmoji;
|
||||
default?: boolean;
|
||||
}
|
||||
|
||||
export const createStringSelect = (
|
||||
custom_id: string,
|
||||
selectOpts: StringSelectOpts,
|
||||
...options: StringSelectOption[]
|
||||
): StringSelectMenu => ({
|
||||
export interface StringSelectOptions {
|
||||
placeholder?: string;
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export const stringSelect = (custom_id: string, selectOpts: StringSelectOpts, ...options: StringSelectOption[]): StringSelectMenu => ({
|
||||
type: Constants.ComponentTypes.STRING_SELECT,
|
||||
custom_id,
|
||||
options,
|
||||
placeholder: selectOpts.placeholder ?? '',
|
||||
placeholder: selectOpts.placeholder,
|
||||
min_values: selectOpts.min_values ?? 1,
|
||||
max_values: selectOpts.max_values ?? 1,
|
||||
disabled: selectOpts.disabled ?? false,
|
||||
required: selectOpts.required ?? false, // Note: not actually a property of StringSelectMenu, but useful for modals
|
||||
});
|
||||
|
||||
export interface TextInputOptions {
|
||||
export interface InputOptions {
|
||||
isParagraph?: boolean;
|
||||
label?: string;
|
||||
min_length?: number;
|
||||
@@ -120,16 +122,16 @@ export interface TextInputOptions {
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export const createTextInput = (custom_id: string, options?: TextInputOptions): TextInput => ({
|
||||
export const input = (custom_id: string, options?: InputOptions): TextInput => ({
|
||||
type: Constants.ComponentTypes.TEXT_INPUT,
|
||||
custom_id,
|
||||
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
|
||||
label: options?.label ?? '',
|
||||
label: options?.label,
|
||||
min_length: options?.min_length ?? 0,
|
||||
max_length: options?.max_length ?? 4000,
|
||||
required: options?.required ?? false,
|
||||
value: options?.value ?? '',
|
||||
placeholder: options?.placeholder ?? '',
|
||||
value: options?.value,
|
||||
placeholder: options?.placeholder,
|
||||
});
|
||||
|
||||
export interface UserSelectOptions {
|
||||
@@ -137,10 +139,11 @@ export interface UserSelectOptions {
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
default_values?: Array<{ id: string; type: 'user' }>;
|
||||
}
|
||||
|
||||
export const createUserSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
|
||||
export const userSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
|
||||
type: Constants.ComponentTypes.USER_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
@@ -155,10 +158,11 @@ export interface RoleSelectOptions {
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
default_values?: Array<{ id: string; type: 'role' }>;
|
||||
}
|
||||
|
||||
export const createRoleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
|
||||
export const roleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
|
||||
type: Constants.ComponentTypes.ROLE_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
@@ -173,10 +177,11 @@ export interface MentionableSelectOptions {
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
|
||||
}
|
||||
|
||||
export const createMentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
|
||||
export const mentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
|
||||
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
|
||||
custom_id,
|
||||
placeholder: options?.placeholder ?? '',
|
||||
@@ -192,10 +197,11 @@ export interface ChannelSelectOptions {
|
||||
min_values?: number;
|
||||
max_values?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
default_values?: Array<{ id: string; type: 'channel' }>;
|
||||
}
|
||||
|
||||
export const createChannelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
|
||||
export const channelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
|
||||
type: Constants.ComponentTypes.CHANNEL_SELECT,
|
||||
custom_id,
|
||||
channel_types: options?.channel_types ?? [],
|
||||
@@ -211,7 +217,7 @@ export interface SectionOptions {
|
||||
accessory: Button | ThumbnailComponent;
|
||||
}
|
||||
|
||||
export const createSection = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
|
||||
export const section = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
|
||||
type: Constants.ComponentTypes.SECTION,
|
||||
accessory,
|
||||
components,
|
||||
@@ -222,7 +228,7 @@ export const createSection = (accessory: Button | ThumbnailComponent, ...compone
|
||||
* @param content The text content to display.
|
||||
* @returns The created text display component.
|
||||
*/
|
||||
export const createTextDisplay = (content: string) => ({
|
||||
export const text = (content: string) => ({
|
||||
type: Constants.ComponentTypes.TEXT_DISPLAY,
|
||||
content,
|
||||
});
|
||||
@@ -235,7 +241,7 @@ export interface ThumbnailOptions {
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createThumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
|
||||
export const thumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
|
||||
type: Constants.ComponentTypes.THUMBNAIL,
|
||||
media: {
|
||||
url,
|
||||
@@ -250,7 +256,7 @@ export interface MediaItem {
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createMediaGallery = (...items: MediaItem[]): MediaGalleryComponent => ({
|
||||
export const gallery = (...items: MediaItem[]): MediaGalleryComponent => ({
|
||||
type: Constants.ComponentTypes.MEDIA_GALLERY,
|
||||
items: items.map((item) => ({
|
||||
type: Constants.ComponentTypes.FILE,
|
||||
@@ -265,7 +271,7 @@ export interface FileOptions {
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
export const createFile = (url: string, spoiler?: boolean): FileComponent => ({
|
||||
export const file = (url: string, spoiler?: boolean): FileComponent => ({
|
||||
type: Constants.ComponentTypes.FILE,
|
||||
file: {
|
||||
url,
|
||||
@@ -282,7 +288,7 @@ export interface SeparatorOptions {
|
||||
divider?: boolean;
|
||||
spacing?: Padding;
|
||||
}
|
||||
export const createSeparator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
|
||||
export const separator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
|
||||
type: Constants.ComponentTypes.SEPARATOR,
|
||||
divider,
|
||||
spacing: spacing ?? Padding.SMALL,
|
||||
@@ -301,14 +307,27 @@ export type ContainerItems =
|
||||
| SeparatorComponent
|
||||
| FileComponent;
|
||||
|
||||
export const createContainer = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
|
||||
export const container = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
|
||||
type: Constants.ComponentTypes.CONTAINER,
|
||||
...options,
|
||||
components,
|
||||
});
|
||||
|
||||
export const createModalLabel = (label: string, component: TextInput | StringSelectMenu): LabelComponent => ({
|
||||
// Modals
|
||||
|
||||
export const label = (label: string, component: LabelComponent['component']): LabelComponent => ({
|
||||
type: Constants.ComponentTypes.LABEL,
|
||||
label,
|
||||
component,
|
||||
});
|
||||
|
||||
export const modal = (
|
||||
options: { custom_id?: string; title?: string },
|
||||
...components: Array<LabelComponent | ActionRow | TextDisplayComponent>
|
||||
): ModalSubmitInteractionData =>
|
||||
({
|
||||
type: 9 as any, // Modal type
|
||||
custom_id: options.custom_id ?? '',
|
||||
title: options.title ?? '',
|
||||
components,
|
||||
} as any);
|
||||
|
||||
@@ -2,7 +2,7 @@ import {
|
||||
Constants,
|
||||
type ComponentBase,
|
||||
type ModalSubmitInteractionDataLabelComponent,
|
||||
type ModalSubmitInteractionDataStringSelectComponent,
|
||||
type ModalSubmitInteractionDataSelectComponent,
|
||||
type ModalSubmitInteractionDataTextInputComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
@@ -14,7 +14,7 @@ export function isModalTextInput(component: ComponentBase): component is ModalSu
|
||||
return component.type === Constants.ComponentTypes.TEXT_INPUT;
|
||||
}
|
||||
|
||||
export function isModalSelect(component: ComponentBase): component is ModalSubmitInteractionDataStringSelectComponent {
|
||||
export function isModalSelect(component: ComponentBase): component is ModalSubmitInteractionDataSelectComponent {
|
||||
return component.type === Constants.ComponentTypes.STRING_SELECT;
|
||||
}
|
||||
|
||||
|
||||
2
packages/lib/src/discord/constants/index.ts
Normal file
2
packages/lib/src/discord/constants/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './text';
|
||||
export * from './locale';
|
||||
34
packages/lib/src/discord/constants/locale.ts
Normal file
34
packages/lib/src/discord/constants/locale.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
export enum Locale {
|
||||
EN_US = 'en-US',
|
||||
EN_GB = 'en-GB',
|
||||
DE = 'de',
|
||||
ES_ES = 'es-ES',
|
||||
FR = 'fr',
|
||||
JA = 'ja',
|
||||
KO = 'ko',
|
||||
RU = 'ru',
|
||||
ZH_CN = 'zh-CN',
|
||||
ID = 'id',
|
||||
DA = 'da',
|
||||
ES_419 = 'es-419',
|
||||
HR = 'hr',
|
||||
IT = 'it',
|
||||
LT = 'lt',
|
||||
HU = 'hu',
|
||||
NL = 'nl',
|
||||
NO = 'no',
|
||||
PL = 'pl',
|
||||
PT_BR = 'pt-BR',
|
||||
RO = 'ro',
|
||||
FI = 'fi',
|
||||
SV_SE = 'sv-SE',
|
||||
VI = 'vi',
|
||||
TR = 'tr',
|
||||
CS = 'cs',
|
||||
EL = 'el',
|
||||
BG = 'bg',
|
||||
UK = 'uk',
|
||||
HI = 'hi',
|
||||
TH = 'th',
|
||||
ZH_TW = 'zh-TW',
|
||||
}
|
||||
@@ -15,10 +15,10 @@ export interface DiscordBotOptions {
|
||||
onReady?: () => void;
|
||||
}
|
||||
|
||||
export function startDiscordBot({
|
||||
export function startBot({
|
||||
token = process.env.DISCORD_BOT_TOKEN || '',
|
||||
intents = [],
|
||||
commandPattern = '**/*.command.{js,ts}',
|
||||
commandPattern = '**/*.command.{js,ts,jsx,tsx}',
|
||||
commandBaseDir = 'src',
|
||||
keyStore = asyncKV,
|
||||
cache = kv,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
export * from './locales';
|
||||
export * from './constants';
|
||||
export * from './commands';
|
||||
export * from './core';
|
||||
export * from './jsx';
|
||||
export * from './components';
|
||||
export * from './pages';
|
||||
export * from './types';
|
||||
|
||||
@@ -57,7 +57,7 @@ const JSD_INTERPOLATION = /\{(.+)\}/gs;
|
||||
const JSD_START_EXP_INTERPOLATION = /\{(.+)\(/gs;
|
||||
const JSD_END_EXP_INTERPOLATION = /\)(.+)\}/gs;
|
||||
|
||||
function parseText(text: string, state: state = {}, parent: Text = {}): string {
|
||||
function parseText(text: string, state: state = {}, parent: Text = {} as any): string {
|
||||
let interpolations = text.match(JSD_INTERPOLATION);
|
||||
if (!interpolations) {
|
||||
if (text.match(JSD_START_EXP_INTERPOLATION)) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createActionRow } from '@/discord/components';
|
||||
import { actionRow } from '@/discord/components';
|
||||
import type { ActionRowElement } from './element.types';
|
||||
|
||||
export function ActionRow(props: { children: any | any[] }) {
|
||||
return createActionRow(...(Array.isArray(props.children) ? props.children : [props.children]));
|
||||
export function ActionRow(props: { children: ActionRowElement['children'] }) {
|
||||
return actionRow(...(Array.isArray(props.children) ? props.children : [props.children]));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { createButton, type ButtonStyle } from '@/discord/components';
|
||||
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
|
||||
import { button, premiumButton, urlButton } from '@/discord/components';
|
||||
import type { ButtonElement, PremiumButtonElement, URLButtonElement } from './element.types';
|
||||
|
||||
export function Button(props: { label: string; customId: string; style: ButtonStyle; emoji?: PartialEmoji; disabled?: boolean }) {
|
||||
return createButton(props.label, props.customId, { style: props.style, emoji: props.emoji, disabled: props.disabled });
|
||||
export function Button(props: ButtonElement['props']) {
|
||||
return button(props.label, props.customId, { style: props.style, emoji: props.emoji, disabled: props.disabled });
|
||||
}
|
||||
|
||||
export function URLButton(props: URLButtonElement['props']) {
|
||||
return urlButton(props.label, props.url, { emoji: props.emoji, disabled: props.disabled });
|
||||
}
|
||||
|
||||
export function PremiumButton(props: PremiumButtonElement['props']) {
|
||||
return premiumButton(props.skuId, { emoji: props.emoji, disabled: props.disabled });
|
||||
}
|
||||
|
||||
14
packages/lib/src/discord/jsx/components/channel-select.ts
Normal file
14
packages/lib/src/discord/jsx/components/channel-select.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { channelSelect } from '@/discord/components';
|
||||
import type { ChannelSelectElement } from './element.types';
|
||||
|
||||
export function ChannelSelect(props: ChannelSelectElement['props']) {
|
||||
return channelSelect(props.customId, {
|
||||
channel_types: props.channelTypes,
|
||||
placeholder: props.placeholder,
|
||||
min_values: props.minValues,
|
||||
max_values: props.maxValues,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
default_values: props.defaultValues,
|
||||
});
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { createContainer } from '@/discord/components';
|
||||
import { container } from '@/discord/components';
|
||||
import type { ContainerElement } from './element.types';
|
||||
|
||||
export function Container(props: { accent?: number; spoiler?: boolean; children: any | any[] }) {
|
||||
return createContainer(
|
||||
export function Container(props: ContainerElement['props'] & { children: ContainerElement['children'] }) {
|
||||
return container(
|
||||
{ accent_color: props.accent, spoiler: props.spoiler },
|
||||
...(Array.isArray(props.children) ? props.children : [props.children]),
|
||||
);
|
||||
|
||||
234
packages/lib/src/discord/jsx/components/element.types.ts
Normal file
234
packages/lib/src/discord/jsx/components/element.types.ts
Normal file
@@ -0,0 +1,234 @@
|
||||
import type { ActionRowItem, ContainerItems, MediaItem, Padding } from '@/discord/components';
|
||||
import type {
|
||||
ActionRow,
|
||||
Button,
|
||||
FileUploadComponent,
|
||||
GuildChannelTypes,
|
||||
LabelComponent,
|
||||
PartialEmoji,
|
||||
SelectMenu,
|
||||
TextDisplayComponent,
|
||||
TextInput,
|
||||
ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
export interface OptionElement {
|
||||
type: 'option';
|
||||
props: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: PartialEmoji;
|
||||
default?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface StringSelectElement {
|
||||
type: 'stringSelect';
|
||||
props: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
};
|
||||
children: OptionElement['props'] | OptionElement['props'][];
|
||||
}
|
||||
|
||||
export interface LabelElement {
|
||||
type: 'label';
|
||||
props: {
|
||||
label: string;
|
||||
};
|
||||
children: SelectMenu | TextInput | FileUploadComponent;
|
||||
}
|
||||
|
||||
export type ModalChildren = ActionRow | LabelComponent | TextDisplayComponent;
|
||||
|
||||
export interface ModalElement {
|
||||
type: 'modal';
|
||||
props: {
|
||||
customId?: string;
|
||||
title?: string;
|
||||
};
|
||||
children: ModalChildren | ModalChildren[];
|
||||
}
|
||||
|
||||
export interface ActionRowElement {
|
||||
type: 'actionRow';
|
||||
props: {};
|
||||
children: ActionRowItem | ActionRowItem[];
|
||||
}
|
||||
|
||||
export interface ButtonElement {
|
||||
type: 'button';
|
||||
props: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface URLButtonElement {
|
||||
type: 'urlButton';
|
||||
props: {
|
||||
label: string;
|
||||
url: string;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface PremiumButtonElement {
|
||||
type: 'premiumButton';
|
||||
props: {
|
||||
skuId: string;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface TextInputElement {
|
||||
type: 'textInput';
|
||||
props: {
|
||||
customId: string;
|
||||
label?: string; // can not be set within a label on a modal
|
||||
isParagraph?: boolean;
|
||||
minLength?: number;
|
||||
maxLength?: number;
|
||||
required?: boolean;
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface TextDisplayElement {
|
||||
type: 'textDisplay';
|
||||
props: {};
|
||||
children: string | string[];
|
||||
}
|
||||
|
||||
export interface ContainerElement {
|
||||
type: 'container';
|
||||
props: {
|
||||
color?: string;
|
||||
accent?: number;
|
||||
spoiler?: boolean;
|
||||
};
|
||||
children: ContainerItems | ContainerItems[];
|
||||
}
|
||||
|
||||
export interface UserSelectElement {
|
||||
type: 'userSelect';
|
||||
props: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
defaultValues?: { id: string; type: 'user' }[];
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface RoleSelectElement {
|
||||
type: 'roleSelect';
|
||||
props: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
defaultValues?: { id: string; type: 'role' }[];
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface MentionableSelectElement {
|
||||
type: 'mentionableSelect';
|
||||
props: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
defaultValues?: { id: string; type: 'user' | 'role' }[];
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface ChannelSelectElement {
|
||||
type: 'channelSelect';
|
||||
props: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
disabled?: boolean;
|
||||
required?: boolean; // if on a modal
|
||||
channelTypes?: GuildChannelTypes[];
|
||||
defaultValues?: { id: string; type: 'channel' }[];
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface SectionElement {
|
||||
type: 'section';
|
||||
props: {};
|
||||
children: (Button | ThumbnailComponent) | [Button | ThumbnailComponent, ...Array<TextDisplayComponent>];
|
||||
}
|
||||
|
||||
export interface ThumbnailElement {
|
||||
type: 'thumbnail';
|
||||
props: {
|
||||
url: string;
|
||||
description?: string;
|
||||
spoiler?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface GalleryElement {
|
||||
type: 'gallery';
|
||||
props: {};
|
||||
children: MediaElement['props'] | MediaElement['props'][];
|
||||
}
|
||||
|
||||
export interface MediaElement {
|
||||
type: 'media';
|
||||
props: {
|
||||
url: string;
|
||||
description?: string;
|
||||
spoiler?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface FileElement {
|
||||
type: 'file';
|
||||
props: {
|
||||
url: string;
|
||||
spoiler?: boolean;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
|
||||
export interface SeparatorElement {
|
||||
type: 'separator';
|
||||
props: {
|
||||
divider?: boolean;
|
||||
spacing?: Padding;
|
||||
};
|
||||
children: never;
|
||||
}
|
||||
6
packages/lib/src/discord/jsx/components/file.ts
Normal file
6
packages/lib/src/discord/jsx/components/file.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { file } from '@/discord/components';
|
||||
import type { FileElement } from './element.types';
|
||||
|
||||
export function File(props: FileElement['props']) {
|
||||
return file(props.url, props.spoiler);
|
||||
}
|
||||
7
packages/lib/src/discord/jsx/components/gallery.ts
Normal file
7
packages/lib/src/discord/jsx/components/gallery.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { gallery } from '@/discord/components';
|
||||
import type { GalleryElement } from './element.types';
|
||||
|
||||
export function Gallery(props: GalleryElement['props'] & { children: GalleryElement['children'] }) {
|
||||
const children = Array.isArray(props.children) ? props.children : [props.children];
|
||||
return gallery(...children);
|
||||
}
|
||||
@@ -1,4 +1,19 @@
|
||||
export * from './action-row';
|
||||
export * from './button';
|
||||
export * from './channel-select';
|
||||
export * from './container';
|
||||
export * from './file';
|
||||
export * from './gallery';
|
||||
export * from './label';
|
||||
export * from './media';
|
||||
export * from './mentionable-select';
|
||||
export * from './modal';
|
||||
export * from './option';
|
||||
export * from './role-select';
|
||||
export * from './section';
|
||||
export * from './separator';
|
||||
export * from './string-select';
|
||||
export * from './text-display';
|
||||
export * from './text-input';
|
||||
export * from './thumbnail';
|
||||
export * from './user-select';
|
||||
|
||||
6
packages/lib/src/discord/jsx/components/label.ts
Normal file
6
packages/lib/src/discord/jsx/components/label.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { label } from '@/discord/components';
|
||||
import type { LabelElement } from './element.types';
|
||||
|
||||
export function Label(props: LabelElement['props'] & { children: LabelElement['children'] }) {
|
||||
return label(props.label, props.children);
|
||||
}
|
||||
5
packages/lib/src/discord/jsx/components/media.ts
Normal file
5
packages/lib/src/discord/jsx/components/media.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { MediaElement } from './element.types';
|
||||
|
||||
export function Media(props: MediaElement['props']) {
|
||||
return props;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { mentionableSelect } from '@/discord/components';
|
||||
import type { MentionableSelectElement } from './element.types';
|
||||
|
||||
export function MentionableSelect(props: MentionableSelectElement['props']) {
|
||||
return mentionableSelect(props.customId, {
|
||||
placeholder: props.placeholder,
|
||||
min_values: props.minValues,
|
||||
max_values: props.maxValues,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
default_values: props.defaultValues,
|
||||
});
|
||||
}
|
||||
6
packages/lib/src/discord/jsx/components/modal.ts
Normal file
6
packages/lib/src/discord/jsx/components/modal.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { modal } from '@/discord/components';
|
||||
import type { ModalElement } from './element.types';
|
||||
|
||||
export function Modal(props: ModalElement['props'] & { children: ModalElement['children'] }) {
|
||||
return modal({ custom_id: props.customId, title: props.title }, ...(Array.isArray(props.children) ? props.children : [props.children]));
|
||||
}
|
||||
5
packages/lib/src/discord/jsx/components/option.ts
Normal file
5
packages/lib/src/discord/jsx/components/option.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { OptionElement } from './element.types';
|
||||
|
||||
export function Option(props: OptionElement['props']) {
|
||||
return props;
|
||||
}
|
||||
13
packages/lib/src/discord/jsx/components/role-select.ts
Normal file
13
packages/lib/src/discord/jsx/components/role-select.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { roleSelect } from '@/discord/components';
|
||||
import type { RoleSelectElement } from './element.types';
|
||||
|
||||
export function RoleSelect(props: RoleSelectElement['props']) {
|
||||
return roleSelect(props.customId, {
|
||||
placeholder: props.placeholder,
|
||||
min_values: props.minValues,
|
||||
max_values: props.maxValues,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
default_values: props.defaultValues,
|
||||
});
|
||||
}
|
||||
7
packages/lib/src/discord/jsx/components/section.ts
Normal file
7
packages/lib/src/discord/jsx/components/section.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { section } from '@/discord/components';
|
||||
import type { SectionElement } from './element.types';
|
||||
|
||||
export function Section(props: SectionElement['props'] & { children: SectionElement['children'] }) {
|
||||
const children = Array.isArray(props.children) ? props.children : [props.children];
|
||||
return section(children[0], ...(children.slice(1) as any[]));
|
||||
}
|
||||
6
packages/lib/src/discord/jsx/components/separator.ts
Normal file
6
packages/lib/src/discord/jsx/components/separator.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { separator } from '@/discord/components';
|
||||
import type { SeparatorElement } from './element.types';
|
||||
|
||||
export function Separator(props: SeparatorElement['props']) {
|
||||
return separator(props.spacing, props.divider);
|
||||
}
|
||||
14
packages/lib/src/discord/jsx/components/string-select.ts
Normal file
14
packages/lib/src/discord/jsx/components/string-select.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { stringSelect } from '@/discord/components';
|
||||
import type { StringSelectElement } from './element.types';
|
||||
|
||||
export function StringSelect(props: StringSelectElement['props'] & { children: StringSelectElement['children'] }) {
|
||||
return stringSelect(
|
||||
props.customId,
|
||||
{
|
||||
placeholder: props.placeholder,
|
||||
min_values: props.minValues,
|
||||
max_values: props.maxValues,
|
||||
},
|
||||
...(Array.isArray(props.children) ? props.children : [props.children]),
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import { createTextDisplay } from '@/discord/components/builders';
|
||||
import { text } from '@/discord/components/builders';
|
||||
import type { TextDisplayElement } from './element.types';
|
||||
|
||||
export function TextDisplay(props: { content: string }) {
|
||||
return createTextDisplay(props.content);
|
||||
export function TextDisplay(props: TextDisplayElement['props'] & { children: TextDisplayElement['children'] }) {
|
||||
const children = Array.isArray(props.children) ? props.children.join('') : props.children;
|
||||
return text(children);
|
||||
}
|
||||
|
||||
14
packages/lib/src/discord/jsx/components/text-input.ts
Normal file
14
packages/lib/src/discord/jsx/components/text-input.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { input } from '@/discord/components';
|
||||
import type { TextInputElement } from './element.types';
|
||||
|
||||
export function TextInput(props: TextInputElement['props']) {
|
||||
return input(props.customId, {
|
||||
isParagraph: props.isParagraph,
|
||||
label: props.label,
|
||||
min_length: props.minLength,
|
||||
max_length: props.maxLength,
|
||||
required: props.required,
|
||||
value: props.value,
|
||||
placeholder: props.placeholder,
|
||||
});
|
||||
}
|
||||
6
packages/lib/src/discord/jsx/components/thumbnail.ts
Normal file
6
packages/lib/src/discord/jsx/components/thumbnail.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { thumbnail } from '@/discord/components';
|
||||
import type { ThumbnailElement } from './element.types';
|
||||
|
||||
export function Thumbnail(props: ThumbnailElement['props']) {
|
||||
return thumbnail(props.url, props.description, props.spoiler);
|
||||
}
|
||||
13
packages/lib/src/discord/jsx/components/user-select.ts
Normal file
13
packages/lib/src/discord/jsx/components/user-select.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { userSelect } from '@/discord/components';
|
||||
import type { UserSelectElement } from './element.types';
|
||||
|
||||
export function UserSelect(props: UserSelectElement['props']) {
|
||||
return userSelect(props.customId, {
|
||||
placeholder: props.placeholder,
|
||||
min_values: props.minValues,
|
||||
max_values: props.maxValues,
|
||||
disabled: props.disabled,
|
||||
required: props.required,
|
||||
default_values: props.defaultValues,
|
||||
});
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export { jsxDEV } from './runtime';
|
||||
export type { JSX } from './jsx';
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
export { jsx } from './runtime';
|
||||
export type { JSX } from './jsx';
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
@@ -19,7 +18,30 @@ import {
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
type ModalSubmitInteractionData,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
import type {
|
||||
ButtonElement,
|
||||
ChannelSelectElement,
|
||||
ContainerElement,
|
||||
FileElement,
|
||||
GalleryElement,
|
||||
LabelElement,
|
||||
MediaElement,
|
||||
MentionableSelectElement,
|
||||
ModalElement,
|
||||
OptionElement,
|
||||
PremiumButtonElement,
|
||||
RoleSelectElement,
|
||||
SectionElement,
|
||||
SeparatorElement,
|
||||
StringSelectElement,
|
||||
TextDisplayElement,
|
||||
TextInputElement,
|
||||
ThumbnailElement,
|
||||
URLButtonElement,
|
||||
UserSelectElement,
|
||||
} from './components/element.types';
|
||||
|
||||
export type Component =
|
||||
| ActionRow
|
||||
@@ -40,30 +62,51 @@ export type Component =
|
||||
| InteractionButton
|
||||
| URLButton
|
||||
| PremiumButton
|
||||
| ThumbnailComponent;
|
||||
| ThumbnailComponent
|
||||
| ModalSubmitInteractionData;
|
||||
|
||||
export type Element = Component | Promise<Component>;
|
||||
export type StarKittenElement = Component | Promise<Component>;
|
||||
|
||||
export interface ElementClass {
|
||||
export interface StarKittenElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
export interface ElementAttributesProperty {
|
||||
export interface StarKittenElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
export interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
// [elemName: string]: any;
|
||||
// Known elements (forcing re-parse)
|
||||
actionRow: { children: any | any[] };
|
||||
button: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
container: { color?: string; accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
textDisplay: { content: string };
|
||||
export interface StarKittenElementChildrenAttribute {
|
||||
children: {};
|
||||
}
|
||||
|
||||
export interface StarKittenIntrinsicElements {
|
||||
actionRow: { children: StarKittenElement | StarKittenElement[] };
|
||||
button: ButtonElement['props'];
|
||||
urlButton: URLButtonElement['props'];
|
||||
premiumButton: PremiumButtonElement['props'];
|
||||
modal: ModalElement['props'] & { children: StarKittenElement | StarKittenElement[] };
|
||||
label: LabelElement['props'] & { children: StarKittenElement | StarKittenElement[] };
|
||||
stringSelect: StringSelectElement['props'] & { children: StringSelectElement['children'] };
|
||||
option: OptionElement['props'];
|
||||
textInput: TextInputElement['props'];
|
||||
textDisplay: TextDisplayElement['props'];
|
||||
container: ContainerElement['props'] & { children: StarKittenElement | StarKittenElement[] };
|
||||
userSelect: UserSelectElement['props'];
|
||||
roleSelect: RoleSelectElement['props'];
|
||||
mentionableSelect: MentionableSelectElement['props'];
|
||||
channelSelect: ChannelSelectElement['props'];
|
||||
section: SectionElement['props'] & { children: StarKittenElement | StarKittenElement[] };
|
||||
thumbnail: ThumbnailElement['props'];
|
||||
gallery: GalleryElement['props'] & { children: StarKittenElement | StarKittenElement[] };
|
||||
media: MediaElement['props'];
|
||||
file: FileElement['props'];
|
||||
separator: SeparatorElement['props'];
|
||||
}
|
||||
|
||||
export declare namespace JSX {
|
||||
export type Element = StarKittenElement;
|
||||
export interface ElementClass extends StarKittenElementClass {}
|
||||
export interface ElementAttributesProperty extends StarKittenElementAttributesProperty {}
|
||||
export interface ElementChildrenAttribute extends StarKittenElementChildrenAttribute {}
|
||||
export interface IntrinsicElements extends StarKittenIntrinsicElements {}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,30 @@
|
||||
import { ActionRow } from './components/action-row';
|
||||
import { Button } from './components/button';
|
||||
import { Container } from './components/container';
|
||||
import { TextDisplay } from './components/text-display';
|
||||
import * as components from './components';
|
||||
|
||||
const intrinsicComponentMap: Record<string, (props: any) => any> = {
|
||||
actionRow: ActionRow,
|
||||
button: Button,
|
||||
container: Container,
|
||||
textDisplay: TextDisplay,
|
||||
actionRow: components.ActionRow,
|
||||
button: components.Button,
|
||||
container: components.Container,
|
||||
file: components.File,
|
||||
gallery: components.Gallery,
|
||||
label: components.Label,
|
||||
media: components.Media,
|
||||
mentionableSelect: components.MentionableSelect,
|
||||
modal: components.Modal,
|
||||
option: components.Option,
|
||||
premiumButton: components.PremiumButton,
|
||||
roleSelect: components.RoleSelect,
|
||||
section: components.Section,
|
||||
separator: components.Separator,
|
||||
stringSelect: components.StringSelect,
|
||||
textDisplay: components.TextDisplay,
|
||||
textInput: components.TextInput,
|
||||
thumbnail: components.Thumbnail,
|
||||
urlButton: components.URLButton,
|
||||
userSelect: components.UserSelect,
|
||||
};
|
||||
|
||||
export function jsx(type: any, props: Record<string, any>) {
|
||||
console.log('JSX', type, props);
|
||||
// console.log('JSX', type, props);
|
||||
if (typeof type === 'function') {
|
||||
return type(props);
|
||||
}
|
||||
@@ -34,7 +47,7 @@ export function jsxDEV(
|
||||
source: any,
|
||||
self: any,
|
||||
) {
|
||||
console.log('JSX DEV', type, props);
|
||||
// console.log('JSX DEV', type, props);
|
||||
if (typeof type === 'function') {
|
||||
return type(props);
|
||||
}
|
||||
|
||||
24
packages/lib/src/discord/jsx/types.d.ts
vendored
24
packages/lib/src/discord/jsx/types.d.ts
vendored
@@ -1,8 +1,22 @@
|
||||
import type { Component, IntrinsicElements as StarKittenIntrinsicElements } from './jsx';
|
||||
import type { Component, StarKittenIntrinsicElements } from './jsx';
|
||||
import type { LabelElement } from './components/label';
|
||||
import type { StringSelectElement } from './components/string-select';
|
||||
import type { PartialEmoji, StringSelectMenu, TextInput } from '@projectdysnomia/dysnomia';
|
||||
|
||||
declare global {
|
||||
namespace JSX {
|
||||
type Element = Component;
|
||||
interface IntrinsicElements extends StarKittenIntrinsicElements {}
|
||||
export declare namespace JSX {
|
||||
// type Element = Component;
|
||||
|
||||
interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
interface ElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
interface ElementChildrenAttribute {
|
||||
children: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements extends StarKittenIntrinsicElements {}
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ function createPageContext<T>(interaction: PagesInteraction, options: PagesOptio
|
||||
custom_id: 'custom_id' in interaction.data ? interaction.data.custom_id : options.initialPage ?? 'root',
|
||||
goToPage: (pageKey: string) => {
|
||||
const page = options.pages[pageKey];
|
||||
this.state.currentPage = pageKey;
|
||||
state.currentPage = pageKey;
|
||||
if (!page) {
|
||||
throw new Error(`Page with key "${pageKey}" not found`);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
|
||||
import { createActionRow, createButton, createMediaGallery, type ButtonOptions, type ContainerItems } from '@/discord/components';
|
||||
import { actionRow, button, gallery, type ButtonOptions, type ContainerItems } from '@/discord/components';
|
||||
import type { PageContext } from './pages';
|
||||
|
||||
export function getSubrouteKey(prefix: string, subroutes: string[]) {
|
||||
@@ -34,7 +34,7 @@ export function renderSubrouteButtons(
|
||||
.map(({ label, value, emoji }) => {
|
||||
const routes = [...subRoutes];
|
||||
routes[subrouteIndex] = currentSubroute == value ? '_' : value;
|
||||
return createButton(label, getSubrouteKey(prefix, routes), {
|
||||
return button(label, getSubrouteKey(prefix, routes), {
|
||||
...options,
|
||||
disabled: value === currentSubroute,
|
||||
emoji,
|
||||
@@ -76,10 +76,10 @@ export function renderSubroutes<T, CType = ContainerItems>(
|
||||
const current = opts[sri] || opts[0];
|
||||
const components = [];
|
||||
|
||||
const actionRow = createActionRow(...renderSubrouteButtons(current.value, currentSubroutes, index, prefix, opts, btnOptions));
|
||||
const actionRow = actionRow(...renderSubrouteButtons(current.value, currentSubroutes, index, prefix, opts, btnOptions));
|
||||
|
||||
if (current.banner) {
|
||||
components.push(createMediaGallery({ url: current.banner }));
|
||||
components.push(gallery({ url: current.banner }));
|
||||
}
|
||||
|
||||
if (!current.actionRowPosition || current.actionRowPosition === 'top') {
|
||||
|
||||
28
packages/lib/src/discord/types/command.type.ts
Normal file
28
packages/lib/src/discord/types/command.type.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import type { ChatInputApplicationCommandStructure, ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import type { ExecutableInteraction } from './interaction.type';
|
||||
import type { Cache } from '@/discord/core/cache.type';
|
||||
import type { KVStore } from '@/discord/core/kv-store.type.ts';
|
||||
import type { Client } from '@projectdysnomia/dysnomia';
|
||||
|
||||
export interface CommandState<T = any> {
|
||||
id: string; // unique id for this command instance
|
||||
name: string; // command name
|
||||
data: T; // internal data storage
|
||||
}
|
||||
|
||||
export interface PartialContext<T = any> {
|
||||
client: Client;
|
||||
cache: Cache;
|
||||
kv: KVStore;
|
||||
id?: string; // unique id for this command instance
|
||||
state?: CommandState<T>; // state associated with this command instance
|
||||
}
|
||||
|
||||
export type CommandContext<T = any> = Required<PartialContext<T>>;
|
||||
|
||||
export type ChatCommandDefinition = Omit<ChatInputApplicationCommandStructure, 'type'>;
|
||||
|
||||
export interface CommandHandler<T extends ApplicationCommandStructure> {
|
||||
definition: T;
|
||||
execute: (interaction: ExecutableInteraction, ctx: CommandContext) => Promise<void>;
|
||||
}
|
||||
2
packages/lib/src/discord/types/index.ts
Normal file
2
packages/lib/src/discord/types/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './command.type';
|
||||
export * from './interaction.type';
|
||||
20
packages/lib/src/discord/types/interaction.type.ts
Normal file
20
packages/lib/src/discord/types/interaction.type.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import type Dysnomia from '@projectdysnomia/dysnomia';
|
||||
|
||||
export type Interaction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction | AutocompleteInteraction | PingInteraction;
|
||||
|
||||
export type ExecutableInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction | AutocompleteInteraction;
|
||||
|
||||
export interface InteractionAugments {
|
||||
isApplicationCommand: () => this is Dysnomia.CommandInteraction;
|
||||
isModalSubmit: () => this is Dysnomia.ModalSubmitInteraction;
|
||||
isMessageComponent: () => this is Dysnomia.ComponentInteraction;
|
||||
isAutocomplete: () => this is Dysnomia.AutocompleteInteraction;
|
||||
isPing: () => this is Dysnomia.PingInteraction;
|
||||
isExecutable: () => this is ExecutableInteraction;
|
||||
}
|
||||
|
||||
export type CommandInteraction = Dysnomia.CommandInteraction & InteractionAugments;
|
||||
export type ModalSubmitInteraction = Dysnomia.ModalSubmitInteraction & InteractionAugments;
|
||||
export type ComponentInteraction = Dysnomia.ComponentInteraction & InteractionAugments;
|
||||
export type AutocompleteInteraction = Dysnomia.AutocompleteInteraction & InteractionAugments;
|
||||
export type PingInteraction = Dysnomia.PingInteraction & InteractionAugments;
|
||||
@@ -91,7 +91,7 @@ export async function refresh(
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Authorization: `Basic ${Buffer.from(`${options.client_id}:${options.client_secret}`).toString('base64')}`,
|
||||
},
|
||||
body: new URLSearchParams(params),
|
||||
body: new URLSearchParams(params as Record<string, string>),
|
||||
});
|
||||
return (await response.json()) as EveTokens;
|
||||
}
|
||||
|
||||
@@ -13,14 +13,23 @@ export const LOCALE_NAMES: { [key in Locales]: string } = {
|
||||
};
|
||||
export function toDiscordLocale(locale: Locales): string {
|
||||
switch (locale) {
|
||||
case 'en': return 'en-US';
|
||||
case 'ru': return 'ru';
|
||||
case 'de': return 'de';
|
||||
case 'fr': return 'fr';
|
||||
case 'ja': return 'ja';
|
||||
case 'es': return 'es-ES';
|
||||
case 'zh': return 'zh-CN';
|
||||
case 'ko': return 'ko';
|
||||
default: return 'en-US';
|
||||
case 'en':
|
||||
return 'en-US';
|
||||
case 'ru':
|
||||
return 'ru';
|
||||
case 'de':
|
||||
return 'de';
|
||||
case 'fr':
|
||||
return 'fr';
|
||||
case 'ja':
|
||||
return 'ja';
|
||||
case 'es':
|
||||
return 'es-ES';
|
||||
case 'zh':
|
||||
return 'zh-CN';
|
||||
case 'ko':
|
||||
return 'ko';
|
||||
default:
|
||||
return 'en-US';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"strict": true,
|
||||
"strict": false,
|
||||
"noImplicitAny": false,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/lib/discord",
|
||||
"lib": ["ESNext", "DOM"],
|
||||
@@ -12,11 +14,14 @@
|
||||
"@data/*": ["./data/*"],
|
||||
"@types/*": ["./types/*"]
|
||||
},
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"noEmitOnError": false,
|
||||
"declaration": true,
|
||||
"outDir": "dist",
|
||||
"allowImportingTsExtensions": false
|
||||
"outDir": "dist/types",
|
||||
"rootDir": "src",
|
||||
"allowImportingTsExtensions": true
|
||||
},
|
||||
"include": ["src", "types", "src/jsx/types.d.ts"],
|
||||
"exclude": ["node_modules", "dist", "build", "**/*.test.ts"]
|
||||
"exclude": ["node_modules", "build", "**/*.test.ts"]
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ export default defineConfig([
|
||||
],
|
||||
platform: 'node',
|
||||
dts: true,
|
||||
minify: false,
|
||||
mangle: false,
|
||||
external: ['bun:sqlite', 'bun'],
|
||||
},
|
||||
]);
|
||||
|
||||
65
packages/lib/types/index.d.ts
vendored
65
packages/lib/types/index.d.ts
vendored
@@ -1,65 +0,0 @@
|
||||
import {
|
||||
type ActionRow,
|
||||
type Button,
|
||||
type ChannelSelectMenu,
|
||||
type GuildChannelTypes,
|
||||
type MentionableSelectMenu,
|
||||
type PartialEmoji,
|
||||
type RoleSelectMenu,
|
||||
type StringSelectMenu,
|
||||
type TextInput,
|
||||
type UserSelectMenu,
|
||||
type LabelComponent,
|
||||
type ContainerComponent,
|
||||
type TextDisplayComponent,
|
||||
type SectionComponent,
|
||||
type MediaGalleryComponent,
|
||||
type SeparatorComponent,
|
||||
type FileComponent,
|
||||
type InteractionButton,
|
||||
type URLButton,
|
||||
type PremiumButton,
|
||||
type ThumbnailComponent,
|
||||
} from '@projectdysnomia/dysnomia';
|
||||
|
||||
declare namespace JSX {
|
||||
type Component =
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
| UserSelectMenu
|
||||
| RoleSelectMenu
|
||||
| MentionableSelectMenu
|
||||
| ChannelSelectMenu
|
||||
| TextInput
|
||||
| LabelComponent
|
||||
| ContainerComponent
|
||||
| TextDisplayComponent
|
||||
| SectionComponent
|
||||
| MediaGalleryComponent
|
||||
| SeparatorComponent
|
||||
| FileComponent
|
||||
| InteractionButton
|
||||
| URLButton
|
||||
| PremiumButton
|
||||
| ThumbnailComponent;
|
||||
|
||||
type Element = Component | Promise<Component>;
|
||||
|
||||
interface ElementClass {
|
||||
render: any;
|
||||
}
|
||||
|
||||
interface ElementAttributesProperty {
|
||||
props: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: { label: string; customId: string; style?: number; emoji?: PartialEmoji; disabled?: boolean };
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
}
|
||||
}
|
||||
193
packages/lib/wiki/discord.md
Normal file
193
packages/lib/wiki/discord.md
Normal file
@@ -0,0 +1,193 @@
|
||||
# Discord
|
||||
|
||||
Star Kitten's discord functionality is built on top of [Project Dysnomia](https://github.com/projectdysnomia/dysnomia) to provide up to date and performance interactions with the Discord API.
|
||||
|
||||
## Create a bot
|
||||
|
||||
Running a bot with Star Kitten is as simple as calling startBot()!
|
||||
|
||||
_src/main.ts_
|
||||
|
||||
```ts
|
||||
import { startBot } from '@star-kitten/lib/discord';
|
||||
startBot({
|
||||
token: 'XXXXXX', // can be omitted if you set a DISCORD_BOT_TOKEN environment variable
|
||||
});
|
||||
```
|
||||
|
||||
You can configure the bot further through environment variables:
|
||||
|
||||
_I recommend using dotenvx to encrypt your .env files rather than using plaintext .env files_
|
||||
|
||||
```.env
|
||||
# Required
|
||||
DISCORD_BOT_TOKEN="XXXXXX" # https://discord.com/developers/applications
|
||||
|
||||
# Optional
|
||||
DEBUG="true" # Enable debug mode
|
||||
NODE_ENV="development"
|
||||
LOG_LEVEL="debug"
|
||||
RESET_COMMANDS="true" # "true" or NODE_ENV === "development" will have the bot delete and register all commands on every startup, useful for development
|
||||
STAR_KITTEN_KV_DB_PATH="./kv.db" # Allows the bot to remember commands between restarts for resume functionality and recovery from crashes. If not set, :memory: is used so commands created before any restart will no longer function
|
||||
|
||||
# if using EVE ESI functionality
|
||||
EVE_CLIENT_ID="XXXXX"
|
||||
EVE_CLIENT_SECRET="XXXX"
|
||||
EVE_CALLBACK_URL="http://my.callback.url"
|
||||
ESI_USER_AGENT="XXXX" # provided with each ESI request so CCP knows who to contact if there is any issue
|
||||
|
||||
JANICE_KEY="XXXXX" # If you have one for using janice to get pricing data or appraisals
|
||||
|
||||
# AI Features
|
||||
PERPLEXITY_API_KEY="XXXXX"
|
||||
```
|
||||
|
||||
Although, a bot without any commands isn't very useful, so on to Commands
|
||||
|
||||
## Commands
|
||||
|
||||
Star Kitten will automatically register commands for you at startup. By default, any file in your `./src` directory that matches the pattern `**/*.command.{js,ts,jsx,tsx}`, like `./src/ping.command.ts` or `./src/commands/time.command.tsx`.
|
||||
|
||||
### Command File
|
||||
|
||||
A command file's exports must match the [CommandHandler](../src/discord/commands/command-handler.ts#14) structure.
|
||||
|
||||
definition: Defines the command to Discord, providing the command name, description, parameters and more.
|
||||
|
||||
execute: Function that is ran when this command is executed. The execute method will get called for every interaction associated with that command, so ensure you properly handle different interaction types, like Autocomplete vs. ApplicationCommand.
|
||||
|
||||
Example:
|
||||
|
||||
```tsx
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import { type ExecutableInteraction, type CommandContext, isApplicationCommand, Locale } from '@star-kitten/lib/discord';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'time',
|
||||
nameLocalizations: {
|
||||
[Locale.DE]: 'zeit',
|
||||
},
|
||||
description: 'Get the current EVE time',
|
||||
descriptionLocalizations: {
|
||||
[Locale.DE]: 'Holen Sie sich die aktuelle EVE-Zeit',
|
||||
},
|
||||
};
|
||||
|
||||
const eveTimeText = {
|
||||
[Locale.EN_US]: 'EVE Time',
|
||||
[Locale.DE]: 'EVE-Zeit',
|
||||
};
|
||||
|
||||
function renderTimeDisplay(locale: string = 'en-US') {
|
||||
const now = new Date();
|
||||
const eveTime = now.toISOString().split('T')[1].split('.')[0];
|
||||
const eveDate = now.toLocaleDateString(locale, {
|
||||
timeZone: 'UTC',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
weekday: 'long',
|
||||
});
|
||||
return (
|
||||
<container>
|
||||
<textDisplay>
|
||||
{`### ${eveTimeText[locale] || eveTimeText[Locale.EN_US]}
|
||||
${eveTime}
|
||||
${eveDate}`}
|
||||
</textDisplay>
|
||||
</container>
|
||||
);
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (!isApplicationCommand(interaction)) return;
|
||||
|
||||
interaction.createMessage({
|
||||
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
components: [renderTimeDisplay(interaction.locale)],
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
```
|
||||
|
||||
## Components V2
|
||||
|
||||
Star Kitten commands should utilize components v2. We also have a JSX syntax for creating commands. Look at the examples below to compare using JSX vs plain JavaScript objecst to define your components.
|
||||
|
||||
### JSX Components
|
||||
|
||||
```tsx
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return (
|
||||
<modal customId="appraisalResult" title="Appraise Items">
|
||||
<label label="Select a market">
|
||||
<stringSelect customId="market" placeholder="Select a market" minValues={1} maxValues={1}>
|
||||
{markets.map((m) => (
|
||||
<option
|
||||
label={m.name}
|
||||
value={m.id.toString()}
|
||||
default={m.id === 2} // Jita
|
||||
/>
|
||||
))}
|
||||
</stringSelect>
|
||||
</label>
|
||||
<label label="Enter items to appraise">
|
||||
<textInput
|
||||
customId="input"
|
||||
isParagraph={true}
|
||||
placeholder={`e.g. Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`}
|
||||
/>
|
||||
</label>
|
||||
</modal>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
### Plain JS Object
|
||||
|
||||
```ts
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return {
|
||||
type: Constants.InteractionResponseTypes.MODAL,
|
||||
custom_id: 'appraisalResult',
|
||||
title: 'Appraise Items',
|
||||
components: [
|
||||
{
|
||||
type: Constants.ComponentTypes.LABEL,
|
||||
label: 'Select a market',
|
||||
component: {
|
||||
type: Constants.ComponentTypes.STRING_SELECT,
|
||||
custom_id: 'market',
|
||||
placeholder: 'Select a market',
|
||||
min_values: 1,
|
||||
max_values: 1,
|
||||
options: markets.map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id.toString(),
|
||||
default: m.id === 2, // Jita
|
||||
})),
|
||||
},
|
||||
},
|
||||
{
|
||||
type: Constants.ComponentTypes.LABEL,
|
||||
label: 'Enter items to appraise',
|
||||
component: {
|
||||
type: Constants.ComponentTypes.TEXT_INPUT,
|
||||
custom_id: 'input',
|
||||
style: Constants.TextInputStyles.PARAGRAPH,
|
||||
placeholder: `e.g. Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
```
|
||||
@@ -1,12 +1,12 @@
|
||||
# Star Kitten Discord Bot
|
||||
|
||||
A Discord bot for [EVE Online](https://www.eveonline.com/).
|
||||
A Discord bot for [EVE Online](https://www.eveonline.com/) built with [bun](https://bun.sh/) and [@star-kitten/lib](https://git.f302.me/jb/star-kitten)
|
||||
|
||||
# [Click this link to use this bot!](https://discord.com/oauth2/authorize?client_id=1288711114388930601)
|
||||
# [Install Star Kitten](https://discord.com/oauth2/authorize?client_id=1288711114388930601)
|
||||
|
||||
## Running the Bot
|
||||
|
||||
This bot runs on [Bun](https://bun.sh/)! To install, run one of the following commands.
|
||||
This bot runs on [Bun](https://bun.sh/)! To install Bun, run one of the following commands.
|
||||
|
||||
_Linux & MacOS_
|
||||
|
||||
@@ -22,44 +22,22 @@ powershell -c "irm bun.sh/install.ps1 | iex"
|
||||
|
||||
---
|
||||
|
||||
Install dependencies.
|
||||
### Install bot dependencies
|
||||
|
||||
```bash
|
||||
bun install
|
||||
```
|
||||
|
||||
### Link the Library & download static data
|
||||
|
||||
`star-kitten-lib` has not been published, so link to it locally before running this web project.
|
||||
### Download static eve reference data & Hoboleaks archive from [EVE Ref](https://everef.net/)
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun link
|
||||
cd ../web
|
||||
bun link star-kitten-lib
|
||||
```
|
||||
|
||||
### Download static eve reference data & Hoboleaks archive from [EVE Ref](https://everef.net/).
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun get-data
|
||||
```
|
||||
|
||||
### Initialize the sqlite database
|
||||
|
||||
```bash
|
||||
cd star-kitten-lib
|
||||
bun generate-migrations
|
||||
bun migrate
|
||||
```
|
||||
Drizzle's migrations seems to fail on the first try sometimes, so just grab the .sql from the generation and run those against the kitten.db file to create the tables & indexes.
|
||||
|
||||
### Run the bot
|
||||
Run the bot locally.
|
||||
|
||||
```bash
|
||||
bun run dev
|
||||
bun dev
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
@@ -67,6 +45,9 @@ bun run dev
|
||||
Create a .env file in the root directory with the following values:
|
||||
|
||||
```yaml
|
||||
# Discord - https://discord.com/developers/applications
|
||||
DISCORD_BOT_TOKEN=YOUR_BOT_TOKEN
|
||||
|
||||
#General
|
||||
BASE_URL=http://localhost:3000
|
||||
DEBUG=true
|
||||
@@ -74,26 +55,13 @@ PORT=3000
|
||||
NODE_ENV=development
|
||||
LOG_LEVEL=debug
|
||||
|
||||
|
||||
# EVE - https://developers.eveonline.com/applications
|
||||
EVE_CLIENT_ID=YOUR_EVE_CLIENT_ID
|
||||
EVE_CLIENT_SECRET=YOUR_EVE_SECRET
|
||||
EVE_CALLBACK_URL=http://localhost:3000/auth/callback
|
||||
ESI_USER_AGENT=ADD_YOUR_USER_AGENT_INFO_HERE
|
||||
|
||||
#Discord - https://discord.com/developers/applications
|
||||
DISCORD_APP_ID=YOUR_APP_ID
|
||||
DISCORD_CLIENT_SECRET=YOUR_CLIENT_SECRET
|
||||
DISCORD_PUBLIC_KEY=YOUR_PUBLIC_KEY
|
||||
DISCORD_BOT_TOKEN=YOUR_BOT_TOKEN
|
||||
|
||||
# ID of a test server to have immediate command refreshes
|
||||
DISCORD_TEST_GUILD_ID=YOUR_TEST_SERVER_ID
|
||||
|
||||
# For using Janice's Appraisal API
|
||||
JANICE_KEY=XXX
|
||||
|
||||
# For using Perplexities AI API
|
||||
PERPLEXITY_API_KEY=XXX
|
||||
|
||||
```
|
||||
|
||||
@@ -47,7 +47,7 @@ async function execute(interaction: ExecutableInteraction, ctx: CommandContext)
|
||||
appraiseModal: {
|
||||
key: 'appraiseModal',
|
||||
type: PageType.MODAL,
|
||||
render: async () => renderAppraisalModal(interaction),
|
||||
render: () => renderAppraisalModal(interaction) as any,
|
||||
},
|
||||
appraisalResult: {
|
||||
key: 'appraisalResult',
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { ExecutableInteraction } from '@star-kitten/lib/discord';
|
||||
import * as StarKitten from '@star-kitten/lib/discord';
|
||||
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/lib/discord/components';
|
||||
import { actionRow, button, container, text } from '@star-kitten/lib/discord/components';
|
||||
import type { PageContext } from '@star-kitten/lib/discord/pages';
|
||||
import { type Appraisal } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
import { formatNumberToShortForm } from '@star-kitten/lib/util/text.js';
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import type { Interaction } from '@projectdysnomia/dysnomia';
|
||||
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/lib/discord/components';
|
||||
import { markets } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
|
||||
export function renderAppraisalModal(interaction: Interaction) {
|
||||
return {
|
||||
// next page to render will be appraisalResult
|
||||
custom_id: `appraisalResult`,
|
||||
title: 'Appraise Items',
|
||||
components: [
|
||||
createModalLabel(
|
||||
'Select your market (default: Jita)',
|
||||
createStringSelect(
|
||||
'market',
|
||||
{
|
||||
placeholder: 'Select a market',
|
||||
},
|
||||
...markets.map((m) => ({
|
||||
label: m.name,
|
||||
value: m.id.toString(),
|
||||
default: m.id === 2, // Jita
|
||||
})),
|
||||
),
|
||||
),
|
||||
createModalLabel(
|
||||
'Enter items to appraise',
|
||||
createTextInput('input', {
|
||||
isParagraph: true,
|
||||
placeholder: `Enter list of items to be appraised.
|
||||
Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`,
|
||||
}),
|
||||
),
|
||||
],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import { markets } from '@star-kitten/lib/eve/third-party/janice.js';
|
||||
import type { ExecutableInteraction } from '@star-kitten/lib/discord';
|
||||
|
||||
export function renderAppraisalModal(interaction: ExecutableInteraction) {
|
||||
return (
|
||||
<modal customId="appraisalResult" title="Appraise Items">
|
||||
<label label="Select a market">
|
||||
<stringSelect customId="market" placeholder="Select a market" minValues={1} maxValues={1}>
|
||||
{markets.map((m) => (
|
||||
<option
|
||||
label={m.name}
|
||||
value={m.id.toString()}
|
||||
default={m.id === 2} // Jita
|
||||
/>
|
||||
))}
|
||||
</stringSelect>
|
||||
</label>
|
||||
<label label="Enter items to appraise">
|
||||
<textInput
|
||||
customId="input"
|
||||
isParagraph={true}
|
||||
placeholder={`e.g. Tritanium 22222
|
||||
Pyerite 8000
|
||||
Mexallon 2444`}
|
||||
/>
|
||||
</label>
|
||||
</modal>
|
||||
);
|
||||
}
|
||||
@@ -2,11 +2,11 @@ import { renderSubroutes, type Page } from '@star-kitten/lib/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
ButtonStyle,
|
||||
createContainer,
|
||||
createSection,
|
||||
createSeparator,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
container,
|
||||
section,
|
||||
separator,
|
||||
text,
|
||||
thumbnail,
|
||||
Padding,
|
||||
} from '@star-kitten/lib/discord/components';
|
||||
import {
|
||||
@@ -88,11 +88,11 @@ const page: Page<SearchState> = {
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
container(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
section(
|
||||
thumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
text(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
|
||||
),
|
||||
...renderSubroutes(
|
||||
context,
|
||||
@@ -125,11 +125,11 @@ const page: Page<SearchState> = {
|
||||
const unit = attr.attribute.unit_id ? renderUnit(getUnit(attr.attribute.unit_id), attr.value) : '';
|
||||
lines.push(`${attr.attribute.display_name.en.padEnd(24)} ${unit}`);
|
||||
});
|
||||
return createTextDisplay('```\n' + lines.join('\n') + '\n```');
|
||||
return text('```\n' + lines.join('\n') + '\n```');
|
||||
},
|
||||
{ style: ButtonStyle.SECONDARY },
|
||||
),
|
||||
createSeparator(Padding.LARGE),
|
||||
separator(Padding.LARGE),
|
||||
searchActionRow('attributes'),
|
||||
),
|
||||
],
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { createActionRow, createButton } from '@star-kitten/lib/discord/components';
|
||||
import { actionRow, button } from '@star-kitten/lib/discord/components';
|
||||
|
||||
export function searchActionRow(pageKey: string) {
|
||||
return createActionRow(
|
||||
createButton('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
createButton('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
createButton('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
createButton('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
createButton('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
return actionRow(
|
||||
button('Main', 'main', { disabled: pageKey === 'main' }),
|
||||
button('Attributes', 'attributes', { disabled: pageKey === 'attributes' }),
|
||||
button('Fittings', 'fittings', { disabled: pageKey === 'fittings' }),
|
||||
button('Skills', 'skills', { disabled: pageKey === 'skills' }),
|
||||
button('Industry', 'industry', { disabled: pageKey === 'industry' }),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
import type { Page } from '@star-kitten/lib/discord/pages';
|
||||
import type { SearchState } from '../search.command';
|
||||
import {
|
||||
createContainer,
|
||||
createMediaGallery,
|
||||
createSection,
|
||||
createTextDisplay,
|
||||
createThumbnail,
|
||||
createURLButton,
|
||||
} from '@star-kitten/lib/discord/components';
|
||||
import { container, gallery, section, text, thumbnail, urlButton } from '@star-kitten/lib/discord/components';
|
||||
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/lib/eve/models/type.js';
|
||||
import { cleanText } from '@star-kitten/lib/eve/utils/markdown.js';
|
||||
import { typeSearch } from '@star-kitten/lib/eve/utils/typeSearch.js';
|
||||
@@ -25,7 +18,7 @@ const page: Page<SearchState> = {
|
||||
|
||||
if (!found) {
|
||||
return {
|
||||
components: [createTextDisplay(`No item found for: ${typeName}`)],
|
||||
components: [text(`No item found for: ${typeName}`)],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -40,11 +33,11 @@ const page: Page<SearchState> = {
|
||||
|
||||
return {
|
||||
components: [
|
||||
createContainer(
|
||||
container(
|
||||
{},
|
||||
createSection(
|
||||
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
createTextDisplay(`
|
||||
section(
|
||||
thumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
|
||||
text(`
|
||||
# [${type.name.en}](https://everef.net/types/${type.type_id})
|
||||
|
||||
${skillBonuses
|
||||
@@ -67,18 +60,18 @@ ${roleBonuses
|
||||
}
|
||||
`),
|
||||
),
|
||||
createMediaGallery({
|
||||
gallery({
|
||||
url: 'https://iili.io/KTPCFRt.md.webp',
|
||||
}),
|
||||
// createSeparator(Padding.LARGE),
|
||||
createSection(
|
||||
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
createTextDisplay(
|
||||
section(
|
||||
urlButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
|
||||
text(
|
||||
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
|
||||
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
|
||||
),
|
||||
),
|
||||
createTextDisplay(`-# Type Id: ${type.type_id}`),
|
||||
text(`-# Type Id: ${type.type_id}`),
|
||||
searchActionRow('main'),
|
||||
),
|
||||
],
|
||||
|
||||
78
packages/star-kitten-bot/src/commands/time.command.tsx
Normal file
78
packages/star-kitten-bot/src/commands/time.command.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
|
||||
import {
|
||||
type ExecutableInteraction,
|
||||
type CommandContext,
|
||||
isApplicationCommand,
|
||||
Locale,
|
||||
} from '@star-kitten/lib/discord';
|
||||
|
||||
const definition: ChatInputApplicationCommandStructure = {
|
||||
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
|
||||
name: 'time',
|
||||
nameLocalizations: {
|
||||
[Locale.DE]: 'zeit',
|
||||
[Locale.ES_ES]: 'hora',
|
||||
[Locale.FR]: 'heure',
|
||||
[Locale.JA]: '時間',
|
||||
[Locale.KO]: '시간',
|
||||
[Locale.RU]: 'время',
|
||||
[Locale.ZH_CN]: '时间',
|
||||
},
|
||||
description: 'Get the current EVE time',
|
||||
descriptionLocalizations: {
|
||||
[Locale.DE]: 'Holen Sie sich die aktuelle EVE-Zeit',
|
||||
[Locale.ES_ES]: 'Obtén la hora actual de EVE',
|
||||
[Locale.FR]: "Obtenez l'heure actuelle d'EVE",
|
||||
[Locale.JA]: '現在のEVE時間を取得します',
|
||||
[Locale.KO]: '현재 EVE 시간을 가져옵니다',
|
||||
[Locale.RU]: 'Получите текущее время EVE',
|
||||
[Locale.ZH_CN]: '获取当前的EVE时间',
|
||||
},
|
||||
};
|
||||
|
||||
const eveTimeText = {
|
||||
[Locale.EN_US]: 'EVE Time',
|
||||
[Locale.EN_GB]: 'EVE Time',
|
||||
[Locale.DE]: 'EVE-Zeit',
|
||||
[Locale.ES_ES]: 'Hora EVE',
|
||||
[Locale.FR]: "Heure d'EVE",
|
||||
[Locale.JA]: 'EVE時間',
|
||||
[Locale.KO]: 'EVE 시간',
|
||||
[Locale.RU]: 'Время EVE',
|
||||
[Locale.ZH_CN]: 'EVE时间',
|
||||
};
|
||||
|
||||
function renderTimeDisplay(locale: string = 'en-US') {
|
||||
const now = new Date();
|
||||
const eveTime = now.toISOString().split('T')[1].split('.')[0];
|
||||
const eveDate = now.toLocaleDateString(locale, {
|
||||
timeZone: 'UTC',
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: '2-digit',
|
||||
weekday: 'long',
|
||||
});
|
||||
return (
|
||||
<container>
|
||||
<textDisplay>
|
||||
{`### ${eveTimeText[locale] || eveTimeText[Locale.EN_US]}
|
||||
${eveTime}
|
||||
${eveDate}`}
|
||||
</textDisplay>
|
||||
</container>
|
||||
);
|
||||
}
|
||||
|
||||
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
|
||||
if (!isApplicationCommand(interaction)) return;
|
||||
|
||||
interaction.createMessage({
|
||||
flags: Constants.MessageFlags.IS_COMPONENTS_V2,
|
||||
components: [renderTimeDisplay(interaction.locale)],
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
definition,
|
||||
execute,
|
||||
};
|
||||
@@ -1,3 +1,3 @@
|
||||
import { startDiscordBot } from '@star-kitten/lib/discord';
|
||||
import { startBot } from '@star-kitten/lib/discord';
|
||||
|
||||
startDiscordBot();
|
||||
startBot();
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
export function renderAppraisal() {
|
||||
const formatter = new Intl.NumberFormat('en-US', {
|
||||
maximumFractionDigits: 2,
|
||||
minimumFractionDigits: 2,
|
||||
});
|
||||
const world = 'world';
|
||||
const rand = Math.random() * 1000;
|
||||
const pageCtx = { state: { currentPage: 'home' } };
|
||||
|
||||
let jsx = (
|
||||
<actionRow>
|
||||
<container accent={0x1da57a}>
|
||||
<textDisplay content={`Hello ${world}`} />
|
||||
{pageCtx.state.currentPage !== 'share' ? (
|
||||
<actionRow>
|
||||
<button customId="share" label="Share in Channel" disabled={rand < 500} />
|
||||
</actionRow>
|
||||
) : undefined}
|
||||
</container>
|
||||
</actionRow>
|
||||
);
|
||||
|
||||
console.log(jsx);
|
||||
}
|
||||
|
||||
renderAppraisal();
|
||||
@@ -4,9 +4,9 @@
|
||||
"composite": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "@star-kitten/lib/discord",
|
||||
"moduleResolution": "bundler",
|
||||
"paths": {
|
||||
"@*": ["./src/*"],
|
||||
"@star-kitten/lib/*": ["../lib/src/*"]
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"typeRoots": ["src/types", "./node_modules/@types"]
|
||||
},
|
||||
|
||||
127
types/jsx.d.ts
vendored
127
types/jsx.d.ts
vendored
@@ -53,25 +53,48 @@ declare namespace JSX {
|
||||
props: {};
|
||||
}
|
||||
|
||||
// This tells TypeScript to check children against the 'children' property
|
||||
interface ElementChildrenAttribute {
|
||||
children: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: {
|
||||
// Known elements - no index signature to enforce type safety
|
||||
actionRow: { children: Element | Element[] };
|
||||
button: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style?: number;
|
||||
style: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
container: {
|
||||
accent?: number;
|
||||
spoiler?: boolean;
|
||||
children: Element | Element[];
|
||||
};
|
||||
textDisplay: { content: string };
|
||||
modal: { customId?: string; title?: string; children: Element | Element[] };
|
||||
label: { label: string; children: Element };
|
||||
stringSelect: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
children: Element | Element[];
|
||||
};
|
||||
option: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: PartialEmoji;
|
||||
default?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@star-kitten/lib/discord/jsx-runtime" {
|
||||
declare namespace JSX {
|
||||
export namespace JSX {
|
||||
type Component =
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
@@ -102,26 +125,52 @@ declare module "@star-kitten/lib/discord/jsx-runtime" {
|
||||
props: {};
|
||||
}
|
||||
|
||||
// This tells TypeScript to check children against the 'children' property
|
||||
interface ElementChildrenAttribute {
|
||||
children: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: {
|
||||
actionRow: { children: Element | Element[] };
|
||||
button: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style?: number;
|
||||
style: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
container: {
|
||||
accent?: number;
|
||||
spoiler?: boolean;
|
||||
children: Element | Element[];
|
||||
};
|
||||
textDisplay: { content: string };
|
||||
modal: {
|
||||
customId?: string;
|
||||
title?: string;
|
||||
children: Element | Element[];
|
||||
};
|
||||
label: { label: string; children: Element };
|
||||
stringSelect: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
children: Element | Element[];
|
||||
};
|
||||
option: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: PartialEmoji;
|
||||
default?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare module "@star-kitten/lib/discord/jsx-dev-runtime" {
|
||||
declare namespace JSX {
|
||||
export namespace JSX {
|
||||
type Component =
|
||||
| Button
|
||||
| StringSelectMenu
|
||||
@@ -152,20 +201,46 @@ declare module "@star-kitten/lib/discord/jsx-dev-runtime" {
|
||||
props: {};
|
||||
}
|
||||
|
||||
// This tells TypeScript to check children against the 'children' property
|
||||
interface ElementChildrenAttribute {
|
||||
children: {};
|
||||
}
|
||||
|
||||
interface IntrinsicElements {
|
||||
// Allow any element, but prefer known elements
|
||||
[elemName: string]: any;
|
||||
// Known elements
|
||||
ActionRow: { children: any | any[] };
|
||||
Button: {
|
||||
actionRow: { children: Element | Element[] };
|
||||
button: {
|
||||
label: string;
|
||||
customId: string;
|
||||
style?: number;
|
||||
style: number;
|
||||
emoji?: PartialEmoji;
|
||||
disabled?: boolean;
|
||||
};
|
||||
Container: { accent?: number; spoiler?: boolean; children: any | any[] };
|
||||
TextDisplay: { content: string };
|
||||
container: {
|
||||
accent?: number;
|
||||
spoiler?: boolean;
|
||||
children: Element | Element[];
|
||||
};
|
||||
textDisplay: { content: string };
|
||||
modal: {
|
||||
customId?: string;
|
||||
title?: string;
|
||||
children: Element | Element[];
|
||||
};
|
||||
label: { label: string; children: Element };
|
||||
stringSelect: {
|
||||
customId: string;
|
||||
placeholder?: string;
|
||||
minValues?: number;
|
||||
maxValues?: number;
|
||||
children: Element | Element[];
|
||||
};
|
||||
option: {
|
||||
label: string;
|
||||
value: string;
|
||||
description?: string;
|
||||
emoji?: PartialEmoji;
|
||||
default?: boolean;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user