adding freight web and holding

This commit is contained in:
JB
2025-12-24 00:38:53 -05:00
parent 0c8630b8ba
commit f39e9132ad
164 changed files with 5559 additions and 156655 deletions

View File

@@ -1,21 +1,21 @@
import * as StarKitten from '@star-kitten/discord';
import type { ExecutableInteraction } from '@star-kitten/discord';
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
import type { PageContext } from '@star-kitten/discord/pages';
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
export function renderAppraisal(
appraisal: Appraisal,
pageCtx: PageContext<any>,
interaction: ExecutableInteraction,
) {
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
const world = 'world';
return (
StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Container", {"color":"0x1da57a"}, StarKitten.createElement("TextDisplay", {}, ""+ `Hello ${world}` +""), pageCtx.state.currentPage !== "share" ? StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Button", {"key":"share","disabled":"{!unknown}"}, "Share in Channel")) : undefined))
)
}
import * as StarKitten from '@star-kitten/discord';
import type { ExecutableInteraction } from '@star-kitten/discord';
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
import type { PageContext } from '@star-kitten/discord/pages';
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
export function renderAppraisal(
appraisal: Appraisal,
pageCtx: PageContext<any>,
interaction: ExecutableInteraction,
) {
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
const world = 'world';
return (
StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Container", {"color":"0x1da57a"}, StarKitten.createElement("TextDisplay", {}, ""+ `Hello ${world}` +""), pageCtx.state.currentPage !== "share" ? StarKitten.createElement("ActionRow", {}, StarKitten.createElement("Button", {"key":"share","disabled":"{!unknown}"}, "Share in Channel")) : undefined))
)
}

View File

@@ -1,29 +0,0 @@
import type {} from '@star-kitten/discord/jsx';
import { ActionRow, Container, Button, TextDisplay } from '@star-kitten/discord';
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 color="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();

View File

@@ -1,69 +1,69 @@
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 =
| ActionRow
| Button
| StringSelectMenu
| UserSelectMenu
| RoleSelectMenu
| MentionableSelectMenu
| ChannelSelectMenu
| TextInput
| LabelComponent
| ContainerComponent
| {
type: 10;
content: string;
}
| 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 };
}
}
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 =
| ActionRow
| Button
| StringSelectMenu
| UserSelectMenu
| RoleSelectMenu
| MentionableSelectMenu
| ChannelSelectMenu
| TextInput
| LabelComponent
| ContainerComponent
| {
type: 10;
content: string;
}
| 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 };
}
}

View File

@@ -14,7 +14,8 @@
},
"author": "Author Name <author.name@mail.com>",
"files": [
"dist"
"dist",
"src"
],
"main": "./dist/index.js",
"module": "./dist/index.js",
@@ -26,9 +27,15 @@
"./pages": "./dist/pages/index.js",
"./common": "./dist/common/index.js",
"./package.json": "./package.json",
"./jsx": "./src/jsx/jsx.ts",
"./jsx-runtime": "./dist/jsx/index.js",
"./jsx-dev-runtime": "./dist/jsx/index.js"
"./jsx": "./dist/jsx/index.js",
"./jsx-runtime": {
"types": "./src/jsx/types.d.ts",
"default": "./dist/jsx/jsx-runtime.js"
},
"./jsx-dev-runtime": {
"types": "./src/jsx/types.d.ts",
"default": "./dist/jsx/jsx-dev-runtime.js"
}
},
"publishConfig": {
"access": "public"

View File

@@ -1,14 +1,14 @@
import type { Cache } from '@core/cache.type';
import type { KVStore } from '@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>>;
import type { Cache } from '@core/cache.type';
import type { KVStore } from '@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>>;

View File

@@ -1,32 +1,32 @@
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';
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>,
): CommandHandler<ChatInputApplicationCommandStructure> {
const def = definition as ChatInputApplicationCommandStructure;
def.type = 1; // CHAT_INPUT
return {
definition: def,
execute,
};
}
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';
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>,
): CommandHandler<ChatInputApplicationCommandStructure> {
const def = definition as ChatInputApplicationCommandStructure;
def.type = 1; // CHAT_INPUT
return {
definition: def,
execute,
};
}

View File

@@ -1,45 +1,45 @@
import {
Interaction,
CommandInteraction,
Constants,
ModalSubmitInteraction,
ComponentInteraction,
AutocompleteInteraction,
PingInteraction,
} from '@projectdysnomia/dysnomia';
import type { ExecutableInteraction } from './command-handler';
export function isApplicationCommand(interaction: Interaction): interaction is CommandInteraction {
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND;
}
export function isModalSubmit(interaction: Interaction): interaction is ModalSubmitInteraction {
return interaction.type === Constants.InteractionTypes.MODAL_SUBMIT;
}
export function isMessageComponent(interaction: Interaction): interaction is ComponentInteraction {
return interaction.type === Constants.InteractionTypes.MESSAGE_COMPONENT;
}
export function isAutocomplete(interaction: Interaction): interaction is AutocompleteInteraction {
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE;
}
export function isPing(interaction: Interaction): interaction is PingInteraction {
return interaction.type === Constants.InteractionTypes.PING;
}
export function commandHasName(interaction: Interaction, name: string): boolean {
return isApplicationCommand(interaction) && interaction.data.name === name;
}
export function commandHasIdPrefix(interaction: Interaction, prefix: string): boolean {
return (isModalSubmit(interaction) || isMessageComponent(interaction)) && interaction.data.custom_id.startsWith(prefix);
}
export function getCommandName(interaction: ExecutableInteraction): string | undefined {
if (isApplicationCommand(interaction) || isAutocomplete(interaction)) {
return interaction.data.name;
}
return undefined;
}
import {
Interaction,
CommandInteraction,
Constants,
ModalSubmitInteraction,
ComponentInteraction,
AutocompleteInteraction,
PingInteraction,
} from '@projectdysnomia/dysnomia';
import type { ExecutableInteraction } from './command-handler';
export function isApplicationCommand(interaction: Interaction): interaction is CommandInteraction {
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND;
}
export function isModalSubmit(interaction: Interaction): interaction is ModalSubmitInteraction {
return interaction.type === Constants.InteractionTypes.MODAL_SUBMIT;
}
export function isMessageComponent(interaction: Interaction): interaction is ComponentInteraction {
return interaction.type === Constants.InteractionTypes.MESSAGE_COMPONENT;
}
export function isAutocomplete(interaction: Interaction): interaction is AutocompleteInteraction {
return interaction.type === Constants.InteractionTypes.APPLICATION_COMMAND_AUTOCOMPLETE;
}
export function isPing(interaction: Interaction): interaction is PingInteraction {
return interaction.type === Constants.InteractionTypes.PING;
}
export function commandHasName(interaction: Interaction, name: string): boolean {
return isApplicationCommand(interaction) && interaction.data.name === name;
}
export function commandHasIdPrefix(interaction: Interaction, prefix: string): boolean {
return (isModalSubmit(interaction) || isMessageComponent(interaction)) && interaction.data.custom_id.startsWith(prefix);
}
export function getCommandName(interaction: ExecutableInteraction): string | undefined {
if (isApplicationCommand(interaction) || isAutocomplete(interaction)) {
return interaction.data.name;
}
return undefined;
}

View File

@@ -1,63 +1,63 @@
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';
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.
if (ctx.state.name && (isApplicationCommand(interaction) || isMessageComponent(interaction))) {
const _originalCreateModal = interaction.createModal.bind(interaction);
interaction.createModal = (content: InteractionModalContent) => {
validateCustomIdLength(content.custom_id);
content.custom_id = `${content.custom_id}_${ctx.state.id}`;
return _originalCreateModal(content);
};
const _originalCreateMessage = interaction.createMessage.bind(interaction);
interaction.createMessage = (content) => {
if (typeof content === 'string') return _originalCreateMessage(content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalCreateMessage(content);
};
const _originalEditMessage = interaction.editMessage.bind(interaction);
interaction.editMessage = (messageID, content) => {
if (typeof content === 'string') return _originalEditMessage(messageID, content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalEditMessage(messageID, content);
};
const _originalCreateFollowup = interaction.createFollowup.bind(interaction);
interaction.createFollowup = (content) => {
if (typeof content === 'string') return _originalCreateFollowup(content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalCreateFollowup(content);
};
}
return [interaction, ctx as CommandContext];
}
function validateCustomIdLength(customId: string) {
if (customId.length > 80) {
throw new Error(`Custom ID too long: ${customId.length} characters (max 80) with this framework. Consider using shorter IDs.`);
}
}
function addCommandIdToComponentCustomIds(components: Component[], commandId: string) {
components.forEach((component) => {
if (!component) return;
if ('custom_id' in component) {
validateCustomIdLength(component.custom_id as string);
component.custom_id = `${component.custom_id}_${commandId}`;
}
if ('components' in component && Array.isArray(component.components)) {
addCommandIdToComponentCustomIds(component.components, commandId);
}
});
}
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';
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.
if (ctx.state.name && (isApplicationCommand(interaction) || isMessageComponent(interaction))) {
const _originalCreateModal = interaction.createModal.bind(interaction);
interaction.createModal = (content: InteractionModalContent) => {
validateCustomIdLength(content.custom_id);
content.custom_id = `${content.custom_id}_${ctx.state.id}`;
return _originalCreateModal(content);
};
const _originalCreateMessage = interaction.createMessage.bind(interaction);
interaction.createMessage = (content) => {
if (typeof content === 'string') return _originalCreateMessage(content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalCreateMessage(content);
};
const _originalEditMessage = interaction.editMessage.bind(interaction);
interaction.editMessage = (messageID, content) => {
if (typeof content === 'string') return _originalEditMessage(messageID, content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalEditMessage(messageID, content);
};
const _originalCreateFollowup = interaction.createFollowup.bind(interaction);
interaction.createFollowup = (content) => {
if (typeof content === 'string') return _originalCreateFollowup(content);
if (content.components) {
addCommandIdToComponentCustomIds(content.components, ctx.state.id);
}
return _originalCreateFollowup(content);
};
}
return [interaction, ctx as CommandContext];
}
function validateCustomIdLength(customId: string) {
if (customId.length > 80) {
throw new Error(`Custom ID too long: ${customId.length} characters (max 80) with this framework. Consider using shorter IDs.`);
}
}
function addCommandIdToComponentCustomIds(components: Component[], commandId: string) {
components.forEach((component) => {
if (!component) return;
if ('custom_id' in component) {
validateCustomIdLength(component.custom_id as string);
component.custom_id = `${component.custom_id}_${commandId}`;
}
if ('components' in component && Array.isArray(component.components)) {
addCommandIdToComponentCustomIds(component.components, commandId);
}
});
}

View File

@@ -1,56 +1,56 @@
import { createReactiveState } from '@star-kitten/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
}
export async function getCommandState<T>(interaction: ExecutableInteraction, ctx: PartialContext): Promise<CommandState<T>> {
const id = instanceIdFromInteraction(interaction);
let state: CommandState<T>;
// get state from kv store if possible
if (ctx.kv.has(`command-state:${id}`)) {
state = await ctx.kv.get<CommandState<T>>(`command-state:${id}`);
}
if (!state) {
state = { id: id, name: '', data: {} as T };
}
const [reactiveState, subscribe] = createReactiveState(state);
subscribe(async (newState) => {
if (ctx.kv) {
await ctx.kv.set(`command-state:${id}`, newState);
}
});
ctx.state = reactiveState;
return reactiveState;
}
function instanceIdFromInteraction(interaction: ExecutableInteraction) {
if (isAutocomplete(interaction)) {
// autocomplete should not be stateful, they get no id
return '';
}
if (isApplicationCommand(interaction)) {
// for application commands, we create a new instance id
const instance_id = crypto.randomUUID();
return instance_id;
}
const interact = interaction;
const customId: string = interact.data.custom_id;
const commandId = customId.split('_').pop();
interaction;
// command id should be a uuid
if (commandId && /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(commandId)) {
return commandId;
}
console.error(`Invalid command id extracted from interaction: ${customId}`);
return '';
}
import { createReactiveState } from '@star-kitten/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
}
export async function getCommandState<T>(interaction: ExecutableInteraction, ctx: PartialContext): Promise<CommandState<T>> {
const id = instanceIdFromInteraction(interaction);
let state: CommandState<T>;
// get state from kv store if possible
if (ctx.kv.has(`command-state:${id}`)) {
state = await ctx.kv.get<CommandState<T>>(`command-state:${id}`);
}
if (!state) {
state = { id: id, name: '', data: {} as T };
}
const [reactiveState, subscribe] = createReactiveState(state);
subscribe(async (newState) => {
if (ctx.kv) {
await ctx.kv.set(`command-state:${id}`, newState);
}
});
ctx.state = reactiveState;
return reactiveState;
}
function instanceIdFromInteraction(interaction: ExecutableInteraction) {
if (isAutocomplete(interaction)) {
// autocomplete should not be stateful, they get no id
return '';
}
if (isApplicationCommand(interaction)) {
// for application commands, we create a new instance id
const instance_id = crypto.randomUUID();
return instance_id;
}
const interact = interaction;
const customId: string = interact.data.custom_id;
const commandId = customId.split('_').pop();
interaction;
// command id should be a uuid
if (commandId && /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(commandId)) {
return commandId;
}
console.error(`Invalid command id extracted from interaction: ${customId}`);
return '';
}

View File

@@ -1,73 +1,73 @@
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 { injectInteraction } from './command-injection';
import { getCommandState } from './command-state';
export async function handleCommands(
interaction: ExecutableInteraction,
commands: Record<string, CommandHandler<ApplicationCommandStructure>>,
ctx: PartialContext,
) {
ctx.state = await getCommandState(interaction, ctx);
if (!ctx.state.name) {
ctx.state.name = getCommandName(interaction);
}
if (isAutocomplete(interaction) && ctx.state.name) {
const acCommand = commands[ctx.state.name];
return acCommand.execute(interaction, ctx as any);
}
if (!ctx.state.id) {
console.error(`No command ID found for interaction ${interaction.id}`);
return;
}
const command = commands[ctx.state.name || ''];
if (!command) {
console.warn(`No command found for interaction: ${JSON.stringify(interaction, undefined, 2)}`);
return;
}
cleanInteractionCustomIds(interaction, ctx.state.id);
const [injectedInteraction, fullContext] = await injectInteraction(interaction, ctx);
return command.execute(injectedInteraction, fullContext);
}
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)) {
handleCommands(interaction, commands, ctx);
}
});
}
function cleanInteractionCustomIds(interaction: ExecutableInteraction, id: string) {
if ('components' in interaction && Array.isArray(interaction.components) && id) {
removeCommandIdFromComponentCustomIds(interaction.components, id);
}
if ('data' in interaction && id) {
if ('custom_id' in interaction.data && typeof interaction.data.custom_id === 'string') {
interaction.data.custom_id = interaction.data.custom_id.replace(`_${id}`, '');
}
if ('components' in interaction.data && Array.isArray(interaction.data.components)) {
removeCommandIdFromComponentCustomIds(interaction.data.components as any, id);
}
}
}
function removeCommandIdFromComponentCustomIds(components: { custom_id?: string; components?: any[] }[], commandId: string) {
components.forEach((component) => {
if ('custom_id' in component) {
component.custom_id = component.custom_id.replace(`_${commandId}`, '');
}
if ('components' in component && Array.isArray(component.components)) {
removeCommandIdFromComponentCustomIds(component.components, commandId);
}
if ('component' in component && 'custom_id' in (component as any).component && Array.isArray(component.components)) {
(component.component as any).custom_id = (component.component as any).custom_id.replace(`_${commandId}`, '');
}
});
}
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 { injectInteraction } from './command-injection';
import { getCommandState } from './command-state';
export async function handleCommands(
interaction: ExecutableInteraction,
commands: Record<string, CommandHandler<ApplicationCommandStructure>>,
ctx: PartialContext,
) {
ctx.state = await getCommandState(interaction, ctx);
if (!ctx.state.name) {
ctx.state.name = getCommandName(interaction);
}
if (isAutocomplete(interaction) && ctx.state.name) {
const acCommand = commands[ctx.state.name];
return acCommand.execute(interaction, ctx as any);
}
if (!ctx.state.id) {
console.error(`No command ID found for interaction ${interaction.id}`);
return;
}
const command = commands[ctx.state.name || ''];
if (!command) {
console.warn(`No command found for interaction: ${JSON.stringify(interaction, undefined, 2)}`);
return;
}
cleanInteractionCustomIds(interaction, ctx.state.id);
const [injectedInteraction, fullContext] = await injectInteraction(interaction, ctx);
return command.execute(injectedInteraction, fullContext);
}
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)) {
handleCommands(interaction, commands, ctx);
}
});
}
function cleanInteractionCustomIds(interaction: ExecutableInteraction, id: string) {
if ('components' in interaction && Array.isArray(interaction.components) && id) {
removeCommandIdFromComponentCustomIds(interaction.components, id);
}
if ('data' in interaction && id) {
if ('custom_id' in interaction.data && typeof interaction.data.custom_id === 'string') {
interaction.data.custom_id = interaction.data.custom_id.replace(`_${id}`, '');
}
if ('components' in interaction.data && Array.isArray(interaction.data.components)) {
removeCommandIdFromComponentCustomIds(interaction.data.components as any, id);
}
}
}
function removeCommandIdFromComponentCustomIds(components: { custom_id?: string; components?: any[] }[], commandId: string) {
components.forEach((component) => {
if ('custom_id' in component) {
component.custom_id = component.custom_id.replace(`_${commandId}`, '');
}
if ('components' in component && Array.isArray(component.components)) {
removeCommandIdFromComponentCustomIds(component.components, commandId);
}
if ('component' in component && 'custom_id' in (component as any).component && Array.isArray(component.components)) {
(component.component as any).custom_id = (component.component as any).custom_id.replace(`_${commandId}`, '');
}
});
}

View File

