new to sapphirejs and discord bot overhaul

hi , i'm a new learner to make discord bot , i've gone a bit trought the basic of discordjs , was able to make work slash command , i switch to sapphirejs from a friend recommandation , but i didnt find how do we manage to send our command to discord api, i would love some direction help its been like 2day i'm learning to make discord bot , still on the basics of doing !ping command , slash command with multiple approach , done js / ts and now trying with sapphire
57 Replies
Favna
Favna4mo ago
have you read our docs? They explain a lot of the basics. https://sapphirejs.dev. Alternatively you can also use @sapphire/cli to generate a getting started template (also documented on the site)
Sapphire Framework
Home | Sapphire
Sapphire is a next-gen Discord bot framework for developers of all skill levels to make the best JavaScript/TypeScript based bots possible.
Hardstyle
Hardstyle4mo ago
yeah i've read most of the docs section, but like i said i've literally new in the bot making world, and overall as developper ( less than 1year since i started from bootcamp ) so i haven't great foundation yet , just here and here overall knowledge i'll check the cli , i've seen it andd was like " should i install it or no " but i think its still great to have it anyway, just to get hand on it
Favna
Favna4mo ago
Well this in particular
but i didnt find how do we manage to send our command to discord api, i would love some direction help
Is answered on https://sapphirejs.dev/docs/Guide/commands/application-commands/application-command-registry/registering-chat-input-commands, however, keep in mind that this is a 4th page in a chain and you should start at "Application Commands -> What are application commands?"
Hardstyle
Hardstyle4mo ago
so the registry : ApplicationCommandRegistry take care of sending the command to discord and include it in the bot ? like you would do the rest.put api call in js ?
import { ApplicationCommandRegistry, Command } from "@sapphire/framework";

export class SlashCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, {
...options,
name: "greet",
description: "Say hello to a user.",
aliases: ["hello", "hi", "hey", "sup", "what's up", "what"],
});
}

public override registerApplicationCommands(
registry: ApplicationCommandRegistry
) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
.addUserOption((option) =>
option //
.setName("user")
.setDescription("User to say hello to")
.setRequired(true)
)
);
}

public override chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
const user = interaction.options.getUser("user");
interaction.reply({
content: `Hello ${user?.username ?? user?.id}!`,
ephemeral: true,
fetchReply: true,
});
}
}
import { ApplicationCommandRegistry, Command } from "@sapphire/framework";

export class SlashCommand extends Command {
public constructor(context: Command.LoaderContext, options: Command.Options) {
super(context, {
...options,
name: "greet",
description: "Say hello to a user.",
aliases: ["hello", "hi", "hey", "sup", "what's up", "what"],
});
}

public override registerApplicationCommands(
registry: ApplicationCommandRegistry
) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
.addUserOption((option) =>
option //
.setName("user")
.setDescription("User to say hello to")
.setRequired(true)
)
);
}

public override chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
const user = interaction.options.getUser("user");
interaction.reply({
content: `Hello ${user?.username ?? user?.id}!`,
ephemeral: true,
fetchReply: true,
});
}
}
if i'm right, this should be okay to register a /greet command or i miss something ? its just the same code as the guide one for most part tho
Favna
Favna4mo ago
Assuming the rest of the setup is correct (folder structure is a big factor for sapphire) then yes that should register a command called greet folder structure is also covered in the guide and generated by the cli also you can check some of the many other bots (/tag bots for @Spinel )
Hardstyle
Hardstyle4mo ago
Yeah got src/commands And greet got generated inside I try with cli generated component and didnt work too
Favna
Favna4mo ago
im sorry but you havent even really described what problem you're having. Lets start there.
Hardstyle
Hardstyle4mo ago
ok so , first , i've initiated a project , using pnpm sapphire/framework command , done src file with commands folder and index.js inside , some setup of tsconfig etc , in the commands folder , i try to make the greet command that is explained in https://sapphirejs.dev/docs/Guide/commands/application-commands/application-command-registry/registering-chat-input-commands the last part i change was
public override chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
const user = interaction.options.getUser("user");
interaction.reply({
content: `Hello ${user?.username ?? user?.id}!`,
ephemeral: true,
fetchReply: true,
});
}
public override chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
const user = interaction.options.getUser("user");
interaction.reply({
content: `Hello ${user?.username ?? user?.id}!`,
ephemeral: true,
fetchReply: true,
});
}
after that i installed sapphire/cli , setup the existing project with it like the screen so my project look like this after creating the folder
Sapphire Framework
Registering Chat Input Commands | Sapphire
To register a Chat Input Command (also known as a Slash Command) with Discord, you need to acquire an application
No description
No description
Hardstyle
Hardstyle4mo ago
and even with cli generated slashcommand , i still dont have it on my bot
Favna
Favna4mo ago
You seem to be mixing JS and TS (index.js but src/command/greet.ts). TS is a super set of JavaScript and you need to compile it down to JS before you can run it. Assuming a well setup tsconfig (cli does that) then you run tsc (i.e. "build": "tsc" in your package.json scripts) which will output a dist folder and then you run the files in dist. Setup the main property in package.json to dist/index.js then run node dist/index.js or node . Your file src/index.js should therefore also be src/index.ts
Hardstyle
Hardstyle4mo ago
import { ApplyOptions } from "@sapphire/decorators";
import { Command } from "@sapphire/framework";

