Allow classic commands in DMs even when disabled, add channel name to music end message, better check for API_TYPE, update docs

This commit is contained in:
Essem 2022-09-09 14:55:03 -05:00
parent 50bff306c0
commit e474d838b0
No known key found for this signature in database
GPG key ID: 7D497397CC3A2A8C
11 changed files with 94 additions and 35 deletions

8
app.js
View file

@ -71,12 +71,14 @@ const services = [
if (process.env.METRICS && process.env.METRICS !== "") services.push({ name: "prometheus", ServiceWorker: PrometheusWorker });
const intents = [
"guilds",
"guildVoiceStates",
"guildMessages",
"directMessages"
];
if (types.classic) intents.push("messageContent");
if (types.classic) {
intents.push("guilds");
intents.push("guildMessages");
intents.push("messageContent");
}
const Admiral = new Fleet({
BotWorker: Shard,

View file

@ -18,7 +18,7 @@ class StopCommand extends MusicCommand {
players.delete(this.channel.guild.id);
queues.delete(this.channel.guild.id);
this.success = true;
return "🔊 The current voice channel session has ended.";
return `🔊 The voice channel session in \`${this.connection.voiceChannel.name}\` has ended.`;
}
static description = "Stops the music";

View file

@ -1,15 +1,18 @@
# Config
esmBot uses environment variables for configuration. To make managing them easier, a `.env` file is included with the bot and can be used to load the variables on bot startup.
esmBot uses a mix of environment variables and JSON for configuration.
Here's an overview of the environment variables required to run the bot:
## Environment Variables (.env)
To make managing environment variables easier, an example `.env` file is included with the bot at `.env.example` and can be used to load the variables on startup.
### Required
- `NODE_ENV`: Used for tuning the bot to different environments. If you don't know what to set it to, leave it as is.
- `TOKEN`: Your bot's token. You can find this [here](https://discord.com/developers/applications) under your application's Bot tab.
- `DB`: The database connection string. By default the `sqlite` and `postgresql` protocols are available, but this can be expanded by putting proper DB driver scripts into `utils/database/`. You can also set this to `dummy` to make the bot not use a database at all.
- `OWNER`: Your Discord user ID. This is used for granting yourself access to certain management commands. Adding multiple users is supported by separating the IDs with a comma; however, this is not recommended for security purposes.
- `PREFIX`: The bot's default command prefix. Note that servers can set their own individual prefixes via the `prefix` command.
- `PREFIX`: The bot's default command prefix for classic commands. Note that servers can set their own individual prefixes via the `prefix` command.
Here's an overview of the variables that are not necessarily required for the bot to run, but can greatly enhance its functionality:
### Optional
These variables that are not necessarily required for the bot to run, but can greatly enhance its functionality:
- `STAYVC`: Set this to true if you want the bot to stay in voice chat after playing music/a sound effect. You can make it leave by using the stop command.
- `DBL`: An API token from [Top.gg](https://top.gg/). Unnecessary for most users since Top.gg tends to ban forks of bots like esmBot from their list.
@ -19,7 +22,60 @@ Here's an overview of the variables that are not necessarily required for the bo
- `TMP_DOMAIN`: The root domain/directory that the images larger than 8MB are stored at. Example: `https://projectlounge.pw/tmp`
- `THRESHOLD`: A filesize threshold that the bot will start deleting old files in `TEMPDIR` at.
- `METRICS`: The HTTP port to serve [Prometheus](https://prometheus.io/)-compatible metrics on.
- `API`: Set this to "none" if you want to process all images locally. Alternatively, set it to "ws" to use an image API server specified in the `image` block of `servers.json`, or "azure" to use the Azure Functions-based API.
- `API_TYPE`: Set this to "none" if you want to process all images locally. Alternatively, set it to "ws" to use an image API server specified in the `image` block of `config/servers.json`, or "azure" to use the Azure Functions-based API.
- `AZURE_URL`: Your Azure webhook URL. Only applies if `API` is set to "azure".
- `AZURE_PASS`: An optional password used for Azure requests. Only applies if `API` is set to "azure".
- `ADMIN_SERVER`: A server to limit owner-only commands to.
- `ADMIN_SERVER`: A Discord server/guild ID to limit owner-only commands such as eval to.
## JSON
The JSON-based configuration files are located in `config/`.
### commands.json
```js
{
"types": {
"classic": false, // Enable/disable "classic" (prefixed) commands, note that classic commands in direct messages will still work
"application": true // Enable/disable application commands (slash and context menu commands)
},
"blacklist": [
// Names of commands that you don't want the bot to load
]
}
```
### messages.json
```js
{
"emotes": [
// Discord emote strings to use in the "Processing... this may take a while" messages, e.g. "<a:processing:818243325891051581>" or "⚙️"
],
"messages": [
// Strings to use in the bot's activity message/playing status
]
}
```
### servers.json
```js
{
"lava": [ // Objects containing info for connecting to Lavalink audio server(s)
{
"name": "test", // A human-friendly name for the server
"url": "localhost:2333", // IP address/domain name and port for the server
"auth": "youshallnotpass", // Password/authorization code for the server
"local": false // Whether or not the esmBot "assets" folder is located next to the Lavalink jar file
}
],
"image": [ // Objects containing info for connecting to WS image server(s)
{
"server": "localhost", // IP address or domain name for the server
"auth": "verycoolpass100", // Password/authorization code for the server
"tls": false // Whether or not this is a secure TLS/wss connection
}
],
"searx": [
// URLs for Searx/SearXNG instances used for image/YouTube searches, e.g. "https://searx.projectlounge.pw"
// Note: instances must support getting results over JSON
]
}
```

View file

@ -1,5 +1,5 @@
# Custom Commands
esmBot has a flexible command handler, allowing you to create new commands and categories simply by creating new files. This page will provide a reference for creating new commands.
esmBot has a powerful and flexible command handler, allowing you to create new commands and categories simply by creating new files. This page will provide a reference for creating new commands.
## Directory Structure
The bot loads commands from subdirectories inside of the `commands` directory, which looks something like this by default:
@ -19,6 +19,9 @@ commands/
```
As you can see, each command is grouped into categories, which are represented by subdirectories. To create a new category, you can simply create a new directory inside of the `commands` directory, and to create a new command, you can create a new JS file under one of those subdirectories.
!!! tip
The `message` category is special; commands in here act as right-click context menu message commands instead of "classic" or slash commands.
## Commnand Structure
It's recommended to use the `Command` class located in `classes/command.js` to create a new command in most cases. This class provides various parameters and fields that will likely be useful when creating a command. Here is a simple example of a working command file:
```js
@ -46,7 +49,7 @@ The parameters available to your command consist of the following:
- `this.worker`: The ID of the current eris-fleet worker. This should be a number greater than or equal to 0.
- `this.ipc`: An eris-fleet [`IPC`](https://danclay.github.io/eris-fleet/classes/IPC.html) instance, useful for communication between worker processes.
- `this.origOptions`: The raw options object provided to the command by the command handler.
- `this.type`: The type of message that activated the command. Can be "classic" (a regular message) or "application" (slash commands).
- `this.type`: The type of message that activated the command. Can be "classic" (a regular message) or "application" (slash/context menu commands).
- `this.channel`: An Eris [`TextChannel`](https://abal.moe/Eris/docs/TextChannel) object of the channel that the command was run in, useful for getting info about a server and how to respond to a message.
- `this.author`: An Eris [`User`](https://abal.moe/Eris/docs/User) object of the user who ran the command, or a [`Member`](https://abal.moe/Eris/docs/Member) object identical to `this.member` if run in a server as a slash command.
- `this.member`: An Eris [`Member`](https://abal.moe/Eris/docs/Member) object of the server member who ran the command. When running the command outside of a server, this parameter is undefined when run as a "classic" command or a [`User`](https://abal.moe/Eris/docs/User) object identical to `this.author` when run as a slash command.
@ -59,10 +62,11 @@ Some options are only available depending on the context/original message type,
- `this.content`: A string of the raw content of the command message, excluding the prefix and command name.
- `this.reference`: An object that's useful if you ever decide to reply to a user inside the command. You can use [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) to combine your message content with this parameter.
The options only available with "application"/slash commands are listed below:
The options only available with application (slash and context menu) commands are listed below:
- `this.interaction`: An Eris [`CommandInteraction`](https://abal.moe/Eris/docs/CommandInteraction) object of the incoming slash command data.
- `this.optionsArray`: A raw array of command options. Should rarely be used.
- `this.success`: A boolean value that causes the bot to respond with a normal message when `true`, or an "ephemeral" message (a message that's only visible to the person who ran the command) when `false`.
Some static fields are also available and can be set depending on your command. These fields are listed below:
@ -80,6 +84,7 @@ static flags = [{
```
- `slashAllowed`: Specifies whether or not the command is available via slash commands.
- `directAllowed`: Specifies whether or not a command is available in direct messages.
- `adminOnly`: Specifies whether or not a command should be limited to the bot owner(s).
## The `run` Function
The main JS code of your command is specified in the `run` function. This function should return a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) of your command output, which is why the `run` function [is an async function by default](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function). The return value inside the `Promise` should be either a string or an object; you should return a string whenever you intend to reply with plain text, or an object if you intend to reply with something else, such as an embed or attachment.

View file

@ -16,7 +16,7 @@ You should then modify the `config/servers.json` file to change the IP addresses
```json
{
"lava": [
{ "name": "localhost", "url": "localhost:2333", "auth": "youshallnotpass", "local": true }
{ "name": "localhost", "url": "lavalink:2333", "auth": "youshallnotpass", "local": true }
],
"image": [
{ "server": "api", "auth": "verycoolpass100", "tls": false }

View file

@ -37,7 +37,7 @@ Choose the distro you're using below for insallation instructions.
```sh
sudo pacman -S git curl cmake pango ffmpeg npm imagemagick libvips sqlite3 libltdl noto-fonts-emoji gobject-introspection libcgif libimagequant meson
```
You'll also need to install [`ttf-ms-fonts`](https://aur.archlinux.org/packages/ttf-ms-fonts/) from the AUR.
You'll also need to install [`ttf-ms-win10-auto`](https://aur.archlinux.org/packages/ttf-ms-win10-auto/) from the AUR.
***

View file

@ -42,7 +42,7 @@ export default async (client, cluster, worker, ipc, member, oldChannel) => {
players.delete(connection.originalChannel.guild.id);
queues.delete(connection.originalChannel.guild.id);
skipVotes.delete(connection.originalChannel.guild.id);
client.createMessage(connection.originalChannel.id, "🔊 The current voice channel session has ended.");
client.createMessage(connection.originalChannel.id, `🔊 The voice channel session in \`${connection.originalChannel.name}\` has ended.`);
}
});
} else if (member.id === connection.host) {
@ -74,7 +74,7 @@ export default async (client, cluster, worker, ipc, member, oldChannel) => {
players.delete(connection.originalChannel.guild.id);
queues.delete(connection.originalChannel.guild.id);
skipVotes.delete(connection.originalChannel.guild.id);
client.createMessage(connection.originalChannel.id, "🔊 The current voice channel session has ended.");
client.createMessage(connection.originalChannel.id, `🔊 The voice channel session in \`${connection.originalChannel.name}\` has ended.`);
} else {
const randomMember = random(members);
players.set(connection.voiceChannel.guild.id, { player: connection.player, type: connection.type, host: randomMember.id, voiceChannel: connection.voiceChannel, originalChannel: connection.originalChannel, loop: connection.loop, shuffle: connection.shuffle, playMessage: connection.playMessage });
@ -92,7 +92,7 @@ export default async (client, cluster, worker, ipc, member, oldChannel) => {
players.delete(connection.originalChannel.guild.id);
queues.delete(connection.originalChannel.guild.id);
skipVotes.delete(connection.originalChannel.guild.id);
await client.createMessage(connection.originalChannel.id, "🔊 The current voice channel session has ended.");
await client.createMessage(connection.originalChannel.id, `🔊 The voice channel session in \`${connection.originalChannel.name}\` has ended.`);
}
}
};

View file

@ -78,10 +78,7 @@ class Shard extends BaseClusterWorker {
log("log", `Loading event from ${file}...`);
const eventArray = file.split("/");
const eventName = eventArray[eventArray.length - 1].split(".")[0];
if (eventName === "messageCreate" && !types.classic) {
log("warn", `Skipped loading event from ${file} because classic commands are disabled...`);
continue;
} else if (eventName === "interactionCreate" && !types.application) {
if (eventName === "interactionCreate" && !types.application) {
log("warn", `Skipped loading event from ${file} because application commands are disabled`);
continue;
}

View file

@ -14,17 +14,19 @@ class AwaitRejoin extends EventEmitter {
this.listener = (member, newChannel) => this.verify(member, newChannel);
this.bot.on("voiceChannelJoin", this.listener);
this.bot.on("voiceChannelSwitch", this.listener);
setTimeout(() => this.stop(), 10000);
this.stopTimeout = setTimeout(() => this.stop(), 10000);
this.checkInterval = setInterval(() => this.verify({ id: memberID }, channel, true), 1000);
}
verify(member, channel, checked) {
if (this.channel.id === channel.id) {
if ((this.member === member.id && this.channel.voiceMembers.has(member.id)) || (this.anyone && !checked)) {
clearTimeout(this.stopTimeout);
this.rejoined = true;
this.stop(member);
return true;
} else if (this.anyone && (!checked || this.channel.voiceMembers.size > 1)) {
clearTimeout(this.stopTimeout);
this.rejoined = true;
this.stop(random(this.channel.voiceMembers.filter((i) => i.id !== this.bot.user.id && !i.bot)));
return true;

View file

@ -11,7 +11,7 @@ import EventEmitter from "events";
// only requiring this to work around an issue regarding worker threads
const nodeRequire = createRequire(import.meta.url);
if (process.env.API_TYPE === "none") {
if (!process.env.API_TYPE || process.env.API_TYPE === "none") {
nodeRequire(`../../build/${process.env.DEBUG && process.env.DEBUG === "true" ? "Debug" : "Release"}/image.node`);
}

View file

@ -23,7 +23,7 @@ export async function checkStatus() {
const response = await request(`http://${node.url}/version`, { headers: { authorization: node.auth } }).then(res => res.body.text());
if (response) newNodes.push(node);
} catch {
logger.error(`Failed to get status of Lavalink node ${node.host}.`);
logger.error(`Failed to get status of Lavalink node ${node.url}.`);
}
}
nodes = newNodes;
@ -230,8 +230,10 @@ export async function nextSong(client, options, connection, track, info, music,
const newTrack = await connection.node.rest.decode(newQueue[0]);
nextSong(client, options, connection, newQueue[0], newTrack, music, voiceChannel, host, player.loop, player.shuffle, track);
try {
if (newQueue[0] !== track && playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (newQueue[0] !== track && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();
if (options.type === "classic") {
if (newQueue[0] !== track && playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (newQueue[0] !== track && player.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();
}
} catch {
// no-op
}
@ -241,19 +243,14 @@ export async function nextSong(client, options, connection, track, info, music,
players.delete(voiceChannel.guild.id);
queues.delete(voiceChannel.guild.id);
skipVotes.delete(voiceChannel.guild.id);
const content = "🔊 The current voice channel session has ended.";
const content = `🔊 The voice channel session in \`${voiceChannel.name}\` has ended.`;
if (options.type === "classic") {
await client.createMessage(options.channel.id, content);
} else {
await options.interaction.createMessage(content);
}
try {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (player?.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();
} catch {
// no-op
}
} else {
}
if (options.type === "classic") {
try {
if (playingMessage.channel.messages.has(playingMessage.id)) await playingMessage.delete();
if (player?.playMessage.channel.messages.has(player.playMessage.id)) await player.playMessage.delete();