@@ -1,19 +1,19 @@
import { Glob } from 'bun';
import { join } from 'node:path';
import type { CommandHandler } from './command-handler';
import type { ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
export async function importCommands(
pattern: string = '**/*.command.{js,ts}',
baseDir: string = join(process.cwd(), 'src'),
commandRegistry: Record<string, CommandHandler<ApplicationCommandStructure>> = {},
): Promise<Record<string, CommandHandler<ApplicationCommandStructure>>> {
const glob = new Glob(pattern);
for await (const file of glob.scan({ cwd: baseDir, absolute: true })) {
const command = (await import(file)).default as CommandHandler<ApplicationCommandStructure>;
commandRegistry[command.definition.name] = command;
}
return commandRegistry;
}
import { Glob } from 'bun';
import { join } from 'node:path';
import type { CommandHandler } from './command-handler';
import type { ApplicationCommandStructure } from '@projectdysnomia/dysnomia';
export async function importCommands(
pattern: string = '**/*.command.{js,ts}',
baseDir: string = join(process.cwd(), 'src'),
commandRegistry: Record<string, CommandHandler<ApplicationCommandStructure>> = {},
): Promise<Record<string, CommandHandler<ApplicationCommandStructure>>> {
const glob = new Glob(pattern);
for await (const file of glob.scan({ cwd: baseDir, absolute: true })) {
const command = (await import(file)).default as CommandHandler<ApplicationCommandStructure>;
commandRegistry[command.definition.name] = command;
}
return commandRegistry;
}

View File

@@ -1,80 +1,80 @@
import {
Constants,
type ApplicationCommandOptions,
type ApplicationCommandOptionsBoolean,
type ApplicationCommandOptionsInteger,
type ApplicationCommandOptionsMentionable,
type ApplicationCommandOptionsNumber,
type ApplicationCommandOptionsRole,
type ApplicationCommandOptionsString,
type ApplicationCommandOptionsSubCommand,
type ApplicationCommandOptionsSubCommandGroup,
type ApplicationCommandOptionsUser,
} from '@projectdysnomia/dysnomia';
export type StringOptionDefinition = Omit<ApplicationCommandOptionsString, 'type'> & { autocomplete?: boolean };
export function stringOption(options: StringOptionDefinition): ApplicationCommandOptionsString {
const def = options as ApplicationCommandOptionsString;
def.type = Constants.ApplicationCommandOptionTypes.STRING;
return def;
}
export type IntegerOptionDefinition = Omit<ApplicationCommandOptionsInteger, 'type'> & { autocomplete?: boolean };
export function integerOption(options: IntegerOptionDefinition): ApplicationCommandOptionsInteger {
const def = options as ApplicationCommandOptionsInteger;
def.type = Constants.ApplicationCommandOptionTypes.INTEGER;
return def;
}
export type BooleanOptionDefinition = Omit<ApplicationCommandOptionsBoolean, 'type'>;
export function booleanOption(options: BooleanOptionDefinition): ApplicationCommandOptionsBoolean {
const def = options as ApplicationCommandOptionsBoolean;
def.type = Constants.ApplicationCommandOptionTypes.BOOLEAN;
return def;
}
export type UserOptionDefinition = Omit<ApplicationCommandOptionsUser, 'type'> & { autocomplete?: boolean };
export function userOption(options: UserOptionDefinition): ApplicationCommandOptionsUser {
const def = options as ApplicationCommandOptionsUser;
def.type = Constants.ApplicationCommandOptionTypes.USER;
return def;
}
export type ChannelOptionDefinition = Omit<ApplicationCommandOptions, 'type'> & { autocomplete?: boolean };
export function channelOption(options: ChannelOptionDefinition): ApplicationCommandOptions {
const def = options as ApplicationCommandOptions;
def.type = Constants.ApplicationCommandOptionTypes.CHANNEL;
return def;
}
export type RoleOptionDefinition = Omit<ApplicationCommandOptionsRole, 'type'> & { autocomplete?: boolean };
export function roleOption(options: RoleOptionDefinition): ApplicationCommandOptionsRole {
const def = options as ApplicationCommandOptionsRole;
def.type = Constants.ApplicationCommandOptionTypes.ROLE;
return def;
}
export type MentionableOptionDefinition = Omit<ApplicationCommandOptionsMentionable, 'type'>;
export function mentionableOption(options: MentionableOptionDefinition): ApplicationCommandOptionsMentionable {
const def = options as ApplicationCommandOptionsMentionable;
def.type = Constants.ApplicationCommandOptionTypes.MENTIONABLE;
return def;
}
export type NumberOptionDefinition = Omit<ApplicationCommandOptionsNumber, 'type'> & { autocomplete?: boolean };
export function numberOption(options: NumberOptionDefinition): ApplicationCommandOptionsNumber {
const def = options as ApplicationCommandOptionsNumber;
def.type = Constants.ApplicationCommandOptionTypes.NUMBER;
return def;
}
export type AttachmentOptionDefinition = Omit<ApplicationCommandOptions, 'type'>;
export function attachmentOption(options: AttachmentOptionDefinition): ApplicationCommandOptions {
const def = options as ApplicationCommandOptions;
def.type = Constants.ApplicationCommandOptionTypes.ATTACHMENT;
return def;
}
export type SubCommandOptionDefinition = Omit<ApplicationCommandOptionsSubCommand, 'type'>;
export function subCommandOption(options: SubCommandOptionDefinition): ApplicationCommandOptionsSubCommand {
const def = options as ApplicationCommandOptionsSubCommand;
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND;
return def;
}
export type SubCommandGroupOptionDefinition = Omit<ApplicationCommandOptionsSubCommandGroup, 'type'>;
export function subCommandGroupOption(options: SubCommandGroupOptionDefinition): ApplicationCommandOptionsSubCommandGroup {
const def = options as ApplicationCommandOptionsSubCommandGroup;
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND_GROUP;
return def;
}
import {
Constants,
type ApplicationCommandOptions,
type ApplicationCommandOptionsBoolean,
type ApplicationCommandOptionsInteger,
type ApplicationCommandOptionsMentionable,
type ApplicationCommandOptionsNumber,
type ApplicationCommandOptionsRole,
type ApplicationCommandOptionsString,
type ApplicationCommandOptionsSubCommand,
type ApplicationCommandOptionsSubCommandGroup,
type ApplicationCommandOptionsUser,
} from '@projectdysnomia/dysnomia';
export type StringOptionDefinition = Omit<ApplicationCommandOptionsString, 'type'> & { autocomplete?: boolean };
export function stringOption(options: StringOptionDefinition): ApplicationCommandOptionsString {
const def = options as ApplicationCommandOptionsString;
def.type = Constants.ApplicationCommandOptionTypes.STRING;
return def;
}
export type IntegerOptionDefinition = Omit<ApplicationCommandOptionsInteger, 'type'> & { autocomplete?: boolean };
export function integerOption(options: IntegerOptionDefinition): ApplicationCommandOptionsInteger {
const def = options as ApplicationCommandOptionsInteger;
def.type = Constants.ApplicationCommandOptionTypes.INTEGER;
return def;
}
export type BooleanOptionDefinition = Omit<ApplicationCommandOptionsBoolean, 'type'>;
export function booleanOption(options: BooleanOptionDefinition): ApplicationCommandOptionsBoolean {
const def = options as ApplicationCommandOptionsBoolean;
def.type = Constants.ApplicationCommandOptionTypes.BOOLEAN;
return def;
}
export type UserOptionDefinition = Omit<ApplicationCommandOptionsUser, 'type'> & { autocomplete?: boolean };
export function userOption(options: UserOptionDefinition): ApplicationCommandOptionsUser {
const def = options as ApplicationCommandOptionsUser;
def.type = Constants.ApplicationCommandOptionTypes.USER;
return def;
}
export type ChannelOptionDefinition = Omit<ApplicationCommandOptions, 'type'> & { autocomplete?: boolean };
export function channelOption(options: ChannelOptionDefinition): ApplicationCommandOptions {
const def = options as ApplicationCommandOptions;
def.type = Constants.ApplicationCommandOptionTypes.CHANNEL;
return def;
}
export type RoleOptionDefinition = Omit<ApplicationCommandOptionsRole, 'type'> & { autocomplete?: boolean };
export function roleOption(options: RoleOptionDefinition): ApplicationCommandOptionsRole {
const def = options as ApplicationCommandOptionsRole;
def.type = Constants.ApplicationCommandOptionTypes.ROLE;
return def;
}
export type MentionableOptionDefinition = Omit<ApplicationCommandOptionsMentionable, 'type'>;
export function mentionableOption(options: MentionableOptionDefinition): ApplicationCommandOptionsMentionable {
const def = options as ApplicationCommandOptionsMentionable;
def.type = Constants.ApplicationCommandOptionTypes.MENTIONABLE;
return def;
}
export type NumberOptionDefinition = Omit<ApplicationCommandOptionsNumber, 'type'> & { autocomplete?: boolean };
export function numberOption(options: NumberOptionDefinition): ApplicationCommandOptionsNumber {
const def = options as ApplicationCommandOptionsNumber;
def.type = Constants.ApplicationCommandOptionTypes.NUMBER;
return def;
}
export type AttachmentOptionDefinition = Omit<ApplicationCommandOptions, 'type'>;
export function attachmentOption(options: AttachmentOptionDefinition): ApplicationCommandOptions {
const def = options as ApplicationCommandOptions;
def.type = Constants.ApplicationCommandOptionTypes.ATTACHMENT;
return def;
}
export type SubCommandOptionDefinition = Omit<ApplicationCommandOptionsSubCommand, 'type'>;
export function subCommandOption(options: SubCommandOptionDefinition): ApplicationCommandOptionsSubCommand {
const def = options as ApplicationCommandOptionsSubCommand;
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND;
return def;
}
export type SubCommandGroupOptionDefinition = Omit<ApplicationCommandOptionsSubCommandGroup, 'type'>;
export function subCommandGroupOption(options: SubCommandGroupOptionDefinition): ApplicationCommandOptionsSubCommandGroup {
const def = options as ApplicationCommandOptionsSubCommandGroup;
def.type = Constants.ApplicationCommandOptionTypes.SUB_COMMAND_GROUP;
return def;
}

View File

@@ -1,11 +1,11 @@
import type { ApplicationCommandStructure, Client } from '@projectdysnomia/dysnomia';
export async function registerCommands(client: Client, commands: ApplicationCommandStructure[]) {
if (!client) throw new Error('Client not initialized');
if (!(await client.getCommands()).length || process.env.RESET_COMMANDS === 'true' || process.env.NODE_ENV === 'development') {
console.debug('Registering commands...');
const response = await client.bulkEditCommands(commands);
console.debug(`Registered ${response.length} commands.`);
}
return commands;
}
import type { ApplicationCommandStructure, Client } from '@projectdysnomia/dysnomia';
export async function registerCommands(client: Client, commands: ApplicationCommandStructure[]) {
if (!client) throw new Error('Client not initialized');
if (!(await client.getCommands()).length || process.env.RESET_COMMANDS === 'true' || process.env.NODE_ENV === 'development') {
console.debug('Registering commands...');
const response = await client.bulkEditCommands(commands);
console.debug(`Registered ${response.length} commands.`);
}
return commands;
}

View File

@@ -1 +1 @@
export * from './text';
export * from './text';

View File

@@ -1,2 +1,2 @@
export const WHITE_SPACE = ' '; // non-breaking space
export const BREAKING_WHITE_SPACE = '\u200B';
export const WHITE_SPACE = ' '; // non-breaking space
export const BREAKING_WHITE_SPACE = '\u200B';

View File

@@ -1,314 +1,314 @@
import {
Constants,
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';
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
export const createActionRow = (...components: ActionRowItem[]): ActionRow => ({
type: Constants.ComponentTypes.ACTION_ROW,
components,
});
export enum ButtonStyle {
PRIMARY = 1,
SECONDARY = 2,
SUCCESS = 3,
DANGER = 4,
}
export interface ButtonOptions {
style?: ButtonStyle;
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createButton = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
type: Constants.ComponentTypes.BUTTON,
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
label,
custom_id,
...options,
});
export interface URLButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createURLButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.LINK,
label,
url,
...options,
});
export interface PremiumButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createPremiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.PREMIUM,
sku_id,
...options,
});
export interface StringSelectOpts {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // Note: not actually a property of StringSelectMenu, but useful for modals
}
export interface StringSelectOption {
label: string;
value: string;
description?: string;
emoji?: {
name?: string;
id?: string;
animated?: boolean;
};
default?: boolean;
}
export const createStringSelect = (
custom_id: string,
selectOpts: StringSelectOpts,
...options: StringSelectOption[]
): StringSelectMenu => ({
type: Constants.ComponentTypes.STRING_SELECT,
custom_id,
options,
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 {
isParagraph?: boolean;
label?: string;
min_length?: number;
max_length?: number;
required?: boolean;
value?: string;
placeholder?: string;
}
export const createTextInput = (custom_id: string, options?: TextInputOptions): TextInput => ({
type: Constants.ComponentTypes.TEXT_INPUT,
custom_id,
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
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 ?? '',
});
export interface UserSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'user' }>;
}
export const createUserSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
type: Constants.ComponentTypes.USER_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface RoleSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'role' }>;
}
export const createRoleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
type: Constants.ComponentTypes.ROLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface MentionableSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
}
export const createMentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface ChannelSelectOptions {
channel_types?: GuildChannelTypes[];
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'channel' }>;
}
export const createChannelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
type: Constants.ComponentTypes.CHANNEL_SELECT,
custom_id,
channel_types: options?.channel_types ?? [],
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface SectionOptions {
components: Array<TextDisplayComponent>;
accessory: Button | ThumbnailComponent;
}
export const createSection = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
type: Constants.ComponentTypes.SECTION,
accessory,
components,
});
/**
* Creates a text display component where the text will be displayed similar to a message: supports markdown
* @param content The text content to display.
* @returns The created text display component.
*/
export const createTextDisplay = (content: string) => ({
type: Constants.ComponentTypes.TEXT_DISPLAY,
content,
});
export interface ThumbnailOptions {
media: {
url: string; // Supports arbitrary urls and attachment://<filename> references
};
description?: string;
spoiler?: boolean;
}
export const createThumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
type: Constants.ComponentTypes.THUMBNAIL,
media: {
url,
},
description,
spoiler,
});
export interface MediaItem {
url: string; // Supports arbitrary urls and attachment://<filename> references
description?: string;
spoiler?: boolean;
}
export const createMediaGallery = (...items: MediaItem[]): MediaGalleryComponent => ({
type: Constants.ComponentTypes.MEDIA_GALLERY,
items: items.map((item) => ({
type: Constants.ComponentTypes.FILE,
media: { url: item.url },
description: item.description,
spoiler: item.spoiler,
})),
});
export interface FileOptions {
url: string; // Supports only attachment://<filename> references
spoiler?: boolean;
}
export const createFile = (url: string, spoiler?: boolean): FileComponent => ({
type: Constants.ComponentTypes.FILE,
file: {
url,
},
spoiler,
});
export enum Padding {
SMALL = 1,
LARGE = 2,
}
export interface SeparatorOptions {
divider?: boolean;
spacing?: Padding;
}
export const createSeparator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
type: Constants.ComponentTypes.SEPARATOR,
divider,
spacing: spacing ?? Padding.SMALL,
});
export interface ContainerOptions {
accent_color?: number;
spoiler?: boolean;
}
export type ContainerItems =
| ActionRow
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent;
export const createContainer = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
type: Constants.ComponentTypes.CONTAINER,
...options,
components,
});
export const createModalLabel = (label: string, component: TextInput | StringSelectMenu): LabelComponent => ({
type: Constants.ComponentTypes.LABEL,
label,
component,
});
import {
Constants,
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';
export type ActionRowItem = Button | StringSelectMenu | UserSelectMenu | RoleSelectMenu | MentionableSelectMenu | ChannelSelectMenu;
export const createActionRow = (...components: ActionRowItem[]): ActionRow => ({
type: Constants.ComponentTypes.ACTION_ROW,
components,
});
export enum ButtonStyle {
PRIMARY = 1,
SECONDARY = 2,
SUCCESS = 3,
DANGER = 4,
}
export interface ButtonOptions {
style?: ButtonStyle;
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createButton = (label: string, custom_id: string, options?: ButtonOptions): InteractionButton => ({
type: Constants.ComponentTypes.BUTTON,
style: options?.style ?? Constants.ButtonStyles.PRIMARY,
label,
custom_id,
...options,
});
export interface URLButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createURLButton = (label: string, url: string, options?: URLButtonOptions): URLButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.LINK,
label,
url,
...options,
});
export interface PremiumButtonOptions {
emoji?: PartialEmoji;
disabled?: boolean;
}
export const createPremiumButton = (sku_id: string, options?: PremiumButtonOptions): PremiumButton => ({
type: Constants.ComponentTypes.BUTTON,
style: Constants.ButtonStyles.PREMIUM,
sku_id,
...options,
});
export interface StringSelectOpts {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
required?: boolean; // Note: not actually a property of StringSelectMenu, but useful for modals
}
export interface StringSelectOption {
label: string;
value: string;
description?: string;
emoji?: {
name?: string;
id?: string;
animated?: boolean;
};
default?: boolean;
}
export const createStringSelect = (
custom_id: string,
selectOpts: StringSelectOpts,
...options: StringSelectOption[]
): StringSelectMenu => ({
type: Constants.ComponentTypes.STRING_SELECT,
custom_id,
options,
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 {
isParagraph?: boolean;
label?: string;
min_length?: number;
max_length?: number;
required?: boolean;
value?: string;
placeholder?: string;
}
export const createTextInput = (custom_id: string, options?: TextInputOptions): TextInput => ({
type: Constants.ComponentTypes.TEXT_INPUT,
custom_id,
style: options.isParagraph ? Constants.TextInputStyles.PARAGRAPH : Constants.TextInputStyles.SHORT,
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 ?? '',
});
export interface UserSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'user' }>;
}
export const createUserSelect = (custom_id: string, options?: UserSelectOptions): UserSelectMenu => ({
type: Constants.ComponentTypes.USER_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface RoleSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'role' }>;
}
export const createRoleSelect = (custom_id: string, options?: RoleSelectOptions): RoleSelectMenu => ({
type: Constants.ComponentTypes.ROLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface MentionableSelectOptions {
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'user' | 'role' }>;
}
export const createMentionableSelect = (custom_id: string, options?: MentionableSelectOptions): MentionableSelectMenu => ({
type: Constants.ComponentTypes.MENTIONABLE_SELECT,
custom_id,
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface ChannelSelectOptions {
channel_types?: GuildChannelTypes[];
placeholder?: string;
min_values?: number;
max_values?: number;
disabled?: boolean;
default_values?: Array<{ id: string; type: 'channel' }>;
}
export const createChannelSelect = (custom_id: string, options?: ChannelSelectOptions): ChannelSelectMenu => ({
type: Constants.ComponentTypes.CHANNEL_SELECT,
custom_id,
channel_types: options?.channel_types ?? [],
placeholder: options?.placeholder ?? '',
min_values: options?.min_values ?? 1,
max_values: options?.max_values ?? 1,
disabled: options?.disabled ?? false,
default_values: options?.default_values ?? [],
});
export interface SectionOptions {
components: Array<TextDisplayComponent>;
accessory: Button | ThumbnailComponent;
}
export const createSection = (accessory: Button | ThumbnailComponent, ...components: Array<TextDisplayComponent>): SectionComponent => ({
type: Constants.ComponentTypes.SECTION,
accessory,
components,
});
/**
* Creates a text display component where the text will be displayed similar to a message: supports markdown
* @param content The text content to display.
* @returns The created text display component.
*/
export const createTextDisplay = (content: string) => ({
type: Constants.ComponentTypes.TEXT_DISPLAY,
content,
});
export interface ThumbnailOptions {
media: {
url: string; // Supports arbitrary urls and attachment://<filename> references
};
description?: string;
spoiler?: boolean;
}
export const createThumbnail = (url: string, description?: string, spoiler?: boolean): ThumbnailComponent => ({
type: Constants.ComponentTypes.THUMBNAIL,
media: {
url,
},
description,
spoiler,
});
export interface MediaItem {
url: string; // Supports arbitrary urls and attachment://<filename> references
description?: string;
spoiler?: boolean;
}
export const createMediaGallery = (...items: MediaItem[]): MediaGalleryComponent => ({
type: Constants.ComponentTypes.MEDIA_GALLERY,
items: items.map((item) => ({
type: Constants.ComponentTypes.FILE,
media: { url: item.url },
description: item.description,
spoiler: item.spoiler,
})),
});
export interface FileOptions {
url: string; // Supports only attachment://<filename> references
spoiler?: boolean;
}
export const createFile = (url: string, spoiler?: boolean): FileComponent => ({
type: Constants.ComponentTypes.FILE,
file: {
url,
},
spoiler,
});
export enum Padding {
SMALL = 1,
LARGE = 2,
}
export interface SeparatorOptions {
divider?: boolean;
spacing?: Padding;
}
export const createSeparator = (spacing?: Padding, divider?: boolean): SeparatorComponent => ({
type: Constants.ComponentTypes.SEPARATOR,
divider,
spacing: spacing ?? Padding.SMALL,
});
export interface ContainerOptions {
accent_color?: number;
spoiler?: boolean;
}
export type ContainerItems =
| ActionRow
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent;
export const createContainer = (options: ContainerOptions, ...components: ContainerItems[]): ContainerComponent => ({
type: Constants.ComponentTypes.CONTAINER,
...options,
components,
});
export const createModalLabel = (label: string, component: TextInput | StringSelectMenu): LabelComponent => ({
type: Constants.ComponentTypes.LABEL,
label,
component,
});

View File

@@ -1,23 +1,23 @@
import {
Constants,
type ComponentBase,
type ModalSubmitInteractionDataLabelComponent,
type ModalSubmitInteractionDataStringSelectComponent,
type ModalSubmitInteractionDataTextInputComponent,
} from '@projectdysnomia/dysnomia';
export function isModalLabel(component: ComponentBase): component is ModalSubmitInteractionDataLabelComponent {
return component.type === Constants.ComponentTypes.LABEL;
}
export function isModalTextInput(component: ComponentBase): component is ModalSubmitInteractionDataTextInputComponent {
return component.type === Constants.ComponentTypes.TEXT_INPUT;
}
export function isModalSelect(component: ComponentBase): component is ModalSubmitInteractionDataStringSelectComponent {
return component.type === Constants.ComponentTypes.STRING_SELECT;
}
export function componentHasIdPrefix(component: ComponentBase, prefix: string): boolean {
return (isModalTextInput(component) || isModalSelect(component)) && component.custom_id.startsWith(prefix);
}
import {
Constants,
type ComponentBase,
type ModalSubmitInteractionDataLabelComponent,
type ModalSubmitInteractionDataStringSelectComponent,
type ModalSubmitInteractionDataTextInputComponent,
} from '@projectdysnomia/dysnomia';
export function isModalLabel(component: ComponentBase): component is ModalSubmitInteractionDataLabelComponent {
return component.type === Constants.ComponentTypes.LABEL;
}
export function isModalTextInput(component: ComponentBase): component is ModalSubmitInteractionDataTextInputComponent {
return component.type === Constants.ComponentTypes.TEXT_INPUT;
}
export function isModalSelect(component: ComponentBase): component is ModalSubmitInteractionDataStringSelectComponent {
return component.type === Constants.ComponentTypes.STRING_SELECT;
}
export function componentHasIdPrefix(component: ComponentBase, prefix: string): boolean {
return (isModalTextInput(component) || isModalSelect(component)) && component.custom_id.startsWith(prefix);
}

View File

@@ -1,2 +1,2 @@
export * from './helpers';
export * from './builders';
export * from './helpers';
export * from './builders';

View File

@@ -1,54 +1,54 @@
import { importCommands, initializeCommandHandling, registerCommands } from '@commands';
import { Client } from '@projectdysnomia/dysnomia';
import kv, { asyncKV } from '@star-kitten/util/kv.js';
import type { KVStore } from './kv-store.type.ts';
import type { Cache } from './cache.type.ts';
export interface DiscordBotOptions {
token?: string;
intents?: number[];
commandPattern?: string;
commandBaseDir?: string;
keyStore?: KVStore;
cache?: Cache;
onError?: (error: Error) => void;
onReady?: () => void;
}
export function startDiscordBot({
token = process.env.DISCORD_BOT_TOKEN || '',
intents = [],
commandPattern = '**/*.command.{js,ts}',
commandBaseDir = 'src',
keyStore = asyncKV,
cache = kv,
onError,
onReady,
}: DiscordBotOptions = {}): Client {
const client = new Client(`Bot ${token}`, {
gateway: {
intents,
},
});
client.on('ready', async () => {
console.debug(`Logged in as ${client.user?.username}#${client.user?.discriminator}`);
onReady?.();
const commands = await importCommands(commandPattern, commandBaseDir);
await registerCommands(
client,
Object.values(commands).map((cmd) => cmd.definition),
);
initializeCommandHandling(commands, { client, cache, kv: keyStore });
console.debug('Bot is ready and command handling is initialized.');
});
client.on('error', (error) => {
console.error('An error occurred:', error);
onError?.(error);
});
client.connect().catch(console.error);
return client;
}
import { importCommands, initializeCommandHandling, registerCommands } from '@commands';
import { Client } from '@projectdysnomia/dysnomia';
import kv, { asyncKV } from '@star-kitten/util/kv.js';
import type { KVStore } from './kv-store.type.ts';
import type { Cache } from './cache.type.ts';
export interface DiscordBotOptions {
token?: string;
intents?: number[];
commandPattern?: string;
commandBaseDir?: string;
keyStore?: KVStore;
cache?: Cache;
onError?: (error: Error) => void;
onReady?: () => void;
}
export function startDiscordBot({
token = process.env.DISCORD_BOT_TOKEN || '',
intents = [],
commandPattern = '**/*.command.{js,ts}',
commandBaseDir = 'src',
keyStore = asyncKV,
cache = kv,
onError,
onReady,
}: DiscordBotOptions = {}): Client {
const client = new Client(`Bot ${token}`, {
gateway: {
intents,
},
});
client.on('ready', async () => {
console.debug(`Logged in as ${client.user?.username}#${client.user?.discriminator}`);
onReady?.();
const commands = await importCommands(commandPattern, commandBaseDir);
await registerCommands(
client,
Object.values(commands).map((cmd) => cmd.definition),
);
initializeCommandHandling(commands, { client, cache, kv: keyStore });
console.debug('Bot is ready and command handling is initialized.');
});
client.on('error', (error) => {
console.error('An error occurred:', error);
onError?.(error);
});
client.connect().catch(console.error);
return client;
}

View File

@@ -1,6 +1,6 @@
export interface Cache {
get: <T>(key: string) => T | undefined;
set: <T>(key: string, value: T, ttl?: number | string) => boolean;
del: (key: string | string[]) => number;
has: (key: string) => boolean;
}
export interface Cache {
get: <T>(key: string) => T | undefined;
set: <T>(key: string, value: T, ttl?: number | string) => boolean;
del: (key: string | string[]) => number;
has: (key: string) => boolean;
}

View File

@@ -1,3 +1,3 @@
export * from './bot';
export * from './cache.type';
export * from './kv-store.type.ts';
export * from './bot';
export * from './cache.type';
export * from './kv-store.type.ts';

View File

@@ -1,7 +1,7 @@
export interface KVStore {
get: <T>(key: string) => Promise<T | undefined>;
set: (key: string, value: any) => Promise<boolean>;
delete: (key: string) => Promise<number>;
has: (key: string) => Promise<boolean>;
clear: () => Promise<void>;
}
export interface KVStore {
get: <T>(key: string) => Promise<T | undefined>;
set: (key: string, value: any) => Promise<boolean>;
delete: (key: string) => Promise<number>;
has: (key: string) => Promise<boolean>;
clear: () => Promise<void>;
}

View File

@@ -2,3 +2,5 @@ export * from './locales';
export * from './commands';
export * from './core';
export * from './jsx';
export * from './components';
export * from './pages';

View File

@@ -1,7 +1,7 @@
export function createElement(tag: string, attrs: Record<string, any> = {}, ...children: any[]) {
return {
tag,
attrs,
children,
};
}
export function createElement(tag: string, attrs: Record<string, any> = {}, ...children: any[]) {
return {
tag,
attrs,
children,
};
}

View File

@@ -1,2 +1,2 @@
export * from './parser';
export * from './createElement';
export * from './parser';
export * from './createElement';

View File

@@ -1,10 +1,10 @@
import { describe, it, expect } from 'bun:test';
import { parseJSDFile } from './parser_new';
import path from 'node:path';
describe('parseJSDFile', () => {
it('should parse a JSD file', async () => {
const result = await parseJSDFile(path.join(__dirname, '../../fixtures/jsd/test.tsd'));
expect(result).toEqual(true);
});
});
import { describe, it, expect } from 'bun:test';
import { parseJSDFile } from './parser_new';
import path from 'node:path';
describe('parseJSDFile', () => {
it('should parse a JSD file', async () => {
const result = await parseJSDFile(path.join(__dirname, '../../fixtures/jsd/test.tsd'));
expect(result).toEqual(true);
});
});

View File

@@ -1,97 +1,97 @@
import fs from 'node:fs/promises';
import parse, { type DOMNode } from 'html-dom-parser';
import type { ChildNode } from 'domhandler';
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
export async function parseJSDFile(filename: string) {
const content = (await fs.readFile(filename)).toString();
const matches = JSD_STRING.exec(content);
if (matches) {
let html = matches[1] + '>';
const root = parse(html);
const translated = translate(root[0]);
const str = content.replace(matches[1] + '>', translated);
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
}
return true;
}
interface state {
inInterpolation?: boolean;
children?: string[][];
parent?: Text[];
}
function translate(root: DOMNode | ChildNode | null, state: state = {}): string | null {
if (!root || typeof root !== 'object') return null;
let children = [];
if ('children' in root && Array.isArray(root.children) && root.children.length > 0) {
for (const child of root.children) {
const translated = translate(child, state);
if (translated) {
if (state.inInterpolation && state.parent[state.children.length - 1] === child) {
state.children[state.children.length - 1].push(translated);
} else {
children.push(translated);
}
}
}
}
if ('nodeType' in root && root.nodeType === 3) {
if (root.data.trim() === '') return null;
return parseText(root.data.trim(), state, root);
}
if ('name' in root && root.name) {
let tagName = root.name || 'unknown';
let attrs = 'attribs' in root ? root.attribs : {};
return `StarKitten.createElement("${tagName}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
}
}
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 {
let interpolations = text.match(JSD_INTERPOLATION);
if (!interpolations) {
if (text.match(JSD_START_EXP_INTERPOLATION)) {
state.inInterpolation = true;
state.children = state.children || [[]];
state.parent = state.parent || [];
state.parent.push(parent);
return text.substring(1, text.length - 1);
} else if (text.match(JSD_END_EXP_INTERPOLATION)) {
const combined = state.children?.[state.children.length - 1].join(' ');
state.children?.[state.children.length - 1].splice(0);
state.children?.pop();
state.parent?.pop();
if (state.children.length === 0) {
state.inInterpolation = false;
return combined + ' ' + text.substring(1, text.length - 1);
}
}
return `"${text}"`;
} else {
text = replaceInterpolations(text);
return `"${text}"`;
}
}
function replaceInterpolations(text: string, isOnJSON: boolean = false) {
let interpolations = null;
while ((interpolations = JSD_INTERPOLATION.exec(text))) {
if (isOnJSON) {
text = text.replace(`"{${interpolations[1]}}"`, interpolations[1]);
} else {
text = text.replace(`{${interpolations[1]}}`, `"+ ${interpolations[1]} +"`);
}
}
return text;
}
import fs from 'node:fs/promises';
import parse, { type DOMNode } from 'html-dom-parser';
import type { ChildNode } from 'domhandler';
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
export async function parseJSDFile(filename: string) {
const content = (await fs.readFile(filename)).toString();
const matches = JSD_STRING.exec(content);
if (matches) {
let html = matches[1] + '>';
const root = parse(html);
const translated = translate(root[0]);
const str = content.replace(matches[1] + '>', translated);
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
}
return true;
}
interface state {
inInterpolation?: boolean;
children?: string[][];
parent?: Text[];
}
function translate(root: DOMNode | ChildNode | null, state: state = {}): string | null {
if (!root || typeof root !== 'object') return null;
let children = [];
if ('children' in root && Array.isArray(root.children) && root.children.length > 0) {
for (const child of root.children) {
const translated = translate(child, state);
if (translated) {
if (state.inInterpolation && state.parent[state.children.length - 1] === child) {
state.children[state.children.length - 1].push(translated);
} else {
children.push(translated);
}
}
}
}
if ('nodeType' in root && root.nodeType === 3) {
if (root.data.trim() === '') return null;
return parseText(root.data.trim(), state, root);
}
if ('name' in root && root.name) {
let tagName = root.name || 'unknown';
let attrs = 'attribs' in root ? root.attribs : {};
return `StarKitten.createElement("${tagName}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
}
}
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 {
let interpolations = text.match(JSD_INTERPOLATION);
if (!interpolations) {
if (text.match(JSD_START_EXP_INTERPOLATION)) {
state.inInterpolation = true;
state.children = state.children || [[]];
state.parent = state.parent || [];
state.parent.push(parent);
return text.substring(1, text.length - 1);
} else if (text.match(JSD_END_EXP_INTERPOLATION)) {
const combined = state.children?.[state.children.length - 1].join(' ');
state.children?.[state.children.length - 1].splice(0);
state.children?.pop();
state.parent?.pop();
if (state.children.length === 0) {
state.inInterpolation = false;
return combined + ' ' + text.substring(1, text.length - 1);
}
}
return `"${text}"`;
} else {
text = replaceInterpolations(text);
return `"${text}"`;
}
}
function replaceInterpolations(text: string, isOnJSON: boolean = false) {
let interpolations = null;
while ((interpolations = JSD_INTERPOLATION.exec(text))) {
if (isOnJSON) {
text = text.replace(`"{${interpolations[1]}}"`, interpolations[1]);
} else {
text = text.replace(`{${interpolations[1]}}`, `"+ ${interpolations[1]} +"`);
}
}
return text;
}

View File

@@ -1,101 +1,101 @@
import fs from 'node:fs/promises';
import * as acorn from 'acorn';
import jsx from 'acorn-jsx';
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
const parser = acorn.Parser.extend(jsx());
export async function parseJSDFile(filename: string) {
const content = (await fs.readFile(filename)).toString();
const matches = JSD_STRING.exec(content);
if (matches) {
const jsxc = matches[1] + '>';
const ast = parser.parse(jsxc, { ecmaVersion: 2020, sourceType: 'module' });
const translated = traverseJSX((ast.body[0] as any).expression);
const str = content.replace(matches[1] + '>', translated);
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
}
return true;
}
function traverseJSX(node: any): string {
if (node.type === 'JSXElement') {
const tag = node.openingElement.name.name;
const attrs: Record<string, any> = {};
for (const attr of node.openingElement.attributes) {
if (attr.type === 'JSXAttribute') {
const name = attr.name.name;
const value = attr.value;
if (value.type === 'Literal') {
attrs[name] = value.value;
} else if (value.type === 'JSXExpressionContainer') {
attrs[name] = `{${generateCode(value.expression)}}`;
} else if (value) {
attrs[name] = value.raw;
}
}
}
const children = [];
for (const child of node.children) {
const translated = traverseJSX(child);
if (translated) {
children.push(translated);
}
}
return `StarKitten.createElement("${tag}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
} else if (node.type === 'JSXExpressionContainer') {
const expr = generateCode(node.expression);
if (node.expression.type === 'TemplateLiteral' || (node.expression.type === 'Literal' && typeof node.expression.value === 'string')) {
return `""+ ${expr} +""`;
} else {
return expr;
}
} else if (node.type === 'JSXText') {
const text = node.value.trim();
if (text) {
return `"${text}"`;
}
}
return '';
}
function generateCode(node: any): string {
if (node.type === 'JSXElement') {
return traverseJSX(node);
} else if (node.type === 'Identifier') {
return node.name;
} else if (node.type === 'Literal') {
return JSON.stringify(node.value);
} else if (node.type === 'TemplateLiteral') {
const quasis = node.quasis.map((q: any) => q.value.raw);
const expressions = node.expressions.map((e: any) => generateCode(e));
let result = quasis[0];
for (let i = 0; i < expressions.length; i++) {
result += '${' + expressions[i] + '}' + quasis[i + 1];
}
return '`' + result + '`';
} else if (node.type === 'MemberExpression') {
const op = node.optional ? '?.' : '.';
return generateCode(node.object) + op + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
} else if (node.type === 'OptionalMemberExpression') {
return generateCode(node.object) + '?.' + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
} else if (node.type === 'CallExpression') {
return generateCode(node.callee) + '(' + node.arguments.map((a: any) => generateCode(a)).join(', ') + ')';
} else if (node.type === 'BinaryExpression') {
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
} else if (node.type === 'ConditionalExpression') {
return generateCode(node.test) + ' ? ' + generateCode(node.consequent) + ' : ' + generateCode(node.alternate);
} else if (node.type === 'LogicalExpression') {
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
} else if (node.type === 'UnaryExpression') {
return node.operator + generateCode(node.argument);
} else if (node.type === 'ObjectExpression') {
return '{' + node.properties.map((p: any) => generateCode(p.key) + ': ' + generateCode(p.value)).join(', ') + '}';
} else if (node.type === 'ArrayExpression') {
return '[' + node.elements.map((e: any) => generateCode(e)).join(', ') + ']';
} else {
return node.raw || node.name || 'unknown';
}
}
import fs from 'node:fs/promises';
import * as acorn from 'acorn';
import jsx from 'acorn-jsx';
const JSD_STRING = /\(\s*(<.*)>\s*\)/gs;
const parser = acorn.Parser.extend(jsx());
export async function parseJSDFile(filename: string) {
const content = (await fs.readFile(filename)).toString();
const matches = JSD_STRING.exec(content);
if (matches) {
const jsxc = matches[1] + '>';
const ast = parser.parse(jsxc, { ecmaVersion: 2020, sourceType: 'module' });
const translated = traverseJSX((ast.body[0] as any).expression);
const str = content.replace(matches[1] + '>', translated);
await fs.writeFile(filename.replace('.tsd', '.ts'), str);
}
return true;
}
function traverseJSX(node: any): string {
if (node.type === 'JSXElement') {
const tag = node.openingElement.name.name;
const attrs: Record<string, any> = {};
for (const attr of node.openingElement.attributes) {
if (attr.type === 'JSXAttribute') {
const name = attr.name.name;
const value = attr.value;
if (value.type === 'Literal') {
attrs[name] = value.value;
} else if (value.type === 'JSXExpressionContainer') {
attrs[name] = `{${generateCode(value.expression)}}`;
} else if (value) {
attrs[name] = value.raw;
}
}
}
const children = [];
for (const child of node.children) {
const translated = traverseJSX(child);
if (translated) {
children.push(translated);
}
}
return `StarKitten.createElement("${tag}", ${JSON.stringify(attrs)}${children.length > 0 ? ', ' + children.join(', ') : ''})`;
} else if (node.type === 'JSXExpressionContainer') {
const expr = generateCode(node.expression);
if (node.expression.type === 'TemplateLiteral' || (node.expression.type === 'Literal' && typeof node.expression.value === 'string')) {
return `""+ ${expr} +""`;
} else {
return expr;
}
} else if (node.type === 'JSXText') {
const text = node.value.trim();
if (text) {
return `"${text}"`;
}
}
return '';
}
function generateCode(node: any): string {
if (node.type === 'JSXElement') {
return traverseJSX(node);
} else if (node.type === 'Identifier') {
return node.name;
} else if (node.type === 'Literal') {
return JSON.stringify(node.value);
} else if (node.type === 'TemplateLiteral') {
const quasis = node.quasis.map((q: any) => q.value.raw);
const expressions = node.expressions.map((e: any) => generateCode(e));
let result = quasis[0];
for (let i = 0; i < expressions.length; i++) {
result += '${' + expressions[i] + '}' + quasis[i + 1];
}
return '`' + result + '`';
} else if (node.type === 'MemberExpression') {
const op = node.optional ? '?.' : '.';
return generateCode(node.object) + op + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
} else if (node.type === 'OptionalMemberExpression') {
return generateCode(node.object) + '?.' + (node.computed ? '[' + generateCode(node.property) + ']' : generateCode(node.property));
} else if (node.type === 'CallExpression') {
return generateCode(node.callee) + '(' + node.arguments.map((a: any) => generateCode(a)).join(', ') + ')';
} else if (node.type === 'BinaryExpression') {
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
} else if (node.type === 'ConditionalExpression') {
return generateCode(node.test) + ' ? ' + generateCode(node.consequent) + ' : ' + generateCode(node.alternate);
} else if (node.type === 'LogicalExpression') {
return generateCode(node.left) + ' ' + node.operator + ' ' + generateCode(node.right);
} else if (node.type === 'UnaryExpression') {
return node.operator + generateCode(node.argument);
} else if (node.type === 'ObjectExpression') {
return '{' + node.properties.map((p: any) => generateCode(p.key) + ': ' + generateCode(p.value)).join(', ') + '}';
} else if (node.type === 'ArrayExpression') {
return '[' + node.elements.map((e: any) => generateCode(e)).join(', ') + ']';
} else {
return node.raw || node.name || 'unknown';
}
}

View File

@@ -1,5 +1,5 @@
import { createActionRow } from '@components';
export function ActionRow(props: { children: any | any[] }) {
return createActionRow(...(Array.isArray(props.children) ? props.children : [props.children]));
}
import { createActionRow } from '@components';
export function ActionRow(props: { children: any | any[] }) {
return createActionRow(...(Array.isArray(props.children) ? props.children : [props.children]));
}

View File

@@ -1,6 +1,6 @@
import { createButton, type ButtonStyle } from '@components';
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
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 });
}
import { createButton, type ButtonStyle } from '@components';
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
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 });
}

View File

@@ -1,8 +1,8 @@
import { createContainer } from '@components';
export function Container(props: { accent?: number; spoiler?: boolean; children: any | any[] }) {
return createContainer(
{ accent_color: props.accent, spoiler: props.spoiler },
...(Array.isArray(props.children) ? props.children : [props.children]),
);
}
import { createContainer } from '@components';
export function Container(props: { accent?: number; spoiler?: boolean; children: any | any[] }) {
return createContainer(
{ accent_color: props.accent, spoiler: props.spoiler },
...(Array.isArray(props.children) ? props.children : [props.children]),
);
}

View File

@@ -1,4 +1,4 @@
export * from './action-row';
export * from './button';
export * from './container';
export * from './text-display';
export * from './action-row';
export * from './button';
export * from './container';
export * from './text-display';

View File

@@ -1,5 +1,5 @@
import { createTextDisplay } from '@components/builders';
export function TextDisplay(props: { content: string }) {
return createTextDisplay(props.content);
}
import { createTextDisplay } from '@components/builders';
export function TextDisplay(props: { content: string }) {
return createTextDisplay(props.content);
}

View File

@@ -1,3 +1,3 @@
export * from './runtime';
export * from './components';
export * as JSX from './jsx';
export * from './components';
export * from './jsx';
export * from './runtime';

View File

@@ -0,0 +1 @@
export { jsxDEV } from './runtime';

View File

@@ -0,0 +1 @@
export { jsx } from './runtime';

View File

@@ -1,69 +1,69 @@
import {
type ActionRow,
type Button,
type ChannelSelectMenu,
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';
export type Component =
| ActionRow
| Button
| StringSelectMenu
| UserSelectMenu
| RoleSelectMenu
| MentionableSelectMenu
| ChannelSelectMenu
| TextInput
| LabelComponent
| ContainerComponent
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent
| InteractionButton
| URLButton
| PremiumButton
| ThumbnailComponent;
export type Element = Component | Promise<Component>;
export interface ElementClass {
render: any;
}
export interface ElementAttributesProperty {
props: {};
}
export 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: { color?: string; accent?: number; spoiler?: boolean; children: any | any[] };
TextDisplay: { content: string };
}
import {
type ActionRow,
type Button,
type ChannelSelectMenu,
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';
export type Component =
| ActionRow
| Button
| StringSelectMenu
| UserSelectMenu
| RoleSelectMenu
| MentionableSelectMenu
| ChannelSelectMenu
| TextInput
| LabelComponent
| ContainerComponent
| TextDisplayComponent
| SectionComponent
| MediaGalleryComponent
| SeparatorComponent
| FileComponent
| InteractionButton
| URLButton
| PremiumButton
| ThumbnailComponent;
export type Element = Component | Promise<Component>;
export interface ElementClass {
render: any;
}
export interface ElementAttributesProperty {
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 };
}

View File

@@ -1,30 +1,52 @@
export function jsx(type: any, props: Record<string, any>) {
console.log('JSX', type, props);
if (typeof type === 'function') {
return type(props);
}
return {
type,
props,
};
}
export function jsxDEV(
type: any,
props: Record<string, any>,
key: string | number | symbol,
isStaticChildren: boolean,
source: any,
self: any,
) {
console.log('JSX DEV', type, props);
if (typeof type === 'function') {
return type(props);
}
return {
type,
props: { ...props, key },
_source: source,
_self: self,
};
}
import { ActionRow } from './components/action-row';
import { Button } from './components/button';
import { Container } from './components/container';
import { TextDisplay } from './components/text-display';
const intrinsicComponentMap: Record<string, (props: any) => any> = {
actionRow: ActionRow,
button: Button,
container: Container,
textDisplay: TextDisplay,
};
export function jsx(type: any, props: Record<string, any>) {
console.log('JSX', type, props);
if (typeof type === 'function') {
return type(props);
}
if (typeof type === 'string' && intrinsicComponentMap[type]) {
return intrinsicComponentMap[type](props);
}
return {
type,
props,
};
}
export function jsxDEV(
type: any,
props: Record<string, any>,
key: string | number | symbol,
isStaticChildren: boolean,
source: any,
self: any,
) {
console.log('JSX DEV', type, props);
if (typeof type === 'function') {
return type(props);
}
if (typeof type === 'string' && intrinsicComponentMap[type]) {
return intrinsicComponentMap[type](props);
}
return {
type,
props: { ...props, key },
_source: source,
_self: self,
};
}

View File

@@ -1,8 +1,8 @@
import type { Component, IntrinsicElements as StarKittenIntrinsicElements } from './jsx';
declare global {
namespace JSX {
type Element = Component;
interface IntrinsicElements extends StarKittenIntrinsicElements {}
}
}
import type { Component, IntrinsicElements as StarKittenIntrinsicElements } from './jsx';
declare global {
namespace JSX {
type Element = Component;
interface IntrinsicElements extends StarKittenIntrinsicElements {}
}
}

View File

@@ -1,26 +1,26 @@
export type Locales = 'en' | 'ru' | 'de' | 'fr' | 'ja' | 'es' | 'zh' | 'ko';
export const ALL_LOCALES: Locales[] = ['en', 'ru', 'de', 'fr', 'ja', 'es', 'zh', 'ko'];
export const DEFAULT_LOCALE: Locales = 'en';
export const LOCALE_NAMES: { [key in Locales]: string } = {
en: 'English',
ru: 'Русский',
de: 'Deutsch',
fr: 'Français',
ja: '日本語',
es: 'Español',
zh: '中文',
ko: '한국어',
};
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';
}
export type Locales = 'en' | 'ru' | 'de' | 'fr' | 'ja' | 'es' | 'zh' | 'ko';
export const ALL_LOCALES: Locales[] = ['en', 'ru', 'de', 'fr', 'ja', 'es', 'zh', 'ko'];
export const DEFAULT_LOCALE: Locales = 'en';
export const LOCALE_NAMES: { [key in Locales]: string } = {
en: 'English',
ru: 'Русский',
de: 'Deutsch',
fr: 'Français',
ja: '日本語',
es: 'Español',
zh: '中文',
ko: '한국어',
};
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';
}
}

View File

@@ -1,2 +1,2 @@
export * from './pages';
export * from './subroutes';
export * from './pages';
export * from './subroutes';

View File

@@ -1,166 +1,166 @@
import { isAutocomplete, isMessageComponent, isModalSubmit, isPing, type CommandContext } from '@commands';
import {
Constants,
type InteractionContentEdit,
type InteractionModalContent,
type CommandInteraction,
type ComponentInteraction,
type ModalSubmitInteraction,
Interaction,
} from '@projectdysnomia/dysnomia';
export type PagesInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction;
export enum PageType {
MODAL = 'modal',
MESSAGE = 'message',
FOLLOWUP = 'followup',
}
export interface Page<T> {
key: string;
type?: PageType; // defaults to MESSAGE
followUpFlags?: number;
render: (
ctx: PageContext<T>,
) => (InteractionModalContent | InteractionContentEdit) | Promise<InteractionModalContent | InteractionContentEdit>;
}
export interface PagesOptions<T> {
pages: Record<string, Page<T>>;
initialPage?: string;
timeout?: number; // in seconds
ephemeral?: boolean; // whether the initial message should be ephemeral
useEmbeds?: boolean; // will not enable components v2
initialStateData?: T; // initial state to merge with default state
router?: (ctx: PageContext<T>) => string; // function to determine the next page key
}
export interface PageState<T> {
currentPage: string;
timeoutAt: number; // timestamp in ms
lastInteractionAt?: number; // timestamp in ms
messageId?: string;
channelId?: string;
data: T;
}
export interface PageContext<T> {
state: PageState<T>;
custom_id: string; // current interaction custom_id
interaction: PagesInteraction;
goToPage: (pageKey: string) => Promise<InteractionContentEdit>;
}
function createPageContext<T>(interaction: PagesInteraction, options: PagesOptions<T>, state: PageState<T>): PageContext<T> {
return {
state,
interaction,
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;
if (!page) {
throw new Error(`Page with key "${pageKey}" not found`);
}
return page.render(createPageContext(interaction, options, { ...state, currentPage: pageKey })) as Promise<InteractionContentEdit>;
},
};
}
function defaultPageState<T>(options: PagesOptions<T>): PageState<T> {
const timeoutAt = options.timeout ? Date.now() + options.timeout * 1000 : Infinity;
return {
currentPage: options.initialPage ?? options.pages[0].key,
timeoutAt,
lastInteractionAt: Date.now(),
data: options.initialStateData ?? ({} as T),
};
}
function getPageState<T>(options: PagesOptions<T>, cmdCtx: CommandContext & { state: { __pageState?: PageState<T> } }) {
const cmdState = cmdCtx.state;
if ('__pageState' in cmdState && cmdState.__pageState) {
return cmdState.__pageState as PageState<T>;
}
cmdState.__pageState = defaultPageState(options);
return cmdState.__pageState as PageState<T>;
}
function validateOptions<T>(options: PagesOptions<T>) {
const keys = Object.keys(options.pages);
const uniqueKeys = new Set(keys);
if (uniqueKeys.size !== keys.length) {
throw new Error('Duplicate page keys found');
}
}
function getFlags(options: PagesOptions<any>) {
let flags = 0;
if (options.ephemeral) {
flags |= Constants.MessageFlags.EPHEMERAL;
}
if (!options.useEmbeds) {
flags |= Constants.MessageFlags.IS_COMPONENTS_V2;
}
return flags;
}
export async function usePages<T>(options: PagesOptions<T>, interaction: Interaction, cmdCtx: CommandContext) {
if (isAutocomplete(interaction) || isPing(interaction)) {
throw new Error('usePages cannot be used with autocomplete or ping interactions');
}
const pagesInteraction = interaction as PagesInteraction;
validateOptions(options);
const pageState = getPageState(options, cmdCtx);
const pageContext = createPageContext(pagesInteraction, options, pageState);
const pageKey =
options.router ? options.router(pageContext) : (pageContext.custom_id ?? options.initialPage ?? Object.keys(options.pages)[0]);
// if we have subroutes, we only want the main route from the page key
const page = options.pages[pageKey.split(':')[0]] ?? options.pages[0];
pageContext.state.currentPage = page.key;
if (page.type === PageType.MODAL && !isModalSubmit(pagesInteraction)) {
// we don't defer modals and can't respond to a modal with a modal.
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.createModal(content as InteractionModalContent);
}
if (page.type === PageType.FOLLOWUP) {
if (!pageState.messageId) {
throw new Error('Cannot send a followup message before an initial message has been sent');
}
const flags = page.type === PageType.FOLLOWUP ? (page.followUpFlags ?? getFlags(options)) : getFlags(options);
await pagesInteraction.defer(flags);
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.createFollowup({
flags,
...(content as InteractionContentEdit),
});
}
if (pageState.messageId && isMessageComponent(pagesInteraction)) {
await pagesInteraction.deferUpdate();
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.editMessage(pageState.messageId, content as InteractionContentEdit);
}
{
await pagesInteraction.defer(getFlags(options));
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
const message = await pagesInteraction.createFollowup({
flags: getFlags(options),
...(content as InteractionContentEdit),
});
pageState.messageId = message.id;
pageState.channelId = message.channel?.id;
return message;
}
}
function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
return typeof (value as Promise<T>)?.then === 'function';
}
import { isAutocomplete, isMessageComponent, isModalSubmit, isPing, type CommandContext } from '@commands';
import {
Constants,
type InteractionContentEdit,
type InteractionModalContent,
type CommandInteraction,
type ComponentInteraction,
type ModalSubmitInteraction,
Interaction,
} from '@projectdysnomia/dysnomia';
export type PagesInteraction = CommandInteraction | ModalSubmitInteraction | ComponentInteraction;
export enum PageType {
MODAL = 'modal',
MESSAGE = 'message',
FOLLOWUP = 'followup',
}
export interface Page<T> {
key: string;
type?: PageType; // defaults to MESSAGE
followUpFlags?: number;
render: (
ctx: PageContext<T>,
) => (InteractionModalContent | InteractionContentEdit) | Promise<InteractionModalContent | InteractionContentEdit>;
}
export interface PagesOptions<T> {
pages: Record<string, Page<T>>;
initialPage?: string;
timeout?: number; // in seconds
ephemeral?: boolean; // whether the initial message should be ephemeral
useEmbeds?: boolean; // will not enable components v2
initialStateData?: T; // initial state to merge with default state
router?: (ctx: PageContext<T>) => string; // function to determine the next page key
}
export interface PageState<T> {
currentPage: string;
timeoutAt: number; // timestamp in ms
lastInteractionAt?: number; // timestamp in ms
messageId?: string;
channelId?: string;
data: T;
}
export interface PageContext<T> {
state: PageState<T>;
custom_id: string; // current interaction custom_id
interaction: PagesInteraction;
goToPage: (pageKey: string) => Promise<InteractionContentEdit>;
}
function createPageContext<T>(interaction: PagesInteraction, options: PagesOptions<T>, state: PageState<T>): PageContext<T> {
return {
state,
interaction,
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;
if (!page) {
throw new Error(`Page with key "${pageKey}" not found`);
}
return page.render(createPageContext(interaction, options, { ...state, currentPage: pageKey })) as Promise<InteractionContentEdit>;
},
};
}
function defaultPageState<T>(options: PagesOptions<T>): PageState<T> {
const timeoutAt = options.timeout ? Date.now() + options.timeout * 1000 : Infinity;
return {
currentPage: options.initialPage ?? options.pages[0].key,
timeoutAt,
lastInteractionAt: Date.now(),
data: options.initialStateData ?? ({} as T),
};
}
function getPageState<T>(options: PagesOptions<T>, cmdCtx: CommandContext & { state: { __pageState?: PageState<T> } }) {
const cmdState = cmdCtx.state;
if ('__pageState' in cmdState && cmdState.__pageState) {
return cmdState.__pageState as PageState<T>;
}
cmdState.__pageState = defaultPageState(options);
return cmdState.__pageState as PageState<T>;
}
function validateOptions<T>(options: PagesOptions<T>) {
const keys = Object.keys(options.pages);
const uniqueKeys = new Set(keys);
if (uniqueKeys.size !== keys.length) {
throw new Error('Duplicate page keys found');
}
}
function getFlags(options: PagesOptions<any>) {
let flags = 0;
if (options.ephemeral) {
flags |= Constants.MessageFlags.EPHEMERAL;
}
if (!options.useEmbeds) {
flags |= Constants.MessageFlags.IS_COMPONENTS_V2;
}
return flags;
}
export async function usePages<T>(options: PagesOptions<T>, interaction: Interaction, cmdCtx: CommandContext) {
if (isAutocomplete(interaction) || isPing(interaction)) {
throw new Error('usePages cannot be used with autocomplete or ping interactions');
}
const pagesInteraction = interaction as PagesInteraction;
validateOptions(options);
const pageState = getPageState(options, cmdCtx);
const pageContext = createPageContext(pagesInteraction, options, pageState);
const pageKey =
options.router ? options.router(pageContext) : (pageContext.custom_id ?? options.initialPage ?? Object.keys(options.pages)[0]);
// if we have subroutes, we only want the main route from the page key
const page = options.pages[pageKey.split(':')[0]] ?? options.pages[0];
pageContext.state.currentPage = page.key;
if (page.type === PageType.MODAL && !isModalSubmit(pagesInteraction)) {
// we don't defer modals and can't respond to a modal with a modal.
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.createModal(content as InteractionModalContent);
}
if (page.type === PageType.FOLLOWUP) {
if (!pageState.messageId) {
throw new Error('Cannot send a followup message before an initial message has been sent');
}
const flags = page.type === PageType.FOLLOWUP ? (page.followUpFlags ?? getFlags(options)) : getFlags(options);
await pagesInteraction.defer(flags);
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.createFollowup({
flags,
...(content as InteractionContentEdit),
});
}
if (pageState.messageId && isMessageComponent(pagesInteraction)) {
await pagesInteraction.deferUpdate();
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
return await pagesInteraction.editMessage(pageState.messageId, content as InteractionContentEdit);
}
{
await pagesInteraction.defer(getFlags(options));
const cnt = page.render(pageContext);
const content = isPromise(cnt) ? await cnt : cnt;
const message = await pagesInteraction.createFollowup({
flags: getFlags(options),
...(content as InteractionContentEdit),
});
pageState.messageId = message.id;
pageState.channelId = message.channel?.id;
return message;
}
}
function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
return typeof (value as Promise<T>)?.then === 'function';
}

View File

@@ -1,99 +1,99 @@
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
import { createActionRow, createButton, createMediaGallery, type ButtonOptions, type ContainerItems } from '@components';
import type { PageContext } from './pages';
export function getSubrouteKey(prefix: string, subroutes: string[]) {
return `${prefix}:${subroutes.join(':')}`;
}
export function parseSubrouteKey(key: string, expectedPrefix: string, expectedLength: number, defaults: string[] = []) {
const parts = key.split(':');
if (parts[0] !== expectedPrefix) {
throw new Error(`Unexpected prefix: ${parts[0]}`);
}
if (parts.length - 1 < expectedLength && defaults.length) {
// fill in defaults
parts.push(...defaults.slice(parts.length - 1));
}
if (parts.length !== expectedLength + 1) {
throw new Error(`Expected ${expectedLength} subroutes, but got ${parts.length - 1}`);
}
return parts.slice(1);
}
export function renderSubrouteButtons(
currentSubroute: string,
subRoutes: string[],
subrouteIndex: number,
prefix: string,
subroutes: { label: string; value: string; emoji?: PartialEmoji }[],
options?: Partial<ButtonOptions>,
) {
return subroutes
.filter((sr) => sr !== undefined)
.map(({ label, value, emoji }) => {
const routes = [...subRoutes];
routes[subrouteIndex] = currentSubroute == value ? '_' : value;
return createButton(label, getSubrouteKey(prefix, routes), {
...options,
disabled: value === currentSubroute,
emoji,
});
});
}
export interface SubrouteOptions {
label: string;
value: string;
emoji?: PartialEmoji;
}
export function renderSubroutes<T, CType = ContainerItems>(
context: PageContext<T>,
prefix: string,
subroutes: (SubrouteOptions & {
banner?: string;
actionRowPosition?: 'top' | 'bottom';
})[][],
render: (currentSubroute: string, ctx: PageContext<T>) => CType,
btnOptions?: Partial<ButtonOptions>,
defaultSubroutes?: string[], // if not provided, will use the first option of each subroute
): CType[] {
const currentSubroutes = parseSubrouteKey(
context.custom_id,
prefix,
subroutes.length,
defaultSubroutes || subroutes.map((s) => s[0].value),
);
const components = subroutes
.filter((sr) => sr.length > 0)
.map((srOpts, index) => {
const opts = srOpts.filter((sr) => sr !== undefined);
if (opts.length === 0) return undefined;
// find the current subroute, or default to the first
const sri = opts.findIndex((s) => s.value === currentSubroutes[index]);
const current = opts[sri] || opts[0];
const components = [];
const actionRow = createActionRow(...renderSubrouteButtons(current.value, currentSubroutes, index, prefix, opts, btnOptions));
if (current.banner) {
components.push(createMediaGallery({ url: current.banner }));
}
if (!current.actionRowPosition || current.actionRowPosition === 'top') {
components.push(actionRow);
}
components.push(render(current.value, context));
if (current.actionRowPosition === 'bottom') {
components.push(actionRow);
}
return components;
})
.flat()
.filter((c) => c !== undefined);
return components;
}
import type { PartialEmoji } from '@projectdysnomia/dysnomia';
import { createActionRow, createButton, createMediaGallery, type ButtonOptions, type ContainerItems } from '@components';
import type { PageContext } from './pages';
export function getSubrouteKey(prefix: string, subroutes: string[]) {
return `${prefix}:${subroutes.join(':')}`;
}
export function parseSubrouteKey(key: string, expectedPrefix: string, expectedLength: number, defaults: string[] = []) {
const parts = key.split(':');
if (parts[0] !== expectedPrefix) {
throw new Error(`Unexpected prefix: ${parts[0]}`);
}
if (parts.length - 1 < expectedLength && defaults.length) {
// fill in defaults
parts.push(...defaults.slice(parts.length - 1));
}
if (parts.length !== expectedLength + 1) {
throw new Error(`Expected ${expectedLength} subroutes, but got ${parts.length - 1}`);
}
return parts.slice(1);
}
export function renderSubrouteButtons(
currentSubroute: string,
subRoutes: string[],
subrouteIndex: number,
prefix: string,
subroutes: { label: string; value: string; emoji?: PartialEmoji }[],
options?: Partial<ButtonOptions>,
) {
return subroutes
.filter((sr) => sr !== undefined)
.map(({ label, value, emoji }) => {
const routes = [...subRoutes];
routes[subrouteIndex] = currentSubroute == value ? '_' : value;
return createButton(label, getSubrouteKey(prefix, routes), {
...options,
disabled: value === currentSubroute,
emoji,
});
});
}
export interface SubrouteOptions {
label: string;
value: string;
emoji?: PartialEmoji;
}
export function renderSubroutes<T, CType = ContainerItems>(
context: PageContext<T>,
prefix: string,
subroutes: (SubrouteOptions & {
banner?: string;
actionRowPosition?: 'top' | 'bottom';
})[][],
render: (currentSubroute: string, ctx: PageContext<T>) => CType,
btnOptions?: Partial<ButtonOptions>,
defaultSubroutes?: string[], // if not provided, will use the first option of each subroute
): CType[] {
const currentSubroutes = parseSubrouteKey(
context.custom_id,
prefix,
subroutes.length,
defaultSubroutes || subroutes.map((s) => s[0].value),
);
const components = subroutes
.filter((sr) => sr.length > 0)
.map((srOpts, index) => {
const opts = srOpts.filter((sr) => sr !== undefined);
if (opts.length === 0) return undefined;
// find the current subroute, or default to the first
const sri = opts.findIndex((s) => s.value === currentSubroutes[index]);
const current = opts[sri] || opts[0];
const components = [];
const actionRow = createActionRow(...renderSubrouteButtons(current.value, currentSubroutes, index, prefix, opts, btnOptions));
if (current.banner) {
components.push(createMediaGallery({ url: current.banner }));
}
if (!current.actionRowPosition || current.actionRowPosition === 'top') {
components.push(actionRow);
}
components.push(render(current.value, context));
if (current.actionRowPosition === 'bottom') {
components.push(actionRow);
}
return components;
})
.flat()
.filter((c) => c !== undefined);
return components;
}

View File

@@ -1,40 +1,15 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
// Enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"composite": true,
"jsx": "react-jsx",
"jsxImportSource": "@star-kitten/discord",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": false,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
// Paths
"paths": {
"@*": ["./src/*"],
"@types": ["./types/*"]
},
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"typeRoots": ["src/types", "./node_modules/@types"]
"typeRoots": ["src/types", "./node_modules/@types"],
"types": []
},
"include": ["src", "types", "src/jsx/types.d.ts"],
"exclude": ["node_modules", "dist", "build", "**/*.test.ts"]

View File

@@ -9,6 +9,8 @@ export default defineConfig([
'./src/pages/index.ts',
'./src/common/index.ts',
'./src/jsx/index.ts',
'./src/jsx/jsx-runtime.ts',
'./src/jsx/jsx-dev-runtime.ts',
],
platform: 'node',
dts: true,

View File

@@ -1,65 +1,65 @@
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 };
}
}
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 };
}
}

View File

@@ -16,7 +16,8 @@
"@projectdysnomia/dysnomia": "github:projectdysnomia/dysnomia#dev",
"@star-kitten/discord": "workspace:^0.0.0",
"@star-kitten/eve": "workspace:^0.0.0",
"@star-kitten/util": "workspace:^0.0.0"
"@star-kitten/util": "workspace:^0.0.0",
"mkdirp": "^3.0.1"
},
"scripts": {
"dev": "bunx dotenvx run -f .env.development -- bun run --watch src/main.ts",

View File

@@ -1,90 +1,90 @@
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { isModalSubmit } from '@star-kitten/discord/commands';
import { componentHasIdPrefix, isModalLabel, isModalSelect, isModalTextInput } from '@star-kitten/discord/components';
import type { CommandContext, ExecutableInteraction } from '@star-kitten/discord/commands';
import { PageType, usePages } from '@star-kitten/discord/pages';
import { renderAppraisal } from './renderAppraisal';
import { renderAppraisalModal } from './renderAppraisalModal';
const definition: ChatInputApplicationCommandStructure = {
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
name: 'appraise',
nameLocalizations: {
de: 'bewerten',
'es-ES': 'tasar',
fr: 'estimer',
ja: '査定',
ko: '감정',
ru: 'оценить',
'zh-CN': '评估',
},
description: 'Evaluate the worth of your space junk',
descriptionLocalizations: {
de: 'Bewerten Sie den Wert Ihres Weltraumschrotts',
'es-ES': 'Evalúa el valor de tu chatarra espacial',
fr: 'Évaluez la valeur de vos déchets spatiaux',
ja: 'あなたの宇宙のガラクタの価値を評価します',
ko: '우주 쓰레기의 가치를 평가하십시오',
ru: 'Оцените стоимость вашего космического мусора',
'zh-CN': '评估您宇宙垃圾的价值',
},
};
export interface AppraisalState {
appraisal?: Appraisal;
}
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
return await usePages<AppraisalState>(
{
pages: {
appraiseModal: {
key: 'appraiseModal',
type: PageType.MODAL,
render: async () => renderAppraisalModal(interaction),
},
appraisalResult: {
key: 'appraisalResult',
render: async (pageCtx) => {
if (!isModalSubmit(interaction)) {
throw new Error('Expected a modal submit interaction for appraisalResult page');
}
let marketId = 2; // Default to Jita
let items = '';
interaction.data.components.forEach((comp) => {
if (isModalLabel(comp)) {
if (isModalSelect(comp.component) && componentHasIdPrefix(comp.component, `market`)) {
marketId = Number.parseInt(comp.component.values[0]) || marketId;
} else if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, `input`)) {
items = comp.component.value || items;
}
}
});
const appraisal = await appraiseItems(items, marketId);
pageCtx.state.data.appraisal = appraisal;
return renderAppraisal(appraisal, pageCtx, interaction);
},
},
share: {
key: 'share',
type: PageType.FOLLOWUP,
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
render: async (pageCtx) => renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction),
},
},
initialPage: 'appraiseModal',
timeout: 300, // 5 minutes
ephemeral: true,
},
interaction,
ctx,
);
}
export default {
definition,
execute,
};
import { Constants, type ChatInputApplicationCommandStructure } from '@projectdysnomia/dysnomia';
import { appraiseItems, type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { isModalSubmit } from '@star-kitten/discord/commands';
import { componentHasIdPrefix, isModalLabel, isModalSelect, isModalTextInput } from '@star-kitten/discord/components';
import type { CommandContext, ExecutableInteraction } from '@star-kitten/discord/commands';
import { PageType, usePages } from '@star-kitten/discord/pages';
import { renderAppraisal } from './renderAppraisal';
import { renderAppraisalModal } from './renderAppraisalModal';
const definition: ChatInputApplicationCommandStructure = {
type: Constants.ApplicationCommandTypes.CHAT_INPUT,
name: 'appraise',
nameLocalizations: {
de: 'bewerten',
'es-ES': 'tasar',
fr: 'estimer',
ja: '査定',
ko: '감정',
ru: 'оценить',
'zh-CN': '评估',
},
description: 'Evaluate the worth of your space junk',
descriptionLocalizations: {
de: 'Bewerten Sie den Wert Ihres Weltraumschrotts',
'es-ES': 'Evalúa el valor de tu chatarra espacial',
fr: 'Évaluez la valeur de vos déchets spatiaux',
ja: 'あなたの宇宙のガラクタの価値を評価します',
ko: '우주 쓰레기의 가치를 평가하십시오',
ru: 'Оцените стоимость вашего космического мусора',
'zh-CN': '评估您宇宙垃圾的价值',
},
};
export interface AppraisalState {
appraisal?: Appraisal;
}
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
return await usePages<AppraisalState>(
{
pages: {
appraiseModal: {
key: 'appraiseModal',
type: PageType.MODAL,
render: async () => renderAppraisalModal(interaction),
},
appraisalResult: {
key: 'appraisalResult',
render: async (pageCtx) => {
if (!isModalSubmit(interaction)) {
throw new Error('Expected a modal submit interaction for appraisalResult page');
}
let marketId = 2; // Default to Jita
let items = '';
interaction.data.components.forEach((comp) => {
if (isModalLabel(comp)) {
if (isModalSelect(comp.component) && componentHasIdPrefix(comp.component, `market`)) {
marketId = Number.parseInt(comp.component.values[0]) || marketId;
} else if (isModalTextInput(comp.component) && componentHasIdPrefix(comp.component, `input`)) {
items = comp.component.value || items;
}
}
});
const appraisal = await appraiseItems(items, marketId);
pageCtx.state.data.appraisal = appraisal;
return renderAppraisal(appraisal, pageCtx, interaction);
},
},
share: {
key: 'share',
type: PageType.FOLLOWUP,
followUpFlags: Constants.MessageFlags.IS_COMPONENTS_V2,
render: async (pageCtx) => renderAppraisal(pageCtx.state.data.appraisal!, pageCtx, interaction),
},
},
initialPage: 'appraiseModal',
timeout: 300, // 5 minutes
ephemeral: true,
},
interaction,
ctx,
);
}
export default {
definition,
execute,
};

View File

@@ -1,50 +0,0 @@
import type { ExecutableInteraction } from '@star-kitten/discord';
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
import type { PageContext } from '@star-kitten/discord/pages';
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
import type { AppraisalState } from './appraise.command';
export function renderAppraisal(
appraisal: Appraisal,
pageCtx: PageContext<AppraisalState>,
interaction: ExecutableInteraction,
) {
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
const container = createContainer(
{
accent_color: 0x1da57a,
},
createTextDisplay(`
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
-# Volume: ${formatter.format(appraisal.totalPackagedVolume)}
\`\`\`
Buy: Sell: Qty: Item:
${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
\`\`\`
-# https://janice.e-351.com/a/${appraisal.id}\n\n
`),
);
if (pageCtx.state.currentPage !== 'share') {
container.components.push(
createActionRow(
createButton('Share in Channel', 'share', {
disabled: !interaction.channel?.id,
}),
),
);
}
return {
type: 1,
components: [container],
};
}

View File

@@ -0,0 +1,79 @@
import type { ExecutableInteraction } from '@star-kitten/discord';
import * as StarKitten from '@star-kitten/discord';
import { createActionRow, createButton, createContainer, createTextDisplay } from '@star-kitten/discord/components';
import type { PageContext } from '@star-kitten/discord/pages';
import { type Appraisal } from '@star-kitten/eve/third-party/janice.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
import type { AppraisalState } from './appraise.command';
export function renderAppraisal(
appraisal: Appraisal,
pageCtx: PageContext<AppraisalState>,
interaction: ExecutableInteraction,
): StarKitten.Component {
const formatter = new Intl.NumberFormat(interaction.locale || 'en-US', {
maximumFractionDigits: 2,
minimumFractionDigits: 2,
});
// const container = createContainer(
// {
// accent_color: 0x1da57a,
// },
// createTextDisplay(`
// # [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
// ### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
// ### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
// ### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
// -# Volume: ${formatter.format(appraisal.totalPackagedVolume)} m³
// \`\`\`
// Buy: Sell: Qty: Item:
// ${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
// \`\`\`
// -# https://janice.e-351.com/a/${appraisal.id}\n\n
// `),
// );
// if (pageCtx.state.currentPage !== 'share') {
// container.components.push(
// createActionRow(
// createButton('Share in Channel', 'share', {
// disabled: !interaction.channel?.id,
// }),
// ),
// );
// }
// return {
// type: 1,
// components: [container],
// };
return (
<container accent={0x1da57a}>
<textDisplay
content={`
# [Appraisal ${appraisal.id} @ ${appraisal.market.name}](https://janice.e-351.com/a/${appraisal.id})
### Buy: \`${formatter.format(appraisal.effectivePrices.totalBuyPrice)}\` ISK
### Split: \`${formatter.format(appraisal.effectivePrices.totalSplitPrice)}\` ISK
### Sell: \`${formatter.format(appraisal.effectivePrices.totalSellPrice)}\` ISK
-# Volume: ${formatter.format(appraisal.totalPackagedVolume)}
\`\`\`
Buy: Sell: Qty: Item:
${appraisal.items.map((i) => `${formatNumberToShortForm(i.effectivePrices.buyPrice).padEnd(10)}${formatNumberToShortForm(i.effectivePrices.sellPrice).padEnd(10)}${formatNumberToShortForm(i.amount).padEnd(10)}${i.itemType.name}`).join('\n')}
\`\`\`
-# https://janice.e-351.com/a/${appraisal.id}\n\n
`}
/>
{pageCtx.state.currentPage !== 'share' ? (
<actionRow>
<button
customId="share"
label="Share in Channel"
disabled={!interaction.channel?.id}
style={StarKitten.ButtonStyle.PRIMARY}
/>
</actionRow>
) : undefined}
</container>
);
}

View File

@@ -1,37 +1,37 @@
import type { Interaction } from '@projectdysnomia/dysnomia';
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/discord/components';
import { markets } from '@star-kitten/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`,
}),
),
],
};
}
import type { Interaction } from '@projectdysnomia/dysnomia';
import { createModalLabel, createStringSelect, createTextInput } from '@star-kitten/discord/components';
import { markets } from '@star-kitten/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`,
}),
),
],
};
}

