Compare commits

...

4 Commits

Author SHA1 Message Date
Jill 8f0fa740d7 remove redundant autocomplete step 2023-11-22 16:45:55 +03:00
Jill e5b049d0b2 refactor autocompletes 2023-11-22 16:32:54 +03:00
Jill 3d5a7b041d weapon inv stealing fix 2023-11-22 15:05:18 +03:00
Jill 7a4b9e1726 finish assigning and removing behaviors 2023-11-22 14:52:15 +03:00
14 changed files with 133 additions and 79 deletions

View File

@ -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 <t:${Math.floor((Date.now() + invinTimer) / 1000)}:R> (or if they perform an action first)!`);

View File

@ -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 { 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: 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;

View File

@ -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 { 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 <t:${Math.floor(nextUsableAt / 1000)}:R>` : ''}`);
},
autocomplete: async (interaction: AutocompleteInteraction) => {
const focused = interaction.options.getFocused(true);
if (focused.name === 'station') {
const found = (await Promise.all(
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<CustomCraftingRecipe>('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;

View File

@ -35,5 +35,5 @@ export default {
changeCounterInteraction(interaction, member, -amount, type);
},
autocomplete: counterAutocomplete
autocomplete: counterAutocomplete,
} satisfies Command;

View File

@ -35,5 +35,5 @@ export default {
changeCounterInteraction(interaction, member, amount, type);
},
autocomplete: counterAutocomplete
autocomplete: counterAutocomplete,
} satisfies Command;

View File

@ -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 { set } from '../lib/autocomplete';
//function extendOption(t: string) {
// return {name: t, value: t};
@ -254,7 +255,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<ItemBehavior>('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<ItemBehavior>('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') {
@ -302,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: 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));
@ -319,7 +336,24 @@ export default {
}
}
await interaction.respond(foundBehaviors.map(b => ({name: `${b.itemType}:${b.name} - ${b.description}`, value: b.name})));
}
}
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 [];
const behaviors = await db<ItemBehavior>('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.toLowerCase()));
return foundBehaviors.map(b => ({
name: `${b.behavior.itemType}:${formatBehavior(b.behavior, b.value)} - ${b.behavior.description}`,
value: b.behavior.name
}));
},
}),
} satisfies Command;

View File

@ -38,5 +38,5 @@ export default {
changeLinkedCounterInteraction(interaction, member, amount, type);
},
autocomplete: linkedCounterAutocomplete
autocomplete: linkedCounterAutocomplete,
} satisfies Command;

View File

@ -1,4 +1,4 @@
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';
@ -235,24 +235,20 @@ export default {
});
},
autocomplete: async (interaction) => {
const focused = interaction.options.getFocused(true);
autocomplete: async (interaction: AutocompleteInteraction) => {
const focused = interaction.options.getFocused();
if (focused.name === 'recipe') {
const customRecipes = await db<CustomCraftingRecipe>('customCraftingRecipes');
const customRecipes = await db<CustomCraftingRecipe>('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;

View File

@ -38,5 +38,5 @@ export default {
changeLinkedCounterInteraction(interaction, member, -amount, type);
},
autocomplete: linkedCounterAutocomplete
autocomplete: linkedCounterAutocomplete,
} satisfies Command;

View File

@ -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);
}

27
src/lib/autocomplete.ts Normal file
View File

@ -0,0 +1,27 @@
import { AutocompleteInteraction, ApplicationCommandOptionChoiceData } from 'discord.js';
import * as log from './log';
export type Autocomplete = (interaction: AutocompleteInteraction) => Promise<ApplicationCommandOptionChoiceData<string | number>[]>
export function set(fns: Record<string, Autocomplete>): 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<void> {
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([]);
}
};
}

View File

@ -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<Counter>('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 }));
};
}

View File

@ -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<CustomItem, 'guild'> & { behaviors?: Omit<ItemBehavior, 'item'>[] };
@ -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() }));
};
}

View File

@ -1,9 +1,10 @@
import { Collection, SlashCommandBuilder, CommandInteraction, Client } from 'discord.js';
import { Autocomplete } from '../lib/autocomplete';
export interface Command {
data: Pick<SlashCommandBuilder, 'toJSON' | 'name'>,
execute: (interaction: CommandInteraction) => Promise<unknown>,
autocomplete?: (interaction: AutocompleteInteraction) => Promise<unknown>,
autocomplete?: Autocomplete,
onClientReady?: (client: Client) => Promise<unknown>,
serverWhitelist?: string[],
}