---
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.