@ApplyOptions<Command.Options>({
description: "A basic slash command",
})
export class UserCommand extends Command {
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
return interaction.reply({ content: "Hello world!" });
}
}
import { ApplyOptions } from "@sapphire/decorators";
import { Command } from "@sapphire/framework";

@ApplyOptions<Command.Options>({
description: "A basic slash command",
})
export class UserCommand extends Command {
public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) =>
builder //
.setName(this.name)
.setDescription(this.description)
);
}

public override async chatInputRun(
interaction: Command.ChatInputCommandInteraction
) {
return interaction.reply({ content: "Hello world!" });
}
}
its the code generated by the sapphire generate slashcommand i've done , and i got and error on the applyOption at the top
Impossible de résoudre la signature d'un élément décoratif de classe quand il est appelé en tant qu'expression.
The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.
Impossible de résoudre la signature d'un élément décoratif de classe quand il est appelé en tant qu'expression.
The runtime will invoke the decorator with 2 arguments, but the decorator expects 1.
the french part saying " impossible to get signature of class decoration when its call as expression " oh , i need to build it to js before anyway ?
Favna
Favna4mo ago
yes and that error requires extending @sapphire/ts-config/decorators specifically the experimental decorators options need to be enabled ESM decorators are not yet supported (not even in Node for that matter, TS will still compile that down to wrapper code)
Hardstyle
Hardstyle4mo ago
so generate component put them , but we dont really need them so i could just delete that ?
Favna
Favna4mo ago
huh? the cli will generate TS code, the TS code is your source code, what you edit. The compiled code (JS) you should never manually edit, it's generated.
Hardstyle
Hardstyle4mo ago
yeah got that part , but it was about the decorator thing, so i was saying that decorator stuff can be deleted since it does an error even with sapphire/decorator installed
Favna
Favna4mo ago
no no you just need to update your tsconfig
Hardstyle
Hardstyle4mo ago
yeah ok , with the "extends" option right
Favna
Favna4mo ago
yes, add @sapphire/ts-config/decorators to it
Hardstyle
Hardstyle4mo ago
yeah done , so now i do pnpm build that execute tsc command , then pnpm start that does the node src/index.js and it should be good ?
Favna
Favna4mo ago
I think so, but I can't really see your editor so I can only make assumptions
Hardstyle
Hardstyle4mo ago
what do you want to see?
Hardstyle
Hardstyle4mo ago
thats how looking my projects for now
No description
Favna
Favna4mo ago
you still need to change src/index.js to src/index.ts . anyway also show your package.json and lastly since you're using pnpm:
Spinel
Spinel4mo ago
When using pnpm you have to make sure that you have the shamefully-hoist and public-hoist-pattern are set correctly in your .npmrc file. This is because pnpm will not hoist any dependencies by default and that poses a problem with how Sapphire works with module augmentation and loading files from the filesystem. Add this to the .npmrc file:
shamefully-hoist=true
public-hoist-pattern[]=@sapphire/*
shamefully-hoist=true
public-hoist-pattern[]=@sapphire/*
Hardstyle
Hardstyle4mo ago
i dont even have .npmrc file
Favna
Favna4mo ago
then create one ? far from the first file you'll be creating haha this config one of the pitfalls of using pnpm over npm / yarn or even Bun 🤷‍♂️
Hardstyle
Hardstyle4mo ago
re , this is my package
No description
Favna
Favna4mo ago
change start to dist/index.js
Hardstyle
Hardstyle4mo ago
and then i change my index.js to .ts in src file ?
Favna
Favna4mo ago
yes
Hardstyle
Hardstyle4mo ago
like that , changed start to dist/index.js
No description
Favna
Favna4mo ago
have you tried running your bot then? it looks good so far.
Hardstyle
Hardstyle4mo ago
pnpm build does an error " permission denied " sh: line 1: /home/hardstyle/projects/discord-bot/eu4bot/node_modules/.bin/tsc: Permission denied  ELIFECYCLE  Command failed with exit code 126.
Favna
Favna4mo ago
cant really tell you whats up there. That's on your file system being fucky. based on the terminal screenshot here I'd guess you're on some variant of *nix so that's gonna be on you to solve. Whatever you did that sets file permissions is posing a problem here
Hardstyle
Hardstyle4mo ago
gonna check it and come when its resolved
Favna
Favna4mo ago
its the joy of working on *nix OS as opposed to MacOS or Windows (Debian and Ubuntu sort of excluded as well because of their popularity)
Hardstyle
Hardstyle4mo ago
ok i'm good able to relaunch my bot rm -rf node_modules does the job for it when i'm generating a sapphire component , do i have to generate listeners and other thing aswell ? or just the command part ? cauz i got the bot working , but none of the command i generate got registered into the bot
Favna
Favna4mo ago
do the files in dist match those in src? like in folder structure and such. Keep in mind that you have a tsconfig.tsbuildinfo file and if you delete dist but not that file then tsc wont output any new files because it will assume that the files are still there (tsconfig.tsbuildinfo is the cache reference file for incremental builds https://www.typescriptlang.org/tsconfig/incremental.html) In general though if you want to just get started up and running it'll save both of us much much trouble if you just send over the folder and I can point out any mistakes in bulk instead of an ever lating back and forth. Alternatively start a fresh project with sapphire new, and if you have any particular commands you want to keep then copy them over afterwards. Alternatively you can also look at some pre-existing bots:
Spinel
Spinel4mo ago
Discord Bots using @sapphire/framework v5.x - Gemboard ᴱ ᴰ - Skyra ᴱ ᴬ ᴰ - Dragonite ᴱ ᴰ - Archangel ᴱ ᴰ - Official Bot Examples ᴱ ᴰ ᴶˢ - KBot ᴱ ᴬ ᴰ v4.x - Radon ᴱ ᴬ - Sapphire Application Commands Examples ᴱ - Zeyr ᴰ ᴬ - Birthdayy ᴰ - RTByte ᴱ ᴬ Legend for the identifiers : Uses ESM (if not specified then uses CJS) : Advanced bot (if not specified it is a simple bot, or not graded) : Uses Docker in production ᴶˢ: Written in JavaScript. If not specified then the bot is written in TypeScript.
Hardstyle
Hardstyle4mo ago
yeah i got file like that in the dist folder
No description
Hardstyle
Hardstyle4mo ago
ok got my first command to work , seems like the bot was doing it as "global " and never add the command since i'm trying , i finally got the guildid thing to specify it and work fine atm
Favna
Favna4mo ago
Awesome!
Hardstyle
Hardstyle4mo ago
Now i need to know how to do complex stuff xd What ibwant to do is a europa 4 bot that track the data of the game , using either game api or skanderberg website api or stuff like that A sort of session organizer combined to a dashboard of that game as a discord bot
Favna
Favna4mo ago
Never heard of that game but good luck with that haha. You'll get there, just don't give up.
Hardstyle
Hardstyle4mo ago
Gonna take time i know + im solo on this project with 0 knowledge or atleast pretty much none since i know how to make basic slashcommand lul Europa universalis 4 is the namenof the game
Favna
Favna4mo ago
Oh that game Heard of it by name only
Hardstyle
Hardstyle4mo ago
Yeah there is a decent bot and a website that goes well together to get info etc but i want to make my own complete bot for my friend. Like a join/leave session with country selection with a embed thing that track people that are in that list , get data from the game for map visualisation , data tracking ( a sort of chart of military power ) etc If you want to take a look check skanderberg.pm website i can maybe get you a save to check whats available there I can get most stuff from theyr api , but dont know if i can mix discord bot to theyr api like /update my friend put the save file and it auto update all the data i could maybe get from the api back and embed on the bot
Favna
Favna4mo ago
Thanks but I'll pass. Such games are not my thing. I'm more for the action games.
Hardstyle
Hardstyle4mo ago
for the database for a bot , are we stuck with a certain database or we can still use whatever we want ? like if i'm doing a little database with prisma or something like that is that cool ?
Favna
Favna4mo ago
whatever you want. Sapphire doesnt ship any database intentionally because there are so many options available
Hardstyle
Hardstyle4mo ago
Yeah o was asking cauz a lot of tuto and bot ive checked use mongo , but seen skyra and other using postgres , so i setup my prisma instance on postgres inside docker container I will take a look at more specific stuff zbout command today i guess With file interaction and stuff Cauz i will have to send a file to an api trought a command Then wait for a process to be dpne and make a bunch of api call , database register info , for the subcommandgroup , are we limited to 2 " choice " after it ?
.addSubcommandGroup(
(subcommandGroup) =>
subcommandGroup
.setName("greet")
.setDescription("Greet a user")
.addSubcommand(
(subcommand) =>
subcommand
.setName("me")
.setDescription("Greet the user")
.addStringOption((option) =>
option
.setName("name")
.setDescription("The user's name")
.setRequired(true)
)
)
)
.addSubcommandGroup(
(subcommandGroup) =>
subcommandGroup
.setName("greet")
.setDescription("Greet a user")
.addSubcommand(
(subcommand) =>
subcommand
.setName("me")
.setDescription("Greet the user")
.addStringOption((option) =>
option
.setName("name")
.setDescription("The user's name")
.setRequired(true)
)
)
)
like this , i would be able to do /join , select greet command , and select my user , then select the user i want to greet ( or whatever i want to do there ) , but i cant add more option after this again i see thgat its not doing what i want tho , so i'm still searching for the on i would need to use
Favna
Favna4mo ago
This is purely theoretical but not possible through other API limit so take it with a grain of salt but Every command can have 25 subcommand groups Every subcommand group can have 25 subcommands Subcommand groups cannot be nested ---- You do not "select" a subcommand like you describe, they show up separately. For example if you type / here check the commands from @Iriss (/iriss will filter). config edit is the config command, edit subcommand
Hardstyle
Hardstyle4mo ago
got it , its the addStringOption i need to use , to have multiple thing yeah , subcommand is like /join(name of the file as the base) , in my case will give me choice between list or campaign , then in campaign i want to have gameid and a tag select menu found out i need to use string option for that last part
Favna
Favna4mo ago
Yeah
Hardstyle
Hardstyle4mo ago
oof , now i need to do a little script for something xd eu4 got 974 country with a XXX TAG , and i need to get all of them in my select menu xd will be hard to manually add them all i'm against a wall on a thing , prob a dumb shit stuff to do and i guess the way i'm trying to do it is not good , and i have to do it an other way
.addStringOption((option) => {
const data = JSON.parse(
fs.readFileSync("src/assets/donnees.json", "utf8")
);
const countryTags = data.country.map(
(country: { description: string; tag: string }) => ({
name: country.description,
value: country.tag,
})
);

const choice = countryTags.map((tag) => tag.value);

return option
.setName("tag")
.setDescription("The name of the campaign")
.setChoices(choice)
.setRequired(true);
}
.addStringOption((option) => {
const data = JSON.parse(
fs.readFileSync("src/assets/donnees.json", "utf8")
);
const countryTags = data.country.map(
(country: { description: string; tag: string }) => ({
name: country.description,
value: country.tag,
})
);

const choice = countryTags.map((tag) => tag.value);

return option
.setName("tag")
.setDescription("The name of the campaign")
.setChoices(choice)
.setRequired(true);
}
this , doesnt work , i try to get all my " tag " field from a json file ( typically a country.tag ) , but i cant get it work to have all my tag into the choice
Hardstyle
Hardstyle4mo ago
like that , but with all the tag
No description
Hardstyle
Hardstyle4mo ago
o k , i got that thing to work , but now i got the other problem that is hte discord limitation , so ihave to change my plan and go on a pagination system with embed and stuff