Skip to content
This repository has been archived by the owner on Jul 7, 2024. It is now read-only.

Commit

Permalink
feat: added autocomplete to options (#22)
Browse files Browse the repository at this point in the history
Co-authored-by: zleyyij <[email protected]>
  • Loading branch information
zleyyij and zleyyij authored Aug 30, 2023
1 parent fe94a2b commit 1c7fed9
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 10 deletions.
35 changes: 35 additions & 0 deletions src/core/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,41 @@ client.once(Events.ClientReady, async () => {
);
});

// when the bot receives an autocomplete interaction, figure out which module it's autocompleting for, and
// call that module's autocomplete code
client.on(Events.InteractionCreate, async interaction => {
if (!interaction.isAutocomplete()) {
return;
}
const input = interaction.options.getFocused(true);
const command = interaction.commandName;
const group = interaction.options.getSubcommandGroup();
const subcommand = interaction.options.getSubcommand(false);

const commandPath: string[] = [];
// the command is always defined
commandPath.push(command);
if (group !== null) {
commandPath.push(group);
}
if (subcommand !== null) {
commandPath.push(subcommand);
}
// non-null-assertion: For an autocomplete interaction to happen, a module
// needs to be resolved once before, then executed
const module: RootModule | SubModule =
resolveModule(commandPath).foundModule!;
// find the option that's currently getting autocompleted
const option = module.options.find(option => option.name === input.name);
// this should never be an issue, but just in case
if (option?.autocomplete === undefined) {
return;
}
// use the autocomplete function defined with the options, and return
const autocompleteValues = await option.autocomplete(input.value);
interaction.respond(autocompleteValues);
});

// Login to discord
client.login(botConfig.authToken);

Expand Down
22 changes: 18 additions & 4 deletions src/core/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {botConfig} from './config.js';
import {
APIApplicationCommandOptionChoice,
APIEmbed,
ApplicationCommandOptionChoiceData,
ChatInputCommandInteraction,
CommandInteractionOption,
} from 'discord.js';
Expand Down Expand Up @@ -77,14 +78,27 @@ export interface ModuleInputOption {
*
* Autocomplete *cannot* be set to true if you have defined choices, and this
* only applies for string, integer, and number options.
*
* **THIS FUNCTIONALITY IS NOT YET IMPLEMENTED**
*/
choices?: APIApplicationCommandOptionChoice[];
/**
* TODO: autocomplete docstring and other thing
* https://discordjs.guide/slash-commands/autocomplete.html#responding-to-autocomplete-interactions
* If you need dynamic option generation, this is for you.
*
* This function is passed whatever the user has currently typed so far.
* It should return an array of {@link ApplicationCommandOptionChoiceData}.
* @example
* ```
* // this very basic example takes the input, and searches an array for matches
* const options = ['foo', 'bar', 'bat']
* const autocompleteFunction = input =>
* // this oneliner removes any options
* options.filter(option => option.startsWith(value)).map(option => {name: option, value: option});
* ```
*
* @doc https://discordjs.guide/slash-commands/autocomplete.html#responding-to-autocomplete-interactions
*/
autocomplete?: (
currentlyTyped: string
) => Promise<ApplicationCommandOptionChoiceData[]>;
}

interface ModuleConfig {
Expand Down
12 changes: 8 additions & 4 deletions src/core/slash_commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -254,17 +254,21 @@ function setOptionFieldsForCommand(
.setDescription(setFromModuleOption.description)
.setRequired(setFromModuleOption.required ?? false);
if (
setFromModuleOption.choices !== undefined &&
[
ModuleOptionType.Integer,
ModuleOptionType.Number,
ModuleOptionType.String,
].includes(setFromModuleOption.type)
) {
// this could be integer, number, or string
(option as SlashCommandStringOption).addChoices(
...(setFromModuleOption.choices! as APIApplicationCommandOptionChoice<string>[])
);
if (setFromModuleOption.choices !== undefined) {
(option as SlashCommandStringOption).addChoices(
...(setFromModuleOption.choices! as APIApplicationCommandOptionChoice<string>[])
);
}
if (setFromModuleOption.autocomplete !== undefined) {
(option as SlashCommandStringOption).setAutocomplete(true);
}
}
return option;
}
Expand Down
26 changes: 24 additions & 2 deletions src/modules/factoids/factoids.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {request} from 'undici';

import * as util from '../../core/util.js';
import {
ApplicationCommandOptionChoiceData,
Attachment,
BaseMessageOptions,
ChatInputCommandInteraction,
Expand Down Expand Up @@ -158,7 +159,7 @@ class FactoidCache {
});
// return true or false depending on whether or not a factoid was deleted
if (deletionResult.deletedCount === 1) {
// The factoid was succesfully deleted
// The factoid was successfully deleted
return true;
}
// Nothing got deleted
Expand Down Expand Up @@ -213,6 +214,22 @@ factoid.onInitialize(async () => {
});
});

/** This function is used for slash command option autocomplete */
async function factoidAutocomplete(
input: string
): Promise<ApplicationCommandOptionChoiceData[]> {
// https://www.mongodb.com/docs/manual/reference/operator/query/regex/#perform-case-insensitive-regular-expression-match
const matches = factoidCache!.factoidCollection.find({
triggers: {$regex: `(${input})(.*)`},
});

// format results in the appropriate autocomplete format
const formattedMatches = await matches
.map(factoid => ({name: factoid.triggers[0], value: factoid.triggers[0]}))
.toArray();
return formattedMatches;
}

/**
* @param interaction The interaction to send the confirmation to
* @param factoidName The factoid name to confirm the deletion of
Expand Down Expand Up @@ -247,6 +264,7 @@ factoid.registerSubModule(
name: 'factoid',
description: 'The factoid to fetch',
required: true,
autocomplete: factoidAutocomplete,
},
],
async (args, interaction) => {
Expand Down Expand Up @@ -410,6 +428,7 @@ factoid.registerSubModule(
name: 'factoid',
description: 'The factoid to forget',
required: true,
autocomplete: factoidAutocomplete,
},
],
async (args, interaction) => {
Expand Down Expand Up @@ -455,6 +474,7 @@ factoid.registerSubModule(
name: 'factoid',
description: 'The factoid to fetch the json of',
required: true,
autocomplete: factoidAutocomplete,
},
],
async (args, interaction) => {
Expand All @@ -472,7 +492,7 @@ factoid.registerSubModule(
}

// Converts the JSON contents to a buffer so it can be sent as an attachment
const serializedFactoid = JSON.stringify(locatedFactoid);
const serializedFactoid = JSON.stringify(locatedFactoid.message);
const files = Buffer.from(serializedFactoid);

await util
Expand Down Expand Up @@ -504,6 +524,7 @@ trigger.registerSubmodule(
name: 'factoid',
description: 'The factoid to add the trigger to',
required: true,
autocomplete: factoidAutocomplete,
},
{
type: util.ModuleOptionType.String,
Expand Down Expand Up @@ -598,6 +619,7 @@ trigger.registerSubmodule(
name: 'trigger',
description: 'The trigger to remove',
required: true,
autocomplete: factoidAutocomplete,
},
],
async args => {
Expand Down

0 comments on commit 1c7fed9

Please sign in to comment.