discord.js bot template based off of SSoB
to add custom commands see here
commands that are already in the bot:
- help
- info
- invite
- ping
- roll
- stats
- checkperms
- find
- prefix
- clear
- crash
- debug
- leaveguild
- servers
install nodejs (v16) here
install all dependencies with npm i in the main directory
in the ./config/ folder rename tempconfig.json to config.json
config.json should look like this:
{
"token": "xxx",
"osu": {
"clientId": "xxx",
"clientSecret": "xxx"
},
"prefix": "xxx",
"owners": ["xxx"],
"logs": {
"console": true,
"file": true
}
}change the values in config.json (see here)
check src/consts/emojis.ts and src/consts/buttons.ts and change the emojis that are formatted as <:name:ID:> (reupload* to a private server that the bot is in)
emoji images are found under ./files/emojis/
to get the emoji id, type the emoji then put a \ in front of it
to compile the bot the bot use tsc or npm run build
to run the compiled code use npm run run
to compile then immediately use npm run br
disabling these permissions will disable the commands listed
| Permission | Usage | Affected Commands |
|---|---|---|
| EmbedLinks | Most commands display information via the use of embeds | All commands |
| Administrator | To access certain data | checkperms, find |
| Property | Type | Description |
|---|---|---|
| token | string | application token for bot to connect to discords API. go to https://discord.com/developers/applications, create a new app, and create a new bot under the bot section. copy the token from there |
| prefix | string | a string at the start of each message to detect if a message is a command. eg. ! => !ping would ping the bot and ?ping or ping wouldn't. |
| owners | string[] | an array of user ids stored as strings. users with these ids can use any command |
| logs | object | see here |
| Property | Type | Description |
|---|---|---|
| console | boolean | Logs are output to the console |
| file | boolean | Logs are saved to the logs folder (dist/src/logs/) |
-
extend the
Commandclass fromsrc/commands/command.tsor copy-paste the command template in the same file (you can use the commands already implemented such as ping as reference) -
set the name property in the constructor
-
to add parameters, modify the params property in the new command and then set the default value of each arg in the constructor
// src/commands/Test.ts
export class Test extends Command {
// set param types
// because params is already defined in Command, it requires the `declare` keyword
protected declare params: {
test: boolean;
page: number;
mode: "main" | "alternate";
};
constructor() {
super();
this.name = "test"; // command automatically sets this to "Test" instead of "test"
// set params default values
this.params = {
test: false,
page: 0,
mode: "main",
};
}
// ...
}- to implement message params, modify the
setParamsMsg()method - params that use flags can use the
setParam()method - for params that use flags, you can follow this template code:
// extends Command
async setParamsMsg() {
// note -
// in `!rs -p 2 -d` the args would be ['-p' '2' '-d']
// commands that use pages can use this built-in method
this.setParamPage();
// strings and numbers
// "-page 2" -> page = 2
// if page isnt found, return 1
this.params.page = this.setParam(1, ["-page", "-p"], "number", {
number_isInt: true,
});
// booleans
// "-test" -> test = true
// in this example, if "-test", is not found, then return false
// if "-test" is found, return true
this.params.test = this.setParam(false, ["-test"], "bool", {});
// "-alt" -> mode = alt
// if "-alt" or "-other" is found, set mode to "alternate"
// else, return current mode
this.params.mode = this.setParam(this.params.mode, ["-alt", "-other"], "bool", {
bool_setValue: "alternate",
});
}- to implement interaction params, modify the
setParamsInteract()method - for each arg, use the various getters in
interaction.optionseg.
this.params.page = interaction.options.getInteger("page");- button params can either be passed via the
overridesproperty in the button handler, or can be retrieved from param files stored in cache. - buttons such as page buttons can be used like so:
helper.tools.commands.buttonPage(page, maxPage, this.input.buttonType);- using param files:
// extends Command
async setParamsButton(){
// for button args that are stored in cache
const temp = helper.tools.commands.getButtonArgs(this.input.id);
if (temp.error) {
interaction.followUp({
content: helper.vars.errors.paramFileMissing,
flags: Discord.MessageFlags.Ephemeral,
allowedMentions: { repliedUser: false },
});
helper.tools.commands.disableAllButtons(this.input.message);
return;
}
this.params.page = temp.page;
}- to create param files
// extends Command
async execute(){
// insert code here
// from https://github.com/sbrstrkkdwmdr/ssob/blob/2ad48ebba57fce74ead27948f1b4f87b26be8773/src/commands/osu_scores.ts#L1436
helper.tools.commands.storeButtonArgs(this.input.id, {
user: this.params.user,
searchid: this.params.searchid,
page: this.params.page + 1,
maxPage: this.scores.length,
mode: this.params.mode,
fails: this.params.showFails,
filterTitle: this.params.filter,
});
}- each setParams method is called via
Command.setParams()when needed, so you can just call that method instead of each individual method - to retrieve override params, modify the
getOverrides()method - override params are used for some commands that don't directly send the interaction to the original command message (eg. page selectors)
// extends Command
getOverrides(): void {
// do nothing if no override values are set
if (!this.input.overrides) return;
// checks if overrides.page is set, then sets params.page
this.setParamOverride('page');
}-
overrides are stored in
Command.input.overrides -
getOverrides isn't stored in setParams, so it make sure to add it to the execute method if needed
-
if you wish to log each command used, make sure to call
Command.logInput()afterCommand.setParams() -
if the command is meant to output a message, make sure to set the content via
Command.ctn, and callCommand.send()afterwards
// extends Command
async execute(){
this.ctn.content = "Hello, world!";
this.send();
}- if the command uses
Command.send()multiple times you can clearCommand.ctnwithCommand.voidcontent()
// extends Command
async execute(){
this.ctn.content = "Hello, world!";
this.send();
this.voidcontent();
const embed = new Discord.EmbedBuilder()
.setTitle("Hello, world!")
.setDescription("Hello, world!");
this.ctn.embeds = [embed];
this.send();
}-
interaction commands only allow one reply at a time, so make sure to avoid sending multiple replies if possible. If multiple are needed, you can always set
Command.ctn.editto true after the first reply or useinteraction.channel.send() -
interaction commands also time out after a few seconds, so if the command takes a while to load, you can send a reply with a loading message, then at the end update it with
Command.ctn.editset to true -
optionally, you can also let your command be accessible in the help command. To do this, add it to
cmds[]insrc/vars/commandData.ts -
some commands can take a while to respond because they're fetching third-party APIs and whatnot
-
because of this, discord can sometimes time-out the interaction before the command is able to respond
-
to avoid this, use
Command.sendLoadingMessage()in/commands
// extends Command
async execute() {
this.sendLoading();
const jsonData = await fetch('https://some.website.com/get?query=query').then(res => res.json());
setTimeout(() => {
const embed = this.createEmbed(jsonData);
this.ctn.embeds = [embed];
this.send();
}, 5000);
}- in
src/commandHandleradd the command name and it's aliases to the switch statementcommandSelect()like so:
// src/commandHandler.ts
import { Test } from "./commands/Test";
export class CommandHandler extends InputHandler {
//...
commandSelect(cmd: string, args: string[]) {
switch (cmd) {
//... other commands
case "test":
command = new Test();
break;
}
return args;
}
//...
}-
if the command uses embeds, or has other requirements, you can add its name to the arrays in commandCheck
-
if the command also takes button inputs, you can add it to the switch statement at the bottom of
async onInteraction()insrc/buttonHandler.ts
import { Test } from "./commands/Test";
//src/buttonHandler.ts
export class ButtonHandler extends InputHandler {
//...
commandSelect(cmd: string, interaction: Discord.ButtonInteraction) {
switch (cmd.toLowerCase()) {
//...
case "Test":
this.selected = new Test();
break;
default:
//...
}
}
}- if the command is meant to be a
/(slash) command add it tosrc/slashCommands.tsat the bottom ofcommands?.setinrun()
// src/slashCommands.ts
function run() {
commands?.set([
//...
{
name: "test",
description: "test command",
dmPermission: false,
options: [
{
name: "test",
description: "Description",
type: Discord.ApplicationCommandOptionType.Boolean,
required: true,
},
{
name: "page",
description: "What page to show",
type: Discord.ApplicationCommandOptionType.Integer,
required: true,
},
{
name: "mode",
description: "What mode to use",
type: Discord.ApplicationCommandOptionType.String,
required: true,
},
],
},
]);
}