mirror of
https://github.com/discordeno/discordeno.git
synced 2026-06-01 08:20:08 +00:00
470 lines
16 KiB
Markdown
470 lines
16 KiB
Markdown
---
|
|
sidebar_position: 4
|
|
---
|
|
|
|
# Migrating
|
|
|
|
## Migrating from Discord.js
|
|
|
|
This migration guide is not intended to discredit Discord.js authors/maintainers or Discord.js itself. In fact,
|
|
Discord.js is the most popular Node.js library, admired and praised by a lot of JavaScript developers.
|
|
|
|
## Finding an Open-Source Discord Bot
|
|
|
|
For the purposes of this guide, I wanted to find a moderation bot that is totally open source to show an example of how
|
|
to convert the bot to Discordeno. Trying to find one was not easy as most bot's were not using the latest Discord.JS
|
|
version 12. Trying to find one that was using TypeScript made it even more difficult. My next best solution was to find
|
|
a moderation bot that was recently updated(showing it is maintained or recently built). The best one I could find was
|
|
[Zodiac Bot](https://github.com/Nukestye/Zodiac).
|
|
|
|
For the purposes of this guide, I will be using the current
|
|
[latest commit](https://github.com/Nukestye/Zodiac/tree/213891a38af1b7ecbd068b661ef9062ab58cc818)
|
|
|
|
## Preparations
|
|
|
|
- First, create a Discordeno Bot using the [Generator Template](https://github.com/discordeno/template) I will name it
|
|
Zodiac.
|
|
|
|
- Then `git clone https://github.com/Skillz4Killz/Zodiac.git`
|
|
|
|
Now that I had the repository cloned, I could begin. Note that although the bot we are converting is built in
|
|
JavaScript, I converted all code to TypeScript in this Guide as Discordeno is designed to be the best lib for TypeScript
|
|
developers.
|
|
|
|
Time to get started!
|
|
|
|
## Converting main.js (index file)
|
|
|
|
The first thing is to convert the `main.js` file which would be the app.js or index.js file. This is the file that is
|
|
run to start your bot. In this case, the bot developer chose `main.js`. In Deno, the initial file is named `mod.ts` so
|
|
we can go ahead and opt for the Deno pattern. Note: there is already a `mod.ts` file created and prebuilt entirely using
|
|
the Generator.
|
|
|
|
Current Discord.JS Code:
|
|
|
|
```js
|
|
/* Keeping this to shoutout/credit the original author <3
|
|
* @author: nukestye
|
|
*/
|
|
|
|
const config = require("./config.json");
|
|
const fs = require("fs");
|
|
const log = console.log;
|
|
|
|
// Setting up the way to get commands
|
|
const { CommandoClient } = require("discord.js-commando");
|
|
const path = require("path");
|
|
|
|
// reading events
|
|
fs.readdir("./src/events/", (err, files) => {
|
|
if (err) return console.error(err);
|
|
files.forEach((file) => {
|
|
const eventFunction = require(`./src/events/${file}`);
|
|
if (eventFunction.disabled) return;
|
|
const event = eventFunction.event || file.split(".")[0];
|
|
const emitter =
|
|
(typeof eventFunction.emitter === "string" ? client[eventFunction.emitter] : eventFunction.emitter) || client;
|
|
const { once } = eventFunction;
|
|
try {
|
|
emitter[
|
|
once ? "once" : "on"
|
|
](event, (...args) => eventFunction.run(...args));
|
|
} catch (error) {
|
|
console.error(error.stack);
|
|
}
|
|
});
|
|
});
|
|
|
|
const client = global.client = new CommandoClient({
|
|
commandPrefix: `${config.prefix}`,
|
|
owner: `${config.owner}`,
|
|
invite: `${config.discord}`,
|
|
unknownCommandResponse: false,
|
|
});
|
|
|
|
// Registing the commands
|
|
client.registry
|
|
.registerDefaultTypes()
|
|
// The different fields for cmds
|
|
.registerGroups([
|
|
["mod", "Moderation Commands"],
|
|
["public", "Public Commands"],
|
|
])
|
|
.registerDefaultGroups()
|
|
// Basic cmds can be disabled like {"cmd: false"}
|
|
.registerDefaultCommands()
|
|
// commands in "/src/commands" will be counted
|
|
.registerCommandsIn(path.join(__dirname, "/src/commands"));
|
|
|
|
// list of activities that the bot goes through
|
|
const activityArray = [`${config.prefix}help | `];
|
|
// Bot lanuch code
|
|
client.once("ready", () => {
|
|
log(`Logged in as ${client.user.tag} in ${client.guilds.size} guild(s)!`);
|
|
setInterval(() => {
|
|
const index = Math.floor(Math.random() * (activityArray.length)); // generates a random number between 1 and the length of the activities array list
|
|
client.user.setActivity(
|
|
activityArray[index],
|
|
{
|
|
type: "PLAYING",
|
|
},
|
|
); // sets bot"s activities to one of the phrases in the arraylist.
|
|
}, 5000); // updates every 10000ms = 10s
|
|
});
|
|
// If an error print it out
|
|
client.on("error", console.error);
|
|
|
|
// Login in using the token in config
|
|
client.login(config.env.TOKEN);
|
|
```
|
|
|
|
Discordeno Version:
|
|
|
|
```ts
|
|
import { botCache, Intents } from "./deps.ts";
|
|
import { configs } from "./configs.ts";
|
|
import { importDirectory } from "./src/utils/helpers.ts";
|
|
import { loadLanguages } from "./src/utils/i18next.ts";
|
|
|
|
console.info(
|
|
"Beginning Bot Startup Process. This can take a little bit depending on your system. Loading now...",
|
|
);
|
|
|
|
// Always require these files be processed before anything else
|
|
await Promise.all([
|
|
"./src/customizations/structures",
|
|
].map(
|
|
(path) => importDirectory(Deno.realPathSync(path)),
|
|
));
|
|
|
|
// Forces deno to read all the files which will fill the commands/inhibitors cache etc.
|
|
await Promise.all(
|
|
[
|
|
"./src/commands",
|
|
"./src/inhibitors",
|
|
"./src/events",
|
|
"./src/arguments",
|
|
"./src/monitors",
|
|
"./src/tasks",
|
|
"./src/permissionLevels",
|
|
"./src/events",
|
|
].map(
|
|
(path) => importDirectory(Deno.realPathSync(path)),
|
|
),
|
|
);
|
|
|
|
// Loads languages
|
|
await loadLanguages();
|
|
await import("./src/database/database.ts");
|
|
|
|
startBot({
|
|
token: configs.token,
|
|
// Pick the intents you wish to have for your bot.
|
|
// For instance, to work with guild message reactions, you will have to pass the Intents.GUILD_MESSAGE_REACTIONS intent to the array.
|
|
intents: Intents.Guilds | Intents.GuildMessages,
|
|
// These are all your event handler functions. Imported from the events folder
|
|
events: botCache.events,
|
|
});
|
|
```
|
|
|
|
Something we haven't converted yet from the `main.js` files is the event listeners. To do that, we will open up the
|
|
events folder and find the corresponding event or create it if necessary. In this case, we have the `ready` event and
|
|
there is already a `ready.ts` file. We can just use that.
|
|
|
|
In our `ready.ts` file we can add the `ready` event listener.
|
|
|
|
```ts
|
|
import { ActivityTypes, botCache, cache, chooseRandom, editBotStatus, StatusTypes } from "../../deps.ts";
|
|
import { registerTasks } from "./../utils/taskHelper.ts";
|
|
|
|
botCache.events.ready = function () {
|
|
editBotStatus(
|
|
StatusTypes.DoNotDisturb,
|
|
"Discordeno Best Lib",
|
|
ActivityTypes.Game,
|
|
);
|
|
|
|
console.log(`Loaded ${botCache.arguments.size} Argument(s)`);
|
|
console.log(`Loaded ${botCache.commands.size} Command(s)`);
|
|
console.log(`Loaded ${Object.keys(botCache.events).length} Event(s)`);
|
|
console.log(`Loaded ${botCache.inhibitors.size} Inhibitor(s)`);
|
|
console.log(`Loaded ${botCache.monitors.size} Monitor(s)`);
|
|
console.log(`Loaded ${botCache.tasks.size} Task(s)`);
|
|
|
|
registerTasks();
|
|
|
|
console.log(
|
|
`[READY] Bot is online and ready in ${cache.guilds.size} guild(s)!`,
|
|
);
|
|
|
|
// list of activities that the bot goes through
|
|
const activityArray = [`${configs.prefix}help | `];
|
|
setInterval(() => {
|
|
const randomActivity = activityArray[Math.floor(Math.random() * activityArray.length)];
|
|
|
|
editBotStatus(botCache, {
|
|
activities: [{ name: randomActivity, type: ActivityTypes.Game, createdAt: Date.now() }],
|
|
status: "online",
|
|
});
|
|
}, 5000);
|
|
};
|
|
```
|
|
|
|
To understand this code, we are setting a function to be run when the bot is `ready`. Then the bot will edit the bot's
|
|
status every 5 seconds. Notice that you also have beautiful enums provided that prevents you from making any
|
|
typos/mistakes.
|
|
|
|
We have now converted the entire `main.js` file, in a matter of seconds. The Discordeno official generator took care of
|
|
the majority of workload and we just modified the `ready.ts` file.
|
|
|
|
`Note:` I did remove some generally well known "bad practices" such as global vars and such. Overall, you will see the
|
|
functionality of the project will not change as we progress through this guide.
|
|
|
|
## Converting Commands
|
|
|
|
The first command in the commands folder is the `addRole` command.
|
|
|
|
This is the code from the bot:
|
|
|
|
```ts
|
|
// Getting the 'Command' features from Commando
|
|
const { Command } = require("discord.js-commando");
|
|
|
|
// Code for the command
|
|
module.exports = class addRoleCommand extends Command {
|
|
constructor(client) {
|
|
super(client, {
|
|
// name of the command, must be in lowercase
|
|
name: "addrole",
|
|
// other ways to call the command, must be in lowercase
|
|
aliases: ["role"],
|
|
// command group its part of
|
|
group: "mod",
|
|
// name within the command group, must be in lowercase
|
|
memberName: "addrole",
|
|
// Is the description used for 'help' command
|
|
description: "Adds mentioned role to mentioned user.",
|
|
// Prevents it from being used in dms
|
|
guildOnly: true,
|
|
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
|
|
clientPermissions: ["ADMINISTRATOR", "MANAGE_ROLES"],
|
|
userPermissions: ["MANAGE_ROLES"],
|
|
// Prevents anyone other than owner to use the command
|
|
ownerOnly: false,
|
|
});
|
|
}
|
|
|
|
// Run code goes here
|
|
run(message) {
|
|
const user = message.mentions.members.first();
|
|
const roleToAdd = message.mentions.roles.first();
|
|
|
|
// checking to see if the user has the role or not
|
|
if (!(user.roles.find((r) => r.name === roleToAdd.name))) {
|
|
user.addRole(roleToAdd);
|
|
message.channel.send(`${user} has been given the role: ${roleToAdd.name}`)
|
|
.then((msg) => {
|
|
msg.delete(5000);
|
|
});
|
|
} else {
|
|
message.channel.send(`${user} already has the role: ${roleToAdd.name}`);
|
|
}
|
|
|
|
// console.error(user, roleToAdd, message.member.roles.find(r => r.name === roleToAdd));
|
|
}
|
|
};
|
|
```
|
|
|
|
This is how to do it with Discordeno:
|
|
|
|
```ts
|
|
import { createCommand } from "./../../utils/helpers.ts";
|
|
|
|
createCommand({
|
|
name: "role",
|
|
// Oher ways to call the command
|
|
aliases: ["addrole"],
|
|
// Is the description used for 'help' command
|
|
description: "Adds mentioned role to mentioned user.",
|
|
// Prevents it from being used in dms
|
|
guildOnly: true,
|
|
botServerPermissions: ["ADMINISTRATOR", "MANAGE_ROLES"],
|
|
userServerPermissions: ["MANAGE_ROLES"],
|
|
arguments: [
|
|
{ name: "member", type: "member" },
|
|
{ name: "role", type: "role" },
|
|
],
|
|
execute: (bot, message, args) => {
|
|
// checking to see if the user has the role or not
|
|
if (!args.member.roles.includes(args.role.id)) {
|
|
bot.helpers.addRole(message.guildId, args.member.id, args.role.id);
|
|
bot.helpers.sendMessage(message.channelId, {
|
|
content: `${args.member.mention} has been given the role: ${args.role.name}`,
|
|
});
|
|
} else {
|
|
bot.helpers.sendMessage(message.channelId, {
|
|
content: `${args.member.mention} already has the role: ${args.role.name}`,
|
|
});
|
|
}
|
|
},
|
|
});
|
|
```
|
|
|
|
Awesome, that is a full command converted from Discord.JS to Discordeno. See how easy it is! Let's convert one more
|
|
command to see how to really take full advantage of Discordeno template and have something amazing.
|
|
|
|
Discord.JS Kick Command Version
|
|
|
|
```js
|
|
// Getting the 'Command' features from Commando
|
|
const { Command } = require("discord.js-commando");
|
|
const { RichEmbed } = require("discord.js");
|
|
const chalk = require("chalk");
|
|
const log = console.log;
|
|
|
|
// Code for the command
|
|
module.exports = class kickCommand extends Command {
|
|
constructor(client) {
|
|
super(client, {
|
|
// name of the command, must be in lowercase
|
|
name: "kick",
|
|
// other ways to call the command, must be in lowercase
|
|
aliases: ["boot", "tempban"],
|
|
// command group its part of
|
|
group: "mod",
|
|
// name within the command group, must be in lowercase
|
|
memberName: "kick",
|
|
// Is the description used for 'help' command
|
|
description: "Kick command.",
|
|
// adds cooldowns to the command
|
|
throttling: {
|
|
// usages in certain time x
|
|
usages: 1,
|
|
// the cooldown
|
|
duration: 10,
|
|
},
|
|
// Prevents it from being used in dms
|
|
guildOnly: true,
|
|
// Permissions, list found here > `discord.js.org/#/docs/main/11.5.1/class/Permissions?scrollTo=s-FLAGS`
|
|
clientPermissions: ["ADMINISTRATOR"],
|
|
userPermissions: ["KICK_MEMBERS"],
|
|
// Prevents anyone other than owner to use the command
|
|
ownerOnly: false,
|
|
});
|
|
}
|
|
|
|
// Run code goes here
|
|
run(message) {
|
|
const messageArry = message.content.split(" ");
|
|
const args = messageArry.slice(1);
|
|
|
|
const kUser = message.guild.member(
|
|
message.mentions.users.first() || message.guild.get(args[0]),
|
|
);
|
|
if (!kUser) return message.channel.send("User cannot be found!");
|
|
const kreason = args.join(" ").slice(22);
|
|
|
|
// setting up the embed for report/log
|
|
const kickEmbed = new RichEmbed()
|
|
.setDescription(`Report: ${kUser} Kick`)
|
|
.addField("Reason >", `${kreason}`)
|
|
.addField("Time", message.createdAt);
|
|
|
|
const reportchannel = message.guild.channels.find("name", "report");
|
|
if (!reportchannel) {
|
|
return message.channel.send("*`Report channel cannot be found!`*");
|
|
}
|
|
|
|
// Delete the message command
|
|
// eslint-disable-next-line camelcase
|
|
message.delete().catch((O_o) => {});
|
|
// Kick the user with reason
|
|
message.guild.member(kUser).kick(kreason);
|
|
// sends the kick report into log/report
|
|
reportchannel.send(kickEmbed);
|
|
// Logs the kick into the terminal
|
|
log(chalk.red("KICK", chalk.underline.bgBlue(kUser) + "!"));
|
|
}
|
|
};
|
|
```
|
|
|
|
Discordeno Version
|
|
|
|
```ts
|
|
import { createCommand } from "./../../utils/helpers.ts";
|
|
|
|
createCommand({
|
|
name: `kick`,
|
|
aliases: ["boot", "tempban"],
|
|
description: "Kick command.",
|
|
// adds cooldowns to the command
|
|
cooldown: {
|
|
// usages in certain duration of seconds below
|
|
allowedUses: 1,
|
|
// the cooldown
|
|
seconds: 10,
|
|
},
|
|
// Prevents it from being used in dms
|
|
guildOnly: true,
|
|
botServerPermissions: ["ADMINISTRATOR"],
|
|
userServerPermissions: ["KICK_MEMBERS"],
|
|
arguments: [
|
|
{
|
|
name: "member",
|
|
type: "member",
|
|
missing: function (message) {
|
|
message.reply(`User cannot be found.`);
|
|
},
|
|
// By default this is true but for the purpose of the guide so you can see this exists.
|
|
required: true,
|
|
},
|
|
{
|
|
name: "reason",
|
|
// The leftover string provided by the user that was not used by previous args.
|
|
type: "...string",
|
|
defaultValue: "No reason provided.",
|
|
// It is silly to lowercase this but for the purpose of the guide you can see that this is also available to you.
|
|
lowercase: true,
|
|
},
|
|
],
|
|
execute: function (bot, message, args: KickArgs) {
|
|
// setting up the embed for report/log
|
|
const embed = new Embed()
|
|
.setDescription(`Report: ${args.member.mention} Kick`)
|
|
.addField("Reason >", args.reason)
|
|
.addField("Time", message.timestamp.toString());
|
|
|
|
const reportchannel = message.guild?.channels.find((channel) => channel.name === "report");
|
|
if (!reportchannel) {
|
|
return bot.helpers.sendMessage(message.channelId, { content: "*`Report channel cannot be found!`*" });
|
|
}
|
|
|
|
// Delete the message command
|
|
bot.helpers.deleteMessage(message.channelId, { content: "Remove kick command trigger." });
|
|
// Kick the user with reason
|
|
bot.helpers.kickMember(message.guildId, args.member.id, args.reason);
|
|
// sends the kick report into log/report
|
|
bot.helpers.sendMessage(message.channelId, { embeds: [embed] });
|
|
},
|
|
});
|
|
|
|
interface KickArgs {
|
|
member: Member;
|
|
reason: string;
|
|
}
|
|
```
|
|
|
|
Let's take a minute and explain the differences here. The first thing you will probably notice is different is the
|
|
`arguments` property. Discordeno provides the `arguments` property because it provides argument
|
|
handling/parsing/validating internally. You don't need to be splitting the message content or going through and
|
|
validating it yourself. All you do is tell Discordeno that you want a member and a reason. It will do the magic and hard
|
|
work to get you that data before you even run the command. You just do `args.member` and you have access to the full
|
|
member object. There are a lot more powerful aspects to Discordeno like arguments. Keep diving in and you will find all
|
|
the wonderful tools available to give you the best developer experience possible.
|
|
|
|
### Need More Examples/Help
|
|
|
|
If you still need more help converting other aspects of your bot please contact me at
|
|
[Discord](https://discord.com/invite/5vBgXk3UcZ). I will continue adding more examples to this guide as more people
|
|
request them.
|