From 2a2cdb8dffc0bfa4e5acfb54fb456ee79fde7ea5 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 03:40:57 +0300 Subject: [PATCH 01/66] item groundwork --- migrations/20231114153325_items.js | 34 ++++ src/commands/item.ts | 257 +++++++++++++++++++++++++++++ src/lib/db.ts | 18 ++ src/lib/items.ts | 65 ++++++++ 4 files changed, 374 insertions(+) create mode 100644 migrations/20231114153325_items.js create mode 100644 src/commands/item.ts create mode 100644 src/lib/items.ts diff --git a/migrations/20231114153325_items.js b/migrations/20231114153325_items.js new file mode 100644 index 0000000..98ef104 --- /dev/null +++ b/migrations/20231114153325_items.js @@ -0,0 +1,34 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('customItems', table => { + table.increments('id'); + table.string('guild').notNullable(); + table.string('name').notNullable(); + table.text('description'); + table.string('emoji').notNullable(); + table.enum('type', ['plain', 'weapon', 'consumable']).notNullable(); + table.integer('maxStack').notNullable(); // or damage for weapons + table.string('behavior'); + table.boolean('untradable').defaultTo(false); + table.float('behaviorValue'); + }) + .createTable('itemInventories', table => { + table.string('user').notNullable(); + table.integer('item').notNullable(); + table.integer('quantity').defaultTo(1); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('customItems') + .dropTable('itemInventories'); +}; diff --git a/src/commands/item.ts b/src/commands/item.ts new file mode 100644 index 0000000..83a850b --- /dev/null +++ b/src/commands/item.ts @@ -0,0 +1,257 @@ +import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; +import { CustomItem, ItemInventory, db } from '../lib/db'; +import { behaviors, defaultItems, formatItems, getItem, getMaxStack } from '../lib/items'; + +//function extendOption(t: string) { +// return {name: t, value: t}; +//} + +module.exports = { + data: new SlashCommandBuilder() + .setName('item') + .setDescription('[ADMIN] Create, edit and otherwise deal with custom items') + .addSubcommandGroup(grp => + grp + .setName('add') + .setDescription('[ADMIN] Create an item') + .addSubcommand(cmd => + cmd + .setName('plain') + .setDescription('A normal, functionless item') + .addStringOption(opt => + opt + .setName('name') + .setDescription('The item name') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('emoji') + .setDescription('An emoji or symbol that could represent this item') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('description') + .setDescription('A short description') + ) + .addIntegerOption(opt => + opt + .setName('maxstack') + .setDescription('Maximum amount of this item you\'re able to hold at once') + ) + .addStringOption(opt => + opt + .setName('behavior') + .setDescription('Special behavior type') + .setChoices(...behaviors.filter(b => b.itemType === 'plain').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) + ) + .addBooleanOption(opt => + opt + .setName('untradable') + .setDescription('Can you give this item to other people?') + ) + .addNumberOption(opt => + opt + .setName('behaviorvalue') + .setDescription('A value to use for the behavior type; not always applicable') + ) + ) + .addSubcommand(cmd => + cmd + .setName('weapon') + .setDescription('A weapon that you can attack things with') + .addStringOption(opt => + opt + .setName('name') + .setDescription('The item name') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('emoji') + .setDescription('An emoji or symbol that could represent this item') + .setRequired(true) + ) + .addIntegerOption(opt => + opt + .setName('damage') + .setDescription('How much base damage this weapon is intended to deal') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('description') + .setDescription('A short description') + ) + .addStringOption(opt => + opt + .setName('behavior') + .setDescription('Special behavior type') + .setChoices(...behaviors.filter(b => b.itemType === 'weapon').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) + ) + .addBooleanOption(opt => + opt + .setName('untradable') + .setDescription('Can you give this item to other people?') + ) + .addNumberOption(opt => + opt + .setName('behaviorvalue') + .setDescription('A value to use for the behavior type; not always applicable') + ) + ) + .addSubcommand(cmd => + cmd + .setName('consumable') + .setDescription('Consumable item, usable once and never again') + .addStringOption(opt => + opt + .setName('name') + .setDescription('The item name') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('emoji') + .setDescription('An emoji or symbol that could represent this item') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('description') + .setDescription('A short description') + ) + .addIntegerOption(opt => + opt + .setName('maxstack') + .setDescription('Maximum amount of this item you\'re able to hold at once') + ) + .addStringOption(opt => + opt + .setName('behavior') + .setDescription('Special behavior type') + .setChoices(...behaviors.filter(b => b.itemType === 'consumable').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) + ) + .addBooleanOption(opt => + opt + .setName('untradable') + .setDescription('Can you give this item to other people?') + ) + .addNumberOption(opt => + opt + .setName('behaviorvalue') + .setDescription('A value to use for the behavior type; not always applicable') + ) + ) + ) + .addSubcommand(cmd => + cmd + .setName('give') + .setDescription('[ADMIN] Give a user an item') + .addUserOption(opt => + opt + .setName('who') + .setDescription('The user') + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('item') + .setDescription('The item') + .setAutocomplete(true) + .setRequired(true) + ) + .addIntegerOption(opt => + opt + .setName('quantity') + .setDescription('Amount of items to give') + ) + ) + .setDefaultMemberPermissions('0') + .setDMPermission(false), + + execute: async (interaction: Interaction) => { + if (!interaction.isChatInputCommand()) return; + + await interaction.deferReply({ephemeral: true}); + + const subcommand = interaction.options.getSubcommand(true); + const group = interaction.options.getSubcommandGroup(); + + if (group === 'add') { + const item = await db('customItems') + .insert({ + 'guild': interaction.guildId!, + 'name': interaction.options.getString('name', true).trim(), + 'description': interaction.options.getString('description') || undefined, + 'emoji': interaction.options.getString('emoji', true).trim(), + 'type': subcommand as 'plain' | 'weapon' | 'consumable', // kind of wild that ts makes you do this + 'maxStack': (interaction.options.getInteger('maxstack') || interaction.options.getInteger('damage')) || (subcommand === 'weapon' ? 1 : 64), + 'behavior': interaction.options.getString('behavior') || undefined, + 'untradable': interaction.options.getBoolean('untradable') || false, + 'behaviorValue': interaction.options.getNumber('behaviorValue') || undefined, + }) + .returning('*'); + + await interaction.followUp(`${JSON.stringify(item[0])}`); + } else { + if (subcommand === 'give') { + const user = interaction.options.getUser('who', true); + const itemID = parseInt(interaction.options.getString('item', true)); + const quantity = interaction.options.getInteger('quantity') || 1; + + const item = await getItem(itemID); + if (!item) return interaction.followUp('No such item exists!'); + + const storedItem = await db('itemInventories') + .where('user', user.id) + .where('item', itemID) + .first(); + + let inv; + if (storedItem) { + inv = await db('itemInventories') + .update({ + 'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) + }) + .limit(1) + .where('user', user.id) + .where('item', itemID) + .returning('*'); + } else { + inv = await db('itemInventories') + .insert({ + 'user': user.id, + 'item': Math.min(itemID, getMaxStack(item)), + 'quantity': quantity + }) + .returning('*'); + } + + await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv[0].quantity)}.`); + } + } + }, + + autocomplete: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(true); + + if (focused.name === 'item') { + const customItems = await db('customItems') + .select('emoji', 'name', 'id') + // @ts-expect-error this LITERALLY works + .whereLike(db.raw('UPPER(name)'), `%${focused.value.toUpperCase()}%`) + .where('guild', interaction.guildId!) + .limit(25); + + const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.value.toUpperCase())); + + const items = [...foundDefaultItems, ...customItems]; + + await interaction.respond( + items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) + ); + } + } +}; \ No newline at end of file diff --git a/src/lib/db.ts b/src/lib/db.ts index d1ae0ef..73da31e 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -44,4 +44,22 @@ export interface CounterConfiguration { id: number, configName: string, value: string +} +export interface CustomItem { + id: number, + guild: string, + name: string, + description?: string, + emoji: string, + type: 'plain' | 'weapon' | 'consumable', + // also damage for weapons; weapons are always unstackable (cus i said so) + maxStack: number, + behavior?: string, + untradable: boolean, + behaviorValue?: number +} +export interface ItemInventory { + user: string, + item: number, + quantity: number } \ No newline at end of file diff --git a/src/lib/items.ts b/src/lib/items.ts new file mode 100644 index 0000000..ad4a160 --- /dev/null +++ b/src/lib/items.ts @@ -0,0 +1,65 @@ +import { User } from 'discord.js'; +import { CustomItem, db } from './db'; + +type DefaultItem = Omit; // uses negative IDs +type Item = DefaultItem | CustomItem; + +interface Behavior { + name: string, + description: string, + itemType: 'plain' | 'weapon' | 'consumable', + // triggers upon use + // for 'weapons', this is on hit + // for 'consumable', this is on use + // for 'plain', ...?? + // returns `true` upon success, `false` otherwise + action?: (item: Item, user: User) => Promise +} + +export const defaultItems: DefaultItem[] = [ + { + 'id': -1, + 'name': 'Coin', + 'emoji': '🪙', + 'type': 'plain', + 'maxStack': 9999, + 'untradable': false + } +]; + +export const behaviors: Behavior[] = [ + { + 'name': 'heal', + 'description': 'Heals the user by `behaviorValue`', + 'itemType': 'consumable', + 'action': async (item: Item, user: User) => { + // todo + return false; + } + } +]; + +export async function getCustomItem(id: number) { + return await db('customItems') + .where('id', id) + .first(); +} + +export async function getItem(id: number): Promise { + if (id >= 0) { + return await getCustomItem(id); + } else { + return defaultItems.find(item => item.id === id); + } +} + +export function getMaxStack(item: Item) { + return item.type === 'weapon' ? 1 : item.maxStack; +} + +export function formatItem(item: Item) { + return `${item.emoji} **${item.name}**`; +} +export function formatItems(item: Item, quantity: number) { + return `${quantity}x ${formatItem(item)}`; +} \ No newline at end of file From 4351e6dfadf9f679a15da2c01a734ce09a583525 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 03:50:29 +0300 Subject: [PATCH 02/66] /inventory --- src/commands/inventory.ts | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 src/commands/inventory.ts diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts new file mode 100644 index 0000000..fd3aba8 --- /dev/null +++ b/src/commands/inventory.ts @@ -0,0 +1,27 @@ +import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { ItemInventory, db } from '../lib/db'; +import { formatItems, getItem } from '../lib/items'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('inventory') + .setDescription('Check your inventory') + .setDMPermission(false), + + execute: async (interaction: Interaction, member: GuildMember) => { + if (!interaction.isChatInputCommand()) return; + + await interaction.deferReply({ephemeral: true}); + + const itemsList = await db('itemInventories') + .select('item', 'quantity') + .where('user', member.user.id); + + // kind of stupid kind of awful + const items = (await Promise.all(itemsList.map(async i => ({item: await getItem(i.item), quantity: i.quantity})))).filter(i => i.item); + + await interaction.followUp( + `Your inventory:\n${items.length === 0 ? '_Your inventory is empty!_' : items.map(i => `- ${formatItems(i.item!, i.quantity)}`).join('\n')}` + ); + } +}; \ No newline at end of file From a0c9b12da0d0cc30abb3ff58fbb9fc41df44c547 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:03:01 +0300 Subject: [PATCH 03/66] linked counters --- migrations/20231115005045_counterLinks.js | 21 +++ src/commands/counter.ts | 76 ++++++--- src/commands/item.ts | 44 +---- src/commands/put.ts | 36 ++++ src/commands/take.ts | 36 ++++ src/lib/counter.ts | 196 +++++++++++++++------- src/lib/db.ts | 3 +- src/lib/items.ts | 69 +++++++- 8 files changed, 353 insertions(+), 128 deletions(-) create mode 100644 migrations/20231115005045_counterLinks.js create mode 100644 src/commands/put.ts create mode 100644 src/commands/take.ts diff --git a/migrations/20231115005045_counterLinks.js b/migrations/20231115005045_counterLinks.js new file mode 100644 index 0000000..17887f3 --- /dev/null +++ b/migrations/20231115005045_counterLinks.js @@ -0,0 +1,21 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .alterTable('counters', table => { + table.integer('linkedItem'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .alterTable('counters', table => { + table.dropColumn('linkedItem'); + }); +}; diff --git a/src/commands/counter.ts b/src/commands/counter.ts index a4ed008..4ac2a98 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -2,6 +2,7 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'disco import { Counter, CounterUserLink, db } from '../lib/db'; import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/counter'; import { outdent } from 'outdent'; +import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/items'; function extendOption(t: string) { return {name: t, value: t}; @@ -9,7 +10,7 @@ function extendOption(t: string) { const help = new Map([ ['message templates', outdent` - When using \`messageTemplate\`, \`messageTemplateIncrease\` or \`messageTemplateDecrease\`, you are providing a **template string**. + When using \`messageTemplate\`, \`messageTemplateIncrease\`, \`messageTemplateDecrease\`, \`messageTemplatePut\` or \`messageTemplateTake\`, you are providing a **template string**. A template string is a **specially-formatted** string with placeholder values. For instance, a template string like so: > **%user** has %action the counter by **%amt**. @@ -215,6 +216,25 @@ module.exports = { .setChoices(...[...help.keys()].map(extendOption)) ) ) + .addSubcommand(sub => + sub + .setName('link') + .setDescription('[ADMIN] THIS IS IRREVERSIBLE! Attach an item to this counter, letting you take or put items in.') + .addStringOption(opt => + opt + .setName('type') + .setDescription('The counter to operate on') + .setRequired(true) + .setAutocomplete(true) + ) + .addStringOption(opt => + opt + .setName('item') + .setDescription('The item') + .setAutocomplete(true) + .setRequired(true) + ) + ) .setDefaultMemberPermissions('0') .setDMPermission(false), @@ -233,10 +253,7 @@ module.exports = { try { counter = await findCounter(type, interaction.guildId!); } catch(err) { - await interaction.followUp({ - content: 'No such counter!' - }); - return; + return interaction.followUp('No such counter!'); } if (subcommand === 'add') { @@ -276,12 +293,7 @@ module.exports = { .where('producer', userType === 'producer') .first(); - if (!link) { - await interaction.followUp({ - content: `<@${user.id}> is not in the ${counter.emoji} **${userType}** allowlist!` - }); - return; - } + if (!link) return interaction.followUp(`<@${user.id}> is not in the ${counter.emoji} **${userType}** allowlist!`); await interaction.followUp({ content: `<@${user.id}> has been removed from the ${counter.emoji} **${userType}** allowlist.` @@ -350,15 +362,14 @@ module.exports = { try { counter = await findCounter(type, interaction.guildId!); } catch(err) { - await interaction.followUp({ - content: 'No such counter!' - }); - return; + return interaction.followUp('No such counter!'); } const config = await getCounterConfigRaw(counter); const key = interaction.options.getString('key', true); const value = interaction.options.getString('value', true); + + if (key === 'emoji' && counter.linkedItem) return interaction.followUp(`Cannot modify emoji - this counter is linked to ${formatItem(await getItem(counter.linkedItem))}`); const defaultConfig = counterConfigs.get(key); if (!defaultConfig) return interaction.followUp(`No config named \`${key}\` exists!`); @@ -366,7 +377,7 @@ module.exports = { const parsedValue = parseConfig(value, defaultConfig.type); const restringedValue = toStringConfig(parsedValue, defaultConfig.type); - await setCounterConfig(counter.id, key, restringedValue); + await setCounterConfig(counter, key, restringedValue); await interaction.followUp(`${counter.emoji} \`${key}\` is now \`${restringedValue}\`. (was \`${config.get(key) || toStringConfig(defaultConfig.default, defaultConfig.type)}\`)`); } else if (subcommand === 'delete') { @@ -376,10 +387,7 @@ module.exports = { try { counter = await findCounter(type, interaction.guildId!); } catch(err) { - await interaction.followUp({ - content: 'No such counter!' - }); - return; + return interaction.followUp('No such counter!'); } await db('counters') @@ -400,6 +408,32 @@ module.exports = { await interaction.followUp(counters.map(c => `${c.emoji} **${c.value}** <#${c.channel}>`).join('\n')); } else if (subcommand === 'help') { await interaction.followUp(help.get(interaction.options.getString('topic', true))!); + } else if (subcommand === 'link') { + const type = interaction.options.getString('type', true); + const itemID = parseInt(interaction.options.getString('item', true)); + + let counter; + try { + counter = await findCounter(type, interaction.guildId!); + } catch(err) { + return interaction.followUp('No such counter!'); + } + + const item = await getItem(itemID); + if (!item) return interaction.followUp('No such item exists!'); + + await db('counters') + .where('id', counter.id) + .update({ + 'linkedItem': item.id, + 'emoji': item.emoji, + 'value': 0 + }); + + await setCounterConfig(counter, 'canIncrement', 'false'); + await setCounterConfig(counter, 'canDecrement', 'false'); + + await interaction.followUp(`Done. **The counter has been reset** to ${formatItems(item, 0)}. Users will not be able to take out or put in items until you enable this with \`canTake\` or \`canPut\`.\n\`canIncrement\` and \`canDecrement\` have also been **automatically disabled**, and you are recommended to keep them as such if you want to maintain balance in the universe.`); } } }, @@ -409,6 +443,8 @@ module.exports = { if (focused.name === 'type') { return counterAutocomplete(interaction); + } else if (focused.name === 'item') { + return itemAutocomplete(interaction); } else if (focused.name === 'value') { const type = interaction.options.getString('type', true); const counter = await findCounter(type, interaction.guildId!); diff --git a/src/commands/item.ts b/src/commands/item.ts index 83a850b..a6f77ed 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../lib/db'; -import { behaviors, defaultItems, formatItems, getItem, getMaxStack } from '../lib/items'; +import { behaviors, formatItems, getItem, getMaxStack, giveItem, itemAutocomplete } from '../lib/items'; //function extendOption(t: string) { // return {name: t, value: t}; @@ -204,32 +204,9 @@ module.exports = { const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); - const storedItem = await db('itemInventories') - .where('user', user.id) - .where('item', itemID) - .first(); + const inv = await giveItem(user.id, item, quantity); - let inv; - if (storedItem) { - inv = await db('itemInventories') - .update({ - 'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) - }) - .limit(1) - .where('user', user.id) - .where('item', itemID) - .returning('*'); - } else { - inv = await db('itemInventories') - .insert({ - 'user': user.id, - 'item': Math.min(itemID, getMaxStack(item)), - 'quantity': quantity - }) - .returning('*'); - } - - await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv[0].quantity)}.`); + await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`); } } }, @@ -238,20 +215,7 @@ module.exports = { const focused = interaction.options.getFocused(true); if (focused.name === 'item') { - const customItems = await db('customItems') - .select('emoji', 'name', 'id') - // @ts-expect-error this LITERALLY works - .whereLike(db.raw('UPPER(name)'), `%${focused.value.toUpperCase()}%`) - .where('guild', interaction.guildId!) - .limit(25); - - const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.value.toUpperCase())); - - const items = [...foundDefaultItems, ...customItems]; - - await interaction.respond( - items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) - ); + return itemAutocomplete(interaction); } } }; \ No newline at end of file diff --git a/src/commands/put.ts b/src/commands/put.ts new file mode 100644 index 0000000..5be26de --- /dev/null +++ b/src/commands/put.ts @@ -0,0 +1,36 @@ +import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('put') + .setDescription('Put an item from your inventory into the counter') + .addStringOption(option => + option + .setName('type') + .setAutocomplete(true) + .setDescription('The name of the counter') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('amount') + .setRequired(false) + .setDescription('Amount of items to put in') + .setMinValue(1) + ) + .setDMPermission(false), + + execute: async (interaction: Interaction, member: GuildMember) => { + if (!interaction.isChatInputCommand()) return; + + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); + const type = interaction.options.getString('type')!; + + await interaction.deferReply({ephemeral: true}); + + changeLinkedCounterInteraction(interaction, member, amount, type); + }, + + autocomplete: linkedCounterAutocomplete +}; \ No newline at end of file diff --git a/src/commands/take.ts b/src/commands/take.ts new file mode 100644 index 0000000..d66a416 --- /dev/null +++ b/src/commands/take.ts @@ -0,0 +1,36 @@ +import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('take') + .setDescription('Take an item from a counter') + .addStringOption(option => + option + .setName('type') + .setAutocomplete(true) + .setDescription('The name of the counter') + .setRequired(true) + ) + .addIntegerOption((option) => + option + .setName('amount') + .setRequired(false) + .setDescription('Amount of items to take') + .setMinValue(1) + ) + .setDMPermission(false), + + execute: async (interaction: Interaction, member: GuildMember) => { + if (!interaction.isChatInputCommand()) return; + + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); + const type = interaction.options.getString('type')!; + + await interaction.deferReply({ephemeral: true}); + + changeLinkedCounterInteraction(interaction, member, -amount, type); + }, + + autocomplete: linkedCounterAutocomplete +}; \ No newline at end of file diff --git a/src/lib/counter.ts b/src/lib/counter.ts index 5c04b83..fe6d2f4 100644 --- a/src/lib/counter.ts +++ b/src/lib/counter.ts @@ -1,6 +1,7 @@ -import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction } from 'discord.js'; +import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction, User } from 'discord.js'; import { getSign } from './util'; import { Counter, CounterConfiguration, CounterUserLink, db } from './db'; +import { formatItems, getItem, getItemQuantity, getMaxStack, giveItem } from './items'; export async function getCounter(id: number) { const counter = await db('counters') @@ -83,11 +84,11 @@ export async function getCounterConfig(id: number, key: string) { return value; } -export async function setCounterConfig(id: number, option: string, value: string) { +export async function setCounterConfig(counter: Counter, option: string, value: string) { // just the ugly way of life - if (option === 'emoji') { + if (option === 'emoji' && !counter.linkedItem) { await db('counters') - .where('id', id) + .where('id', counter.id) .update({ 'emoji': value }); @@ -98,13 +99,13 @@ export async function setCounterConfig(id: number, option: string, value: string .update({ value: value }) - .where('id', id) + .where('id', counter.id) .where('configName', option); if (updated === 0) { await db('counterConfigurations') .insert({ - 'id': id, + 'id': counter.id, 'configName': option, 'value': value }); @@ -174,6 +175,30 @@ export const counterConfigs = new Map([ type: ConfigType.String, default: 'null' }], + ['messageTemplateTake', { + type: ConfigType.String, + default: '**%user** has taken **%amt** from the counter.' + }], + ['messageTemplatePut', { + type: ConfigType.String, + default: '**%user** has put **%amt** into the counter.' + }], + ['canIncrement', { + type: ConfigType.Bool, + default: true + }], + ['canDecrement', { + type: ConfigType.Bool, + default: true + }], + ['canPut', { + type: ConfigType.Bool, + default: false + }], + ['canTake', { + type: ConfigType.Bool, + default: false + }], // these ones are fake and are just stand-ins for values defined inside the actual counters table ['emoji', { @@ -210,7 +235,7 @@ export async function updateCounter(bot: Client, counter: Counter, value: number } } -export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number) { +export async function announceCounterUpdate(bot: Client, member: GuildMember, delta: number, counter: Counter, value: number, linked: boolean = false) { const channel = await bot.channels.fetch(counter.channel) as TextChannel; let template = await getCounterConfig(counter.id, 'messageTemplate') as string; @@ -218,6 +243,10 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de if (templateIncrease !== 'null' && delta > 0) template = templateIncrease; const templateDecrease = await getCounterConfig(counter.id, 'messageTemplateDecrease') as string; if (templateDecrease !== 'null' && delta < 0) template = templateDecrease; + const templatePut = await getCounterConfig(counter.id, 'messageTemplatePut') as string; + if (templatePut !== 'null' && delta > 0 && linked) template = templatePut; + const templateTake = await getCounterConfig(counter.id, 'messageTemplateTake') as string; + if (templateTake !== 'null' && delta < 0 && linked) template = templateTake; const anonymous = await getCounterConfig(counter.id, 'anonymous') as boolean; @@ -226,7 +255,7 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de .setDescription( template .replaceAll('%user', anonymous ? 'someone' : member.toString()) - .replaceAll('%action', delta > 0 ? 'increased' : 'decreased') + .replaceAll('%action', delta > 0 ? (linked ? 'put into' : 'increased') : (linked ? 'taken from' : 'decreased')) .replaceAll('%amt', Math.abs(delta).toString()) .replaceAll('%total', value.toString()) ) @@ -249,66 +278,107 @@ export async function announceCounterUpdate(bot: Client, member: GuildMember, de }); } -export async function changeCounterInteraction(interaction: CommandInteraction, member: GuildMember, amount: number, type: string) { - try { - const counter = await findCounter(type, member.guild.id); +async function canUseCounter(user: User, counter: Counter, amount: number, isLinkedAction = false): Promise { + if (amount > 0 && !(await getCounterConfig(counter.id, isLinkedAction ? 'canPut' : 'canIncrement') as boolean)) return false; + if (amount > 0 && counter.allowlistProducer) { + const userLink = await db('counterUserLink') + .where('id', counter.id) + .where('user', user.id) + .where('producer', true) + .first(); - let canUse = true; - if (amount > 0 && counter.allowlistProducer) { - const userLink = await db('counterUserLink') - .where('id', counter.id) - .where('user', member.id) - .where('producer', true) - .first(); - - if (!userLink) canUse = false; - } - if (amount < 0 && counter.allowlistConsumer) { - const userLink = await db('counterUserLink') - .where('id', counter.id) - .where('user', member.id) - .where('producer', false) - .first(); - - if (!userLink) canUse = false; - } - - if (!canUse) { - await interaction.followUp({ - content: `You cannot **${amount > 0 ? 'produce' : 'consume'}** ${counter.emoji}.` - }); - return; - } - - const newCount = await changeCounter(counter.id, amount); - await updateCounter(interaction.client, counter, newCount); - await announceCounterUpdate(interaction.client, member, amount, counter, newCount); - await interaction.followUp({ - content: `${counter.emoji} **You have ${amount > 0 ? 'increased' : 'decreased'} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\`` - }); - } catch(err) { - await interaction.followUp({ - content: (err as Error).toString() - }); + if (!userLink) return false; } + if (amount < 0 && !(await getCounterConfig(counter.id, isLinkedAction ? 'canTake' : 'canDecrement') as boolean)) return false; + if (amount < 0 && counter.allowlistConsumer) { + const userLink = await db('counterUserLink') + .where('id', counter.id) + .where('user', user.id) + .where('producer', false) + .first(); + + if (!userLink) return false; + } + + return true; } -export async function counterAutocomplete(interaction: AutocompleteInteraction) { - const focusedValue = interaction.options.getFocused(); - const guild = interaction.guildId; +function changeCounterInteractionBuilder(linked: boolean) { + return async (interaction: CommandInteraction, member: GuildMember, amount: number, type: string) => { + try { + const counter = await findCounter(type, member.guild.id); + if (linked && !counter.linkedItem) return interaction.followUp('There is no such linked counter!'); - const query = db('counters') - .select('emoji', 'key') - .whereLike('key', `%${focusedValue.toLowerCase()}%`) - .limit(25); + const canUse = await canUseCounter(member.user, counter, amount, linked); - if (guild) { - query.where('guild', guild); - } + if (!canUse) { + return interaction.followUp(`You cannot **${amount > 0 ? (linked ? 'put' : 'produce') : (linked ? 'take' : 'consume')}** ${counter.emoji}.`); + } - const foundCounters = await query; + let item; + let newInv; + if (linked) { + const inv = await getItemQuantity(member.id, counter.linkedItem!); + item = (await getItem(counter.linkedItem!))!; - await interaction.respond( - foundCounters.map(choice => ({ name: choice.emoji, value: choice.key })) - ); -} \ No newline at end of file + // change counter by -10 = increment own counter by 10 + const amtInv = -amount; + const amtAbs = Math.abs(amtInv); + + if (amtInv > getMaxStack(item)) { + return interaction.followUp(`You cannot take ${formatItems(item, amtAbs)}, because the max stack size is ${getMaxStack(item)}x!`); + } + if ((inv.quantity + amtInv) > getMaxStack(item)) { + return interaction.followUp(`You cannot take ${formatItems(item, amtAbs)}, because the max stack size is ${getMaxStack(item)}x and you already have ${inv.quantity}x!`); + } + if ((inv.quantity + amtInv) < 0) { + return interaction.followUp(`You cannot put in ${formatItems(item, amtAbs)}, as you only have ${formatItems(item, inv.quantity)}!`); + } + + newInv = await giveItem(member.id, item, amtInv); + } + + const newCount = await changeCounter(counter.id, amount); + await updateCounter(interaction.client, counter, newCount); + await announceCounterUpdate(interaction.client, member, amount, counter, newCount, linked); + await interaction.followUp({ + content: `${counter.emoji} **You have ${amount > 0 ? (linked ? 'put into' : 'increased') : (linked ? 'taken from' : 'decreased')} the counter.**\n\`\`\`diff\n ${newCount - amount}\n${getSign(amount)}${Math.abs(amount)}\n ${newCount}\`\`\`${newInv ? `\nYou now have ${formatItems(item, newInv.quantity)}.` : ''}` + }); + } catch(err) { + await interaction.followUp({ + content: (err as Error).toString() + }); + } + }; +} + +export const changeCounterInteraction = changeCounterInteractionBuilder(false); +export const changeLinkedCounterInteraction = changeCounterInteractionBuilder(true); + +function counterAutocompleteBuilder(linked: boolean) { + return async (interaction: AutocompleteInteraction) => { + const focusedValue = interaction.options.getFocused(); + const guild = interaction.guildId; + + const query = db('counters') + .select('emoji', 'key') + .whereLike('key', `%${focusedValue.toLowerCase()}%`) + .limit(25); + + if (guild) { + query.where('guild', guild); + } + if (linked) { + query.whereNotNull('linkedItem'); + } + + const foundCounters = await query; + + await interaction.respond( + foundCounters.map(choice => ({ name: choice.emoji, value: choice.key })) + ); + }; +} + +export const counterAutocomplete = counterAutocompleteBuilder(false); +export const linkedCounterAutocomplete = counterAutocompleteBuilder(true); \ No newline at end of file diff --git a/src/lib/db.ts b/src/lib/db.ts index 73da31e..6ce77c3 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -33,7 +33,8 @@ export interface Counter { guild: string, message?: string, allowlistConsumer: boolean, - allowlistProducer: boolean + allowlistProducer: boolean, + linkedItem?: number } export interface CounterUserLink { id: number, diff --git a/src/lib/items.ts b/src/lib/items.ts index ad4a160..688cd46 100644 --- a/src/lib/items.ts +++ b/src/lib/items.ts @@ -1,5 +1,5 @@ -import { User } from 'discord.js'; -import { CustomItem, db } from './db'; +import { AutocompleteInteraction, User } from 'discord.js'; +import { CustomItem, ItemInventory, db } from './db'; type DefaultItem = Omit; // uses negative IDs type Item = DefaultItem | CustomItem; @@ -53,13 +53,74 @@ export async function getItem(id: number): Promise { } } +export async function getItemQuantity(user: string, itemID: number) { + return (await db('itemInventories') + .where('item', itemID) + .where('user', user) + .first()) + || { + 'user': user, + 'item': itemID, + 'quantity': 0 + }; +} + +export async function giveItem(user: string, item: Item, quantity = 1) { + const storedItem = await db('itemInventories') + .where('user', user) + .where('item', item.id) + .first(); + + let inv; + if (storedItem) { + inv = await db('itemInventories') + .update({ + 'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) + }) + .limit(1) + .where('user', user) + .where('item', item.id) + .returning('*'); + } else { + inv = await db('itemInventories') + .insert({ + 'user': user, + 'item': Math.min(item.id, getMaxStack(item)), + 'quantity': quantity + }) + .returning('*'); + } + + return inv[0]; +} + export function getMaxStack(item: Item) { return item.type === 'weapon' ? 1 : item.maxStack; } -export function formatItem(item: Item) { +export function formatItem(item: Item | undefined) { + if (!item) return '? **MISSINGNO**'; return `${item.emoji} **${item.name}**`; } -export function formatItems(item: Item, quantity: number) { +export function formatItems(item: Item | undefined, quantity: number) { return `${quantity}x ${formatItem(item)}`; +} + +export async function itemAutocomplete(interaction: AutocompleteInteraction) { + const focused = interaction.options.getFocused(); + + const customItems = await db('customItems') + .select('emoji', 'name', 'id') + // @ts-expect-error this LITERALLY works + .whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`) + .where('guild', interaction.guildId!) + .limit(25); + + const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); + + const items = [...foundDefaultItems, ...customItems]; + + await interaction.respond( + items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) + ); } \ No newline at end of file From 233a663d0c33766888439b641dc8fe5c445c6d33 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:08:01 +0300 Subject: [PATCH 04/66] light counter consistency stuff --- src/commands/counter.ts | 8 +++++--- src/lib/counter.ts | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/commands/counter.ts b/src/commands/counter.ts index 4ac2a98..6282cea 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -144,7 +144,7 @@ module.exports = { .addStringOption(option => option .setName('key') - .setDescription('The codename. Best to leave descriptive for later; used in searching for counters') + .setDescription('Give your counter a simple name') .setRequired(true) ) .addStringOption(option => @@ -399,13 +399,13 @@ module.exports = { .delete(); await interaction.followUp({ - content: `The ${counter.emoji} counter has been removed. 😭` + content: `The ${counter.emoji} ${counter.key} counter has been removed. 😭` }); } else if (subcommand === 'list') { const counters = await db('counters') .where('guild', interaction.guildId!); - await interaction.followUp(counters.map(c => `${c.emoji} **${c.value}** <#${c.channel}>`).join('\n')); + await interaction.followUp(counters.map(c => `${c.emoji} ${c.key}: **${c.value}** <#${c.channel}>`).join('\n')); } else if (subcommand === 'help') { await interaction.followUp(help.get(interaction.options.getString('topic', true))!); } else if (subcommand === 'link') { @@ -421,12 +421,14 @@ module.exports = { const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); + if (item.untradable) return interaction.followUp('This item is untradable!'); await db('counters') .where('id', counter.id) .update({ 'linkedItem': item.id, 'emoji': item.emoji, + 'key': item.name, 'value': 0 }); diff --git a/src/lib/counter.ts b/src/lib/counter.ts index fe6d2f4..db7992d 100644 --- a/src/lib/counter.ts +++ b/src/lib/counter.ts @@ -375,7 +375,7 @@ function counterAutocompleteBuilder(linked: boolean) { const foundCounters = await query; await interaction.respond( - foundCounters.map(choice => ({ name: choice.emoji, value: choice.key })) + foundCounters.map(choice => ({ name: `${choice.emoji} ${choice.key}`, value: choice.key })) ); }; } From 2bb3512316a0ca0ad426baec6ac5bf0f9ba8d559 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:18:25 +0300 Subject: [PATCH 05/66] min and max limits for counters --- src/commands/counter.ts | 3 ++- src/lib/counter.ts | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/commands/counter.ts b/src/commands/counter.ts index 6282cea..450e0d1 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -434,8 +434,9 @@ module.exports = { await setCounterConfig(counter, 'canIncrement', 'false'); await setCounterConfig(counter, 'canDecrement', 'false'); + await setCounterConfig(counter, 'min', '0'); - await interaction.followUp(`Done. **The counter has been reset** to ${formatItems(item, 0)}. Users will not be able to take out or put in items until you enable this with \`canTake\` or \`canPut\`.\n\`canIncrement\` and \`canDecrement\` have also been **automatically disabled**, and you are recommended to keep them as such if you want to maintain balance in the universe.`); + await interaction.followUp(`Done. **The counter has been reset** to ${formatItems(item, 0)}. Users will not be able to take out or put in items until you enable this with \`canTake\` or \`canPut\`.\n\`canIncrement\` and \`canDecrement\` have also been **automatically disabled** and \`min\` has been set to **0**, and you are recommended to keep these values as such if you want to maintain balance in the universe.`); } } }, diff --git a/src/lib/counter.ts b/src/lib/counter.ts index db7992d..bd3cb6f 100644 --- a/src/lib/counter.ts +++ b/src/lib/counter.ts @@ -199,6 +199,14 @@ export const counterConfigs = new Map([ type: ConfigType.Bool, default: false }], + ['min', { + type: ConfigType.Number, + default: -Number.MIN_SAFE_INTEGER + }], + ['max', { + type: ConfigType.Number, + default: Number.MAX_SAFE_INTEGER + }], // these ones are fake and are just stand-ins for values defined inside the actual counters table ['emoji', { @@ -338,6 +346,19 @@ function changeCounterInteractionBuilder(linked: boolean) { newInv = await giveItem(member.id, item, amtInv); } + const min = await getCounterConfig(counter.id, 'min') as number; + const max = await getCounterConfig(counter.id, 'max') as number; + if (counter.value + amount < min) { + if (min === 0) { + return interaction.followUp(`You cannot remove more than how much is in the counter (${counter.value}x ${counter.emoji})!`); + } else { + return interaction.followUp(`You cannot decrement past the minimum value (${min})!`); + } + } + if (counter.value + amount > max) { + return interaction.followUp(`You are adding more than the counter can hold (${max}x)!`); + } + const newCount = await changeCounter(counter.id, amount); await updateCounter(interaction.client, counter, newCount); await announceCounterUpdate(interaction.client, member, amount, counter, newCount, linked); From 7f6607f3d955dd7641064db0ac49b11923236f85 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:56:46 +0300 Subject: [PATCH 06/66] rough data draft of recipes --- src/lib/items.ts | 108 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/src/lib/items.ts b/src/lib/items.ts index 688cd46..87d6437 100644 --- a/src/lib/items.ts +++ b/src/lib/items.ts @@ -16,6 +16,12 @@ interface Behavior { action?: (item: Item, user: User) => Promise } +enum DefaultItems { + COIN = 1, + WORKBENCH = 2, + PEBBLE = 3 +} + export const defaultItems: DefaultItem[] = [ { 'id': -1, @@ -24,6 +30,24 @@ export const defaultItems: DefaultItem[] = [ 'type': 'plain', 'maxStack': 9999, 'untradable': false + }, + { + 'id': -2, + 'name': 'Workbench', + 'description': 'A place for you to work with tools, for simple things', + 'emoji': '🛠️', + 'type': 'plain', + 'maxStack': 1, + 'untradable': false + }, + { + 'id': -3, + 'name': 'Pebble', + 'description': 'If you get 5 of them you will instantly ! !!!', + 'emoji': '🪨', + 'type': 'plain', + 'maxStack': 64, + 'untradable': false } ]; @@ -39,17 +63,96 @@ export const behaviors: Behavior[] = [ } ]; +interface Items { + item: Item, + quantity: number +} + +const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => + `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; + +interface CraftingStation { + key: string, + name: string, + description: string, + emoji: string, + requires?: Item, + // in seconds + cooldown?: number, + formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[]) => string, + manipulateResults?: (outputs: Items[]) => Items[] +} + +export const craftingStations: CraftingStation[] = [ + { + key: 'forage', + name: 'Forage', + description: 'Pick up various sticks and stones from the forest', + emoji: '🌲', + cooldown: 60 * 5, + formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, + manipulateResults: (outputs) => + outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) + }, + { + key: 'hand', + name: 'Hand', + description: 'You can use your hands to make a small assortment of things', + emoji: '✋' + }, + { + key: 'workbench', + name: 'Workbench', + description: 'A place for you to work with tools, for simple things', + emoji: '🛠️', + requires: getDefaultItem(DefaultItems.WORKBENCH) + } +]; + +interface Recipe { + station: string, + inputs: Items[], + requirements: Items[], + outputs: Items[] +} + +export const recipes: Recipe[] = [ + { + station: 'forage', + inputs: [], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ] + }, + { + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } + ] + } +]; + export async function getCustomItem(id: number) { return await db('customItems') .where('id', id) .first(); } +export function getDefaultItem(id: DefaultItems): Item +export function getDefaultItem(id: number): Item | undefined { + return defaultItems.find(item => Math.abs(item.id) === Math.abs(id)); +} + export async function getItem(id: number): Promise { if (id >= 0) { return await getCustomItem(id); } else { - return defaultItems.find(item => item.id === id); + return getDefaultItem(id); } } @@ -105,6 +208,9 @@ export function formatItem(item: Item | undefined) { export function formatItems(item: Item | undefined, quantity: number) { return `${quantity}x ${formatItem(item)}`; } +export function formatItemsArray(items: Items[]) { + return items.map(i => formatItems(i.item, i.quantity)).join(' '); +} export async function itemAutocomplete(interaction: AutocompleteInteraction) { const focused = interaction.options.getFocused(); From c6e5b9a00f8f574db962c33e8df86925bf8efab2 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:57:20 +0300 Subject: [PATCH 07/66] move stuff to rpg folder --- src/commands/counter.ts | 4 ++-- src/commands/decrease.ts | 2 +- src/commands/increase.ts | 2 +- src/commands/inventory.ts | 2 +- src/commands/item.ts | 4 ++-- src/commands/put.ts | 2 +- src/commands/take.ts | 2 +- src/lib/{ => rpg}/counter.ts | 4 ++-- src/lib/{ => rpg}/items.ts | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) rename src/lib/{ => rpg}/counter.ts (99%) rename src/lib/{ => rpg}/items.ts (99%) diff --git a/src/commands/counter.ts b/src/commands/counter.ts index 450e0d1..63a9d68 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -1,8 +1,8 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; import { Counter, CounterUserLink, db } from '../lib/db'; -import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/counter'; +import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/rpg/counter'; import { outdent } from 'outdent'; -import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/items'; +import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/rpg/items'; function extendOption(t: string) { return {name: t, value: t}; diff --git a/src/commands/decrease.ts b/src/commands/decrease.ts index 8b80a92..1198b06 100644 --- a/src/commands/decrease.ts +++ b/src/commands/decrease.ts @@ -1,5 +1,5 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction, counterAutocomplete } from '../lib/counter'; +import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/commands/increase.ts b/src/commands/increase.ts index 54be345..5364460 100644 --- a/src/commands/increase.ts +++ b/src/commands/increase.ts @@ -1,5 +1,5 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeCounterInteraction, counterAutocomplete } from '../lib/counter'; +import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index fd3aba8..06ba5d7 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -1,6 +1,6 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; import { ItemInventory, db } from '../lib/db'; -import { formatItems, getItem } from '../lib/items'; +import { formatItems, getItem } from '../lib/rpg/items'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/commands/item.ts b/src/commands/item.ts index a6f77ed..a3b4f8e 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; -import { CustomItem, ItemInventory, db } from '../lib/db'; -import { behaviors, formatItems, getItem, getMaxStack, giveItem, itemAutocomplete } from '../lib/items'; +import { CustomItem, db } from '../lib/db'; +import { behaviors, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; //function extendOption(t: string) { // return {name: t, value: t}; diff --git a/src/commands/put.ts b/src/commands/put.ts index 5be26de..09e5f7f 100644 --- a/src/commands/put.ts +++ b/src/commands/put.ts @@ -1,5 +1,5 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter'; +import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/commands/take.ts b/src/commands/take.ts index d66a416..2162989 100644 --- a/src/commands/take.ts +++ b/src/commands/take.ts @@ -1,5 +1,5 @@ import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/counter'; +import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/lib/counter.ts b/src/lib/rpg/counter.ts similarity index 99% rename from src/lib/counter.ts rename to src/lib/rpg/counter.ts index bd3cb6f..543924d 100644 --- a/src/lib/counter.ts +++ b/src/lib/rpg/counter.ts @@ -1,6 +1,6 @@ import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, AutocompleteInteraction, User } from 'discord.js'; -import { getSign } from './util'; -import { Counter, CounterConfiguration, CounterUserLink, db } from './db'; +import { getSign } from '../util'; +import { Counter, CounterConfiguration, CounterUserLink, db } from '../db'; import { formatItems, getItem, getItemQuantity, getMaxStack, giveItem } from './items'; export async function getCounter(id: number) { diff --git a/src/lib/items.ts b/src/lib/rpg/items.ts similarity index 99% rename from src/lib/items.ts rename to src/lib/rpg/items.ts index 87d6437..d87890c 100644 --- a/src/lib/items.ts +++ b/src/lib/rpg/items.ts @@ -1,5 +1,5 @@ import { AutocompleteInteraction, User } from 'discord.js'; -import { CustomItem, ItemInventory, db } from './db'; +import { CustomItem, ItemInventory, db } from '../db'; type DefaultItem = Omit; // uses negative IDs type Item = DefaultItem | CustomItem; From b0389f3e585848930ea60e4e6837e7a217bbcf1c Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 13:59:38 +0300 Subject: [PATCH 08/66] move items, recipes etc to data file --- src/lib/rpg/data.ts | 95 ++++++++++++++++++++++++++++++++++++++ src/lib/rpg/items.ts | 106 +++---------------------------------------- 2 files changed, 101 insertions(+), 100 deletions(-) create mode 100644 src/lib/rpg/data.ts diff --git a/src/lib/rpg/data.ts b/src/lib/rpg/data.ts new file mode 100644 index 0000000..234d82a --- /dev/null +++ b/src/lib/rpg/data.ts @@ -0,0 +1,95 @@ +import { Behavior, CraftingStation, DefaultItem, Recipe, formatItems, getDefaultItem } from './items'; + +enum DefaultItems { + COIN = 1, + WORKBENCH = 2, + PEBBLE = 3 +} + +export const defaultItems: DefaultItem[] = [ + { + 'id': -1, + 'name': 'Coin', + 'emoji': '🪙', + 'type': 'plain', + 'maxStack': 9999, + 'untradable': false + }, + { + 'id': -2, + 'name': 'Workbench', + 'description': 'A place for you to work with tools, for simple things', + 'emoji': '🛠️', + 'type': 'plain', + 'maxStack': 1, + 'untradable': false + }, + { + 'id': -3, + 'name': 'Pebble', + 'description': 'If you get 5 of them you will instantly ! !!!', + 'emoji': '🪨', + 'type': 'plain', + 'maxStack': 64, + 'untradable': false + } +]; + +export const behaviors: Behavior[] = [ + { + 'name': 'heal', + 'description': 'Heals the user by `behaviorValue`', + 'itemType': 'consumable', + 'action': async (item, user) => { + // todo + return false; + } + } +]; + +export const craftingStations: CraftingStation[] = [ + { + key: 'forage', + name: 'Forage', + description: 'Pick up various sticks and stones from the forest', + emoji: '🌲', + cooldown: 60 * 5, + formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, + manipulateResults: (outputs) => + outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) + }, + { + key: 'hand', + name: 'Hand', + description: 'You can use your hands to make a small assortment of things', + emoji: '✋' + }, + { + key: 'workbench', + name: 'Workbench', + description: 'A place for you to work with tools, for simple things', + emoji: '🛠️', + requires: getDefaultItem(DefaultItems.WORKBENCH) + } +]; + +export const recipes: Recipe[] = [ + { + station: 'forage', + inputs: [], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ] + }, + { + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } + ] + } +]; diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index d87890c..fafe6b9 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,10 +1,10 @@ import { AutocompleteInteraction, User } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; -type DefaultItem = Omit; // uses negative IDs -type Item = DefaultItem | CustomItem; +export type DefaultItem = Omit; // uses negative IDs +export type Item = DefaultItem | CustomItem; -interface Behavior { +export interface Behavior { name: string, description: string, itemType: 'plain' | 'weapon' | 'consumable', @@ -16,54 +16,7 @@ interface Behavior { action?: (item: Item, user: User) => Promise } -enum DefaultItems { - COIN = 1, - WORKBENCH = 2, - PEBBLE = 3 -} - -export const defaultItems: DefaultItem[] = [ - { - 'id': -1, - 'name': 'Coin', - 'emoji': '🪙', - 'type': 'plain', - 'maxStack': 9999, - 'untradable': false - }, - { - 'id': -2, - 'name': 'Workbench', - 'description': 'A place for you to work with tools, for simple things', - 'emoji': '🛠️', - 'type': 'plain', - 'maxStack': 1, - 'untradable': false - }, - { - 'id': -3, - 'name': 'Pebble', - 'description': 'If you get 5 of them you will instantly ! !!!', - 'emoji': '🪨', - 'type': 'plain', - 'maxStack': 64, - 'untradable': false - } -]; - -export const behaviors: Behavior[] = [ - { - 'name': 'heal', - 'description': 'Heals the user by `behaviorValue`', - 'itemType': 'consumable', - 'action': async (item: Item, user: User) => { - // todo - return false; - } - } -]; - -interface Items { +export interface Items { item: Item, quantity: number } @@ -71,7 +24,7 @@ interface Items { const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; -interface CraftingStation { +export interface CraftingStation { key: string, name: string, description: string, @@ -83,60 +36,13 @@ interface CraftingStation { manipulateResults?: (outputs: Items[]) => Items[] } -export const craftingStations: CraftingStation[] = [ - { - key: 'forage', - name: 'Forage', - description: 'Pick up various sticks and stones from the forest', - emoji: '🌲', - cooldown: 60 * 5, - formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, - manipulateResults: (outputs) => - outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) - }, - { - key: 'hand', - name: 'Hand', - description: 'You can use your hands to make a small assortment of things', - emoji: '✋' - }, - { - key: 'workbench', - name: 'Workbench', - description: 'A place for you to work with tools, for simple things', - emoji: '🛠️', - requires: getDefaultItem(DefaultItems.WORKBENCH) - } -]; - -interface Recipe { +export interface Recipe { station: string, inputs: Items[], requirements: Items[], outputs: Items[] } -export const recipes: Recipe[] = [ - { - station: 'forage', - inputs: [], - requirements: [], - outputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } - ] - }, - { - station: 'workbench', - inputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } - ], - requirements: [], - outputs: [ - { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } - ] - } -]; - export async function getCustomItem(id: number) { return await db('customItems') .where('id', id) From 68d7e2833533c946332814d79434cc1cbe52ed1b Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 14:01:51 +0300 Subject: [PATCH 09/66] stop using string keys in objects everywhere ffs --- src/lib/rpg/counter.ts | 10 ++++----- src/lib/rpg/data.ts | 50 +++++++++++++++++++++--------------------- src/lib/rpg/items.ts | 15 +++++++------ 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/lib/rpg/counter.ts b/src/lib/rpg/counter.ts index 543924d..a9e24b9 100644 --- a/src/lib/rpg/counter.ts +++ b/src/lib/rpg/counter.ts @@ -90,7 +90,7 @@ export async function setCounterConfig(counter: Counter, option: string, value: await db('counters') .where('id', counter.id) .update({ - 'emoji': value + emoji: value }); return; } @@ -105,9 +105,9 @@ export async function setCounterConfig(counter: Counter, option: string, value: if (updated === 0) { await db('counterConfigurations') .insert({ - 'id': counter.id, - 'configName': option, - 'value': value + id: counter.id, + configName: option, + value: value }); } } @@ -238,7 +238,7 @@ export async function updateCounter(bot: Client, counter: Counter, value: number await db('counters') .where('id', counter.id) .update({ - 'message': message.id + message: message.id }); } } diff --git a/src/lib/rpg/data.ts b/src/lib/rpg/data.ts index 234d82a..9081962 100644 --- a/src/lib/rpg/data.ts +++ b/src/lib/rpg/data.ts @@ -1,6 +1,6 @@ import { Behavior, CraftingStation, DefaultItem, Recipe, formatItems, getDefaultItem } from './items'; -enum DefaultItems { +export enum DefaultItems { COIN = 1, WORKBENCH = 2, PEBBLE = 3 @@ -8,39 +8,39 @@ enum DefaultItems { export const defaultItems: DefaultItem[] = [ { - 'id': -1, - 'name': 'Coin', - 'emoji': '🪙', - 'type': 'plain', - 'maxStack': 9999, - 'untradable': false + id: -1, + name: 'Coin', + emoji: '🪙', + type: 'plain', + maxStack: 9999, + untradable: false }, { - 'id': -2, - 'name': 'Workbench', - 'description': 'A place for you to work with tools, for simple things', - 'emoji': '🛠️', - 'type': 'plain', - 'maxStack': 1, - 'untradable': false + id: -2, + name: 'Workbench', + description: 'A place for you to work with tools, for simple things', + emoji: '🛠️', + type: 'plain', + maxStack: 1, + untradable: false }, { - 'id': -3, - 'name': 'Pebble', - 'description': 'If you get 5 of them you will instantly ! !!!', - 'emoji': '🪨', - 'type': 'plain', - 'maxStack': 64, - 'untradable': false + id: -3, + name: 'Pebble', + description: 'If you get 5 of them you will instantly ! !!!', + emoji: '🪨', + type: 'plain', + maxStack: 64, + untradable: false } ]; export const behaviors: Behavior[] = [ { - 'name': 'heal', - 'description': 'Heals the user by `behaviorValue`', - 'itemType': 'consumable', - 'action': async (item, user) => { + name: 'heal', + description: 'Heals the user by `behaviorValue`', + itemType: 'consumable', + action: async (item, user) => { // todo return false; } diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index fafe6b9..b94eba5 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,5 +1,6 @@ import { AutocompleteInteraction, User } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; +import { DefaultItems, defaultItems } from './data'; export type DefaultItem = Omit; // uses negative IDs export type Item = DefaultItem | CustomItem; @@ -68,9 +69,9 @@ export async function getItemQuantity(user: string, itemID: number) { .where('user', user) .first()) || { - 'user': user, - 'item': itemID, - 'quantity': 0 + user: user, + item: itemID, + quantity: 0 }; } @@ -84,7 +85,7 @@ export async function giveItem(user: string, item: Item, quantity = 1) { if (storedItem) { inv = await db('itemInventories') .update({ - 'quantity': db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) + quantity: db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) }) .limit(1) .where('user', user) @@ -93,9 +94,9 @@ export async function giveItem(user: string, item: Item, quantity = 1) { } else { inv = await db('itemInventories') .insert({ - 'user': user, - 'item': Math.min(item.id, getMaxStack(item)), - 'quantity': quantity + user: user, + item: Math.min(item.id, getMaxStack(item)), + quantity: quantity }) .returning('*'); } From 3fcfe5851b8547c66cb9ab4f9de262e4651808cf Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:04:36 +0300 Subject: [PATCH 10/66] crafting????? --- ...20231115140015_craftingStationCooldowns.js | 21 +++ src/commands/craft.ts | 122 ++++++++++++++++++ src/commands/item.ts | 3 +- src/lib/db.ts | 5 + src/lib/rpg/data.ts | 6 +- src/lib/rpg/items.ts | 30 ++++- 6 files changed, 181 insertions(+), 6 deletions(-) create mode 100644 migrations/20231115140015_craftingStationCooldowns.js create mode 100644 src/commands/craft.ts diff --git a/migrations/20231115140015_craftingStationCooldowns.js b/migrations/20231115140015_craftingStationCooldowns.js new file mode 100644 index 0000000..b170c23 --- /dev/null +++ b/migrations/20231115140015_craftingStationCooldowns.js @@ -0,0 +1,21 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('craftingStationCooldowns', table => { + table.string('station').notNullable(); + table.string('user').notNullable(); + table.timestamp('usedAt').notNullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('craftingStationCooldowns'); +}; diff --git a/src/commands/craft.ts b/src/commands/craft.ts new file mode 100644 index 0000000..158a081 --- /dev/null +++ b/src/commands/craft.ts @@ -0,0 +1,122 @@ +import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { craftingStations, defaultRecipes } from '../lib/rpg/data'; +import { canUseStation, formatItem, formatItems, formatItemsArray, formatRecipe, getItemQuantity, getMaxStack, getRecipe, getStation, giveItem } from '../lib/rpg/items'; +import { CraftingStationCooldown, db } from '../lib/db'; + +module.exports = { + data: new SlashCommandBuilder() + .setName('craft') + .setDescription('Craft an item with items you have') + .addStringOption(option => + option + .setName('station') + .setAutocomplete(true) + .setDescription('Which station to use') + .setRequired(true) + ) + .addStringOption(option => + option + .setName('recipe') + .setAutocomplete(true) + .setDescription('What to craft') + .setRequired(true) + ) + .setDMPermission(false), + + execute: async (interaction: Interaction, member: GuildMember) => { + if (!interaction.isChatInputCommand()) return; + + const recipeID = parseInt(interaction.options.getString('recipe', true)); + + await interaction.deferReply({ephemeral: true}); + + const recipe = getRecipe(recipeID); + if (!recipe) return interaction.followUp('Recipe does not exist!'); + + const station = getStation(recipe.station)!; + if (!canUseStation(member.id, station)) return interaction.followUp(`${station.emoji} You need ${formatItem(station.requires)} to use this station!`); + + for (const input of recipe.inputs) { + const inv = await getItemQuantity(member.id, input.item.id); + if (inv.quantity < input.quantity) return interaction.followUp(`You need ${formatItems(input.item, input.quantity)} for this recipe! (You have ${formatItems(input.item, inv.quantity)})`); + } + for (const req of recipe.requirements) { + const inv = await getItemQuantity(member.id, req.item.id); + if (inv.quantity < req.quantity) return interaction.followUp(`You need ${formatItems(req.item, req.quantity)} to begin this recipe! (You have ${formatItems(req.item, inv.quantity)}. Don't worry, these items will not be consumed.)`); + } + for (const out of recipe.outputs) { + const inv = await getItemQuantity(member.id, out.item.id); + if (inv.quantity + out.quantity > getMaxStack(out.item)) return interaction.followUp(`You do not have enough inventory storage for this recipe! (${formatItems(out.item, inv.quantity + out.quantity)} is bigger than the stack size of ${getMaxStack(out.item)}.)`); + } + + let cooldown; + if (station.cooldown) { + cooldown = await db('craftingStationCooldowns') + .where('station', station.key) + .where('user', member.id) + .first(); + + if (cooldown && (cooldown.usedAt + station.cooldown * 1000) > Date.now()) + return interaction.followUp(`${station.emoji} You can use this station again !`); + } + + // proceed with crafting! + + for (const input of recipe.inputs) { + giveItem(member.id, input.item, -input.quantity); + } + const outputs = station.manipulateResults ? station.manipulateResults(recipe.outputs) : recipe.outputs; + for (const output of outputs) { + giveItem(member.id, output.item, output.quantity); + } + + let nextUsableAt; + if (station.cooldown) { + if (!cooldown) { + await db('craftingStationCooldowns') + .insert({ + station: station.key, + user: member.id, + usedAt: db.fn.now() + }); + } else { + await db('craftingStationCooldowns') + .where('station', station.key) + .where('user', member.id) + .update({ + usedAt: db.fn.now() + }); + } + + nextUsableAt = Date.now() + station.cooldown * 1000; + } + + return interaction.followUp(`${station.emoji} Crafted ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again ` : ''}`); + }, + + autocomplete: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(true); + + if (focused.name === 'station') { + const found = craftingStations + .filter(station => canUseStation(interaction.user.id, station)) + .filter(station => station.name.toLowerCase().includes(focused.value.toLowerCase())) + .map(station => ({ + name: `${station.emoji} ${station.name}`, + value: station.key + })); + + return interaction.respond(found); + } else if (focused.name === 'recipe') { + const found = defaultRecipes + .filter(recipe => recipe.station === interaction.options.getString('station')) + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0) + .map(recipe => ({ + name: formatRecipe(recipe), + value: recipe.id + })); + + return interaction.respond(found); + } + } +}; \ No newline at end of file diff --git a/src/commands/item.ts b/src/commands/item.ts index a3b4f8e..9ac48a8 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,6 +1,7 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; import { CustomItem, db } from '../lib/db'; -import { behaviors, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; +import { formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; +import { behaviors } from '../lib/rpg/data'; //function extendOption(t: string) { // return {name: t, value: t}; diff --git a/src/lib/db.ts b/src/lib/db.ts index 6ce77c3..110a2a1 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -63,4 +63,9 @@ export interface ItemInventory { user: string, item: number, quantity: number +} +export interface CraftingStationCooldown { + station: string, + user: string, + usedAt: number } \ No newline at end of file diff --git a/src/lib/rpg/data.ts b/src/lib/rpg/data.ts index 9081962..bd5cb4a 100644 --- a/src/lib/rpg/data.ts +++ b/src/lib/rpg/data.ts @@ -1,4 +1,4 @@ -import { Behavior, CraftingStation, DefaultItem, Recipe, formatItems, getDefaultItem } from './items'; +import { Behavior, CraftingStation, DefaultItem, DefaultRecipe, formatItems, getDefaultItem } from './items'; export enum DefaultItems { COIN = 1, @@ -73,8 +73,9 @@ export const craftingStations: CraftingStation[] = [ } ]; -export const recipes: Recipe[] = [ +export const defaultRecipes: DefaultRecipe[] = [ { + id: -1, station: 'forage', inputs: [], requirements: [], @@ -83,6 +84,7 @@ export const recipes: Recipe[] = [ ] }, { + id: -2, station: 'workbench', inputs: [ { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index b94eba5..c075524 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, User } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; -import { DefaultItems, defaultItems } from './data'; +import { DefaultItems, craftingStations, defaultItems, defaultRecipes } from './data'; export type DefaultItem = Omit; // uses negative IDs export type Item = DefaultItem | CustomItem; @@ -37,12 +37,22 @@ export interface CraftingStation { manipulateResults?: (outputs: Items[]) => Items[] } -export interface Recipe { +export function getStation(key: string) { + return craftingStations.find(station => station.key === key); +} +export function formatRecipe(recipe: DefaultRecipe) { + const station = getStation(recipe.station); + return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs); +} + +export interface DefaultRecipe { + id: number, station: string, inputs: Items[], requirements: Items[], outputs: Items[] } +export type Recipe = DefaultRecipe export async function getCustomItem(id: number) { return await db('customItems') @@ -63,7 +73,21 @@ export async function getItem(id: number): Promise { } } -export async function getItemQuantity(user: string, itemID: number) { +export function getDefaultRecipe(id: number): DefaultRecipe | undefined { + return defaultRecipes.find(recipe => recipe.id === id); +} +export function getRecipe(id: number): Recipe | undefined { + return getDefaultRecipe(id); // currently just a stub +} + +export async function canUseStation(user: string, station: CraftingStation) { + if (!station.requires) return true; + + const inv = await getItemQuantity(user, station.requires.id); + return inv.quantity > 0; +} + +export async function getItemQuantity(user: string, itemID: number): Promise { return (await db('itemInventories') .where('item', itemID) .where('user', user) From 42d875a68f46999a56c153bc2fd42a9559a47e5e Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:11:48 +0300 Subject: [PATCH 11/66] move stuff around until circular dependencies go away --- src/commands/craft.ts | 5 +- src/commands/item.ts | 2 +- src/lib/rpg/behaviors.ts | 26 +++++++++ src/lib/rpg/craftingStations.ts | 50 +++++++++++++++++ src/lib/rpg/data.ts | 97 --------------------------------- src/lib/rpg/items.ts | 97 +++++++++++++-------------------- src/lib/rpg/recipes.ts | 49 +++++++++++++++++ 7 files changed, 166 insertions(+), 160 deletions(-) create mode 100644 src/lib/rpg/behaviors.ts create mode 100644 src/lib/rpg/craftingStations.ts delete mode 100644 src/lib/rpg/data.ts create mode 100644 src/lib/rpg/recipes.ts diff --git a/src/commands/craft.ts b/src/commands/craft.ts index 158a081..3934f3e 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -1,7 +1,8 @@ import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; -import { craftingStations, defaultRecipes } from '../lib/rpg/data'; -import { canUseStation, formatItem, formatItems, formatItemsArray, formatRecipe, getItemQuantity, getMaxStack, getRecipe, getStation, giveItem } from '../lib/rpg/items'; import { CraftingStationCooldown, db } from '../lib/db'; +import { getStation, canUseStation, craftingStations } from '../lib/rpg/craftingStations'; +import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; +import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; module.exports = { data: new SlashCommandBuilder() diff --git a/src/commands/item.ts b/src/commands/item.ts index 9ac48a8..2016c8d 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,7 +1,7 @@ import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; import { CustomItem, db } from '../lib/db'; import { formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; -import { behaviors } from '../lib/rpg/data'; +import { behaviors } from '../lib/rpg/behaviors'; //function extendOption(t: string) { // return {name: t, value: t}; diff --git a/src/lib/rpg/behaviors.ts b/src/lib/rpg/behaviors.ts new file mode 100644 index 0000000..b867781 --- /dev/null +++ b/src/lib/rpg/behaviors.ts @@ -0,0 +1,26 @@ +import type { User } from 'discord.js'; +import type { Item } from './items'; + +export interface Behavior { + name: string, + description: string, + itemType: 'plain' | 'weapon' | 'consumable', + // triggers upon use + // for 'weapons', this is on hit + // for 'consumable', this is on use + // for 'plain', ...?? + // returns `true` upon success, `false` otherwise + action?: (item: Item, user: User) => Promise +} + +export const behaviors: Behavior[] = [ + { + name: 'heal', + description: 'Heals the user by `behaviorValue`', + itemType: 'consumable', + action: async (item, user) => { + // todo + return false; + } + } +]; \ No newline at end of file diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts new file mode 100644 index 0000000..f08535e --- /dev/null +++ b/src/lib/rpg/craftingStations.ts @@ -0,0 +1,50 @@ +import { DefaultItems, Item, Items, formatItems, getDefaultItem, getItemQuantity } from './items'; + +export interface CraftingStation { + key: string, + name: string, + description: string, + emoji: string, + requires?: Item, + // in seconds + cooldown?: number, + formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[]) => string, + manipulateResults?: (outputs: Items[]) => Items[] +} + +export function getStation(key: string) { + return craftingStations.find(station => station.key === key); +} + +export const craftingStations: CraftingStation[] = [ + { + key: 'forage', + name: 'Forage', + description: 'Pick up various sticks and stones from the forest', + emoji: '🌲', + cooldown: 60 * 5, + formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, + manipulateResults: (outputs) => + outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) + }, + { + key: 'hand', + name: 'Hand', + description: 'You can use your hands to make a small assortment of things', + emoji: '✋' + }, + { + key: 'workbench', + name: 'Workbench', + description: 'A place for you to work with tools, for simple things', + emoji: '🛠️', + requires: getDefaultItem(DefaultItems.WORKBENCH) + } +]; + +export async function canUseStation(user: string, station: CraftingStation) { + if (!station.requires) return true; + + const inv = await getItemQuantity(user, station.requires.id); + return inv.quantity > 0; +} \ No newline at end of file diff --git a/src/lib/rpg/data.ts b/src/lib/rpg/data.ts deleted file mode 100644 index bd5cb4a..0000000 --- a/src/lib/rpg/data.ts +++ /dev/null @@ -1,97 +0,0 @@ -import { Behavior, CraftingStation, DefaultItem, DefaultRecipe, formatItems, getDefaultItem } from './items'; - -export enum DefaultItems { - COIN = 1, - WORKBENCH = 2, - PEBBLE = 3 -} - -export const defaultItems: DefaultItem[] = [ - { - id: -1, - name: 'Coin', - emoji: '🪙', - type: 'plain', - maxStack: 9999, - untradable: false - }, - { - id: -2, - name: 'Workbench', - description: 'A place for you to work with tools, for simple things', - emoji: '🛠️', - type: 'plain', - maxStack: 1, - untradable: false - }, - { - id: -3, - name: 'Pebble', - description: 'If you get 5 of them you will instantly ! !!!', - emoji: '🪨', - type: 'plain', - maxStack: 64, - untradable: false - } -]; - -export const behaviors: Behavior[] = [ - { - name: 'heal', - description: 'Heals the user by `behaviorValue`', - itemType: 'consumable', - action: async (item, user) => { - // todo - return false; - } - } -]; - -export const craftingStations: CraftingStation[] = [ - { - key: 'forage', - name: 'Forage', - description: 'Pick up various sticks and stones from the forest', - emoji: '🌲', - cooldown: 60 * 5, - formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, - manipulateResults: (outputs) => - outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) - }, - { - key: 'hand', - name: 'Hand', - description: 'You can use your hands to make a small assortment of things', - emoji: '✋' - }, - { - key: 'workbench', - name: 'Workbench', - description: 'A place for you to work with tools, for simple things', - emoji: '🛠️', - requires: getDefaultItem(DefaultItems.WORKBENCH) - } -]; - -export const defaultRecipes: DefaultRecipe[] = [ - { - id: -1, - station: 'forage', - inputs: [], - requirements: [], - outputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } - ] - }, - { - id: -2, - station: 'workbench', - inputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } - ], - requirements: [], - outputs: [ - { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } - ] - } -]; diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index c075524..ed1ab65 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,64 +1,49 @@ -import { AutocompleteInteraction, User } from 'discord.js'; +import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; -import { DefaultItems, craftingStations, defaultItems, defaultRecipes } from './data'; export type DefaultItem = Omit; // uses negative IDs export type Item = DefaultItem | CustomItem; -export interface Behavior { - name: string, - description: string, - itemType: 'plain' | 'weapon' | 'consumable', - // triggers upon use - // for 'weapons', this is on hit - // for 'consumable', this is on use - // for 'plain', ...?? - // returns `true` upon success, `false` otherwise - action?: (item: Item, user: User) => Promise -} - export interface Items { item: Item, quantity: number } -const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => - `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; - -export interface CraftingStation { - key: string, - name: string, - description: string, - emoji: string, - requires?: Item, - // in seconds - cooldown?: number, - formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[]) => string, - manipulateResults?: (outputs: Items[]) => Items[] +export enum DefaultItems { + COIN = 1, + WORKBENCH = 2, + PEBBLE = 3 } -export function getStation(key: string) { - return craftingStations.find(station => station.key === key); -} -export function formatRecipe(recipe: DefaultRecipe) { - const station = getStation(recipe.station); - return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs); -} +export const defaultItems: DefaultItem[] = [ + { + id: -1, + name: 'Coin', + emoji: '🪙', + type: 'plain', + maxStack: 9999, + untradable: false + }, + { + id: -2, + name: 'Workbench', + description: 'A place for you to work with tools, for simple things', + emoji: '🛠️', + type: 'plain', + maxStack: 1, + untradable: false + }, + { + id: -3, + name: 'Pebble', + description: 'If you get 5 of them you will instantly ! !!!', + emoji: '🪨', + type: 'plain', + maxStack: 64, + untradable: false + } +]; -export interface DefaultRecipe { - id: number, - station: string, - inputs: Items[], - requirements: Items[], - outputs: Items[] -} -export type Recipe = DefaultRecipe - -export async function getCustomItem(id: number) { - return await db('customItems') - .where('id', id) - .first(); -} export function getDefaultItem(id: DefaultItems): Item export function getDefaultItem(id: number): Item | undefined { @@ -73,18 +58,10 @@ export async function getItem(id: number): Promise { } } -export function getDefaultRecipe(id: number): DefaultRecipe | undefined { - return defaultRecipes.find(recipe => recipe.id === id); -} -export function getRecipe(id: number): Recipe | undefined { - return getDefaultRecipe(id); // currently just a stub -} - -export async function canUseStation(user: string, station: CraftingStation) { - if (!station.requires) return true; - - const inv = await getItemQuantity(user, station.requires.id); - return inv.quantity > 0; +export async function getCustomItem(id: number) { + return await db('customItems') + .where('id', id) + .first(); } export async function getItemQuantity(user: string, itemID: number): Promise { diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts new file mode 100644 index 0000000..4deab8d --- /dev/null +++ b/src/lib/rpg/recipes.ts @@ -0,0 +1,49 @@ +import { getStation } from './craftingStations'; +import { DefaultItems, Items, formatItemsArray, getDefaultItem } from './items'; + +export interface DefaultRecipe { + id: number, + station: string, + inputs: Items[], + requirements: Items[], + outputs: Items[] +} +export type Recipe = DefaultRecipe + +export const defaultRecipes: DefaultRecipe[] = [ + { + id: -1, + station: 'forage', + inputs: [], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ] + }, + { + id: -2, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } + ] + } +]; + +export function getDefaultRecipe(id: number): DefaultRecipe | undefined { + return defaultRecipes.find(recipe => recipe.id === id); +} +export function getRecipe(id: number): Recipe | undefined { + return getDefaultRecipe(id); // currently just a stub +} + +const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => + `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; + +export function formatRecipe(recipe: DefaultRecipe) { + const station = getStation(recipe.station); + return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs); +} \ No newline at end of file From 41af7fa2e2037209df8c748385d7caf4c7f90edc Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:22:57 +0300 Subject: [PATCH 12/66] various tiny fixes now that i can actually test it --- src/commands/craft.ts | 8 ++++---- src/lib/rpg/craftingStations.ts | 4 ++-- src/lib/rpg/items.ts | 15 ++++++++------- src/lib/rpg/recipes.ts | 8 ++++---- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/src/commands/craft.ts b/src/commands/craft.ts index 3934f3e..e196064 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -78,14 +78,14 @@ module.exports = { .insert({ station: station.key, user: member.id, - usedAt: db.fn.now() + usedAt: Date.now() }); } else { await db('craftingStationCooldowns') .where('station', station.key) .where('user', member.id) .update({ - usedAt: db.fn.now() + usedAt: Date.now() }); } @@ -113,8 +113,8 @@ module.exports = { .filter(recipe => recipe.station === interaction.options.getString('station')) .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0) .map(recipe => ({ - name: formatRecipe(recipe), - value: recipe.id + name: formatRecipe(recipe, true), + value: recipe.id.toString() })); return interaction.respond(found); diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index f08535e..05c0cb4 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -8,7 +8,7 @@ export interface CraftingStation { requires?: Item, // in seconds cooldown?: number, - formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[]) => string, + formatRecipe?: (inputs: Items[], requirements: Items[], outputs: Items[], disableBold?: boolean) => string, manipulateResults?: (outputs: Items[]) => Items[] } @@ -23,7 +23,7 @@ export const craftingStations: CraftingStation[] = [ description: 'Pick up various sticks and stones from the forest', emoji: '🌲', cooldown: 60 * 5, - formatRecipe: (_inputs, _requirements, outputs) => `${outputs.map(i => formatItems(i.item, i.quantity) + '?').join(' ')}`, + formatRecipe: (_inputs, _requirements, outputs, disableBold = false) => `${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, manipulateResults: (outputs) => outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) }, diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index ed1ab65..6c5e7d8 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -109,15 +109,16 @@ export function getMaxStack(item: Item) { return item.type === 'weapon' ? 1 : item.maxStack; } -export function formatItem(item: Item | undefined) { - if (!item) return '? **MISSINGNO**'; - return `${item.emoji} **${item.name}**`; +export function formatItem(item: Item | undefined, disableBold = false) { + if (!item) return disableBold ? '? MISSINGNO' : '? **MISSINGNO**'; + return disableBold ? `${item.emoji} ${item.name}` : `${item.emoji} **${item.name}**`; } -export function formatItems(item: Item | undefined, quantity: number) { - return `${quantity}x ${formatItem(item)}`; +export function formatItems(item: Item | undefined, quantity: number, disableBold = false) { + return `${quantity}x ${formatItem(item, disableBold)}`; } -export function formatItemsArray(items: Items[]) { - return items.map(i => formatItems(i.item, i.quantity)).join(' '); +export function formatItemsArray(items: Items[], disableBold = false) { + if (items.length === 0) return disableBold ? 'nothing' : '**nothing**'; + return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } export async function itemAutocomplete(interaction: AutocompleteInteraction) { diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 4deab8d..ab08068 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -40,10 +40,10 @@ export function getRecipe(id: number): Recipe | undefined { return getDefaultRecipe(id); // currently just a stub } -const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[]) => - `${formatItemsArray(inputs)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements)}`} => ${formatItemsArray(outputs)}`; +const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => + `${formatItemsArray(inputs, disableBold)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements, disableBold)}`} => ${formatItemsArray(outputs, disableBold)}`; -export function formatRecipe(recipe: DefaultRecipe) { +export function formatRecipe(recipe: DefaultRecipe, disableBold = false) { const station = getStation(recipe.station); - return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs); + return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs, disableBold); } \ No newline at end of file From 169b81ea0636ac0729d08609230f2149da05342a Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:33:08 +0300 Subject: [PATCH 13/66] fairly give foraging results --- src/lib/rpg/craftingStations.ts | 19 +++++++++++++++++-- src/lib/util.ts | 4 ++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 05c0cb4..944534f 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -1,3 +1,4 @@ +import { pickRandom } from '../util'; import { DefaultItems, Item, Items, formatItems, getDefaultItem, getItemQuantity } from './items'; export interface CraftingStation { @@ -24,8 +25,22 @@ export const craftingStations: CraftingStation[] = [ emoji: '🌲', cooldown: 60 * 5, formatRecipe: (_inputs, _requirements, outputs, disableBold = false) => `${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, - manipulateResults: (outputs) => - outputs.map(o => ({item: o.item, quantity: Math.floor(o.quantity * Math.random())})).filter(o => o.quantity !== 0) + manipulateResults: (outputs) => { + const totalItems = outputs.reduce((a, b) => a + b.quantity, 0); + // grab from 1/2 to the entire pool, ensure it never goes below 1 + const rolledItems = Math.max(Math.round(totalItems/2 + Math.random() * totalItems/2), 1); + const res: Items[] = []; + for (let i = 0; i < rolledItems; i++) { + const rolled = pickRandom(outputs); + const r = res.find(r => r.item.id === rolled.item.id); + if (r) { + r.quantity = r.quantity + 1; + } else { + res.push({ item: rolled.item, quantity: rolled.quantity }); + } + } + return res; + } }, { key: 'hand', diff --git a/src/lib/util.ts b/src/lib/util.ts index 8803979..cb36114 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -23,4 +23,8 @@ export async function writeTmpFile(data: string | Buffer, filename?: string, ext const path = join(tmpdir(), file); await fsp.writeFile(path, data); return path; +} + +export function pickRandom(list: T[]): T { + return list[Math.floor(Math.random() * list.length)]; } \ No newline at end of file From 32cdaf519971e37d3364a7bbc5f233dde960c508 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:42:24 +0300 Subject: [PATCH 14/66] some extra items & recipes... we're fucking Getting Somewhere!!!!! --- src/lib/rpg/items.ts | 32 +++++++++++++++++++++++++++++++- src/lib/rpg/recipes.ts | 33 +++++++++++++++++++++++++++++---- 2 files changed, 60 insertions(+), 5 deletions(-) diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 6c5e7d8..780fd39 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -12,7 +12,10 @@ export interface Items { export enum DefaultItems { COIN = 1, WORKBENCH = 2, - PEBBLE = 3 + PEBBLE = 3, + TWIG = 4, + APPLE = 5, + BERRIES = 6, } export const defaultItems: DefaultItem[] = [ @@ -41,6 +44,33 @@ export const defaultItems: DefaultItem[] = [ type: 'plain', maxStack: 64, untradable: false + }, + { + id: -4, + name: 'Twig', + description: 'Just a tiny bit of wood', + emoji: '🌿', + type: 'plain', + maxStack: 64, + untradable: false + }, + { + id: -5, + name: 'Apple', + description: 'A forager\'s snack', + emoji: '🍎', + type: 'consumable', + maxStack: 16, + untradable: false + }, + { + id: -6, + name: 'Berries', + description: 'A little treat for the road!', + emoji: '🍓', + type: 'consumable', + maxStack: 16, + untradable: false } ]; diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index ab08068..d11323a 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -17,20 +17,45 @@ export const defaultRecipes: DefaultRecipe[] = [ inputs: [], requirements: [], outputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 3 }, + { item: getDefaultItem(DefaultItems.BERRIES), quantity: 2 } ] }, { id: -2, - station: 'workbench', + station: 'hand', inputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 } + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 2 }, ], requirements: [], outputs: [ { item: getDefaultItem(DefaultItems.WORKBENCH), quantity: 1 } ] - } + }, + { + id: -3, + station: 'forage', + inputs: [], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 3 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 4 }, + { item: getDefaultItem(DefaultItems.APPLE), quantity: 1 } + ] + }, + { + id: -4, + station: 'forage', + inputs: [], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.TWIG), quantity: 1 }, + { item: getDefaultItem(DefaultItems.APPLE), quantity: 4 }, + { item: getDefaultItem(DefaultItems.BERRIES), quantity: 6 }, + ] + }, ]; export function getDefaultRecipe(id: number): DefaultRecipe | undefined { From 39014d3b900eafe0278f0485a432956881441e1e Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 18:46:00 +0300 Subject: [PATCH 15/66] oops i made foraging too op --- src/lib/rpg/craftingStations.ts | 6 +++--- src/lib/rpg/recipes.ts | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 944534f..73f86c5 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -27,8 +27,8 @@ export const craftingStations: CraftingStation[] = [ formatRecipe: (_inputs, _requirements, outputs, disableBold = false) => `${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, manipulateResults: (outputs) => { const totalItems = outputs.reduce((a, b) => a + b.quantity, 0); - // grab from 1/2 to the entire pool, ensure it never goes below 1 - const rolledItems = Math.max(Math.round(totalItems/2 + Math.random() * totalItems/2), 1); + // grab from 1/3 to the entire pool, ensure it never goes below 1 + const rolledItems = Math.max(Math.round(totalItems/3 + Math.random() * totalItems*2/3), 1); const res: Items[] = []; for (let i = 0; i < rolledItems; i++) { const rolled = pickRandom(outputs); @@ -36,7 +36,7 @@ export const craftingStations: CraftingStation[] = [ if (r) { r.quantity = r.quantity + 1; } else { - res.push({ item: rolled.item, quantity: rolled.quantity }); + res.push({ item: rolled.item, quantity: 1 }); } } return res; diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index d11323a..09ba490 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -52,6 +52,7 @@ export const defaultRecipes: DefaultRecipe[] = [ requirements: [], outputs: [ { item: getDefaultItem(DefaultItems.TWIG), quantity: 1 }, + { item: getDefaultItem(DefaultItems.COIN), quantity: 1 }, { item: getDefaultItem(DefaultItems.APPLE), quantity: 4 }, { item: getDefaultItem(DefaultItems.BERRIES), quantity: 6 }, ] From 8a935e00db81ab14574595140972c13a888cf65f Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 19:02:10 +0300 Subject: [PATCH 16/66] some extra basic items & recipes --- src/lib/rpg/craftingStations.ts | 5 +++-- src/lib/rpg/items.ts | 32 +++++++++++++++++++++++++++++- src/lib/rpg/recipes.ts | 35 +++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 73f86c5..09672c8 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -1,5 +1,5 @@ import { pickRandom } from '../util'; -import { DefaultItems, Item, Items, formatItems, getDefaultItem, getItemQuantity } from './items'; +import { DefaultItems, Item, Items, formatItems, formatItemsArray, getDefaultItem, getItemQuantity } from './items'; export interface CraftingStation { key: string, @@ -24,7 +24,8 @@ export const craftingStations: CraftingStation[] = [ description: 'Pick up various sticks and stones from the forest', emoji: '🌲', cooldown: 60 * 5, - formatRecipe: (_inputs, _requirements, outputs, disableBold = false) => `${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, + formatRecipe: (_inputs, requirements, outputs, disableBold = false) => + `${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)}: ` : ''}${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, manipulateResults: (outputs) => { const totalItems = outputs.reduce((a, b) => a + b.quantity, 0); // grab from 1/3 to the entire pool, ensure it never goes below 1 diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 780fd39..282b6a8 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -16,6 +16,9 @@ export enum DefaultItems { TWIG = 4, APPLE = 5, BERRIES = 6, + LOG = 7, + AXE = 8, + BLOOD = 9, } export const defaultItems: DefaultItem[] = [ @@ -71,7 +74,34 @@ export const defaultItems: DefaultItem[] = [ type: 'consumable', maxStack: 16, untradable: false - } + }, + { + id: -7, + name: 'Log', + description: '㏒', + emoji: '🪵', + type: 'plain', + maxStack: 64, + untradable: false + }, + { + id: -8, + name: 'Axe', + description: 'You could chop trees with this. Or commit murder! The choice is up to you', + emoji: '🪓', + type: 'weapon', + maxStack: 1, + untradable: false + }, + { + id: -9, + name: 'Blood', + description: 'ow', + emoji: '🩸', + type: 'plain', + maxStack: 1024, + untradable: false + }, ]; diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 09ba490..09ae787 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -57,6 +57,41 @@ export const defaultRecipes: DefaultRecipe[] = [ { item: getDefaultItem(DefaultItems.BERRIES), quantity: 6 }, ] }, + { + id: -5, + station: 'forage', + inputs: [], + requirements: [ + { item: getDefaultItem(DefaultItems.AXE), quantity: 1 }, + ], + outputs: [ + { item: getDefaultItem(DefaultItems.TWIG), quantity: 1 }, + { item: getDefaultItem(DefaultItems.LOG), quantity: 3 }, + ] + }, + { + id: -6, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 2 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.AXE), quantity: 1 }, + ] + }, + { + id: -7, + station: 'hand', + inputs: [], + requirements: [ + { item: getDefaultItem(DefaultItems.AXE), quantity: 1 }, + ], + outputs: [ + { item: getDefaultItem(DefaultItems.BLOOD), quantity: 6 }, + ] + } ]; export function getDefaultRecipe(id: number): DefaultRecipe | undefined { From cb503e3cf913fe9caecf473898236cc24608bf2a Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 19:02:31 +0300 Subject: [PATCH 17/66] slight forage nerf --- src/lib/rpg/recipes.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 09ae787..4a40fc7 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -18,7 +18,7 @@ export const defaultRecipes: DefaultRecipe[] = [ requirements: [], outputs: [ { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, - { item: getDefaultItem(DefaultItems.TWIG), quantity: 3 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 2 }, { item: getDefaultItem(DefaultItems.BERRIES), quantity: 2 } ] }, @@ -40,7 +40,7 @@ export const defaultRecipes: DefaultRecipe[] = [ inputs: [], requirements: [], outputs: [ - { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 3 }, + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 2 }, { item: getDefaultItem(DefaultItems.TWIG), quantity: 4 }, { item: getDefaultItem(DefaultItems.APPLE), quantity: 1 } ] From 9af3499d3ddb9ecc47a2a85db949164af56c5b31 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 19:03:26 +0300 Subject: [PATCH 18/66] make sure the counts in foraging aren't enritely irrelevant --- src/lib/rpg/craftingStations.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 09672c8..a595aa4 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -35,7 +35,12 @@ export const craftingStations: CraftingStation[] = [ const rolled = pickRandom(outputs); const r = res.find(r => r.item.id === rolled.item.id); if (r) { - r.quantity = r.quantity + 1; + if (r.quantity === rolled.quantity) { + // don't roll more than can be rolled + i--; + } else { + r.quantity = r.quantity + 1; + } } else { res.push({ item: rolled.item, quantity: 1 }); } From 20b914417fa5829e2048060ec4fc6e0d09ea73e2 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 19:09:15 +0300 Subject: [PATCH 19/66] small consistency tweaks --- src/commands/craft.ts | 6 +++--- src/lib/rpg/craftingStations.ts | 9 +++++++++ 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/commands/craft.ts b/src/commands/craft.ts index e196064..047f3be 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; import { CraftingStationCooldown, db } from '../lib/db'; -import { getStation, canUseStation, craftingStations } from '../lib/rpg/craftingStations'; +import { getStation, canUseStation, craftingStations, verb } from '../lib/rpg/craftingStations'; import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; @@ -47,7 +47,7 @@ module.exports = { } for (const out of recipe.outputs) { const inv = await getItemQuantity(member.id, out.item.id); - if (inv.quantity + out.quantity > getMaxStack(out.item)) return interaction.followUp(`You do not have enough inventory storage for this recipe! (${formatItems(out.item, inv.quantity + out.quantity)} is bigger than the stack size of ${getMaxStack(out.item)}.)`); + if (inv.quantity + out.quantity > getMaxStack(out.item)) return interaction.followUp(`You do not have enough inventory storage for this recipe! (${formatItems(out.item, inv.quantity + out.quantity)} is bigger than the stack size of ${getMaxStack(out.item)}x.)`); } let cooldown; @@ -92,7 +92,7 @@ module.exports = { nextUsableAt = Date.now() + station.cooldown * 1000; } - return interaction.followUp(`${station.emoji} Crafted ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again ` : ''}`); + return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, autocomplete: async (interaction: AutocompleteInteraction) => { diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index a595aa4..8cfa057 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -4,6 +4,7 @@ import { DefaultItems, Item, Items, formatItems, formatItemsArray, getDefaultIte export interface CraftingStation { key: string, name: string, + verb?: string, description: string, emoji: string, requires?: Item, @@ -17,10 +18,13 @@ export function getStation(key: string) { return craftingStations.find(station => station.key === key); } +export const defaultVerb = 'Crafted'; + export const craftingStations: CraftingStation[] = [ { key: 'forage', name: 'Forage', + verb: 'Foraged', description: 'Pick up various sticks and stones from the forest', emoji: '🌲', cooldown: 60 * 5, @@ -51,6 +55,7 @@ export const craftingStations: CraftingStation[] = [ { key: 'hand', name: 'Hand', + verb: 'Made', description: 'You can use your hands to make a small assortment of things', emoji: '✋' }, @@ -68,4 +73,8 @@ export async function canUseStation(user: string, station: CraftingStation) { const inv = await getItemQuantity(user, station.requires.id); return inv.quantity > 0; +} + +export function verb(station: CraftingStation) { + return station.verb || defaultVerb; } \ No newline at end of file From f807baf3f2d474799945ce75e506c24187d91805 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Thu, 16 Nov 2023 13:33:11 +0300 Subject: [PATCH 20/66] typecheck commands --- src/commands/change.ts | 11 +++++++---- src/commands/color.ts | 11 +++++++---- src/commands/counter.ts | 9 +++++---- src/commands/craft.ts | 11 +++++++---- src/commands/decrease.ts | 11 +++++++---- src/commands/emotedump.ts | 11 ++++++----- src/commands/garbagecollectroles.ts | 11 ++++++----- src/commands/increase.ts | 11 +++++++---- src/commands/inventory.ts | 11 +++++++---- src/commands/investigate.ts | 11 +++++++---- src/commands/item.ts | 9 +++++---- src/commands/markov.ts | 11 +++++++---- src/commands/monitor.ts | 10 ++++++---- src/commands/pronouns.ts | 11 +++++++---- src/commands/put.ts | 11 +++++++---- src/commands/subscribe.ts | 9 +++++---- src/commands/survey.ts | 7 ++++--- src/commands/take.ts | 11 +++++++---- src/commands/twosentencehorror.ts | 11 +++++++---- src/index.ts | 6 ++++-- src/types/index.d.ts | 12 ++++++++++-- 21 files changed, 135 insertions(+), 81 deletions(-) diff --git a/src/commands/change.ts b/src/commands/change.ts index 221f265..ea8f952 100644 --- a/src/commands/change.ts +++ b/src/commands/change.ts @@ -1,5 +1,6 @@ -import { CategoryChannel, GuildMember, EmbedBuilder, TextChannel, Interaction, ChannelType, SlashCommandBuilder } from 'discord.js'; +import { CategoryChannel, GuildMember, EmbedBuilder, TextChannel, CommandInteraction, ChannelType, SlashCommandBuilder } from 'discord.js'; import { knownServers } from '../lib/knownServers'; +import { Command } from '../types/index'; const rand = [ 'This change has no significance.', @@ -38,7 +39,7 @@ const nicknames = [ 'goobert' ]; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('change') .setDescription('Change') @@ -51,9 +52,11 @@ module.exports = { serverWhitelist: [...knownServers.firepit, ...knownServers.fbi], - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const what = interaction.options.getString('what', true); let title = `**${member.displayName}** changed the **${what}**`; let response; @@ -108,4 +111,4 @@ module.exports = { ephemeral: false, }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/color.ts b/src/commands/color.ts index 56f4b24..318ecd8 100644 --- a/src/commands/color.ts +++ b/src/commands/color.ts @@ -1,8 +1,9 @@ -import { RoleCreateOptions, GuildMember, Interaction, EmbedBuilder, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from 'discord.js'; +import { RoleCreateOptions, GuildMember, CommandInteraction, EmbedBuilder, TextChannel, ActionRowBuilder, ButtonBuilder, ButtonStyle, SlashCommandBuilder } from 'discord.js'; import { default as parseColor, Color } from 'parse-color'; import { isColorRole, COLOR_ROLE_SEPERATOR } from '../lib/assignableRoles'; import { knownServers } from '../lib/knownServers'; import * as log from '../lib/log'; +import { Command } from '../types/index'; const PREVIEW_DURATION = 1000 * 60; @@ -35,7 +36,7 @@ async function applyColor(member: GuildMember, color: Color) { await member.roles.add(role); } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('color') .setDescription('Change your role color.') @@ -49,9 +50,11 @@ module.exports = { serverWhitelist: [...knownServers.firepit], - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const color = interaction.options.getString('color'); const preview = interaction.options.getBoolean('preview'); @@ -129,4 +132,4 @@ module.exports = { }); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/counter.ts b/src/commands/counter.ts index 63a9d68..e25a573 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -1,8 +1,9 @@ -import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; +import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { Counter, CounterUserLink, db } from '../lib/db'; import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, getOptions, parseConfig, setCounterConfig, toStringConfig, updateCounter } from '../lib/rpg/counter'; import { outdent } from 'outdent'; import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/rpg/items'; +import { Command } from '../types/index'; function extendOption(t: string) { return {name: t, value: t}; @@ -28,7 +29,7 @@ const help = new Map([ `] ]); -module.exports = { +export default { data: new SlashCommandBuilder() .setName('counter') .setDescription('[ADMIN] Counter management') @@ -238,7 +239,7 @@ module.exports = { .setDefaultMemberPermissions('0') .setDMPermission(false), - execute: async (interaction: Interaction) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ephemeral: true}); @@ -487,4 +488,4 @@ module.exports = { await interaction.respond(options); } }} -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/craft.ts b/src/commands/craft.ts index 047f3be..f169923 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -1,10 +1,11 @@ -import { AutocompleteInteraction, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { AutocompleteInteraction, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { CraftingStationCooldown, db } from '../lib/db'; import { getStation, canUseStation, craftingStations, verb } from '../lib/rpg/craftingStations'; import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('craft') .setDescription('Craft an item with items you have') @@ -24,9 +25,11 @@ module.exports = { ) .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const recipeID = parseInt(interaction.options.getString('recipe', true)); await interaction.deferReply({ephemeral: true}); @@ -120,4 +123,4 @@ module.exports = { return interaction.respond(found); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/decrease.ts b/src/commands/decrease.ts index 1198b06..26edf6a 100644 --- a/src/commands/decrease.ts +++ b/src/commands/decrease.ts @@ -1,7 +1,8 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('decrease') .setDescription('Decrease a counter') @@ -21,9 +22,11 @@ module.exports = { ) .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; @@ -33,4 +36,4 @@ module.exports = { }, autocomplete: counterAutocomplete -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/emotedump.ts b/src/commands/emotedump.ts index 8f3ab6d..7910e1a 100644 --- a/src/commands/emotedump.ts +++ b/src/commands/emotedump.ts @@ -1,7 +1,8 @@ -import { GuildMember, EmbedBuilder, SlashCommandBuilder, Interaction } from 'discord.js'; +import { GuildMember, EmbedBuilder, SlashCommandBuilder, CommandInteraction } from 'discord.js'; import { writeTmpFile } from '../lib/util'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('emotedump') .setDescription('Dump every emote in the server for Gitea') @@ -14,11 +15,11 @@ module.exports = { .setMaxValue(512) ), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; const size = interaction.options.getInteger('size') || 64; - const emojis = member.guild.emojis; + const emojis = interaction.guild!.emojis; const embed = new EmbedBuilder() .setDescription(`names: \`${emojis.cache.map(emote => emote.name).join(',')}\``); @@ -38,4 +39,4 @@ module.exports = { ephemeral: true }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/garbagecollectroles.ts b/src/commands/garbagecollectroles.ts index 07ccf09..aa4018c 100644 --- a/src/commands/garbagecollectroles.ts +++ b/src/commands/garbagecollectroles.ts @@ -1,6 +1,7 @@ -import { Guild, GuildMember, Interaction, Role, SlashCommandBuilder } from 'discord.js'; +import { Guild, GuildMember, CommandInteraction, Role, SlashCommandBuilder } from 'discord.js'; import { isColorRole, isPronounRole } from '../lib/assignableRoles'; import { knownServers } from '../lib/knownServers'; +import { Command } from '../types/index'; async function fetchRoleMembers(role: Role) { const members = await role.guild.members.fetch(); @@ -21,7 +22,7 @@ async function garbageCollectRoles(guild: Guild, dryRun: boolean): Promise r) as Role[]; } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('garbage-collect-roles') .setDescription('Garbage collect unused roles for colors and pronouns.') @@ -30,7 +31,7 @@ module.exports = { serverWhitelist: [...knownServers.firepit], - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ @@ -38,7 +39,7 @@ module.exports = { }); const dryrun = interaction.options.getBoolean('dry-run'); - const colorRoles = await garbageCollectRoles(member.guild, dryrun || false); + const colorRoles = await garbageCollectRoles(interaction.guild!, dryrun || false); if (dryrun) { interaction.followUp({ @@ -52,4 +53,4 @@ module.exports = { }); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/increase.ts b/src/commands/increase.ts index 5364460..4a581c2 100644 --- a/src/commands/increase.ts +++ b/src/commands/increase.ts @@ -1,7 +1,8 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('increase') .setDescription('Increase a counter') @@ -21,9 +22,11 @@ module.exports = { ) .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; @@ -33,4 +36,4 @@ module.exports = { }, autocomplete: counterAutocomplete -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index 06ba5d7..fa3bd36 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -1,16 +1,19 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { ItemInventory, db } from '../lib/db'; import { formatItems, getItem } from '../lib/rpg/items'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('inventory') .setDescription('Check your inventory') .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + await interaction.deferReply({ephemeral: true}); const itemsList = await db('itemInventories') @@ -24,4 +27,4 @@ module.exports = { `Your inventory:\n${items.length === 0 ? '_Your inventory is empty!_' : items.map(i => `- ${formatItems(i.item!, i.quantity)}`).join('\n')}` ); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/investigate.ts b/src/commands/investigate.ts index 6315988..1de5be0 100644 --- a/src/commands/investigate.ts +++ b/src/commands/investigate.ts @@ -1,4 +1,5 @@ -import { GuildMember, EmbedBuilder, SlashCommandBuilder, Interaction } from 'discord.js'; +import { GuildMember, EmbedBuilder, SlashCommandBuilder, CommandInteraction } from 'discord.js'; +import { Command } from '../types/index'; const rand = require('random-seed').create(); const results = [ @@ -19,16 +20,18 @@ function seperate(l: string[]): string { return l.slice(0, -1).join(', ') + ' or ' + l.slice(-1); } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('investigate') .setDescription('Investigate someone.') .addUserOption((option) => option.setName('who').setDescription('Investigate who?').setRequired(true)) .addBooleanOption((option) => option.setName('sheriff').setDescription('Switch to Sheriff-style investigation').setRequired(false)), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const who = interaction.options.getUser('who', true); const sheriff = interaction.options.getBoolean('sheriff'); let response; @@ -69,4 +72,4 @@ module.exports = { ephemeral: true, }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/item.ts b/src/commands/item.ts index 2016c8d..2bc0406 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,13 +1,14 @@ -import { AutocompleteInteraction, Interaction, SlashCommandBuilder } from 'discord.js'; +import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { CustomItem, db } from '../lib/db'; import { formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; import { behaviors } from '../lib/rpg/behaviors'; +import { Command } from '../types/index'; //function extendOption(t: string) { // return {name: t, value: t}; //} -module.exports = { +export default { data: new SlashCommandBuilder() .setName('item') .setDescription('[ADMIN] Create, edit and otherwise deal with custom items') @@ -172,7 +173,7 @@ module.exports = { .setDefaultMemberPermissions('0') .setDMPermission(false), - execute: async (interaction: Interaction) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ephemeral: true}); @@ -219,4 +220,4 @@ module.exports = { return itemAutocomplete(interaction); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/markov.ts b/src/commands/markov.ts index 37de9fa..67efc4a 100644 --- a/src/commands/markov.ts +++ b/src/commands/markov.ts @@ -1,5 +1,6 @@ -import { GuildMember, SlashCommandBuilder, Interaction, messageLink } from 'discord.js'; +import { GuildMember, SlashCommandBuilder, CommandInteraction, messageLink } from 'discord.js'; import { getTextResponsePrettyPlease, randomWord, sendSegments, startGame } from '../lib/game'; +import { Command } from '../types/index'; const END_TEMPLATES = [ 'Alright! Here\'s the messages you all conjured:', @@ -8,7 +9,7 @@ const END_TEMPLATES = [ 'That does it! Here\'s what you\'ve all cooked up together:' ]; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('markov') .setDescription('Play a Markov chain game') @@ -28,9 +29,11 @@ module.exports = { .setMinValue(1) .setMaxValue(100)), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + const context = interaction.options.getInteger('context') || 3; const maxIterations = interaction.options.getInteger('iterations') || 10; @@ -72,4 +75,4 @@ module.exports = { }); }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/monitor.ts b/src/commands/monitor.ts index 559c248..e9d990b 100644 --- a/src/commands/monitor.ts +++ b/src/commands/monitor.ts @@ -1,12 +1,14 @@ -import { EmbedBuilder, Interaction, SlashCommandBuilder } from 'discord.js'; +import { EmbedBuilder, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import got from 'got'; import { knownServers } from '../lib/knownServers'; +import { Command } from '../types/index'; + const rand = require('random-seed').create(); const imagesEndpoint = 'https://commons.wikimedia.org/w/api.php?action=query&cmlimit=500&cmtitle=Category%3ALiminal_spaces&cmtype=file&list=categorymembers&format=json'; const imageEndpoint = 'https://commons.wikimedia.org/w/api.php?action=query&piprop=thumbnail&pithumbsize=200&prop=pageimages&titles={}&format=json'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('monitor') .setDescription('Monitor') @@ -19,7 +21,7 @@ module.exports = { serverWhitelist: [...knownServers.firepit_extended, ...knownServers.fbi], - execute: async (interaction: Interaction) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ephemeral: false}); @@ -49,4 +51,4 @@ module.exports = { embeds: [embed] }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/pronouns.ts b/src/commands/pronouns.ts index c927211..bee3d11 100644 --- a/src/commands/pronouns.ts +++ b/src/commands/pronouns.ts @@ -1,13 +1,14 @@ -import { RoleCreateOptions, GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { RoleCreateOptions, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { pronouns, PRONOUN_ROLE_SEPERATOR } from '../lib/assignableRoles'; import { knownServers } from '../lib/knownServers'; import * as log from '../lib/log'; +import { Command } from '../types/index'; function extendOption(t: string) { return {name: t, value: t}; } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('pronoun') .setDescription('Give yourself a pronoun or two.') @@ -21,9 +22,11 @@ module.exports = { serverWhitelist: [...knownServers.firepit], - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + const member = interaction.member! as GuildMember; + await interaction.deferReply({ ephemeral: true }); @@ -57,4 +60,4 @@ module.exports = { }); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/put.ts b/src/commands/put.ts index 09e5f7f..5fe441b 100644 --- a/src/commands/put.ts +++ b/src/commands/put.ts @@ -1,7 +1,8 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('put') .setDescription('Put an item from your inventory into the counter') @@ -21,8 +22,10 @@ module.exports = { ) .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; @@ -33,4 +36,4 @@ module.exports = { }, autocomplete: linkedCounterAutocomplete -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/subscribe.ts b/src/commands/subscribe.ts index 9749945..e84e45b 100644 --- a/src/commands/subscribe.ts +++ b/src/commands/subscribe.ts @@ -1,7 +1,8 @@ -import { Interaction, SlashCommandBuilder } from 'discord.js'; +import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { isSubscribed, subscribe, timeAnnouncements, unsubscribe } from '../lib/subscriptions'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('subscribe') .setDescription('[ADMIN] Subscribe/unsubscribe to a time announcement') @@ -14,7 +15,7 @@ module.exports = { ) .setDefaultMemberPermissions('0'), - execute: async (interaction: Interaction) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; await interaction.deferReply({ephemeral: true}); @@ -34,4 +35,4 @@ module.exports = { }); } } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/survey.ts b/src/commands/survey.ts index 42d40b9..fae14c0 100644 --- a/src/commands/survey.ts +++ b/src/commands/survey.ts @@ -1,6 +1,7 @@ import { CommandInteraction, GuildMember, ActionRowBuilder, ButtonBuilder, Client, Collection, MessageComponentInteraction, StringSelectMenuBuilder, ModalBuilder, TextChannel, TextInputStyle, Message, ButtonStyle, ComponentType, APIButtonComponentWithCustomId, Events, TextInputBuilder, SlashCommandBuilder } from 'discord.js'; import * as fs from 'fs/promises'; import { knownServers } from '../lib/knownServers'; +import { Command } from '../types/index'; const RESPONSES_CHANNEL = '983762973858361364'; const GENERAL_CHANNEL = '587108210683412493'; @@ -599,7 +600,7 @@ async function advanceSurvey(userId: string, dontAdvanceProgress = false) { } } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('createsurvey') .setDescription('Re-create the survey button'), @@ -621,7 +622,7 @@ module.exports = { }); }, - onClientReady: (bot: Client) => { + onClientReady: async (bot: Client) => { bot.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isMessageComponent()) return; if (interaction.isModalSubmit()) return; @@ -758,4 +759,4 @@ module.exports = { advanceSurvey(member.id); }); } -}; +} satisfies Command; diff --git a/src/commands/take.ts b/src/commands/take.ts index 2162989..37e3c27 100644 --- a/src/commands/take.ts +++ b/src/commands/take.ts @@ -1,7 +1,8 @@ -import { GuildMember, Interaction, SlashCommandBuilder } from 'discord.js'; +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; +import { Command } from '../types/index'; -module.exports = { +export default { data: new SlashCommandBuilder() .setName('take') .setDescription('Take an item from a counter') @@ -21,8 +22,10 @@ module.exports = { ) .setDMPermission(false), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; @@ -33,4 +36,4 @@ module.exports = { }, autocomplete: linkedCounterAutocomplete -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/commands/twosentencehorror.ts b/src/commands/twosentencehorror.ts index 6c1f54d..42bc2db 100644 --- a/src/commands/twosentencehorror.ts +++ b/src/commands/twosentencehorror.ts @@ -1,6 +1,7 @@ -import { GuildMember, SlashCommandBuilder, Interaction, messageLink, User } from 'discord.js'; +import { GuildMember, SlashCommandBuilder, CommandInteraction, messageLink, User } from 'discord.js'; import { getTextResponsePrettyPlease, sendSegments, startGame } from '../lib/game'; import { shuffle } from 'd3-array'; +import { Command } from '../types/index'; const horrorStarters = [ 'I was playing with my boobs.', @@ -59,13 +60,15 @@ function shift(arr: T[]): T[] { return [...arr.slice(1), arr[0]]; } -module.exports = { +export default { data: new SlashCommandBuilder() .setName('twosentencehorror') .setDescription('Communally create the worst horror stories known to man'), - execute: async (interaction: Interaction, member: GuildMember) => { + execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; startGame(interaction, member.user, 'Two Sentence Horror', async (players, channel) => { players = shuffle(players); @@ -106,4 +109,4 @@ module.exports = { }); }); } -}; \ No newline at end of file +} satisfies Command; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 3f7b982..6aab7eb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,6 +6,7 @@ import { initializeAnnouncements } from './lib/subscriptions'; import * as log from './lib/log'; import chalk from 'chalk'; import prettyBytes from 'pretty-bytes'; +import { Command } from './types/index'; const bot = new Client({ intents: [ @@ -43,7 +44,7 @@ bot.on(Events.ClientReady, async () => { bot.commands = new Collection(); const cmdFiles = fs.readdirSync(path.join(__dirname, './commands')).filter((file) => file.endsWith('.js')); for (const file of cmdFiles) { - const cmd = (await import(`./commands/${file}`)); + const cmd = (await import(`./commands/${file}`)) as Command; bot.commands.set(cmd.data.name, cmd); if (cmd.onClientReady) cmd.onClientReady(bot); } @@ -90,7 +91,7 @@ bot.on(Events.InteractionCreate, async (interaction) => { log.nonsense(stringifyCommand(interaction)); try { - await command.execute(interaction, interaction.member); + await command.execute(interaction); } catch (error) { if (interaction.isRepliable() && !interaction.replied && !interaction.deferred) interaction.reply({ content: `\`ERROR\`\n\`\`\`\n${error}\n\`\`\``, ephemeral: true }); if (interaction.deferred) interaction.followUp(`\`ERROR\`\n\`\`\`\n${error}\n\`\`\``); @@ -101,6 +102,7 @@ bot.on(Events.InteractionCreate, async (interaction) => { if (!command) return; try { + if (!command.autocomplete) throw `Trying to invoke autocomplete for command ${interaction.commandName} which does not have it defined`; await command.autocomplete(interaction); } catch (error) { log.error(error); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 79ef120..c3a0d45 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,7 +1,15 @@ -import { Collection } from 'discord.js'; +import { Collection, SlashCommandBuilder, CommandInteraction, Client } from 'discord.js'; + +export interface Command { + data: Pick, + execute: (interaction: CommandInteraction) => Promise, + autocomplete?: (interaction: AutocompleteInteraction) => Promise, + onClientReady?: (client: Client) => Promise, + serverWhitelist?: string[], +} declare module 'discord.js' { export interface Client { - commands: Collection; + commands: Collection; } } \ No newline at end of file From 9658dd81ee6709e95f17ba021d6e0af6a6a8ece4 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Thu, 16 Nov 2023 13:46:01 +0300 Subject: [PATCH 21/66] update dependencies --- package.json | 14 +-- pnpm-lock.yaml | 315 +++++++++++++++++++++++++------------------------ 2 files changed, 169 insertions(+), 160 deletions(-) diff --git a/package.json b/package.json index a266572..59ca5bd 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "dependencies": { "chalk": "^4.1.2", "d3-array": "^2.12.1", - "discord.js": "^14.13.0", + "discord.js": "^14.14.1", "got": "^11.8.6", "knex": "^3.0.1", "outdent": "^0.8.0", @@ -24,12 +24,12 @@ "sqlite3": "^5.1.6" }, "devDependencies": { - "@types/d3-array": "^3.0.9", - "@types/parse-color": "^1.0.2", - "@typescript-eslint/eslint-plugin": "^6.9.0", - "@typescript-eslint/parser": "^6.9.0", - "discord-api-types": "^0.37.50", - "eslint": "^8.52.0", + "@types/d3-array": "^3.2.1", + "@types/parse-color": "^1.0.3", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", + "discord-api-types": "^0.37.63", + "eslint": "^8.53.0", "typescript": "5.2.2" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 046356a..fb12ff4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,8 +12,8 @@ dependencies: specifier: ^2.12.1 version: 2.12.1 discord.js: - specifier: ^14.13.0 - version: 14.13.0 + specifier: ^14.14.1 + version: 14.14.1 got: specifier: ^11.8.6 version: 11.8.6 @@ -38,23 +38,23 @@ dependencies: devDependencies: '@types/d3-array': - specifier: ^3.0.9 - version: 3.0.9 + specifier: ^3.2.1 + version: 3.2.1 '@types/parse-color': - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.0.3 + version: 1.0.3 '@typescript-eslint/eslint-plugin': - specifier: ^6.9.0 - version: 6.9.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0)(typescript@5.2.2) + specifier: ^6.11.0 + version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2) '@typescript-eslint/parser': - specifier: ^6.9.0 - version: 6.9.0(eslint@8.52.0)(typescript@5.2.2) + specifier: ^6.11.0 + version: 6.11.0(eslint@8.53.0)(typescript@5.2.2) discord-api-types: - specifier: ^0.37.50 - version: 0.37.50 + specifier: ^0.37.63 + version: 0.37.63 eslint: - specifier: ^8.52.0 - version: 8.52.0 + specifier: ^8.53.0 + version: 8.53.0 typescript: specifier: 5.2.2 version: 5.2.2 @@ -66,14 +66,14 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@discordjs/builders@1.6.5: - resolution: {integrity: sha512-SdweyCs/+mHj+PNhGLLle7RrRFX9ZAhzynHahMCLqp5Zeq7np7XC6/mgzHc79QoVlQ1zZtOkTTiJpOZu5V8Ufg==} + /@discordjs/builders@1.7.0: + resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} engines: {node: '>=16.11.0'} dependencies: - '@discordjs/formatters': 0.3.2 - '@discordjs/util': 1.0.1 + '@discordjs/formatters': 0.3.3 + '@discordjs/util': 1.0.2 '@sapphire/shapeshift': 3.9.3 - discord-api-types: 0.37.50 + discord-api-types: 0.37.61 fast-deep-equal: 3.1.3 ts-mixer: 6.0.3 tslib: 2.6.2 @@ -84,44 +84,49 @@ packages: engines: {node: '>=16.11.0'} dev: false - /@discordjs/formatters@0.3.2: - resolution: {integrity: sha512-lE++JZK8LSSDRM5nLjhuvWhGuKiXqu+JZ/DsOR89DVVia3z9fdCJVcHF2W/1Zxgq0re7kCzmAJlCMMX3tetKpA==} - engines: {node: '>=16.11.0'} - dependencies: - discord-api-types: 0.37.50 + /@discordjs/collection@2.0.0: + resolution: {integrity: sha512-YTWIXLrf5FsrLMycpMM9Q6vnZoR/lN2AWX23/Cuo8uOOtS8eHB2dyQaaGnaF8aZPYnttf2bkLMcXn/j6JUOi3w==} + engines: {node: '>=18'} dev: false - /@discordjs/rest@2.0.1: - resolution: {integrity: sha512-/eWAdDRvwX/rIE2tuQUmKaxmWeHmGealttIzGzlYfI4+a7y9b6ZoMp8BG/jaohs8D8iEnCNYaZiOFLVFLQb8Zg==} + /@discordjs/formatters@0.3.3: + resolution: {integrity: sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==} engines: {node: '>=16.11.0'} dependencies: - '@discordjs/collection': 1.5.3 - '@discordjs/util': 1.0.1 + discord-api-types: 0.37.61 + dev: false + + /@discordjs/rest@2.1.0: + resolution: {integrity: sha512-5gFWFkZX2JCFSRzs8ltx8bWmyVi0wPMk6pBa9KGIQSDPMmrP+uOrZ9j9HOwvmVWGe+LmZ5Bov0jMnQd6/jVReg==} + engines: {node: '>=16.11.0'} + dependencies: + '@discordjs/collection': 2.0.0 + '@discordjs/util': 1.0.2 '@sapphire/async-queue': 1.5.0 '@sapphire/snowflake': 3.5.1 '@vladfrangu/async_event_emitter': 2.2.2 - discord-api-types: 0.37.50 + discord-api-types: 0.37.61 magic-bytes.js: 1.5.0 tslib: 2.6.2 - undici: 5.22.1 + undici: 5.27.2 dev: false - /@discordjs/util@1.0.1: - resolution: {integrity: sha512-d0N2yCxB8r4bn00/hvFZwM7goDcUhtViC5un4hPj73Ba4yrChLSJD8fy7Ps5jpTLg1fE9n4K0xBLc1y9WGwSsA==} + /@discordjs/util@1.0.2: + resolution: {integrity: sha512-IRNbimrmfb75GMNEjyznqM1tkI7HrZOf14njX7tCAAUetyZM1Pr8hX/EK2lxBCOgWDRmigbp24fD1hdMfQK5lw==} engines: {node: '>=16.11.0'} dev: false - /@discordjs/ws@1.0.1: - resolution: {integrity: sha512-avvAolBqN3yrSvdBPcJ/0j2g42ABzrv3PEL76e3YTp2WYMGH7cuspkjfSyNWaqYl1J+669dlLp+YFMxSVQyS5g==} + /@discordjs/ws@1.0.2: + resolution: {integrity: sha512-+XI82Rm2hKnFwAySXEep4A7Kfoowt6weO6381jgW+wVdTpMS/56qCvoXyFRY0slcv7c/U8My2PwIB2/wEaAh7Q==} engines: {node: '>=16.11.0'} dependencies: - '@discordjs/collection': 1.5.3 - '@discordjs/rest': 2.0.1 - '@discordjs/util': 1.0.1 + '@discordjs/collection': 2.0.0 + '@discordjs/rest': 2.1.0 + '@discordjs/util': 1.0.2 '@sapphire/async-queue': 1.5.0 - '@types/ws': 8.5.8 + '@types/ws': 8.5.9 '@vladfrangu/async_event_emitter': 2.2.2 - discord-api-types: 0.37.50 + discord-api-types: 0.37.61 tslib: 2.6.2 ws: 8.14.2 transitivePeerDependencies: @@ -129,13 +134,13 @@ packages: - utf-8-validate dev: false - /@eslint-community/eslint-utils@4.4.0(eslint@8.52.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.53.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.52.0 + eslint: 8.53.0 eslint-visitor-keys: 3.4.3 dev: true @@ -144,8 +149,8 @@ packages: engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} dev: true - /@eslint/eslintrc@2.1.2: - resolution: {integrity: sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==} + /@eslint/eslintrc@2.1.3: + resolution: {integrity: sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 @@ -161,11 +166,16 @@ packages: - supports-color dev: true - /@eslint/js@8.52.0: - resolution: {integrity: sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==} + /@eslint/js@8.53.0: + resolution: {integrity: sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true + /@fastify/busboy@2.1.0: + resolution: {integrity: sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==} + engines: {node: '>=14'} + dev: false + /@gar/promisify@1.1.3: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} requiresBuild: true @@ -291,58 +301,58 @@ packages: /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: - '@types/http-cache-semantics': 4.0.3 + '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.8.9 - '@types/responselike': 1.0.2 + '@types/node': 20.9.0 + '@types/responselike': 1.0.3 dev: false - /@types/d3-array@3.0.9: - resolution: {integrity: sha512-mZowFN3p64ajCJJ4riVYlOjNlBJv3hctgAY01pjw3qTnJePD8s9DZmYDzhHKvzfCYvdjwylkU38+Vdt7Cu2FDA==} + /@types/d3-array@3.2.1: + resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} dev: true - /@types/http-cache-semantics@4.0.3: - resolution: {integrity: sha512-V46MYLFp08Wf2mmaBhvgjStM3tPa+2GAdy/iqoX+noX1//zje2x4XmrIU0cAwyClATsTmahbtoQ2EwP7I5WSiA==} + /@types/http-cache-semantics@4.0.4: + resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} dev: false - /@types/json-schema@7.0.14: - resolution: {integrity: sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==} + /@types/json-schema@7.0.15: + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.9.0 dev: false - /@types/node@20.8.9: - resolution: {integrity: sha512-UzykFsT3FhHb1h7yD4CA4YhBHq545JC0YnEz41xkipN88eKQtL6rSgocL5tbAP6Ola9Izm/Aw4Ora8He4x0BHg==} + /@types/node@20.9.0: + resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} dependencies: undici-types: 5.26.5 dev: false - /@types/parse-color@1.0.2: - resolution: {integrity: sha512-Bx+cf6G8qAD7Ks1uAdoF7KkeqZZdHBoJrQXddbLaqBynAtHQsMEI/A8u9MpkOvedloHAqk9+dyxgCUmPNjVz/Q==} + /@types/parse-color@1.0.3: + resolution: {integrity: sha512-1zpnjrXCl0KklQOtMDDXhqQN9ouePkt4NBoAJ/dRjyMqWMkegyIqeZUOf3Xq4yaUPPdY8wmR8cye/D9v1kcrsQ==} dev: true - /@types/responselike@1.0.2: - resolution: {integrity: sha512-/4YQT5Kp6HxUDb4yhRkm0bJ7TbjvTddqX7PZ5hz6qV3pxSo72f/6YPRo+Mu2DU307tm9IioO69l7uAwn5XNcFA==} + /@types/responselike@1.0.3: + resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.9.0 dev: false - /@types/semver@7.5.4: - resolution: {integrity: sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==} + /@types/semver@7.5.5: + resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==} dev: true - /@types/ws@8.5.8: - resolution: {integrity: sha512-flUksGIQCnJd6sZ1l5dqCEG/ksaoAg/eUwiLAGTJQcfgvZJKF++Ta4bJA6A5aPSJmsr+xlseHn4KLgVlNnvPTg==} + /@types/ws@8.5.9: + resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: - '@types/node': 20.8.9 + '@types/node': 20.9.0 dev: false - /@typescript-eslint/eslint-plugin@6.9.0(@typescript-eslint/parser@6.9.0)(eslint@8.52.0)(typescript@5.2.2): - resolution: {integrity: sha512-lgX7F0azQwRPB7t7WAyeHWVfW1YJ9NIgd9mvGhfQpRY56X6AVf8mwM8Wol+0z4liE7XX3QOt8MN1rUKCfSjRIA==} + /@typescript-eslint/eslint-plugin@6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-uXnpZDc4VRjY4iuypDBKzW1rz9T5YBBK0snMn8MaTSNd2kMlj50LnLBABELjJiOL5YHk7ZD8hbSpI9ubzqYI0w==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha @@ -353,13 +363,13 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.10.0 - '@typescript-eslint/parser': 6.9.0(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/scope-manager': 6.9.0 - '@typescript-eslint/type-utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.9.0 + '@typescript-eslint/parser': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/type-utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 graphemer: 1.4.0 ignore: 5.2.4 natural-compare: 1.4.0 @@ -370,8 +380,8 @@ packages: - supports-color dev: true - /@typescript-eslint/parser@6.9.0(eslint@8.52.0)(typescript@5.2.2): - resolution: {integrity: sha512-GZmjMh4AJ/5gaH4XF2eXA8tMnHWP+Pm1mjQR2QN4Iz+j/zO04b9TOvJYOX2sCNIQHtRStKTxRY1FX7LhpJT4Gw==} + /@typescript-eslint/parser@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-+whEdjk+d5do5nxfxx73oanLL9ghKO3EwM9kBCkUtWMRwWuPaFv9ScuqlYfQ6pAD6ZiJhky7TZ2ZYhrMsfMxVQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -380,27 +390,27 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 6.9.0 - '@typescript-eslint/types': 6.9.0 - '@typescript-eslint/typescript-estree': 6.9.0(typescript@5.2.2) - '@typescript-eslint/visitor-keys': 6.9.0 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager@6.9.0: - resolution: {integrity: sha512-1R8A9Mc39n4pCCz9o79qRO31HGNDvC7UhPhv26TovDsWPBDx+Sg3rOZdCELIA3ZmNoWAuxaMOT7aWtGRSYkQxw==} + /@typescript-eslint/scope-manager@6.11.0: + resolution: {integrity: sha512-0A8KoVvIURG4uhxAdjSaxy8RdRE//HztaZdG8KiHLP8WOXSk0vlF7Pvogv+vlJA5Rnjj/wDcFENvDaHb+gKd1A==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.9.0 - '@typescript-eslint/visitor-keys': 6.9.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 dev: true - /@typescript-eslint/type-utils@6.9.0(eslint@8.52.0)(typescript@5.2.2): - resolution: {integrity: sha512-XXeahmfbpuhVbhSOROIzJ+b13krFmgtc4GlEuu1WBT+RpyGPIA4Y/eGnXzjbDj5gZLzpAXO/sj+IF/x2GtTMjQ==} + /@typescript-eslint/type-utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-nA4IOXwZtqBjIoYrJcYxLRO+F9ri+leVGoJcMW1uqr4r1Hq7vW5cyWrA43lFbpRvQ9XgNrnfLpIkO3i1emDBIA==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 @@ -409,23 +419,23 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 6.9.0(typescript@5.2.2) - '@typescript-eslint/utils': 6.9.0(eslint@8.52.0)(typescript@5.2.2) + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + '@typescript-eslint/utils': 6.11.0(eslint@8.53.0)(typescript@5.2.2) debug: 4.3.4 - eslint: 8.52.0 + eslint: 8.53.0 ts-api-utils: 1.0.3(typescript@5.2.2) typescript: 5.2.2 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types@6.9.0: - resolution: {integrity: sha512-+KB0lbkpxBkBSiVCuQvduqMJy+I1FyDbdwSpM3IoBS7APl4Bu15lStPjgBIdykdRqQNYqYNMa8Kuidax6phaEw==} + /@typescript-eslint/types@6.11.0: + resolution: {integrity: sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==} engines: {node: ^16.0.0 || >=18.0.0} dev: true - /@typescript-eslint/typescript-estree@6.9.0(typescript@5.2.2): - resolution: {integrity: sha512-NJM2BnJFZBEAbCfBP00zONKXvMqihZCrmwCaik0UhLr0vAgb6oguXxLX1k00oQyD+vZZ+CJn3kocvv2yxm4awQ==} + /@typescript-eslint/typescript-estree@6.11.0(typescript@5.2.2): + resolution: {integrity: sha512-Aezzv1o2tWJwvZhedzvD5Yv7+Lpu1by/U1LZ5gLc4tCx8jUmuSCMioPFRjliN/6SJIvY6HpTtJIWubKuYYYesQ==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: typescript: '*' @@ -433,8 +443,8 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 6.9.0 - '@typescript-eslint/visitor-keys': 6.9.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/visitor-keys': 6.11.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 @@ -445,30 +455,30 @@ packages: - supports-color dev: true - /@typescript-eslint/utils@6.9.0(eslint@8.52.0)(typescript@5.2.2): - resolution: {integrity: sha512-5Wf+Jsqya7WcCO8me504FBigeQKVLAMPmUzYgDbWchINNh1KJbxCgVya3EQ2MjvJMVeXl3pofRmprqX6mfQkjQ==} + /@typescript-eslint/utils@6.11.0(eslint@8.53.0)(typescript@5.2.2): + resolution: {integrity: sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==} engines: {node: ^16.0.0 || >=18.0.0} peerDependencies: eslint: ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) - '@types/json-schema': 7.0.14 - '@types/semver': 7.5.4 - '@typescript-eslint/scope-manager': 6.9.0 - '@typescript-eslint/types': 6.9.0 - '@typescript-eslint/typescript-estree': 6.9.0(typescript@5.2.2) - eslint: 8.52.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.5 + '@typescript-eslint/scope-manager': 6.11.0 + '@typescript-eslint/types': 6.11.0 + '@typescript-eslint/typescript-estree': 6.11.0(typescript@5.2.2) + eslint: 8.53.0 semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript dev: true - /@typescript-eslint/visitor-keys@6.9.0: - resolution: {integrity: sha512-dGtAfqjV6RFOtIP8I0B4ZTBRrlTT8NHHlZZSchQx3qReaoDeXhYM++M4So2AgFK9ZB0emRPA6JI1HkafzA2Ibg==} + /@typescript-eslint/visitor-keys@6.11.0: + resolution: {integrity: sha512-+SUN/W7WjBr05uRxPggJPSzyB8zUpaYo2hByKasWbqr3PM8AXfZt8UHdNpBS1v9SA62qnSSMF3380SwDqqprgQ==} engines: {node: ^16.0.0 || >=18.0.0} dependencies: - '@typescript-eslint/types': 6.9.0 + '@typescript-eslint/types': 6.11.0 eslint-visitor-keys: 3.4.3 dev: true @@ -594,13 +604,6 @@ packages: fill-range: 7.0.1 dev: true - /busboy@1.6.0: - resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} - engines: {node: '>=10.16.0'} - dependencies: - streamsearch: 1.1.0 - dev: false - /cacache@15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} engines: {node: '>= 10'} @@ -769,26 +772,31 @@ packages: path-type: 4.0.0 dev: true - /discord-api-types@0.37.50: - resolution: {integrity: sha512-X4CDiMnDbA3s3RaUXWXmgAIbY1uxab3fqe3qwzg5XutR3wjqi7M3IkgQbsIBzpqBN2YWr/Qdv7JrFRqSgb4TFg==} + /discord-api-types@0.37.61: + resolution: {integrity: sha512-o/dXNFfhBpYHpQFdT6FWzeO7pKc838QeeZ9d91CfVAtpr5XLK4B/zYxQbYgPdoMiTDvJfzcsLW5naXgmHGDNXw==} + dev: false - /discord.js@14.13.0: - resolution: {integrity: sha512-Kufdvg7fpyTEwANGy9x7i4od4yu5c6gVddGi5CKm4Y5a6sF0VBODObI3o0Bh7TGCj0LfNT8Qp8z04wnLFzgnbA==} + /discord-api-types@0.37.63: + resolution: {integrity: sha512-WbEDWj/1JGCIC1oCMIC4z9XbYY8PrWpV5eqFFQymJhJlHMqgIjqoYbU812X5oj5cwbRrEh6Va4LNLumB2Nt6IQ==} + dev: true + + /discord.js@14.14.1: + resolution: {integrity: sha512-/hUVzkIerxKHyRKopJy5xejp4MYKDPTszAnpYxzVVv4qJYf+Tkt+jnT2N29PIPschicaEEpXwF2ARrTYHYwQ5w==} engines: {node: '>=16.11.0'} dependencies: - '@discordjs/builders': 1.6.5 + '@discordjs/builders': 1.7.0 '@discordjs/collection': 1.5.3 - '@discordjs/formatters': 0.3.2 - '@discordjs/rest': 2.0.1 - '@discordjs/util': 1.0.1 - '@discordjs/ws': 1.0.1 + '@discordjs/formatters': 0.3.3 + '@discordjs/rest': 2.1.0 + '@discordjs/util': 1.0.2 + '@discordjs/ws': 1.0.2 '@sapphire/snowflake': 3.5.1 - '@types/ws': 8.5.8 - discord-api-types: 0.37.50 + '@types/ws': 8.5.9 + discord-api-types: 0.37.61 fast-deep-equal: 3.1.3 lodash.snakecase: 4.1.1 tslib: 2.6.2 - undici: 5.22.1 + undici: 5.27.2 ws: 8.14.2 transitivePeerDependencies: - bufferutil @@ -857,15 +865,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint@8.52.0: - resolution: {integrity: sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==} + /eslint@8.53.0: + resolution: {integrity: sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.52.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.53.0) '@eslint-community/regexpp': 4.10.0 - '@eslint/eslintrc': 2.1.2 - '@eslint/js': 8.52.0 + '@eslint/eslintrc': 2.1.3 + '@eslint/js': 8.53.0 '@humanwhocodes/config-array': 0.11.13 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -945,8 +953,8 @@ packages: /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - /fast-glob@3.3.1: - resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} + /fast-glob@3.3.2: + resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==} engines: {node: '>=8.6.0'} dependencies: '@nodelib/fs.stat': 2.0.5 @@ -974,7 +982,7 @@ packages: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} dependencies: - flat-cache: 3.1.1 + flat-cache: 3.2.0 dev: true /fill-range@7.0.1: @@ -992,9 +1000,9 @@ packages: path-exists: 4.0.0 dev: true - /flat-cache@3.1.1: - resolution: {integrity: sha512-/qM2b3LUIaIgviBQovTLvijfyOQXPtSRnRK26ksj2J7rzPIecePUIpJsZ4T02Qg+xiAEKIs5K8dsHEd+VaKa/Q==} - engines: {node: '>=12.0.0'} + /flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} dependencies: flatted: 3.2.9 keyv: 4.5.4 @@ -1105,7 +1113,7 @@ packages: dependencies: array-union: 2.1.0 dir-glob: 3.0.1 - fast-glob: 3.3.1 + fast-glob: 3.3.2 ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 @@ -1118,7 +1126,7 @@ packages: '@sindresorhus/is': 4.6.0 '@szmarczak/http-timer': 4.0.6 '@types/cacheable-request': 6.0.3 - '@types/responselike': 1.0.2 + '@types/responselike': 1.0.3 cacheable-lookup: 5.0.4 cacheable-request: 7.0.4 decompress-response: 6.0.0 @@ -1192,7 +1200,7 @@ packages: resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==} requiresBuild: true dependencies: - ms: 2.1.2 + ms: 2.1.3 dev: false optional: true @@ -1562,6 +1570,12 @@ packages: /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + /ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + requiresBuild: true + dev: false + optional: true + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true @@ -1783,8 +1797,8 @@ packages: once: 1.4.0 dev: false - /punycode@2.3.0: - resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} + /punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} dev: true @@ -1978,11 +1992,6 @@ packages: dev: false optional: true - /streamsearch@1.1.0: - resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} - engines: {node: '>=10.0.0'} - dev: false - /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2097,11 +2106,11 @@ packages: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} dev: false - /undici@5.22.1: - resolution: {integrity: sha512-Ji2IJhFXZY0x/0tVBXeQwgPlLWw13GVzpsWPQ3rV50IFMMof2I55PZZxtm4P6iNq+L5znYN9nSTAq0ZyE6lSJw==} + /undici@5.27.2: + resolution: {integrity: sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==} engines: {node: '>=14.0'} dependencies: - busboy: 1.6.0 + '@fastify/busboy': 2.1.0 dev: false /unique-filename@1.1.1: @@ -2123,7 +2132,7 @@ packages: /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: - punycode: 2.3.0 + punycode: 2.3.1 dev: true /util-deprecate@1.0.2: From 9eecec4894d5eae155ba31fef0dedca0bd9f5299 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 23:09:43 +0300 Subject: [PATCH 22/66] fix dupe bug oops! --- src/lib/rpg/counter.ts | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/lib/rpg/counter.ts b/src/lib/rpg/counter.ts index a9e24b9..c9e0c39 100644 --- a/src/lib/rpg/counter.ts +++ b/src/lib/rpg/counter.ts @@ -323,6 +323,19 @@ function changeCounterInteractionBuilder(linked: boolean) { return interaction.followUp(`You cannot **${amount > 0 ? (linked ? 'put' : 'produce') : (linked ? 'take' : 'consume')}** ${counter.emoji}.`); } + const min = await getCounterConfig(counter.id, 'min') as number; + const max = await getCounterConfig(counter.id, 'max') as number; + if (counter.value + amount < min) { + if (min === 0) { + return interaction.followUp(`You cannot remove more than how much is in the counter (${counter.value}x ${counter.emoji})!`); + } else { + return interaction.followUp(`You cannot decrement past the minimum value (${min})!`); + } + } + if (counter.value + amount > max) { + return interaction.followUp(`You are adding more than the counter can hold (${max}x)!`); + } + let item; let newInv; if (linked) { @@ -346,19 +359,6 @@ function changeCounterInteractionBuilder(linked: boolean) { newInv = await giveItem(member.id, item, amtInv); } - const min = await getCounterConfig(counter.id, 'min') as number; - const max = await getCounterConfig(counter.id, 'max') as number; - if (counter.value + amount < min) { - if (min === 0) { - return interaction.followUp(`You cannot remove more than how much is in the counter (${counter.value}x ${counter.emoji})!`); - } else { - return interaction.followUp(`You cannot decrement past the minimum value (${min})!`); - } - } - if (counter.value + amount > max) { - return interaction.followUp(`You are adding more than the counter can hold (${max}x)!`); - } - const newCount = await changeCounter(counter.id, amount); await updateCounter(interaction.client, counter, newCount); await announceCounterUpdate(interaction.client, member, amount, counter, newCount, linked); From a2a56f60f154b82d883759a662928b0122a00657 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 15 Nov 2023 23:47:20 +0300 Subject: [PATCH 23/66] add fishing --- src/lib/rpg/craftingStations.ts | 24 ++++++++++- src/lib/rpg/items.ts | 70 +++++++++++++++++++++++++++++++++ src/lib/rpg/recipes.ts | 52 +++++++++++++++++++++++- 3 files changed, 143 insertions(+), 3 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 8cfa057..5a104d2 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -1,5 +1,5 @@ import { pickRandom } from '../util'; -import { DefaultItems, Item, Items, formatItems, formatItemsArray, getDefaultItem, getItemQuantity } from './items'; +import { DefaultItems, Item, Items, formatItem, formatItems, formatItemsArray, getDefaultItem, getItemQuantity } from './items'; export interface CraftingStation { key: string, @@ -65,7 +65,27 @@ export const craftingStations: CraftingStation[] = [ description: 'A place for you to work with tools, for simple things', emoji: '🛠️', requires: getDefaultItem(DefaultItems.WORKBENCH) - } + }, + { + key: 'fishing', + name: 'Fishing', + verb: 'Fished up', + description: 'fish gaming wednesday', + emoji: '🎣', + requires: getDefaultItem(DefaultItems.FISHING_ROD), + formatRecipe: (inputs, requirements, outputs, disableBold = false) => + `${formatItemsArray(inputs)} => ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`, + // weighted random + manipulateResults: (outputs) => { + const pool: Item[] = []; + for (const out of outputs) { + for (let i = 0; i < out.quantity; i++) { + pool.push(out.item); + } + } + return [{ item: pickRandom(pool), quantity: 1 }]; + } + }, ]; export async function canUseStation(user: string, station: CraftingStation) { diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 282b6a8..f383729 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -19,6 +19,13 @@ export enum DefaultItems { LOG = 7, AXE = 8, BLOOD = 9, + BAIT = 10, + FISHING_ROD = 11, + CARP = 12, + PUFFERFISH = 13, + EXOTIC_FISH = 14, + SHOVEL = 15, + DIRT = 16, } export const defaultItems: DefaultItem[] = [ @@ -102,6 +109,69 @@ export const defaultItems: DefaultItem[] = [ maxStack: 1024, untradable: false }, + { + id: -10, + name: 'Bait', + description: 'I _guess_ you could eat this.', + emoji: '🪱', + type: 'consumable', + maxStack: 128, + untradable: false + }, + { + id: -11, + name: 'Fishing Rod', + description: 'Give a man a fish, and he will eat for a day', + emoji: '🎣', + type: 'plain', + maxStack: 1, + untradable: false + }, + { + id: -12, + name: 'Carp', + description: 'wow', + emoji: '🐟️', + type: 'plain', + maxStack: 16, + untradable: false + }, + { + id: -13, + name: 'Pufferfish', + description: 'yummy!', + emoji: '🐡', + type: 'plain', + maxStack: 16, + untradable: false + }, + { + id: -14, + name: 'Exotic Fish', + description: 'lucky!', + emoji: '🐠', + type: 'plain', + maxStack: 16, + untradable: false, + }, + { + id: -15, + name: 'Shovel', + description: 'Did you know there\'s no shovel emoji', + emoji: '♠️', + type: 'plain', + maxStack: 1, + untradable: false, + }, + { + id: -16, + name: 'Dirt', + description: 'https://media.discordapp.net/attachments/819472665291128873/1081454188325785650/ezgif-2-5ccc7dedf8.gif', + emoji: '🟫', + type: 'consumable', + maxStack: 64, + untradable: false, + }, ]; diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 4a40fc7..00548c8 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -91,7 +91,57 @@ export const defaultRecipes: DefaultRecipe[] = [ outputs: [ { item: getDefaultItem(DefaultItems.BLOOD), quantity: 6 }, ] - } + }, + { + id: -8, + station: 'fishing', + inputs: [ + { item: getDefaultItem(DefaultItems.BAIT), quantity: 1 } + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.CARP), quantity: 12 }, + { item: getDefaultItem(DefaultItems.PUFFERFISH), quantity: 4 }, + { item: getDefaultItem(DefaultItems.EXOTIC_FISH), quantity: 1 }, + ] + }, + { + id: -9, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.TWIG), quantity: 2 }, + { item: getDefaultItem(DefaultItems.BAIT), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.FISHING_ROD), quantity: 1 } + ] + }, + { + id: -10, + station: 'forage', + inputs: [], + requirements: [ + { item: getDefaultItem(DefaultItems.SHOVEL), quantity: 1 }, + ], + outputs: [ + { item: getDefaultItem(DefaultItems.BAIT), quantity: 3 }, + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 1 }, + { item: getDefaultItem(DefaultItems.DIRT), quantity: 1 }, + ], + }, + { + id: -11, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 3 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 2 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.SHOVEL), quantity: 1 }, + ] + }, ]; export function getDefaultRecipe(id: number): DefaultRecipe | undefined { From 0594cc71dbc0860af5685fa308257eb909c0dac1 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Thu, 16 Nov 2023 00:01:55 +0300 Subject: [PATCH 24/66] small tweaks --- src/commands/craft.ts | 14 ++++++++++---- src/commands/inventory.ts | 2 +- src/lib/rpg/craftingStations.ts | 2 +- src/lib/rpg/items.ts | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/commands/craft.ts b/src/commands/craft.ts index f169923..a644819 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { CraftingStationCooldown, db } from '../lib/db'; -import { getStation, canUseStation, craftingStations, verb } from '../lib/rpg/craftingStations'; +import { getStation, canUseStation, craftingStations, verb, CraftingStation } from '../lib/rpg/craftingStations'; import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; @@ -95,15 +95,21 @@ export default { nextUsableAt = Date.now() + station.cooldown * 1000; } - return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${nextUsableAt ? `\n${station.name} usable again ` : ''}`); + return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${outputs.length === 1 ? `\n_${outputs[0].item.description}_` : ''}${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, autocomplete: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(true); if (focused.name === 'station') { - const found = craftingStations - .filter(station => canUseStation(interaction.user.id, station)) + const found = (await Promise.all( + craftingStations + .map(async station => [station, await canUseStation(interaction.user.id, station)]) + )) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .filter(([_station, usable]) => usable) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + .map(([station, _]) => station as CraftingStation) .filter(station => station.name.toLowerCase().includes(focused.value.toLowerCase())) .map(station => ({ name: `${station.emoji} ${station.name}`, diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index fa3bd36..3ef8351 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -24,7 +24,7 @@ export default { const items = (await Promise.all(itemsList.map(async i => ({item: await getItem(i.item), quantity: i.quantity})))).filter(i => i.item); await interaction.followUp( - `Your inventory:\n${items.length === 0 ? '_Your inventory is empty!_' : items.map(i => `- ${formatItems(i.item!, i.quantity)}`).join('\n')}` + `Your inventory:\n${items.length === 0 ? '_Your inventory is empty!_' : items.map(i => `- ${formatItems(i.item!, i.quantity)}\n_${i.item!.description}_`).join('\n')}` ); } } satisfies Command; \ No newline at end of file diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 5a104d2..63b7056 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -74,7 +74,7 @@ export const craftingStations: CraftingStation[] = [ emoji: '🎣', requires: getDefaultItem(DefaultItems.FISHING_ROD), formatRecipe: (inputs, requirements, outputs, disableBold = false) => - `${formatItemsArray(inputs)} => ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`, + `${formatItemsArray(inputs, disableBold)} => ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`, // weighted random manipulateResults: (outputs) => { const pool: Item[] = []; diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index f383729..4edcab0 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -112,7 +112,7 @@ export const defaultItems: DefaultItem[] = [ { id: -10, name: 'Bait', - description: 'I _guess_ you could eat this.', + description: 'I guess you could eat this.', emoji: '🪱', type: 'consumable', maxStack: 128, From c714596653200070b4ce15e2b67671ca8486034f Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Fri, 17 Nov 2023 20:30:27 +0300 Subject: [PATCH 25/66] lint fix --- src/types/index.d.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/types/index.d.ts b/src/types/index.d.ts index c3a0d45..4b89a84 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,10 +1,10 @@ import { Collection, SlashCommandBuilder, CommandInteraction, Client } from 'discord.js'; export interface Command { - data: Pick, - execute: (interaction: CommandInteraction) => Promise, - autocomplete?: (interaction: AutocompleteInteraction) => Promise, - onClientReady?: (client: Client) => Promise, + data: Pick, + execute: (interaction: CommandInteraction) => Promise, + autocomplete?: (interaction: AutocompleteInteraction) => Promise, + onClientReady?: (client: Client) => Promise, serverWhitelist?: string[], } From 77dbd8ee3f3417cf1ee2566eb9c04b83dfae5615 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Fri, 17 Nov 2023 21:23:35 +0300 Subject: [PATCH 26/66] recipe grounwork --- config.example.json | 4 ++- migrations/20231117173052_craftingRecipes.js | 27 ++++++++++++++++++ src/commands/recipe.ts | 30 ++++++++++++++++++++ src/index.ts | 6 +++- src/types/index.d.ts | 7 +++++ 5 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 migrations/20231117173052_craftingRecipes.js create mode 100644 src/commands/recipe.ts diff --git a/config.example.json b/config.example.json index acfbde2..f7d4828 100644 --- a/config.example.json +++ b/config.example.json @@ -1,3 +1,5 @@ { - "token": "token" + "token": "token", + "sitePort": 15385, + "siteURL": "https://localhost:15385" } \ No newline at end of file diff --git a/migrations/20231117173052_craftingRecipes.js b/migrations/20231117173052_craftingRecipes.js new file mode 100644 index 0000000..1bb6ae4 --- /dev/null +++ b/migrations/20231117173052_craftingRecipes.js @@ -0,0 +1,27 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('craftingRecipes', (table) => { + table.increments('id'); + table.string('table'); + }) + .createTable('craftingRecipeItems', (table) => { + table.integer('id').references('id').inTable('craftingRecipes').notNullable(); + table.integer('item').notNullable(); + table.integer('quantity').defaultTo(1); + table.enum('type', ['input', 'output', 'requirement']); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('craftingRecipes') + .dropTable('craftingRecipeItems'); +}; diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts new file mode 100644 index 0000000..0042ab2 --- /dev/null +++ b/src/commands/recipe.ts @@ -0,0 +1,30 @@ +import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { Command } from '../types/index'; + +export default { + data: new SlashCommandBuilder() + .setName('recipe') + .setDescription('[ADMIN] Manage custom recipes for items') + .addSubcommand(sub => + sub + .setName('create') + .setDescription('[ADMIN] Create a custom recipe') + ) + .setDMPermission(false) + .setDefaultMemberPermissions(0), + + execute: async (interaction: CommandInteraction) => { + if (!interaction.isChatInputCommand()) return; + + interaction.deferReply({ ephemeral: true }); + + const sub = interaction.options.getSubcommand(true); + + if (sub === 'create') { + interaction.reply({ + ephemeral: true, + content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe\nOnce done, click the button below and paste the resulting string in.` + }); + } + } +} satisfies Command; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 6aab7eb..30ed419 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Client, GatewayIntentBits, Events, Collection, CommandInteraction, CommandInteractionOption, ApplicationCommandOptionType } from 'discord.js'; import * as fs from 'fs'; -const { token } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); +const { token, sitePort, siteURL } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); import * as path from 'path'; import { initializeAnnouncements } from './lib/subscriptions'; import * as log from './lib/log'; @@ -20,6 +20,10 @@ const bot = new Client({ ], }); +bot.config = { + token, sitePort, siteURL +}; + async function init() { log.nonsense('booting chip...'); diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 4b89a84..31e3b8f 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -8,8 +8,15 @@ export interface Command { serverWhitelist?: string[], } +export interface Config { + token: string, + sitePort: number, + siteURL: string +} + declare module 'discord.js' { export interface Client { + config: Config, commands: Collection; } } \ No newline at end of file From eb1dd27d6b200cf18678762bc2074c6a19b628ce Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Fri, 17 Nov 2023 23:11:50 +0300 Subject: [PATCH 27/66] basic web stuff --- package.json | 2 + pnpm-lock.yaml | 2336 ++++++++++++++++++++++++++++++- src/commands/recipe.ts | 2 +- src/index.ts | 6 +- src/web.ts | 10 + static/assets/jillo.png | Bin 0 -> 20798 bytes static/create-recipe/index.html | 1 + static/index.html | 27 + static/style.css | 80 ++ svelte.config.mjs | 7 + vite.config.mjs | 7 + 11 files changed, 2450 insertions(+), 28 deletions(-) create mode 100644 src/web.ts create mode 100644 static/assets/jillo.png create mode 100644 static/create-recipe/index.html create mode 100644 static/index.html create mode 100644 static/style.css create mode 100644 svelte.config.mjs create mode 100644 vite.config.mjs diff --git a/package.json b/package.json index 59ca5bd..feb5762 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "chalk": "^4.1.2", "d3-array": "^2.12.1", "discord.js": "^14.14.1", + "express": "^4.18.2", "got": "^11.8.6", "knex": "^3.0.1", "outdent": "^0.8.0", @@ -25,6 +26,7 @@ }, "devDependencies": { "@types/d3-array": "^3.2.1", + "@types/express": "^4.17.21", "@types/parse-color": "^1.0.3", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb12ff4..35c2dd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,12 @@ dependencies: discord.js: specifier: ^14.14.1 version: 14.14.1 + express: + specifier: ^4.18.2 + version: 4.18.2 + express-svelte: + specifier: ^1.0.8 + version: 1.0.8(express@4.18.2)(svelte@3.59.2) got: specifier: ^11.8.6 version: 11.8.6 @@ -40,6 +46,9 @@ devDependencies: '@types/d3-array': specifier: ^3.2.1 version: 3.2.1 + '@types/express': + specifier: ^4.17.21 + version: 4.17.21 '@types/parse-color': specifier: ^1.0.3 version: 1.0.3 @@ -66,6 +75,1196 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@ampproject/remapping@2.2.1: + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: false + + /@babel/code-frame@7.22.13: + resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.22.20 + chalk: 2.4.2 + dev: false + + /@babel/compat-data@7.23.3: + resolution: {integrity: sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/core@7.23.3: + resolution: {integrity: sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.3 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) + '@babel/helpers': 7.23.2 + '@babel/parser': 7.23.3 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.3 + '@babel/types': 7.23.3 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/generator@7.23.3: + resolution: {integrity: sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + jsesc: 2.5.2 + dev: false + + /@babel/helper-annotate-as-pure@7.22.5: + resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: + resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-compilation-targets@7.22.15: + resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.23.3 + '@babel/helper-validator-option': 7.22.15 + browserslist: 4.22.1 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: false + + /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.3): + resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-member-expression-to-functions': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + semver: 6.3.1 + dev: false + + /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.3): + resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-annotate-as-pure': 7.22.5 + regexpu-core: 5.3.2 + semver: 6.3.1 + dev: false + + /@babel/helper-define-polyfill-provider@0.4.3(@babel/core@7.23.3): + resolution: {integrity: sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + debug: 4.3.4 + lodash.debounce: 4.0.8 + resolve: 1.22.8 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.15 + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-hoist-variables@7.22.5: + resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-member-expression-to-functions@7.23.0: + resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-module-imports@7.22.15: + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: false + + /@babel/helper-optimise-call-expression@7.22.5: + resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-plugin-utils@7.22.5: + resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.3): + resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-wrap-function': 7.22.20 + dev: false + + /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.3): + resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-member-expression-to-functions': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + dev: false + + /@babel/helper-simple-access@7.22.5: + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-skip-transparent-expression-wrappers@7.22.5: + resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-split-export-declaration@7.22.6: + resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/helper-string-parser@7.22.5: + resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-option@7.22.15: + resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-wrap-function@7.22.20: + resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-function-name': 7.23.0 + '@babel/template': 7.22.15 + '@babel/types': 7.23.3 + dev: false + + /@babel/helpers@7.23.2: + resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.3 + '@babel/types': 7.23.3 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/highlight@7.22.20: + resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: false + + /@babel/parser@7.23.3: + resolution: {integrity: sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.23.3 + dev: false + + /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-transform-optional-chaining': 7.23.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3): + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + dev: false + + /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.3): + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.3): + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.3): + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.3): + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.3): + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.3): + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.3): + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.3): + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.3): + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.3): + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-async-generator-functions@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-block-scoping@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-class-static-block@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-classes@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-optimise-call-expression': 7.22.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) + '@babel/helper-split-export-declaration': 7.22.6 + globals: 11.12.0 + dev: false + + /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/template': 7.22.15 + dev: false + + /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-dynamic-import@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-export-namespace-from@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-for-of@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-json-strings@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-logical-assignment-operators@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-simple-access': 7.22.5 + dev: false + + /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + dev: false + + /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.3): + resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-nullish-coalescing-operator@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-numeric-separator@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-object-rest-spread@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.3 + '@babel/core': 7.23.3 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-optional-catch-binding@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-optional-chaining@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-private-property-in-object@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-annotate-as-pure': 7.22.5 + '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3) + dev: false + + /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + regenerator-transform: 0.15.2 + dev: false + + /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-runtime@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-XcQ3X58CKBdBnnZpPaQjgVMePsXtSZzHoku70q9tUAQp02ggPQNM04BF3RvlW1GSM/McbSOQAzEK4MXbS7/JFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.23.3) + babel-plugin-polyfill-corejs3: 0.8.6(@babel/core@7.23.3) + babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.23.3) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 + dev: false + + /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) + '@babel/helper-plugin-utils': 7.22.5 + dev: false + + /@babel/preset-env@7.23.3(@babel/core@7.23.3): + resolution: {integrity: sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/compat-data': 7.23.3 + '@babel/core': 7.23.3 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/helper-validator-option': 7.22.15 + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3) + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.3) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.3) + '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-async-generator-functions': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-block-scoping': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-class-static-block': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-classes': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-dynamic-import': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-export-namespace-from': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-for-of': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-json-strings': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-logical-assignment-operators': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-modules-systemjs': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.3) + '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-numeric-separator': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-object-rest-spread': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-optional-catch-binding': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-optional-chaining': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-private-property-in-object': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.3) + '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.3) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.3) + babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.23.3) + babel-plugin-polyfill-corejs3: 0.8.6(@babel/core@7.23.3) + babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.23.3) + core-js-compat: 3.33.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.3): + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/types': 7.23.3 + esutils: 2.0.3 + dev: false + + /@babel/regjsgen@0.8.0: + resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} + dev: false + + /@babel/runtime@7.23.2: + resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.14.0 + dev: false + + /@babel/template@7.22.15: + resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/parser': 7.23.3 + '@babel/types': 7.23.3 + dev: false + + /@babel/traverse@7.23.3: + resolution: {integrity: sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.22.13 + '@babel/generator': 7.23.3 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.23.3 + '@babel/types': 7.23.3 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@babel/types@7.23.3: + resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.22.5 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: false + /@discordjs/builders@1.7.0: resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} engines: {node: '>=16.11.0'} @@ -202,6 +1401,43 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@jridgewell/gen-mapping@0.3.3: + resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.20 + dev: false + + /@jridgewell/resolve-uri@3.1.1: + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/set-array@1.1.2: + resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} + engines: {node: '>=6.0.0'} + dev: false + + /@jridgewell/source-map@0.3.5: + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + dependencies: + '@jridgewell/gen-mapping': 0.3.3 + '@jridgewell/trace-mapping': 0.3.20 + dev: false + + /@jridgewell/sourcemap-codec@1.4.15: + resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} + dev: false + + /@jridgewell/trace-mapping@0.3.20: + resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.4.15 + dev: false + /@mapbox/node-pre-gyp@1.0.11: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true @@ -226,12 +1462,10 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 - dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} - dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -239,7 +1473,6 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 - dev: true /@npmcli/fs@1.1.1: resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} @@ -261,6 +1494,90 @@ packages: dev: false optional: true + /@nuxt/devalue@1.2.5: + resolution: {integrity: sha512-Tg86C7tqzvZtZli2BQVqgzZN136mZDTgauvJXagglKkP2xt5Kw3NUIiJyjX0Ww/IZy2xVmD0LN+CEPpij4dB2g==} + dependencies: + consola: 2.15.3 + dev: false + + /@rollup/plugin-babel@5.3.1(@babel/core@7.23.3)(rollup@2.79.1): + resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} + engines: {node: '>= 10.0.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@types/babel__core': ^7.1.9 + rollup: ^1.20.0||^2.0.0 + peerDependenciesMeta: + '@types/babel__core': + optional: true + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-module-imports': 7.22.15 + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + rollup: 2.79.1 + dev: false + + /@rollup/plugin-commonjs@16.0.0(rollup@2.79.1): + resolution: {integrity: sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^2.30.0 + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + commondir: 1.0.1 + estree-walker: 2.0.2 + glob: 7.2.3 + is-reference: 1.2.1 + magic-string: 0.25.9 + resolve: 1.22.8 + rollup: 2.79.1 + dev: false + + /@rollup/plugin-node-resolve@10.0.0(rollup@2.79.1): + resolution: {integrity: sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A==} + engines: {node: '>= 10.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + '@types/resolve': 1.17.1 + builtin-modules: 3.3.0 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.8 + rollup: 2.79.1 + dev: false + + /@rollup/plugin-replace@2.4.2(rollup@2.79.1): + resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} + peerDependencies: + rollup: ^1.20.0 || ^2.0.0 + dependencies: + '@rollup/pluginutils': 3.1.0(rollup@2.79.1) + magic-string: 0.25.9 + rollup: 2.79.1 + dev: false + + /@rollup/pluginutils@3.1.0(rollup@2.79.1): + resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} + engines: {node: '>= 8.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0 + dependencies: + '@types/estree': 0.0.39 + estree-walker: 1.0.1 + picomatch: 2.3.1 + rollup: 2.79.1 + dev: false + + /@rollup/pluginutils@4.2.1: + resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} + engines: {node: '>= 8.0.0'} + dependencies: + estree-walker: 2.0.2 + picomatch: 2.3.1 + dev: false + /@sapphire/async-queue@1.5.0: resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -298,6 +1615,13 @@ packages: dev: false optional: true + /@types/body-parser@1.19.5: + resolution: {integrity: sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==} + dependencies: + '@types/connect': 3.4.38 + '@types/node': 20.9.0 + dev: true + /@types/cacheable-request@6.0.3: resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} dependencies: @@ -307,14 +1631,50 @@ packages: '@types/responselike': 1.0.3 dev: false + /@types/connect@3.4.38: + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + dependencies: + '@types/node': 20.9.0 + dev: true + /@types/d3-array@3.2.1: resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} dev: true + /@types/estree@0.0.39: + resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} + dev: false + + /@types/estree@1.0.5: + resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} + dev: false + + /@types/express-serve-static-core@4.17.41: + resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} + dependencies: + '@types/node': 20.9.0 + '@types/qs': 6.9.10 + '@types/range-parser': 1.2.7 + '@types/send': 0.17.4 + dev: true + + /@types/express@4.17.21: + resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} + dependencies: + '@types/body-parser': 1.19.5 + '@types/express-serve-static-core': 4.17.41 + '@types/qs': 6.9.10 + '@types/serve-static': 1.15.5 + dev: true + /@types/http-cache-semantics@4.0.4: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} dev: false + /@types/http-errors@2.0.4: + resolution: {integrity: sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==} + dev: true + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -325,16 +1685,37 @@ packages: '@types/node': 20.9.0 dev: false + /@types/mime@1.3.5: + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + dev: true + + /@types/mime@3.0.4: + resolution: {integrity: sha512-iJt33IQnVRkqeqC7PzBHPTC6fDlRNRW8vjrgqtScAhrmMwe8c4Eo7+fUGTa+XdWrpEgpyKWMYmi2dIwMAYRzPw==} + dev: true + /@types/node@20.9.0: resolution: {integrity: sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==} dependencies: undici-types: 5.26.5 - dev: false /@types/parse-color@1.0.3: resolution: {integrity: sha512-1zpnjrXCl0KklQOtMDDXhqQN9ouePkt4NBoAJ/dRjyMqWMkegyIqeZUOf3Xq4yaUPPdY8wmR8cye/D9v1kcrsQ==} dev: true + /@types/qs@6.9.10: + resolution: {integrity: sha512-3Gnx08Ns1sEoCrWssEgTSJs/rsT2vhGP+Ja9cnnk9k4ALxinORlQneLXFeFKOTJMOeZUFD1s7w+w2AphTpvzZw==} + dev: true + + /@types/range-parser@1.2.7: + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + dev: true + + /@types/resolve@1.17.1: + resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} + dependencies: + '@types/node': 20.9.0 + dev: false + /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: @@ -345,6 +1726,21 @@ packages: resolution: {integrity: sha512-+d+WYC1BxJ6yVOgUgzK8gWvp5qF8ssV5r4nsDcZWKRWcDQLQ619tvWAxJQYGgBrO1MnLJC7a5GtiYsAoQ47dJg==} dev: true + /@types/send@0.17.4: + resolution: {integrity: sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==} + dependencies: + '@types/mime': 1.3.5 + '@types/node': 20.9.0 + dev: true + + /@types/serve-static@1.15.5: + resolution: {integrity: sha512-PDRk21MnK70hja/YF8AHfC7yIsiQHn1rcXx7ijCFBX/k+XQJhQT/gw3xekXKJvx+5SXaMMS8oqQy09Mzvz2TuQ==} + dependencies: + '@types/http-errors': 2.0.4 + '@types/mime': 3.0.4 + '@types/node': 20.9.0 + dev: true + /@types/ws@8.5.9: resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: @@ -496,6 +1892,14 @@ packages: requiresBuild: true dev: false + /accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + dev: false + /acorn-jsx@5.3.2(acorn@8.11.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -508,7 +1912,6 @@ packages: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true - dev: true /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -551,6 +1954,13 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: false + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -583,14 +1993,79 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + dev: false + /array-union@2.1.0: resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} engines: {node: '>=8'} dev: true + /at-least-node@1.0.0: + resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} + engines: {node: '>= 4.0.0'} + dev: false + + /babel-plugin-polyfill-corejs2@0.4.6(@babel/core@7.23.3): + resolution: {integrity: sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/compat-data': 7.23.3 + '@babel/core': 7.23.3 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + + /babel-plugin-polyfill-corejs3@0.8.6(@babel/core@7.23.3): + resolution: {integrity: sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) + core-js-compat: 3.33.2 + transitivePeerDependencies: + - supports-color + dev: false + + /babel-plugin-polyfill-regenerator@0.5.3(@babel/core@7.23.3): + resolution: {integrity: sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + dependencies: + '@babel/core': 7.23.3 + '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) + transitivePeerDependencies: + - supports-color + dev: false + /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + /body-parser@1.20.1: + resolution: {integrity: sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.11.0 + raw-body: 2.5.1 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -602,7 +2077,31 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - dev: true + + /browserslist@4.22.1: + resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001563 + electron-to-chromium: 1.4.588 + node-releases: 2.0.13 + update-browserslist-db: 1.0.13(browserslist@4.22.1) + dev: false + + /buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + dev: false + + /builtin-modules@3.3.0: + resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} + engines: {node: '>=6'} + dev: false + + /bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + dev: false /cacache@15.3.0: resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==} @@ -650,11 +2149,32 @@ packages: responselike: 2.0.1 dev: false + /call-bind@1.0.5: + resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} + dependencies: + function-bind: 1.1.2 + get-intrinsic: 1.2.2 + set-function-length: 1.1.1 + dev: false + /callsites@3.1.0: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} dev: true + /caniuse-lite@1.0.30001563: + resolution: {integrity: sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==} + dev: false + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: false + /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -684,12 +2204,22 @@ packages: resolution: {integrity: sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==} dev: false + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -707,13 +2237,61 @@ packages: engines: {node: '>=14'} dev: false + /commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + dev: false + + /commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + dev: false + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + /consola@2.15.3: + resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} + dev: false + /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: false + /content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + dev: false + + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: false + + /cookie-signature@1.0.6: + resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} + dev: false + + /cookie@0.5.0: + resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} + engines: {node: '>= 0.6'} + dev: false + + /core-js-compat@3.33.2: + resolution: {integrity: sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==} + dependencies: + browserslist: 4.22.1 + dev: false + + /core-js@3.33.2: + resolution: {integrity: sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==} + requiresBuild: true + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -729,6 +2307,17 @@ packages: internmap: 1.0.1 dev: false + /debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + dependencies: + ms: 2.0.0 + dev: false + /debug@4.3.4: resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -751,15 +2340,39 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: false + /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} dev: false + /define-data-property@1.1.1: + resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} + engines: {node: '>= 0.4'} + dependencies: + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: false + /delegates@1.0.0: resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} dev: false + /depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dev: false + + /destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + dev: false + /detect-libc@2.0.2: resolution: {integrity: sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==} engines: {node: '>=8'} @@ -810,11 +2423,24 @@ packages: esutils: 2.0.3 dev: true + /ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + dev: false + + /electron-to-chromium@1.4.588: + resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==} + dev: false + /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} requiresBuild: true dev: false + /encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + dev: false + /encoding@0.1.13: resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==} requiresBuild: true @@ -847,6 +2473,15 @@ packages: engines: {node: '>=6'} dev: false + /escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + dev: false + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: false + /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -945,10 +2580,107 @@ packages: engines: {node: '>=4.0'} dev: true + /estree-walker@0.2.1: + resolution: {integrity: sha512-6/I1dwNKk0N9iGOU3ydzAAurz4NPo/ttxZNCqgIVbWFvWyzWBSNonRrJ5CpjDuyBfmM7ENN7WCzUi9aT/UPXXQ==} + dev: false + + /estree-walker@1.0.1: + resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} + dev: false + + /estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + dev: false + /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - dev: true + + /etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + dev: false + + /eval@0.1.8: + resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} + engines: {node: '>= 0.8'} + dependencies: + '@types/node': 20.9.0 + require-like: 0.1.2 + dev: false + + /express-svelte@1.0.8(express@4.18.2)(svelte@3.59.2): + resolution: {integrity: sha512-O2oEAUcbLJwRLtIDWyGq34rcipcBRd3zic8FFB7zHGzr3+V6cP3GFd1lfTBK+wI9HcLxeGzbxJ3gMkAOx9pksw==} + peerDependencies: + express: ^4.17.1 + svelte: ^3.38.2 + dependencies: + '@babel/core': 7.23.3 + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) + '@babel/plugin-transform-runtime': 7.23.3(@babel/core@7.23.3) + '@babel/preset-env': 7.23.3(@babel/core@7.23.3) + '@babel/runtime': 7.23.2 + '@nuxt/devalue': 1.2.5 + '@rollup/plugin-babel': 5.3.1(@babel/core@7.23.3)(rollup@2.79.1) + '@rollup/plugin-commonjs': 16.0.0(rollup@2.79.1) + '@rollup/plugin-node-resolve': 10.0.0(rollup@2.79.1) + '@rollup/plugin-replace': 2.4.2(rollup@2.79.1) + core-js: 3.33.2 + eval: 0.1.8 + express: 4.18.2 + fast-glob: 3.3.2 + fs-extra: 9.1.0 + lodash: 4.17.21 + rollup: 2.79.1 + rollup-plugin-css-only: 3.1.0(rollup@2.79.1) + rollup-plugin-svelte: 7.1.6(rollup@2.79.1)(svelte@3.59.2) + rollup-plugin-svelte-svg: 0.2.3(svelte@3.59.2) + rollup-plugin-terser: 7.0.2(rollup@2.79.1) + source-map-support: 0.5.21 + svelte: 3.59.2 + transitivePeerDependencies: + - '@types/babel__core' + - supports-color + dev: false + + /express@4.18.2: + resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} + engines: {node: '>= 0.10.0'} + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.1 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.5.0 + cookie-signature: 1.0.6 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.2.0 + fresh: 0.5.2 + http-errors: 2.0.0 + merge-descriptors: 1.0.1 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.7 + proxy-addr: 2.0.7 + qs: 6.11.0 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.18.0 + serve-static: 1.15.0 + setprototypeof: 1.2.0 + statuses: 2.0.1 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + dev: false /fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -962,7 +2694,6 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 - dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -976,7 +2707,6 @@ packages: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 - dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -990,7 +2720,21 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 - dev: true + + /finalhandler@1.2.0: + resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} + engines: {node: '>= 0.8'} + dependencies: + debug: 2.6.9 + encodeurl: 1.0.2 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.1 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + dev: false /find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} @@ -1013,6 +2757,26 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + dev: false + + /fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + dev: false + + /fs-extra@9.1.0: + resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} + engines: {node: '>=10'} + dependencies: + at-least-node: 1.0.0 + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: false + /fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -1024,6 +2788,14 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} requiresBuild: true + /fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: false + optional: true + /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: false @@ -1059,6 +2831,20 @@ packages: dev: false optional: true + /gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + dev: false + + /get-intrinsic@1.2.2: + resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} + dependencies: + function-bind: 1.1.2 + has-proto: 1.0.1 + has-symbols: 1.0.3 + hasown: 2.0.0 + dev: false + /get-package-type@0.1.0: resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} engines: {node: '>=8.0.0'} @@ -1080,7 +2866,6 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 - dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -1100,6 +2885,11 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 + /globals@11.12.0: + resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} + engines: {node: '>=4'} + dev: false + /globals@13.23.0: resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} engines: {node: '>=8'} @@ -1119,6 +2909,12 @@ packages: slash: 3.0.0 dev: true + /gopd@1.0.1: + resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + dependencies: + get-intrinsic: 1.2.2 + dev: false + /got@11.8.6: resolution: {integrity: sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==} engines: {node: '>=10.19.0'} @@ -1140,16 +2936,36 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} requiresBuild: true dev: false - optional: true /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: false + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + /has-property-descriptors@1.0.1: + resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} + dependencies: + get-intrinsic: 1.2.2 + dev: false + + /has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + dev: false + + /has-symbols@1.0.3: + resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} + engines: {node: '>= 0.4'} + dev: false + /has-unicode@2.0.1: resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} dev: false @@ -1165,6 +2981,17 @@ packages: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} dev: false + /http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + dev: false + /http-proxy-agent@4.0.1: resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==} engines: {node: '>= 6'} @@ -1204,6 +3031,13 @@ packages: dev: false optional: true + /iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + dependencies: + safer-buffer: 2.1.2 + dev: false + /iconv-lite@0.6.3: resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} engines: {node: '>=0.10.0'} @@ -1269,6 +3103,11 @@ packages: dev: false optional: true + /ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + dev: false + /is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} dependencies: @@ -1278,7 +3117,6 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} - dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -1291,7 +3129,6 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 - dev: true /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} @@ -1299,20 +3136,42 @@ packages: dev: false optional: true + /is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + dev: false + /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} - dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true + /is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + dependencies: + '@types/estree': 1.0.5 + dev: false + /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} requiresBuild: true + /jest-worker@26.6.2: + resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/node': 20.9.0 + merge-stream: 2.0.0 + supports-color: 7.2.0 + dev: false + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1320,6 +3179,17 @@ packages: argparse: 2.0.1 dev: true + /jsesc@0.5.0: + resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} + hasBin: true + dev: false + + /jsesc@2.5.2: + resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} + engines: {node: '>=4'} + hasBin: true + dev: false + /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1335,6 +3205,20 @@ packages: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: false + /json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + dev: false + + /jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + dev: false + /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -1402,6 +3286,10 @@ packages: p-locate: 5.0.0 dev: true + /lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -1419,6 +3307,12 @@ packages: engines: {node: '>=8'} dev: false + /lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + dependencies: + yallist: 3.1.1 + dev: false + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1430,6 +3324,12 @@ packages: resolution: {integrity: sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==} dev: false + /magic-string@0.25.9: + resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} + dependencies: + sourcemap-codec: 1.4.8 + dev: false + /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -1464,10 +3364,27 @@ packages: dev: false optional: true + /media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + dev: false + + /merge-descriptors@1.0.1: + resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} + dev: false + + /merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + dev: false + /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} - dev: true + + /methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + dev: false /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} @@ -1475,7 +3392,24 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 - dev: true + + /mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + dev: false + + /mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + dependencies: + mime-db: 1.52.0 + dev: false + + /mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + dev: false /mimic-response@1.0.1: resolution: {integrity: sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==} @@ -1567,6 +3501,10 @@ packages: hasBin: true dev: false + /ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + dev: false + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1574,7 +3512,6 @@ packages: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} requiresBuild: true dev: false - optional: true /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -1585,7 +3522,6 @@ packages: engines: {node: '>= 0.6'} requiresBuild: true dev: false - optional: true /node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} @@ -1625,6 +3561,10 @@ packages: dev: false optional: true + /node-releases@2.0.13: + resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} + dev: false + /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -1664,6 +3604,17 @@ packages: engines: {node: '>=0.10.0'} dev: false + /object-inspect@1.13.1: + resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} + dev: false + + /on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + dependencies: + ee-first: 1.1.1 + dev: false + /once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} dependencies: @@ -1726,6 +3677,11 @@ packages: color-convert: 0.5.3 dev: false + /parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + dev: false + /path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1745,6 +3701,10 @@ packages: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false + /path-to-regexp@0.1.7: + resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} + dev: false + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1754,10 +3714,13 @@ packages: resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} dev: false + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: false + /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -1790,6 +3753,14 @@ packages: dev: false optional: true + /proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + dev: false + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -1802,9 +3773,15 @@ packages: engines: {node: '>=6'} dev: true + /qs@6.11.0: + resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} + engines: {node: '>=0.6'} + dependencies: + side-channel: 1.0.4 + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - dev: true /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} @@ -1818,6 +3795,27 @@ packages: json-stringify-safe: 5.0.1 dev: false + /randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + dependencies: + safe-buffer: 5.2.1 + dev: false + + /range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + dev: false + + /raw-body@2.5.1: + resolution: {integrity: sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==} + engines: {node: '>= 0.8'} + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + dev: false + /readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -1834,6 +3832,50 @@ packages: resolve: 1.22.8 dev: false + /regenerate-unicode-properties@10.1.1: + resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} + engines: {node: '>=4'} + dependencies: + regenerate: 1.4.2 + dev: false + + /regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + dev: false + + /regenerator-runtime@0.14.0: + resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + dev: false + + /regenerator-transform@0.15.2: + resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} + dependencies: + '@babel/runtime': 7.23.2 + dev: false + + /regexpu-core@5.3.2: + resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} + engines: {node: '>=4'} + dependencies: + '@babel/regjsgen': 0.8.0 + regenerate: 1.4.2 + regenerate-unicode-properties: 10.1.1 + regjsparser: 0.9.1 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + dev: false + + /regjsparser@0.9.1: + resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} + hasBin: true + dependencies: + jsesc: 0.5.0 + dev: false + + /require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + dev: false + /resolve-alpn@1.2.1: resolution: {integrity: sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==} dev: false @@ -1848,6 +3890,11 @@ packages: engines: {node: '>=8'} dev: false + /resolve.exports@2.0.2: + resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} + engines: {node: '>=10'} + dev: false + /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -1873,7 +3920,6 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - dev: true /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -1881,11 +3927,70 @@ packages: dependencies: glob: 7.2.3 + /rollup-plugin-css-only@3.1.0(rollup@2.79.1): + resolution: {integrity: sha512-TYMOE5uoD76vpj+RTkQLzC9cQtbnJNktHPB507FzRWBVaofg7KhIqq1kGbcVOadARSozWF883Ho9KpSPKH8gqA==} + engines: {node: '>=10.12.0'} + peerDependencies: + rollup: 1 || 2 + dependencies: + '@rollup/pluginutils': 4.2.1 + rollup: 2.79.1 + dev: false + + /rollup-plugin-svelte-svg@0.2.3(svelte@3.59.2): + resolution: {integrity: sha512-WH2KqihgVbcJT/PrjiJ/ipctrGueRdOLaaEuAIbXYqfsEzNb9krOVLyc5TL7to3cEKL6DiAa8IEe7IL1oqVolg==} + peerDependencies: + svelte: ^3.16.7 + dependencies: + rollup-pluginutils: 1.5.2 + svelte: 3.59.2 + dev: false + + /rollup-plugin-svelte@7.1.6(rollup@2.79.1)(svelte@3.59.2): + resolution: {integrity: sha512-nVFRBpGWI2qUY1OcSiEEA/kjCY2+vAjO9BI8SzA7NRrh2GTunLd6w2EYmnMt/atgdg8GvcNjLsmZmbQs/u4SQA==} + engines: {node: '>=10'} + peerDependencies: + rollup: '>=2.0.0' + svelte: '>=3.5.0' + dependencies: + '@rollup/pluginutils': 4.2.1 + resolve.exports: 2.0.2 + rollup: 2.79.1 + svelte: 3.59.2 + dev: false + + /rollup-plugin-terser@7.0.2(rollup@2.79.1): + resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} + deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser + peerDependencies: + rollup: ^2.0.0 + dependencies: + '@babel/code-frame': 7.22.13 + jest-worker: 26.6.2 + rollup: 2.79.1 + serialize-javascript: 4.0.0 + terser: 5.24.0 + dev: false + + /rollup-pluginutils@1.5.2: + resolution: {integrity: sha512-SjdWWWO/CUoMpDy8RUbZ/pSpG68YHmhk5ROKNIoi2En9bJ8bTt3IhYi254RWiTclQmL7Awmrq+rZFOhZkJAHmQ==} + dependencies: + estree-walker: 0.2.1 + minimatch: 3.1.2 + dev: false + + /rollup@2.79.1: + resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} + engines: {node: '>=10.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.3 + dev: false + /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 - dev: true /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -1896,7 +4001,6 @@ packages: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} requiresBuild: true dev: false - optional: true /semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -1910,10 +4014,63 @@ packages: dependencies: lru-cache: 6.0.0 + /send@0.18.0: + resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} + engines: {node: '>= 0.8.0'} + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + dev: false + + /serialize-javascript@4.0.0: + resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} + dependencies: + randombytes: 2.1.0 + dev: false + + /serve-static@1.15.0: + resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} + engines: {node: '>= 0.8.0'} + dependencies: + encodeurl: 1.0.2 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.18.0 + transitivePeerDependencies: + - supports-color + dev: false + /set-blocking@2.0.0: resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} dev: false + /set-function-length@1.1.1: + resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} + engines: {node: '>= 0.4'} + dependencies: + define-data-property: 1.1.1 + get-intrinsic: 1.2.2 + gopd: 1.0.1 + has-property-descriptors: 1.0.1 + dev: false + + /setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + dev: false + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -1926,6 +4083,14 @@ packages: engines: {node: '>=8'} dev: true + /side-channel@1.0.4: + resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} + dependencies: + call-bind: 1.0.5 + get-intrinsic: 1.2.2 + object-inspect: 1.13.1 + dev: false + /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: false @@ -1965,6 +4130,23 @@ packages: dev: false optional: true + /source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + dev: false + + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + + /sourcemap-codec@1.4.8: + resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead + dev: false + /sqlite3@5.1.6: resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} requiresBuild: true @@ -1992,6 +4174,11 @@ packages: dev: false optional: true + /statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + dev: false + /string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2019,6 +4206,13 @@ packages: engines: {node: '>=8'} dev: true + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: false + /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -2030,6 +4224,11 @@ packages: engines: {node: '>= 0.4'} dev: false + /svelte@3.59.2: + resolution: {integrity: sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==} + engines: {node: '>= 8'} + dev: false + /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} @@ -2047,6 +4246,17 @@ packages: engines: {node: '>=8.0.0'} dev: false + /terser@5.24.0: + resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.2 + commander: 2.20.3 + source-map-support: 0.5.21 + dev: false + /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -2056,12 +4266,21 @@ packages: engines: {node: '>=8'} dev: false + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: false + /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 - dev: true + + /toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + dev: false /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -2096,6 +4315,14 @@ packages: engines: {node: '>=10'} dev: true + /type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + dev: false + /typescript@5.2.2: resolution: {integrity: sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==} engines: {node: '>=14.17'} @@ -2104,7 +4331,6 @@ packages: /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - dev: false /undici@5.27.2: resolution: {integrity: sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==} @@ -2113,6 +4339,29 @@ packages: '@fastify/busboy': 2.1.0 dev: false + /unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + dev: false + + /unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + dev: false + + /unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + dev: false + + /unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + dev: false + /unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} requiresBuild: true @@ -2129,6 +4378,27 @@ packages: dev: false optional: true + /universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + dev: false + + /unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + dev: false + + /update-browserslist-db@1.0.13(browserslist@4.22.1): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.22.1 + escalade: 3.1.1 + picocolors: 1.0.0 + dev: false + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: @@ -2140,6 +4410,16 @@ packages: requiresBuild: true dev: false + /utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + dev: false + + /vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + dev: false + /webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: false @@ -2181,6 +4461,10 @@ packages: optional: true dev: false + /yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + dev: false + /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 0042ab2..2f325fc 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -23,7 +23,7 @@ export default { if (sub === 'create') { interaction.reply({ ephemeral: true, - content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe\nOnce done, click the button below and paste the resulting string in.` + content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/\nOnce done, click the button below and paste the resulting string in.` }); } } diff --git a/src/index.ts b/src/index.ts index 30ed419..3ce7cc5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,6 +7,7 @@ import * as log from './lib/log'; import chalk from 'chalk'; import prettyBytes from 'pretty-bytes'; import { Command } from './types/index'; +import { startServer } from './web'; const bot = new Client({ intents: [ @@ -27,6 +28,9 @@ bot.config = { async function init() { log.nonsense('booting chip...'); + log.nonsense('starting up web interface...'); + await startServer(sitePort); + log.nonsense('setting up connection...'); try { @@ -48,7 +52,7 @@ bot.on(Events.ClientReady, async () => { bot.commands = new Collection(); const cmdFiles = fs.readdirSync(path.join(__dirname, './commands')).filter((file) => file.endsWith('.js')); for (const file of cmdFiles) { - const cmd = (await import(`./commands/${file}`)) as Command; + const cmd = (await import(`./commands/${file}`)).default as Command; bot.commands.set(cmd.data.name, cmd); if (cmd.onClientReady) cmd.onClientReady(bot); } diff --git a/src/web.ts b/src/web.ts new file mode 100644 index 0000000..bccaebd --- /dev/null +++ b/src/web.ts @@ -0,0 +1,10 @@ +import express from 'express'; +import * as log from './lib/log'; + +export async function startServer(port: number) { + const app = express(); + + app.use(express.static('static/')); + + app.listen(port, () => log.info(`web interface listening on ${port}`)); +} \ No newline at end of file diff --git a/static/assets/jillo.png b/static/assets/jillo.png new file mode 100644 index 0000000000000000000000000000000000000000..e9e5e1a2d8faa4d9ea9da0b52ba4bbeb0646ad60 GIT binary patch literal 20798 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 literal 0 HcmV?d00001 diff --git a/static/create-recipe/index.html b/static/create-recipe/index.html new file mode 100644 index 0000000..32f95c0 --- /dev/null +++ b/static/create-recipe/index.html @@ -0,0 +1 @@ +hi \ No newline at end of file diff --git a/static/index.html b/static/index.html new file mode 100644 index 0000000..d4b4966 --- /dev/null +++ b/static/index.html @@ -0,0 +1,27 @@ + + + + + + jillo + + + + + + + + + + +
+ +

jillo!

+
+ invite + · + repo +
+
+ + \ No newline at end of file diff --git a/static/style.css b/static/style.css new file mode 100644 index 0000000..399f018 --- /dev/null +++ b/static/style.css @@ -0,0 +1,80 @@ +:root { + --accent-color: #f17d10; +} + +body { + padding: 0px; + margin: 0px; + overflow-x: hidden; + color: var(--text-color); + background-color: var(--background-color); + font-family: 'Balsamiq Sans', sans-serif; + font-weight: 300; + width: 100%; + min-height: 100vh; + text-underline-offset: 3px; + font-size: 16px; + color-scheme: dark; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; +} + +:root { + --text-color: #111; + --background-color: #fefefd; +} + +@media (prefers-color-scheme: dark) { + :root { + --text-color: #eee; + --background-color: #111110; + } +} + +a { + text-decoration: none; + color: var(--accent-color); +} +a:hover { + text-decoration: underline; +} + +#main { + display: flex; + text-align: center; + flex-direction: column; + align-items: center; + justify-content: center; + min-height: 100vh; +} +#main img { + display: block; + height: 18rem; + width: auto; + animation: 1s popup; + animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); + transition: transform 0.15s, opacity 0.1s; +} +#main img:active { + transform: scale(0.97); + opacity: 0.9; +} +#main :not(img) { + animation: 0.8s fadein; +} +#main h1 { + font-size: 4rem; + margin-top: 0.5rem; + margin-bottom: 1rem; +} + +@keyframes fadein { + 0% { opacity: 0; } + 100% { opacity: 1; } +} +@keyframes popup { + 0% { transform: scale(0) rotate(40deg) } + 100% { transform: scale(1) rotate(0deg) } +} \ No newline at end of file diff --git a/svelte.config.mjs b/svelte.config.mjs new file mode 100644 index 0000000..3bce8ea --- /dev/null +++ b/svelte.config.mjs @@ -0,0 +1,7 @@ +import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; + +export default { + // Consult https://svelte.dev/docs#compile-time-svelte-preprocess + // for more information about preprocessors + preprocess: vitePreprocess(), +}; diff --git a/vite.config.mjs b/vite.config.mjs new file mode 100644 index 0000000..e3a889f --- /dev/null +++ b/vite.config.mjs @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import { svelte } from '@sveltejs/vite-plugin-svelte'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [svelte()], +}); From 3da36de9f61f5c7bddc5b70be65af6895e9830be Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 02:55:39 +0300 Subject: [PATCH 28/66] web interface holy FUCK --- src/commands/recipe.ts | 2 +- src/web.ts | 18 ++++ static/assets/jillo_small.png | Bin 0 -> 16458 bytes static/create-recipe/index.html | 59 ++++++++++- static/create-recipe/script.js | 145 ++++++++++++++++++++++++++ static/favicon.ico | Bin 0 -> 36534 bytes static/style.css | 174 +++++++++++++++++++++++++++++++- 7 files changed, 394 insertions(+), 4 deletions(-) create mode 100644 static/assets/jillo_small.png create mode 100644 static/create-recipe/script.js create mode 100644 static/favicon.ico diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 2f325fc..1527fc1 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -23,7 +23,7 @@ export default { if (sub === 'create') { interaction.reply({ ephemeral: true, - content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/\nOnce done, click the button below and paste the resulting string in.` + content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/?guild=${interaction.guildId}\nOnce done, click the button below and paste the resulting string in.` }); } } diff --git a/src/web.ts b/src/web.ts index bccaebd..b2c625a 100644 --- a/src/web.ts +++ b/src/web.ts @@ -1,10 +1,28 @@ import express from 'express'; import * as log from './lib/log'; +import { CustomItem, db } from './lib/db'; +import { defaultItems } from './lib/rpg/items'; export async function startServer(port: number) { const app = express(); app.use(express.static('static/')); + app.get('/api/items', async (req, res) => { + const guildID = req.query.guild; + + let customItems : Partial[]; + if (guildID) { + customItems = await db('customItems') + .select('emoji', 'name', 'id', 'description') + .where('guild', guildID) + .limit(25); + } else { + customItems = []; + } + + res.json([...defaultItems, ...customItems]); + }); + app.listen(port, () => log.info(`web interface listening on ${port}`)); } \ No newline at end of file diff --git a/static/assets/jillo_small.png b/static/assets/jillo_small.png new file mode 100644 index 0000000000000000000000000000000000000000..5d89a8dc9b5a7340a17788d7ea2b70df5f7809cd GIT binary patch literal 16458 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 k_0PUvRMWX@Mw{jQA0b8aDC + + + + + jillo + + + + + + + + + + + + +
+
+
+
+ jillo +
+ +
+ +

Recipe Creator

+ +
+ loading available items... +
+ +

Drag items from above into the below lists:

+ +

Inputs Ingredients necessary to create the outputs

+
+
+

Requirements Unlike inputs, these are not consumed, but are necessary

+
+
+

Outputs The result of the recipe

+
+
+ +

Drag an item out of the list in order to remove it

+ +

Recipe string

+

+      Copy-paste this into Jillo to create your recipe
+    
+ + \ No newline at end of file diff --git a/static/create-recipe/script.js b/static/create-recipe/script.js new file mode 100644 index 0000000..3b498a7 --- /dev/null +++ b/static/create-recipe/script.js @@ -0,0 +1,145 @@ +let resolveLoaded; +const loaded = new Promise(resolve => resolveLoaded = resolve); + +function e(unsafeText) { + let div = document.createElement('div'); + div.innerText = unsafeText; + return div.innerHTML; +} + +/** + * @type {import('../../src/lib/rpg/items').Item | null} + */ +let draggedItem = null; +/** + * @type {Record} + */ +let itemLists = {}; + +function listToString(list) { + return list.map(stack => `${stack.item.id},${stack.quantity}`).join(';'); +} +function updateString() { + document.querySelector('#recipe-string').innerText = [ + listToString(itemLists['inputs'] || []), + listToString(itemLists['requirements'] || []), + listToString(itemLists['outputs'] || []), + ].join('|'); +} + +/** + * @param {import('../../src/lib/rpg/items').Item} item + */ +function renderItem(item) { + const i = document.createElement('div'); + i.innerHTML = ` +
+ ${e(item.emoji)} +
+
+
${e(item.name)}
+
${item.description ? e(item.description) : 'No description'}
+
+ `; + i.classList.add('item'); + i.draggable = true; + i.addEventListener('dragstart', event => { + draggedItem = item; + event.target.classList.add('dragging'); + }); + i.addEventListener('dragend', event => { + draggedItem = null; + event.target.classList.remove('dragging'); + }); + return i; +} +function renderItemStack(item, quantity, type) { + const i = document.createElement('div'); + i.innerHTML = ` +
+ ${e(item.emoji)} +
+
+ x${quantity} +
+ `; + i.classList.add('itemstack'); + i.draggable = true; + i.addEventListener('dragstart', event => { + event.target.classList.add('dragging'); + }); + i.addEventListener('dragend', event => { + event.target.classList.remove('dragging'); + itemLists[type] = itemLists[type] || []; + const items = itemLists[type]; + const stackIdx = items.findIndex(n => n.item.id === item.id); + if (stackIdx !== -1) items.splice(stackIdx, 1); + document.querySelector(`.item-list[data-type="${type}"]`).replaceWith(renderItemList(items, type)); + updateString(); + }); + return i; +} +function renderItemList(items, type) { + const i = document.createElement('div'); + i.textContent = ''; + items.forEach(itemStack => { + i.appendChild(renderItemStack(itemStack.item, itemStack.quantity, type)); + }); + i.dataset.type = type; + i.classList.add('item-list'); + + // prevent default to allow drop + i.addEventListener('dragover', (event) => event.preventDefault(), false); + + i.addEventListener('dragenter', event => draggedItem && event.target.classList.add('dropping')); + i.addEventListener('dragleave', event => draggedItem && event.target.classList.remove('dropping')); + + i.addEventListener('drop', (event) => { + event.preventDefault(); + event.target.classList.remove('dropping'); + + if (!draggedItem) return; + + itemLists[type] = itemLists[type] || []; + const items = itemLists[type]; + + const itemStack = items.find(v => v.item.id === draggedItem.id); + + if (!itemStack) { + items.push({ + item: draggedItem, + quantity: 1 + }); + } else { + itemStack.quantity = itemStack.quantity + 1; + } + + updateString(); + + draggedItem = null; + + event.target.replaceWith(renderItemList(items, type)); + }); + + return i; +} + +Promise.all([ + fetch(`/api/items${document.location.search}`) + .then(res => res.json()), + loaded +]).then(items => { + const itemsContainer = document.querySelector('.items'); + itemsContainer.textContent = ''; + items[0].forEach(item => + itemsContainer.appendChild(renderItem(item)) + ); + document.querySelectorAll('.item-list').forEach(elem => { + const type = elem.dataset.type; + elem.replaceWith(renderItemList([], type)); + }); + + updateString(); +}); + +document.addEventListener('DOMContentLoaded', () => resolveLoaded()); \ No newline at end of file diff --git a/static/favicon.ico b/static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..28a815393975fdd4ba5726f1e920aadab7741783 GIT binary patch literal 36534 zcmcg#2YeLOw~l|`pCr3!v_K#bdT#-w_nPdc(R=T`7pb92uTm^@q$vu5ARr<|5ET&% zs3?L`L{I@Cr0ni_-* z3H9;uDTjeWJ|AH4IRAw#QX8-S7Z@0u&(}YCv7fQTTyehAE-66q&apG?@+j8e?m$qQ{ zH}dxP){NsjtGxMbiY(v%gVb$3Ljpq*Brtb^gca{038P+@+2?HgM=runwWcFUzk=a;2!zm3vh z&{nD5aitV{VXOof=x8$r$J>lSaaN4fp#1H=&tI%pA5ZvXcih)#ZVimjU_8Mf=7M3x zxc*3_8*Tvfq7{o24jo_ha^}A z&3H%lUa@4v-7yb5zVpi`GB9zUghg~0Vw1mcXQ>^NC}R&?k_9)D+~Vwt{R-f|<#+eE z&pUeAnm4>N=|GKq#d=B2E^DOem^a;yCZl#stsbvPiMo>{cUWf$%+uBun76GhPk6T{ z5!FX+tQ0q|fE%2ib92mbsFl&r5IxJ!VEzWUk=BVhhsDom`X0yoO8a}>3F{S;D zrY`=0(e<@s`fWf!bbjEq-p}9kox<9V9q~)Sc#Lt|-(dQZbnWkN8jDdoIM|#U*Z2>w z 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 K8!YV#i8p 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 literal 0 HcmV?d00001 diff --git a/static/style.css b/static/style.css index 399f018..db33617 100644 --- a/static/style.css +++ b/static/style.css @@ -11,10 +11,9 @@ body { font-family: 'Balsamiq Sans', sans-serif; font-weight: 300; width: 100%; - min-height: 100vh; text-underline-offset: 3px; font-size: 16px; - color-scheme: dark; + color-scheme: light dark; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -23,13 +22,19 @@ body { :root { --text-color: #111; + --text-color-light: #444; --background-color: #fefefd; + --background-color-dark: #fafafb; + --background-color-dark-2: #f8f8f9; } @media (prefers-color-scheme: dark) { :root { --text-color: #eee; + --text-color-light: #aaa; --background-color: #111110; + --background-color-dark: #151514; + --background-color-dark-2: #171718; } } @@ -77,4 +82,169 @@ a:hover { @keyframes popup { 0% { transform: scale(0) rotate(40deg) } 100% { transform: scale(1) rotate(0deg) } +} + +#content { + max-width: 1000px; + width: 100%; + margin: auto; + margin-bottom: 6rem; +} + +.header { + height: 3rem; + display: flex; + align-items: stretch; + padding: 0rem 1rem; + flex-direction: row; + text-shadow: 2px 2px 2px #000000; + margin-bottom: 2rem; +} +.header .bg { + position: absolute; + left: 0%; + right: 0%; + height: 3rem; + background: linear-gradient(#444, #222); + z-index: -1; + border-bottom: 2px ridge #aaa; +} +.header .left { + font-size: 1.5rem; + flex: 1 1 0px; + display: flex; + align-items: center; +} +.header .left a { + text-decoration: none !important; + color: #fff; +} + +.header .links { + flex: 0 0 auto; + text-align: right; + display: flex; + flex-direction: row; + align-items: center; + gap: 0.25rem; +} +.header .links img { + display: block; + width: auto; + height: 100%; +} + +.items { + max-width: 500px; + padding: 1em; + height: 200px; + overflow: auto; + background: linear-gradient(var(--background-color-dark), var(--background-color-dark-2)); + border-radius: 1em; + display: flex; + flex-direction: column; +} +.item { + display: flex; + flex-direction: row; + height: 3rem; + gap: 0.5rem; + outline: 0px solid rgba(255, 255, 255, 0.0); + transition: outline 0.1s; + padding: 0.5rem; + border-radius: 2rem; + cursor: grab; +} +.item:hover { + outline: 1px solid var(--text-color-light); +} +.item.dragging { + outline: 2px dashed var(--text-color-light); + transition: none; + background-color: var(--background-color); +} +.item .icon { + flex: 0 0 auto; + font-size: 1rem; + line-height: 1; + background-color: rgba(199, 199, 199, 0.25); + border-radius: 4rem; + display: flex; + align-items: center; + justify-content: center; + aspect-ratio: 1 / 1; + user-select: none; +} +.item .right { + flex: 1 1 0px; + display: flex; + flex-direction: column; + line-height: 1; +} +.item .right, .item .right > * { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.item .right .description { + font-size: 75%; + color: var(--text-color-light); +} +.item-list { + min-height: 2rem; + display: flex; + flex-direction: row; + flex-wrap: wrap; +} +.item-list.dropping::after { + display: flex; + align-items: center; + justify-content: center; + content: '+'; + height: 1.5rem; + width: 1.5rem; + outline: 2px dashed var(--text-color-light); + border-radius: 2rem; + padding: 0.5rem; + margin: 0.25rem; +} +.itemstack { + display: flex; + align-items: center; + height: 1.5rem; + line-height: 1; + padding: 0.5rem; + margin: 0.25rem; + outline: 1px solid var(--text-color-light); + background-color: var(--background-color-dark); + border-radius: 2rem; + gap: 0.5rem; +} +.itemstack.dragging { + outline: 2px dashed var(--text-color-light); + transition: none; + background-color: var(--background-color); +} + +.subtitle { + color: var(--text-color-light); + font-size: 1rem; + font-weight: normal; + margin-left: 0.5rem; +} +.subtitle::before { + content: '·'; + margin-right: 0.5rem; +} + +pre { + background: linear-gradient(var(--background-color-dark), var(--background-color-dark-2)); + overflow: auto; + word-break: normal; + padding: 0.5rem; +} + +.note { + font-style: italic; + color: var(--text-color-light); } \ No newline at end of file From 3b9c66ebfd0cb75b333c9934cf3e0e6d9aee9c6d Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 14:59:29 +0300 Subject: [PATCH 29/66] recipe creator FINISH --- customCraftingRecipes | 0 deploy-commands.cjs | 2 +- migrations/20231117173052_craftingRecipes.js | 12 +- src/commands/recipe.ts | 185 ++++++++++++++++++- src/commands/survey.ts | 1 + src/lib/db.ts | 10 + 6 files changed, 199 insertions(+), 11 deletions(-) create mode 100644 customCraftingRecipes diff --git a/customCraftingRecipes b/customCraftingRecipes new file mode 100644 index 0000000..e69de29 diff --git a/deploy-commands.cjs b/deploy-commands.cjs index f7e04a0..7053d6d 100644 --- a/deploy-commands.cjs +++ b/deploy-commands.cjs @@ -17,7 +17,7 @@ rest const commandFiles = fs.readdirSync("./dist/commands").filter((file) => file.endsWith(".js") && !file.startsWith('.')); for (const file of commandFiles) { - const command = require(`./dist/commands/${file}`); + const command = require(`./dist/commands/${file}`).default; commands.push(command); } diff --git a/migrations/20231117173052_craftingRecipes.js b/migrations/20231117173052_craftingRecipes.js index 1bb6ae4..3c9bd11 100644 --- a/migrations/20231117173052_craftingRecipes.js +++ b/migrations/20231117173052_craftingRecipes.js @@ -4,12 +4,12 @@ */ exports.up = function(knex) { return knex.schema - .createTable('craftingRecipes', (table) => { + .createTable('customCraftingRecipes', (table) => { table.increments('id'); - table.string('table'); + table.string('station'); }) - .createTable('craftingRecipeItems', (table) => { - table.integer('id').references('id').inTable('craftingRecipes').notNullable(); + .createTable('customCraftingRecipeItems', (table) => { + table.integer('id').references('id').inTable('customCraftingRecipes').notNullable(); table.integer('item').notNullable(); table.integer('quantity').defaultTo(1); table.enum('type', ['input', 'output', 'requirement']); @@ -22,6 +22,6 @@ exports.up = function(knex) { */ exports.down = function(knex) { return knex.schema - .dropTable('craftingRecipes') - .dropTable('craftingRecipeItems'); + .dropTable('customCraftingRecipes') + .dropTable('customCraftingRecipes'); }; diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 1527fc1..ef196e3 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -1,5 +1,9 @@ -import { CommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, Events, ModalBuilder, SlashCommandBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; import { Command } from '../types/index'; +import { Items, getItem } from '../lib/rpg/items'; +import { formatRecipe } from '../lib/rpg/recipes'; +import { craftingStations, getStation } from '../lib/rpg/craftingStations'; +import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../lib/db'; export default { data: new SlashCommandBuilder() @@ -16,15 +20,188 @@ export default { execute: async (interaction: CommandInteraction) => { if (!interaction.isChatInputCommand()) return; - interaction.deferReply({ ephemeral: true }); + await interaction.deferReply({ ephemeral: true }); const sub = interaction.options.getSubcommand(true); if (sub === 'create') { - interaction.reply({ + const row = new ActionRowBuilder().addComponents( + new ButtonBuilder().setCustomId(`recipe-create-${interaction.guildId}`).setLabel('I\'ve got my string ready!').setStyle(ButtonStyle.Primary) + ); + await interaction.followUp({ ephemeral: true, - content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/?guild=${interaction.guildId}\nOnce done, click the button below and paste the resulting string in.` + content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/?guild=${interaction.guildId}\nOnce done, click the button below and paste the resulting string in.`, + components: [row] }); } + }, + + async onClientReady(bot) { + bot.on(Events.InteractionCreate, async (interaction) => { + if (!('customId' in interaction)) return; + const id = interaction.customId; + if (!id.startsWith('recipe-')) return; + if (!interaction.member) return; + + if (id.startsWith('recipe-create-')) { + //const guildID = id.split('-')[2]; + + if (interaction.isMessageComponent()) { + const modal = new ModalBuilder() + .setCustomId(interaction.customId) + .setTitle('Recipe Creator'); + + const input = new TextInputBuilder() + .setCustomId('recipe-create-textbox') + .setLabel('Paste in your recipe string here:') + .setStyle(TextInputStyle.Paragraph) + .setRequired(true); + + const row = new ActionRowBuilder().addComponents(input); + modal.addComponents(row); + interaction.showModal(modal); + } else if (interaction.isModalSubmit()) { + const field = interaction.fields.getField('recipe-create-textbox', ComponentType.TextInput); + const recipeString = field.value; + + await interaction.deferReply({ ephemeral: true }); + + let parsed; + try { + parsed = await Promise.all( + recipeString + .split('|') + .map(items => + Promise.all( + items + .split(';') + .map(itemStack => + itemStack.split(',') + ) + .map(async ([itemID, quantity]) => ( + { + item: (await getItem(parseInt(itemID)))!, + quantity: parseInt(quantity) + } + )) + ) + ) + ) as Items[][]; + } catch (err) { + await interaction.followUp(`This is not a valid string!: \`${(err as Error).message}\``); + return; + } + + const + inputs = parsed[0] || [], + requirements = parsed[1] || [], + outputs = parsed[2] || []; + + const recipe = { + inputs, requirements, outputs, + station: 'hands', + id: 0 + }; + + const components = [ + new ActionRowBuilder().addComponents( + new StringSelectMenuBuilder() + .addOptions( + ...craftingStations + .map(station => ({ + label: `${station.emoji} ${station.name}`, + value: station.key, + description: station.description + })) + ) + .setMinValues(1) + .setMaxValues(1) + .setCustomId('recipe-select-station') + ), + new ActionRowBuilder().addComponents( + new ButtonBuilder() + .setCustomId('recipe-select-done') + .setLabel('Done') + .setStyle(ButtonStyle.Primary) + .setDisabled(true) + ) + ]; + + const msg = await interaction.followUp({ + content: `${formatRecipe(recipe)}\n_Select a crafting station, and you're good to go!_`, + components: components, + fetchReply: true + }); + + const selectCollector = msg.createMessageComponentCollector({ + componentType: ComponentType.StringSelect, + time: 60_000 * 5, + }); + + selectCollector.on('collect', selectInteraction => { + const newStation = selectInteraction.values[0]; + recipe.station = newStation; + components[1].components[0].setDisabled(false); + interaction.editReply({ + content: `${formatRecipe(recipe)}\n_Select a crafting station, and you're good to go!_`, + components: components + }); + const station = getStation(newStation); + selectInteraction.reply({ + content: `Set station to ${station?.emoji} **${station?.name}**`, + ephemeral: true + }); + }); + selectCollector.on('end', () => { + interaction.editReply({ + content: msg.content, + components: [] + }); + }); + + const buttonInteraction = await msg.awaitMessageComponent({ componentType: ComponentType.Button, time: 60_000 * 5 }); + selectCollector.stop(); + + const [customRecipe] = await db('customCraftingRecipes') + .insert({ + station: recipe.station + }) + .returning('id'); + + for (const input of recipe.inputs) { + await db('customCraftingRecipeItems') + .insert({ + id: customRecipe.id, + item: input.item.id, + quantity: input.quantity, + type: 'input' + }); + } + for (const req of recipe.requirements) { + await db('customCraftingRecipeItems') + .insert({ + id: customRecipe.id, + item: req.item.id, + quantity: req.quantity, + type: 'requirement' + }); + } + for (const output of recipe.outputs) { + await db('customCraftingRecipeItems') + .insert({ + id: customRecipe.id, + item: output.item.id, + quantity: output.quantity, + type: 'output' + }); + } + + buttonInteraction.reply({ + ephemeral: true, + content: 'Your recipe has been created 🎉' + }); + } + } + }); } } satisfies Command; \ No newline at end of file diff --git a/src/commands/survey.ts b/src/commands/survey.ts index fae14c0..47912e8 100644 --- a/src/commands/survey.ts +++ b/src/commands/survey.ts @@ -733,6 +733,7 @@ export default { }); bot.on(Events.InteractionCreate, interaction => { if (!interaction.isModalSubmit()) return; + if (!interaction.customId.startsWith('survey-')) return; if (!interaction.member) return; const member = interaction.member as GuildMember; diff --git a/src/lib/db.ts b/src/lib/db.ts index 110a2a1..d9bbca1 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -68,4 +68,14 @@ export interface CraftingStationCooldown { station: string, user: string, usedAt: number +} +export interface CustomCraftingRecipe { + id: number, + station: string +} +export interface CustomCraftingRecipeItem { + id: number, + item: number, + quantity: number, + type: 'input' | 'output' | 'requirement' } \ No newline at end of file From f2c1d0efa92e85291a15f310bcce98298be23d53 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 14:59:48 +0300 Subject: [PATCH 30/66] wtf did i do there --- customCraftingRecipes | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 customCraftingRecipes diff --git a/customCraftingRecipes b/customCraftingRecipes deleted file mode 100644 index e69de29..0000000 From 07e6e162ff90484449c77a887f258ae1d85d19e8 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 15:24:56 +0300 Subject: [PATCH 31/66] =?UTF-8?q?CUSTOM=20RECIPES=20=F0=9F=8E=89?= =?UTF-8?q?=F0=9F=8E=89=F0=9F=8E=89=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- migrations/20231117173052_craftingRecipes.js | 3 +- src/commands/craft.ts | 36 +++++++++----- src/commands/recipe.ts | 3 +- src/lib/db.ts | 1 + src/lib/rpg/recipes.ts | 50 ++++++++++++++++++-- 5 files changed, 75 insertions(+), 18 deletions(-) diff --git a/migrations/20231117173052_craftingRecipes.js b/migrations/20231117173052_craftingRecipes.js index 3c9bd11..66a37e7 100644 --- a/migrations/20231117173052_craftingRecipes.js +++ b/migrations/20231117173052_craftingRecipes.js @@ -6,6 +6,7 @@ exports.up = function(knex) { return knex.schema .createTable('customCraftingRecipes', (table) => { table.increments('id'); + table.string('guild'); table.string('station'); }) .createTable('customCraftingRecipeItems', (table) => { @@ -23,5 +24,5 @@ exports.up = function(knex) { exports.down = function(knex) { return knex.schema .dropTable('customCraftingRecipes') - .dropTable('customCraftingRecipes'); + .dropTable('customCraftingRecipeItems'); }; diff --git a/src/commands/craft.ts b/src/commands/craft.ts index a644819..5577302 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -1,8 +1,8 @@ import { AutocompleteInteraction, GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { CraftingStationCooldown, db } from '../lib/db'; +import { CraftingStationCooldown, CustomCraftingRecipe, db } from '../lib/db'; import { getStation, canUseStation, craftingStations, verb, CraftingStation } from '../lib/rpg/craftingStations'; import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; -import { getRecipe, defaultRecipes, formatRecipe } from '../lib/rpg/recipes'; +import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; export default { @@ -34,7 +34,7 @@ export default { await interaction.deferReply({ephemeral: true}); - const recipe = getRecipe(recipeID); + const recipe = await getRecipe(recipeID); if (!recipe) return interaction.followUp('Recipe does not exist!'); const station = getStation(recipe.station)!; @@ -118,15 +118,29 @@ export default { return interaction.respond(found); } else if (focused.name === 'recipe') { - const found = defaultRecipes - .filter(recipe => recipe.station === interaction.options.getString('station')) - .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0) - .map(recipe => ({ - name: formatRecipe(recipe, true), - value: recipe.id.toString() - })); + const station = interaction.options.getString('station'); - return interaction.respond(found); + const foundDefaultRecipes = defaultRecipes + .filter(recipe => recipe.station === station) + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + + const customRecipes = await db('customCraftingRecipes') + .where('station', station); + + const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); + + const foundCustomRecipes = resolvedCustomRecipes + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + + const recipes = [...foundDefaultRecipes, ...foundCustomRecipes]; + + return interaction.respond( + recipes + .map(recipe => ({ + name: formatRecipe(recipe, true), + value: recipe.id.toString() + })) + ); } } } satisfies Command; \ No newline at end of file diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index ef196e3..e6e5919 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -44,7 +44,7 @@ export default { if (!interaction.member) return; if (id.startsWith('recipe-create-')) { - //const guildID = id.split('-')[2]; + const guildID = id.split('-')[2]; if (interaction.isMessageComponent()) { const modal = new ModalBuilder() @@ -164,6 +164,7 @@ export default { const [customRecipe] = await db('customCraftingRecipes') .insert({ + guild: guildID, station: recipe.station }) .returning('id'); diff --git a/src/lib/db.ts b/src/lib/db.ts index d9bbca1..8e78d22 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -71,6 +71,7 @@ export interface CraftingStationCooldown { } export interface CustomCraftingRecipe { id: number, + guild: string, station: string } export interface CustomCraftingRecipeItem { diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 00548c8..9addbab 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -1,5 +1,6 @@ +import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../db'; import { getStation } from './craftingStations'; -import { DefaultItems, Items, formatItemsArray, getDefaultItem } from './items'; +import { DefaultItems, Items, formatItemsArray, getDefaultItem, getItem } from './items'; export interface DefaultRecipe { id: number, @@ -8,7 +9,15 @@ export interface DefaultRecipe { requirements: Items[], outputs: Items[] } -export type Recipe = DefaultRecipe +export interface ResolvedCustomRecipe { + id: number, + guild: string, + station: string, + inputs: Items[], + requirements: Items[], + outputs: Items[] +} +export type Recipe = DefaultRecipe | ResolvedCustomRecipe export const defaultRecipes: DefaultRecipe[] = [ { @@ -147,14 +156,45 @@ export const defaultRecipes: DefaultRecipe[] = [ export function getDefaultRecipe(id: number): DefaultRecipe | undefined { return defaultRecipes.find(recipe => recipe.id === id); } -export function getRecipe(id: number): Recipe | undefined { - return getDefaultRecipe(id); // currently just a stub +export async function getCustomRecipe(id: number): Promise { + const recipe = await db('customCraftingRecipes') + .where('id', id) + .first(); + + if (!recipe) return; + + return await resolveCustomRecipe(recipe); +} +export async function getRecipe(id: number): Promise { + if (id >= 0) { + return await getCustomRecipe(id); + } else { + return getDefaultRecipe(id); + } } const defaultFormatRecipe = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => `${formatItemsArray(inputs, disableBold)}${requirements.length === 0 ? '' : ` w/ ${formatItemsArray(requirements, disableBold)}`} => ${formatItemsArray(outputs, disableBold)}`; -export function formatRecipe(recipe: DefaultRecipe, disableBold = false) { +export function formatRecipe(recipe: Recipe, disableBold = false) { const station = getStation(recipe.station); return (station?.formatRecipe || defaultFormatRecipe)(recipe.inputs, recipe.requirements, recipe.outputs, disableBold); +} + +function resolveItems(items: CustomCraftingRecipeItem[]) { + return Promise.all(items.map(async i => ({item: (await getItem(i.item))!, quantity: i.quantity}))); +} + +export async function resolveCustomRecipe(recipe: CustomCraftingRecipe): Promise { + const items = await db('customCraftingRecipeItems') + .where('id', recipe.id); + + return { + id: recipe.id, + guild: recipe.guild, + station: recipe.station, + inputs: await resolveItems(items.filter(i => i.type === 'input')), + requirements: await resolveItems(items.filter(i => i.type === 'requirement')), + outputs: await resolveItems(items.filter(i => i.type === 'output')), + }; } \ No newline at end of file From fd3dbfa65b28db16fcb6d3f8830620eadcc86bc0 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 15:49:50 +0300 Subject: [PATCH 32/66] add more stuff to main page --- src/index.ts | 2 +- src/web.ts | 10 +++++++++- static/create-recipe/script.js | 4 ++-- static/index.html | 5 +++++ static/script.js | 29 +++++++++++++++++++++++++++++ static/style.css | 15 +++++++++++++-- 6 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 static/script.js diff --git a/src/index.ts b/src/index.ts index 3ce7cc5..a0d3592 100644 --- a/src/index.ts +++ b/src/index.ts @@ -29,7 +29,7 @@ async function init() { log.nonsense('booting chip...'); log.nonsense('starting up web interface...'); - await startServer(sitePort); + await startServer(bot, sitePort); log.nonsense('setting up connection...'); diff --git a/src/web.ts b/src/web.ts index b2c625a..32326fb 100644 --- a/src/web.ts +++ b/src/web.ts @@ -2,8 +2,9 @@ import express from 'express'; import * as log from './lib/log'; import { CustomItem, db } from './lib/db'; import { defaultItems } from './lib/rpg/items'; +import { Client } from 'discord.js'; -export async function startServer(port: number) { +export async function startServer(bot: Client, port: number) { const app = express(); app.use(express.static('static/')); @@ -24,5 +25,12 @@ export async function startServer(port: number) { res.json([...defaultItems, ...customItems]); }); + app.get('/api/status', async (_, res) => { + res.json({ + guilds: bot.guilds.cache.size, + uptime: bot.uptime + }); + }); + app.listen(port, () => log.info(`web interface listening on ${port}`)); } \ No newline at end of file diff --git a/static/create-recipe/script.js b/static/create-recipe/script.js index 3b498a7..f705817 100644 --- a/static/create-recipe/script.js +++ b/static/create-recipe/script.js @@ -128,10 +128,10 @@ Promise.all([ fetch(`/api/items${document.location.search}`) .then(res => res.json()), loaded -]).then(items => { +]).then(([items]) => { const itemsContainer = document.querySelector('.items'); itemsContainer.textContent = ''; - items[0].forEach(item => + items.forEach(item => itemsContainer.appendChild(renderItem(item)) ); document.querySelectorAll('.item-list').forEach(elem => { diff --git a/static/index.html b/static/index.html index d4b4966..5283dc7 100644 --- a/static/index.html +++ b/static/index.html @@ -12,6 +12,8 @@ + +
@@ -22,6 +24,9 @@ · repo
+
+ ··· +
\ No newline at end of file diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..83c38e8 --- /dev/null +++ b/static/script.js @@ -0,0 +1,29 @@ +let resolveLoaded; +const loaded = new Promise(resolve => resolveLoaded = resolve); + +function formatUptime(s) { + let units = [ + ['d', (s / (1000 * 60 * 60 * 24))], + ['h', (s / (1000 * 60 * 60)) % 24], + ['m', (s / (1000 * 60)) % 60], + ['s', (s / 1000) % 60] + ]; + + return units.filter(([_, t]) => t > 1).map(([sh, t]) => `${Math.ceil(t)}${sh}`).join(' '); +} + +Promise.all([ + fetch('/api/status') + .then(res => res.json()), + loaded +]).then(([status]) => { + document.querySelector('#status').innerHTML = ` +
online · ${status.guilds} guilds · up for ${formatUptime(status.uptime)} + `; + const firstRender = Date.now(); + setInterval(() => { + document.querySelector('#uptime').innerText = formatUptime(status.uptime + Date.now() - firstRender); + }, 1000); +}); + +document.addEventListener('DOMContentLoaded', () => resolveLoaded()); \ No newline at end of file diff --git a/static/style.css b/static/style.css index db33617..4ca85c8 100644 --- a/static/style.css +++ b/static/style.css @@ -62,11 +62,11 @@ a:hover { animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1); transition: transform 0.15s, opacity 0.1s; } -#main img:active { +#main > img:active { transform: scale(0.97); opacity: 0.9; } -#main :not(img) { +#main > :not(img) { animation: 0.8s fadein; } #main h1 { @@ -74,6 +74,17 @@ a:hover { margin-top: 0.5rem; margin-bottom: 1rem; } +#main #status { + color: var(--text-color-light); + font-size: 0.85rem; +} +#status .status { + width: 0.5rem; + height: 0.5rem; + background-color: #00a55e; + display: inline-block; + border-radius: 1rem; +} @keyframes fadein { 0% { opacity: 0; } From 7d3bf20eaa06e9e7696eea401b2cb2332c5bb72d Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 16:03:52 +0300 Subject: [PATCH 33/66] delete items --- src/commands/item.ts | 36 ++++++++++++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index 2bc0406..b19f412 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,8 +1,9 @@ import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { CustomItem, db } from '../lib/db'; -import { formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; +import { CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; +import { formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; import { behaviors } from '../lib/rpg/behaviors'; import { Command } from '../types/index'; +import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; //function extendOption(t: string) { // return {name: t, value: t}; @@ -170,6 +171,18 @@ export default { .setDescription('Amount of items to give') ) ) + .addSubcommand(cmd => + cmd + .setName('delete') + .setDescription('[ADMIN] Delete a custom item') + .addStringOption(opt => + opt + .setName('item') + .setDescription('The item') + .setAutocomplete(true) + .setRequired(true) + ) + ) .setDefaultMemberPermissions('0') .setDMPermission(false), @@ -209,6 +222,25 @@ export default { const inv = await giveItem(user.id, item, quantity); await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`); + } else if (subcommand === 'delete') { + const itemID = parseInt(interaction.options.getString('item', true)); + + const item = await getItem(itemID); + if (!item) return interaction.followUp('No such item exists!'); + + const usedIn = await db('customCraftingRecipeItems') + .where('item', item.id); + + if (usedIn.length > 0) { + const recipes = (await Promise.all(usedIn.map(i => getCustomRecipe(i.id)))).filter(r => r !== undefined); + return interaction.followUp(`⚠️ This item is used in the following recipes:\n${recipes.map(r => `- ${formatRecipe(r!)}`).join('\n')}`); + } + + await db('customItems') + .where('id', item.id) + .delete(); + + interaction.followUp(`${formatItem(item)} has been deleted.`); } } }, From 26903e03a8e8e423cc0b930f4b87a0e69e5b3191 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 16:09:09 +0300 Subject: [PATCH 34/66] delete recipes --- src/commands/recipe.ts | 53 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index e6e5919..a2d1b68 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -1,7 +1,7 @@ import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, Events, ModalBuilder, SlashCommandBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; import { Command } from '../types/index'; import { Items, getItem } from '../lib/rpg/items'; -import { formatRecipe } from '../lib/rpg/recipes'; +import { formatRecipe, getCustomRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { craftingStations, getStation } from '../lib/rpg/craftingStations'; import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../lib/db'; @@ -14,6 +14,18 @@ export default { .setName('create') .setDescription('[ADMIN] Create a custom recipe') ) + .addSubcommand(sub => + sub + .setName('delete') + .setDescription('[ADMIN] Delete a custom recipe') + .addStringOption(opt => + opt + .setName('recipe') + .setAutocomplete(true) + .setDescription('Which recipe to remove') + .setRequired(true) + ) + ) .setDMPermission(false) .setDefaultMemberPermissions(0), @@ -33,6 +45,21 @@ export default { content: `To create a recipe, go here: ${interaction.client.config.siteURL}/create-recipe/?guild=${interaction.guildId}\nOnce done, click the button below and paste the resulting string in.`, components: [row] }); + } else if (sub === 'delete') { + const recipeID = interaction.options.getString('recipe', true); + const recipe = await getCustomRecipe(parseInt(recipeID)); + + if (!recipe) return interaction.followUp('Recipe does no exist!'); + + await db('customCraftingRecipes') + .where('id', recipe.id) + .delete(); + + await db('customCraftingRecipeItems') + .where('id', recipe.id) + .delete(); + + await interaction.followUp(`Deleted recipe ${formatRecipe(recipe)}`); } }, @@ -204,5 +231,29 @@ export default { } } }); + }, + + autocomplete: async (interaction) => { + const focused = interaction.options.getFocused(true); + + if (focused.name === 'recipe') { + const station = interaction.options.getString('station'); + + const customRecipes = await db('customCraftingRecipes') + .where('station', station); + + const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); + + const foundCustomRecipes = resolvedCustomRecipes + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + + return interaction.respond( + foundCustomRecipes + .map(recipe => ({ + name: formatRecipe(recipe, true), + value: recipe.id.toString() + })) + ); + } } } satisfies Command; \ No newline at end of file From 37af0ea68fcfb3fd273acabf2def5621452bbc19 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 17:12:27 +0300 Subject: [PATCH 35/66] fixes i would've caught if i tested this for more than 2 seconds --- src/commands/item.ts | 8 +++++--- src/commands/recipe.ts | 35 ++++++++++++++++----------------- src/lib/rpg/items.ts | 44 ++++++++++++++++++++++++++---------------- 3 files changed, 49 insertions(+), 38 deletions(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index b19f412..1994962 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,6 +1,6 @@ import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; -import { formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; +import { customItemAutocomplete, formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; import { behaviors } from '../lib/rpg/behaviors'; import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; @@ -177,7 +177,7 @@ export default { .setDescription('[ADMIN] Delete a custom item') .addStringOption(opt => opt - .setName('item') + .setName('customitem') .setDescription('The item') .setAutocomplete(true) .setRequired(true) @@ -223,7 +223,7 @@ export default { await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`); } else if (subcommand === 'delete') { - const itemID = parseInt(interaction.options.getString('item', true)); + const itemID = parseInt(interaction.options.getString('customitem', true)); const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); @@ -250,6 +250,8 @@ export default { if (focused.name === 'item') { return itemAutocomplete(interaction); + } else if (focused.name === 'customitem') { + return customItemAutocomplete(interaction); } } } satisfies Command; \ No newline at end of file diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index a2d1b68..47de3fe 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -98,20 +98,22 @@ export default { parsed = await Promise.all( recipeString .split('|') - .map(items => - Promise.all( - items - .split(';') - .map(itemStack => - itemStack.split(',') - ) - .map(async ([itemID, quantity]) => ( - { - item: (await getItem(parseInt(itemID)))!, - quantity: parseInt(quantity) - } - )) - ) + .map(async items => + items === '' ? + [] : + await Promise.all( + items + .split(';') + .map(itemStack => + itemStack.split(',') + ) + .map(async ([itemID, quantity]) => ( + { + item: (await getItem(parseInt(itemID)))!, + quantity: parseInt(quantity) + } + )) + ) ) ) as Items[][]; } catch (err) { @@ -237,10 +239,7 @@ export default { const focused = interaction.options.getFocused(true); if (focused.name === 'recipe') { - const station = interaction.options.getString('station'); - - const customRecipes = await db('customCraftingRecipes') - .where('station', station); + const customRecipes = await db('customCraftingRecipes'); const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 4edcab0..3aca51b 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -251,21 +251,31 @@ export function formatItemsArray(items: Items[], disableBold = false) { return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } -export async function itemAutocomplete(interaction: AutocompleteInteraction) { - const focused = interaction.options.getFocused(); +function createItemAutocomplete(onlyCustom: boolean) { + return async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); + + const customItems = await db('customItems') + .select('emoji', 'name', 'id') + // @ts-expect-error this LITERALLY works + .whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`) + .where('guild', interaction.guildId!) + .limit(25); + + const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); + + let items; + if (onlyCustom) { + items = customItems; + } else { + items = [...foundDefaultItems, ...customItems]; + } + + await interaction.respond( + items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) + ); + }; +} - const customItems = await db('customItems') - .select('emoji', 'name', 'id') - // @ts-expect-error this LITERALLY works - .whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`) - .where('guild', interaction.guildId!) - .limit(25); - - const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); - - const items = [...foundDefaultItems, ...customItems]; - - await interaction.respond( - items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) - ); -} \ No newline at end of file +export const itemAutocomplete = createItemAutocomplete(false); +export const customItemAutocomplete = createItemAutocomplete(true); \ No newline at end of file From baa32191b41bf3435fa61443bfdca284a74b12a6 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 17:53:09 +0300 Subject: [PATCH 36/66] extra content --- src/lib/rpg/craftingStations.ts | 77 ++++++++++++++-------- src/lib/rpg/items.ts | 110 ++++++++++++++++++++++++++++++++ src/lib/rpg/recipes.ts | 107 ++++++++++++++++++++++++++++++- 3 files changed, 266 insertions(+), 28 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index 63b7056..ddddafc 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -20,6 +20,34 @@ export function getStation(key: string) { export const defaultVerb = 'Crafted'; +const rollBunch = (outputs: Items[]) => { + const totalItems = outputs.reduce((a, b) => a + b.quantity, 0); + // grab from 1/3 to the entire pool, ensure it never goes below 1 + const rolledItems = Math.max(Math.round(totalItems/3 + Math.random() * totalItems*2/3), 1); + const res: Items[] = []; + for (let i = 0; i < rolledItems; i++) { + const rolled = pickRandom(outputs); + const r = res.find(r => r.item.id === rolled.item.id); + if (r) { + if (r.quantity === rolled.quantity) { + // don't roll more than can be rolled + i--; + } else { + r.quantity = r.quantity + 1; + } + } else { + res.push({ item: rolled.item, quantity: 1 }); + } + } + return res; +}; + +const formatMaybeCountable = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => + `${formatItemsArray(inputs, disableBold)} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)}: ` : ''}${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`; + +const formatMaybe = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => + `${formatItemsArray(inputs, disableBold)} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)} ` : ''}=> ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`; + export const craftingStations: CraftingStation[] = [ { key: 'forage', @@ -28,29 +56,8 @@ export const craftingStations: CraftingStation[] = [ description: 'Pick up various sticks and stones from the forest', emoji: '🌲', cooldown: 60 * 5, - formatRecipe: (_inputs, requirements, outputs, disableBold = false) => - `${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)}: ` : ''}${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`, - manipulateResults: (outputs) => { - const totalItems = outputs.reduce((a, b) => a + b.quantity, 0); - // grab from 1/3 to the entire pool, ensure it never goes below 1 - const rolledItems = Math.max(Math.round(totalItems/3 + Math.random() * totalItems*2/3), 1); - const res: Items[] = []; - for (let i = 0; i < rolledItems; i++) { - const rolled = pickRandom(outputs); - const r = res.find(r => r.item.id === rolled.item.id); - if (r) { - if (r.quantity === rolled.quantity) { - // don't roll more than can be rolled - i--; - } else { - r.quantity = r.quantity + 1; - } - } else { - res.push({ item: rolled.item, quantity: 1 }); - } - } - return res; - } + formatRecipe: formatMaybeCountable, + manipulateResults: rollBunch }, { key: 'hand', @@ -72,9 +79,9 @@ export const craftingStations: CraftingStation[] = [ verb: 'Fished up', description: 'fish gaming wednesday', emoji: '🎣', + cooldown: 60 * 60 * 2, requires: getDefaultItem(DefaultItems.FISHING_ROD), - formatRecipe: (inputs, requirements, outputs, disableBold = false) => - `${formatItemsArray(inputs, disableBold)} => ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`, + formatRecipe: formatMaybe, // weighted random manipulateResults: (outputs) => { const pool: Item[] = []; @@ -86,6 +93,26 @@ export const craftingStations: CraftingStation[] = [ return [{ item: pickRandom(pool), quantity: 1 }]; } }, + { + key: 'mining', + name: 'Mining', + verb: 'Mined', + description: 'mine diamonds', + emoji: '⛏️', + cooldown: 60 * 30, + requires: getDefaultItem(DefaultItems.MINESHAFT), + formatRecipe: formatMaybeCountable, + manipulateResults: rollBunch, + }, + { + key: 'smelting', + name: 'Smelting', + verb: 'Smelt', + description: 'Smelt ores, minerals, food, whatever you please', + emoji: '🔥', + cooldown: 30, + requires: getDefaultItem(DefaultItems.FURNACE), + }, ]; export async function canUseStation(user: string, station: CraftingStation) { diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 3aca51b..edf4d64 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -26,6 +26,17 @@ export enum DefaultItems { EXOTIC_FISH = 14, SHOVEL = 15, DIRT = 16, + MINESHAFT = 17, + PICKAXE = 18, + IRON_PICKAXE = 19, + COAL = 20, + IRON_ORE = 21, + IRON_INGOT = 22, + DIAMOND = 23, + RUBY = 24, + EMERALD = 25, + FURNACE = 26, + FRIED_FISH = 27, } export const defaultItems: DefaultItem[] = [ @@ -172,6 +183,105 @@ export const defaultItems: DefaultItem[] = [ maxStack: 64, untradable: false, }, + { + id: -17, + name: 'Mineshaft', + description: 'A place for you to mine ores and minerals!', + emoji: '⛏️', + type: 'plain', + maxStack: 1, + untradable: true + }, + { + id: -18, + name: 'Pickaxe', + description: 'Basic mining equipment', + emoji: '⛏️', + type: 'plain', + maxStack: 8, + untradable: false + }, + { + id: -19, + name: 'Iron Pickaxe', + description: 'More durable and strong mining equipment', + emoji: '⚒️', + type: 'plain', + maxStack: 8, + untradable: false + }, + { + id: -20, + name: 'Coal', + description: 'Fuel, NOT EDIBLE', + emoji: '◾️', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -21, + name: 'Iron Ore', + description: 'Unsmelted iron', + emoji: '◽️', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -22, + name: 'Iron Ingot', + description: 'A sturdy material', + emoji: '◻️', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -23, + name: 'Diamond', + description: 'Shiny rock!', + emoji: '💎', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -24, + name: 'Ruby', + description: 'Reference to the progarmiing......g.', + emoji: '🔻', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -25, + name: 'Emerald', + description: 'A currency in some other world', + emoji: '🟩', + type: 'plain', + maxStack: 128, + untradable: false + }, + { + id: -26, + name: 'Furnace', + description: 'A smeltery for your own needs', + emoji: '🔥', + type: 'plain', + maxStack: 1, + untradable: false + }, + { + id: -27, + name: 'Fried Fish', + description: 'A very nice and filling meal', + emoji: '🍱', + type: 'consumable', + maxStack: 16, + untradable: false + }, ]; diff --git a/src/lib/rpg/recipes.ts b/src/lib/rpg/recipes.ts index 9addbab..9dba031 100644 --- a/src/lib/rpg/recipes.ts +++ b/src/lib/rpg/recipes.ts @@ -92,7 +92,7 @@ export const defaultRecipes: DefaultRecipe[] = [ }, { id: -7, - station: 'hand', + station: 'forage', inputs: [], requirements: [ { item: getDefaultItem(DefaultItems.AXE), quantity: 1 }, @@ -134,9 +134,9 @@ export const defaultRecipes: DefaultRecipe[] = [ { item: getDefaultItem(DefaultItems.SHOVEL), quantity: 1 }, ], outputs: [ - { item: getDefaultItem(DefaultItems.BAIT), quantity: 3 }, + { item: getDefaultItem(DefaultItems.BAIT), quantity: 4 }, { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 1 }, - { item: getDefaultItem(DefaultItems.DIRT), quantity: 1 }, + { item: getDefaultItem(DefaultItems.DIRT), quantity: 3 }, ], }, { @@ -151,6 +151,107 @@ export const defaultRecipes: DefaultRecipe[] = [ { item: getDefaultItem(DefaultItems.SHOVEL), quantity: 1 }, ] }, + { + id: -12, + station: 'hand', + inputs: [ + { item: getDefaultItem(DefaultItems.DIRT), quantity: 4 }, + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.MINESHAFT), quantity: 1 }, + ] + }, + { + id: -13, + station: 'mining', + inputs: [ + { item: getDefaultItem(DefaultItems.PICKAXE), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 10 }, + { item: getDefaultItem(DefaultItems.COAL), quantity: 5 }, + { item: getDefaultItem(DefaultItems.IRON_ORE), quantity: 5 }, + ] + }, + { + id: -14, + station: 'mining', + inputs: [ + { item: getDefaultItem(DefaultItems.IRON_PICKAXE), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 10 }, + { item: getDefaultItem(DefaultItems.COAL), quantity: 5 }, + { item: getDefaultItem(DefaultItems.IRON_ORE), quantity: 5 }, + { item: getDefaultItem(DefaultItems.DIAMOND), quantity: 1 }, + { item: getDefaultItem(DefaultItems.EMERALD), quantity: 1 }, + { item: getDefaultItem(DefaultItems.RUBY), quantity: 1 }, + ] + }, + { + id: -15, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 3 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.PICKAXE), quantity: 1 }, + ] + }, + { + id: -16, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.IRON_INGOT), quantity: 4 }, + { item: getDefaultItem(DefaultItems.TWIG), quantity: 3 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.IRON_PICKAXE), quantity: 1 }, + ] + }, + { + id: -17, + station: 'smelting', + inputs: [ + { item: getDefaultItem(DefaultItems.IRON_ORE), quantity: 2 }, + { item: getDefaultItem(DefaultItems.COAL), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.IRON_INGOT), quantity: 1 }, + ] + }, + { + id: -18, + station: 'smelting', + inputs: [ + { item: getDefaultItem(DefaultItems.CARP), quantity: 1 }, + { item: getDefaultItem(DefaultItems.COAL), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.FRIED_FISH), quantity: 1 }, + ] + }, + { + id: -19, + station: 'workbench', + inputs: [ + { item: getDefaultItem(DefaultItems.PEBBLE), quantity: 4 }, + { item: getDefaultItem(DefaultItems.COAL), quantity: 1 }, + ], + requirements: [], + outputs: [ + { item: getDefaultItem(DefaultItems.FURNACE), quantity: 1 }, + ] + } ]; export function getDefaultRecipe(id: number): DefaultRecipe | undefined { From 6dc87caa106922bf548b2a37a6e32075add3e534 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 17:54:12 +0300 Subject: [PATCH 37/66] fix recipe formatting --- src/lib/rpg/craftingStations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/rpg/craftingStations.ts b/src/lib/rpg/craftingStations.ts index ddddafc..e95348d 100644 --- a/src/lib/rpg/craftingStations.ts +++ b/src/lib/rpg/craftingStations.ts @@ -43,10 +43,10 @@ const rollBunch = (outputs: Items[]) => { }; const formatMaybeCountable = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => - `${formatItemsArray(inputs, disableBold)} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)}: ` : ''}${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`; + `${inputs.length > 0 ? formatItemsArray(inputs, disableBold) : ''} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)}: ` : ''}${outputs.map(i => formatItems(i.item, i.quantity, disableBold) + '?').join(' ')}`; const formatMaybe = (inputs: Items[], requirements: Items[], outputs: Items[], disableBold = false) => - `${formatItemsArray(inputs, disableBold)} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)} ` : ''}=> ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`; + `${inputs.length > 0 ? formatItemsArray(inputs, disableBold) : ''} ${requirements.length > 0 ? `w/ ${formatItemsArray(requirements, disableBold)} ` : ''}=> ${outputs.map(i => formatItem(i.item, disableBold) + '?').join(' ')}`; export const craftingStations: CraftingStation[] = [ { From 60a3823b4718c92a7798e99201c0603eea4a96d8 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 18 Nov 2023 17:54:36 +0300 Subject: [PATCH 38/66] trim recipe string on input --- src/commands/recipe.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 47de3fe..86b8dfe 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -89,7 +89,7 @@ export default { interaction.showModal(modal); } else if (interaction.isModalSubmit()) { const field = interaction.fields.getField('recipe-create-textbox', ComponentType.TextInput); - const recipeString = field.value; + const recipeString = field.value.trim(); await interaction.deferReply({ ephemeral: true }); From 249cf024909b0b2f45e8b707c311fb8380a12ea6 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sun, 19 Nov 2023 20:00:23 +0300 Subject: [PATCH 39/66] remove stack if quantity = 0 --- src/lib/rpg/items.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index edf4d64..d2efe1a 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -316,7 +316,7 @@ export async function getItemQuantity(user: string, itemID: number): Promise { const storedItem = await db('itemInventories') .where('user', user) .where('item', item.id) @@ -324,6 +324,18 @@ export async function giveItem(user: string, item: Item, quantity = 1) { let inv; if (storedItem) { + if (storedItem.quantity + quantity === 0) { + await db('itemInventories') + .delete() + .where('user', user) + .where('item', item.id); + return { + user: user, + item: item.id, + quantity: 0 + }; + } + inv = await db('itemInventories') .update({ quantity: db.raw('MIN(quantity + ?, ?)', [quantity, getMaxStack(item)]) From 0cd2b02282c64ec186ade5d5edbada11ddbc0c32 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sun, 19 Nov 2023 20:01:00 +0300 Subject: [PATCH 40/66] don't show empty stacks in inventory --- src/commands/inventory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index 3ef8351..b8cf6b4 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -21,7 +21,7 @@ export default { .where('user', member.user.id); // kind of stupid kind of awful - const items = (await Promise.all(itemsList.map(async i => ({item: await getItem(i.item), quantity: i.quantity})))).filter(i => i.item); + const items = (await Promise.all(itemsList.map(async i => ({item: await getItem(i.item), quantity: i.quantity})))).filter(i => i.item && i.quantity !== 0); await interaction.followUp( `Your inventory:\n${items.length === 0 ? '_Your inventory is empty!_' : items.map(i => `- ${formatItems(i.item!, i.quantity)}\n_${i.item!.description}_`).join('\n')}` From c5d9954fcfd16350c4ef4d5e8a5716cd318ec1f1 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sun, 19 Nov 2023 23:28:31 +0300 Subject: [PATCH 41/66] oauth2 because i am fucking insane --- config.example.json | 4 +- migrations/20231119183807_sessions.js | 23 + package.json | 8 +- pnpm-lock.yaml | 1941 ++----------------------- src/index.ts | 4 +- src/lib/db.ts | 7 + src/types/index.d.ts | 4 +- src/web.ts | 141 +- 8 files changed, 290 insertions(+), 1842 deletions(-) create mode 100644 migrations/20231119183807_sessions.js diff --git a/config.example.json b/config.example.json index f7d4828..c2414c7 100644 --- a/config.example.json +++ b/config.example.json @@ -1,5 +1,7 @@ { "token": "token", "sitePort": 15385, - "siteURL": "https://localhost:15385" + "siteURL": "http://localhost:15385", + "clientId": "", + "clientSecret": "" } \ No newline at end of file diff --git a/migrations/20231119183807_sessions.js b/migrations/20231119183807_sessions.js new file mode 100644 index 0000000..584f1c6 --- /dev/null +++ b/migrations/20231119183807_sessions.js @@ -0,0 +1,23 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('sessions', table => { + table.string('id').notNullable(); + table.string('tokenType').notNullable(); + table.string('accessToken').notNullable(); + table.string('refreshToken').notNullable(); + table.timestamp('expiresAt').notNullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('sessions'); +}; diff --git a/package.json b/package.json index feb5762..28a612e 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,8 @@ "author": "oatmealine", "license": "AGPL-3.0", "dependencies": { + "@discordjs/core": "^1.1.1", + "@discordjs/rest": "^2.2.0", "chalk": "^4.1.2", "d3-array": "^2.12.1", "discord.js": "^14.14.1", @@ -22,12 +24,16 @@ "parse-color": "^1.0.0", "pretty-bytes": "^5.6.0", "random-seed": "^0.3.0", - "sqlite3": "^5.1.6" + "sqlite3": "^5.1.6", + "tough-cookie": "^4.1.3", + "uid-safe": "^2.1.5" }, "devDependencies": { "@types/d3-array": "^3.2.1", "@types/express": "^4.17.21", "@types/parse-color": "^1.0.3", + "@types/tough-cookie": "^4.0.5", + "@types/uid-safe": "^2.1.5", "@typescript-eslint/eslint-plugin": "^6.11.0", "@typescript-eslint/parser": "^6.11.0", "discord-api-types": "^0.37.63", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 35c2dd5..665d2ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5,6 +5,12 @@ settings: excludeLinksFromLockfile: false dependencies: + '@discordjs/core': + specifier: ^1.1.1 + version: 1.1.1 + '@discordjs/rest': + specifier: ^2.2.0 + version: 2.2.0 chalk: specifier: ^4.1.2 version: 4.1.2 @@ -17,9 +23,6 @@ dependencies: express: specifier: ^4.18.2 version: 4.18.2 - express-svelte: - specifier: ^1.0.8 - version: 1.0.8(express@4.18.2)(svelte@3.59.2) got: specifier: ^11.8.6 version: 11.8.6 @@ -41,6 +44,12 @@ dependencies: sqlite3: specifier: ^5.1.6 version: 5.1.6 + tough-cookie: + specifier: ^4.1.3 + version: 4.1.3 + uid-safe: + specifier: ^2.1.5 + version: 2.1.5 devDependencies: '@types/d3-array': @@ -52,6 +61,12 @@ devDependencies: '@types/parse-color': specifier: ^1.0.3 version: 1.0.3 + '@types/tough-cookie': + specifier: ^4.0.5 + version: 4.0.5 + '@types/uid-safe': + specifier: ^2.1.5 + version: 2.1.5 '@typescript-eslint/eslint-plugin': specifier: ^6.11.0 version: 6.11.0(@typescript-eslint/parser@6.11.0)(eslint@8.53.0)(typescript@5.2.2) @@ -75,1196 +90,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /@ampproject/remapping@2.2.1: - resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 - dev: false - - /@babel/code-frame@7.22.13: - resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/highlight': 7.22.20 - chalk: 2.4.2 - dev: false - - /@babel/compat-data@7.23.3: - resolution: {integrity: sha512-BmR4bWbDIoFJmJ9z2cZ8Gmm2MXgEDgjdWgpKmKWUt54UGFJdlj31ECtbaDvCG/qVdG3AQ1SfpZEs01lUFbzLOQ==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/core@7.23.3: - resolution: {integrity: sha512-Jg+msLuNuCJDyBvFv5+OKOUjWMZgd85bKjbICd3zWrKAo+bJ49HJufi7CQE0q0uR8NGyO6xkCACScNqyjHSZew==} - engines: {node: '>=6.9.0'} - dependencies: - '@ampproject/remapping': 2.2.1 - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.23.3 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) - '@babel/helpers': 7.23.2 - '@babel/parser': 7.23.3 - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.3 - '@babel/types': 7.23.3 - convert-source-map: 2.0.0 - debug: 4.3.4 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/generator@7.23.3: - resolution: {integrity: sha512-keeZWAV4LU3tW0qRi19HRpabC/ilM0HRBBzf9/k8FFiG4KVpiv0FIy4hHfLfFQZNhziCTPTmd59zoyv6DNISzg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 - jsesc: 2.5.2 - dev: false - - /@babel/helper-annotate-as-pure@7.22.5: - resolution: {integrity: sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-builder-binary-assignment-operator-visitor@7.22.15: - resolution: {integrity: sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-compilation-targets@7.22.15: - resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/compat-data': 7.23.3 - '@babel/helper-validator-option': 7.22.15 - browserslist: 4.22.1 - lru-cache: 5.1.1 - semver: 6.3.1 - dev: false - - /@babel/helper-create-class-features-plugin@7.22.15(@babel/core@7.23.3): - resolution: {integrity: sha512-jKkwA59IXcvSaiK2UN45kKwSC9o+KuoXsBDvHvU/7BecYIp8GQ2UwrVvFgJASUT+hBnwJx6MhvMCuMzwZZ7jlg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-member-expression-to-functions': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - semver: 6.3.1 - dev: false - - /@babel/helper-create-regexp-features-plugin@7.22.15(@babel/core@7.23.3): - resolution: {integrity: sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-annotate-as-pure': 7.22.5 - regexpu-core: 5.3.2 - semver: 6.3.1 - dev: false - - /@babel/helper-define-polyfill-provider@0.4.3(@babel/core@7.23.3): - resolution: {integrity: sha512-WBrLmuPP47n7PNwsZ57pqam6G/RGo1vw/87b0Blc53tZNGZ4x7YvZ6HgQe2vo1W/FR20OgjeZuGXzudPiXHFug==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4 - lodash.debounce: 4.0.8 - resolve: 1.22.8 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/helper-environment-visitor@7.22.20: - resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-function-name@7.23.0: - resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.15 - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-hoist-variables@7.22.5: - resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-member-expression-to-functions@7.23.0: - resolution: {integrity: sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-module-imports@7.22.15: - resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-module-transforms@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-simple-access': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/helper-validator-identifier': 7.22.20 - dev: false - - /@babel/helper-optimise-call-expression@7.22.5: - resolution: {integrity: sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-plugin-utils@7.22.5: - resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-remap-async-to-generator@7.22.20(@babel/core@7.23.3): - resolution: {integrity: sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-wrap-function': 7.22.20 - dev: false - - /@babel/helper-replace-supers@7.22.20(@babel/core@7.23.3): - resolution: {integrity: sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-member-expression-to-functions': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - dev: false - - /@babel/helper-simple-access@7.22.5: - resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-skip-transparent-expression-wrappers@7.22.5: - resolution: {integrity: sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-split-export-declaration@7.22.6: - resolution: {integrity: sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/helper-string-parser@7.22.5: - resolution: {integrity: sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-identifier@7.22.20: - resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-validator-option@7.22.15: - resolution: {integrity: sha512-bMn7RmyFjY/mdECUbgn9eoSY4vqvacUnS9i9vGAGttgFWesO6B4CYWA7XlpbWgBt71iv/hfbPlynohStqnu5hA==} - engines: {node: '>=6.9.0'} - dev: false - - /@babel/helper-wrap-function@7.22.20: - resolution: {integrity: sha512-pms/UwkOpnQe/PDAEdV/d7dVCoBbB+R4FvYoHGZz+4VPcg7RtYy2KP7S2lbuWM6FCSgob5wshfGESbC/hzNXZw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-function-name': 7.23.0 - '@babel/template': 7.22.15 - '@babel/types': 7.23.3 - dev: false - - /@babel/helpers@7.23.2: - resolution: {integrity: sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/template': 7.22.15 - '@babel/traverse': 7.23.3 - '@babel/types': 7.23.3 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/highlight@7.22.20: - resolution: {integrity: sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-validator-identifier': 7.22.20 - chalk: 2.4.2 - js-tokens: 4.0.0 - dev: false - - /@babel/parser@7.23.3: - resolution: {integrity: sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==} - engines: {node: '>=6.0.0'} - hasBin: true - dependencies: - '@babel/types': 7.23.3 - dev: false - - /@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-iRkKcCqb7iGnq9+3G6rZ+Ciz5VywC4XNRHe57lKM+jOeYAoR0lVqdeeDRfh0tQcTfw/+vBhHn926FmQhLtlFLQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-WwlxbfMNdVEpQjZmK5mhm7oSwD3dS6eU+Iwsi4Knl9wAletWem7kaRsGOG+8UEbRyqxY4SS5zvtfXwX+jMxUwQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.13.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-transform-optional-chaining': 7.23.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-XaJak1qcityzrX0/IU5nKHb34VaibwP3saKqG6a/tppelgllOH13LUann4ZCIBcVOeE6H18K4Vx9QKkVww3z/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3): - resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - dev: false - - /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.3): - resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.3): - resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.23.3): - resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-export-namespace-from@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-import-assertions@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-lPgDSU+SJLK3xmFDTV2ZRQAiM7UuUjGidwBywFavObCiZc1BeAAcMtHJKUya92hPHO+at63JJPLygilZard8jw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-import-attributes@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-pawnE0P9g10xgoP7yKr6CK63K2FMsTE+FZidZO/1PwRdzmAPVs+HS1mAURUsgaoxammTJvULUdIkEK0gOcU2tA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.3): - resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.3): - resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.3): - resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.3): - resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.23.3): - resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.3): - resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.23.3): - resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-arrow-functions@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-NzQcQrzaQPkaEwoTm4Mhyl8jI1huEL/WWIEvudjTCMJ9aBZNpsJbMASx7EQECtQQPS/DcnFpo0FIh3LvEO9cxQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-async-generator-functions@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-59GsVNavGxAXCDDbakWSMJhajASb4kBCqDjqJsv+p5nKdbz7istmZ3HrX3L2LuiI80+zsOADCvooqQH3qGCucQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-async-to-generator@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-A7LFsKi4U4fomjqXJlZg/u0ft/n8/7n7lpffUP/ZULx/DtV9SGlNKZolHH6PE8Xl1ngCc0M11OaeZptXVkfKSw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-remap-async-to-generator': 7.22.20(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-block-scoped-functions@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-vI+0sIaPIO6CNuM9Kk5VmXcMVRiOpDh7w2zZt9GXzmE/9KD70CUEVhvPR/etAeNK/FAEkhxQtXOzVF3EuRL41A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-block-scoping@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-QPZxHrThbQia7UdvfpaRRlq/J9ciz1J4go0k+lPBXbgaNeY7IQrBj/9ceWjvMMI07/ZBzHl/F0R/2K0qH7jCVw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-class-properties@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-uM+AN8yCIjDPccsKGlw271xjJtGii+xQIF/uMPS8H15L12jZTsLfF4o5vNO7d/oUguOyfdikHGc/yi9ge4SGIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-class-static-block@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-PENDVxdr7ZxKPyi5Ffc0LjXdnJyrJxyqF5T5YjlVg4a0VFfQHW0r8iAtRiDXkfHlu1wwcvdtnndGYIeJLSuRMQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.12.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-classes@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-FGEQmugvAEu2QtgtU0uTASXevfLMFfBeVCIIdcQhn/uBQsMTjBajdnAtanQlOcuihWh10PZ7+HWvc7NtBwP74w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-optimise-call-expression': 7.22.5 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) - '@babel/helper-split-export-declaration': 7.22.6 - globals: 11.12.0 - dev: false - - /@babel/plugin-transform-computed-properties@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-dTj83UVTLw/+nbiHqQSFdwO9CbTtwq1DsDqm3CUEtDrZNET5rT5E6bIdTlOftDTDLMYxvxHNEYO4B9SLl8SLZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/template': 7.22.15 - dev: false - - /@babel/plugin-transform-destructuring@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-n225npDqjDIr967cMScVKHXJs7rout1q+tt50inyBCPkyZ8KxeI6d+GIbSBTT/w/9WdlWDOej3V9HE5Lgk57gw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-dotall-regex@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-vgnFYDHAKzFaTVp+mneDsIEbnJ2Np/9ng9iviHw3P/KVcgONxpNULEW/51Z/BaFojG2GI2GwwXck5uV1+1NOYQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-duplicate-keys@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-RrqQ+BQmU3Oyav3J+7/myfvRCq7Tbz+kKLLshUmMwNlDHExbGL7ARhajvoBJEvc+fCguPPu887N+3RRXBVKZUA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-dynamic-import@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-vTG+cTGxPFou12Rj7ll+eD5yWeNl5/8xvQvF08y5Gv3v4mZQoyFf8/n9zg4q5vvCWt5jmgymfzMAldO7orBn7A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-exponentiation-operator@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-5fhCsl1odX96u7ILKHBj4/Y8vipoqwsJMh4csSA8qFfxrZDEA4Ssku2DyNvMJSmZNOEBT750LfFPbtrnTP90BQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-builder-binary-assignment-operator-visitor': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-export-namespace-from@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-yCLhW34wpJWRdTxxWtFZASJisihrfyMOTOQexhVzA78jlU+dH7Dw+zQgcPepQ5F3C6bAIiblZZ+qBggJdHiBAg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-for-of@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-X8jSm8X1CMwxmK878qsUGJRmbysKNbdpTv/O1/v0LuY/ZkZrng5WYiekYSdg9m09OTmDDUWeEDsTE+17WYbAZw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-function-name@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-I1QXp1LxIvt8yLaib49dRW5Okt7Q4oaxao6tFVKS/anCdEOMtYwWVKoiOA1p34GOWIZjUK0E+zCp7+l1pfQyiw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-json-strings@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-H9Ej2OiISIZowZHaBwF0tsJOih1PftXJtE8EWqlEIwpc7LMTGq0rPOrywKLQ4nefzx8/HMR0D3JGXoMHYvhi0A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-literals@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-wZ0PIXRxnwZvl9AYpqNUxpZ5BiTGrYt7kueGQ+N5FiQ7RCOD4cm8iShd6S6ggfVIWaJf2EMk8eRzAh52RfP4rQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-logical-assignment-operators@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-+pD5ZbxofyOygEp+zZAfujY2ShNCXRpDRIPOiBmTO693hhyOEteZgl876Xs9SAHPQpcV0vz8LvA/T+w8AzyX8A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-member-expression-literals@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-sC3LdDBDi5x96LA+Ytekz2ZPk8i/Ck+DEuDbRAll5rknJ5XRTSaPKEYwomLcs1AA8wg9b3KjIQRsnApj+q51Ag==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-modules-amd@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-vJYQGxeKM4t8hYCKVBlZX/gtIY2I7mRGFNcm85sgXGMTBcoV3QdVtdpbcWEbzbfUIUZKwvgFT82mRvaQIebZzw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-modules-commonjs@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-aVS0F65LKsdNOtcz6FRCpE4OgsP2OFnW46qNxNIX9h3wuzaNcSQsJysuMwqSibC98HPrf2vCgtxKNwS0DAlgcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-simple-access': 7.22.5 - dev: false - - /@babel/plugin-transform-modules-systemjs@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-ZxyKGTkF9xT9YJuKQRo19ewf3pXpopuYQd8cDXqNzc3mUNbOME0RKMoZxviQk74hwzfQsEe66dE92MaZbdHKNQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-identifier': 7.22.20 - dev: false - - /@babel/plugin-transform-modules-umd@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-zHsy9iXX2nIsCBFPud3jKn1IRPWg3Ing1qOZgeKV39m1ZgIdpJqvlWVeiHBZC6ITRG0MfskhYe9cLgntfSFPIg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-named-capturing-groups-regex@7.22.5(@babel/core@7.23.3): - resolution: {integrity: sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-new-target@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-YJ3xKqtJMAT5/TIZnpAR3I+K+WaDowYbN3xyxI8zxx/Gsypwf9B9h0VB+1Nh6ACAAPRS5NSRje0uVv5i79HYGQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-nullish-coalescing-operator@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-xzg24Lnld4DYIdysyf07zJ1P+iIfJpxtVFOzX4g+bsJ3Ng5Le7rXx9KwqKzuyaUeRnt+I1EICwQITqc0E2PmpA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-numeric-separator@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-s9GO7fIBi/BLsZ0v3Rftr6Oe4t0ctJ8h4CCXfPoEJwmvAPMyNrfkOOJzm6b9PX9YXcCJWWQd/sBF/N26eBiMVw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-object-rest-spread@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-VxHt0ANkDmu8TANdE9Kc0rndo/ccsmfe2Cx2y5sI4hu3AukHQ5wAu4cM7j3ba8B9548ijVyclBU+nuDQftZsog==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.23.3 - '@babel/core': 7.23.3 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-object-super@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-BwQ8q0x2JG+3lxCVFohg+KbQM7plfpBwThdW9A6TMtWwLsbDA01Ek2Zb/AgDN39BiZsExm4qrXxjk+P1/fzGrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-replace-supers': 7.22.20(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-optional-catch-binding@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-LxYSb0iLjUamfm7f1D7GpiS4j0UAC8AOiehnsGAP8BEsIX8EOi3qV6bbctw8M7ZvLtcoZfZX5Z7rN9PlWk0m5A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-optional-chaining@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-zvL8vIfIUgMccIAK1lxjvNv572JHFJIKb4MWBz5OGdBQA0fB0Xluix5rmOby48exiJc987neOmP/m9Fnpkz3Tg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-parameters@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-private-methods@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-UzqRcRtWsDMTLrRWFvUBDwmw06tCQH9Rl1uAjfh6ijMSmGYQ+fpdB+cnqRC8EMh5tuuxSv0/TejGL+7vyj+50g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-private-property-in-object@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-a5m2oLNFyje2e/rGKjVfAELTVI5mbA0FeZpBnkOWWV7eSmKQ+T/XW0Vf+29ScLzSxX+rnsarvU0oie/4m6hkxA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-annotate-as-pure': 7.22.5 - '@babel/helper-create-class-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3) - dev: false - - /@babel/plugin-transform-property-literals@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-jR3Jn3y7cZp4oEWPFAlRsSWjxKe4PZILGBSd4nis1TsC5qeSpb+nrtihJuDhNI7QHiVbUaiXa0X2RZY3/TI6Nw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-regenerator@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-KP+75h0KghBMcVpuKisx3XTu9Ncut8Q8TuvGO4IhY+9D5DFEckQefOuIsB/gQ2tG71lCke4NMrtIPS8pOj18BQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - regenerator-transform: 0.15.2 - dev: false - - /@babel/plugin-transform-reserved-words@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-QnNTazY54YqgGxwIexMZva9gqbPa15t/x9VS+0fsEFWplwVpXYZivtgl43Z1vMpc1bdPP2PP8siFeVcnFvA3Cg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-runtime@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-XcQ3X58CKBdBnnZpPaQjgVMePsXtSZzHoku70q9tUAQp02ggPQNM04BF3RvlW1GSM/McbSOQAzEK4MXbS7/JFg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-imports': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.23.3) - babel-plugin-polyfill-corejs3: 0.8.6(@babel/core@7.23.3) - babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.23.3) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/plugin-transform-shorthand-properties@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-ED2fgqZLmexWiN+YNFX26fx4gh5qHDhn1O2gvEhreLW2iI63Sqm4llRLCXALKrCnbN4Jy0VcMQZl/SAzqug/jg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-spread@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-VvfVYlrlBVu+77xVTOAoxQ6mZbnIq5FM0aGBSFEcIh03qHf+zNqA4DC/3XMUozTg7bZV3e3mZQ0i13VB6v5yUg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-skip-transparent-expression-wrappers': 7.22.5 - dev: false - - /@babel/plugin-transform-sticky-regex@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-HZOyN9g+rtvnOU3Yh7kSxXrKbzgrm5X4GncPY1QOquu7epga5MxKHVpYu2hvQnry/H+JjckSYRb93iNfsioAGg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-template-literals@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-Flok06AYNp7GV2oJPZZcP9vZdszev6vPBkHLwxwSpaIqx75wn6mUd3UFWsSsA0l8nXAKkyCmL/sR02m8RYGeHg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-typeof-symbol@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-4t15ViVnaFdrPC74be1gXBSMzXk3B4Us9lP7uLRQHTFpV5Dvt33pn+2MyyNxmN3VTTm3oTrZVMUmuw3oBnQ2oQ==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-unicode-escapes@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-OMCUx/bU6ChE3r4+ZdylEqAjaQgHAgipgW8nsCfu5pGqDcFytVd91AwRvUJSBZDz0exPGgnjoqhgRYLRjFZc9Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-unicode-property-regex@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-KcLIm+pDZkWZQAFJ9pdfmh89EwVfmNovFBcXko8szpBeF8z68kWIPeKlmSOkT9BXJxs2C0uk+5LxoxIv62MROA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-unicode-regex@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-wMHpNA4x2cIA32b/ci3AfwNgheiva2W0WUKWTK7vBHBhDKfPsc5cFGNWm69WBqpwd86u1qwZ9PWevKqm1A3yAw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/plugin-transform-unicode-sets-regex@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-W7lliA/v9bNR83Qc3q1ip9CQMZ09CcHDbHfbLRDNuAhn1Mvkr1ZNF7hPmztMQvtTGVLJ9m8IZqWsTkXOml8dbw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-create-regexp-features-plugin': 7.22.15(@babel/core@7.23.3) - '@babel/helper-plugin-utils': 7.22.5 - dev: false - - /@babel/preset-env@7.23.3(@babel/core@7.23.3): - resolution: {integrity: sha512-ovzGc2uuyNfNAs/jyjIGxS8arOHS5FENZaNn4rtE7UdKMMkqHCvboHfcuhWLZNX5cB44QfcGNWjaevxMzzMf+Q==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - dependencies: - '@babel/compat-data': 7.23.3 - '@babel/core': 7.23.3 - '@babel/helper-compilation-targets': 7.22.15 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/helper-validator-option': 7.22.15 - '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.23.3) - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.3) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.3) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.23.3) - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-export-namespace-from': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-import-assertions': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-syntax-import-attributes': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.3) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.3) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.3) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.23.3) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.3) - '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.23.3) - '@babel/plugin-transform-arrow-functions': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-async-generator-functions': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-async-to-generator': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-block-scoped-functions': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-block-scoping': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-class-properties': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-class-static-block': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-classes': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-computed-properties': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-destructuring': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-dotall-regex': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-duplicate-keys': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-dynamic-import': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-exponentiation-operator': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-export-namespace-from': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-for-of': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-function-name': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-json-strings': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-literals': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-logical-assignment-operators': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-member-expression-literals': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-modules-amd': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-modules-commonjs': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-modules-systemjs': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-modules-umd': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-named-capturing-groups-regex': 7.22.5(@babel/core@7.23.3) - '@babel/plugin-transform-new-target': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-nullish-coalescing-operator': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-numeric-separator': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-object-rest-spread': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-object-super': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-optional-catch-binding': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-optional-chaining': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-parameters': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-private-methods': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-private-property-in-object': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-property-literals': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-regenerator': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-reserved-words': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-shorthand-properties': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-spread': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-sticky-regex': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-template-literals': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-typeof-symbol': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-unicode-escapes': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-unicode-property-regex': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-unicode-regex': 7.23.3(@babel/core@7.23.3) - '@babel/plugin-transform-unicode-sets-regex': 7.23.3(@babel/core@7.23.3) - '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.23.3) - babel-plugin-polyfill-corejs2: 0.4.6(@babel/core@7.23.3) - babel-plugin-polyfill-corejs3: 0.8.6(@babel/core@7.23.3) - babel-plugin-polyfill-regenerator: 0.5.3(@babel/core@7.23.3) - core-js-compat: 3.33.2 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.23.3): - resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} - peerDependencies: - '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-plugin-utils': 7.22.5 - '@babel/types': 7.23.3 - esutils: 2.0.3 - dev: false - - /@babel/regjsgen@0.8.0: - resolution: {integrity: sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==} - dev: false - - /@babel/runtime@7.23.2: - resolution: {integrity: sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg==} - engines: {node: '>=6.9.0'} - dependencies: - regenerator-runtime: 0.14.0 - dev: false - - /@babel/template@7.22.15: - resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/parser': 7.23.3 - '@babel/types': 7.23.3 - dev: false - - /@babel/traverse@7.23.3: - resolution: {integrity: sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.22.13 - '@babel/generator': 7.23.3 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.3 - '@babel/types': 7.23.3 - debug: 4.3.4 - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - dev: false - - /@babel/types@7.23.3: - resolution: {integrity: sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/helper-string-parser': 7.22.5 - '@babel/helper-validator-identifier': 7.22.20 - to-fast-properties: 2.0.0 - dev: false - /@discordjs/builders@1.7.0: resolution: {integrity: sha512-GDtbKMkg433cOZur8Dv6c25EHxduNIBsxeHrsRoIM8+AwmEZ8r0tEpckx/sHwTLwQPOF3e2JWloZh9ofCaMfAw==} engines: {node: '>=16.11.0'} @@ -1288,6 +113,21 @@ packages: engines: {node: '>=18'} dev: false + /@discordjs/core@1.1.1: + resolution: {integrity: sha512-3tDqc6KCAtE0CxNl5300uPzFnNsY/GAmJhc6oGutbl/la+4mRv5zVb4N68cmcaeD2Il/ySH4zIc00sq+cyhtUA==} + engines: {node: '>=18'} + dependencies: + '@discordjs/rest': 2.2.0 + '@discordjs/util': 1.0.2 + '@discordjs/ws': 1.0.2 + '@sapphire/snowflake': 3.5.1 + '@vladfrangu/async_event_emitter': 2.2.2 + discord-api-types: 0.37.61 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + dev: false + /@discordjs/formatters@0.3.3: resolution: {integrity: sha512-wTcI1Q5cps1eSGhl6+6AzzZkBBlVrBdc9IUhJbijRgVjCNIIIZPgqnUj3ntFODsHrdbGU8BEG9XmDQmgEEYn3w==} engines: {node: '>=16.11.0'} @@ -1295,8 +135,8 @@ packages: discord-api-types: 0.37.61 dev: false - /@discordjs/rest@2.1.0: - resolution: {integrity: sha512-5gFWFkZX2JCFSRzs8ltx8bWmyVi0wPMk6pBa9KGIQSDPMmrP+uOrZ9j9HOwvmVWGe+LmZ5Bov0jMnQd6/jVReg==} + /@discordjs/rest@2.2.0: + resolution: {integrity: sha512-nXm9wT8oqrYFRMEqTXQx9DUTeEtXUDMmnUKIhZn6O2EeDY9VCdwj23XCPq7fkqMPKdF7ldAfeVKyxxFdbZl59A==} engines: {node: '>=16.11.0'} dependencies: '@discordjs/collection': 2.0.0 @@ -1320,7 +160,7 @@ packages: engines: {node: '>=16.11.0'} dependencies: '@discordjs/collection': 2.0.0 - '@discordjs/rest': 2.1.0 + '@discordjs/rest': 2.2.0 '@discordjs/util': 1.0.2 '@sapphire/async-queue': 1.5.0 '@types/ws': 8.5.9 @@ -1401,43 +241,6 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true - /@jridgewell/gen-mapping@0.3.3: - resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} - engines: {node: '>=6.0.0'} - dependencies: - '@jridgewell/set-array': 1.1.2 - '@jridgewell/sourcemap-codec': 1.4.15 - '@jridgewell/trace-mapping': 0.3.20 - dev: false - - /@jridgewell/resolve-uri@3.1.1: - resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/set-array@1.1.2: - resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} - engines: {node: '>=6.0.0'} - dev: false - - /@jridgewell/source-map@0.3.5: - resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} - dependencies: - '@jridgewell/gen-mapping': 0.3.3 - '@jridgewell/trace-mapping': 0.3.20 - dev: false - - /@jridgewell/sourcemap-codec@1.4.15: - resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false - - /@jridgewell/trace-mapping@0.3.20: - resolution: {integrity: sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==} - dependencies: - '@jridgewell/resolve-uri': 3.1.1 - '@jridgewell/sourcemap-codec': 1.4.15 - dev: false - /@mapbox/node-pre-gyp@1.0.11: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true @@ -1462,10 +265,12 @@ packages: dependencies: '@nodelib/fs.stat': 2.0.5 run-parallel: 1.2.0 + dev: true /@nodelib/fs.stat@2.0.5: resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} engines: {node: '>= 8'} + dev: true /@nodelib/fs.walk@1.2.8: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} @@ -1473,6 +278,7 @@ packages: dependencies: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + dev: true /@npmcli/fs@1.1.1: resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==} @@ -1494,90 +300,6 @@ packages: dev: false optional: true - /@nuxt/devalue@1.2.5: - resolution: {integrity: sha512-Tg86C7tqzvZtZli2BQVqgzZN136mZDTgauvJXagglKkP2xt5Kw3NUIiJyjX0Ww/IZy2xVmD0LN+CEPpij4dB2g==} - dependencies: - consola: 2.15.3 - dev: false - - /@rollup/plugin-babel@5.3.1(@babel/core@7.23.3)(rollup@2.79.1): - resolution: {integrity: sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==} - engines: {node: '>= 10.0.0'} - peerDependencies: - '@babel/core': ^7.0.0 - '@types/babel__core': ^7.1.9 - rollup: ^1.20.0||^2.0.0 - peerDependenciesMeta: - '@types/babel__core': - optional: true - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-module-imports': 7.22.15 - '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - rollup: 2.79.1 - dev: false - - /@rollup/plugin-commonjs@16.0.0(rollup@2.79.1): - resolution: {integrity: sha512-LuNyypCP3msCGVQJ7ki8PqYdpjfEkE/xtFa5DqlF+7IBD0JsfMZ87C58heSwIMint58sAUZbt3ITqOmdQv/dXw==} - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: ^2.30.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - commondir: 1.0.1 - estree-walker: 2.0.2 - glob: 7.2.3 - is-reference: 1.2.1 - magic-string: 0.25.9 - resolve: 1.22.8 - rollup: 2.79.1 - dev: false - - /@rollup/plugin-node-resolve@10.0.0(rollup@2.79.1): - resolution: {integrity: sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A==} - engines: {node: '>= 10.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - '@types/resolve': 1.17.1 - builtin-modules: 3.3.0 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.8 - rollup: 2.79.1 - dev: false - - /@rollup/plugin-replace@2.4.2(rollup@2.79.1): - resolution: {integrity: sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==} - peerDependencies: - rollup: ^1.20.0 || ^2.0.0 - dependencies: - '@rollup/pluginutils': 3.1.0(rollup@2.79.1) - magic-string: 0.25.9 - rollup: 2.79.1 - dev: false - - /@rollup/pluginutils@3.1.0(rollup@2.79.1): - resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==} - engines: {node: '>= 8.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0 - dependencies: - '@types/estree': 0.0.39 - estree-walker: 1.0.1 - picomatch: 2.3.1 - rollup: 2.79.1 - dev: false - - /@rollup/pluginutils@4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: false - /@sapphire/async-queue@1.5.0: resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -1641,14 +363,6 @@ packages: resolution: {integrity: sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==} dev: true - /@types/estree@0.0.39: - resolution: {integrity: sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==} - dev: false - - /@types/estree@1.0.5: - resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} - dev: false - /@types/express-serve-static-core@4.17.41: resolution: {integrity: sha512-OaJ7XLaelTgrvlZD8/aa0vvvxZdUmlCn6MtWeB7TkiKW70BQLc9XEPpDLPdbo52ZhXUCrznlWdCHWxJWtdyajA==} dependencies: @@ -1710,12 +424,6 @@ packages: resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} dev: true - /@types/resolve@1.17.1: - resolution: {integrity: sha512-yy7HuzQhj0dhGpD8RLXSZWEkLsV9ibvxvi6EiJ3bkqLAO1RGo0WbkWQiwpRlSFymTJRz0d3k5LM3kkx8ArDbLw==} - dependencies: - '@types/node': 20.9.0 - dev: false - /@types/responselike@1.0.3: resolution: {integrity: sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==} dependencies: @@ -1741,6 +449,14 @@ packages: '@types/node': 20.9.0 dev: true + /@types/tough-cookie@4.0.5: + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + dev: true + + /@types/uid-safe@2.1.5: + resolution: {integrity: sha512-RwEfbxqXKEay2b5p8QQVllfnMbVPUZChiKKZ2M6+OSRRmvr4HTCCUZTWhr/QlmrMnNE0ViNBBbP1+5plF9OGRw==} + dev: true + /@types/ws@8.5.9: resolution: {integrity: sha512-jbdrY0a8lxfdTp/+r7Z4CkycbOFN8WX+IOchLJr3juT/xzbJ8URyTVSJ/hvNdadTgM1mnedb47n+Y31GsFnQlg==} dependencies: @@ -1912,6 +628,7 @@ packages: resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} engines: {node: '>=0.4.0'} hasBin: true + dev: true /agent-base@6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} @@ -1954,13 +671,6 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} - /ansi-styles@3.2.1: - resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} - engines: {node: '>=4'} - dependencies: - color-convert: 1.9.3 - dev: false - /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -2002,47 +712,6 @@ packages: engines: {node: '>=8'} dev: true - /at-least-node@1.0.0: - resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==} - engines: {node: '>= 4.0.0'} - dev: false - - /babel-plugin-polyfill-corejs2@0.4.6(@babel/core@7.23.3): - resolution: {integrity: sha512-jhHiWVZIlnPbEUKSSNb9YoWcQGdlTLq7z1GHL4AjFxaoOUMuuEVJ+Y4pAaQUGOGk93YsVCKPbqbfw3m0SM6H8Q==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/compat-data': 7.23.3 - '@babel/core': 7.23.3 - '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - dev: false - - /babel-plugin-polyfill-corejs3@0.8.6(@babel/core@7.23.3): - resolution: {integrity: sha512-leDIc4l4tUgU7str5BWLS2h8q2N4Nf6lGZP6UrNDxdtfF2g69eJ5L0H7S8A5Ln/arfFAfHor5InAdZuIOwZdgQ==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) - core-js-compat: 3.33.2 - transitivePeerDependencies: - - supports-color - dev: false - - /babel-plugin-polyfill-regenerator@0.5.3(@babel/core@7.23.3): - resolution: {integrity: sha512-8sHeDOmXC8csczMrYEOf0UTNa4yE2SxV5JGeT/LP1n0OYVDUUFPxG9vdk2AlDlIit4t+Kf0xCtpgXPBwnn/9pw==} - peerDependencies: - '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 - dependencies: - '@babel/core': 7.23.3 - '@babel/helper-define-polyfill-provider': 0.4.3(@babel/core@7.23.3) - transitivePeerDependencies: - - supports-color - dev: false - /balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -2077,26 +746,7 @@ packages: engines: {node: '>=8'} dependencies: fill-range: 7.0.1 - - /browserslist@4.22.1: - resolution: {integrity: sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - dependencies: - caniuse-lite: 1.0.30001563 - electron-to-chromium: 1.4.588 - node-releases: 2.0.13 - update-browserslist-db: 1.0.13(browserslist@4.22.1) - dev: false - - /buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - dev: false - - /builtin-modules@3.3.0: - resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} - engines: {node: '>=6'} - dev: false + dev: true /bytes@3.1.2: resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} @@ -2162,19 +812,6 @@ packages: engines: {node: '>=6'} dev: true - /caniuse-lite@1.0.30001563: - resolution: {integrity: sha512-na2WUmOxnwIZtwnFI2CZ/3er0wdNzU7hN+cPYz/z2ajHThnkWjNBOpEPP4n+4r2WPM847JaMotaJE3bnfzjyKw==} - dev: false - - /chalk@2.4.2: - resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} - engines: {node: '>=4'} - dependencies: - ansi-styles: 3.2.1 - escape-string-regexp: 1.0.5 - supports-color: 5.5.0 - dev: false - /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -2204,22 +841,12 @@ packages: resolution: {integrity: sha512-RwBeO/B/vZR3dfKL1ye/vx8MHZ40ugzpyfeVG5GsiuGnrlMWe2o8wxBbLCpw9CsxV+wHuzYlCiWnybrIA0ling==} dev: false - /color-convert@1.9.3: - resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} - dependencies: - color-name: 1.1.3 - dev: false - /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} dependencies: color-name: 1.1.4 - /color-name@1.1.3: - resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: false - /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -2237,21 +864,9 @@ packages: engines: {node: '>=14'} dev: false - /commander@2.20.3: - resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} - dev: false - - /commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - dev: false - /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - /consola@2.15.3: - resolution: {integrity: sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==} - dev: false - /console-control-strings@1.1.0: resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} dev: false @@ -2268,10 +883,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - dev: false - /cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} dev: false @@ -2281,17 +892,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /core-js-compat@3.33.2: - resolution: {integrity: sha512-axfo+wxFVxnqf8RvxTzoAlzW4gRoacrHeoFlc9n0x50+7BEyZL/Rt3hicaED1/CEd7I6tPCPVUYcJwCMO5XUYw==} - dependencies: - browserslist: 4.22.1 - dev: false - - /core-js@3.33.2: - resolution: {integrity: sha512-XeBzWI6QL3nJQiHmdzbAOiMYqjrb7hwU7A39Qhvd/POSa/t9E1AeZyEZx3fNvp/vtM8zXwhoL0FsiS0hD0pruQ==} - requiresBuild: true - dev: false - /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -2340,11 +940,6 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true - /deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - dev: false - /defer-to-connect@2.0.1: resolution: {integrity: sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==} engines: {node: '>=10'} @@ -2400,7 +995,7 @@ packages: '@discordjs/builders': 1.7.0 '@discordjs/collection': 1.5.3 '@discordjs/formatters': 0.3.3 - '@discordjs/rest': 2.1.0 + '@discordjs/rest': 2.2.0 '@discordjs/util': 1.0.2 '@discordjs/ws': 1.0.2 '@sapphire/snowflake': 3.5.1 @@ -2427,10 +1022,6 @@ packages: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false - /electron-to-chromium@1.4.588: - resolution: {integrity: sha512-soytjxwbgcCu7nh5Pf4S2/4wa6UIu+A3p03U2yVr53qGxi1/VTR3ENI+p50v+UxqqZAfl48j3z55ud7VHIOr9w==} - dev: false - /emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} requiresBuild: true @@ -2477,11 +1068,6 @@ packages: resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} dev: false - /escape-string-regexp@1.0.5: - resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} - engines: {node: '>=0.8.0'} - dev: false - /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -2580,69 +1166,16 @@ packages: engines: {node: '>=4.0'} dev: true - /estree-walker@0.2.1: - resolution: {integrity: sha512-6/I1dwNKk0N9iGOU3ydzAAurz4NPo/ttxZNCqgIVbWFvWyzWBSNonRrJ5CpjDuyBfmM7ENN7WCzUi9aT/UPXXQ==} - dev: false - - /estree-walker@1.0.1: - resolution: {integrity: sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==} - dev: false - - /estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - dev: false - /esutils@2.0.3: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + dev: true /etag@1.8.1: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} dev: false - /eval@0.1.8: - resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} - engines: {node: '>= 0.8'} - dependencies: - '@types/node': 20.9.0 - require-like: 0.1.2 - dev: false - - /express-svelte@1.0.8(express@4.18.2)(svelte@3.59.2): - resolution: {integrity: sha512-O2oEAUcbLJwRLtIDWyGq34rcipcBRd3zic8FFB7zHGzr3+V6cP3GFd1lfTBK+wI9HcLxeGzbxJ3gMkAOx9pksw==} - peerDependencies: - express: ^4.17.1 - svelte: ^3.38.2 - dependencies: - '@babel/core': 7.23.3 - '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.23.3) - '@babel/plugin-transform-runtime': 7.23.3(@babel/core@7.23.3) - '@babel/preset-env': 7.23.3(@babel/core@7.23.3) - '@babel/runtime': 7.23.2 - '@nuxt/devalue': 1.2.5 - '@rollup/plugin-babel': 5.3.1(@babel/core@7.23.3)(rollup@2.79.1) - '@rollup/plugin-commonjs': 16.0.0(rollup@2.79.1) - '@rollup/plugin-node-resolve': 10.0.0(rollup@2.79.1) - '@rollup/plugin-replace': 2.4.2(rollup@2.79.1) - core-js: 3.33.2 - eval: 0.1.8 - express: 4.18.2 - fast-glob: 3.3.2 - fs-extra: 9.1.0 - lodash: 4.17.21 - rollup: 2.79.1 - rollup-plugin-css-only: 3.1.0(rollup@2.79.1) - rollup-plugin-svelte: 7.1.6(rollup@2.79.1)(svelte@3.59.2) - rollup-plugin-svelte-svg: 0.2.3(svelte@3.59.2) - rollup-plugin-terser: 7.0.2(rollup@2.79.1) - source-map-support: 0.5.21 - svelte: 3.59.2 - transitivePeerDependencies: - - '@types/babel__core' - - supports-color - dev: false - /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -2694,6 +1227,7 @@ packages: glob-parent: 5.1.2 merge2: 1.4.1 micromatch: 4.0.5 + dev: true /fast-json-stable-stringify@2.1.0: resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} @@ -2707,6 +1241,7 @@ packages: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: reusify: 1.0.4 + dev: true /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} @@ -2720,6 +1255,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /finalhandler@1.2.0: resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} @@ -2767,16 +1303,6 @@ packages: engines: {node: '>= 0.6'} dev: false - /fs-extra@9.1.0: - resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} - engines: {node: '>=10'} - dependencies: - at-least-node: 1.0.0 - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - dev: false - /fs-minipass@2.1.0: resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} engines: {node: '>= 8'} @@ -2788,14 +1314,6 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} requiresBuild: true - /fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - requiresBuild: true - dev: false - optional: true - /function-bind@1.1.2: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: false @@ -2831,11 +1349,6 @@ packages: dev: false optional: true - /gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - dev: false - /get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} dependencies: @@ -2866,6 +1379,7 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -2885,11 +1399,6 @@ packages: once: 1.4.0 path-is-absolute: 1.0.1 - /globals@11.12.0: - resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} - engines: {node: '>=4'} - dev: false - /globals@13.23.0: resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} engines: {node: '>=8'} @@ -2936,16 +1445,12 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} requiresBuild: true dev: false + optional: true /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true - /has-flag@3.0.0: - resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} - engines: {node: '>=4'} - dev: false - /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -3117,6 +1622,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-fullwidth-code-point@3.0.0: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} @@ -3129,6 +1635,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} @@ -3136,42 +1643,20 @@ packages: dev: false optional: true - /is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - dev: false - /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} engines: {node: '>=8'} dev: true - /is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - dependencies: - '@types/estree': 1.0.5 - dev: false - /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} requiresBuild: true - /jest-worker@26.6.2: - resolution: {integrity: sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==} - engines: {node: '>= 10.13.0'} - dependencies: - '@types/node': 20.9.0 - merge-stream: 2.0.0 - supports-color: 7.2.0 - dev: false - - /js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: false - /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -3179,17 +1664,6 @@ packages: argparse: 2.0.1 dev: true - /jsesc@0.5.0: - resolution: {integrity: sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==} - hasBin: true - dev: false - - /jsesc@2.5.2: - resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} - engines: {node: '>=4'} - hasBin: true - dev: false - /json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -3205,20 +1679,6 @@ packages: resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} dev: false - /json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - dev: false - - /jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - dev: false - /keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} dependencies: @@ -3286,10 +1746,6 @@ packages: p-locate: 5.0.0 dev: true - /lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - dev: false - /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -3307,12 +1763,6 @@ packages: engines: {node: '>=8'} dev: false - /lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - dependencies: - yallist: 3.1.1 - dev: false - /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -3324,12 +1774,6 @@ packages: resolution: {integrity: sha512-wJkXvutRbNWcc37tt5j1HyOK1nosspdh3dj6LUYYAvF6JYNqs53IfRvK9oEpcwiDA1NdoIi64yAMfdivPeVAyw==} dev: false - /magic-string@0.25.9: - resolution: {integrity: sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==} - dependencies: - sourcemap-codec: 1.4.8 - dev: false - /make-dir@3.1.0: resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} engines: {node: '>=8'} @@ -3373,13 +1817,10 @@ packages: resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} dev: false - /merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - dev: false - /merge2@1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + dev: true /methods@1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} @@ -3392,6 +1833,7 @@ packages: dependencies: braces: 3.0.2 picomatch: 2.3.1 + dev: true /mime-db@1.52.0: resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} @@ -3561,10 +2003,6 @@ packages: dev: false optional: true - /node-releases@2.0.13: - resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} - dev: false - /nopt@5.0.0: resolution: {integrity: sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==} engines: {node: '>=6'} @@ -3714,13 +2152,10 @@ packages: resolution: {integrity: sha512-w6ZzNu6oMmIzEAYVw+RLK0+nqHPt8K3ZnknKi+g48Ak2pr3dtljJW3o+D/n2zzCG07Zoe9VOX3aiKpj+BN0pjg==} dev: false - /picocolors@1.0.0: - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: false - /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + dev: true /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} @@ -3761,6 +2196,10 @@ packages: ipaddr.js: 1.9.1 dev: false + /psl@1.9.0: + resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} + dev: false + /pump@3.0.0: resolution: {integrity: sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==} dependencies: @@ -3771,7 +2210,6 @@ packages: /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - dev: true /qs@6.11.0: resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} @@ -3780,14 +2218,24 @@ packages: side-channel: 1.0.4 dev: false + /querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + dev: false + /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + dev: true /quick-lru@5.1.1: resolution: {integrity: sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==} engines: {node: '>=10'} dev: false + /random-bytes@1.0.0: + resolution: {integrity: sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==} + engines: {node: '>= 0.8'} + dev: false + /random-seed@0.3.0: resolution: {integrity: sha512-y13xtn3kcTlLub3HKWXxJNeC2qK4mB59evwZ5EkeRlolx+Bp2ztF7LbcZmyCnOqlHQrLnfuNbi1sVmm9lPDlDA==} engines: {node: '>= 0.6.0'} @@ -3795,12 +2243,6 @@ packages: json-stringify-safe: 5.0.1 dev: false - /randombytes@2.1.0: - resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} - dependencies: - safe-buffer: 5.2.1 - dev: false - /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -3832,48 +2274,8 @@ packages: resolve: 1.22.8 dev: false - /regenerate-unicode-properties@10.1.1: - resolution: {integrity: sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==} - engines: {node: '>=4'} - dependencies: - regenerate: 1.4.2 - dev: false - - /regenerate@1.4.2: - resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} - dev: false - - /regenerator-runtime@0.14.0: - resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} - dev: false - - /regenerator-transform@0.15.2: - resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} - dependencies: - '@babel/runtime': 7.23.2 - dev: false - - /regexpu-core@5.3.2: - resolution: {integrity: sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==} - engines: {node: '>=4'} - dependencies: - '@babel/regjsgen': 0.8.0 - regenerate: 1.4.2 - regenerate-unicode-properties: 10.1.1 - regjsparser: 0.9.1 - unicode-match-property-ecmascript: 2.0.0 - unicode-match-property-value-ecmascript: 2.1.0 - dev: false - - /regjsparser@0.9.1: - resolution: {integrity: sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==} - hasBin: true - dependencies: - jsesc: 0.5.0 - dev: false - - /require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + /requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} dev: false /resolve-alpn@1.2.1: @@ -3890,11 +2292,6 @@ packages: engines: {node: '>=8'} dev: false - /resolve.exports@2.0.2: - resolution: {integrity: sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==} - engines: {node: '>=10'} - dev: false - /resolve@1.22.8: resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} hasBin: true @@ -3920,6 +2317,7 @@ packages: /reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + dev: true /rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} @@ -3927,70 +2325,11 @@ packages: dependencies: glob: 7.2.3 - /rollup-plugin-css-only@3.1.0(rollup@2.79.1): - resolution: {integrity: sha512-TYMOE5uoD76vpj+RTkQLzC9cQtbnJNktHPB507FzRWBVaofg7KhIqq1kGbcVOadARSozWF883Ho9KpSPKH8gqA==} - engines: {node: '>=10.12.0'} - peerDependencies: - rollup: 1 || 2 - dependencies: - '@rollup/pluginutils': 4.2.1 - rollup: 2.79.1 - dev: false - - /rollup-plugin-svelte-svg@0.2.3(svelte@3.59.2): - resolution: {integrity: sha512-WH2KqihgVbcJT/PrjiJ/ipctrGueRdOLaaEuAIbXYqfsEzNb9krOVLyc5TL7to3cEKL6DiAa8IEe7IL1oqVolg==} - peerDependencies: - svelte: ^3.16.7 - dependencies: - rollup-pluginutils: 1.5.2 - svelte: 3.59.2 - dev: false - - /rollup-plugin-svelte@7.1.6(rollup@2.79.1)(svelte@3.59.2): - resolution: {integrity: sha512-nVFRBpGWI2qUY1OcSiEEA/kjCY2+vAjO9BI8SzA7NRrh2GTunLd6w2EYmnMt/atgdg8GvcNjLsmZmbQs/u4SQA==} - engines: {node: '>=10'} - peerDependencies: - rollup: '>=2.0.0' - svelte: '>=3.5.0' - dependencies: - '@rollup/pluginutils': 4.2.1 - resolve.exports: 2.0.2 - rollup: 2.79.1 - svelte: 3.59.2 - dev: false - - /rollup-plugin-terser@7.0.2(rollup@2.79.1): - resolution: {integrity: sha512-w3iIaU4OxcF52UUXiZNsNeuXIMDvFrr+ZXK6bFZ0Q60qyVfq4uLptoS4bbq3paG3x216eQllFZX7zt6TIImguQ==} - deprecated: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser - peerDependencies: - rollup: ^2.0.0 - dependencies: - '@babel/code-frame': 7.22.13 - jest-worker: 26.6.2 - rollup: 2.79.1 - serialize-javascript: 4.0.0 - terser: 5.24.0 - dev: false - - /rollup-pluginutils@1.5.2: - resolution: {integrity: sha512-SjdWWWO/CUoMpDy8RUbZ/pSpG68YHmhk5ROKNIoi2En9bJ8bTt3IhYi254RWiTclQmL7Awmrq+rZFOhZkJAHmQ==} - dependencies: - estree-walker: 0.2.1 - minimatch: 3.1.2 - dev: false - - /rollup@2.79.1: - resolution: {integrity: sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==} - engines: {node: '>=10.0.0'} - hasBin: true - optionalDependencies: - fsevents: 2.3.3 - dev: false - /run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: queue-microtask: 1.2.3 + dev: true /safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4035,12 +2374,6 @@ packages: - supports-color dev: false - /serialize-javascript@4.0.0: - resolution: {integrity: sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==} - dependencies: - randombytes: 2.1.0 - dev: false - /serve-static@1.15.0: resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} engines: {node: '>= 0.8.0'} @@ -4130,23 +2463,6 @@ packages: dev: false optional: true - /source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - dev: false - - /source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - dev: false - - /sourcemap-codec@1.4.8: - resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} - deprecated: Please use @jridgewell/sourcemap-codec instead - dev: false - /sqlite3@5.1.6: resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} requiresBuild: true @@ -4206,13 +2522,6 @@ packages: engines: {node: '>=8'} dev: true - /supports-color@5.5.0: - resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} - engines: {node: '>=4'} - dependencies: - has-flag: 3.0.0 - dev: false - /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -4224,11 +2533,6 @@ packages: engines: {node: '>= 0.4'} dev: false - /svelte@3.59.2: - resolution: {integrity: sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==} - engines: {node: '>= 8'} - dev: false - /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} engines: {node: '>=10'} @@ -4246,17 +2550,6 @@ packages: engines: {node: '>=8.0.0'} dev: false - /terser@5.24.0: - resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==} - engines: {node: '>=10'} - hasBin: true - dependencies: - '@jridgewell/source-map': 0.3.5 - acorn: 8.11.2 - commander: 2.20.3 - source-map-support: 0.5.21 - dev: false - /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true @@ -4266,22 +2559,28 @@ packages: engines: {node: '>=8'} dev: false - /to-fast-properties@2.0.0: - resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} - engines: {node: '>=4'} - dev: false - /to-regex-range@5.0.1: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /toidentifier@1.0.1: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} dev: false + /tough-cookie@4.1.3: + resolution: {integrity: sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==} + engines: {node: '>=6'} + dependencies: + psl: 1.9.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + dev: false + /tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: false @@ -4329,6 +2628,13 @@ packages: hasBin: true dev: true + /uid-safe@2.1.5: + resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} + engines: {node: '>= 0.8'} + dependencies: + random-bytes: 1.0.0 + dev: false + /undici-types@5.26.5: resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} @@ -4339,29 +2645,6 @@ packages: '@fastify/busboy': 2.1.0 dev: false - /unicode-canonical-property-names-ecmascript@2.0.0: - resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} - engines: {node: '>=4'} - dev: false - - /unicode-match-property-ecmascript@2.0.0: - resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} - engines: {node: '>=4'} - dependencies: - unicode-canonical-property-names-ecmascript: 2.0.0 - unicode-property-aliases-ecmascript: 2.1.0 - dev: false - - /unicode-match-property-value-ecmascript@2.1.0: - resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} - engines: {node: '>=4'} - dev: false - - /unicode-property-aliases-ecmascript@2.1.0: - resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} - engines: {node: '>=4'} - dev: false - /unique-filename@1.1.1: resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==} requiresBuild: true @@ -4378,9 +2661,9 @@ packages: dev: false optional: true - /universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} + /universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} dev: false /unpipe@1.0.0: @@ -4388,23 +2671,19 @@ packages: engines: {node: '>= 0.8'} dev: false - /update-browserslist-db@1.0.13(browserslist@4.22.1): - resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - dependencies: - browserslist: 4.22.1 - escalade: 3.1.1 - picocolors: 1.0.0 - dev: false - /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.1 dev: true + /url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} requiresBuild: true @@ -4461,10 +2740,6 @@ packages: optional: true dev: false - /yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - dev: false - /yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} diff --git a/src/index.ts b/src/index.ts index a0d3592..03590b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { Client, GatewayIntentBits, Events, Collection, CommandInteraction, CommandInteractionOption, ApplicationCommandOptionType } from 'discord.js'; import * as fs from 'fs'; -const { token, sitePort, siteURL } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); +const { token, sitePort, siteURL, clientId, clientSecret } = JSON.parse(fs.readFileSync('./config.json', 'utf8')); import * as path from 'path'; import { initializeAnnouncements } from './lib/subscriptions'; import * as log from './lib/log'; @@ -22,7 +22,7 @@ const bot = new Client({ }); bot.config = { - token, sitePort, siteURL + token, sitePort, siteURL, clientId, clientSecret }; async function init() { diff --git a/src/lib/db.ts b/src/lib/db.ts index 8e78d22..db1f2da 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -79,4 +79,11 @@ export interface CustomCraftingRecipeItem { item: number, quantity: number, type: 'input' | 'output' | 'requirement' +} +export interface Session { + id: string, + tokenType: string, + accessToken: string, + refreshToken: string, + expiresAt: number, } \ No newline at end of file diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 31e3b8f..cfbdeb0 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -11,7 +11,9 @@ export interface Command { export interface Config { token: string, sitePort: number, - siteURL: string + siteURL: string, + clientId: string, + clientSecret: string, } declare module 'discord.js' { diff --git a/src/web.ts b/src/web.ts index 32326fb..23fd148 100644 --- a/src/web.ts +++ b/src/web.ts @@ -1,14 +1,74 @@ import express from 'express'; import * as log from './lib/log'; -import { CustomItem, db } from './lib/db'; +import { CustomItem, Session, db } from './lib/db'; import { defaultItems } from './lib/rpg/items'; -import { Client } from 'discord.js'; +import { Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; +import got from 'got'; +import uid from 'uid-safe'; +import { Cookie, parse } from 'tough-cookie'; +import { IncomingHttpHeaders } from 'http'; + +const DISCORD_ENDPOINT = 'https://discord.com/api/v10'; +const UID_BYTE_LENGTH = 18; +const COOKIE_KEY = 'PHPSESSID'; +const COOKIE_EXPIRES = 1_000 * 60 * 60 * 24 * 365; + +async function getSessionString(cookieStr: string) { + const cookies = cookieStr.split('; ').map(s => parse(s)!).filter(c => c !== null); + const sessionCookie = cookies.find(c => c.key === COOKIE_KEY); + + if (!sessionCookie) { + return await uid(UID_BYTE_LENGTH); + } else { + return sessionCookie.value; + } +} + +async function getSession(bot: Client, headers: IncomingHttpHeaders) { + const cookie = headers['cookie']; + if (!cookie) return; + + const sessionStr = await getSessionString(cookie); + + const session = await db('sessions') + .where('id', sessionStr) + .first(); + + if (!session) return; + + if (Date.now() < session.expiresAt) return session; + + let resp; + try { + resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + grant_type: 'refresh_token', + refresh_token: session.refreshToken, + } satisfies RESTPostOAuth2RefreshTokenURLEncodedData + }).json() as RESTPostOAuth2AccessTokenResult; + } catch(err) { + log.error(err); + return; + } + + const sessionData = { + tokenType: resp.token_type, + accessToken: resp.access_token, + refreshToken: resp.refresh_token, + expiresAt: Date.now() + resp.expires_in * 1000, + }; + + return (await db('sessions') + .where('id', sessionStr) + .update(sessionData) + .returning('*'))[0]; +} export async function startServer(bot: Client, port: number) { const app = express(); - app.use(express.static('static/')); - app.get('/api/items', async (req, res) => { const guildID = req.query.guild; @@ -32,5 +92,78 @@ export async function startServer(bot: Client, port: number) { }); }); + app.get('/', async (req, res) => { + const code = req.query.code as string; + + if (code) { + try { + const resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + code, + grant_type: 'authorization_code', + redirect_uri: bot.config.siteURL, + } satisfies RESTPostOAuth2AccessTokenURLEncodedData + // if you're looking to change this then you are blissfully unaware of the past + // and have learnt 0 lessons + }).json() as RESTPostOAuth2AccessTokenResult; + + const sessionId = await getSessionString(decodeURIComponent(req.headers.cookie || '')); + + const session = await db('sessions') + .where('id', sessionId) + .first(); + + const sessionData = { + tokenType: resp.token_type, + accessToken: resp.access_token, + refreshToken: resp.refresh_token, + expiresAt: Date.now() + resp.expires_in * 1000, + }; + + if (session) { + await db('sessions') + .where('id', sessionId) + .update(sessionData); + } else { + await db('sessions') + .insert({id: sessionId, ...sessionData} satisfies Session); + } + + const cookie = new Cookie({ + key: COOKIE_KEY, + value: sessionId, + expires: new Date(Date.now() + COOKIE_EXPIRES), + sameSite: 'strict' + }); + res.setHeader('Set-Cookie', cookie.toString()); + + return res.redirect('/profile'); + } catch (err) { + log.error(err); + return res.status(500); + } + } + + res.sendFile('index.html', { root: 'static/' }); + }); + + app.get('/profile', async (req, res) => { + const session = await getSession(bot, req.headers); + if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); + + const user = await got('https://discord.com/api/users/@me', { + headers: { + authorization: `${session.tokenType} ${session.accessToken}` + } + }).json(); + + //res.sendFile('profile/index.html', { root: 'static/' }); + res.json(user); + }); + + app.use(express.static('static/')); + app.listen(port, () => log.info(`web interface listening on ${port}`)); } \ No newline at end of file From a487fc2f4cc90896c55cde1df952b9844c795de0 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 00:11:55 +0300 Subject: [PATCH 42/66] show login thing on homepage --- package.json | 1 + pnpm-lock.yaml | 186 ++++++++++++++++++++- src/web.ts | 41 ++++- static/assets/avatar.png | Bin 0 -> 13274 bytes static/style.css | 37 +++- static/index.html => views/home.handlebars | 9 + 6 files changed, 260 insertions(+), 14 deletions(-) create mode 100644 static/assets/avatar.png rename static/index.html => views/home.handlebars (74%) diff --git a/package.json b/package.json index 28a612e..2645282 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "d3-array": "^2.12.1", "discord.js": "^14.14.1", "express": "^4.18.2", + "express-handlebars": "^7.1.2", "got": "^11.8.6", "knex": "^3.0.1", "outdent": "^0.8.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 665d2ed..4087693 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,6 +23,9 @@ dependencies: express: specifier: ^4.18.2 version: 4.18.2 + express-handlebars: + specifier: ^7.1.2 + version: 7.1.2 got: specifier: ^11.8.6 version: 11.8.6 @@ -241,6 +244,18 @@ packages: resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} dev: true + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + /@mapbox/node-pre-gyp@1.0.11: resolution: {integrity: sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==} hasBin: true @@ -300,6 +315,13 @@ packages: dev: false optional: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + /@sapphire/async-queue@1.5.0: resolution: {integrity: sha512-JkLdIsP8fPAdh9ZZjrbHWR/+mZj0wvKS5ICibcLrRI1j84UmLMshx5n9QmL8b95d4onJ2xxiyugTgSAX7AalmA==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -671,12 +693,22 @@ packages: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: false + /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} dependencies: color-convert: 2.0.1 + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: false + /aproba@2.0.0: resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} dev: false @@ -741,6 +773,12 @@ packages: balanced-match: 1.0.2 concat-map: 0.0.1 + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: false + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} @@ -899,7 +937,6 @@ packages: path-key: 3.1.1 shebang-command: 2.0.0 which: 2.0.2 - dev: true /d3-array@2.12.1: resolution: {integrity: sha512-B0ErZK/66mHtEsR1TkPEEkwdy+WDesimkM5gpZr5Dsg54BiTA5RXtYW5qTLIAcekaS9xfZrzBLF/OAkB3Qn1YQ==} @@ -1018,6 +1055,10 @@ packages: esutils: 2.0.3 dev: true + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: false + /ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} dev: false @@ -1027,6 +1068,10 @@ packages: requiresBuild: true dev: false + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: false + /encodeurl@1.0.2: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} @@ -1176,6 +1221,15 @@ packages: engines: {node: '>= 0.6'} dev: false + /express-handlebars@7.1.2: + resolution: {integrity: sha512-ss9d3mBChOLTEtyfzXCsxlItUxpgS3i4cb/F70G6Q5ohQzmD12XB4x/Y9U6YboeeYBJZt7WQ5yUNu7ZSQ/EGyQ==} + engines: {node: '>=v16'} + dependencies: + glob: 10.3.10 + graceful-fs: 4.2.11 + handlebars: 4.7.8 + dev: false + /express@4.18.2: resolution: {integrity: sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==} engines: {node: '>= 0.10.0'} @@ -1293,6 +1347,14 @@ packages: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} dev: true + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: false + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1388,6 +1450,18 @@ packages: is-glob: 4.0.3 dev: true + /glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.3 + minipass: 5.0.0 + path-scurry: 1.10.1 + dev: false + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} requiresBuild: true @@ -1445,12 +1519,24 @@ packages: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} requiresBuild: true dev: false - optional: true /graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} dev: true + /handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.17.4 + dev: false + /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1657,6 +1743,15 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} requiresBuild: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: false + /js-yaml@4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1763,6 +1858,11 @@ packages: engines: {node: '>=8'} dev: false + /lru-cache@10.0.3: + resolution: {integrity: sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==} + engines: {node: 14 || >=16.14} + dev: false + /lru-cache@6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1868,6 +1968,17 @@ packages: dependencies: brace-expansion: 1.1.11 + /minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: false + + /minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: false + /minipass-collect@1.0.2: resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} engines: {node: '>= 8'} @@ -1965,6 +2076,10 @@ packages: requiresBuild: true dev: false + /neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + dev: false + /node-addon-api@4.3.0: resolution: {integrity: sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ==} dev: false @@ -2133,12 +2248,19 @@ packages: /path-key@3.1.1: resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} engines: {node: '>=8'} - dev: true /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} dev: false + /path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.0.3 + minipass: 5.0.0 + dev: false + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false @@ -2409,12 +2531,10 @@ packages: engines: {node: '>=8'} dependencies: shebang-regex: 3.0.0 - dev: true /shebang-regex@3.0.0: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} - dev: true /side-channel@1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} @@ -2428,6 +2548,11 @@ packages: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: false + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: false + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} @@ -2463,6 +2588,11 @@ packages: dev: false optional: true + /source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + dev: false + /sqlite3@5.1.6: resolution: {integrity: sha512-olYkWoKFVNSSSQNvxVUfjiVbz3YtBwTJj+mfV5zpHmqW3sELx2Cf4QCdirMelhM5Zh+KDVaKgQHqCxrqiWHybw==} requiresBuild: true @@ -2504,6 +2634,15 @@ packages: strip-ansi: 6.0.1 dev: false + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: false + /string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} requiresBuild: true @@ -2517,6 +2656,13 @@ packages: dependencies: ansi-regex: 5.0.1 + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: false + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -2628,6 +2774,14 @@ packages: hasBin: true dev: true + /uglify-js@3.17.4: + resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} + engines: {node: '>=0.8.0'} + hasBin: true + requiresBuild: true + dev: false + optional: true + /uid-safe@2.1.5: resolution: {integrity: sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==} engines: {node: '>= 0.8'} @@ -2723,6 +2877,28 @@ packages: string-width: 4.2.3 dev: false + /wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + dev: false + + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: false + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: false + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} requiresBuild: true diff --git a/src/web.ts b/src/web.ts index 23fd148..7ae4a6b 100644 --- a/src/web.ts +++ b/src/web.ts @@ -1,8 +1,9 @@ import express from 'express'; +import { engine } from 'express-handlebars'; import * as log from './lib/log'; import { CustomItem, Session, db } from './lib/db'; import { defaultItems } from './lib/rpg/items'; -import { Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; +import { APIUser, CDN, Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; import got from 'got'; import uid from 'uid-safe'; import { Cookie, parse } from 'tough-cookie'; @@ -66,8 +67,27 @@ async function getSession(bot: Client, headers: IncomingHttpHeaders) { .returning('*'))[0]; } +export async function getUser(session: Session | undefined) { + if (!session) return null; + try { + return await got('https://discord.com/api/users/@me', { + headers: { + authorization: `${session.tokenType} ${session.accessToken}` + } + }).json() as APIUser; + } catch(err) { + log.error(err); + return null; + } +} + export async function startServer(bot: Client, port: number) { const app = express(); + const cdn = new CDN(); + + app.engine('handlebars', engine()); + app.set('view engine', 'handlebars'); + app.set('views', './views'); app.get('/api/items', async (req, res) => { const guildID = req.query.guild; @@ -128,7 +148,8 @@ export async function startServer(bot: Client, port: number) { .update(sessionData); } else { await db('sessions') - .insert({id: sessionId, ...sessionData} satisfies Session); + .insert({id: sessionId, ...sessionData} satisfies Session) + .returning('*'); } const cookie = new Cookie({ @@ -146,18 +167,22 @@ export async function startServer(bot: Client, port: number) { } } - res.sendFile('index.html', { root: 'static/' }); + const session = await getSession(bot, req.headers); + const user = await getUser(session); + + res.render('home', { + signedIn: session !== undefined, + username: user?.global_name, + avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : null, + layout: false, + }); }); app.get('/profile', async (req, res) => { const session = await getSession(bot, req.headers); if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); - const user = await got('https://discord.com/api/users/@me', { - headers: { - authorization: `${session.tokenType} ${session.accessToken}` - } - }).json(); + const user = await getUser(session); //res.sendFile('profile/index.html', { root: 'static/' }); res.json(user); diff --git a/static/assets/avatar.png b/static/assets/avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..4a04f818a28024fc75fca9049b225242095153eb GIT binary patch literal 13274 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 Y7e?12o~RdIj{pDw07*qoM6N<$g4l1^FaQ7m literal 0 HcmV?d00001 diff --git a/static/style.css b/static/style.css index 4ca85c8..4eb5244 100644 --- a/static/style.css +++ b/static/style.css @@ -11,6 +11,7 @@ body { font-family: 'Balsamiq Sans', sans-serif; font-weight: 300; width: 100%; + min-height: 100vh; text-underline-offset: 3px; font-size: 16px; color-scheme: light dark; @@ -18,6 +19,8 @@ body { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%; + display: flex; + flex-direction: column; } :root { @@ -52,7 +55,8 @@ a:hover { flex-direction: column; align-items: center; justify-content: center; - min-height: 100vh; + min-height: 100%; + flex: 1 0 auto; } #main img { display: block; @@ -95,6 +99,37 @@ a:hover { 100% { transform: scale(1) rotate(0deg) } } +#login { + display: flex; + flex-direction: row; + width: fit-content; + height: 2rem; + border: 1px solid var(--text-color-light); + border-radius: 2rem; + align-items: center; + padding: 0.25em 0.5em; + margin: 0.5rem; + gap: 0.5em; + cursor: pointer; + transition: 0.1s color, 0.1s background-color; + font-size: 1.2rem; + align-self: flex-end; +} +#login:hover { + border-color: var(--accent-color); + background-color: var(--accent-color); +} +#login .avatar { + display: block; + aspect-ratio: 1 / 1; + border-radius: 2rem; + width: auto; + height: 100%; +} +#login:not(:hover) .username.logged-out { + color: var(--text-color-light); +} + #content { max-width: 1000px; width: 100%; diff --git a/static/index.html b/views/home.handlebars similarity index 74% rename from static/index.html rename to views/home.handlebars index 5283dc7..04f9b07 100644 --- a/static/index.html +++ b/views/home.handlebars @@ -16,6 +16,15 @@ +
+ {{#if signedIn}} +
{{username}}
+ + {{else}} +
log in
+ + {{/if}} +

jillo!

From 679dd7a8328261aab48121d8b397f7064f3753e8 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 00:16:55 +0300 Subject: [PATCH 43/66] get user guilds on /profile --- src/web.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/web.ts b/src/web.ts index 7ae4a6b..66f3a7d 100644 --- a/src/web.ts +++ b/src/web.ts @@ -3,7 +3,7 @@ import { engine } from 'express-handlebars'; import * as log from './lib/log'; import { CustomItem, Session, db } from './lib/db'; import { defaultItems } from './lib/rpg/items'; -import { APIUser, CDN, Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; +import { APIPartialGuild, APIUser, CDN, Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; import got from 'got'; import uid from 'uid-safe'; import { Cookie, parse } from 'tough-cookie'; @@ -80,6 +80,19 @@ export async function getUser(session: Session | undefined) { return null; } } +export async function getGuilds(session: Session | undefined) { + if (!session) return null; + try { + return await got('https://discord.com/api/users/@me/guilds', { + headers: { + authorization: `${session.tokenType} ${session.accessToken}` + } + }).json() as APIPartialGuild[]; + } catch(err) { + log.error(err); + return null; + } +} export async function startServer(bot: Client, port: number) { const app = express(); @@ -183,9 +196,13 @@ export async function startServer(bot: Client, port: number) { if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); const user = await getUser(session); + const guilds = await getGuilds(session); //res.sendFile('profile/index.html', { root: 'static/' }); - res.json(user); + res.json({ + user, + guilds + }); }); app.use(express.static('static/')); From 6d31321c14d77e474c23692f4bfb09b16281c693 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 04:37:18 +0300 Subject: [PATCH 44/66] move some functions around --- src/web.ts | 143 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 61 deletions(-) diff --git a/src/web.ts b/src/web.ts index 66f3a7d..9acca65 100644 --- a/src/web.ts +++ b/src/web.ts @@ -11,6 +11,7 @@ import { IncomingHttpHeaders } from 'http'; const DISCORD_ENDPOINT = 'https://discord.com/api/v10'; const UID_BYTE_LENGTH = 18; +const UID_STRING_LENGTH = 24; // why? const COOKIE_KEY = 'PHPSESSID'; const COOKIE_EXPIRES = 1_000 * 60 * 60 * 24 * 365; @@ -18,13 +19,87 @@ async function getSessionString(cookieStr: string) { const cookies = cookieStr.split('; ').map(s => parse(s)!).filter(c => c !== null); const sessionCookie = cookies.find(c => c.key === COOKIE_KEY); - if (!sessionCookie) { + if (!sessionCookie || sessionCookie.value.length !== UID_STRING_LENGTH) { return await uid(UID_BYTE_LENGTH); } else { return sessionCookie.value; } } +async function setSession(sessionId: string, sessionData: Omit) { + const session = await db('sessions') + .where('id', sessionId) + .first(); + + if (session) { + await db('sessions') + .where('id', sessionId) + .update(sessionData); + } else { + await db('sessions') + .insert({id: sessionId, ...sessionData}) + .returning('*'); + } +} + +async function getToken(bot: Client, code: string) { + try { + return await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + code, + grant_type: 'authorization_code', + redirect_uri: bot.config.siteURL, + } satisfies RESTPostOAuth2AccessTokenURLEncodedData + // if you're looking to change this then you are blissfully unaware of the past + // and have learnt 0 lessons + }).json() as RESTPostOAuth2AccessTokenResult + } catch(err) { + log.error(err); + return; + } +} + +async function refreshToken(bot: Client, sessionId: string, refreshToken: string) { + let resp; + try { + resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + grant_type: 'refresh_token', + refresh_token: refreshToken, + } satisfies RESTPostOAuth2RefreshTokenURLEncodedData + }).json() as RESTPostOAuth2AccessTokenResult; + } catch(err) { + log.error(err); + return; + } + + const sessionData = { + tokenType: resp.token_type, + accessToken: resp.access_token, + refreshToken: resp.refresh_token, + expiresAt: Date.now() + resp.expires_in * 1000, + }; + + return (await db('sessions') + .where('id', sessionId) + .update(sessionData) + .returning('*'))[0]; +} + +function updateCookie(res: express.Response, sessionId: string) { + const cookie = new Cookie({ + key: COOKIE_KEY, + value: sessionId, + expires: new Date(Date.now() + COOKIE_EXPIRES), + sameSite: 'strict' + }); + res.setHeader('Set-Cookie', cookie.toString()); +} + async function getSession(bot: Client, headers: IncomingHttpHeaders) { const cookie = headers['cookie']; if (!cookie) return; @@ -39,32 +114,7 @@ async function getSession(bot: Client, headers: IncomingHttpHeaders) { if (Date.now() < session.expiresAt) return session; - let resp; - try { - resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { - form: { - client_id: bot.config.clientId, - client_secret: bot.config.clientSecret, - grant_type: 'refresh_token', - refresh_token: session.refreshToken, - } satisfies RESTPostOAuth2RefreshTokenURLEncodedData - }).json() as RESTPostOAuth2AccessTokenResult; - } catch(err) { - log.error(err); - return; - } - - const sessionData = { - tokenType: resp.token_type, - accessToken: resp.access_token, - refreshToken: resp.refresh_token, - expiresAt: Date.now() + resp.expires_in * 1000, - }; - - return (await db('sessions') - .where('id', sessionStr) - .update(sessionData) - .returning('*'))[0]; + const newSession = refreshToken(bot, session.id, session.refreshToken); } export async function getUser(session: Session | undefined) { @@ -130,48 +180,19 @@ export async function startServer(bot: Client, port: number) { if (code) { try { - const resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { - form: { - client_id: bot.config.clientId, - client_secret: bot.config.clientSecret, - code, - grant_type: 'authorization_code', - redirect_uri: bot.config.siteURL, - } satisfies RESTPostOAuth2AccessTokenURLEncodedData - // if you're looking to change this then you are blissfully unaware of the past - // and have learnt 0 lessons - }).json() as RESTPostOAuth2AccessTokenResult; + const resp = await getToken(bot, code); + if (!resp) return res.status(400).send('Invalid code provided'); const sessionId = await getSessionString(decodeURIComponent(req.headers.cookie || '')); - const session = await db('sessions') - .where('id', sessionId) - .first(); - - const sessionData = { + setSession(sessionId, { tokenType: resp.token_type, accessToken: resp.access_token, refreshToken: resp.refresh_token, expiresAt: Date.now() + resp.expires_in * 1000, - }; - - if (session) { - await db('sessions') - .where('id', sessionId) - .update(sessionData); - } else { - await db('sessions') - .insert({id: sessionId, ...sessionData} satisfies Session) - .returning('*'); - } - - const cookie = new Cookie({ - key: COOKIE_KEY, - value: sessionId, - expires: new Date(Date.now() + COOKIE_EXPIRES), - sameSite: 'strict' }); - res.setHeader('Set-Cookie', cookie.toString()); + + updateCookie(res, sessionId); return res.redirect('/profile'); } catch (err) { From cddcfee26ee10717dda918a9a1864a076a93a80c Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 04:56:18 +0300 Subject: [PATCH 45/66] move stuff into seperate files --- src/index.ts | 2 +- src/web.ts | 232 ---------------------------------------------- src/web/oauth2.ts | 116 +++++++++++++++++++++++ src/web/user.ts | 32 +++++++ src/web/web.ts | 95 +++++++++++++++++++ 5 files changed, 244 insertions(+), 233 deletions(-) delete mode 100644 src/web.ts create mode 100644 src/web/oauth2.ts create mode 100644 src/web/user.ts create mode 100644 src/web/web.ts diff --git a/src/index.ts b/src/index.ts index 03590b3..d16e1f9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -7,7 +7,7 @@ import * as log from './lib/log'; import chalk from 'chalk'; import prettyBytes from 'pretty-bytes'; import { Command } from './types/index'; -import { startServer } from './web'; +import { startServer } from './web/web'; const bot = new Client({ intents: [ diff --git a/src/web.ts b/src/web.ts deleted file mode 100644 index 9acca65..0000000 --- a/src/web.ts +++ /dev/null @@ -1,232 +0,0 @@ -import express from 'express'; -import { engine } from 'express-handlebars'; -import * as log from './lib/log'; -import { CustomItem, Session, db } from './lib/db'; -import { defaultItems } from './lib/rpg/items'; -import { APIPartialGuild, APIUser, CDN, Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; -import got from 'got'; -import uid from 'uid-safe'; -import { Cookie, parse } from 'tough-cookie'; -import { IncomingHttpHeaders } from 'http'; - -const DISCORD_ENDPOINT = 'https://discord.com/api/v10'; -const UID_BYTE_LENGTH = 18; -const UID_STRING_LENGTH = 24; // why? -const COOKIE_KEY = 'PHPSESSID'; -const COOKIE_EXPIRES = 1_000 * 60 * 60 * 24 * 365; - -async function getSessionString(cookieStr: string) { - const cookies = cookieStr.split('; ').map(s => parse(s)!).filter(c => c !== null); - const sessionCookie = cookies.find(c => c.key === COOKIE_KEY); - - if (!sessionCookie || sessionCookie.value.length !== UID_STRING_LENGTH) { - return await uid(UID_BYTE_LENGTH); - } else { - return sessionCookie.value; - } -} - -async function setSession(sessionId: string, sessionData: Omit) { - const session = await db('sessions') - .where('id', sessionId) - .first(); - - if (session) { - await db('sessions') - .where('id', sessionId) - .update(sessionData); - } else { - await db('sessions') - .insert({id: sessionId, ...sessionData}) - .returning('*'); - } -} - -async function getToken(bot: Client, code: string) { - try { - return await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { - form: { - client_id: bot.config.clientId, - client_secret: bot.config.clientSecret, - code, - grant_type: 'authorization_code', - redirect_uri: bot.config.siteURL, - } satisfies RESTPostOAuth2AccessTokenURLEncodedData - // if you're looking to change this then you are blissfully unaware of the past - // and have learnt 0 lessons - }).json() as RESTPostOAuth2AccessTokenResult - } catch(err) { - log.error(err); - return; - } -} - -async function refreshToken(bot: Client, sessionId: string, refreshToken: string) { - let resp; - try { - resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { - form: { - client_id: bot.config.clientId, - client_secret: bot.config.clientSecret, - grant_type: 'refresh_token', - refresh_token: refreshToken, - } satisfies RESTPostOAuth2RefreshTokenURLEncodedData - }).json() as RESTPostOAuth2AccessTokenResult; - } catch(err) { - log.error(err); - return; - } - - const sessionData = { - tokenType: resp.token_type, - accessToken: resp.access_token, - refreshToken: resp.refresh_token, - expiresAt: Date.now() + resp.expires_in * 1000, - }; - - return (await db('sessions') - .where('id', sessionId) - .update(sessionData) - .returning('*'))[0]; -} - -function updateCookie(res: express.Response, sessionId: string) { - const cookie = new Cookie({ - key: COOKIE_KEY, - value: sessionId, - expires: new Date(Date.now() + COOKIE_EXPIRES), - sameSite: 'strict' - }); - res.setHeader('Set-Cookie', cookie.toString()); -} - -async function getSession(bot: Client, headers: IncomingHttpHeaders) { - const cookie = headers['cookie']; - if (!cookie) return; - - const sessionStr = await getSessionString(cookie); - - const session = await db('sessions') - .where('id', sessionStr) - .first(); - - if (!session) return; - - if (Date.now() < session.expiresAt) return session; - - const newSession = refreshToken(bot, session.id, session.refreshToken); -} - -export async function getUser(session: Session | undefined) { - if (!session) return null; - try { - return await got('https://discord.com/api/users/@me', { - headers: { - authorization: `${session.tokenType} ${session.accessToken}` - } - }).json() as APIUser; - } catch(err) { - log.error(err); - return null; - } -} -export async function getGuilds(session: Session | undefined) { - if (!session) return null; - try { - return await got('https://discord.com/api/users/@me/guilds', { - headers: { - authorization: `${session.tokenType} ${session.accessToken}` - } - }).json() as APIPartialGuild[]; - } catch(err) { - log.error(err); - return null; - } -} - -export async function startServer(bot: Client, port: number) { - const app = express(); - const cdn = new CDN(); - - app.engine('handlebars', engine()); - app.set('view engine', 'handlebars'); - app.set('views', './views'); - - app.get('/api/items', async (req, res) => { - const guildID = req.query.guild; - - let customItems : Partial[]; - if (guildID) { - customItems = await db('customItems') - .select('emoji', 'name', 'id', 'description') - .where('guild', guildID) - .limit(25); - } else { - customItems = []; - } - - res.json([...defaultItems, ...customItems]); - }); - - app.get('/api/status', async (_, res) => { - res.json({ - guilds: bot.guilds.cache.size, - uptime: bot.uptime - }); - }); - - app.get('/', async (req, res) => { - const code = req.query.code as string; - - if (code) { - try { - const resp = await getToken(bot, code); - if (!resp) return res.status(400).send('Invalid code provided'); - - const sessionId = await getSessionString(decodeURIComponent(req.headers.cookie || '')); - - setSession(sessionId, { - tokenType: resp.token_type, - accessToken: resp.access_token, - refreshToken: resp.refresh_token, - expiresAt: Date.now() + resp.expires_in * 1000, - }); - - updateCookie(res, sessionId); - - return res.redirect('/profile'); - } catch (err) { - log.error(err); - return res.status(500); - } - } - - const session = await getSession(bot, req.headers); - const user = await getUser(session); - - res.render('home', { - signedIn: session !== undefined, - username: user?.global_name, - avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : null, - layout: false, - }); - }); - - app.get('/profile', async (req, res) => { - const session = await getSession(bot, req.headers); - if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); - - const user = await getUser(session); - const guilds = await getGuilds(session); - - //res.sendFile('profile/index.html', { root: 'static/' }); - res.json({ - user, - guilds - }); - }); - - app.use(express.static('static/')); - - app.listen(port, () => log.info(`web interface listening on ${port}`)); -} \ No newline at end of file diff --git a/src/web/oauth2.ts b/src/web/oauth2.ts new file mode 100644 index 0000000..a5782de --- /dev/null +++ b/src/web/oauth2.ts @@ -0,0 +1,116 @@ +import type { Response } from 'express'; +import type { IncomingHttpHeaders } from 'http'; +import * as log from '../lib/log'; +import { Cookie, parse as parseCookie } from 'tough-cookie'; +import uid from 'uid-safe'; +import { Client, RESTPostOAuth2AccessTokenResult, RESTPostOAuth2AccessTokenURLEncodedData, RESTPostOAuth2RefreshTokenURLEncodedData, Routes } from 'discord.js'; +import got from 'got'; +import { Session, db } from '../lib/db'; + +export const DISCORD_ENDPOINT = 'https://discord.com/api/v10'; +const UID_BYTE_LENGTH = 18; +const UID_STRING_LENGTH = 24; // why? +const COOKIE_KEY = 'PHPSESSID'; +const COOKIE_EXPIRES = 1_000 * 60 * 60 * 24 * 365; + +export async function getSessionString(cookieStr: string) { + const cookies = cookieStr.split('; ').map(s => parseCookie(s)!).filter(c => c !== null); + const sessionCookie = cookies.find(c => c.key === COOKIE_KEY); + + if (!sessionCookie || sessionCookie.value.length !== UID_STRING_LENGTH) { + return await uid(UID_BYTE_LENGTH); + } else { + return sessionCookie.value; + } +} + +export function updateCookie(res: Response, sessionId: string) { + const cookie = new Cookie({ + key: COOKIE_KEY, + value: sessionId, + expires: new Date(Date.now() + COOKIE_EXPIRES), + sameSite: 'strict' + }); + res.setHeader('Set-Cookie', cookie.toString()); +} + +export async function getToken(bot: Client, code: string) { + try { + return await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + code, + grant_type: 'authorization_code', + redirect_uri: bot.config.siteURL, + } satisfies RESTPostOAuth2AccessTokenURLEncodedData + // if you're looking to change this then you are blissfully unaware of the past + // and have learnt 0 lessons + }).json() as RESTPostOAuth2AccessTokenResult + } catch(err) { + log.error(err); + return; + } +} + +async function refreshToken(bot: Client, sessionId: string, refreshToken: string) { + let resp; + try { + resp = await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + form: { + client_id: bot.config.clientId, + client_secret: bot.config.clientSecret, + grant_type: 'refresh_token', + refresh_token: refreshToken, + } satisfies RESTPostOAuth2RefreshTokenURLEncodedData + }).json() as RESTPostOAuth2AccessTokenResult; + } catch(err) { + log.error(err); + return; + } + + const sessionData = { + tokenType: resp.token_type, + accessToken: resp.access_token, + refreshToken: resp.refresh_token, + expiresAt: Date.now() + resp.expires_in * 1000, + }; + + return (await db('sessions') + .where('id', sessionId) + .update(sessionData) + .returning('*'))[0]; +} + +export async function getSession(bot: Client, headers: IncomingHttpHeaders) { + const cookie = headers['cookie']; + if (!cookie) return; + + const sessionStr = await getSessionString(cookie); + + const session = await db('sessions') + .where('id', sessionStr) + .first(); + + if (!session) return; + + if (Date.now() < session.expiresAt) return session; + + const newSession = refreshToken(bot, session.id, session.refreshToken); +} + +export async function setSession(sessionId: string, sessionData: Omit) { + const session = await db('sessions') + .where('id', sessionId) + .first(); + + if (session) { + await db('sessions') + .where('id', sessionId) + .update(sessionData); + } else { + await db('sessions') + .insert({id: sessionId, ...sessionData}) + .returning('*'); + } +} \ No newline at end of file diff --git a/src/web/user.ts b/src/web/user.ts new file mode 100644 index 0000000..ee6989b --- /dev/null +++ b/src/web/user.ts @@ -0,0 +1,32 @@ +import { Session, db } from '../lib/db'; +import * as log from '../lib/log'; +import got from 'got'; +import { APIPartialGuild, APIUser, Routes } from 'discord.js'; +import { DISCORD_ENDPOINT } from './oauth2'; + +export async function getUser(session: Session | undefined) { + if (!session) return null; + try { + return await got(DISCORD_ENDPOINT + Routes.user(), { + headers: { + authorization: `${session.tokenType} ${session.accessToken}` + } + }).json() as APIUser; + } catch(err) { + log.error(err); + return null; + } +} +export async function getGuilds(session: Session | undefined) { + if (!session) return null; + try { + return await got(DISCORD_ENDPOINT + Routes.userGuilds(), { + headers: { + authorization: `${session.tokenType} ${session.accessToken}` + } + }).json() as APIPartialGuild[]; + } catch(err) { + log.error(err); + return null; + } +} \ No newline at end of file diff --git a/src/web/web.ts b/src/web/web.ts new file mode 100644 index 0000000..2583f11 --- /dev/null +++ b/src/web/web.ts @@ -0,0 +1,95 @@ +import express from 'express'; +import { engine } from 'express-handlebars'; +import * as log from '../lib/log'; +import { CustomItem, Session, db } from '../lib/db'; +import { defaultItems } from '../lib/rpg/items'; +import { Client, CDN } from 'discord.js'; +import { getToken, getSessionString, getSession, setSession, updateCookie } from './oauth2'; +import { getUser, getGuilds } from './user'; + +export async function startServer(bot: Client, port: number) { + const app = express(); + const cdn = new CDN(); + + app.engine('handlebars', engine()); + app.set('view engine', 'handlebars'); + app.set('views', './views'); + + app.get('/api/items', async (req, res) => { + const guildID = req.query.guild; + + let customItems : Partial[]; + if (guildID) { + customItems = await db('customItems') + .select('emoji', 'name', 'id', 'description') + .where('guild', guildID) + .limit(25); + } else { + customItems = []; + } + + res.json([...defaultItems, ...customItems]); + }); + + app.get('/api/status', async (_, res) => { + res.json({ + guilds: bot.guilds.cache.size, + uptime: bot.uptime + }); + }); + + app.get('/', async (req, res) => { + const code = req.query.code as string; + + if (code) { + try { + const resp = await getToken(bot, code); + if (!resp) return res.status(400).send('Invalid code provided'); + + const sessionId = await getSessionString(decodeURIComponent(req.headers.cookie || '')); + + setSession(sessionId, { + tokenType: resp.token_type, + accessToken: resp.access_token, + refreshToken: resp.refresh_token, + expiresAt: Date.now() + resp.expires_in * 1000, + }); + + updateCookie(res, sessionId); + + return res.redirect('/profile'); + } catch (err) { + log.error(err); + return res.status(500); + } + } + + const session = await getSession(bot, req.headers); + const user = await getUser(session); + + res.render('home', { + signedIn: session !== undefined, + username: user?.global_name, + avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : null, + layout: false, + }); + }); + + app.get('/profile', async (req, res) => { + const session = await getSession(bot, req.headers); + if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); + + const user = await getUser(session); + const guilds = await getGuilds(session); + + //res.sendFile('profile/index.html', { root: 'static/' }); + res.json({ + user, + guilds + }); + }); + + app.use(express.static('static/')); + + app.listen(port, () => log.info(`web interface listening on ${port}`)); +} \ No newline at end of file From fcc7956b4dae65e2105326eedf54b07e8d166ef8 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 04:58:22 +0300 Subject: [PATCH 46/66] slightly improve home view --- views/home.handlebars | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/views/home.handlebars b/views/home.handlebars index 04f9b07..b25b020 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -19,9 +19,12 @@
{{#if signedIn}}
{{username}}
- {{else}}
log in
+ {{/if}} + {{#if avatar}} + + {{else}} {{/if}}
From ef08ef020bbbc17967f0462076f1f1ccd55fcd34 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 05:06:57 +0300 Subject: [PATCH 47/66] fix migration for empty counters table --- migrations/20231113151937_giveCountersIds.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/20231113151937_giveCountersIds.js b/migrations/20231113151937_giveCountersIds.js index a1424e2..6161b0e 100644 --- a/migrations/20231113151937_giveCountersIds.js +++ b/migrations/20231113151937_giveCountersIds.js @@ -19,12 +19,12 @@ exports.up = async function(knex) { // awfulllllllllllllll const rows = await knex('counters').select('*'); - await knex('counters_').insert(rows); + if (rows.length > 0) await knex('counters_').insert(rows); await knex.schema .dropTable('counters') .renameTable('counters_', 'counters'); - + await knex.schema .alterTable('counterUserLink', table => { table.integer('id').references('id').inTable('counters'); From 4ab739d681c1f724f1da52306264579cd2b7cd94 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 07:30:14 +0300 Subject: [PATCH 48/66] simple profile screen --- src/web/web.ts | 48 ++++++++++++++++++++++++---- static/style.css | 59 ++++++++++++++++++++++++++++++++++- views/home.handlebars | 6 +--- views/layouts/main.handlebars | 34 ++++++++++++++++++++ views/profile.handlebars | 27 ++++++++++++++++ 5 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 views/layouts/main.handlebars create mode 100644 views/profile.handlebars diff --git a/src/web/web.ts b/src/web/web.ts index 2583f11..99dcfab 100644 --- a/src/web/web.ts +++ b/src/web/web.ts @@ -1,17 +1,47 @@ import express from 'express'; -import { engine } from 'express-handlebars'; +import { create } from 'express-handlebars'; import * as log from '../lib/log'; -import { CustomItem, Session, db } from '../lib/db'; +import { CustomItem, Counter, Session, CustomCraftingRecipe, db } from '../lib/db'; import { defaultItems } from '../lib/rpg/items'; import { Client, CDN } from 'discord.js'; import { getToken, getSessionString, getSession, setSession, updateCookie } from './oauth2'; import { getUser, getGuilds } from './user'; +async function getGuildInfo(bot: Client, id: string) { + const guild = await bot.guilds.cache.get(id); + if (!guild) return; + + const items = await db('customItems') + .where('guild', guild) + .count({count: '*'}); + + const counters = await db('counters') + .where('guild', guild) + .count({count: '*'}); + + const recipes = await db('customCraftingRecipes') + .where('guild', guild) + .count({count: '*'}); + + return { + items: items[0].count as number, + counters: counters[0].count as number, + recipes: recipes[0].count as number, + } +} + export async function startServer(bot: Client, port: number) { const app = express(); const cdn = new CDN(); - app.engine('handlebars', engine()); + const hbs = create({ + helpers: { + avatar: (id: string, hash: string) => (id && hash) ? cdn.avatar(id, hash, { size: 128 }) : '/assets/avatar.png', + icon: (id: string, hash: string) => (id && hash) ? cdn.icon(id, hash, { size: 128, forceStatic: true }) : '/assets/avatar.png', + } + }) + + app.engine('handlebars', hbs.engine); app.set('view engine', 'handlebars'); app.set('views', './views'); @@ -70,7 +100,7 @@ export async function startServer(bot: Client, port: number) { res.render('home', { signedIn: session !== undefined, username: user?.global_name, - avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : null, + avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : '/assets/avatar.png', layout: false, }); }); @@ -80,12 +110,18 @@ export async function startServer(bot: Client, port: number) { if (!session) return res.redirect(`https://discord.com/api/oauth2/authorize?client_id=${bot.config.clientId}&redirect_uri=${encodeURIComponent(bot.config.siteURL)}&response_type=code&scope=identify%20guilds`); const user = await getUser(session); + if (!user) return; const guilds = await getGuilds(session); + if (!guilds) return; //res.sendFile('profile/index.html', { root: 'static/' }); - res.json({ + res.render('profile', { user, - guilds + guilds: await Promise.all( + guilds.map(async guild => + ({...guild, jillo: await getGuildInfo(bot, guild.id)}) + ) + ), }); }); diff --git a/static/style.css b/static/style.css index 4eb5244..4b140e3 100644 --- a/static/style.css +++ b/static/style.css @@ -21,6 +21,7 @@ body { -webkit-text-size-adjust: 100%; display: flex; flex-direction: column; + justify-content: flex-start; } :root { @@ -133,7 +134,7 @@ a:hover { #content { max-width: 1000px; width: 100%; - margin: auto; + margin: 0 auto; margin-bottom: 6rem; } @@ -293,4 +294,60 @@ pre { .note { font-style: italic; color: var(--text-color-light); +} + +.user { + display: flex; + flex-direction: row; + height: 2rem; + padding: 0.5em; + align-items: center; + width: fit-content; + margin: 0 auto; +} +.user .avatar { + display: block; + aspect-ratio: 1 / 1; + border-radius: 2rem; + width: auto; + height: 100%; + margin-right: 0.5em; +} + +.guilds { + display: flex; + flex-direction: column; + align-items: center; +} +.guild { + order: 0; + display: flex; + width: 600px; + max-width: 100%; + height: 3rem; + padding: 0.5rem; + gap: 0.5rem; + margin: 0.5rem; + background-color: var(--background-color-dark); +} +.guild.unavailable { + order: 1; +} +.guild.unavailable .icon { + filter: grayscale(100%); +} +.guild .icon { + flex: 0 0 auto; + display: block; + aspect-ratio: 1 / 1; + border-radius: 2rem; + width: auto; + height: 100%; +} +.guild .right { + display: flex; + flex-direction: column; +} +.guild .info { + color: var(--text-color-light); } \ No newline at end of file diff --git a/views/home.handlebars b/views/home.handlebars index b25b020..7c53449 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -22,11 +22,7 @@ {{else}}
log in
{{/if}} - {{#if avatar}} - - {{else}} - - {{/if}} +
diff --git a/views/layouts/main.handlebars b/views/layouts/main.handlebars new file mode 100644 index 0000000..bc10d4a --- /dev/null +++ b/views/layouts/main.handlebars @@ -0,0 +1,34 @@ + + + + + + jillo + + + + + + + + + + +
+
+
+
+ jillo +
+ +
+ + {{{body}}} +
+ + \ No newline at end of file diff --git a/views/profile.handlebars b/views/profile.handlebars new file mode 100644 index 0000000..52b8ba8 --- /dev/null +++ b/views/profile.handlebars @@ -0,0 +1,27 @@ +
+ +
Logged in as {{user.global_name}}
+
+ +
+

Guilds

+ {{#each guilds}} + {{#if jillo}} +
+ +
+
{{name}}
+
{{jillo.counters}} counters · {{jillo.items}} items · {{jillo.recipes}} recipes
+
+
+ {{else}} +
+ +
+
{{name}}
+
Jillo is not in this server
+
+
+ {{/if}} + {{/each}} +
\ No newline at end of file From 249390cf5d4f1a14b0612e2b612e8846c3e25d87 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Mon, 20 Nov 2023 18:20:12 +0300 Subject: [PATCH 49/66] fixin stuff i didn't catch on lapop --- src/web/oauth2.ts | 5 +++-- src/web/user.ts | 2 +- src/web/web.ts | 15 +++++++-------- views/home.handlebars | 4 ++-- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/web/oauth2.ts b/src/web/oauth2.ts index a5782de..45ebcc3 100644 --- a/src/web/oauth2.ts +++ b/src/web/oauth2.ts @@ -36,7 +36,7 @@ export function updateCookie(res: Response, sessionId: string) { export async function getToken(bot: Client, code: string) { try { - return await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { + return await got.post(DISCORD_ENDPOINT + Routes.oauth2TokenExchange(), { form: { client_id: bot.config.clientId, client_secret: bot.config.clientSecret, @@ -46,7 +46,7 @@ export async function getToken(bot: Client, code: string) { } satisfies RESTPostOAuth2AccessTokenURLEncodedData // if you're looking to change this then you are blissfully unaware of the past // and have learnt 0 lessons - }).json() as RESTPostOAuth2AccessTokenResult + }).json() as RESTPostOAuth2AccessTokenResult; } catch(err) { log.error(err); return; @@ -97,6 +97,7 @@ export async function getSession(bot: Client, headers: IncomingHttpHeaders) { if (Date.now() < session.expiresAt) return session; const newSession = refreshToken(bot, session.id, session.refreshToken); + return newSession; } export async function setSession(sessionId: string, sessionData: Omit) { diff --git a/src/web/user.ts b/src/web/user.ts index ee6989b..9c286ac 100644 --- a/src/web/user.ts +++ b/src/web/user.ts @@ -1,4 +1,4 @@ -import { Session, db } from '../lib/db'; +import { Session } from '../lib/db'; import * as log from '../lib/log'; import got from 'got'; import { APIPartialGuild, APIUser, Routes } from 'discord.js'; diff --git a/src/web/web.ts b/src/web/web.ts index 99dcfab..ba0ec0e 100644 --- a/src/web/web.ts +++ b/src/web/web.ts @@ -1,7 +1,7 @@ import express from 'express'; import { create } from 'express-handlebars'; import * as log from '../lib/log'; -import { CustomItem, Counter, Session, CustomCraftingRecipe, db } from '../lib/db'; +import { CustomItem, Counter, CustomCraftingRecipe, db } from '../lib/db'; import { defaultItems } from '../lib/rpg/items'; import { Client, CDN } from 'discord.js'; import { getToken, getSessionString, getSession, setSession, updateCookie } from './oauth2'; @@ -12,22 +12,22 @@ async function getGuildInfo(bot: Client, id: string) { if (!guild) return; const items = await db('customItems') - .where('guild', guild) + .where('guild', guild.id) .count({count: '*'}); const counters = await db('counters') - .where('guild', guild) + .where('guild', guild.id) .count({count: '*'}); const recipes = await db('customCraftingRecipes') - .where('guild', guild) + .where('guild', guild.id) .count({count: '*'}); return { items: items[0].count as number, counters: counters[0].count as number, recipes: recipes[0].count as number, - } + }; } export async function startServer(bot: Client, port: number) { @@ -39,7 +39,7 @@ export async function startServer(bot: Client, port: number) { avatar: (id: string, hash: string) => (id && hash) ? cdn.avatar(id, hash, { size: 128 }) : '/assets/avatar.png', icon: (id: string, hash: string) => (id && hash) ? cdn.icon(id, hash, { size: 128, forceStatic: true }) : '/assets/avatar.png', } - }) + }); app.engine('handlebars', hbs.engine); app.set('view engine', 'handlebars'); @@ -99,8 +99,7 @@ export async function startServer(bot: Client, port: number) { res.render('home', { signedIn: session !== undefined, - username: user?.global_name, - avatar: user?.avatar ? cdn.avatar(user.id, user.avatar, { size: 128 }) : '/assets/avatar.png', + user: user, layout: false, }); }); diff --git a/views/home.handlebars b/views/home.handlebars index 7c53449..5b1e91c 100644 --- a/views/home.handlebars +++ b/views/home.handlebars @@ -18,11 +18,11 @@
{{#if signedIn}} -
{{username}}
+
{{user.global_name}}
{{else}}
log in
{{/if}} - +
From 5be9dbfe215c9ac1d486d232b28d3e4a54d2ccbf Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 11:40:44 +0300 Subject: [PATCH 50/66] health groundwork --- migrations/20231121082135_initHealth.js | 19 +++++++++++++++++++ src/lib/db.ts | 3 +++ src/lib/rpg/items.ts | 3 ++- src/lib/rpg/pvp.ts | 15 +++++++++++++++ 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 migrations/20231121082135_initHealth.js create mode 100644 src/lib/rpg/pvp.ts diff --git a/migrations/20231121082135_initHealth.js b/migrations/20231121082135_initHealth.js new file mode 100644 index 0000000..876d373 --- /dev/null +++ b/migrations/20231121082135_initHealth.js @@ -0,0 +1,19 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('initHealth', table => + table.string('user').notNullable().unique() + ); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('initHealth'); +}; diff --git a/src/lib/db.ts b/src/lib/db.ts index db1f2da..7616aa7 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -86,4 +86,7 @@ export interface Session { accessToken: string, refreshToken: string, expiresAt: number, +} +export interface InitHealth { + user: string, } \ No newline at end of file diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index d2efe1a..4fbd118 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,5 +1,6 @@ import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; +import { MAX_HEALTH } from './pvp'; export type DefaultItem = Omit; // uses negative IDs export type Item = DefaultItem | CustomItem; @@ -117,7 +118,7 @@ export const defaultItems: DefaultItem[] = [ description: 'ow', emoji: '🩸', type: 'plain', - maxStack: 1024, + maxStack: MAX_HEALTH, untradable: false }, { diff --git a/src/lib/rpg/pvp.ts b/src/lib/rpg/pvp.ts new file mode 100644 index 0000000..87e59f6 --- /dev/null +++ b/src/lib/rpg/pvp.ts @@ -0,0 +1,15 @@ +import { InitHealth, db } from '../db'; +import { DefaultItems, getDefaultItem, giveItem } from './items'; + +export const MAX_HEALTH = 100; + +export async function initHealth(user: string) { + const isInitialized = await db('initHealth') + .where('user', user) + .first(); + + if (!isInitialized) { + giveItem(user, getDefaultItem(DefaultItems.BLOOD), MAX_HEALTH); + await db('initHealth').insert({ user }); + } +} \ No newline at end of file From 783af8652cd4cf564a954113b292cbc94b4e9f54 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 22:17:15 +0300 Subject: [PATCH 51/66] i think weapons working real ? --- src/commands/attack.ts | 47 +++++++++++++++++++++++++++++++++++++++ src/commands/craft.ts | 3 +++ src/commands/emotedump.ts | 2 +- src/commands/inventory.ts | 3 +++ src/commands/put.ts | 3 +++ src/commands/take.ts | 3 +++ src/lib/rpg/items.ts | 21 +++++++++++------ src/lib/rpg/pvp.ts | 42 ++++++++++++++++++++++++++++++---- 8 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 src/commands/attack.ts diff --git a/src/commands/attack.ts b/src/commands/attack.ts new file mode 100644 index 0000000..b429ea4 --- /dev/null +++ b/src/commands/attack.ts @@ -0,0 +1,47 @@ +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { weaponAutocomplete, getItem, getItemQuantity, formatItems } from '../lib/rpg/items'; +import { Command } from '../types/index'; +import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID } from '../lib/rpg/pvp'; + +export default { + data: new SlashCommandBuilder() + .setName('attack') + .setDescription('Attack someone using a weapon you have') + .addStringOption(option => + option + .setName('weapon') + .setAutocomplete(true) + .setDescription('The weapon to use') + .setRequired(true) + ) + .addUserOption(option => + option + .setName('user') + .setRequired(true) + .setDescription('Who to attack with the weapon') + ) + .setDMPermission(false), + + execute: async (interaction: CommandInteraction) => { + if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; + await initHealth(member.id); + const weaponID = parseInt(interaction.options.getString('weapon', true)); + const user = interaction.options.getUser('user', true); + + await interaction.deferReply({ephemeral: true}); + + const weapon = await getItem(weaponID); + if (!weapon) return interaction.followUp('No such item exists!'); + if (weapon.type !== 'weapon') return interaction.followUp('That is not a weapon!'); + + const dmg = weapon.maxStack; + await dealDamage(user.id, dmg); + const newHealth = await getItemQuantity(user.id, BLOOD_ID); + + await interaction.followUp(`You hit ${user} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.`); + }, + + autocomplete: weaponAutocomplete, +} satisfies Command; \ No newline at end of file diff --git a/src/commands/craft.ts b/src/commands/craft.ts index 5577302..ae7b910 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -4,6 +4,7 @@ import { getStation, canUseStation, craftingStations, verb, CraftingStation } fr import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; +import { initHealth } from '../lib/rpg/pvp'; export default { data: new SlashCommandBuilder() @@ -30,6 +31,8 @@ export default { const member = interaction.member! as GuildMember; + await initHealth(member.id); + const recipeID = parseInt(interaction.options.getString('recipe', true)); await interaction.deferReply({ephemeral: true}); diff --git a/src/commands/emotedump.ts b/src/commands/emotedump.ts index 7910e1a..11c4f8f 100644 --- a/src/commands/emotedump.ts +++ b/src/commands/emotedump.ts @@ -1,4 +1,4 @@ -import { GuildMember, EmbedBuilder, SlashCommandBuilder, CommandInteraction } from 'discord.js'; +import { EmbedBuilder, SlashCommandBuilder, CommandInteraction } from 'discord.js'; import { writeTmpFile } from '../lib/util'; import { Command } from '../types/index'; diff --git a/src/commands/inventory.ts b/src/commands/inventory.ts index b8cf6b4..f50116e 100644 --- a/src/commands/inventory.ts +++ b/src/commands/inventory.ts @@ -1,6 +1,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { ItemInventory, db } from '../lib/db'; import { formatItems, getItem } from '../lib/rpg/items'; +import { initHealth } from '../lib/rpg/pvp'; import { Command } from '../types/index'; export default { @@ -14,6 +15,8 @@ export default { const member = interaction.member! as GuildMember; + await initHealth(member.id); + await interaction.deferReply({ephemeral: true}); const itemsList = await db('itemInventories') diff --git a/src/commands/put.ts b/src/commands/put.ts index 5fe441b..0e02a3f 100644 --- a/src/commands/put.ts +++ b/src/commands/put.ts @@ -1,6 +1,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; +import { initHealth } from '../lib/rpg/pvp'; export default { data: new SlashCommandBuilder() @@ -27,6 +28,8 @@ export default { const member = interaction.member! as GuildMember; + await initHealth(member.id); + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; diff --git a/src/commands/take.ts b/src/commands/take.ts index 37e3c27..31c22ce 100644 --- a/src/commands/take.ts +++ b/src/commands/take.ts @@ -1,5 +1,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; +import { initHealth } from '../lib/rpg/pvp'; import { Command } from '../types/index'; export default { @@ -27,6 +28,8 @@ export default { const member = interaction.member! as GuildMember; + await initHealth(member.id); + const amount = Math.trunc(interaction.options.getInteger('amount') || 1); const type = interaction.options.getString('type')!; diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 4fbd118..c48cb2d 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,6 +1,5 @@ import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; -import { MAX_HEALTH } from './pvp'; export type DefaultItem = Omit; // uses negative IDs export type Item = DefaultItem | CustomItem; @@ -118,7 +117,7 @@ export const defaultItems: DefaultItem[] = [ description: 'ow', emoji: '🩸', type: 'plain', - maxStack: MAX_HEALTH, + maxStack: 50, untradable: false }, { @@ -374,18 +373,23 @@ export function formatItemsArray(items: Items[], disableBold = false) { return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } -function createItemAutocomplete(onlyCustom: boolean) { +function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null) { return async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); - const customItems = await db('customItems') + const itemQuery = db('customItems') .select('emoji', 'name', 'id') // @ts-expect-error this LITERALLY works .whereLike(db.raw('UPPER(name)'), `%${focused.toUpperCase()}%`) .where('guild', interaction.guildId!) .limit(25); + + if (filterType) itemQuery.where('type', filterType); + + const customItems = await itemQuery; - const foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); + let foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); + if (filterType) foundDefaultItems = foundDefaultItems.filter(i => i.type === filterType); let items; if (onlyCustom) { @@ -400,5 +404,8 @@ function createItemAutocomplete(onlyCustom: boolean) { }; } -export const itemAutocomplete = createItemAutocomplete(false); -export const customItemAutocomplete = createItemAutocomplete(true); \ No newline at end of file +export const itemAutocomplete = createItemAutocomplete(false, null); +export const customItemAutocomplete = createItemAutocomplete(true, null); +export const plainAutocomplete = createItemAutocomplete(false, 'plain'); +export const weaponAutocomplete = createItemAutocomplete(false, 'weapon'); +export const consumableAutocomplete = createItemAutocomplete(false, 'consumable'); \ No newline at end of file diff --git a/src/lib/rpg/pvp.ts b/src/lib/rpg/pvp.ts index 87e59f6..5bd5161 100644 --- a/src/lib/rpg/pvp.ts +++ b/src/lib/rpg/pvp.ts @@ -1,7 +1,11 @@ -import { InitHealth, db } from '../db'; -import { DefaultItems, getDefaultItem, giveItem } from './items'; +import { InitHealth, ItemInventory, db } from '../db'; +import { DefaultItems, getDefaultItem, giveItem, getItemQuantity, formatItems } from './items'; +import { Client } from 'discord.js'; -export const MAX_HEALTH = 100; +export const BLOOD_ID = DefaultItems.BLOOD; +export const BLOOD_ITEM = getDefaultItem(BLOOD_ID); +export const MAX_HEALTH = BLOOD_ITEM.maxStack; +const BLOOD_GAIN_PER_HOUR = 5; export async function initHealth(user: string) { const isInitialized = await db('initHealth') @@ -9,7 +13,37 @@ export async function initHealth(user: string) { .first(); if (!isInitialized) { - giveItem(user, getDefaultItem(DefaultItems.BLOOD), MAX_HEALTH); + giveItem(user, BLOOD_ITEM, MAX_HEALTH); await db('initHealth').insert({ user }); } +} + +export async function getHealth(user: string) { + return await getItemQuantity(user, BLOOD_ID); +} + +export async function dealDamage(user: string, dmg: number) { + return await giveItem(user, BLOOD_ITEM, -dmg); +} + +async function healthCron(bot: Client) { + await db('itemInventories') + .where('item', BLOOD_ID) + .update({ + quantity: db.raw('MIN(quantity + ?, ?)', [BLOOD_GAIN_PER_HOUR, MAX_HEALTH]) + }); + + const debtedUsers = await db('itemInventories') + .select('user', 'quantity') + .where('quantity', '<', '0'); + + for (const debted of debtedUsers) { + const user = await bot.users.fetch(debted.user); + if (!user) continue; + await user.send(`${formatItems(BLOOD_ITEM, debted.quantity)} You are bleeding out to death`); + } +} + +export function init(bot: Client) { + setInterval(() => healthCron(bot), 1_000 * 60 * 60); } \ No newline at end of file From b8620285243ee04467705a564f696346e01fa9b5 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 22:34:32 +0300 Subject: [PATCH 52/66] fix dmg stuff --- src/index.ts | 3 +++ src/lib/rpg/items.ts | 2 +- src/lib/rpg/pvp.ts | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/index.ts b/src/index.ts index d16e1f9..30dc29f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ import chalk from 'chalk'; import prettyBytes from 'pretty-bytes'; import { Command } from './types/index'; import { startServer } from './web/web'; +import { init as initPVP } from './lib/rpg/pvp'; const bot = new Client({ intents: [ @@ -40,6 +41,8 @@ async function init() { log.error(`${chalk.bold('emergency mode could not be established.')} shutting down.`); process.exit(1); } + + initPVP(bot); } bot.on(Events.ClientReady, async () => { diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index c48cb2d..43f9762 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -324,7 +324,7 @@ export async function giveItem(user: string, item: Item, quantity = 1): Promise< let inv; if (storedItem) { - if (storedItem.quantity + quantity === 0) { + if (storedItem.quantity + quantity === 0 && item.id !== DefaultItems.BLOOD) { // let blood show as 0x await db('itemInventories') .delete() .where('user', user) diff --git a/src/lib/rpg/pvp.ts b/src/lib/rpg/pvp.ts index 5bd5161..f7a047b 100644 --- a/src/lib/rpg/pvp.ts +++ b/src/lib/rpg/pvp.ts @@ -2,7 +2,7 @@ import { InitHealth, ItemInventory, db } from '../db'; import { DefaultItems, getDefaultItem, giveItem, getItemQuantity, formatItems } from './items'; import { Client } from 'discord.js'; -export const BLOOD_ID = DefaultItems.BLOOD; +export const BLOOD_ID = -DefaultItems.BLOOD; export const BLOOD_ITEM = getDefaultItem(BLOOD_ID); export const MAX_HEALTH = BLOOD_ITEM.maxStack; const BLOOD_GAIN_PER_HOUR = 5; @@ -19,10 +19,12 @@ export async function initHealth(user: string) { } export async function getHealth(user: string) { + await initHealth(user); return await getItemQuantity(user, BLOOD_ID); } export async function dealDamage(user: string, dmg: number) { + await initHealth(user); return await giveItem(user, BLOOD_ITEM, -dmg); } @@ -45,5 +47,6 @@ async function healthCron(bot: Client) { } export function init(bot: Client) { + healthCron(bot); setInterval(() => healthCron(bot), 1_000 * 60 * 60); } \ No newline at end of file From f44f79a955ec687d2ad3744b9b73b23d269d2793 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 23:28:32 +0300 Subject: [PATCH 53/66] pvp cooldowns --- migrations/20231121194328_invincible.js | 20 +++++++++++++++ src/commands/attack.ts | 9 ++++--- src/commands/craft.ts | 3 ++- src/lib/db.ts | 4 +++ src/lib/rpg/counter.ts | 2 ++ src/lib/rpg/pvp.ts | 33 ++++++++++++++++++++++++- 6 files changed, 66 insertions(+), 5 deletions(-) create mode 100644 migrations/20231121194328_invincible.js diff --git a/migrations/20231121194328_invincible.js b/migrations/20231121194328_invincible.js new file mode 100644 index 0000000..a672087 --- /dev/null +++ b/migrations/20231121194328_invincible.js @@ -0,0 +1,20 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('invincibleUsers', table => { + table.string('user').notNullable().unique(); + table.timestamp('since').notNullable(); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.down = function(knex) { + return knex.schema + .dropTable('invincibleUsers'); +}; diff --git a/src/commands/attack.ts b/src/commands/attack.ts index b429ea4..0dad3a4 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -1,7 +1,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { weaponAutocomplete, getItem, getItemQuantity, formatItems } from '../lib/rpg/items'; +import { weaponAutocomplete, getItem, getItemQuantity, formatItems, formatItem } from '../lib/rpg/items'; import { Command } from '../types/index'; -import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID } from '../lib/rpg/pvp'; +import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp'; export default { data: new SlashCommandBuilder() @@ -35,12 +35,15 @@ export default { const weapon = await getItem(weaponID); if (!weapon) return interaction.followUp('No such item exists!'); if (weapon.type !== 'weapon') return interaction.followUp('That is not a weapon!'); + const invinTimer = await getInvincibleMs(user.id); + if (invinTimer > 0) return interaction.followUp(`You can only attack this user (or if they perform an action first)!`); const dmg = weapon.maxStack; await dealDamage(user.id, dmg); const newHealth = await getItemQuantity(user.id, BLOOD_ID); - await interaction.followUp(`You hit ${user} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.`); + if (user.id !== member.id) await resetInvincible(member.id); + await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); }, autocomplete: weaponAutocomplete, diff --git a/src/commands/craft.ts b/src/commands/craft.ts index ae7b910..a0d3d4f 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -4,7 +4,7 @@ import { getStation, canUseStation, craftingStations, verb, CraftingStation } fr import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, formatItemsArray } from '../lib/rpg/items'; import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; -import { initHealth } from '../lib/rpg/pvp'; +import { initHealth, resetInvincible } from '../lib/rpg/pvp'; export default { data: new SlashCommandBuilder() @@ -98,6 +98,7 @@ export default { nextUsableAt = Date.now() + station.cooldown * 1000; } + await resetInvincible(member.id); return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${outputs.length === 1 ? `\n_${outputs[0].item.description}_` : ''}${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, diff --git a/src/lib/db.ts b/src/lib/db.ts index 7616aa7..812994c 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -89,4 +89,8 @@ export interface Session { } export interface InitHealth { user: string, +} +export interface InvincibleUser { + user: string, + since: number, } \ No newline at end of file diff --git a/src/lib/rpg/counter.ts b/src/lib/rpg/counter.ts index c9e0c39..9ec814f 100644 --- a/src/lib/rpg/counter.ts +++ b/src/lib/rpg/counter.ts @@ -2,6 +2,7 @@ import { Client, CommandInteraction, GuildMember, EmbedBuilder, TextChannel, Aut import { getSign } from '../util'; import { Counter, CounterConfiguration, CounterUserLink, db } from '../db'; import { formatItems, getItem, getItemQuantity, getMaxStack, giveItem } from './items'; +import { resetInvincible } from './pvp'; export async function getCounter(id: number) { const counter = await db('counters') @@ -359,6 +360,7 @@ function changeCounterInteractionBuilder(linked: boolean) { newInv = await giveItem(member.id, item, amtInv); } + await resetInvincible(member.id); const newCount = await changeCounter(counter.id, amount); await updateCounter(interaction.client, counter, newCount); await announceCounterUpdate(interaction.client, member, amount, counter, newCount, linked); diff --git a/src/lib/rpg/pvp.ts b/src/lib/rpg/pvp.ts index f7a047b..7bc89d5 100644 --- a/src/lib/rpg/pvp.ts +++ b/src/lib/rpg/pvp.ts @@ -1,4 +1,4 @@ -import { InitHealth, ItemInventory, db } from '../db'; +import { InitHealth, InvincibleUser, ItemInventory, db } from '../db'; import { DefaultItems, getDefaultItem, giveItem, getItemQuantity, formatItems } from './items'; import { Client } from 'discord.js'; @@ -6,6 +6,7 @@ export const BLOOD_ID = -DefaultItems.BLOOD; export const BLOOD_ITEM = getDefaultItem(BLOOD_ID); export const MAX_HEALTH = BLOOD_ITEM.maxStack; const BLOOD_GAIN_PER_HOUR = 5; +export const INVINCIBLE_TIMER = 1_000 * 60 * 60; export async function initHealth(user: string) { const isInitialized = await db('initHealth') @@ -18,6 +19,35 @@ export async function initHealth(user: string) { } } +export async function getInvincibleMs(user: string) { + const invincible = await db('invincibleUsers') + .where('user', user) + .first(); + + if (!invincible) return 0; + return Math.max((invincible.since + INVINCIBLE_TIMER) - Date.now(), 0); +} + +export async function resetInvincible(user: string) { + await db('invincibleUsers') + .where('user', user) + .delete(); +} + +export async function applyInvincible(user: string) { + const exists = await db('invincibleUsers') + .where('user', user) + .first(); + + if (exists) { + await db('invincibleUsers') + .update({ since: Date.now() }); + } else { + await db('invincibleUsers') + .insert({ since: Date.now(), user }); + } +} + export async function getHealth(user: string) { await initHealth(user); return await getItemQuantity(user, BLOOD_ID); @@ -25,6 +55,7 @@ export async function getHealth(user: string) { export async function dealDamage(user: string, dmg: number) { await initHealth(user); + await applyInvincible(user); return await giveItem(user, BLOOD_ITEM, -dmg); } From c4980da8b7f0be8fce4b1c7ca4531ea006fd4fa7 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 23:31:00 +0300 Subject: [PATCH 54/66] item deletion + linked counter interaction fix. idk if it works but i don't wanna bother testing --- src/commands/item.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index 1994962..89d0dcf 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,5 +1,5 @@ import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; +import { Counter, CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; import { customItemAutocomplete, formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; import { behaviors } from '../lib/rpg/behaviors'; import { Command } from '../types/index'; @@ -236,6 +236,13 @@ export default { return interaction.followUp(`⚠️ This item is used in the following recipes:\n${recipes.map(r => `- ${formatRecipe(r!)}`).join('\n')}`); } + const linkedWith = await db('counters') + .where('linkedItem', item.id); + + if (linkedWith.length > 0) { + return interaction.followUp(`⚠️ This item is used in the following counters:\n${linkedWith.map(c => `- ${c.key} ${c.value} in <#${c.channel}>`).join('\n')}`); + } + await db('customItems') .where('id', item.id) .delete(); From 8ad18e9b19b1d5aeb44cb42fc5bbf8a817e57156 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Tue, 21 Nov 2023 23:40:15 +0300 Subject: [PATCH 55/66] remove behaviors and rethink how they should work --- migrations/20231121203119_behaviors.js | 26 +++++++++++++++++++ src/commands/item.ts | 36 -------------------------- src/lib/db.ts | 2 -- src/lib/rpg/items.ts | 3 ++- 4 files changed, 28 insertions(+), 39 deletions(-) create mode 100644 migrations/20231121203119_behaviors.js diff --git a/migrations/20231121203119_behaviors.js b/migrations/20231121203119_behaviors.js new file mode 100644 index 0000000..8f55873 --- /dev/null +++ b/migrations/20231121203119_behaviors.js @@ -0,0 +1,26 @@ +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +exports.up = function(knex) { + return knex.schema + .createTable('itemBehaviors', table => { + table.integer('item').notNullable(); + table.string('behavior').notNullable(); + table.float('value'); + }) + .alterTable('customItems', table => { + table.dropColumn('behavior'); + table.dropColumn('behaviorValue'); + }); +}; + +/** + * @param { import("knex").Knex } knex + * @returns { Promise } + */ +// eslint-disable-next-line @typescript-eslint/no-unused-vars +exports.down = function(knex) { + // no + throw 'Not implemented'; +}; diff --git a/src/commands/item.ts b/src/commands/item.ts index 89d0dcf..af4968e 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,7 +1,6 @@ import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { Counter, CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; import { customItemAutocomplete, formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; -import { behaviors } from '../lib/rpg/behaviors'; import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; @@ -43,22 +42,11 @@ export default { .setName('maxstack') .setDescription('Maximum amount of this item you\'re able to hold at once') ) - .addStringOption(opt => - opt - .setName('behavior') - .setDescription('Special behavior type') - .setChoices(...behaviors.filter(b => b.itemType === 'plain').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) - ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) - .addNumberOption(opt => - opt - .setName('behaviorvalue') - .setDescription('A value to use for the behavior type; not always applicable') - ) ) .addSubcommand(cmd => cmd @@ -87,22 +75,11 @@ export default { .setName('description') .setDescription('A short description') ) - .addStringOption(opt => - opt - .setName('behavior') - .setDescription('Special behavior type') - .setChoices(...behaviors.filter(b => b.itemType === 'weapon').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) - ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) - .addNumberOption(opt => - opt - .setName('behaviorvalue') - .setDescription('A value to use for the behavior type; not always applicable') - ) ) .addSubcommand(cmd => cmd @@ -130,22 +107,11 @@ export default { .setName('maxstack') .setDescription('Maximum amount of this item you\'re able to hold at once') ) - .addStringOption(opt => - opt - .setName('behavior') - .setDescription('Special behavior type') - .setChoices(...behaviors.filter(b => b.itemType === 'consumable').map(b => ({name: `${b.name} - ${b.description}`, value: b.name}))) - ) .addBooleanOption(opt => opt .setName('untradable') .setDescription('Can you give this item to other people?') ) - .addNumberOption(opt => - opt - .setName('behaviorvalue') - .setDescription('A value to use for the behavior type; not always applicable') - ) ) ) .addSubcommand(cmd => @@ -203,9 +169,7 @@ export default { 'emoji': interaction.options.getString('emoji', true).trim(), 'type': subcommand as 'plain' | 'weapon' | 'consumable', // kind of wild that ts makes you do this 'maxStack': (interaction.options.getInteger('maxstack') || interaction.options.getInteger('damage')) || (subcommand === 'weapon' ? 1 : 64), - 'behavior': interaction.options.getString('behavior') || undefined, 'untradable': interaction.options.getBoolean('untradable') || false, - 'behaviorValue': interaction.options.getNumber('behaviorValue') || undefined, }) .returning('*'); diff --git a/src/lib/db.ts b/src/lib/db.ts index 812994c..62244b6 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -55,9 +55,7 @@ export interface CustomItem { type: 'plain' | 'weapon' | 'consumable', // also damage for weapons; weapons are always unstackable (cus i said so) maxStack: number, - behavior?: string, untradable: boolean, - behaviorValue?: number } export interface ItemInventory { user: string, diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 43f9762..e9ec8a3 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,7 +1,8 @@ import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemInventory, db } from '../db'; -export type DefaultItem = Omit; // uses negative IDs +// uses negative IDs +export type DefaultItem = Omit; export type Item = DefaultItem | CustomItem; export interface Items { From ea24a931cad94dc27486b7f02e57fc282fc7e90b Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 00:14:07 +0300 Subject: [PATCH 56/66] slight behavior redesign progress --- src/lib/db.ts | 5 +++ src/lib/rpg/behaviors.ts | 78 +++++++++++++++++++++++++++++++++------- src/lib/rpg/items.ts | 8 +++-- src/lib/util.ts | 15 +++++++- 4 files changed, 90 insertions(+), 16 deletions(-) diff --git a/src/lib/db.ts b/src/lib/db.ts index 62244b6..e55e449 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -91,4 +91,9 @@ export interface InitHealth { export interface InvincibleUser { user: string, since: number, +} +export interface ItemBehavior { + item: number, + behavior: string, + value?: number } \ No newline at end of file diff --git a/src/lib/rpg/behaviors.ts b/src/lib/rpg/behaviors.ts index b867781..e9496c6 100644 --- a/src/lib/rpg/behaviors.ts +++ b/src/lib/rpg/behaviors.ts @@ -1,26 +1,78 @@ -import type { User } from 'discord.js'; -import type { Item } from './items'; +import { giveItem, type Item, isDefaultItem } from './items'; +import { Either, Left } from '../util'; +import { ItemBehavior, db } from '../db'; +import { BLOOD_ITEM, dealDamage } from './pvp'; export interface Behavior { name: string, description: string, itemType: 'plain' | 'weapon' | 'consumable', + // make it look fancy + format?: (value: number) => string, // triggers upon use - // for 'weapons', this is on hit - // for 'consumable', this is on use - // for 'plain', ...?? - // returns `true` upon success, `false` otherwise - action?: (item: Item, user: User) => Promise + // for 'weapons', this is on attack + // for 'consumable' and `plain`, this is on use + // returns Left upon success, the reason for failure otherwise (Right) + onUse?: (value: number, item: Item, user: string) => Promise>, + // triggers upon `weapons` attack + // returns the new damage value upon success (Left), the reason for failure otherwise (Right) + onAttack?: (value: number, item: Item, user: string, target: string, damage: number) => Promise>, } export const behaviors: Behavior[] = [ { name: 'heal', - description: 'Heals the user by `behaviorValue`', + description: 'Heals the user by `value`', itemType: 'consumable', - action: async (item, user) => { - // todo - return false; - } + async onUse(value, item, user) { + await dealDamage(user, -Math.floor(value)); + return new Left(null); + }, + }, + { + name: 'damage', + description: 'Damages the user by `value', + itemType: 'consumable', + async onUse(value, item, user) { + await dealDamage(user, Math.floor(value)); + return new Left(null); + }, + }, + { + name: 'random_up', + description: 'Randomizes the attack value up by a maximum of `value`', + itemType: 'weapon', + format: (value) => `random +${value}`, + async onAttack(value, item, user, target, damage) { + return new Left(damage + Math.round(Math.random() * value)); + }, + }, + { + name: 'random_down', + description: 'Randomizes the attack value down by a maximum of `value`', + itemType: 'weapon', + format: (value) => `random -${value}`, + async onAttack(value, item, user, target, damage) { + return new Left(damage - Math.round(Math.random() * value)); + }, + }, + { + name: 'lifesteal', + description: 'Gain blood by stabbing your foes, scaled by `value`x', + itemType: 'weapon', + format: (value) => `lifesteal: ${value}x`, + async onAttack(value, item, user, target, damage) { + giveItem(user, BLOOD_ITEM, Math.floor(damage * value)); + return new Left(damage); + }, } -]; \ No newline at end of file +]; + +export async function getBehaviors(item: Item) { + if (isDefaultItem(item)) { + return item.behaviors || []; + } else { + return await db('itemBehaviors') + .where('item', item.id); + } +} \ No newline at end of file diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index e9ec8a3..19d49ad 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,8 +1,8 @@ import { AutocompleteInteraction } from 'discord.js'; -import { CustomItem, ItemInventory, db } from '../db'; +import { CustomItem, ItemBehavior, ItemInventory, db } from '../db'; // uses negative IDs -export type DefaultItem = Omit; +export type DefaultItem = Omit & { behaviors?: Omit[] }; export type Item = DefaultItem | CustomItem; export interface Items { @@ -362,6 +362,10 @@ export function getMaxStack(item: Item) { return item.type === 'weapon' ? 1 : item.maxStack; } +export function isDefaultItem(item: Item): item is DefaultItem { + return (item as DefaultItem).behaviors !== undefined; // ough +} + export function formatItem(item: Item | undefined, disableBold = false) { if (!item) return disableBold ? '? MISSINGNO' : '? **MISSINGNO**'; return disableBold ? `${item.emoji} ${item.name}` : `${item.emoji} **${item.name}**`; diff --git a/src/lib/util.ts b/src/lib/util.ts index cb36114..d822174 100644 --- a/src/lib/util.ts +++ b/src/lib/util.ts @@ -27,4 +27,17 @@ export async function writeTmpFile(data: string | Buffer, filename?: string, ext export function pickRandom(list: T[]): T { return list[Math.floor(Math.random() * list.length)]; -} \ No newline at end of file +} + +// WE OUT HERE +export type Either = Left | Right + +export class Left { + constructor(private readonly value: L) {} + public getValue() { return this.value; } +} + +export class Right { + constructor(private readonly value: R) {} + public getValue() { return this.value; } +} From ddb85d1d2f8efd9145c287bdb498123b5e7e3454 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 11:40:37 +0300 Subject: [PATCH 57/66] behavior adding --- src/commands/item.ts | 101 ++++++++++++++++++++++++++++++++++++++- src/lib/rpg/behaviors.ts | 10 +++- 2 files changed, 108 insertions(+), 3 deletions(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index af4968e..6b23c37 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -1,8 +1,9 @@ import { AutocompleteInteraction, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { Counter, CustomCraftingRecipeItem, CustomItem, db } from '../lib/db'; -import { customItemAutocomplete, formatItem, formatItems, getItem, giveItem, itemAutocomplete } from '../lib/rpg/items'; +import { Counter, CustomCraftingRecipeItem, CustomItem, ItemBehavior, db } from '../lib/db'; +import { customItemAutocomplete, formatItem, formatItems, getCustomItem, getItem, giveItem, isDefaultItem, itemAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; +import { behaviors, formatBehavior, getBehavior } from '../lib/rpg/behaviors'; //function extendOption(t: string) { // return {name: t, value: t}; @@ -149,6 +150,54 @@ export default { .setRequired(true) ) ) + .addSubcommandGroup(grp => + grp + .setName('behavior') + .setDescription('[ADMIN] Item behavior management') + .addSubcommand(cmd => + cmd + .setName('add') + .setDescription('[ADMIN] Give an item a behavior') + .addStringOption(opt => + opt + .setName('customitem') + .setDescription('The item') + .setAutocomplete(true) + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('behavior') + .setDescription('The behavior to add') + .setAutocomplete(true) + .setRequired(true) + ) + .addNumberOption(opt => + opt + .setName('value') + .setDescription('A value to assign the behavior, not always applicable') + ) + ) + .addSubcommand(cmd => + cmd + .setName('remove') + .setDescription('[ADMIN] Rid an item of a behavior') + .addStringOption(opt => + opt + .setName('customitem') + .setDescription('The item') + .setAutocomplete(true) + .setRequired(true) + ) + .addStringOption(opt => + opt + .setName('removebehavior') + .setDescription('The behavior to remove') + .setAutocomplete(true) + .setRequired(true) + ) + ) + ) .setDefaultMemberPermissions('0') .setDMPermission(false), @@ -174,6 +223,39 @@ export default { .returning('*'); await interaction.followUp(`${JSON.stringify(item[0])}`); + } else if (group === 'behavior') { + const itemID = parseInt(interaction.options.getString('customitem', true)); + const item = await getCustomItem(itemID); + if (!item) return await interaction.followUp('No such item exists!'); + if (item.guild !== interaction.guildId) return await interaction.followUp('This item is from a different server! Nice try though'); + + if (subcommand === 'add') { + const behaviorName = interaction.options.getString('behavior', true); + const value = interaction.options.getNumber('value'); + + const behavior = getBehavior(behaviorName); + if (!behavior) return await interaction.followUp(`No such behavior ${behaviorName}!`); + + const existingBehavior = await db('itemBehaviors') + .where('item', item.id) + .where('behavior', behavior.name) + .first(); + + if (existingBehavior) { + return await interaction.followUp(`${formatItem(item)} already has **${formatBehavior(behavior, existingBehavior.value)}**!`); + } + + await db('itemBehaviors') + .insert({ + item: item.id, + behavior: behavior.name, + value: value || undefined, + }); + + return await interaction.followUp(`${formatItem(item)} now has **${formatBehavior(behavior, value || undefined)}**`); + } else if (subcommand === 'remove') { + /////////////////// asngheasgyjdreajdneyonv + } } else { if (subcommand === 'give') { const user = interaction.options.getUser('who', true); @@ -183,6 +265,10 @@ export default { const item = await getItem(itemID); if (!item) return interaction.followUp('No such item exists!'); + if (!isDefaultItem(item)) { + if (item.guild !== interaction.guildId) return await interaction.followUp('This item is from a different server! Nice try though'); + } + const inv = await giveItem(user.id, item, quantity); await interaction.followUp(`${user.toString()} now has ${formatItems(item, inv.quantity)}.`); @@ -223,6 +309,17 @@ export default { return itemAutocomplete(interaction); } else if (focused.name === 'customitem') { return customItemAutocomplete(interaction); + } else if (focused.name === 'behavior') { + let foundBehaviors = behaviors.filter(b => b.name.toLowerCase().includes(focused.value.toLowerCase())); + const itemID = interaction.options.getString('customitem'); + if (itemID) { + const item = await getItem(parseInt(itemID)); + if (item) { + foundBehaviors = foundBehaviors.filter(b => b.itemType === item.type); + } + } + + await interaction.respond(foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name}))); } } } satisfies Command; \ No newline at end of file diff --git a/src/lib/rpg/behaviors.ts b/src/lib/rpg/behaviors.ts index e9496c6..fd07ced 100644 --- a/src/lib/rpg/behaviors.ts +++ b/src/lib/rpg/behaviors.ts @@ -8,7 +8,7 @@ export interface Behavior { description: string, itemType: 'plain' | 'weapon' | 'consumable', // make it look fancy - format?: (value: number) => string, + format?: (value?: number) => string, // triggers upon use // for 'weapons', this is on attack // for 'consumable' and `plain`, this is on use @@ -19,6 +19,8 @@ export interface Behavior { onAttack?: (value: number, item: Item, user: string, target: string, damage: number) => Promise>, } +const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`; + export const behaviors: Behavior[] = [ { name: 'heal', @@ -75,4 +77,10 @@ export async function getBehaviors(item: Item) { return await db('itemBehaviors') .where('item', item.id); } +} +export function getBehavior(behavior: string) { + return behaviors.find(b => b.name === behavior); +} +export function formatBehavior(behavior: Behavior, value: number | undefined) { + return behavior.format ? behavior.format(value) : defaultFormat(behavior, value); } \ No newline at end of file From 7a4b9e1726c0c2135a02ec8540990bbf63a48da0 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 14:52:15 +0300 Subject: [PATCH 58/66] finish assigning and removing behaviors --- src/commands/item.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index 6b23c37..7f31f10 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -254,7 +254,26 @@ export default { return await interaction.followUp(`${formatItem(item)} now has **${formatBehavior(behavior, value || undefined)}**`); } else if (subcommand === 'remove') { - /////////////////// asngheasgyjdreajdneyonv + const behaviorName = interaction.options.getString('removebehavior', true); + + const behavior = getBehavior(behaviorName); + if (!behavior) return await interaction.followUp(`No such behavior ${behaviorName}!`); + + const existingBehavior = await db('itemBehaviors') + .where('item', item.id) + .where('behavior', behavior.name) + .first(); + + if (!existingBehavior) { + return await interaction.followUp(`${formatItem(item)} does not have behavior \`${behaviorName}\`!`); + } + + await db('itemBehaviors') + .where('item', item.id) + .where('behavior', behavior.name) + .delete(); + + return await interaction.followUp(`Deleted behavior ${formatBehavior(behavior, existingBehavior.value)} from ${formatItem(item)}.`); } } else { if (subcommand === 'give') { @@ -320,6 +339,21 @@ export default { } await interaction.respond(foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name}))); + } else if (focused.name === 'removebehavior') { + const itemID = interaction.options.getString('customitem'); + if (!itemID) return await interaction.respond([]); + const behaviors = await db('itemBehaviors') + .where('item', itemID); + + const foundBehaviors = behaviors + .map(b => ({ behavior: getBehavior(b.behavior)!, value: b.value })) + .filter(b => b.behavior) + .filter(b => b.behavior.name.toLowerCase().includes(focused.value.toLowerCase())); + + await interaction.respond(foundBehaviors.map(b => ({ + name: `${b.behavior.itemType}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`, + value: b.behavior.name + }))); } } } satisfies Command; \ No newline at end of file From 3d5a7b041d684e6ad2efe100c9144b2f3bdacfc5 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 15:05:18 +0300 Subject: [PATCH 59/66] weapon inv stealing fix --- src/commands/attack.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/commands/attack.ts b/src/commands/attack.ts index 0dad3a4..462d877 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -35,6 +35,8 @@ export default { const weapon = await getItem(weaponID); if (!weapon) return interaction.followUp('No such item exists!'); if (weapon.type !== 'weapon') return interaction.followUp('That is not a weapon!'); + const inv = await getItemQuantity(member.id, weapon.id); + if (inv.quantity === 0) return interaction.followUp('You do not have this weapon!'); const invinTimer = await getInvincibleMs(user.id); if (invinTimer > 0) return interaction.followUp(`You can only attack this user (or if they perform an action first)!`); From e5b049d0b28de863047bbf914d8b03a9861d9755 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 16:32:54 +0300 Subject: [PATCH 60/66] refactor autocompletes --- src/commands/attack.ts | 3 ++- src/commands/counter.ts | 29 ++++++++++++++--------------- src/commands/craft.ts | 36 ++++++++++++++++-------------------- src/commands/decrease.ts | 3 ++- src/commands/increase.ts | 3 ++- src/commands/item.ts | 34 +++++++++++++++++----------------- src/commands/put.ts | 3 ++- src/commands/recipe.ts | 31 ++++++++++++++----------------- src/commands/take.ts | 3 ++- src/lib/autocomplete.ts | 27 +++++++++++++++++++++++++++ src/lib/rpg/counter.ts | 7 +++---- src/lib/rpg/items.ts | 7 +++---- 12 files changed, 104 insertions(+), 82 deletions(-) create mode 100644 src/lib/autocomplete.ts diff --git a/src/commands/attack.ts b/src/commands/attack.ts index 462d877..60011c6 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -2,6 +2,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { weaponAutocomplete, getItem, getItemQuantity, formatItems, formatItem } from '../lib/rpg/items'; import { Command } from '../types/index'; import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp'; +import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -48,5 +49,5 @@ export default { await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); }, - autocomplete: weaponAutocomplete, + autocomplete: autocomplete(weaponAutocomplete), } satisfies Command; \ No newline at end of file diff --git a/src/commands/counter.ts b/src/commands/counter.ts index e25a573..7167fc1 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -4,6 +4,7 @@ import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, import { outdent } from 'outdent'; import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; +import { autocomplete, set } from '../lib/autocomplete'; function extendOption(t: string) { return {name: t, value: t}; @@ -442,24 +443,22 @@ export default { } }, - autocomplete: async (interaction: AutocompleteInteraction) => {{ - const focused = interaction.options.getFocused(true); + autocomplete: autocomplete(set({ + type: counterAutocomplete, + item: itemAutocomplete, + value: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); - if (focused.name === 'type') { - return counterAutocomplete(interaction); - } else if (focused.name === 'item') { - return itemAutocomplete(interaction); - } else if (focused.name === 'value') { const type = interaction.options.getString('type', true); const counter = await findCounter(type, interaction.guildId!); const config = await getCounterConfigRaw(counter); const key = interaction.options.getString('key'); - if (!key) return interaction.respond([]); + if (!key) return []; const defaultConfig = counterConfigs.get(key); - if (!defaultConfig) return interaction.respond([]); + if (!defaultConfig) return []; const defaultOptions = getOptions(defaultConfig.type); @@ -472,20 +471,20 @@ export default { value: `${toStringConfig(defaultConfig.default, defaultConfig.type)}`, name: `Default: ${toStringConfig(defaultConfig.default, defaultConfig.type)}` }, - ...defaultOptions.filter(s => s.startsWith(focused.value)).map(extendOption) + ...defaultOptions.filter(s => s.startsWith(focused)).map(extendOption) ]; - if (focused.value !== '' && !options.find(opt => opt.value === focused.value)) { + if (focused !== '' && !options.find(opt => opt.value === focused)) { options = [ { - value: focused.value, - name: focused.value + value: focused, + name: focused }, ...options ]; } - await interaction.respond(options); + return options; } - }} + })), } satisfies Command; \ No newline at end of file diff --git a/src/commands/craft.ts b/src/commands/craft.ts index a0d3d4f..fbfb061 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -5,6 +5,7 @@ import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, format import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; import { initHealth, resetInvincible } from '../lib/rpg/pvp'; +import { autocomplete, set } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -102,11 +103,9 @@ export default { return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${outputs.length === 1 ? `\n_${outputs[0].item.description}_` : ''}${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, - autocomplete: async (interaction: AutocompleteInteraction) => { - const focused = interaction.options.getFocused(true); - - if (focused.name === 'station') { - const found = (await Promise.all( + autocomplete: autocomplete(set({ + station: async (interaction: AutocompleteInteraction) => + (await Promise.all( craftingStations .map(async station => [station, await canUseStation(interaction.user.id, station)]) )) @@ -114,19 +113,18 @@ export default { .filter(([_station, usable]) => usable) // eslint-disable-next-line @typescript-eslint/no-unused-vars .map(([station, _]) => station as CraftingStation) - .filter(station => station.name.toLowerCase().includes(focused.value.toLowerCase())) + .filter(station => station.name.toLowerCase().includes(interaction.options.getFocused().toLowerCase())) .map(station => ({ name: `${station.emoji} ${station.name}`, value: station.key - })); - - return interaction.respond(found); - } else if (focused.name === 'recipe') { + })), + recipe: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); const station = interaction.options.getString('station'); const foundDefaultRecipes = defaultRecipes .filter(recipe => recipe.station === station) - .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0); const customRecipes = await db('customCraftingRecipes') .where('station', station); @@ -134,17 +132,15 @@ export default { const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); const foundCustomRecipes = resolvedCustomRecipes - .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0); const recipes = [...foundDefaultRecipes, ...foundCustomRecipes]; - return interaction.respond( - recipes - .map(recipe => ({ - name: formatRecipe(recipe, true), - value: recipe.id.toString() - })) - ); + return recipes + .map(recipe => ({ + name: formatRecipe(recipe, true), + value: recipe.id.toString() + })); } - } + })), } satisfies Command; \ No newline at end of file diff --git a/src/commands/decrease.ts b/src/commands/decrease.ts index 26edf6a..2be9adc 100644 --- a/src/commands/decrease.ts +++ b/src/commands/decrease.ts @@ -1,6 +1,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; +import { autocomplete } from '../lib/autocomplete' export default { data: new SlashCommandBuilder() @@ -35,5 +36,5 @@ export default { changeCounterInteraction(interaction, member, -amount, type); }, - autocomplete: counterAutocomplete + autocomplete: autocomplete(counterAutocomplete), } satisfies Command; \ No newline at end of file diff --git a/src/commands/increase.ts b/src/commands/increase.ts index 4a581c2..d060c7e 100644 --- a/src/commands/increase.ts +++ b/src/commands/increase.ts @@ -1,6 +1,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; +import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -35,5 +36,5 @@ export default { changeCounterInteraction(interaction, member, amount, type); }, - autocomplete: counterAutocomplete + autocomplete: autocomplete(counterAutocomplete) } satisfies Command; \ No newline at end of file diff --git a/src/commands/item.ts b/src/commands/item.ts index 7f31f10..e449453 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -4,6 +4,7 @@ import { customItemAutocomplete, formatItem, formatItems, getCustomItem, getItem import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; import { behaviors, formatBehavior, getBehavior } from '../lib/rpg/behaviors'; +import { autocomplete, set } from '../lib/autocomplete'; //function extendOption(t: string) { // return {name: t, value: t}; @@ -321,15 +322,12 @@ export default { } }, - autocomplete: async (interaction: AutocompleteInteraction) => { - const focused = interaction.options.getFocused(true); - - if (focused.name === 'item') { - return itemAutocomplete(interaction); - } else if (focused.name === 'customitem') { - return customItemAutocomplete(interaction); - } else if (focused.name === 'behavior') { - let foundBehaviors = behaviors.filter(b => b.name.toLowerCase().includes(focused.value.toLowerCase())); + autocomplete: autocomplete(set({ + item: itemAutocomplete, + customitem: customItemAutocomplete, + behavior: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); + let foundBehaviors = behaviors.filter(b => b.name.toLowerCase().includes(focused.toLowerCase())); const itemID = interaction.options.getString('customitem'); if (itemID) { const item = await getItem(parseInt(itemID)); @@ -338,22 +336,24 @@ export default { } } - await interaction.respond(foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name}))); - } else if (focused.name === 'removebehavior') { + return foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name})); + }, + removebehavior: async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); const itemID = interaction.options.getString('customitem'); - if (!itemID) return await interaction.respond([]); + if (!itemID) return []; const behaviors = await db('itemBehaviors') .where('item', itemID); const foundBehaviors = behaviors .map(b => ({ behavior: getBehavior(b.behavior)!, value: b.value })) .filter(b => b.behavior) - .filter(b => b.behavior.name.toLowerCase().includes(focused.value.toLowerCase())); + .filter(b => b.behavior.name.toLowerCase().includes(focused.toLowerCase())); - await interaction.respond(foundBehaviors.map(b => ({ + return foundBehaviors.map(b => ({ name: `${b.behavior.itemType}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`, value: b.behavior.name - }))); - } - } + })); + }, + })), } satisfies Command; \ No newline at end of file diff --git a/src/commands/put.ts b/src/commands/put.ts index 0e02a3f..1b6888b 100644 --- a/src/commands/put.ts +++ b/src/commands/put.ts @@ -2,6 +2,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; import { initHealth } from '../lib/rpg/pvp'; +import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -38,5 +39,5 @@ export default { changeLinkedCounterInteraction(interaction, member, amount, type); }, - autocomplete: linkedCounterAutocomplete + autocomplete: autocomplete(linkedCounterAutocomplete), } satisfies Command; \ No newline at end of file diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 86b8dfe..893e70e 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -1,9 +1,10 @@ -import { ActionRowBuilder, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, Events, ModalBuilder, SlashCommandBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; +import { ActionRowBuilder, AutocompleteInteraction, ButtonBuilder, ButtonStyle, CommandInteraction, ComponentType, Events, ModalBuilder, SlashCommandBuilder, StringSelectMenuBuilder, TextInputBuilder, TextInputStyle } from 'discord.js'; import { Command } from '../types/index'; import { Items, getItem } from '../lib/rpg/items'; import { formatRecipe, getCustomRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { craftingStations, getStation } from '../lib/rpg/craftingStations'; import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../lib/db'; +import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -235,24 +236,20 @@ export default { }); }, - autocomplete: async (interaction) => { - const focused = interaction.options.getFocused(true); + autocomplete: autocomplete(async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(); - if (focused.name === 'recipe') { - const customRecipes = await db('customCraftingRecipes'); + const customRecipes = await db('customCraftingRecipes'); - const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); + const resolvedCustomRecipes = await Promise.all(customRecipes.map(resolveCustomRecipe)); - const foundCustomRecipes = resolvedCustomRecipes - .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.value.toLowerCase())).length > 0); + const foundCustomRecipes = resolvedCustomRecipes + .filter(recipe => recipe.outputs.filter(n => n.item.name.toLowerCase().includes(focused.toLowerCase())).length > 0); - return interaction.respond( - foundCustomRecipes - .map(recipe => ({ - name: formatRecipe(recipe, true), - value: recipe.id.toString() - })) - ); - } - } + return foundCustomRecipes + .map(recipe => ({ + name: formatRecipe(recipe, true), + value: recipe.id.toString() + })); + }), } satisfies Command; \ No newline at end of file diff --git a/src/commands/take.ts b/src/commands/take.ts index 31c22ce..bdba406 100644 --- a/src/commands/take.ts +++ b/src/commands/take.ts @@ -2,6 +2,7 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; import { initHealth } from '../lib/rpg/pvp'; import { Command } from '../types/index'; +import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -38,5 +39,5 @@ export default { changeLinkedCounterInteraction(interaction, member, -amount, type); }, - autocomplete: linkedCounterAutocomplete + autocomplete: autocomplete(linkedCounterAutocomplete) } satisfies Command; \ No newline at end of file diff --git a/src/lib/autocomplete.ts b/src/lib/autocomplete.ts new file mode 100644 index 0000000..23af2c3 --- /dev/null +++ b/src/lib/autocomplete.ts @@ -0,0 +1,27 @@ +import { AutocompleteInteraction, ApplicationCommandOptionChoiceData } from 'discord.js'; +import * as log from './log'; + +export type Autocomplete = (interaction: AutocompleteInteraction) => Promise[]> + +export function set(fns: Record): Autocomplete { + return async (interaction: AutocompleteInteraction) => { + const focused = interaction.options.getFocused(true); + const fn = fns[focused.name]; + + if (!fn) return []; + + return fn(interaction); + }; +} +export function autocomplete(fn: Autocomplete): (interaction: AutocompleteInteraction) => Promise { + return async (interaction: AutocompleteInteraction) => { + try { + const arr = await fn(interaction); + if (arr.length > 25) log.warn(`Autocomplete for ${interaction.options.getFocused(true).name} exceeded limit of 25 autocomplete results`); + return interaction.respond(arr.slice(0, 25)); + } catch (err) { + log.error(err); + return interaction.respond([]); + } + }; +} \ No newline at end of file diff --git a/src/lib/rpg/counter.ts b/src/lib/rpg/counter.ts index 9ec814f..2ea47d8 100644 --- a/src/lib/rpg/counter.ts +++ b/src/lib/rpg/counter.ts @@ -3,6 +3,7 @@ import { getSign } from '../util'; import { Counter, CounterConfiguration, CounterUserLink, db } from '../db'; import { formatItems, getItem, getItemQuantity, getMaxStack, giveItem } from './items'; import { resetInvincible } from './pvp'; +import { Autocomplete } from '../autocomplete'; export async function getCounter(id: number) { const counter = await db('counters') @@ -378,7 +379,7 @@ function changeCounterInteractionBuilder(linked: boolean) { export const changeCounterInteraction = changeCounterInteractionBuilder(false); export const changeLinkedCounterInteraction = changeCounterInteractionBuilder(true); -function counterAutocompleteBuilder(linked: boolean) { +function counterAutocompleteBuilder(linked: boolean): Autocomplete { return async (interaction: AutocompleteInteraction) => { const focusedValue = interaction.options.getFocused(); const guild = interaction.guildId; @@ -397,9 +398,7 @@ function counterAutocompleteBuilder(linked: boolean) { const foundCounters = await query; - await interaction.respond( - foundCounters.map(choice => ({ name: `${choice.emoji} ${choice.key}`, value: choice.key })) - ); + return foundCounters.map(choice => ({ name: `${choice.emoji} ${choice.key}`, value: choice.key })); }; } diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 19d49ad..56a4f56 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -1,5 +1,6 @@ import { AutocompleteInteraction } from 'discord.js'; import { CustomItem, ItemBehavior, ItemInventory, db } from '../db'; +import { Autocomplete } from '../autocomplete'; // uses negative IDs export type DefaultItem = Omit & { behaviors?: Omit[] }; @@ -378,7 +379,7 @@ export function formatItemsArray(items: Items[], disableBold = false) { return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } -function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null) { +function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null): Autocomplete { return async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); @@ -403,9 +404,7 @@ function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weap items = [...foundDefaultItems, ...customItems]; } - await interaction.respond( - items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })) - ); + return items.map(choice => ({ name: `${choice.emoji} ${choice.name}`, value: choice.id.toString() })); }; } From 8f0fa740d7f682f3c09eeb93e50ec264d159af66 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 16:45:55 +0300 Subject: [PATCH 61/66] remove redundant autocomplete step --- src/commands/attack.ts | 3 +-- src/commands/counter.ts | 6 +++--- src/commands/craft.ts | 6 +++--- src/commands/decrease.ts | 3 +-- src/commands/increase.ts | 3 +-- src/commands/item.ts | 6 +++--- src/commands/put.ts | 3 +-- src/commands/recipe.ts | 5 ++--- src/commands/take.ts | 3 +-- src/index.ts | 3 ++- src/types/index.d.ts | 3 ++- 11 files changed, 20 insertions(+), 24 deletions(-) diff --git a/src/commands/attack.ts b/src/commands/attack.ts index 60011c6..462d877 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -2,7 +2,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { weaponAutocomplete, getItem, getItemQuantity, formatItems, formatItem } from '../lib/rpg/items'; import { Command } from '../types/index'; import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp'; -import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -49,5 +48,5 @@ export default { await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); }, - autocomplete: autocomplete(weaponAutocomplete), + autocomplete: weaponAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/commands/counter.ts b/src/commands/counter.ts index 7167fc1..cb98c6f 100644 --- a/src/commands/counter.ts +++ b/src/commands/counter.ts @@ -4,7 +4,7 @@ import { counterAutocomplete, counterConfigs, findCounter, getCounterConfigRaw, import { outdent } from 'outdent'; import { formatItem, formatItems, getItem, itemAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; -import { autocomplete, set } from '../lib/autocomplete'; +import { set } from '../lib/autocomplete'; function extendOption(t: string) { return {name: t, value: t}; @@ -443,7 +443,7 @@ export default { } }, - autocomplete: autocomplete(set({ + autocomplete: set({ type: counterAutocomplete, item: itemAutocomplete, value: async (interaction: AutocompleteInteraction) => { @@ -486,5 +486,5 @@ export default { return options; } - })), + }), } satisfies Command; \ No newline at end of file diff --git a/src/commands/craft.ts b/src/commands/craft.ts index fbfb061..53f2646 100644 --- a/src/commands/craft.ts +++ b/src/commands/craft.ts @@ -5,7 +5,7 @@ import { formatItem, getItemQuantity, formatItems, getMaxStack, giveItem, format import { getRecipe, defaultRecipes, formatRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { Command } from '../types/index'; import { initHealth, resetInvincible } from '../lib/rpg/pvp'; -import { autocomplete, set } from '../lib/autocomplete'; +import { set } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -103,7 +103,7 @@ export default { return interaction.followUp(`${station.emoji} ${verb(station)} ${formatItemsArray(outputs)}!${outputs.length === 1 ? `\n_${outputs[0].item.description}_` : ''}${nextUsableAt ? `\n${station.name} usable again ` : ''}`); }, - autocomplete: autocomplete(set({ + autocomplete: set({ station: async (interaction: AutocompleteInteraction) => (await Promise.all( craftingStations @@ -142,5 +142,5 @@ export default { value: recipe.id.toString() })); } - })), + }), } satisfies Command; \ No newline at end of file diff --git a/src/commands/decrease.ts b/src/commands/decrease.ts index 2be9adc..829f2aa 100644 --- a/src/commands/decrease.ts +++ b/src/commands/decrease.ts @@ -1,7 +1,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; -import { autocomplete } from '../lib/autocomplete' export default { data: new SlashCommandBuilder() @@ -36,5 +35,5 @@ export default { changeCounterInteraction(interaction, member, -amount, type); }, - autocomplete: autocomplete(counterAutocomplete), + autocomplete: counterAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/commands/increase.ts b/src/commands/increase.ts index d060c7e..c921e35 100644 --- a/src/commands/increase.ts +++ b/src/commands/increase.ts @@ -1,7 +1,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; import { changeCounterInteraction, counterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; -import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -36,5 +35,5 @@ export default { changeCounterInteraction(interaction, member, amount, type); }, - autocomplete: autocomplete(counterAutocomplete) + autocomplete: counterAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/commands/item.ts b/src/commands/item.ts index e449453..439d64a 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -4,7 +4,7 @@ import { customItemAutocomplete, formatItem, formatItems, getCustomItem, getItem import { Command } from '../types/index'; import { formatRecipe, getCustomRecipe } from '../lib/rpg/recipes'; import { behaviors, formatBehavior, getBehavior } from '../lib/rpg/behaviors'; -import { autocomplete, set } from '../lib/autocomplete'; +import { set } from '../lib/autocomplete'; //function extendOption(t: string) { // return {name: t, value: t}; @@ -322,7 +322,7 @@ export default { } }, - autocomplete: autocomplete(set({ + autocomplete: set({ item: itemAutocomplete, customitem: customItemAutocomplete, behavior: async (interaction: AutocompleteInteraction) => { @@ -355,5 +355,5 @@ export default { value: b.behavior.name })); }, - })), + }), } satisfies Command; \ No newline at end of file diff --git a/src/commands/put.ts b/src/commands/put.ts index 1b6888b..a81140a 100644 --- a/src/commands/put.ts +++ b/src/commands/put.ts @@ -2,7 +2,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; import { Command } from '../types/index'; import { initHealth } from '../lib/rpg/pvp'; -import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -39,5 +38,5 @@ export default { changeLinkedCounterInteraction(interaction, member, amount, type); }, - autocomplete: autocomplete(linkedCounterAutocomplete), + autocomplete: linkedCounterAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/commands/recipe.ts b/src/commands/recipe.ts index 893e70e..98e6428 100644 --- a/src/commands/recipe.ts +++ b/src/commands/recipe.ts @@ -4,7 +4,6 @@ import { Items, getItem } from '../lib/rpg/items'; import { formatRecipe, getCustomRecipe, resolveCustomRecipe } from '../lib/rpg/recipes'; import { craftingStations, getStation } from '../lib/rpg/craftingStations'; import { CustomCraftingRecipe, CustomCraftingRecipeItem, db } from '../lib/db'; -import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -236,7 +235,7 @@ export default { }); }, - autocomplete: autocomplete(async (interaction: AutocompleteInteraction) => { + autocomplete: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); const customRecipes = await db('customCraftingRecipes'); @@ -251,5 +250,5 @@ export default { name: formatRecipe(recipe, true), value: recipe.id.toString() })); - }), + }, } satisfies Command; \ No newline at end of file diff --git a/src/commands/take.ts b/src/commands/take.ts index bdba406..5ad607f 100644 --- a/src/commands/take.ts +++ b/src/commands/take.ts @@ -2,7 +2,6 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { changeLinkedCounterInteraction, linkedCounterAutocomplete } from '../lib/rpg/counter'; import { initHealth } from '../lib/rpg/pvp'; import { Command } from '../types/index'; -import { autocomplete } from '../lib/autocomplete'; export default { data: new SlashCommandBuilder() @@ -39,5 +38,5 @@ export default { changeLinkedCounterInteraction(interaction, member, -amount, type); }, - autocomplete: autocomplete(linkedCounterAutocomplete) + autocomplete: linkedCounterAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 30dc29f..b88fbda 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,6 +9,7 @@ import prettyBytes from 'pretty-bytes'; import { Command } from './types/index'; import { startServer } from './web/web'; import { init as initPVP } from './lib/rpg/pvp'; +import { autocomplete } from './lib/autocomplete'; const bot = new Client({ intents: [ @@ -114,7 +115,7 @@ bot.on(Events.InteractionCreate, async (interaction) => { try { if (!command.autocomplete) throw `Trying to invoke autocomplete for command ${interaction.commandName} which does not have it defined`; - await command.autocomplete(interaction); + await autocomplete(command.autocomplete)(interaction); } catch (error) { log.error(error); } diff --git a/src/types/index.d.ts b/src/types/index.d.ts index cfbdeb0..293edc7 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,9 +1,10 @@ import { Collection, SlashCommandBuilder, CommandInteraction, Client } from 'discord.js'; +import { Autocomplete } from '../lib/autocomplete'; export interface Command { data: Pick, execute: (interaction: CommandInteraction) => Promise, - autocomplete?: (interaction: AutocompleteInteraction) => Promise, + autocomplete?: Autocomplete, onClientReady?: (client: Client) => Promise, serverWhitelist?: string[], } From 1a8d49ae9f0bfbb61d6339f2c7baa88b9cd2caea Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 20:58:20 +0300 Subject: [PATCH 62/66] fix item creation message --- src/commands/item.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/item.ts b/src/commands/item.ts index 439d64a..0e291fd 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -223,7 +223,7 @@ export default { }) .returning('*'); - await interaction.followUp(`${JSON.stringify(item[0])}`); + await interaction.followUp(`${formatItem(item[0])} has been successfully created! 🎉\nYou can now use \`/item behavior\` to give it some custom functionality.`); } else if (group === 'behavior') { const itemID = parseInt(interaction.options.getString('customitem', true)); const item = await getCustomItem(itemID); From 4e35d478548d779a34d92cbe53a9e7e50f220d73 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 22:02:00 +0300 Subject: [PATCH 63/66] using and eating items, behaviors implemented --- src/commands/attack.ts | 4 +-- src/commands/eat.ts | 60 ++++++++++++++++++++++++++++++++++++++++ src/commands/use.ts | 58 ++++++++++++++++++++++++++++++++++++++ src/lib/rpg/behaviors.ts | 19 ++++++++----- src/lib/rpg/items.ts | 24 ++++++++++++---- 5 files changed, 150 insertions(+), 15 deletions(-) create mode 100644 src/commands/eat.ts create mode 100644 src/commands/use.ts diff --git a/src/commands/attack.ts b/src/commands/attack.ts index 462d877..a146e7a 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -1,5 +1,5 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; -import { weaponAutocomplete, getItem, getItemQuantity, formatItems, formatItem } from '../lib/rpg/items'; +import { getItem, getItemQuantity, formatItems, formatItem, weaponInventoryAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp'; @@ -48,5 +48,5 @@ export default { await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); }, - autocomplete: weaponAutocomplete, + autocomplete: weaponInventoryAutocomplete, } satisfies Command; \ No newline at end of file diff --git a/src/commands/eat.ts b/src/commands/eat.ts new file mode 100644 index 0000000..933d334 --- /dev/null +++ b/src/commands/eat.ts @@ -0,0 +1,60 @@ +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { Command } from '../types/index'; +import { initHealth, resetInvincible } from '../lib/rpg/pvp'; +import { consumableInventoryAutocomplete, formatItem, formatItems, getItem, getItemQuantity, giveItem } from '../lib/rpg/items'; +import { getBehavior, getBehaviors } from '../lib/rpg/behaviors'; +import { Right } from '../lib/util'; + +export default { + data: new SlashCommandBuilder() + .setName('eat') + .setDescription('Eat an item from your inventory') + .addStringOption(option => + option + .setName('item') + .setAutocomplete(true) + .setDescription('The item to eat') + .setRequired(true) + ) + .setDMPermission(false), + + execute: async (interaction: CommandInteraction) => { + if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; + + await initHealth(member.id); + + await interaction.deferReply({ ephemeral: true }); + + const itemID = parseInt(interaction.options.getString('item', true)); + const item = await getItem(itemID); + if (!item) return await interaction.followUp('Item does not exist!'); + + const itemInv = await getItemQuantity(member.id, item.id); + if (itemInv.quantity <= 0) return await interaction.followUp(`You do not have ${formatItem(item)}!`); + + const behaviors = await getBehaviors(item); + + const messages = []; + for (const itemBehavior of behaviors) { + const behavior = getBehavior(itemBehavior.behavior); + if (!behavior) continue; + if (!behavior.onUse) continue; + const res = await behavior.onUse(itemBehavior.value, item, member.id); + if (res instanceof Right) { + await interaction.followUp(`You tried to eat ${formatItems(item, 1)}... but failed!\n${res.getValue()}`); + return; + } else { + messages.push(res.getValue()); + } + } + + await resetInvincible(member.id); + const newInv = await giveItem(member.id, item, -1); + + return await interaction.followUp(`You ate ${formatItems(item, 1)}!\n${messages.map(m => `_${m}_`).join('\n')}\nYou now have ${formatItems(item, newInv.quantity)}`); + }, + + autocomplete: consumableInventoryAutocomplete, +} satisfies Command; \ No newline at end of file diff --git a/src/commands/use.ts b/src/commands/use.ts new file mode 100644 index 0000000..85991ce --- /dev/null +++ b/src/commands/use.ts @@ -0,0 +1,58 @@ +import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js'; +import { Command } from '../types/index'; +import { initHealth, resetInvincible } from '../lib/rpg/pvp'; +import { formatItem, getItem, getItemQuantity, plainInventoryAutocomplete } from '../lib/rpg/items'; +import { getBehavior, getBehaviors } from '../lib/rpg/behaviors'; +import { Right } from '../lib/util'; + +export default { + data: new SlashCommandBuilder() + .setName('use') + .setDescription('Use an item from your inventory') + .addStringOption(option => + option + .setName('item') + .setAutocomplete(true) + .setDescription('The item to use') + .setRequired(true) + ) + .setDMPermission(false), + + execute: async (interaction: CommandInteraction) => { + if (!interaction.isChatInputCommand()) return; + + const member = interaction.member! as GuildMember; + + await initHealth(member.id); + + await interaction.deferReply({ ephemeral: true }); + + const itemID = parseInt(interaction.options.getString('item', true)); + const item = await getItem(itemID); + if (!item) return await interaction.followUp('Item does not exist!'); + + const itemInv = await getItemQuantity(member.id, item.id); + if (itemInv.quantity <= 0) return await interaction.followUp(`You do not have ${formatItem(item)}!`); + + const behaviors = await getBehaviors(item); + + const messages = []; + for (const itemBehavior of behaviors) { + const behavior = getBehavior(itemBehavior.behavior); + if (!behavior) continue; + if (!behavior.onUse) continue; + const res = await behavior.onUse(itemBehavior.value, item, member.id); + if (res instanceof Right) { + await interaction.followUp(`You tried to use ${formatItem(item)}... but failed!\n${res.getValue()}`); + return; + } else { + messages.push(res.getValue()); + } + } + + await resetInvincible(member.id); + return await interaction.followUp(`You used ${formatItem(item)}!\n${messages.map(m => `_${m}_`).join('\n')}`); + }, + + autocomplete: plainInventoryAutocomplete, +} satisfies Command; \ No newline at end of file diff --git a/src/lib/rpg/behaviors.ts b/src/lib/rpg/behaviors.ts index fd07ced..d58a8cc 100644 --- a/src/lib/rpg/behaviors.ts +++ b/src/lib/rpg/behaviors.ts @@ -1,5 +1,5 @@ -import { giveItem, type Item, isDefaultItem } from './items'; -import { Either, Left } from '../util'; +import { giveItem, type Item, isDefaultItem, formatItems } from './items'; +import { Either, Left, Right } from '../util'; import { ItemBehavior, db } from '../db'; import { BLOOD_ITEM, dealDamage } from './pvp'; @@ -12,11 +12,11 @@ export interface Behavior { // triggers upon use // for 'weapons', this is on attack // for 'consumable' and `plain`, this is on use - // returns Left upon success, the reason for failure otherwise (Right) - onUse?: (value: number, item: Item, user: string) => Promise>, + // returns Left upon success with an optional message, the reason for failure otherwise (Right) + onUse?: (value: number | undefined, item: Item, user: string) => Promise>, // triggers upon `weapons` attack // returns the new damage value upon success (Left), the reason for failure otherwise (Right) - onAttack?: (value: number, item: Item, user: string, target: string, damage: number) => Promise>, + onAttack?: (value: number | undefined, item: Item, user: string, target: string, damage: number) => Promise>, } const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`; @@ -27,8 +27,9 @@ export const behaviors: Behavior[] = [ description: 'Heals the user by `value`', itemType: 'consumable', async onUse(value, item, user) { + if (!value) return new Right('A value is required for this behavior'); await dealDamage(user, -Math.floor(value)); - return new Left(null); + return new Left(`You were healed by ${formatItems(BLOOD_ITEM, value)}!`); }, }, { @@ -36,8 +37,9 @@ export const behaviors: Behavior[] = [ description: 'Damages the user by `value', itemType: 'consumable', async onUse(value, item, user) { + if (!value) return new Right('A value is required for this behavior'); await dealDamage(user, Math.floor(value)); - return new Left(null); + return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, value)}!`); }, }, { @@ -46,6 +48,7 @@ export const behaviors: Behavior[] = [ itemType: 'weapon', format: (value) => `random +${value}`, async onAttack(value, item, user, target, damage) { + if (!value) return new Right('A value is required for this behavior'); return new Left(damage + Math.round(Math.random() * value)); }, }, @@ -55,6 +58,7 @@ export const behaviors: Behavior[] = [ itemType: 'weapon', format: (value) => `random -${value}`, async onAttack(value, item, user, target, damage) { + if (!value) return new Right('A value is required for this behavior'); return new Left(damage - Math.round(Math.random() * value)); }, }, @@ -64,6 +68,7 @@ export const behaviors: Behavior[] = [ itemType: 'weapon', format: (value) => `lifesteal: ${value}x`, async onAttack(value, item, user, target, damage) { + if (!value) return new Right('A value is required for this behavior'); giveItem(user, BLOOD_ITEM, Math.floor(damage * value)); return new Left(damage); }, diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 56a4f56..fa0876e 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -84,7 +84,8 @@ export const defaultItems: DefaultItem[] = [ emoji: '🍎', type: 'consumable', maxStack: 16, - untradable: false + untradable: false, + behaviors: [{ behavior: 'heal', value: 10 }], }, { id: -6, @@ -93,7 +94,8 @@ export const defaultItems: DefaultItem[] = [ emoji: '🍓', type: 'consumable', maxStack: 16, - untradable: false + untradable: false, + behaviors: [{ behavior: 'heal', value: 4 }], }, { id: -7, @@ -129,7 +131,8 @@ export const defaultItems: DefaultItem[] = [ emoji: '🪱', type: 'consumable', maxStack: 128, - untradable: false + untradable: false, + behaviors: [{ behavior: 'heal', value: 1 }], }, { id: -11, @@ -282,7 +285,8 @@ export const defaultItems: DefaultItem[] = [ emoji: '🍱', type: 'consumable', maxStack: 16, - untradable: false + untradable: false, + behaviors: [{ behavior: 'heal', value: 35 }], }, ]; @@ -379,7 +383,7 @@ export function formatItemsArray(items: Items[], disableBold = false) { return items.map(i => formatItems(i.item, i.quantity, disableBold)).join(' '); } -function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null): Autocomplete { +function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weapon' | 'consumable' | null, inventory: boolean = false): Autocomplete { return async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); @@ -391,11 +395,16 @@ function createItemAutocomplete(onlyCustom: boolean, filterType: 'plain' | 'weap .limit(25); if (filterType) itemQuery.where('type', filterType); + if (inventory) itemQuery + .innerJoin('itemInventories', 'itemInventories.item', '=', 'customItems.id') + .where('itemInventories.user', '=', interaction.member!.user.id) + .where('itemInventories.quantity', '>', '0'); const customItems = await itemQuery; let foundDefaultItems = defaultItems.filter(item => item.name.toUpperCase().includes(focused.toUpperCase())); if (filterType) foundDefaultItems = foundDefaultItems.filter(i => i.type === filterType); + if (inventory) foundDefaultItems = (await Promise.all(foundDefaultItems.map(async i => ({...i, inv: await getItemQuantity(interaction.member!.user.id, i.id)})))).filter(i => i.inv.quantity > 0); let items; if (onlyCustom) { @@ -412,4 +421,7 @@ export const itemAutocomplete = createItemAutocomplete(false, null); export const customItemAutocomplete = createItemAutocomplete(true, null); export const plainAutocomplete = createItemAutocomplete(false, 'plain'); export const weaponAutocomplete = createItemAutocomplete(false, 'weapon'); -export const consumableAutocomplete = createItemAutocomplete(false, 'consumable'); \ No newline at end of file +export const consumableAutocomplete = createItemAutocomplete(false, 'consumable'); +export const plainInventoryAutocomplete = createItemAutocomplete(false, 'plain', true); +export const weaponInventoryAutocomplete = createItemAutocomplete(false, 'weapon', true); +export const consumableInventoryAutocomplete = createItemAutocomplete(false, 'consumable', true); \ No newline at end of file From e02ecfba4b84f6c3314ce3c744a352879a3dcd56 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Wed, 22 Nov 2023 22:34:41 +0300 Subject: [PATCH 64/66] fix isDefaultItem check --- src/lib/rpg/items.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index fa0876e..06b200b 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -368,7 +368,7 @@ export function getMaxStack(item: Item) { } export function isDefaultItem(item: Item): item is DefaultItem { - return (item as DefaultItem).behaviors !== undefined; // ough + return item.id < 0; } export function formatItem(item: Item | undefined, disableBold = false) { From 1dee5ed060bbd4629aceb24814e3412cdfb192b9 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Thu, 23 Nov 2023 22:34:24 +0300 Subject: [PATCH 65/66] fix dumb typo --- src/lib/rpg/items.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/rpg/items.ts b/src/lib/rpg/items.ts index 06b200b..9dc1442 100644 --- a/src/lib/rpg/items.ts +++ b/src/lib/rpg/items.ts @@ -354,8 +354,8 @@ export async function giveItem(user: string, item: Item, quantity = 1): Promise< inv = await db('itemInventories') .insert({ user: user, - item: Math.min(item.id, getMaxStack(item)), - quantity: quantity + item: item.id, + quantity: Math.min(quantity, getMaxStack(item)), }) .returning('*'); } From 465c42437cbc79394499bee78bbd563e3ebd2253 Mon Sep 17 00:00:00 2001 From: "Jill \"oatmealine\" Monoids" Date: Sat, 25 Nov 2023 19:21:19 +0300 Subject: [PATCH 66/66] behavior tweaks, rebalancing --- src/commands/attack.ts | 41 +++++++++++++++++++---- src/commands/eat.ts | 6 +++- src/commands/item.ts | 6 ++-- src/commands/use.ts | 6 +++- src/lib/rpg/behaviors.ts | 71 ++++++++++++++++++++++++---------------- src/lib/rpg/pvp.ts | 4 +-- 6 files changed, 92 insertions(+), 42 deletions(-) diff --git a/src/commands/attack.ts b/src/commands/attack.ts index a146e7a..4089443 100644 --- a/src/commands/attack.ts +++ b/src/commands/attack.ts @@ -2,6 +2,8 @@ import { GuildMember, CommandInteraction, SlashCommandBuilder } from 'discord.js import { getItem, getItemQuantity, formatItems, formatItem, weaponInventoryAutocomplete } from '../lib/rpg/items'; import { Command } from '../types/index'; import { initHealth, dealDamage, BLOOD_ITEM, BLOOD_ID, resetInvincible, INVINCIBLE_TIMER, getInvincibleMs } from '../lib/rpg/pvp'; +import { getBehavior, getBehaviors } from '../lib/rpg/behaviors'; +import { Right } from '../lib/util'; export default { data: new SlashCommandBuilder() @@ -28,7 +30,7 @@ export default { const member = interaction.member! as GuildMember; await initHealth(member.id); const weaponID = parseInt(interaction.options.getString('weapon', true)); - const user = interaction.options.getUser('user', true); + const target = interaction.options.getUser('user', true); await interaction.deferReply({ephemeral: true}); @@ -37,15 +39,40 @@ export default { if (weapon.type !== 'weapon') return interaction.followUp('That is not a weapon!'); const inv = await getItemQuantity(member.id, weapon.id); if (inv.quantity === 0) return interaction.followUp('You do not have this weapon!'); - const invinTimer = await getInvincibleMs(user.id); + const invinTimer = await getInvincibleMs(target.id); if (invinTimer > 0) return interaction.followUp(`You can only attack this user (or if they perform an action first)!`); - const dmg = weapon.maxStack; - await dealDamage(user.id, dmg); - const newHealth = await getItemQuantity(user.id, BLOOD_ID); + let dmg = weapon.maxStack; + const messages = []; - if (user.id !== member.id) await resetInvincible(member.id); - await interaction.followUp(`You hit ${user} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage! They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); + const behaviors = await getBehaviors(weapon); + for (const itemBehavior of behaviors) { + const behavior = getBehavior(itemBehavior.behavior); + if (!behavior) continue; + if (!behavior.onAttack) continue; + const res = await behavior.onAttack({ + value: itemBehavior.value, + damage: dmg, + item: weapon, + user: member.id, + target: target.id, + }); + if (res instanceof Right) { + await interaction.followUp(`You tried to attack with ${formatItem(weapon)}... but failed!\n${res.getValue()}`); + return; + } else { + const { message, damage } = res.getValue(); + if (message) messages.push(message); + if (damage) dmg = damage; + } + } + + await dealDamage(target.id, dmg); + const newHealth = await getItemQuantity(target.id, BLOOD_ID); + + if (target.id !== member.id) await resetInvincible(member.id); + + await interaction.followUp(`You hit ${target} with ${formatItem(weapon)} for ${BLOOD_ITEM.emoji} **${dmg}** damage!\n${messages.map(m => `_${m}_\n`).join('')}They are now at ${formatItems(BLOOD_ITEM, newHealth.quantity)}.\nYou can attack them again (or if they perform an action first).`); }, autocomplete: weaponInventoryAutocomplete, diff --git a/src/commands/eat.ts b/src/commands/eat.ts index 933d334..5e389ee 100644 --- a/src/commands/eat.ts +++ b/src/commands/eat.ts @@ -41,7 +41,11 @@ export default { const behavior = getBehavior(itemBehavior.behavior); if (!behavior) continue; if (!behavior.onUse) continue; - const res = await behavior.onUse(itemBehavior.value, item, member.id); + const res = await behavior.onUse({ + value: itemBehavior.value, + item, + user: member.id + }); if (res instanceof Right) { await interaction.followUp(`You tried to eat ${formatItems(item, 1)}... but failed!\n${res.getValue()}`); return; diff --git a/src/commands/item.ts b/src/commands/item.ts index 0e291fd..9f97aac 100644 --- a/src/commands/item.ts +++ b/src/commands/item.ts @@ -332,11 +332,11 @@ export default { if (itemID) { const item = await getItem(parseInt(itemID)); if (item) { - foundBehaviors = foundBehaviors.filter(b => b.itemType === item.type); + foundBehaviors = foundBehaviors.filter(b => b.type === item.type); } } - return foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name})); + return foundBehaviors.map(b => ({name: `${b.type}:${b.name} - ${b.description}`, value: b.name})); }, removebehavior: async (interaction: AutocompleteInteraction) => { const focused = interaction.options.getFocused(); @@ -351,7 +351,7 @@ export default { .filter(b => b.behavior.name.toLowerCase().includes(focused.toLowerCase())); return foundBehaviors.map(b => ({ - name: `${b.behavior.itemType}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`, + name: `${b.behavior.type}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`, value: b.behavior.name })); }, diff --git a/src/commands/use.ts b/src/commands/use.ts index 85991ce..25dc2d1 100644 --- a/src/commands/use.ts +++ b/src/commands/use.ts @@ -41,7 +41,11 @@ export default { const behavior = getBehavior(itemBehavior.behavior); if (!behavior) continue; if (!behavior.onUse) continue; - const res = await behavior.onUse(itemBehavior.value, item, member.id); + const res = await behavior.onUse({ + value: itemBehavior.value, + item, + user: member.id + }); if (res instanceof Right) { await interaction.followUp(`You tried to use ${formatItem(item)}... but failed!\n${res.getValue()}`); return; diff --git a/src/lib/rpg/behaviors.ts b/src/lib/rpg/behaviors.ts index d58a8cc..fd6dc7b 100644 --- a/src/lib/rpg/behaviors.ts +++ b/src/lib/rpg/behaviors.ts @@ -3,20 +3,32 @@ import { Either, Left, Right } from '../util'; import { ItemBehavior, db } from '../db'; import { BLOOD_ITEM, dealDamage } from './pvp'; +interface BehaviorContext { + value: number | undefined, +} +type ItemContext = BehaviorContext & { + item: Item, + user: string, +} +type AttackContext = ItemContext & { + target: string, + damage: number, +} + export interface Behavior { name: string, description: string, - itemType: 'plain' | 'weapon' | 'consumable', + type: 'plain' | 'weapon' | 'consumable', // make it look fancy format?: (value?: number) => string, // triggers upon use // for 'weapons', this is on attack // for 'consumable' and `plain`, this is on use // returns Left upon success with an optional message, the reason for failure otherwise (Right) - onUse?: (value: number | undefined, item: Item, user: string) => Promise>, + onUse?: (ctx: ItemContext) => Promise>, // triggers upon `weapons` attack - // returns the new damage value upon success (Left), the reason for failure otherwise (Right) - onAttack?: (value: number | undefined, item: Item, user: string, target: string, damage: number) => Promise>, + // returns the new damage value if applicable upon success and an optional message (Left), the reason for failure otherwise (Right) + onAttack?: (ctx: AttackContext) => Promise>, } const defaultFormat = (behavior: Behavior, value?: number) => `${behavior.name}${value ? ' ' + value.toString() : ''}`; @@ -25,52 +37,55 @@ export const behaviors: Behavior[] = [ { name: 'heal', description: 'Heals the user by `value`', - itemType: 'consumable', - async onUse(value, item, user) { - if (!value) return new Right('A value is required for this behavior'); - await dealDamage(user, -Math.floor(value)); - return new Left(`You were healed by ${formatItems(BLOOD_ITEM, value)}!`); + type: 'consumable', + async onUse(ctx) { + if (!ctx.value) return new Right('A value is required for this behavior'); + await dealDamage(ctx.user, -Math.floor(ctx.value)); + return new Left(`You were healed by ${formatItems(BLOOD_ITEM, ctx.value)}!`); }, }, { name: 'damage', description: 'Damages the user by `value', - itemType: 'consumable', - async onUse(value, item, user) { - if (!value) return new Right('A value is required for this behavior'); - await dealDamage(user, Math.floor(value)); - return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, value)}!`); + type: 'consumable', + async onUse(ctx) { + if (!ctx.value) return new Right('A value is required for this behavior'); + await dealDamage(ctx.user, Math.floor(ctx.value)); + return new Left(`You were damaged by ${formatItems(BLOOD_ITEM, ctx.value)}!`); }, }, { name: 'random_up', description: 'Randomizes the attack value up by a maximum of `value`', - itemType: 'weapon', + type: 'weapon', format: (value) => `random +${value}`, - async onAttack(value, item, user, target, damage) { - if (!value) return new Right('A value is required for this behavior'); - return new Left(damage + Math.round(Math.random() * value)); + async onAttack(ctx) { + if (!ctx.value) return new Right('A value is required for this behavior'); + return new Left({ damage: ctx.damage + Math.round(Math.random() * ctx.value) }); }, }, { name: 'random_down', description: 'Randomizes the attack value down by a maximum of `value`', - itemType: 'weapon', + type: 'weapon', format: (value) => `random -${value}`, - async onAttack(value, item, user, target, damage) { - if (!value) return new Right('A value is required for this behavior'); - return new Left(damage - Math.round(Math.random() * value)); + async onAttack(ctx) { + if (!ctx.value) return new Right('A value is required for this behavior'); + return new Left({ damage: ctx.damage - Math.round(Math.random() * ctx.value) }); }, }, { name: 'lifesteal', description: 'Gain blood by stabbing your foes, scaled by `value`x', - itemType: 'weapon', - format: (value) => `lifesteal: ${value}x`, - async onAttack(value, item, user, target, damage) { - if (!value) return new Right('A value is required for this behavior'); - giveItem(user, BLOOD_ITEM, Math.floor(damage * value)); - return new Left(damage); + type: 'weapon', + format: (value) => `lifesteal ${value}x`, + async onAttack(ctx) { + if (!ctx.value) return new Right('A value is required for this behavior'); + const amt = Math.floor(ctx.damage * ctx.value); + giveItem(ctx.user, BLOOD_ITEM, amt); + return new Left({ + message: `You gained ${formatItems(BLOOD_ITEM, amt)} from your target!` + }); }, } ]; diff --git a/src/lib/rpg/pvp.ts b/src/lib/rpg/pvp.ts index 7bc89d5..62bdf3c 100644 --- a/src/lib/rpg/pvp.ts +++ b/src/lib/rpg/pvp.ts @@ -5,8 +5,8 @@ import { Client } from 'discord.js'; export const BLOOD_ID = -DefaultItems.BLOOD; export const BLOOD_ITEM = getDefaultItem(BLOOD_ID); export const MAX_HEALTH = BLOOD_ITEM.maxStack; -const BLOOD_GAIN_PER_HOUR = 5; -export const INVINCIBLE_TIMER = 1_000 * 60 * 60; +const BLOOD_GAIN_PER_HOUR = 2; +export const INVINCIBLE_TIMER = 1_000 * 60 * 30; export async function initHealth(user: string) { const isInitialized = await db('initHealth')