🇺🇸 Reading in English / 🇪🇸 Leer en Español
A powerful data-driven Discord bot for documenting resources and providing quick messages (tags), supporting categories, nested tags, resource/tag commands and more.
Enki has two types of data: Tags and Resources. In this example we will be configuring Enki for a community where we offer products.
Tip
Every Enki config uses HOCON (.conf
). If you wish to learn more about it, check out the official documentation.
A Tag is a message (that can have content, embed and buttons) that is triggered by a command. They are loaded by Tag Categories, defined in the content/tag-atlas.conf
file.
In Discord:
- The tag category is loaded as a command with an option to select the tag.
- Optionally, each tag inside the category can include an alias as a command.
Tip
For example, you can have a tag category for frequently asked questions in your server, or another one for videos, each on their own command (/faq <tag>
and /video <tag>
, respectively).
The Tag Atlas is an array of tag categories, each of which define the path, the category command, and what data to use while autocompleting.
content/tag-atlas.conf
[
{
// Glob array to match tags from this category, relative to current directory (config).
// See the "Globs" section for more information about globs.
tags: ["tags/faq/**.conf"],
// Command to use to search for tags of this category.
command: {
// Command Schema with "tag" option, for the tag to search
},
// What data to use to fuzzy search for tags while autocompleting, apart from the tag's keywords.
searchBy: {
// Whether to search by the tag's content.
content: true,
// Whether to search by the tag's embeds titles and descriptions.
embeds: true
},
// A message to show when this command is called without any tags or an invalid one.
// Don't include it or set it to `false` if you don't need it.
// ^ On that case, the tag option will be required, and any invalid tags will show an error.
message: {
// Message Schema with buttons
}
}
]
After defining a tag category, you can now create your tags inside the path you provided.
content/tags/faq/my-question.conf
{
// What keywords the user can use to trigger this tag, requires at least one. Will also be used for autocompletion.
// Only the first keyword will be shown for autocompletion, typing the others will show the first one.
// WARN: The first keyword will be used as the tag's ID. While more than one tag can have the same "secondary" keywords,
// the ID must be unique.
keywords: ["my-question"],
// Optionally, define a command that will trigger this command (an alias of /<category> <this tag>).
// Don't include it or set it to `false` if you don't need it.
command: {
// Command schema without options.
name: "my-question",
description: "Command that triggers the my-question tag."
},
// Optional, a fancy tag name, shown alongside its message summary in autocompletion.
displayName: "My Question",
message: {
// Message schema with buttons.
}
}
You can then use:
/faq my-question
- To trigger the "my-question" tag./my-question
- Same as above, thanks to thecommand
property inmy-question.conf
.
A Resource is an object that contains tags and tag categories. They are defined in the content/resource-atlas.conf
file.
In Discord:
- Each resource is loaded as the main command.
- "Direct" tags are loaded as subcommands.
- Tag categories are loaded as subcommands (with an option to select the tag).
- Optionally, each tag inside a tag category can include an alias as a subcommand (in the main command).
Tip
For example, you can have a "my-product" resource, letting you include videos, faqs, etc per product.
The resource atlas is located in the content/resources-atlas.conf
file. It contains an array of resources.
[
{
// The entry point for this resource's tags and tag categories.
command: {
name: "my-product",
description: "Command that triggers the my-product resource."
},
// Glob pattern array for the tags of this resource, relative to current path.
// Warn: Every tag included here must have a command specified, which will be used as a subcommand.
tags: ["my-product/**.conf"],
// Follows the same format as a tag atlas
categories: [
{
// Relative to current path.
tags: [
"my-product/faq/**.conf"
],
// While the option is called command, it'd actually be a subcommand inside the resource's command
command: {
// Command schema with option "tag"
},
searchBy: {
content: true,
embeds: true
},
message: {
// Message Schema with buttons
}
}
]
}
]
Now you can use:
/my-product <tag>
to query a tag fromconfig/resources/my-product
./my-product faq [tag]
to query a tag fromconfig/resources/my-product/faq
.
"Globs" are patterns to match files using special syntax. The following characters are the most common special ones.
Tip
You can view a more detailed explanation here.
For context there, Enki only uses the { absolute: true }
option.
*
Matches 0 or more characters in a single path portion. For instance,faq/*.conf
matches all.conf
files only in thefaq
folder, not recursively.**
Same as above, but matches recursively.!(pattern|pattern)
Matches anything that does not match any of the patterns provided. You can use it alongside another glob in the array to exclude files, for instance["*.conf", "!*_ignore.conf"]
will match every file, except those ending in_ignore.conf
.?
Matches any 1 character. For instance,faq/?.conf
will matchfaq/A.conf
,faq/B.conf
, etc; but notfaq/AB.conf
(because it has two characters).
There are multiple formats Enki re-uses across its configuration. These are called "Schemas".
The message schema is used when configuring what message will be sent to the user.
{
// WARNING: Even though every option is optional, either content or embeds must be provided (you can't omit both).
// The message content, optional.
content: "Showing faqs",
// The message embeds, optional.
// Check https://discord.com/developers/docs/resources/message#embed-object-embed-limits to see the character limits on embeds.
// Enki will also make sure the embeds are valid, including the "sum of characters" part.
// Every part is optional, but it needs to have at least a title, an author, a description, a thumbnail, 1 field, an image or a footer.
embeds: [{
title: "Embed title",
description: "Embed Description",
url: "https://embed.url/",
// https://www.iso.org/iso-8601-date-and-time-format.html
timestamp: "2024-07-08",
color: "#FFFFFF",
footer: {
text: "Footer's text",
// Optional
icon: "https://footer-icon.url/",
},
image: "https://image.url/",
thumbnail: "https://thumbnail.url/",
author: {
name: "Author's name",
// Optional
url: "https://author.url/",
// Optional
icon: "https://author-icon.url/",
},
fields: [
{
name: "Field 1 Name",
value: "Field 1 Value",
// Optional
inline: false,
},
{
name: "Field 2 Name",
value: "Field 2 Value",
inline: false,
}
]
}],
// Array of file paths (not globs) to send those files alongside the message, optional.
// These file paths can be:
// - Relative to the current directory, if they start with ./ (e.g., "./relative/path/file.png").
// - Absolute paths starting from root (folder containing src, README, etc), if they start with /
// (e.g., "/absolute/path/to/file.png").
files: [
"./relative/path/file.png",
"/absolute/file.png"
],
// The message buttons, optional.
// WARNING: Not every message option will read buttons. Check the doc first.
buttons: [
{
// Either "url", "tag" or "message" type. See next buttons for examples
type: "url",
// The button label.
label: "Link 1",
// The button's emoji, optional.
emoji: "🔗",
// The button url.
url: "https://example.com",
},
{
type: "tag",
label: "See related tag",
emoji: "❓",
// Tag reference schema, see the end to check all the available options.
tag: {
},
},
{
type: "message",
label: "See this other message",
emoji: "👀",
// Used internally, must be unique across messages of this button.
id: "someMsg",
message: {
// Message schema without buttons
content: "Other message content",
embeds: [{
title: "Other embed",
// etc
}]
}
}
],
// An optional short summary of the message, used for autocompletion.
// If not specified, a summary will be generated from the message's content and/or embed titles.
summary: "My message",
// Variants of this message that the querier can select manually, available for tag messages.
// If specified, the "variant" option in config will be attached to the tag's options.
// Can be used to select localized versions, change the answer depending on the user's software version, etc.
// You can make use of HOCON's `include` syntax to create variants in other files.
variants: {
spanish: {
// Message schema with buttons.
},
}
}
The command schema is used when configuring sub/commands and their options.
{
// The command's name.
name: "my-command",
// The command's description.
description: "My command.",
// Optional, localization data for Discord clients on a specific language.
// See https://discord-api-types.dev/api/0.37.92/discord-api-types-rest/common/enum/Locale#Index for available locales.
locale: {
SpanishES: {
name: "comando",
description: "Mi comando."
}
},
// The command's options. Some commands will have it, some don't.
// The exact options also depend on the context.
options: {
someOption: {
name: "option",
description: "This option's description.",
// Optional as well.
locale: {
SpanishES: {
name: "nombre",
description: "La descripción de esta opción."
}
}
}
},
// Optional, the command's integration types, overriding the config's defaultIntegrations.
// In essence, where the command can be installed.
// See https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationIntegrationType.
integrationTypes: [
"GuildInstall", // Will show up when the app is installed on a Guild.
"UserInstall", // Will show up when the app is installed on a User.
],
// Optional, the command's supported interaction contexts, overriding the config's defaultContexts.
// In essence, where the command can be used after being installed with one of its supported integration types.
// See https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionContextType.
interactionContexts: [
"BotDM", // Can be used in the bot's DM.
"PrivateChannel", // Can be used in a private channel including DM Groups, after being "UserInstall"ed.
"Guild", // Can be used in a guild, after being "GuildInstall"ed.
],
}
Caution
Your application must support the integrationTypes
you want to use. You can change this on your app's settings,
on the Installation page under Installation Contexts.
If you use an integration type that your application doesn't support, an error will be thrown.
The tag reference schema is used when "referring" to a tag somewhere and triggering its message. Currently only used for tag buttons.
Caution
Tag references are checked on startup to see if the tag/category/resource exists. If it doesn't, an error will be thrown. This includes referring to a category message when the category doesn't have one configured.
Tip
As a reminder:
- A tag category and resource IDs are their command names.
- A tag ID is its first keyword.
Currently available options:
Triggering a category message.
{
category: "my-category"
}
Triggering a tag from a category.
{
category: "my-category",
tag: "my-tag"
}
Triggering a tag category message inside a resource.
{
resource: "resource",
category: "tag-category",
}
Triggering a tag from a resource.
{
resource: "resource",
tag: "my-tag"
}
Triggering a tag from a tag category inside a resource.
{
resource: "resource",
category: "tag-category",
tag: "my-tag"
}
The config.conf
file lets you configure the behavior or messages of the bot.
{
// Bot's token
token: "TOKEN",
// Whether the bot should update its commands when it starts. You can set it to `false` while testing.
updateCommands: true,
// Defines where the atlases are.
source: {
// Either "local" or "git".
type: "",
// The folder where the tag and resource atlases are contained.
// For "local" type, it's relative to the Enki root (where the package.json file is), and defaults to "content".
// For "git" type, it's relative to the git repository root, doesn't default to anything, not specifying it loads them directly from root.
contentFolder: "content",
// The following options are only available for the "git" type.
// The url of the git repository. Must end in `.git`.
gitUrl: "",
// Optionally, the folder where repository clones will be stored. Defaults to __clone__.
cloneFolder: "",
},
errors: {
// Message schema that will be triggered when the user specifies an unknown tag.
tagNotFound: {
content: "Unknown tag."
},
// Message schema that will be shown to the user for general errors.
generic: {
content: "An error has happened, please contact an admin."
}
},
options: {
// Command option schema to select a variant, for tags that have it.
variant: {
name: "variant",
description: "Select a specific variant of this tag."
},
// Command option schema to choose whether the output of the tag should be ephemeral, useful
// to check what a tag contains before triggering it.
hide: {
name: "hide",
description: "Whether to only show this tag to yourself."
}
},
// Default integration types for all commands, unless they explicitly override it.
// In essence, where the command can be installed.
// See https://discord-api-types.dev/api/discord-api-types-v10/enum/ApplicationIntegrationType.
// Optional for backwards compatibility, defaults to ["GuildInstall"]. Will be required in next major version.
defaultIntegrations: [
"GuildInstall", // Will show up when the app is installed on a Guild.
"UserInstall", // Will show up when the app is installed on a User.
],
// Default interaction contexts for all commands, unless they explicitly override it.
// In essence, where the command can be used after being installed with one of its supported integration types.
// See https://discord-api-types.dev/api/discord-api-types-v10/enum/InteractionContextType.
// Optional for backwards compatibility, defaults to ["Guild", "BotDM", "PrivateChannel"]. Will be required in next major version.
defaultContexts: [
"BotDM", // Can be used in the bot's DM.
"PrivateChannel", // Can be used in a private channel including DM Groups, after being "UserInstall"ed.
"Guild", // Can be used in a guild, after being "GuildInstall"ed.
],
}
Caution
Your application must support the defaultIntegrationTypes
you want to use. You can change this on your app's settings,
on the Installation page under Installation Contexts.
If you use an integration type that your application doesn't support, an error will be thrown.
Enki requires at least Node.js 20.
- Run
npm install
to install the dependencies. - Run
npm run build
to build the bot. - Fill your
config.conf
file and fill your atlases. - Run
npm run start
to start the bot.
Optionally, you can run npm run start:parse
to only start the "parsing" part of the bot, which will only parse and assert your content, but not run the bot.