View File

@@ -1,140 +1,140 @@
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
import type { SearchState } from '../search.command';
import {
ButtonStyle,
createContainer,
createSection,
createSeparator,
createTextDisplay,
createThumbnail,
Padding,
} from '@star-kitten/discord/components';
import {
getGroup,
getType,
getUnit,
renderUnit,
typeGetAttribute,
typeHasAnyAttribute,
type Type,
} from '@star-kitten/eve/models';
import { attributeOrdering } from '@star-kitten/eve';
import { searchActionRow } from './helpers';
import { toTitleCase } from '@star-kitten/util/text.js';
enum Images {
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
FITTING = 'https://iili.io/KufiFYG.md.webp',
FACILITIES = 'https://iili.io/KufikGt.md.webp',
}
const attributeCategoryMap = {
structure: 'UI/Fitting/Structure',
armor: 'UI/Common/Armor',
shield: 'UI/Common/Shield',
ewar: 'UI/Common/EWarResistances',
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
targeting: 'UI/Fitting/FittingWindow/Targeting',
facilities: 'UI/InfoWindow/SharedFacilities',
fighters: 'UI/InfoWindow/FighterFacilities',
on_death: 'UI/InfoWindow/OnDeath',
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
propulsion: 'UI/Compare/Propulsion',
};
const groupedCategories = [
// defenses
['shield', 'armor', 'structure', 'ewar'],
// fittings
['capacitor', 'targeting', 'propulsion'],
// facilities
['facilities', 'fighters', 'on_death', 'jump_drive'],
];
function getAttributeOrdering(type: Type) {
const group = getGroup(type.group_id);
switch (group.category_id) {
case 11:
return attributeOrdering['11'];
case 87:
return attributeOrdering['87'];
default:
return attributeOrdering.default;
}
}
const bannerMap = {
shield: Images.DEFENSES,
armor: Images.DEFENSES,
structure: Images.DEFENSES,
ewar: Images.DEFENSES,
capacitor: Images.FITTING,
targeting: Images.FITTING,
propulsion: Images.FITTING,
facilities: Images.FACILITIES,
fighters: Images.FACILITIES,
on_death: Images.FACILITIES,
jump_drive: Images.FACILITIES,
};
const page: Page<SearchState> = {
key: 'attributes',
render: (context) => {
const type = getType(context.state.data.type_id);
const ordering = getAttributeOrdering(type);
return {
components: [
createContainer(
{},
createSection(
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
),
...renderSubroutes(
context,
'attributes',
groupedCategories.map((group) =>
group.map((cat) => {
const attrCat = ordering[attributeCategoryMap[cat]];
const attrs = attrCat.groupedCategories
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
: attrCat.normalAttributes;
if (!typeHasAnyAttribute(type, attrs)) {
return undefined;
}
return {
label: toTitleCase(cat.replace('_', ' ')),
value: cat,
banner: bannerMap[cat],
};
}),
),
(currentRoute) => {
const lines: string[] = [];
const attrCat = ordering[attributeCategoryMap[currentRoute]];
const attrs = attrCat.groupedCategories
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
: attrCat.normalAttributes;
attrs.map((attrId) => {
const attr = typeGetAttribute(type, attrId);
if (!attr) return;
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```');
},
{ style: ButtonStyle.SECONDARY },
),
createSeparator(Padding.LARGE),
searchActionRow('attributes'),
),
],
};
},
};
export default page;
import { renderSubroutes, type Page } from '@star-kitten/discord/pages';
import type { SearchState } from '../search.command';
import {
ButtonStyle,
createContainer,
createSection,
createSeparator,
createTextDisplay,
createThumbnail,
Padding,
} from '@star-kitten/discord/components';
import {
getGroup,
getType,
getUnit,
renderUnit,
typeGetAttribute,
typeHasAnyAttribute,
type Type,
} from '@star-kitten/eve/models';
import { attributeOrdering } from '@star-kitten/eve';
import { searchActionRow } from './helpers';
import { toTitleCase } from '@star-kitten/util/text.js';
enum Images {
ATTRIBUTES = 'https://iili.io/KTbaMR2.md.webp',
DEFENSES = 'https://iili.io/KTbSVoX.md.webp',
FITTING = 'https://iili.io/KufiFYG.md.webp',
FACILITIES = 'https://iili.io/KufikGt.md.webp',
}
const attributeCategoryMap = {
structure: 'UI/Fitting/Structure',
armor: 'UI/Common/Armor',
shield: 'UI/Common/Shield',
ewar: 'UI/Common/EWarResistances',
capacitor: 'UI/Fitting/FittingWindow/Capacitor',
targeting: 'UI/Fitting/FittingWindow/Targeting',
facilities: 'UI/InfoWindow/SharedFacilities',
fighters: 'UI/InfoWindow/FighterFacilities',
on_death: 'UI/InfoWindow/OnDeath',
jump_drive: 'UI/InfoWindow/JumpDriveSystems',
propulsion: 'UI/Compare/Propulsion',
};
const groupedCategories = [
// defenses
['shield', 'armor', 'structure', 'ewar'],
// fittings
['capacitor', 'targeting', 'propulsion'],
// facilities
['facilities', 'fighters', 'on_death', 'jump_drive'],
];
function getAttributeOrdering(type: Type) {
const group = getGroup(type.group_id);
switch (group.category_id) {
case 11:
return attributeOrdering['11'];
case 87:
return attributeOrdering['87'];
default:
return attributeOrdering.default;
}
}
const bannerMap = {
shield: Images.DEFENSES,
armor: Images.DEFENSES,
structure: Images.DEFENSES,
ewar: Images.DEFENSES,
capacitor: Images.FITTING,
targeting: Images.FITTING,
propulsion: Images.FITTING,
facilities: Images.FACILITIES,
fighters: Images.FACILITIES,
on_death: Images.FACILITIES,
jump_drive: Images.FACILITIES,
};
const page: Page<SearchState> = {
key: 'attributes',
render: (context) => {
const type = getType(context.state.data.type_id);
const ordering = getAttributeOrdering(type);
return {
components: [
createContainer(
{},
createSection(
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
createTextDisplay(`# [${type.name.en}](https://everef.net/types/${type.type_id})\n## Attributes`),
),
...renderSubroutes(
context,
'attributes',
groupedCategories.map((group) =>
group.map((cat) => {
const attrCat = ordering[attributeCategoryMap[cat]];
const attrs = attrCat.groupedCategories
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
: attrCat.normalAttributes;
if (!typeHasAnyAttribute(type, attrs)) {
return undefined;
}
return {
label: toTitleCase(cat.replace('_', ' ')),
value: cat,
banner: bannerMap[cat],
};
}),
),
(currentRoute) => {
const lines: string[] = [];
const attrCat = ordering[attributeCategoryMap[currentRoute]];
const attrs = attrCat.groupedCategories
? attrCat.groupedCategories.map(([name, id]) => id).concat(attrCat.normalAttributes) || []
: attrCat.normalAttributes;
attrs.map((attrId) => {
const attr = typeGetAttribute(type, attrId);
if (!attr) return;
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```');
},
{ style: ButtonStyle.SECONDARY },
),
createSeparator(Padding.LARGE),
searchActionRow('attributes'),
),
],
};
},
};
export default page;

View File

@@ -1,11 +1,11 @@
import { createActionRow, createButton } from '@star-kitten/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' }),
);
}
import { createActionRow, createButton } from '@star-kitten/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' }),
);
}

View File

@@ -1,89 +1,89 @@
import type { Page } from '@star-kitten/discord/pages';
import type { SearchState } from '../search.command';
import {
createContainer,
createMediaGallery,
createSection,
createTextDisplay,
createThumbnail,
createURLButton,
} from '@star-kitten/discord/components';
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
import { isApplicationCommand } from '@star-kitten/discord';
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
import { searchActionRow } from './helpers';
const page: Page<SearchState> = {
key: 'main',
render: async (context) => {
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
const found = await typeSearch(typeName as string);
if (!found) {
return {
components: [createTextDisplay(`No item found for: ${typeName}`)],
};
}
context.state.data.type_id = found.type_id;
}
const type = getType(context.state.data.type_id);
const skillBonuses = getSkillBonuses(type);
const roleBonuses = getRoleBonuses(type);
const price = await fetchPrice(type.type_id);
return {
components: [
createContainer(
{},
createSection(
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
createTextDisplay(`
# [${type.name.en}](https://everef.net/types/${type.type_id})
${skillBonuses
.map((bonus) => {
return `## Bonus per level of ${bonus.skill.name.en}
${bonus.bonuses
.sort((a, b) => a.importance - b.importance)
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
.join('\n')}`;
})
.join('\n')}
${
roleBonuses.length > 0
? `\n## Role Bonuses
${roleBonuses
.sort((a, b) => a.importance - b.importance)
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
.join('\n')}`
: ''
}
`),
),
createMediaGallery({
url: 'https://iili.io/KTPCFRt.md.webp',
}),
// createSeparator(Padding.LARGE),
createSection(
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
createTextDisplay(
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
),
),
createTextDisplay(`-# Type Id: ${type.type_id}`),
searchActionRow('main'),
),
],
};
},
};
export default page;
import type { Page } from '@star-kitten/discord/pages';
import type { SearchState } from '../search.command';
import {
createContainer,
createMediaGallery,
createSection,
createTextDisplay,
createThumbnail,
createURLButton,
} from '@star-kitten/discord/components';
import { getRoleBonuses, getSkillBonuses, getType } from '@star-kitten/eve/models/type.js';
import { cleanText } from '@star-kitten/eve/utils/markdown.js';
import { typeSearch } from '@star-kitten/eve/utils/typeSearch.js';
import { isApplicationCommand } from '@star-kitten/discord';
import { fetchPrice } from '@star-kitten/eve/third-party/evetycoon.js';
import { formatNumberToShortForm } from '@star-kitten/util/text.js';
import { searchActionRow } from './helpers';
const page: Page<SearchState> = {
key: 'main',
render: async (context) => {
if (!context.state.data.type_id && isApplicationCommand(context.interaction)) {
const typeName = context.interaction.data.options?.find((opt) => opt.name === 'name')?.value;
const found = await typeSearch(typeName as string);
if (!found) {
return {
components: [createTextDisplay(`No item found for: ${typeName}`)],
};
}
context.state.data.type_id = found.type_id;
}
const type = getType(context.state.data.type_id);
const skillBonuses = getSkillBonuses(type);
const roleBonuses = getRoleBonuses(type);
const price = await fetchPrice(type.type_id);
return {
components: [
createContainer(
{},
createSection(
createThumbnail(`https://images.evetech.net/types/${type.type_id}/icon`),
createTextDisplay(`
# [${type.name.en}](https://everef.net/types/${type.type_id})
${skillBonuses
.map((bonus) => {
return `## Bonus per level of ${bonus.skill.name.en}
${bonus.bonuses
.sort((a, b) => a.importance - b.importance)
.map((b) => `${b.bonus}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
.join('\n')}`;
})
.join('\n')}
${
roleBonuses.length > 0
? `\n## Role Bonuses
${roleBonuses
.sort((a, b) => a.importance - b.importance)
.map((b) => `${b.bonus ?? ''}${b.unit?.display_name ?? '-'} ${cleanText(b.bonus_text.en)}`)
.join('\n')}`
: ''
}
`),
),
createMediaGallery({
url: 'https://iili.io/KTPCFRt.md.webp',
}),
// createSeparator(Padding.LARGE),
createSection(
createURLButton('View on EVE Tycoon', `https://evetycoon.com/market/${type.type_id}`),
createTextDisplay(
`## Buy: ${price ? formatNumberToShortForm(price.buyAvgFivePercent) : '--'} ISK
## Sell: ${price ? formatNumberToShortForm(price.sellAvgFivePercent) : '--'} ISK`,
),
),
createTextDisplay(`-# Type Id: ${type.type_id}`),
searchActionRow('main'),
),
],
};
},
};
export default page;

View File

@@ -1,69 +1,69 @@
import {
createChatCommand,
isAutocomplete,
stringOption,
type CommandContext,
type ExecutableInteraction,
} from '@star-kitten/discord';
import { usePages } from '@star-kitten/discord/pages';
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
import main from './pages/main';
import attributes from './pages/attributes';
let now = Date.now();
console.debug('Initializing type search...');
await initializeTypeSearch().catch((e) => {
console.error('Failed to initialize type search', e);
process.exit(1);
});
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
export interface SearchState {
type_id: number;
}
export default createChatCommand(
{
name: 'search',
description: 'Search for a type',
options: [
stringOption({
name: 'name',
description: 'The type name to search for',
autocomplete: true,
required: true,
}),
],
},
execute,
);
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
if (isAutocomplete(interaction)) {
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
if (focusedOption?.name === 'name') {
const value = focusedOption.value as string;
const results = await typeSearchAutoComplete(value);
if (results) {
await interaction.result(results);
} else {
await interaction.result([]);
}
}
return;
}
usePages<SearchState>(
{
pages: {
main,
attributes,
},
initialPage: 'main',
ephemeral: false,
},
interaction,
ctx,
);
}
import {
createChatCommand,
isAutocomplete,
stringOption,
type CommandContext,
type ExecutableInteraction,
} from '@star-kitten/discord';
import { usePages } from '@star-kitten/discord/pages';
import { initializeTypeSearch, typeSearchAutoComplete } from '@star-kitten/eve/utils/typeSearch.js';
import main from './pages/main';
import attributes from './pages/attributes';
let now = Date.now();
console.debug('Initializing type search...');
await initializeTypeSearch().catch((e) => {
console.error('Failed to initialize type search', e);
process.exit(1);
});
console.debug(`Type search initialized. Took ${Date.now() - now}ms`);
export interface SearchState {
type_id: number;
}
export default createChatCommand(
{
name: 'search',
description: 'Search for a type',
options: [
stringOption({
name: 'name',
description: 'The type name to search for',
autocomplete: true,
required: true,
}),
],
},
execute,
);
async function execute(interaction: ExecutableInteraction, ctx: CommandContext) {
if (isAutocomplete(interaction)) {
const focusedOption = interaction.data.options?.find((opt) => opt.focused);
if (focusedOption?.name === 'name') {
const value = focusedOption.value as string;
const results = await typeSearchAutoComplete(value);
if (results) {
await interaction.result(results);
} else {
await interaction.result([]);
}
}
return;
}
usePages<SearchState>(
{
pages: {
main,
attributes,
},
initialPage: 'main',
ephemeral: false,
},
interaction,
ctx,
);
}

View File

@@ -1,3 +1,3 @@
import { startDiscordBot } from '@star-kitten/discord';
startDiscordBot();
import { startDiscordBot } from '@star-kitten/discord';
startDiscordBot();

View File

@@ -0,0 +1,26 @@
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();

View File

@@ -1,38 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
// Enable latest features
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"composite": true,
"jsx": "react-jsx",
"jsxImportSource": "@star-kitten/discord",
"allowJs": true,
// Bundler mode
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
// Best practices
"strict": false,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
// Some stricter flags (disabled by default)
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
// Paths
"paths": {
"@*": ["./src/*"]
},
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"typeRoots": ["src/types", "./node_modules/@types"]
},
"include": ["src", "types"],

View File

@@ -1,30 +0,0 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_DEVELOPMENT="02572da3d4f3a844588a944214c0e142a5a01deaa6551456af146d34b574024416"
# .env.development
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY="02292a330aa041b5f7efc51504e0c208accba67a6877a217ab43cbb59c3c0c3e66"
# .env
DEBUG="encrypted:BC7p62nrs3NV7XdxnBbO1WsHGm8IgDEbOS1RmgORHSqh05vGIv+hmqwau61FamrU/puT4btAsG+iLcSeypCQV5e7bBpr0qu0HQoVyMzunBvrN5ivzNY0Af800lNynsBXzq0cXTY="
PORT="encrypted:BJpY7J2J+0z4LUNnKRr7HzcpETcdnWFuRAOC3hVl2cZyiCBl706vJqv+iY3BgA0mus73t9fwYjGRrPSXSQbcSEBzr+Jquj8Gkvy7loXKkKp4Gz1tqX554txfY0XjrgMO3oHATO8="
NODE_ENV="encrypted:BJun6Kdf/kBSrIYUgw7pfnMwlrTjvUUq/w2yjqn+X5UgaxUxzLsI0JabYlxQCxoDMSEagQYWI5HkRaZvYuXHzyN2aXm6drC2bahg9aWZTVyWYu00FFwIah7l/tuMA/caeO7s5dwkuOgCvlOQ"
LOG_LEVEL="encrypted:BKBvZDS7xkzgg5IqiTc9izmt4om4CjX7t4LA8gMY+0ru0NtVBpkSkchil4PvaRNhktcNjtIzfE6sduRoFw5T1tt88PjtKWIhORyACZk2ZtR3vuO/xecq2q1rIIp5kD1gcp4ujltK"
BASE_URL="encrypted:BMueek9QzKR3k+Foe0xUruZUxwwIXBcrL7B6ksdBNFaF92nHHy2HLLOhOpKNebGfSwb9mBR5kOXb095+hsDEcqAiv5nc5BjWGfk/wkFpRfFcmyEGYlmrxLclbFdQFXIqYrzmN7ae/VV1VlRzNLBipO0smJj4LZY649oV2A=="
EVE_CLIENT_ID="encrypted:BPOAohc2MmP7VvvLybwvcl7XgzYRqWC6IzBkOf+T73kN8YlHYKt611uYNjU27G6hkVRK3DSfgCuxTPzrQBPxvAY6pDbqFG8pVU9cKDmoeHYtiKh7KkHuBN/cEzll6x8hpIwwrY32nzQjxYMvyVO5UgG7OHK1T/jkeya2TW2DSgGn"
EVE_CLIENT_SECRET="encrypted:BE0VYniO3JEFNt1R4iUrGA7W7cSp7gQtG7Y86VQeWnte+idjnqSFmv2lmz83rc7Idvi/VU+ipuY6RL2+49jAb/oUaXGUiwguBAnlFU+ypOVy2Ed29o7yggqiB2+dUuu4xDAsLAfSXErnw4gsDsEPAMqaKhCYz0LHEvJX5ZdwfAcfrpWoLeI/Vm0="
EVE_CALLBACK_URL="encrypted:BIjACAnGtL06X0vkvmydydup9HZDcPA+DAYUAhlH3lsq8GJPD5XxlRwVx02VzZQuATfqm1JwGDyYbw8ceaWD2RLlcjSXPF7MWDpYzG1FExE7FZbFRBBO8XqGH9X8kfxYsuca/Td8KuIPjyS5BNkyM4GQcTlojKa15Hk4GXmpNP6Gyb45XNPCegRJL5aARQ=="
ESI_USER_AGENT="encrypted:BEUaqMbwPNvJeF/d5q5LJ5Owd5wcQ8Jg/BTGn6qns4cwlX/e6QqLLmfp8E8SxQ8Z6h+qDLpZj0HROJIOK9Y2Xb6qjB+hCnjceRMTx1QWpNS6jXQ85TQiZfYzee57QFleau621B77KIuM5DjPUZZ02efAL+2Yk83amrh3vnzvwrnvM5mMGTzc2TbeQwB6MEdjAdvAz65VGX9DnCwbtP3bMKE7og4+sKMUrTYZpCsILug="
DISCORD_APP_ID="encrypted:BM3yHCf9kTxcIQzzmNseT0/xol6ZLYTjZ3m3NKybW1oD2joZ/gTUIg2+mgfaeCqY1CPaSGppxguDPFgthMbWihAdeGuxiITiwLDulCTLcgjBsyT6IlsKUSsE5ZiEZl1A+ikNG1/8rxrF0MsIjVqfw+U+ev8="
DISCORD_APP_SECRET="encrypted:BNVLmKb2ZJq1+iIYKwGaCtcM+hak5NPXLNgJnPzTlpd/5zUTKc8OtYZLhg2oqtOYv8rxf8sbjpXNWB6lZL5J5NUuPcQSfVOMr3U1BN6b9WsfWZ/2Pr4cm8kqqqjJVTF49/DasRQU5VlIXodvCI2XfWrcPFV20NvE8HzsJQz/g5cF"
DISCORD_PUBLIC_KEY="encrypted:BEmxCcAqODfgukOza3EGzsXdrnXv3qVbzlLFYsD4Iba9mQFCaOwChIQNkN/+Ve5NV5sZeefU5sEXZRYbb/hRjJjMRX4NrhEZn2Pg3mI5/FG/+uCD0cWWs4JGzTRwUcBcG8FZ2Mw/kP1ymUqMRkJCYC+XdyYqcP8zNrQ6/aca3HLcqPma7j2/1lsbX4UJ3QcKDg3bsY+107MLLJJ4+TYiwcE="
DISCORD_BOT_TOKEN="encrypted:BCLvoICUVOYz7pkV3f+dufxEZiWcKxQ7E56cCUXtLQjjorW5WsCftgoCdP5NXKSbhHYZBqrTCAHqPp/kLgaVjDXxXu2+61DiCNjB3t/kNIudkfsuzJ0vusKcVzgnQOmTmYnOZ+SYo7hKPrjLi2/Vk8r6K26TATp9t+iuSBMsEs7t9nnnySSYIXyHfXRXbRBg4NUFOmeDRUqBhhix9mgr+MI5SPmQgsXCvA=="
DISCORD_TEST_GUILD_ID="encrypted:BKRJRRvQw2aoBrVoVZpPPYSzCy+2VXLLzBb1zNzUR5510qobPNDbUoIlJdm41moQS0ALG94l8miVLtkOKW6MGZyepq+gE1zSEu7eIJWHB8/eSUnGuNeqSghI4Kxr8kn04Bl4eDb1UgP1Fu+8XSXjVel85Q=="
JANICE_KEY="encrypted:BPq+CJycBbmmoDHNmHHYiQ00PeISDQTqp2IdlLZD52V2wWPOmXnRnqgvoXff64ebUayySaW1sQtvoaMJE3Gt9E/FUquYiRTPUMT5+oW7Xo60IRgAhRW9n2m/YUDIORxR0J0qxKc9cdE75VWJNqLELKKwsriBdpqj0+4yM/Mn6IaU"
PERPLEXITY_API_KEY="encrypted:BHxscbb6WwhFgt5Cp/WzrYGJ6nJvIFeoS3JrTaibPbQ7kiu3C/Zx8klgRhlBF25+HAA8chZHT5MeE99FSD59RqIQTcnKAyfUnxNq+ovoXMV7Nk2Fb/rrdLnvUf8VoeH7L4ZyOVF1wv0r4xm/7Qqc1JN50fME6Qa47KnnQekq/n6o2e5HVI153yJnlCQu/SvcyXCY6VW2"

View File

@@ -1,23 +0,0 @@
#/-------------------[DOTENV_PUBLIC_KEY]--------------------/
#/ public-key encryption for .env files /
#/ [how it works](https://dotenvx.com/encryption) /
#/----------------------------------------------------------/
DOTENV_PUBLIC_KEY_PRODUCTION="02f0469506f6722d8fcc179c199ff159ca32f082000c8e7a1465891adb50a4c031"
# .env.production
DEBUG="encrypted:BKnKyDjoQANl7bGi3568JTnA/7sUBdUVlc8nNznTwxs5Lu/4iMDu1cY0x0iE7b0z+RXEuA/w6bERB5uTmWkCdglEI5S6LhnXZzcV55iLBY8rHO/MIDBF39vn/PBsMiA86gBmtaIn"
PORT="encrypted:BKaGvooBBBWB13491yjfYwYT2zG1MZiYi3+Y6wW8ZhuvMOEsfPuW94rv3cE2LoguALsBFXH9rn3lQJysZLJcYd9AxJoLqxmWHkEpbQ35PYDSvkJ0GsEGlrd74hVbnh57A7Dmqqc="
NODE_ENV="encrypted:BIiY7XZ9vy9stbiNC2u+o4ibruGTtMuRXUJPs3lMxkHKlK6gksg0ddTPbia/qZkZudGjnEhmqYDUPfSWQDdmf8gFsuBgSgYGhR2GCNw7mCCmsDO/wE3ojNnlvuetnVfLeJa7ugvOGjX5QTs="
LOG_LEVEL="encrypted:BEAJviH6nTAR4AdFEoiud4ZHV+dwvURoZys4M9KsYAn5MD+nlNEnzS+9vtE3NPwqzfkpK0Z/46xB+SUIXwhwaJ9Yzgz2WqLK1UXEB6fhQqHXdIqvW2ug/+hUxW9k0ueMu5I9btE="
BASE_URL="encrypted:BJ6YYd2cX31HuTvGnNxLK33KQgzWxU9yRtlwAc79hhbuioHP5lMu7LxCu8NnXfcaEvevWsEt3cG4I7PNqzlBmU1WhTwsdJ4Kqi2cvs6hKwMeVWvtgdI+ymojG/GoglkbHjSc9737dsth2+erI4qbjkafqYuOC9S9"
DISCORD_APP_ID="encrypted:BMqY+6wep/q7bWO9Yc/tJukPHH5H6vvItZpOFK5zaLP92Fx4S+qyZvH/LZKfUBSxZ1d1vbAqo4V+HPxNPyvwXaZyu/qlRf5ZTP/hkt1k9RoNb5UNOMSoD6GSZFS24/JEKuDPzQnvEOb0+prPHEJknvTMXmI="
DISCORD_APP_SECRET="encrypted:BBBWkXA3zi8rCWEiMH66v1hFb6Jqw0Bn4H/6b5qWbVakm5UDckOmgQSjXosJLA1VX0vY+38s5fT1ICPIBv2b0rzpaQ09GpRIwryyTT+VLvMcZfrt1CP4ISC0uzlE2p9qceK9EG+6I7ge4pRkzpxotwnWAg9SCqVcNb04kRwPhUvj"
DISCORD_PUBLIC_KEY="encrypted:BPqsvWPJ7/IBYynqFMHmEcpMBS+T5CXyfnzZU3flouPwcCaKkIc3xkLhtVco7nWdv2v/hw9ulysmVJhT6CiW9r1k0XRqRnv4q1PDDSTLrP0c1cXPg6pFLcOEv7e2CU+Gkj7UEFZz3xgrb6aWkkLRO0yATcA20xbdnOv3rbigOrJCfJwbbAWPQk7yun8oF6O/mGvWW9Blv8if54fp8vBOeUs="
DISCORD_BOT_TOKEN="encrypted:BMfVvYAKvIsMW+dS1X5SZcXfUIxrsunb3q4iXM2ifTlSP8ZlFpA2jxvgYj16jKcexiyVUBhWMSyUC4eS2AuIY/6fKCuA/5JtvqMltzcxPYAi3VEYIz3AgURiMRNy2QsNnqEXxNiakNaPq5Tv4dqVB4z7YeQ16QdvpDWxD4XUQFtG8Q5jZQK/ISj4xJT9cmaxLUJB9XQcbAa8Oseghpk/A1i5JXO05Rx4vQ=="
EVE_CLIENT_ID="encrypted:BCBtASu/2DN+EvLPM//WQjBdfwRscwSC5zKBHLTTTcXMfVur8GtDB6ZcBWstmC4YdiimjPobi1RJ+qdYndu1SM300g6UwOmmO2sNhOpG5nyP0mT2HNgwcJzl+Z7Ad1Vr/iByzYyqkc+uYr9NwhvJDPud+HP11dTjKvw+9Ht9/abA"
EVE_CLIENT_SECRET="encrypted:BMccZ43R9rT33amzo0zfIgLM8hKDCMXLrj+5h0TNLH1RhwrsUxcKgl17MAVqV+8uPBbB171kRRnjKLaQDjjJM27Jv1SV5bn316qrIx35Tkl2Ocd5wjEs7TSAjf8HwzUhiH9F68+IrQ36Vm8w27+RmsaRtvTtiWWVmYvBXw4PFMprTE7SG0bFq3M="
EVE_CALLBACK_URL="encrypted:BDsEZqRGXFzRigkBq50UYj14UvNjRM4Ao3PLSxVTeyc+2Fad1DQa9mfFE9yBnp3l1H5KMQcPJdWf/MxyAa9J0RvXm9l01lbmkgXu+C+HJXWHKJ7/b91NrQqngm2l76jp80WjtmdJ2D5WOUGrIxZatQaqgh8TexQAjVwjkTeQO97PJnbF0FyNQOlu"
ESI_USER_AGENT="encrypted:BJ9Pib5a8/qxfROzBfjlAKr/fEvgepN8o6NCI0l3aiYvFuk5hczaA57TKPMP6P2Ct/Juj47YuU8bqF147y8C556NMiY2HDPbrnenXKdAh4xCerjXhkFqowPvEMVxoeuiyhRM7mPmUSAw7AbYi7AxDtCTw80/6S2/b9/32XBk4eCnSdJmM9kFxwHVFQNK83V0Sr5XEymT4S1kntvqlFsBel/5KxMMfNieqTiT+b5mVyM="
AUTH_DB_PATH="encrypted:BAqssA/4tJHhxv+pQuSXln5reiqtIdaJzIakctW9fs3omlsZr8j7pXHvZEPQAyYnH2u396tXQxZLXSfdj68q5odUEXUDt6kxN70h3ikL/4gbkfpPkW24wd4NlVPA21GZR+rBpvfpZN1u57Lvp8Lm/QvUfTlka4H5"
JANICE_KEY="encrypted:BFEsjnnZNfYFIXvGXKVtko5c8zh5sZze7hjFORfAb4QsHqHh/SqXVKClMCyEa8OMCjNtd8Zmz8LOckaOUYAh09Xi57KM6Eh33CirHipys0rdeURcwSkI9RSXPZOvmOfKZ9yDmhd3iov3AF6b+wwDQ9/rhYZrqh/NWETeHV98Xgv1"
PERPLEXITY_API_KEY="encrypted:BIRn8UX4BgL/4QOCaz2cNZVfiJY3zR/Qclr2UI8FnDsUR8mu+hWi8SVWeaauzLSRwiWU1Ihc3/sWUxi8Jz/Ma1dGcDdPwPO7kiZuN2a1Tl3NKiSMmDlNYszLekhpESoUzVOa/605lcKsTemqC8SvVfq7rOPuC2QP7/7bIGSPGDShIfBjU7dUpvfpDX0/Vf++kLHkNiV1"

View File

@@ -1,8 +0,0 @@
trailingComma: all
tabWidth: 2
useTabs: false
semi: true
singleQuote: true
printWidth: 140
experimentalTernaries: true
quoteProps: consistent

View File

@@ -1,83 +0,0 @@
# Star Kitten Web
Project created with [Brisa](https://github.com/brisa-build/brisa).
## Getting Started
### Installation
```bash
bun install
```
### Link the Library
`star-kitten-lib` has not been published, so link to it locally before running this web project.
```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.
## Environment Variables
Create a .env file in the root directory with the following values:
```yaml
#General
BASE_URL=http://localhost:3000
DEBUG=true
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
# For using Janice's Appraisal API
JANICE_KEY=XXX
# For using Perplexities AI API
PERPLEXITY_API_KEY=XXX
```
### Development
```bash
bun dev
```
### Build
```bash
bun build
```
### Start
```bash
bun start
```

View File

@@ -1,7 +0,0 @@
import type { Configuration } from "brisa";
import tailwindcss from 'brisa-tailwindcss';
export default {
integrations: [tailwindcss()],
} as Configuration;

View File

@@ -1,4 +0,0 @@
export interface IntrinsicCustomElements {
'counter-client': JSX.WebComponentAttributes<typeof import("D:\dev\@star-kitten\packages\eve-web\src\web-components\counter-client.tsx").default>;
}
export type PageRoute = "/" | "/about" | "/auth/error" | "/auth/success";

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,55 +0,0 @@
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __require = import.meta.require;
// src/utils/cookies.ts
function getCookies(headers) {
if (!headers)
return {};
const cookieHeader = headers.get("Cookie");
const cookies = {};
if (cookieHeader === null)
return {};
for (const kv of cookieHeader.split(";")) {
const [cookieKey, ...cookieVal] = kv.split("=");
const key = cookieKey.trim();
cookies[key] = cookieVal.join("=");
}
return cookies;
}
function setCookie(response, key, value, maxAge) {
response.headers.append("Set-Cookie", `${key}=${value}${maxAge ? "; Path=/; Max-Age=" + maxAge : ""}`);
}
function removeCookie(response, key) {
response.headers.append("Set-Cookie", `${key}=""; Path=/; Max-Age=-1;`);
}
// src/api/auth/discordID/[discordID]/characterID/[characterID]/scopes/[scopes].ts
async function GET({ store, route: { params } }) {
const eveauth = store.get("eveauth");
const response = await eveauth.redirect(params["scopes"]);
setCookie(response, "discordID", params["discordID"], 60 * 10);
setCookie(response, "characterID", params["characterID"], 60 * 10);
return response;
}
export {
GET
};
//# debugId=AEE66F1C816CC1AD64756E2164756E21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcdXRpbHNcXGNvb2tpZXMudHMiLCAiLi5cXHNyY1xcYXBpXFxhdXRoXFxkaXNjb3JkSURcXFtkaXNjb3JkSURdXFxjaGFyYWN0ZXJJRFxcW2NoYXJhY3RlcklEXVxcc2NvcGVzXFxbc2NvcGVzXS50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJcbmV4cG9ydCBmdW5jdGlvbiBnZXRDb29raWVzKGhlYWRlcnM6IEhlYWRlcnMpIHtcbiAgaWYgKCFoZWFkZXJzKSByZXR1cm4ge307XG4gIGNvbnN0IGNvb2tpZUhlYWRlciA9IGhlYWRlcnMuZ2V0KFwiQ29va2llXCIpO1xuICBjb25zdCBjb29raWVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cbiAgaWYgKGNvb2tpZUhlYWRlciA9PT0gbnVsbCkgcmV0dXJuIHt9O1xuXG4gIGZvciAoY29uc3Qga3Ygb2YgY29va2llSGVhZGVyLnNwbGl0KFwiO1wiKSkge1xuICAgIGNvbnN0IFtjb29raWVLZXksIC4uLmNvb2tpZVZhbF0gPSBrdi5zcGxpdChcIj1cIik7XG4gICAgY29uc3Qga2V5ID0gY29va2llS2V5LnRyaW0oKTtcbiAgICBjb29raWVzW2tleV0gPSBjb29raWVWYWwuam9pbihcIj1cIik7XG4gIH1cblxuICByZXR1cm4gY29va2llcztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldENvb2tpZShyZXNwb25zZTogUmVzcG9uc2UsIGtleTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCBtYXhBZ2U/OiBudW1iZXIpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PSR7dmFsdWV9JHttYXhBZ2UgPyAnOyBQYXRoPS87IE1heC1BZ2U9JyArIG1heEFnZSA6ICcnfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVtb3ZlQ29va2llKHJlc3BvbnNlOiBSZXNwb25zZSwga2V5OiBzdHJpbmcpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PVwiXCI7IFBhdGg9LzsgTWF4LUFnZT0tMTtgKTtcbn0iLAogICAgImltcG9ydCB0eXBlIHsgRVZFQXV0aCB9IGZyb20gJ0AvbWlkZGxld2FyZSc7XG5pbXBvcnQgeyBzZXRDb29raWUgfSBmcm9tICdAL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICdicmlzYSc7XG5cbi8vIEdFVCAvYXBpL2F1dGgvZGlzY29yZElELzpkaXNjb3JkSUQvY2hhcmFjdGVySUQvOmNoYXJhY3RlcklEL3Njb3Blcy86c2NvcGVzXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gR0VUKHsgc3RvcmUsIHJvdXRlOiB7IHBhcmFtcyB9IH06IFJlcXVlc3RDb250ZXh0KSB7XG4gIC8vIHRoaXMgaXMgdXNlZCB0byBzZXQgdGhlIHNjb3BlcyB0aGF0IHdlcmUgc2VudCwgc28ganVzdCBwYXNzIHRoZW0gYWxvbmcgdG8gYXV0aCBkaXJlY3RseSBcbiAgLy8gd2l0aCB0aGUgcHJvdmlkZWQgc2NvcGVzXG4gIGNvbnN0IGV2ZWF1dGg6IEVWRUF1dGggPSBzdG9yZS5nZXQoJ2V2ZWF1dGgnKTtcbiAgY29uc3QgcmVzcG9uc2UgPSBhd2FpdCBldmVhdXRoLnJlZGlyZWN0KHBhcmFtcyFbJ3Njb3BlcyddIGFzIHN0cmluZyk7XG4gIHNldENvb2tpZShyZXNwb25zZSwgJ2Rpc2NvcmRJRCcsIHBhcmFtcyFbJ2Rpc2NvcmRJRCddIGFzIHN0cmluZywgNjAgKiAxMCAvKiAxMCBtaW4gKi8pO1xuICBzZXRDb29raWUocmVzcG9uc2UsICdjaGFyYWN0ZXJJRCcsIHBhcmFtcyFbJ2NoYXJhY3RlcklEJ10gYXMgc3RyaW5nLCA2MCAqIDEwIC8qIDEwIG1pbiAqLyk7XG4gIHJldHVybiByZXNwb25zZTtcbn0iCiAgXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFDTyxTQUFTLFVBQVUsQ0FBQyxTQUFrQjtBQUFBLEVBQzNDLEtBQUs7QUFBQSxJQUFTLE9BQU8sQ0FBQztBQUFBLEVBQ3RCLE1BQU0sZUFBZSxRQUFRLElBQUksUUFBUTtBQUFBLEVBQ3pDLE1BQU0sVUFBa0MsQ0FBQztBQUFBLEVBRXpDLElBQUksaUJBQWlCO0FBQUEsSUFBTSxPQUFPLENBQUM7QUFBQSxFQUVuQyxXQUFXLE1BQU0sYUFBYSxNQUFNLEdBQUcsR0FBRztBQUFBLElBQ3hDLE9BQU8sY0FBYyxhQUFhLEdBQUcsTUFBTSxHQUFHO0FBQUEsSUFDOUMsTUFBTSxNQUFNLFVBQVUsS0FBSztBQUFBLElBQzNCLFFBQVEsT0FBTyxVQUFVLEtBQUssR0FBRztBQUFBLEVBQ25DO0FBQUEsRUFFQSxPQUFPO0FBQUE7QUFHRixTQUFTLFNBQVMsQ0FBQyxVQUFvQixLQUFhLE9BQWUsUUFBaUI7QUFBQSxFQUN6RixTQUFTLFFBQVEsT0FBTyxjQUFjLEdBQUcsT0FBTyxRQUFRLFNBQVMsdUJBQXVCLFNBQVMsSUFBSTtBQUFBO0FBR2hHLFNBQVMsWUFBWSxDQUFDLFVBQW9CLEtBQWE7QUFBQSxFQUM1RCxTQUFTLFFBQVEsT0FBTyxjQUFjLEdBQUcsNkJBQTZCO0FBQUE7O0FDakJ4RSxlQUFzQixHQUFHLEdBQUcsT0FBTyxTQUFTLFlBQTRCO0FBQUEsRUFHdEUsTUFBTSxVQUFtQixNQUFNLElBQUksU0FBUztBQUFBLEVBQzVDLE1BQU0sV0FBVyxNQUFNLFFBQVEsU0FBUyxPQUFRLFNBQW1CO0FBQUEsRUFDbkUsVUFBVSxVQUFVLGFBQWEsT0FBUSxjQUF3QixLQUFLLEVBQWU7QUFBQSxFQUNyRixVQUFVLFVBQVUsZUFBZSxPQUFRLGdCQUEwQixLQUFLLEVBQWU7QUFBQSxFQUN6RixPQUFPO0FBQUE7IiwKICAiZGVidWdJZCI6ICJBRUU2NkYxQzgxNkNDMUFENjQ3NTZFMjE2NDc1NkUyMSIsCiAgIm5hbWVzIjogW10KfQ==

View File

@@ -1,54 +0,0 @@
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __require = import.meta.require;
// src/utils/cookies.ts
function getCookies(headers) {
if (!headers)
return {};
const cookieHeader = headers.get("Cookie");
const cookies = {};
if (cookieHeader === null)
return {};
for (const kv of cookieHeader.split(";")) {
const [cookieKey, ...cookieVal] = kv.split("=");
const key = cookieKey.trim();
cookies[key] = cookieVal.join("=");
}
return cookies;
}
function setCookie(response, key, value, maxAge) {
response.headers.append("Set-Cookie", `${key}=${value}${maxAge ? "; Path=/; Max-Age=" + maxAge : ""}`);
}
function removeCookie(response, key) {
response.headers.append("Set-Cookie", `${key}=""; Path=/; Max-Age=-1;`);
}
// src/api/auth/discordID/[discordID]/index.ts
async function GET({ store, route: { params } }) {
const eveauth = store.get("eveauth");
const response = await eveauth.redirect("publicData");
setCookie(response, "discordID", params["discordID"], 60 * 10);
return response;
}
export {
GET
};
//# debugId=7AF70B8658B4D3D064756E2164756E21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcdXRpbHNcXGNvb2tpZXMudHMiLCAiLi5cXHNyY1xcYXBpXFxhdXRoXFxkaXNjb3JkSURcXFtkaXNjb3JkSURdXFxpbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJcbmV4cG9ydCBmdW5jdGlvbiBnZXRDb29raWVzKGhlYWRlcnM6IEhlYWRlcnMpIHtcbiAgaWYgKCFoZWFkZXJzKSByZXR1cm4ge307XG4gIGNvbnN0IGNvb2tpZUhlYWRlciA9IGhlYWRlcnMuZ2V0KFwiQ29va2llXCIpO1xuICBjb25zdCBjb29raWVzOiBSZWNvcmQ8c3RyaW5nLCBzdHJpbmc+ID0ge307XG5cbiAgaWYgKGNvb2tpZUhlYWRlciA9PT0gbnVsbCkgcmV0dXJuIHt9O1xuXG4gIGZvciAoY29uc3Qga3Ygb2YgY29va2llSGVhZGVyLnNwbGl0KFwiO1wiKSkge1xuICAgIGNvbnN0IFtjb29raWVLZXksIC4uLmNvb2tpZVZhbF0gPSBrdi5zcGxpdChcIj1cIik7XG4gICAgY29uc3Qga2V5ID0gY29va2llS2V5LnRyaW0oKTtcbiAgICBjb29raWVzW2tleV0gPSBjb29raWVWYWwuam9pbihcIj1cIik7XG4gIH1cblxuICByZXR1cm4gY29va2llcztcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIHNldENvb2tpZShyZXNwb25zZTogUmVzcG9uc2UsIGtleTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nLCBtYXhBZ2U/OiBudW1iZXIpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PSR7dmFsdWV9JHttYXhBZ2UgPyAnOyBQYXRoPS87IE1heC1BZ2U9JyArIG1heEFnZSA6ICcnfWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gcmVtb3ZlQ29va2llKHJlc3BvbnNlOiBSZXNwb25zZSwga2V5OiBzdHJpbmcpIHtcbiAgcmVzcG9uc2UuaGVhZGVycy5hcHBlbmQoJ1NldC1Db29raWUnLCBgJHtrZXl9PVwiXCI7IFBhdGg9LzsgTWF4LUFnZT0tMTtgKTtcbn0iLAogICAgImltcG9ydCB0eXBlIHsgRVZFQXV0aCB9IGZyb20gJ0AvbWlkZGxld2FyZSc7XG5pbXBvcnQgeyBzZXRDb29raWUgfSBmcm9tICdAL3V0aWxzJztcbmltcG9ydCB0eXBlIHsgUmVxdWVzdENvbnRleHQgfSBmcm9tICdicmlzYSc7XG5cbi8vIEdFVCAvYXBpL2F1dGgvZGlzY29yZElELzpkaXNjb3JkSURcbmV4cG9ydCBhc3luYyBmdW5jdGlvbiBHRVQoeyBzdG9yZSwgcm91dGU6IHsgcGFyYW1zIH19OiBSZXF1ZXN0Q29udGV4dCkge1xuICAvLyBjYWxsZWQgd2hlbiBhZGRpbmcgYSBuZXcgY2hhcmFjdGVyLCBzbyBqdXN0IHJlZGlyZWN0IHRvIGF1dGggYW5kIHNldCBjb29raWVzXG4gIGNvbnN0IGV2ZWF1dGg6IEVWRUF1dGggPSAgc3RvcmUuZ2V0KCdldmVhdXRoJyk7XG4gIGNvbnN0IHJlc3BvbnNlID0gYXdhaXQgZXZlYXV0aC5yZWRpcmVjdCgncHVibGljRGF0YScpO1xuICBzZXRDb29raWUocmVzcG9uc2UsICdkaXNjb3JkSUQnLCBwYXJhbXMhWydkaXNjb3JkSUQnXSBhcyBzdHJpbmcsIDYwICogMTAgLyogMTAgbWluICovKTtcbiAgcmV0dXJuIHJlc3BvbnNlO1xufSIKICBdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNPLFNBQVMsVUFBVSxDQUFDLFNBQWtCO0FBQUEsRUFDM0MsS0FBSztBQUFBLElBQVMsT0FBTyxDQUFDO0FBQUEsRUFDdEIsTUFBTSxlQUFlLFFBQVEsSUFBSSxRQUFRO0FBQUEsRUFDekMsTUFBTSxVQUFrQyxDQUFDO0FBQUEsRUFFekMsSUFBSSxpQkFBaUI7QUFBQSxJQUFNLE9BQU8sQ0FBQztBQUFBLEVBRW5DLFdBQVcsTUFBTSxhQUFhLE1BQU0sR0FBRyxHQUFHO0FBQUEsSUFDeEMsT0FBTyxjQUFjLGFBQWEsR0FBRyxNQUFNLEdBQUc7QUFBQSxJQUM5QyxNQUFNLE1BQU0sVUFBVSxLQUFLO0FBQUEsSUFDM0IsUUFBUSxPQUFPLFVBQVUsS0FBSyxHQUFHO0FBQUEsRUFDbkM7QUFBQSxFQUVBLE9BQU87QUFBQTtBQUdGLFNBQVMsU0FBUyxDQUFDLFVBQW9CLEtBQWEsT0FBZSxRQUFpQjtBQUFBLEVBQ3pGLFNBQVMsUUFBUSxPQUFPLGNBQWMsR0FBRyxPQUFPLFFBQVEsU0FBUyx1QkFBdUIsU0FBUyxJQUFJO0FBQUE7QUFHaEcsU0FBUyxZQUFZLENBQUMsVUFBb0IsS0FBYTtBQUFBLEVBQzVELFNBQVMsUUFBUSxPQUFPLGNBQWMsR0FBRyw2QkFBNkI7QUFBQTs7QUNqQnhFLGVBQXNCLEdBQUcsR0FBRyxPQUFPLFNBQVMsWUFBMkI7QUFBQSxFQUVyRSxNQUFNLFVBQW9CLE1BQU0sSUFBSSxTQUFTO0FBQUEsRUFDN0MsTUFBTSxXQUFXLE1BQU0sUUFBUSxTQUFTLFlBQVk7QUFBQSxFQUNwRCxVQUFVLFVBQVUsYUFBYSxPQUFRLGNBQXdCLEtBQUssRUFBZTtBQUFBLEVBQ3JGLE9BQU87QUFBQTsiLAogICJkZWJ1Z0lkIjogIjdBRjcwQjg2NThCNEQzRDA2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9

View File

@@ -1,31 +0,0 @@
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __require = import.meta.require;
// src/api/auth/index.ts
function GET(request) {
const eveauth = request.store.get("eveauth");
return eveauth.redirect();
}
export {
GET
};
//# debugId=2B9DFE71D16A520B64756E2164756E21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXHNyY1xcYXBpXFxhdXRoXFxpbmRleC50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsKICAgICJpbXBvcnQgdHlwZSB7IFJlcXVlc3RDb250ZXh0IH0gZnJvbSAnYnJpc2EnO1xuaW1wb3J0IHR5cGUgeyBFVkVBdXRoIH0gZnJvbSAnQC9taWRkbGV3YXJlJztcblxuLy8gR0VUIC9hcGkvYXV0aC9cbmV4cG9ydCBmdW5jdGlvbiBHRVQocmVxdWVzdDogUmVxdWVzdENvbnRleHQpIHtcbiAgY29uc3QgZXZlYXV0aDogRVZFQXV0aCA9ICByZXF1ZXN0LnN0b3JlLmdldCgnZXZlYXV0aCcpO1xuICByZXR1cm4gZXZlYXV0aC5yZWRpcmVjdCgpO1xufSIKICBdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUlPLFNBQVMsR0FBRyxDQUFDLFNBQXlCO0FBQUEsRUFDM0MsTUFBTSxVQUFvQixRQUFRLE1BQU0sSUFBSSxTQUFTO0FBQUEsRUFDckQsT0FBTyxRQUFRLFNBQVM7QUFBQTsiLAogICJkZWJ1Z0lkIjogIjJCOURGRTcxRDE2QTUyMEI2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9

View File

@@ -1 +0,0 @@
export default ["style-4253422825010316650.css","style-4799912372787954844.css","style-16814944961881043919.css"]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
l$=new Set;u$=(k)=>{let g=(f)=>document.getElementById(f);l$.add(k);for(let f of l$){let j=g(`S:${f}`),h=g(`U:${f}`);if(j&&h)l$.delete(f),j.replaceWith(h.content.cloneNode(!0)),h.remove(),g(`R:${f}`)?.remove()}};

View File

@@ -1 +0,0 @@
\pages\index.js

File diff suppressed because one or more lines are too long

View File

@@ -1,56 +0,0 @@
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __require = import.meta.require;
// node_modules/brisa/jsx-runtime/index.js
var n = Symbol.for("isJSX");
function S(r) {
return i(null, r);
}
function i(r, { children: e, ...o }, s) {
let a = e;
if (Array.isArray(e) && !l(e))
a = e.map((t) => t?.[n] ? t : S({ children: t }));
return Object.assign([r, { ...o, key: s }, a], { [n]: true });
}
function l(r) {
return Array.isArray(r) && ((n in r) || m(r));
}
function m(r) {
return r?.[0] === "HTML" && typeof r[1]?.html === "string";
}
// src/pages/auth/error.tsx
function Error() {
return i(S, {
children: i("div", {
children: [i("h1", {
children: "EVE auth failed, close this tab and try again."
}, undefined, false, undefined, this), i("p", {
children: "If this issue persists reach out to an Administrator."
}, undefined, false, undefined, this)]
}, undefined, true, undefined, this)
}, undefined, false, undefined, this);
}
export {
Error as default
};
//# debugId=F0E3E3449C64DA0864756E2164756E21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXG5vZGVfbW9kdWxlc1xcYnJpc2FcXGpzeC1ydW50aW1lXFxpbmRleC5qcyIsICIuLlxcc3JjXFxwYWdlc1xcYXV0aFxcZXJyb3IudHN4Il0sCiAgInNvdXJjZXNDb250ZW50IjogWwogICAgIi8vIEBidW5cbnZhciBuPVN5bWJvbC5mb3IoXCJpc0pTWFwiKTtmdW5jdGlvbiBTKHIpe3JldHVybiBpKG51bGwscil9ZnVuY3Rpb24gaShyLHtjaGlsZHJlbjplLC4uLm99LHMpe2xldCBhPWU7aWYoQXJyYXkuaXNBcnJheShlKSYmIWwoZSkpYT1lLm1hcCgodCk9PnQ/LltuXT90OlMoe2NoaWxkcmVuOnR9KSk7cmV0dXJuIE9iamVjdC5hc3NpZ24oW3Isey4uLm8sa2V5OnN9LGFdLHtbbl06ITB9KX1mdW5jdGlvbiBsKHIpe3JldHVybiBBcnJheS5pc0FycmF5KHIpJiYoKG4gaW4gcil8fG0ocikpfWZ1bmN0aW9uIG0ocil7cmV0dXJuIHI/LlswXT09PVwiSFRNTFwiJiZ0eXBlb2YgclsxXT8uaHRtbD09PVwic3RyaW5nXCJ9ZXhwb3J0e2kgYXMganN4cyxpIGFzIGpzeERFVixpIGFzIGpzeCxtIGFzIGlzRGFuZ2VySFRNTCxsIGFzIGlzQXJyYXdPZkpTWENvbnRlbnQsUyBhcyBGcmFnbWVudH07XG4iLAogICAgImV4cG9ydCBkZWZhdWx0IGZ1bmN0aW9uIEVycm9yKCkge1xuICByZXR1cm4ganN4REVWXzd4ODFoMGtuKEZyYWdtZW50Xzh2Zzl4M3NxLCB7XG4gICAgY2hpbGRyZW46IGpzeERFVl83eDgxaDBrbihcImRpdlwiLCB7XG4gICAgICBjaGlsZHJlbjogW2pzeERFVl83eDgxaDBrbihcImgxXCIsIHtcbiAgICAgICAgY2hpbGRyZW46IFwiRVZFIGF1dGggZmFpbGVkLCBjbG9zZSB0aGlzIHRhYiBhbmQgdHJ5IGFnYWluLlwiXG4gICAgICB9LCB1bmRlZmluZWQsIGZhbHNlLCB1bmRlZmluZWQsIHRoaXMpLCBqc3hERVZfN3g4MWgwa24oXCJwXCIsIHtcbiAgICAgICAgY2hpbGRyZW46IFwiSWYgdGhpcyBpc3N1ZSBwZXJzaXN0cyByZWFjaCBvdXQgdG8gYW4gQWRtaW5pc3RyYXRvci5cIlxuICAgICAgfSwgdW5kZWZpbmVkLCBmYWxzZSwgdW5kZWZpbmVkLCB0aGlzKV1cbiAgICB9LCB1bmRlZmluZWQsIHRydWUsIHVuZGVmaW5lZCwgdGhpcylcbiAgfSwgdW5kZWZpbmVkLCBmYWxzZSwgdW5kZWZpbmVkLCB0aGlzKTtcbn1cbmltcG9ydCB7IGpzeCBhcyBqc3hfdzc3eWFmczQsIGpzeHMgYXMganN4c19laDZjNzhuaiwganN4REVWIGFzIGpzeERFVl83eDgxaDBrbiwgRnJhZ21lbnQgYXMgRnJhZ21lbnRfOHZnOXgzc3EgfSBmcm9tICdicmlzYS9qc3gtcnVudGltZSc7XG4iCiAgXSwKICAibWFwcGluZ3MiOiAiOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFDQSxJQUFJLElBQUUsT0FBTyxJQUFJLE9BQU87QUFBRSxTQUFTLENBQUMsQ0FBQyxHQUFFO0FBQUEsRUFBQyxPQUFPLEVBQUUsTUFBSyxDQUFDO0FBQUE7QUFBRSxTQUFTLENBQUMsQ0FBQyxLQUFHLFVBQVMsTUFBSyxLQUFHLEdBQUU7QUFBQSxFQUFDLElBQUksSUFBRTtBQUFBLEVBQUUsSUFBRyxNQUFNLFFBQVEsQ0FBQyxNQUFJLEVBQUUsQ0FBQztBQUFBLElBQUUsSUFBRSxFQUFFLElBQUksQ0FBQyxNQUFJLElBQUksS0FBRyxJQUFFLEVBQUUsRUFBQyxVQUFTLEVBQUMsQ0FBQyxDQUFDO0FBQUEsRUFBRSxPQUFPLE9BQU8sT0FBTyxDQUFDLEdBQUUsS0FBSSxHQUFFLEtBQUksRUFBQyxHQUFFLENBQUMsR0FBRSxHQUFFLElBQUcsS0FBRSxDQUFDO0FBQUE7QUFBRSxTQUFTLENBQUMsQ0FBQyxHQUFFO0FBQUEsRUFBQyxPQUFPLE1BQU0sUUFBUSxDQUFDLE9BQUssS0FBSyxNQUFJLEVBQUUsQ0FBQztBQUFBO0FBQUcsU0FBUyxDQUFDLENBQUMsR0FBRTtBQUFBLEVBQUMsT0FBTyxJQUFJLE9BQUssVUFBUSxPQUFPLEVBQUUsSUFBSSxTQUFPO0FBQUE7OztBQ0R6VSxTQUF3QixLQUFLLEdBQUc7QUFBQSxFQUM5QixPQUFPLEVBQWdCLEdBQW1CO0FBQUEsSUFDeEMsVUFBVSxFQUFnQixPQUFPO0FBQUEsTUFDL0IsVUFBVSxDQUFDLEVBQWdCLE1BQU07QUFBQSxRQUMvQixVQUFVO0FBQUEsTUFDWixHQUFHLFdBQVcsT0FBTyxXQUFXLElBQUksR0FBRyxFQUFnQixLQUFLO0FBQUEsUUFDMUQsVUFBVTtBQUFBLE1BQ1osR0FBRyxXQUFXLE9BQU8sV0FBVyxJQUFJLENBQUM7QUFBQSxJQUN2QyxHQUFHLFdBQVcsTUFBTSxXQUFXLElBQUk7QUFBQSxFQUNyQyxHQUFHLFdBQVcsT0FBTyxXQUFXLElBQUk7QUFBQTsiLAogICJkZWJ1Z0lkIjogIkYwRTNFMzQ0OUM2NERBMDg2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9

View File

@@ -1,55 +0,0 @@
// @bun
var __create = Object.create;
var __getProtoOf = Object.getPrototypeOf;
var __defProp = Object.defineProperty;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __toESM = (mod, isNodeMode, target) => {
target = mod != null ? __create(__getProtoOf(mod)) : {};
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
for (let key of __getOwnPropNames(mod))
if (!__hasOwnProp.call(to, key))
__defProp(to, key, {
get: () => mod[key],
enumerable: true
});
return to;
};
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
var __require = import.meta.require;
// node_modules/brisa/jsx-runtime/index.js
var n = Symbol.for("isJSX");
function S(r) {
return i(null, r);
}
function i(r, { children: e, ...o }, s) {
let a = e;
if (Array.isArray(e) && !l(e))
a = e.map((t) => t?.[n] ? t : S({ children: t }));
return Object.assign([r, { ...o, key: s }, a], { [n]: true });
}
function l(r) {
return Array.isArray(r) && ((n in r) || m(r));
}
function m(r) {
return r?.[0] === "HTML" && typeof r[1]?.html === "string";
}
// src/pages/auth/success.tsx
function Success() {
return i(S, {
children: i("div", {
class: "hero",
children: i("h1", {
children: "EVE auth success, you may close this browser tab."
}, undefined, false, undefined, this)
}, undefined, false, undefined, this)
}, undefined, false, undefined, this);
}
export {
Success as default
};
//# debugId=DE19EB2F6E2E7A6A64756E2164756E21
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsiLi5cXG5vZGVfbW9kdWxlc1xcYnJpc2FcXGpzeC1ydW50aW1lXFxpbmRleC5qcyIsICIuLlxcc3JjXFxwYWdlc1xcYXV0aFxcc3VjY2Vzcy50c3giXSwKICAic291cmNlc0NvbnRlbnQiOiBbCiAgICAiLy8gQGJ1blxudmFyIG49U3ltYm9sLmZvcihcImlzSlNYXCIpO2Z1bmN0aW9uIFMocil7cmV0dXJuIGkobnVsbCxyKX1mdW5jdGlvbiBpKHIse2NoaWxkcmVuOmUsLi4ub30scyl7bGV0IGE9ZTtpZihBcnJheS5pc0FycmF5KGUpJiYhbChlKSlhPWUubWFwKCh0KT0+dD8uW25dP3Q6Uyh7Y2hpbGRyZW46dH0pKTtyZXR1cm4gT2JqZWN0LmFzc2lnbihbcix7Li4ubyxrZXk6c30sYV0se1tuXTohMH0pfWZ1bmN0aW9uIGwocil7cmV0dXJuIEFycmF5LmlzQXJyYXkocikmJigobiBpbiByKXx8bShyKSl9ZnVuY3Rpb24gbShyKXtyZXR1cm4gcj8uWzBdPT09XCJIVE1MXCImJnR5cGVvZiByWzFdPy5odG1sPT09XCJzdHJpbmdcIn1leHBvcnR7aSBhcyBqc3hzLGkgYXMganN4REVWLGkgYXMganN4LG0gYXMgaXNEYW5nZXJIVE1MLGwgYXMgaXNBcnJhd09mSlNYQ29udGVudCxTIGFzIEZyYWdtZW50fTtcbiIsCiAgICAiZXhwb3J0IGRlZmF1bHQgZnVuY3Rpb24gU3VjY2VzcygpIHtcbiAgcmV0dXJuIGpzeERFVl83eDgxaDBrbihGcmFnbWVudF84dmc5eDNzcSwge1xuICAgIGNoaWxkcmVuOiBqc3hERVZfN3g4MWgwa24oXCJkaXZcIiwge1xuICAgICAgY2xhc3M6IFwiaGVyb1wiLFxuICAgICAgY2hpbGRyZW46IGpzeERFVl83eDgxaDBrbihcImgxXCIsIHtcbiAgICAgICAgY2hpbGRyZW46IFwiRVZFIGF1dGggc3VjY2VzcywgeW91IG1heSBjbG9zZSB0aGlzIGJyb3dzZXIgdGFiLlwiXG4gICAgICB9LCB1bmRlZmluZWQsIGZhbHNlLCB1bmRlZmluZWQsIHRoaXMpXG4gICAgfSwgdW5kZWZpbmVkLCBmYWxzZSwgdW5kZWZpbmVkLCB0aGlzKVxuICB9LCB1bmRlZmluZWQsIGZhbHNlLCB1bmRlZmluZWQsIHRoaXMpO1xufVxuaW1wb3J0IHsganN4IGFzIGpzeF93Nzd5YWZzNCwganN4cyBhcyBqc3hzX2VoNmM3OG5qLCBqc3hERVYgYXMganN4REVWXzd4ODFoMGtuLCBGcmFnbWVudCBhcyBGcmFnbWVudF84dmc5eDNzcSB9IGZyb20gJ2JyaXNhL2pzeC1ydW50aW1lJztcbiIKICBdLAogICJtYXBwaW5ncyI6ICI7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUNBLElBQUksSUFBRSxPQUFPLElBQUksT0FBTztBQUFFLFNBQVMsQ0FBQyxDQUFDLEdBQUU7QUFBQSxFQUFDLE9BQU8sRUFBRSxNQUFLLENBQUM7QUFBQTtBQUFFLFNBQVMsQ0FBQyxDQUFDLEtBQUcsVUFBUyxNQUFLLEtBQUcsR0FBRTtBQUFBLEVBQUMsSUFBSSxJQUFFO0FBQUEsRUFBRSxJQUFHLE1BQU0sUUFBUSxDQUFDLE1BQUksRUFBRSxDQUFDO0FBQUEsSUFBRSxJQUFFLEVBQUUsSUFBSSxDQUFDLE1BQUksSUFBSSxLQUFHLElBQUUsRUFBRSxFQUFDLFVBQVMsRUFBQyxDQUFDLENBQUM7QUFBQSxFQUFFLE9BQU8sT0FBTyxPQUFPLENBQUMsR0FBRSxLQUFJLEdBQUUsS0FBSSxFQUFDLEdBQUUsQ0FBQyxHQUFFLEdBQUUsSUFBRyxLQUFFLENBQUM7QUFBQTtBQUFFLFNBQVMsQ0FBQyxDQUFDLEdBQUU7QUFBQSxFQUFDLE9BQU8sTUFBTSxRQUFRLENBQUMsT0FBSyxLQUFLLE1BQUksRUFBRSxDQUFDO0FBQUE7QUFBRyxTQUFTLENBQUMsQ0FBQyxHQUFFO0FBQUEsRUFBQyxPQUFPLElBQUksT0FBSyxVQUFRLE9BQU8sRUFBRSxJQUFJLFNBQU87QUFBQTs7O0FDRHpVLFNBQXdCLE9BQU8sR0FBRztBQUFBLEVBQ2hDLE9BQU8sRUFBZ0IsR0FBbUI7QUFBQSxJQUN4QyxVQUFVLEVBQWdCLE9BQU87QUFBQSxNQUMvQixPQUFPO0FBQUEsTUFDUCxVQUFVLEVBQWdCLE1BQU07QUFBQSxRQUM5QixVQUFVO0FBQUEsTUFDWixHQUFHLFdBQVcsT0FBTyxXQUFXLElBQUk7QUFBQSxJQUN0QyxHQUFHLFdBQVcsT0FBTyxXQUFXLElBQUk7QUFBQSxFQUN0QyxHQUFHLFdBQVcsT0FBTyxXQUFXLElBQUk7QUFBQTsiLAogICJkZWJ1Z0lkIjogIkRFMTlFQjJGNkUyRTdBNkE2NDc1NkUyMTY0NzU2RTIxIiwKICAibmFtZXMiOiBbXQp9

File diff suppressed because one or more lines are too long

View File

@@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" viewBox="0 0 200 199" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;">
<g transform="matrix(1,0,0,1,-1.46627e-05,-0.862671)">
<path d="M124.615,158.942C194.326,134.262 190.916,42.893 140.767,0.863C184.318,20.349 206.756,72.952 198.203,118.905C191.512,154.854 164.558,184.522 129.69,194.524C111.34,199.788 91.695,200.819 73.085,196.201C36.436,187.108 7.653,155.954 1.303,118.778C-3.783,88.998 6.075,58.343 29.922,38.996C54.808,18.806 92.107,14.056 119.324,32.517C146.142,50.708 155.776,89.452 135.231,116.111C124.748,129.714 107.413,137.633 90.191,135.012C72.184,132.272 58.17,116.038 60.606,97.494C63.655,121.563 92.134,130.674 109.243,113.883C125.024,98.394 116.512,74.337 100.517,62.603C87.184,52.821 68.389,55.268 55.26,64.22C39.06,75.266 32.739,94.717 36.289,113.574C44.063,154.866 87.626,172.037 124.615,158.942L124.615,158.942Z" style="fill:url(#_Radial1);"/>
</g>
<defs>
<radialGradient id="_Radial1" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(52.186,-99.1373,99.1373,52.186,88.8208,100)"><stop offset="0" style="stop-color:rgb(44,240,204);stop-opacity:1"/><stop offset="0.53" style="stop-color:rgb(44,230,209);stop-opacity:1"/><stop offset="1" style="stop-color:rgb(44,195,228);stop-opacity:1"/></radialGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +0,0 @@
footer {
display: grid;
font-family: "Poppins", sans-serif;
font-size: 27px;
line-height: 1.5;
height: 15vh;
place-items: center;
}

View File

@@ -1 +0,0 @@
/* Navigation Styles */

View File

@@ -1,329 +0,0 @@
{
"lockfileVersion": 1,
"workspaces": {
"": {
"name": "Star Kitten Web",
"dependencies": {
"brisa": "0.2.7",
"brisa-tailwindcss": "0.2.7",
"drizzle-orm": "^0.40.0",
"oslo": "^1.2.1",
},
"devDependencies": {
"@types/bun": "latest",
"daisyui": "^5.0.0",
"drizzle-kit": "^0.30.5",
"typescript": "latest",
},
},
},
"packages": {
"@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
"@drizzle-team/brocli": ["@drizzle-team/brocli@0.10.2", "", {}, "sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w=="],
"@emnapi/core": ["@emnapi/core@0.45.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw=="],
"@emnapi/runtime": ["@emnapi/runtime@0.45.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w=="],
"@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="],
"@esbuild-kit/esm-loader": ["@esbuild-kit/esm-loader@2.6.5", "", { "dependencies": { "@esbuild-kit/core-utils": "^3.3.2", "get-tsconfig": "^4.7.0" } }, "sha512-FxEMIkJKnodyA1OaCUoEvbYRkoZlLZ4d/eXFu9Fh8CbBBgP5EmZxrfTRyN0qpXZ4vOvqnE5YdRdcrmUUXuU+dA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.19.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.19.12", "", { "os": "android", "cpu": "arm" }, "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.19.12", "", { "os": "android", "cpu": "arm64" }, "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.19.12", "", { "os": "android", "cpu": "x64" }, "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.19.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.19.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.19.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.19.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.19.12", "", { "os": "linux", "cpu": "arm" }, "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.19.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.19.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.19.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.19.12", "", { "os": "linux", "cpu": "none" }, "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.19.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.19.12", "", { "os": "linux", "cpu": "x64" }, "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.19.12", "", { "os": "none", "cpu": "x64" }, "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.19.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.19.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.19.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.19.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.19.12", "", { "os": "win32", "cpu": "x64" }, "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA=="],
"@node-rs/argon2": ["@node-rs/argon2@1.7.0", "", { "optionalDependencies": { "@node-rs/argon2-android-arm-eabi": "1.7.0", "@node-rs/argon2-android-arm64": "1.7.0", "@node-rs/argon2-darwin-arm64": "1.7.0", "@node-rs/argon2-darwin-x64": "1.7.0", "@node-rs/argon2-freebsd-x64": "1.7.0", "@node-rs/argon2-linux-arm-gnueabihf": "1.7.0", "@node-rs/argon2-linux-arm64-gnu": "1.7.0", "@node-rs/argon2-linux-arm64-musl": "1.7.0", "@node-rs/argon2-linux-x64-gnu": "1.7.0", "@node-rs/argon2-linux-x64-musl": "1.7.0", "@node-rs/argon2-wasm32-wasi": "1.7.0", "@node-rs/argon2-win32-arm64-msvc": "1.7.0", "@node-rs/argon2-win32-ia32-msvc": "1.7.0", "@node-rs/argon2-win32-x64-msvc": "1.7.0" } }, "sha512-zfULc+/tmcWcxn+nHkbyY8vP3+MpEqKORbszt4UkpqZgBgDAAIYvuDN/zukfTgdmo6tmJKKVfzigZOPk4LlIog=="],
"@node-rs/argon2-android-arm-eabi": ["@node-rs/argon2-android-arm-eabi@1.7.0", "", { "os": "android", "cpu": "arm" }, "sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg=="],
"@node-rs/argon2-android-arm64": ["@node-rs/argon2-android-arm64@1.7.0", "", { "os": "android", "cpu": "arm64" }, "sha512-s9j/G30xKUx8WU50WIhF0fIl1EdhBGq0RQ06lEhZ0Gi0ap8lhqbE2Bn5h3/G2D1k0Dx+yjeVVNmt/xOQIRG38A=="],
"@node-rs/argon2-darwin-arm64": ["@node-rs/argon2-darwin-arm64@1.7.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-ZIz4L6HGOB9U1kW23g+m7anGNuTZ0RuTw0vNp3o+2DWpb8u8rODq6A8tH4JRL79S+Co/Nq608m9uackN2pe0Rw=="],
"@node-rs/argon2-darwin-x64": ["@node-rs/argon2-darwin-x64@1.7.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-5oi/pxqVhODW/pj1+3zElMTn/YukQeywPHHYDbcAW3KsojFjKySfhcJMd1DjKTc+CHQI+4lOxZzSUzK7mI14Hw=="],
"@node-rs/argon2-freebsd-x64": ["@node-rs/argon2-freebsd-x64@1.7.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-Ify08683hA4QVXYoIm5SUWOY5DPIT/CMB0CQT+IdxQAg/F+qp342+lUkeAtD5bvStQuCx/dFO3bnnzoe2clMhA=="],
"@node-rs/argon2-linux-arm-gnueabihf": ["@node-rs/argon2-linux-arm-gnueabihf@1.7.0", "", { "os": "linux", "cpu": "arm" }, "sha512-7DjDZ1h5AUHAtRNjD19RnQatbhL+uuxBASuuXIBu4/w6Dx8n7YPxwTP4MXfsvuRgKuMWiOb/Ub/HJ3kXVCXRkg=="],
"@node-rs/argon2-linux-arm64-gnu": ["@node-rs/argon2-linux-arm64-gnu@1.7.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-nJDoMP4Y3YcqGswE4DvP080w6O24RmnFEDnL0emdI8Nou17kNYBzP2546Nasx9GCyLzRcYQwZOUjrtUuQ+od2g=="],
"@node-rs/argon2-linux-arm64-musl": ["@node-rs/argon2-linux-arm64-musl@1.7.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BKWS8iVconhE3jrb9mj6t1J9vwUqQPpzCbUKxfTGJfc+kNL58F1SXHBoe2cDYGnHrFEHTY0YochzXoAfm4Dm/A=="],
"@node-rs/argon2-linux-x64-gnu": ["@node-rs/argon2-linux-x64-gnu@1.7.0", "", { "os": "linux", "cpu": "x64" }, "sha512-EmgqZOlf4Jurk/szW1iTsVISx25bKksVC5uttJDUloTgsAgIGReCpUUO1R24pBhu9ESJa47iv8NSf3yAfGv6jQ=="],
"@node-rs/argon2-linux-x64-musl": ["@node-rs/argon2-linux-x64-musl@1.7.0", "", { "os": "linux", "cpu": "x64" }, "sha512-/o1efYCYIxjfuoRYyBTi2Iy+1iFfhqHCvvVsnjNSgO1xWiWrX0Rrt/xXW5Zsl7vS2Y+yu8PL8KFWRzZhaVxfKA=="],
"@node-rs/argon2-wasm32-wasi": ["@node-rs/argon2-wasm32-wasi@1.7.0", "", { "dependencies": { "@emnapi/core": "^0.45.0", "@emnapi/runtime": "^0.45.0", "@tybys/wasm-util": "^0.8.1", "memfs-browser": "^3.4.13000" }, "cpu": "none" }, "sha512-Evmk9VcxqnuwQftfAfYEr6YZYSPLzmKUsbFIMep5nTt9PT4XYRFAERj7wNYp+rOcBenF3X4xoB+LhwcOMTNE5w=="],
"@node-rs/argon2-win32-arm64-msvc": ["@node-rs/argon2-win32-arm64-msvc@1.7.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-qgsU7T004COWWpSA0tppDqDxbPLgg8FaU09krIJ7FBl71Sz8SFO40h7fDIjfbTT5w7u6mcaINMQ5bSHu75PCaA=="],
"@node-rs/argon2-win32-ia32-msvc": ["@node-rs/argon2-win32-ia32-msvc@1.7.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-JGafwWYQ/HpZ3XSwP4adQ6W41pRvhcdXvpzIWtKvX+17+xEXAe2nmGWM6s27pVkg1iV2ZtoYLRDkOUoGqZkCcg=="],
"@node-rs/argon2-win32-x64-msvc": ["@node-rs/argon2-win32-x64-msvc@1.7.0", "", { "os": "win32", "cpu": "x64" }, "sha512-9oq4ShyFakw8AG3mRls0AoCpxBFcimYx7+jvXeAf2OqKNO+mSA6eZ9z7KQeVCi0+SOEUYxMGf5UiGiDb9R6+9Q=="],
"@node-rs/bcrypt": ["@node-rs/bcrypt@1.9.0", "", { "optionalDependencies": { "@node-rs/bcrypt-android-arm-eabi": "1.9.0", "@node-rs/bcrypt-android-arm64": "1.9.0", "@node-rs/bcrypt-darwin-arm64": "1.9.0", "@node-rs/bcrypt-darwin-x64": "1.9.0", "@node-rs/bcrypt-freebsd-x64": "1.9.0", "@node-rs/bcrypt-linux-arm-gnueabihf": "1.9.0", "@node-rs/bcrypt-linux-arm64-gnu": "1.9.0", "@node-rs/bcrypt-linux-arm64-musl": "1.9.0", "@node-rs/bcrypt-linux-x64-gnu": "1.9.0", "@node-rs/bcrypt-linux-x64-musl": "1.9.0", "@node-rs/bcrypt-wasm32-wasi": "1.9.0", "@node-rs/bcrypt-win32-arm64-msvc": "1.9.0", "@node-rs/bcrypt-win32-ia32-msvc": "1.9.0", "@node-rs/bcrypt-win32-x64-msvc": "1.9.0" } }, "sha512-u2OlIxW264bFUfvbFqDz9HZKFjwe8FHFtn7T/U8mYjPZ7DWYpbUB+/dkW/QgYfMSfR0ejkyuWaBBe0coW7/7ig=="],
"@node-rs/bcrypt-android-arm-eabi": ["@node-rs/bcrypt-android-arm-eabi@1.9.0", "", { "os": "android", "cpu": "arm" }, "sha512-nOCFISGtnodGHNiLrG0WYLWr81qQzZKYfmwHc7muUeq+KY0sQXyHOwZk9OuNQAWv/lnntmtbwkwT0QNEmOyLvA=="],
"@node-rs/bcrypt-android-arm64": ["@node-rs/bcrypt-android-arm64@1.9.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+ZrIAtigVmjYkqZQTThHVlz0+TG6D+GDHWhVKvR2DifjtqJ0i+mb9gjo++hN+fWEQdWNGxKCiBBjwgT4EcXd6A=="],
"@node-rs/bcrypt-darwin-arm64": ["@node-rs/bcrypt-darwin-arm64@1.9.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-CQiS+F9Pa0XozvkXR1g7uXE9QvBOPOplDg0iCCPRYTN9PqA5qYxhwe48G3o+v2UeQceNRrbnEtWuANm7JRqIhw=="],
"@node-rs/bcrypt-darwin-x64": ["@node-rs/bcrypt-darwin-x64@1.9.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-4pTKGawYd7sNEjdJ7R/R67uwQH1VvwPZ0SSUMmeNHbxD5QlwAPXdDH11q22uzVXsvNFZ6nGQBg8No5OUGpx6Ug=="],
"@node-rs/bcrypt-freebsd-x64": ["@node-rs/bcrypt-freebsd-x64@1.9.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-UmWzySX4BJhT/B8xmTru6iFif3h0Rpx3TqxRLCcbgmH43r7k5/9QuhpiyzpvKGpKHJCFNm4F3rC2wghvw5FCIg=="],
"@node-rs/bcrypt-linux-arm-gnueabihf": ["@node-rs/bcrypt-linux-arm-gnueabihf@1.9.0", "", { "os": "linux", "cpu": "arm" }, "sha512-8qoX4PgBND2cVwsbajoAWo3NwdfJPEXgpCsZQZURz42oMjbGyhhSYbovBCskGU3EBLoC8RA2B1jFWooeYVn5BA=="],
"@node-rs/bcrypt-linux-arm64-gnu": ["@node-rs/bcrypt-linux-arm64-gnu@1.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TuAC6kx0SbcIA4mSEWPi+OCcDjTQUMl213v5gMNlttF+D4ieIZx6pPDGTaMO6M2PDHTeCG0CBzZl0Lu+9b0c7Q=="],
"@node-rs/bcrypt-linux-arm64-musl": ["@node-rs/bcrypt-linux-arm64-musl@1.9.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-/sIvKDABOI8QOEnLD7hIj02BVaNOuCIWBKvxcJOt8+TuwJ6zmY1UI5kSv9d99WbiHjTp97wtAUbZQwauU4b9ew=="],
"@node-rs/bcrypt-linux-x64-gnu": ["@node-rs/bcrypt-linux-x64-gnu@1.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-DyyhDHDsLBsCKz1tZ1hLvUZSc1DK0FU0v52jK6IBQxrj24WscSU9zZe7ie/V9kdmA4Ep57BfpWX8Dsa2JxGdgQ=="],
"@node-rs/bcrypt-linux-x64-musl": ["@node-rs/bcrypt-linux-x64-musl@1.9.0", "", { "os": "linux", "cpu": "x64" }, "sha512-duIiuqQ+Lew8ASSAYm6ZRqcmfBGWwsi81XLUwz86a2HR7Qv6V4yc3ZAUQovAikhjCsIqe8C11JlAZSK6+PlXYg=="],
"@node-rs/bcrypt-wasm32-wasi": ["@node-rs/bcrypt-wasm32-wasi@1.9.0", "", { "dependencies": { "@emnapi/core": "^0.45.0", "@emnapi/runtime": "^0.45.0", "@tybys/wasm-util": "^0.8.1", "memfs-browser": "^3.4.13000" }, "cpu": "none" }, "sha512-ylaGmn9Wjwv/D5lxtawttx3H6Uu2WTTR7lWlRHGT6Ga/MB1Vj4OjSGUW8G8zIVnKuXpGbZ92pgHlt4HUpSLctw=="],
"@node-rs/bcrypt-win32-arm64-msvc": ["@node-rs/bcrypt-win32-arm64-msvc@1.9.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-2h86gF7QFyEzODuDFml/Dp1MSJoZjxJ4yyT2Erf4NkwsiA5MqowUhUsorRwZhX6+2CtlGa7orbwi13AKMsYndw=="],
"@node-rs/bcrypt-win32-ia32-msvc": ["@node-rs/bcrypt-win32-ia32-msvc@1.9.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqxalCvhs4FkN0+gWWfa4Bdy2NQAkfiqq/CEf6mNXC13RSV673Ev9V8sRlQyNpCHCNkeXfOT9pgoBdJmMs9muA=="],
"@node-rs/bcrypt-win32-x64-msvc": ["@node-rs/bcrypt-win32-x64-msvc@1.9.0", "", { "os": "win32", "cpu": "x64" }, "sha512-2y0Tuo6ZAT2Cz8V7DHulSlv1Bip3zbzeXyeur+uR25IRNYXKvI/P99Zl85Fbuu/zzYAZRLLlGTRe6/9IHofe/w=="],
"@petamoriken/float16": ["@petamoriken/float16@3.9.1", "", {}, "sha512-j+ejhYwY6PeB+v1kn7lZFACUIG97u90WxMuGosILFsl9d4Ovi0sjk0GlPfoEcx+FzvXZDAfioD+NGnnPamXgMA=="],
"@tailwindcss/node": ["@tailwindcss/node@4.0.9", "", { "dependencies": { "enhanced-resolve": "^5.18.1", "jiti": "^2.4.2", "tailwindcss": "4.0.9" } }, "sha512-tOJvdI7XfJbARYhxX+0RArAhmuDcczTC46DGCEziqxzzbIaPnfYaIyRT31n4u8lROrsO7Q6u/K9bmQHL2uL1bQ=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.0.9", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.0.9", "@tailwindcss/oxide-darwin-arm64": "4.0.9", "@tailwindcss/oxide-darwin-x64": "4.0.9", "@tailwindcss/oxide-freebsd-x64": "4.0.9", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.9", "@tailwindcss/oxide-linux-arm64-gnu": "4.0.9", "@tailwindcss/oxide-linux-arm64-musl": "4.0.9", "@tailwindcss/oxide-linux-x64-gnu": "4.0.9", "@tailwindcss/oxide-linux-x64-musl": "4.0.9", "@tailwindcss/oxide-win32-arm64-msvc": "4.0.9", "@tailwindcss/oxide-win32-x64-msvc": "4.0.9" } }, "sha512-eLizHmXFqHswJONwfqi/WZjtmWZpIalpvMlNhTM99/bkHtUs6IqgI1XQ0/W5eO2HiRQcIlXUogI2ycvKhVLNcA=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.0.9", "", { "os": "android", "cpu": "arm64" }, "sha512-YBgy6+2flE/8dbtrdotVInhMVIxnHJPbAwa7U1gX4l2ThUIaPUp18LjB9wEH8wAGMBZUb//SzLtdXXNBHPUl6Q=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.0.9", "", { "os": "darwin", "cpu": "arm64" }, "sha512-pWdl4J2dIHXALgy2jVkwKBmtEb73kqIfMpYmcgESr7oPQ+lbcQ4+tlPeVXaSAmang+vglAfFpXQCOvs/aGSqlw=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.0.9", "", { "os": "darwin", "cpu": "x64" }, "sha512-4Dq3lKp0/C7vrRSkNPtBGVebEyWt9QPPlQctxJ0H3MDyiQYvzVYf8jKow7h5QkWNe8hbatEqljMj/Y0M+ERYJg=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.0.9", "", { "os": "freebsd", "cpu": "x64" }, "sha512-k7U1RwRODta8x0uealtVt3RoWAWqA+D5FAOsvVGpYoI6ObgmnzqWW6pnVwz70tL8UZ/QXjeMyiICXyjzB6OGtQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.0.9", "", { "os": "linux", "cpu": "arm" }, "sha512-NDDjVweHz2zo4j+oS8y3KwKL5wGCZoXGA9ruJM982uVJLdsF8/1AeKvUwKRlMBpxHt1EdWJSAh8a0Mfhl28GlQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-jk90UZ0jzJl3Dy1BhuFfRZ2KP9wVKMXPjmCtY4U6fF2LvrjP5gWFJj5VHzfzHonJexjrGe1lMzgtjriuZkxagg=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.0.9", "", { "os": "linux", "cpu": "arm64" }, "sha512-3eMjyTC6HBxh9nRgOHzrc96PYh1/jWOwHZ3Kk0JN0Kl25BJ80Lj9HEvvwVDNTgPg154LdICwuFLuhfgH9DULmg=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-v0D8WqI/c3WpWH1kq/HP0J899ATLdGZmENa2/emmNjubT0sWtEke9W9+wXeEoACuGAhF9i3PO5MeyditpDCiWQ=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.0.9", "", { "os": "linux", "cpu": "x64" }, "sha512-Kvp0TCkfeXyeehqLJr7otsc4hd/BUPfcIGrQiwsTVCfaMfjQZCG7DjI+9/QqPZha8YapLA9UoIcUILRYO7NE1Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.0.9", "", { "os": "win32", "cpu": "arm64" }, "sha512-m3+60T/7YvWekajNq/eexjhV8z10rswcz4BC9bioJ7YaN+7K8W2AmLmG0B79H14m6UHE571qB0XsPus4n0QVgQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.0.9", "", { "os": "win32", "cpu": "x64" }, "sha512-dpc05mSlqkwVNOUjGu/ZXd5U1XNch1kHFJ4/cHkZFvaW1RzbHmRt24gvM8/HC6IirMxNarzVw4IXVtvrOoZtxA=="],
"@tailwindcss/postcss": ["@tailwindcss/postcss@4.0.9", "", { "dependencies": { "@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.0.9", "@tailwindcss/oxide": "4.0.9", "lightningcss": "^1.29.1", "postcss": "^8.4.41", "tailwindcss": "4.0.9" } }, "sha512-BT/E+pdMqulavEAVM5NCpxmGEwHiLDPpkmg/c/X25ZBW+izTe+aZ+v1gf/HXTrihRoCxrUp5U4YyHsBTzspQKQ=="],
"@tybys/wasm-util": ["@tybys/wasm-util@0.8.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q=="],
"@types/bun": ["@types/bun@1.2.4", "", { "dependencies": { "bun-types": "1.2.4" } }, "sha512-QtuV5OMR8/rdKJs213iwXDpfVvnskPXY/S0ZiFbsTjQZycuqPbMW8Gf/XhLfwE5njW8sxI2WjISURXPlHypMFA=="],
"@types/node": ["@types/node@22.13.8", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-G3EfaZS+iOGYWLLRCEAXdWK9my08oHNZ+FHluRiggIYJPOXzhOiDgpVCUHaUvyIC5/fj7C/p637jdzC666AOKQ=="],
"@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="],
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"brisa": ["brisa@0.2.7", "", { "dependencies": { "astring": "1.9.0", "csstype": "3.1.3", "diff-dom-streaming": "0.6.5", "meriyah": "6.0.5" }, "bin": { "brisa": "index.js" } }, "sha512-76fjLQkwfbAxdkA7bpQf7RUdvwsuC6l1nsNndn3yL6C2uD+SQ9RvHTuqwt8hWOz37j2zfN31AIxkJkuA9zwFMg=="],
"brisa-tailwindcss": ["brisa-tailwindcss@0.2.7", "", { "peerDependencies": { "@tailwindcss/postcss": "^4.0.0", "postcss": "^8.4.48", "tailwindcss": "^4.0.1" } }, "sha512-ZH4iSEWqY1V+G2sfn7l9YpR/d0AcEK+pWbpIQgoPeFG/qeyDVihHahg7Vp2ZWVp8Bbqq4v0vUhI09zV+OJLFdA=="],
"buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
"bun-types": ["bun-types@1.2.4", "", { "dependencies": { "@types/node": "*", "@types/ws": "~8.5.10" } }, "sha512-nDPymR207ZZEoWD4AavvEaa/KZe/qlrbMSchqpQwovPZCKc7pwMoENjEtHgMKaAjJhy+x6vfqSBA1QU3bJgs0Q=="],
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"daisyui": ["daisyui@5.0.0", "", {}, "sha512-U0K9Bac3Bi3zZGm6ojrw12F0vBHTpEgf46zv/BYxLe07hF0Xnx7emIQliwaRBgJuYhY0BhwQ6wSnq5cJXHA2yA=="],
"debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
"detect-libc": ["detect-libc@1.0.3", "", { "bin": { "detect-libc": "./bin/detect-libc.js" } }, "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg=="],
"diff-dom-streaming": ["diff-dom-streaming@0.6.5", "", {}, "sha512-S6a7AVm5S0cwOkIYLTg+ilPaCnHM4/QoPol11PLYRKYqud/MfW+wW54+K2zBFqNY3DoNZZXO8yaF3iwFeJkz+A=="],
"drizzle-kit": ["drizzle-kit@0.30.5", "", { "dependencies": { "@drizzle-team/brocli": "^0.10.2", "@esbuild-kit/esm-loader": "^2.5.5", "esbuild": "^0.19.7", "esbuild-register": "^3.5.0", "gel": "^2.0.0" }, "bin": { "drizzle-kit": "bin.cjs" } }, "sha512-l6dMSE100u7sDaTbLczibrQZjA35jLsHNqIV+jmhNVO3O8jzM6kywMOmV9uOz9ZVSCMPQhAZEFjL/qDPVrqpUA=="],
"drizzle-orm": ["drizzle-orm@0.40.0", "", { "peerDependencies": { "@aws-sdk/client-rds-data": ">=3", "@cloudflare/workers-types": ">=4", "@electric-sql/pglite": ">=0.2.0", "@libsql/client": ">=0.10.0", "@libsql/client-wasm": ">=0.10.0", "@neondatabase/serverless": ">=0.10.0", "@op-engineering/op-sqlite": ">=2", "@opentelemetry/api": "^1.4.1", "@planetscale/database": ">=1", "@prisma/client": "*", "@tidbcloud/serverless": "*", "@types/better-sqlite3": "*", "@types/pg": "*", "@types/sql.js": "*", "@vercel/postgres": ">=0.8.0", "@xata.io/client": "*", "better-sqlite3": ">=7", "bun-types": "*", "expo-sqlite": ">=14.0.0", "gel": ">=2", "knex": "*", "kysely": "*", "mysql2": ">=2", "pg": ">=8", "postgres": ">=3", "sql.js": ">=1", "sqlite3": ">=5" }, "optionalPeers": ["@aws-sdk/client-rds-data", "@cloudflare/workers-types", "@electric-sql/pglite", "@libsql/client", "@libsql/client-wasm", "@neondatabase/serverless", "@op-engineering/op-sqlite", "@opentelemetry/api", "@planetscale/database", "@prisma/client", "@tidbcloud/serverless", "@types/better-sqlite3", "@types/pg", "@types/sql.js", "@vercel/postgres", "@xata.io/client", "better-sqlite3", "bun-types", "expo-sqlite", "gel", "knex", "kysely", "mysql2", "pg", "postgres", "sql.js", "sqlite3"] }, "sha512-7ptk/HQiMSrEZHnAsSlBESXWj52VwgMmyTEfoNmpNN2ZXpcz13LwHfXTIghsAEud7Z5UJhDOp8U07ujcqme7wg=="],
"enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
"env-paths": ["env-paths@3.0.0", "", {}, "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A=="],
"esbuild": ["esbuild@0.19.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.19.12", "@esbuild/android-arm": "0.19.12", "@esbuild/android-arm64": "0.19.12", "@esbuild/android-x64": "0.19.12", "@esbuild/darwin-arm64": "0.19.12", "@esbuild/darwin-x64": "0.19.12", "@esbuild/freebsd-arm64": "0.19.12", "@esbuild/freebsd-x64": "0.19.12", "@esbuild/linux-arm": "0.19.12", "@esbuild/linux-arm64": "0.19.12", "@esbuild/linux-ia32": "0.19.12", "@esbuild/linux-loong64": "0.19.12", "@esbuild/linux-mips64el": "0.19.12", "@esbuild/linux-ppc64": "0.19.12", "@esbuild/linux-riscv64": "0.19.12", "@esbuild/linux-s390x": "0.19.12", "@esbuild/linux-x64": "0.19.12", "@esbuild/netbsd-x64": "0.19.12", "@esbuild/openbsd-x64": "0.19.12", "@esbuild/sunos-x64": "0.19.12", "@esbuild/win32-arm64": "0.19.12", "@esbuild/win32-ia32": "0.19.12", "@esbuild/win32-x64": "0.19.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg=="],
"esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
"fs-monkey": ["fs-monkey@1.0.6", "", {}, "sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg=="],
"gel": ["gel@2.0.0", "", { "dependencies": { "@petamoriken/float16": "^3.8.7", "debug": "^4.3.4", "env-paths": "^3.0.0", "semver": "^7.6.2", "shell-quote": "^1.8.1", "which": "^4.0.0" }, "bin": { "gel": "dist/cli.mjs" } }, "sha512-Oq3Fjay71s00xzDc0BF/mpcLmnA+uRqMEJK8p5K4PaZjUEsxaeo+kR9OHBVAf289/qPd+0OcLOLUN0UhqiUCog=="],
"get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"isexe": ["isexe@3.1.1", "", {}, "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ=="],
"jiti": ["jiti@2.4.2", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
"lightningcss": ["lightningcss@1.29.1", "", { "dependencies": { "detect-libc": "^1.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.1", "lightningcss-darwin-x64": "1.29.1", "lightningcss-freebsd-x64": "1.29.1", "lightningcss-linux-arm-gnueabihf": "1.29.1", "lightningcss-linux-arm64-gnu": "1.29.1", "lightningcss-linux-arm64-musl": "1.29.1", "lightningcss-linux-x64-gnu": "1.29.1", "lightningcss-linux-x64-musl": "1.29.1", "lightningcss-win32-arm64-msvc": "1.29.1", "lightningcss-win32-x64-msvc": "1.29.1" } }, "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.29.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.29.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.29.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.29.1", "", { "os": "linux", "cpu": "arm" }, "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.29.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.29.1", "", { "os": "linux", "cpu": "x64" }, "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.29.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog=="],
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.1", "", { "os": "win32", "cpu": "x64" }, "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q=="],
"memfs": ["memfs@3.5.3", "", { "dependencies": { "fs-monkey": "^1.0.4" } }, "sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw=="],
"memfs-browser": ["memfs-browser@3.5.10302", "", { "dependencies": { "memfs": "3.5.3" } }, "sha512-JJTc/nh3ig05O0gBBGZjTCPOyydaTxNF0uHYBrcc1gHNnO+KIHIvo0Y1FKCJsaei6FCl8C6xfQomXqu+cuzkIw=="],
"meriyah": ["meriyah@6.0.5", "", {}, "sha512-SrMqQCox7TTwtftWKHy/ZaVe+ZRpRl20pAgDo+PS9hzcAJrMjYsBJQPPiLXTnjztrqdfGS+Zz99r6Bwvydta1w=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.8", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w=="],
"oslo": ["oslo@1.2.1", "", { "dependencies": { "@node-rs/argon2": "1.7.0", "@node-rs/bcrypt": "1.9.0" } }, "sha512-HfIhB5ruTdQv0XX2XlncWQiJ5SIHZ7NHZhVyHth0CSZ/xzge00etRyYy/3wp/Dsu+PkxMC+6+B2lS/GcKoewkA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["postcss@8.5.3", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A=="],
"resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
"semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"shell-quote": ["shell-quote@1.8.2", "", {}, "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA=="],
"source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"source-map-support": ["source-map-support@0.5.21", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w=="],
"tailwindcss": ["tailwindcss@4.0.9", "", {}, "sha512-12laZu+fv1ONDRoNR9ipTOpUD7RN9essRVkX36sjxuRUInpN7hIiHN4lBd/SIFjbISvnXzp8h/hXzmU8SQQYhw=="],
"tapable": ["tapable@2.2.1", "", {}, "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="],
"undici-types": ["undici-types@6.20.0", "", {}, "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="],
"which": ["which@4.0.0", "", { "dependencies": { "isexe": "^3.1.1" }, "bin": { "node-which": "bin/which.js" } }, "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg=="],
"@esbuild-kit/core-utils/esbuild": ["esbuild@0.18.20", "", { "optionalDependencies": { "@esbuild/android-arm": "0.18.20", "@esbuild/android-arm64": "0.18.20", "@esbuild/android-x64": "0.18.20", "@esbuild/darwin-arm64": "0.18.20", "@esbuild/darwin-x64": "0.18.20", "@esbuild/freebsd-arm64": "0.18.20", "@esbuild/freebsd-x64": "0.18.20", "@esbuild/linux-arm": "0.18.20", "@esbuild/linux-arm64": "0.18.20", "@esbuild/linux-ia32": "0.18.20", "@esbuild/linux-loong64": "0.18.20", "@esbuild/linux-mips64el": "0.18.20", "@esbuild/linux-ppc64": "0.18.20", "@esbuild/linux-riscv64": "0.18.20", "@esbuild/linux-s390x": "0.18.20", "@esbuild/linux-x64": "0.18.20", "@esbuild/netbsd-x64": "0.18.20", "@esbuild/openbsd-x64": "0.18.20", "@esbuild/sunos-x64": "0.18.20", "@esbuild/win32-arm64": "0.18.20", "@esbuild/win32-ia32": "0.18.20", "@esbuild/win32-x64": "0.18.20" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.18.20", "", { "os": "android", "cpu": "arm" }, "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.18.20", "", { "os": "android", "cpu": "arm64" }, "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.18.20", "", { "os": "android", "cpu": "x64" }, "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.18.20", "", { "os": "darwin", "cpu": "arm64" }, "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.18.20", "", { "os": "darwin", "cpu": "x64" }, "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.18.20", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.18.20", "", { "os": "freebsd", "cpu": "x64" }, "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.18.20", "", { "os": "linux", "cpu": "arm" }, "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.18.20", "", { "os": "linux", "cpu": "arm64" }, "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.18.20", "", { "os": "linux", "cpu": "ia32" }, "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.18.20", "", { "os": "linux", "cpu": "ppc64" }, "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.18.20", "", { "os": "linux", "cpu": "none" }, "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.18.20", "", { "os": "linux", "cpu": "s390x" }, "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.18.20", "", { "os": "linux", "cpu": "x64" }, "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.18.20", "", { "os": "none", "cpu": "x64" }, "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.18.20", "", { "os": "openbsd", "cpu": "x64" }, "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.18.20", "", { "os": "sunos", "cpu": "x64" }, "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.18.20", "", { "os": "win32", "cpu": "arm64" }, "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.18.20", "", { "os": "win32", "cpu": "ia32" }, "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g=="],
"@esbuild-kit/core-utils/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.18.20", "", { "os": "win32", "cpu": "x64" }, "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ=="],
}
}

View File

@@ -1,2 +0,0 @@
[test]
preload = "brisa/test"

View File

@@ -1,25 +0,0 @@
{
"name": "@star-kitten/eve-web",
"module": "src/pages/index.tsx",
"type": "module",
"scripts": {
"dev": "bunx dotenvx run -f .env.development -- brisa dev",
"dev:debug": "brisa dev --debug",
"build": "brisa build",
"start": "bunx dotenvx run -f .env.production -- brisa start"
},
"dependencies": {
"brisa": "0.2.7",
"brisa-tailwindcss": "0.2.7",
"drizzle-orm": "^0.40.0",
"oslo": "^1.2.1",
"@star-kitten/util": "workspace:^0.0.0",
"@star-kitten/eve": "workspace:^0.0.0"
},
"devDependencies": {
"@types/bun": "latest",
"daisyui": "^5.0.0",
"drizzle-kit": "^0.30.5",
"typescript": "latest"
}
}

View File

@@ -1,76 +0,0 @@
import type { RequestContext } from 'brisa';
import type { EVEAuth } from '@/middleware';
import { getCookies, removeCookie, setCookie } from '@/utils';
import { CharacterAPI, characterIdFromToken } from '@star-kitten/eve/esi';
import { CharacterHelper, UserHelper } from '@star-kitten/eve/db';
// GET /api/auth/callback
export async function GET(request: RequestContext) {
const eveauth: EVEAuth = request.store.get('eveauth');
const response = new Response('', { status: 302 });
try {
const cookies = getCookies(request.headers);
const cookieDiscordID = cookies['discordID'];
if (!cookieDiscordID) {
throw new Error(`Missing discordID cookie in /api/auth/callback`);
}
const cookieCharacterID = cookies['characterID'];
const token = await eveauth.validate(response);
const characterID = characterIdFromToken(token.access_token);
if (cookieCharacterID && parseInt(cookieCharacterID) !== characterID) {
throw new Error(`Character ID mismatch: ${cookieCharacterID} !== ${characterID}`);
}
let user = UserHelper.findByDiscordId(cookieDiscordID);
let character = CharacterHelper.findByUserAndEveID(user.id, Number(characterID));
if (!user) {
user = UserHelper.create(cookieDiscordID);
}
if (!user) {
throw new Error(`Something went wrong with creating a user for id: ${cookieDiscordID}`);
}
if (!character) {
const data = await CharacterAPI.getCharacterPublicData(characterID);
if (!data) {
throw new Error(`Failed to retreive character public data for id: ${characterID} - unable to create character`);
}
character = CharacterHelper.create(characterID, data.name || 'UNKNOWN NAME', user, token);
// refetch from db to get id
user = UserHelper.findByDiscordId(cookieDiscordID);
character = CharacterHelper.findByUserAndEveID(user.id, Number(characterID));
if (!character) {
throw new Error(`Failed to retreive character from db for id: ${characterID}`);
}
if (!user.mainCharacterID) {
user.mainCharacterID = character.id;
UserHelper.save(user);
}
} else {
// Update existing character with new token
character.accessToken = token.access_token;
character.expiresAt = new Date(Date.now() + token.expires_in * 1000);
character.refreshToken = token.refresh_token;
CharacterHelper.save(character);
}
setCookie(response, 'currentUser', user.id + '', 60 * 60 * 24 * 30 /* 30 days */);
response.headers.set('location', '/auth/success');
} catch (err) {
console.error(`Error: Callback failed with ${err}`);
response.headers.set('location', '/auth/error');
return response;
} finally {
removeCookie(response, 'discordID');
removeCookie(response, 'characterID');
removeCookie(response, 'state');
}
return response;
}

View File

@@ -1,30 +0,0 @@
import type { EVEAuth } from '@/middleware';
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
import { CharacterHelper, UserHelper } from '@star-kitten/eve/db';
// GET /api/auth/discordID/:discordID/addScopes/characterID/:characterID/scopes/:scopes
export async function GET({ store, route: { params } }: RequestContext) {
const eveauth: EVEAuth = store.get('eveauth');
const discordID = params!['discordID'] as string;
const characterID = params!['characterID'] as string;
const requiredScopes = (params!['scopes'] as string).split(',');
const user = UserHelper.findByDiscordId(discordID);
const character = CharacterHelper.findByUserAndEveID(user.id, Number(characterID));
if (!character) {
throw new Error(`Character ${characterID} not found`);
}
const currentScopes = CharacterHelper.getScopes(character);
const set = new Set(currentScopes);
requiredScopes.forEach((scope) => set.add(scope));
const scopes = Array.from(set).join(' ');
// As this is adding scopes, we need to redirect the user to the auth page
const response = await eveauth.redirect(scopes);
setCookie(response, 'discordID', discordID, 60 * 10 /* 10 min */);
setCookie(response, 'characterID', characterID, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,29 +0,0 @@
import type { EVEAuth } from '@/middleware';
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
import { joinScopes, SCOPES } from '@star-kitten/eve/esi';
const allScopes = Object.values(SCOPES).filter((value) => typeof value === 'string') as string[];
const allScopesString = allScopes.join(' ');
const module_Scopes: { [key: string]: string } = {
'Full ESI': allScopesString,
'Public': joinScopes(SCOPES.PUBLIC_DATA),
'Characters': joinScopes(SCOPES.PUBLIC_DATA),
'Mail': joinScopes(SCOPES.MAIL_READ_MAIL, SCOPES.MAIL_SEND_MAIL, SCOPES.MAIL_ORGANIZE_MAIL),
'Skills': joinScopes(SCOPES.SKILLS_READ_SKILLQUEUE, SCOPES.SKILLS_READ_SKILLS),
}
// GET /api/auth/discordID/:discordID/characterID/:characterID/modules/:modules
export async function GET({ store, route: { params } }: RequestContext) {
// we need to check the modules that were sent, in order to get the correct scopes
// that each module requires.
let modules = (params!['modules'] as string).split(' ');
let scopes = joinScopes(...modules.map((module) => module_Scopes[module]));
const eveauth: EVEAuth = store.get('eveauth');
const response = await eveauth.redirect(scopes);
setCookie(response, 'discordID', params!['discordID'] as string, 60 * 10 /* 10 min */);
setCookie(response, 'characterID', params!['characterID'] as string, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,28 +0,0 @@
import type { EVEAuth } from '@/middleware';
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
import { CharacterHelper, UserHelper } from '@star-kitten/eve/db';
import { joinScopes } from '@star-kitten/eve/esi';
// GET /api/auth/discordID/:discordID/characterID/:characterID/refresh
export async function GET({ store, route: { params } }: RequestContext) {
const discordID = params!['discordID'] as string;
const characterID = params!['characterID'] as string;
const user = UserHelper.findByDiscordId(discordID);
const character = CharacterHelper.findByUserAndEveID(user.id, Number(characterID));
if (!user) {
throw new Error(`User not found for discordID: ${discordID}`);
}
if (!character) {
throw new Error(`Character not found for user: ${user.id} and characterID: ${characterID}`);
}
const scopes = CharacterHelper.getScopes(character);
const eveauth: EVEAuth = store.get('eveauth');
const response = await eveauth.redirect(joinScopes(...scopes));
setCookie(response, 'discordID', discordID, 60 * 10 /* 10 min */);
setCookie(response, 'characterID', characterID, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,14 +0,0 @@
import type { EVEAuth } from '@/middleware';
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
// GET /api/auth/discordID/:discordID/characterID/:characterID/scopes/:scopes
export async function GET({ store, route: { params } }: RequestContext) {
// this is used to set the scopes that were sent, so just pass them along to auth directly
// with the provided scopes
const eveauth: EVEAuth = store.get('eveauth');
const response = await eveauth.redirect(params!['scopes'] as string);
setCookie(response, 'discordID', params!['discordID'] as string, 60 * 10 /* 10 min */);
setCookie(response, 'characterID', params!['characterID'] as string, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,12 +0,0 @@
import type { EVEAuth } from '@/middleware';
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
// GET /api/auth/discordID/:discordID
export async function GET({ store, route: { params }}: RequestContext) {
// called when adding a new character, so just redirect to auth and set cookies
const eveauth: EVEAuth = store.get('eveauth');
const response = await eveauth.redirect('publicData');
setCookie(response, 'discordID', params!['discordID'] as string, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,29 +0,0 @@
import { setCookie } from '@/utils';
import type { RequestContext } from 'brisa';
import { CharacterHelper, UserHelper } from '@star-kitten/eve/db'
// GET /api/auth/discordID/:discordID/removeScopes/characterID/:characterID/scopes/:scopes
export function GET({ route: { params } }: RequestContext) {
const discordID = params!['discordID'] as string;
const characterID = params!['characterID'] as string;
const removeScopes = (params!['scopes'] as string).split(',');
const user = UserHelper.findByDiscordId(discordID);
const character = CharacterHelper.findByUserAndEveID(user.id, Number(characterID));
if (!character) {
throw new Error(`Character ${characterID} not found`);
}
const currentScopes = CharacterHelper.getScopes(character);
const set = new Set(currentScopes);
removeScopes.forEach((scope) => set.delete(scope));
// As this is removing scopes, we can do this without user interaction
CharacterHelper.refreshTokens(character, Array.from(set).join(' '));
// redirect to success page
const response = new Response('', { status: 302 });
response.headers.set('location', '/auth/success');
setCookie(response, 'discordID', params!['discordID'] as string, 60 * 10 /* 10 min */);
setCookie(response, 'discordID', characterID, 60 * 10 /* 10 min */);
return response;
}

View File

@@ -1,8 +0,0 @@
import type { RequestContext } from 'brisa';
import type { EVEAuth } from '@/middleware';
// GET /api/auth/
export function GET(request: RequestContext) {
const eveauth: EVEAuth = request.store.get('eveauth');
return eveauth.redirect();
}

View File

@@ -1,36 +0,0 @@
import { renderComponent } from 'brisa/server';
export default function CounterServer({
initialValue = 0,
}: {
initialValue: number;
}) {
function increment(e: Event) {
const value = Number((e.target as HTMLButtonElement).dataset.value);
renderComponent({ element: <CounterServer initialValue={value + 1} /> });
}
function decrement(e: Event) {
const value = Number((e.target as HTMLButtonElement).dataset.value);
renderComponent({ element: <CounterServer initialValue={value - 1} /> });
}
return (
<div class="counter">
<div class="counter-container">
<h2>Server counter</h2>
<button
data-value={initialValue}
class="increment-button"
onClick={increment}
></button>
<div class="counter-value">{initialValue}</div>
<button
data-value={initialValue}
class="decrement-button"
onClick={decrement}
></button>
</div>
</div>
);
}

View File

@@ -1,54 +0,0 @@
export default function Footer() {
return (
<footer class="footer sm:footer-horizontal bg-neutral text-neutral-content items-center p-4">
<aside class="grid-flow-col items-center">
<svg
width="36"
height="36"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fillRule="evenodd"
clipRule="evenodd"
class="fill-current">
<path
d="M22.672 15.226l-2.432.811.841 2.515c.33 1.019-.209 2.127-1.23 2.456-1.15.325-2.148-.321-2.463-1.226l-.84-2.518-5.013 1.677.84 2.517c.391 1.203-.434 2.542-1.831 2.542-.88 0-1.601-.564-1.86-1.314l-.842-2.516-2.431.809c-1.135.328-2.145-.317-2.463-1.229-.329-1.018.211-2.127 1.231-2.456l2.432-.809-1.621-4.823-2.432.808c-1.355.384-2.558-.59-2.558-1.839 0-.817.509-1.582 1.327-1.846l2.433-.809-.842-2.515c-.33-1.02.211-2.129 1.232-2.458 1.02-.329 2.13.209 2.461 1.229l.842 2.515 5.011-1.677-.839-2.517c-.403-1.238.484-2.553 1.843-2.553.819 0 1.585.509 1.85 1.326l.841 2.517 2.431-.81c1.02-.33 2.131.211 2.461 1.229.332 1.018-.21 2.126-1.23 2.456l-2.433.809 1.622 4.823 2.433-.809c1.242-.401 2.557.484 2.557 1.838 0 .819-.51 1.583-1.328 1.847m-8.992-6.428l-5.01 1.675 1.619 4.828 5.011-1.674-1.62-4.829z"></path>
</svg>
<p>Copyright © {new Date().getFullYear()} Star Kitten Cafe - All rights reserved</p>
</aside>
<nav class="grid-flow-col gap-4 md:place-self-center md:justify-self-end">
<a href="https://github.com/roman-kaas/star-kitten" target='_blank'>
<svg
width="24"
height="24"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 98 96"
class="fill-current">
<path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z" fill="#fff"/>
</svg>
</a>
{/* <a>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current">
<path
d="M19.615 3.184c-3.604-.246-11.631-.245-15.23 0-3.897.266-4.356 2.62-4.385 8.816.029 6.185.484 8.549 4.385 8.816 3.6.245 11.626.246 15.23 0 3.897-.266 4.356-2.62 4.385-8.816-.029-6.185-.484-8.549-4.385-8.816zm-10.615 12.816v-8l8 3.993-8 4.007z"></path>
</svg>
</a>
<a>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
class="fill-current">
<path
d="M9 8h-3v4h3v12h5v-12h3.642l.358-4h-4v-1.667c0-.955.192-1.333 1.115-1.333h2.885v-5h-3.808c-3.596 0-5.192 1.583-5.192 4.615v3.385z"></path>
</svg>
</a> */}
</nav>
</footer>
);
}

View File

@@ -1,45 +0,0 @@
import { CharacterHelper, type User } from '@star-kitten/eve/db';
import { CharacterAPI } from '@star-kitten/eve/esi';
export default function Nav({
user,
}: {
user: User;
}) {
const main = user.mainCharacterID && CharacterHelper.find(user.mainCharacterID);
return (
<div class="navbar bg-base-100 shadow-sm">
<div class="flex-1">
<a class="btn btn-ghost text-xl">Star Kitten</a>
</div>
<div class="flex gap-2">
<input type="text" placeholder="Search" class="input input-bordered w-24 md:w-auto" />
<div class="dropdown dropdown-end">
<div tabIndex={0} role="button" class="btn btn-ghost btn-circle avatar">
<div class="w-10 rounded-full">
<img
alt="Tailwind CSS Navbar component"
src={main && CharacterAPI.getPortraitURL(main.eveID) || ''} />
</div>
</div>
<ul
tabIndex={0}
class="menu menu-sm dropdown-content bg-base-100 rounded-box z-1 mt-3 w-52 p-2 shadow">
<li>
{main && main.name}
</li>
<li>
<a class="justify-between">
Characters
<span class="badge">{user.characterIDs.length}</span>
</a>
</li>
<li><a>Link Character</a></li>
<li><a>Settings</a></li>
<li><a>Logout</a></li>
</ul>
</div>
</div>
</div>
);
}

View File

@@ -1,46 +0,0 @@
import { type Character } from '@star-kitten/eve/db';
import { calculateTrainingPercentage, getCharacterSkillQueue } from '@star-kitten/eve/esi';
import { getSkill, getType } from '@star-kitten/eve/models';
export default async function SkillQueueStat({
character,
}: {
character: Character;
}) {
const queue = await getCharacterSkillQueue(character);
const current = queue?.find((skill) => skill.queue_position === 0);
if (!current || !current.start_date) {
return (
<div class="stat">
<div class="stat-figure text-secondary">
</div>
<div class="stat-title">No Skills Training</div>
</div >
);
}
const skill = await getSkill(current.skill_id);
const percentage = calculateTrainingPercentage(current) * 100;
return (
<div class="stat">
<div class="stat-figure text-secondary">
</div>
<div class="stat-title">Currently Training</div>
<div class="stat-value">{(await getType(skill.type_id)).name.en} {current.finished_level}</div>
<div class="stat-desc"><progress class="progress progress-primary w-full" value={percentage} max="100"></progress></div>
</div>
);
}
SkillQueueStat.suspense = () => (
<div class="stat">
<div class="stat-figure text-secondary">
</div>
<div class="stat-title">Currently Training</div>
<div class="flex w-52 flex-col gap-4">
<div class="skeleton h-16 w-full"></div>
<div class="skeleton h-4 w-full"></div>
</div>
</div>
)

Some files were not shown because too many files have changed in this diff Show More