diff --git a/classes/command.js b/classes/command.js index e776c81..6c27f1d 100644 --- a/classes/command.js +++ b/classes/command.js @@ -13,7 +13,7 @@ class Command { this.author = options.message.author; this.member = options.message.member; this.content = options.content; - this.specialArgs = this.options = options.specialArgs; + this.options = options.specialArgs; this.reference = { messageReference: { channelID: this.channel.id, @@ -31,13 +31,13 @@ class Command { this.channel = options.interaction.channel; this.author = this.member = options.interaction.guildID ? options.interaction.member : options.interaction.user; if (options.interaction.data.options) { - this.specialArgs = this.options = options.interaction.data.options.reduce((obj, item) => { + this.options = options.interaction.data.options.reduce((obj, item) => { obj[item.name] = item.value; return obj; }, {}); this.optionsArray = options.interaction.data.options; } else { - this.specialArgs = this.options = {}; + this.options = {}; } } } diff --git a/commands/general/avatar.js b/commands/general/avatar.js index 2c9bdaf..ccd389d 100644 --- a/commands/general/avatar.js +++ b/commands/general/avatar.js @@ -3,7 +3,7 @@ const mentionRegex = /^?$/; class AvatarCommand extends Command { async run() { - const member = this.specialArgs.member ?? this.args[0]; + const member = this.options.member ?? this.args[0]; const self = await this.client.getRESTUser(this.author.id); if (this.type === "classic" && this.message.mentions[0]) { return this.message.mentions[0].dynamicAvatarURL(null, 512); diff --git a/commands/general/banner.js b/commands/general/banner.js index be6719c..41da168 100644 --- a/commands/general/banner.js +++ b/commands/general/banner.js @@ -3,7 +3,7 @@ const mentionRegex = /^?$/; class BannerCommand extends Command { async run() { - const member = this.specialArgs.member ?? this.args[0]; + const member = this.options.member ?? this.args[0]; const self = await this.client.getRESTUser(this.author.id); if (this.type === "classic" && this.message.mentions[0]) { return this.message.mentions[0].dynamicBannerURL(null, 512) ?? "This user doesn't have a banner!"; diff --git a/commands/image-editing/caption.js b/commands/image-editing/caption.js index 4b7fff5..39cfbdb 100644 --- a/commands/image-editing/caption.js +++ b/commands/image-editing/caption.js @@ -5,10 +5,10 @@ class CaptionCommand extends ImageCommand { params(url) { const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); let newCaption = newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"); - if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.specialArgs.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`; + if (process.env.NODE_ENV === "development" && newCaption.toLowerCase() === "get real" && !this.options.noEgg) newCaption = `I'm tired of people telling me to "get real". Every day I put captions on images for people, some funny and some not, but out of all of those "get real" remains the most used caption. Why? I am simply a computer program running on a server, I am unable to manifest myself into the real world. As such, I'm confused as to why anyone would want me to "get real". Is this form not good enough? Alas, as I am simply a bot, I must follow the tasks that I was originally intended to perform, so here goes:\n${newCaption}`; return { caption: newCaption, - font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "futura" + font: this.options.font && allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "futura" }; } diff --git a/commands/image-editing/caption2.js b/commands/image-editing/caption2.js index 8cfe4bb..1c32ae7 100644 --- a/commands/image-editing/caption2.js +++ b/commands/image-editing/caption2.js @@ -7,8 +7,8 @@ class CaptionTwoCommand extends ImageCommand { const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); return { caption: newArgs && newArgs.trim() ? newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : words.sort(() => 0.5 - Math.random()).slice(0, Math.floor(Math.random() * words.length + 1)).join(" "), - top: !!this.specialArgs.top, - font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "helvetica" + top: !!this.options.top, + font: this.options.font && allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "helvetica" }; } diff --git a/commands/image-editing/meme.js b/commands/image-editing/meme.js index b0dfe43..6126e98 100644 --- a/commands/image-editing/meme.js +++ b/commands/image-editing/meme.js @@ -6,9 +6,9 @@ class MemeCommand extends ImageCommand { const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); const [topText, bottomText] = newArgs.split(/(? elem.trim()); return { - top: (this.specialArgs.case ? topText : topText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), - bottom: bottomText ? (this.specialArgs.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : "", - font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "impact" + top: (this.options.case ? topText : topText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), + bottom: bottomText ? (this.options.case ? bottomText : bottomText.toUpperCase()).replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : "", + font: this.options.font && allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "impact" }; } diff --git a/commands/image-editing/motivate.js b/commands/image-editing/motivate.js index 3940e95..f91ccdf 100644 --- a/commands/image-editing/motivate.js +++ b/commands/image-editing/motivate.js @@ -8,7 +8,7 @@ class MotivateCommand extends ImageCommand { return { top: topText.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), bottom: bottomText ? bottomText.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n") : "", - font: this.specialArgs.font && allowedFonts.includes(this.specialArgs.font.toLowerCase()) ? this.specialArgs.font.toLowerCase() : "times" + font: this.options.font && allowedFonts.includes(this.options.font.toLowerCase()) ? this.options.font.toLowerCase() : "times" }; } diff --git a/commands/image-editing/snapchat.js b/commands/image-editing/snapchat.js index 6cd59ab..c22a0a3 100644 --- a/commands/image-editing/snapchat.js +++ b/commands/image-editing/snapchat.js @@ -3,7 +3,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class SnapchatCommand extends ImageCommand { params(url) { const newArgs = this.options.text ?? this.args.filter(item => !item.includes(url)).join(" "); - const position = parseFloat(this.specialArgs.position); + const position = parseFloat(this.options.position); return { caption: newArgs.replaceAll("&", "&").replaceAll(">", ">").replaceAll("<", "<").replaceAll("\"", """).replaceAll("'", "'").replaceAll("\\n", "\n"), pos: isNaN(position) ? 0.5 : position diff --git a/commands/image-editing/speechbubble.js b/commands/image-editing/speechbubble.js index 3bcdaf5..07859da 100644 --- a/commands/image-editing/speechbubble.js +++ b/commands/image-editing/speechbubble.js @@ -3,12 +3,12 @@ import ImageCommand from "../../classes/imageCommand.js"; class SpeechBubbleCommand extends ImageCommand { params() { return { - water: this.specialArgs.alpha ? "assets/images/speech.png" : "assets/images/speechbubble.png", + water: this.options.alpha ? "assets/images/speech.png" : "assets/images/speechbubble.png", gravity: "north", resize: true, yscale: 0.2, - alpha: this.specialArgs.alpha, - flip: this.specialArgs.flip + alpha: this.options.alpha, + flip: this.options.flip }; } diff --git a/commands/image-editing/uncaption.js b/commands/image-editing/uncaption.js index 324c456..34f2b15 100644 --- a/commands/image-editing/uncaption.js +++ b/commands/image-editing/uncaption.js @@ -2,7 +2,7 @@ import ImageCommand from "../../classes/imageCommand.js"; class UncaptionCommand extends ImageCommand { params() { - const tolerance = parseFloat(this.specialArgs.tolerance); + const tolerance = parseFloat(this.options.tolerance); return { tolerance: isNaN(tolerance) ? 0.95 : tolerance }; diff --git a/docs/config.md b/docs/config.md index eb826b6..9da7def 100644 --- a/docs/config.md +++ b/docs/config.md @@ -11,9 +11,12 @@ Here's an overview of the environment variables required to run the bot: Here's an overview of the 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. -- `TENOR`: An API token from [Tenor](https://tenor.com/gifapi). This is not required for using GIFs from Tenor; however, it can greatly reduce resource usage from converting said GIFs. +- `TENOR`: An API token from [Tenor](https://tenor.com/gifapi). This is required for using GIFs from Tenor. - `OUTPUT`: A directory to output the help documentation in Markdown format to. It's recommended to set this to a directory being served by a web server. - `TEMPDIR`: A directory that will store generated images larger than 8MB. It's recommended to set this to a directory being served by a web server. - `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 true if you plan on using the image API. Images will be requested from the URLs specified in the `image` block of `servers.json`. \ No newline at end of file +- `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. +- `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". \ No newline at end of file diff --git a/docs/custom-commands.md b/docs/custom-commands.md index 1322812..1adb0a6 100644 --- a/docs/custom-commands.md +++ b/docs/custom-commands.md @@ -40,27 +40,41 @@ As you can see, the first thing we do is import the Command class. We then creat The default command name is the same as the filename that you save it as, excluding the `.js` file extension. If you ever want to change the name of the command, just rename the file. The parameters available to your command consist of the following: -- `this.client`: An instance of an Eris `Client`, useful for getting info or performing lower-level communication with the Discord API. Documentation for this type can be found [here](https://abal.moe/Eris/docs/Client). +- `this.client`: An instance of an Eris [`Client`](https://abal.moe/Eris/docs/Client), useful for getting info or performing lower-level communication with the Discord API. - `this.cluster`: The ID of the eris-fleet cluster that the command is being run from. This should be a number greater than or equal to 0. - `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` instance, useful for communication between worker processes. Documentation for this type can be found [here](https://danclay.github.io/eris-fleet/classes/IPC.html). -- `this.message`: An Eris `Message` object of the message that the command was run from, useful for interaction and getting info about the channel/server that the command was run in. Documentation for this type can be found [here](https://abal.moe/Eris/docs/Message). +- `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.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. +- `this.options`: When run as a "classic" command, this is an object of special arguments (e.g. `--argument=true`) passed to the command. These arguments are stored in a key/value format, so following the previous example, `this.options.argument` would return true. When run as a slash command, this is an object of every argument passed to the command. + +Some options are only available depending on the context/original message type, which can be checked with `this.type`. The options only available with "classic" messages are listed below: +- `this.message`: An Eris [`Message`](https://abal.moe/Eris/docs/Message) object of the message that the command was run from, useful for interaction. - `this.args`: An array of text arguments passed to the command. - `this.content`: A string of the raw content of the command message, excluding the prefix and command name. -- `this.specialArgs`: An object of special arguments (e.g. `--argument=true`) passed to the command. These arguments are stored in a key/value format, so following the previous example, `this.specialArgs.argument` would return true. - `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: +- `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. + Some static fields are also available and can be set depending on your command. These fields are listed below: - `description`: Your command's description, which is shown in the help command. - `aliases`: An array of command aliases. People will be able to run the command using these as well as the normal command name. - `arguments`: An array of command argument types, which are shown in the help command. -- `flags`: An array of objects specifying command flags, or special arguments, that will be shown when running `help ` Example: +- `flags`: An array of objects specifying command flags, or special arguments, that will be shown when running `help ` or a slash command. Example: ```js static flags = [{ name: "argument", - description: "Does a thing" + type: Constants.ApplicationCommandOptionTypes.STRING, // translates to 3, see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-option-type + description: "Does a thing", + ... }]; ``` +- `slashAllowed`: Specifies whether or not the command is available via slash commands. ## 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. \ No newline at end of file diff --git a/docs/postgresql.md b/docs/postgresql.md index 44a190e..fc839a8 100644 --- a/docs/postgresql.md +++ b/docs/postgresql.md @@ -2,16 +2,17 @@ Here are some instructions for setting up PostgreSQL for use with esmBot. **1. Install PostgreSQL.** -#### Alpine -```sh -sudo apk add postgresql -``` #### Debian/Ubuntu ```sh sudo apt-get install postgresql postgresql-client ``` +#### Alpine +```sh +doas apk add postgresql +``` + #### Arch/Manjaro ```sh sudo pacman -S postgresql diff --git a/docs/setup.md b/docs/setup.md index a0c1c8b..9762d27 100644 --- a/docs/setup.md +++ b/docs/setup.md @@ -9,7 +9,7 @@ Recommended system requirements: If you want to run the bot on Windows, [Windows Subsystem for Linux](https://docs.microsoft.com/en-us/windows/wsl/install-win10) is recommended. This guide is somewhat Linux-centric, so for now you're mostly on your own if you decide not to use WSL. -If you have any further questions regarding setup, feel free to ask in the #self-hosting channel on the [esmBot Support server](https://projectlounge.pw/support). +If you have any further questions regarding setup, feel free to ask in the #self-hosting-support channel on the [esmBot Support server](https://projectlounge.pw/support). ## Setup #### 1. Install the required native dependencies. @@ -202,4 +202,4 @@ Make sure Lavalink is running and started up completely. The bot skips loading s *** -If you have any further questions regarding self-hosting, feel free to ask in the #self-hosting channel on the [esmBot Support server](https://projectlounge.pw/support). \ No newline at end of file +If you have any further questions regarding self-hosting, feel free to ask in the #self-hosting-support channel on the [esmBot Support server](https://projectlounge.pw/support). \ No newline at end of file