<file_sep>import { ClientEvents } from "discord.js";

export type DiscordEvent = keyof ClientEvents;
<file_sep>import {
  MetadataStorage,
  DiscordEvent
} from "..";

export function Once(event: DiscordEvent);
export function Once(event: string);
export function Once(event: DiscordEvent) {
  return (target: Object, key: string, descriptor: PropertyDescriptor): void => {
    MetadataStorage.Instance.AddOn({
      class: target.constructor,
      key,
      params: {
        from: target.constructor,
        guards: [],
        event,
        once: true,
        method: descriptor.value,
        originalParams: {}
      }
    });
  };
}
<file_sep>import { PrefixType } from "..";

export interface IDiscordParams {
  prefix?: PrefixType;
  commandCaseSensitive?: boolean;
  importCommands?: (string | Function)[];
}
<file_sep>import { Client, CommandMessage } from "..";

export interface ClassCommand {
  execute(command: CommandMessage, client: Client): Promise<any> | any;
}
<file_sep>export * from "./IOn";
export * from "./IDecorator";
export * from "./DiscordEvent";
export * from "./IAppConfig";
export * from "./LoadClass";
export * from "./IGuard";
export * from "./ICommandParams";
export * from "./CommandMessage";
export * from "./ICommandInfos";
export * from "./ICommandNotFoundParams";
export * from "./IDiscordParams";
export * from "./GuardFunction";
export * from "./IInstance";
export * from "./PrefixType";
export * from "./ClassCommand";
<file_sep><p align="center">
  <br/>
  <img src="https://i.imgur.com/afS1H2x.png" width="150px">
  <br/>
  <br/>
  <h1 align="center">
    <p  align="center">
      discord.ts (@typeit/discord)
    </p>
  </h1>
  <p align="center">
    Create your discord bot by using TypeScript and decorators!  
  </p>
  <br/>
</p>

## Introduction
This module is built on `discord.js`, so the internal behavior (methods, properties, ...) is the same.

## ☎️ Need help ?
**[Simply join the Discord server](https://discord.gg/VDjwu8E)**
You can also find help with the [different projects that use discord.ts](https://github.com/OwenCalvin/discord.ts/network/dependents?package_id=UGFja2FnZS00Njc1MzYwNzU%3D) and in the [examples folder](https://github.com/OwenCalvin/discord.ts/tree/master/examples)

## 💾 Installation
Use [`npm`](https://www.npmjs.com/package/@typeit/discord) or `yarn` to install `typeit/discord` with the peer dependecies (`discord.js`)
you must install `reflect-metadata` for the decorators and import it at your entry point:
```sh
npm i @typeit/discord discord.js reflect-metadata
```

```typescript
import "reflect-metadata";

// start ...
```

Your tsconfig.json should look like this:
```json
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es2017",
    "noImplicitAny": false,
    "sourceMap": true,
    "outDir": "build",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "importHelpers": true,
    "forceConsistentCasingInFileNames": true,
    "lib": [
      "es2017",
      "esnext.asynciterable"
    ],
    "moduleResolution": "node"
  },
  "exclude": [
    "node_modules"
  ]
}
```

## 🚀 Getting started
So we start with an empty class (abstract is not necessary but this is more type-safe, the class shouldn't be initialized).
```typescript
abstract class AppDiscord {
}
```

Then you must declare it as a Discord app class with the `@Discord` decorator :

```typescript
import { Discord } from "@typeit/discord";

@Discord() // Decorate the class
abstract class AppDiscord {
}
```

We can now declare methods that will be executed whenever a Discord event is triggered.  
Our methods must be decorated with the `@On(event: string)` or `@Once(event: string)` decorator.  
When the event is triggered, the method is called and we receive the values (in arguments) related to the event.
Here, we receive the message instance (details below) :

```typescript
import { Discord, On } from "@typeit/discord";

@Discord()
abstract class AppDiscord {
  @On("message")
  private onMessage(message: Message) {
    // ...
  }
}
```

## Start your application
In order to start your application, you must use the DiscordTS `Client` (not the client that is provided by discord.js!).  
It works the same as the discord.js's Client (same methods, properties, ...) but the `login` method is overriden and you can set the `silent` property (into `Client` initialization) in order to not log anything in the console.
```typescript
// Use the Client that are provided by @typeit/discord NOT discord.js
import { Client } from "@typeit/discord";

function start() {
  const client = new Client();
  client.login(
    "YOUR_TOKEN",
    `${__dirname}/*Discord.ts` // glob string to load the classes
  );
}

start();
```

## Client payload injection
You will also receive the client instance always as the last payload:
```typescript
import {
  Discord,
  On,
  Client
} from "@typeit/discord";
import { Message } from "discord.js";

@Discord()
abstract class AppDiscord {
  @On("message")
  private onMessage(
    message: Message,
    client: Client // Client instance injected here
  ) {
    // ...
  }
}
```

## 📟 Commands
You can simply use the `@Command` and `@CommandNotFound` decorators to implement a command system in your app.  

When you use the `@Command` or the `@CommandNotFound` decorator, you should type your first parameters as a `CommandMessage`. It provides the command parameters, the prefix, and the command (specified [here](https://github.com/OwenCalvin/discord.ts/blob/master/src/Types/CommandMessage.ts)).  

```typescript
import {
  Discord,
  On,
  Client,
  Command,
  CommandMessage
} from "@typeit/discord";

@Discord({ prefix: "!" })
abstract class AppDiscord {
  @Command("hello")
  private hello(
    message: CommandMessage,
    client: Client
  ) {
    // ...
  }

  @CommandNotFound()
  private notFound(
    message: CommandMessage,
    client: Client
  ) {
    // ...
  }
}
```

### The command parameters
You can specify the `prefix` and the `commandCaseSensitive` on the `@Discord` and `@Command` params (you can specify only the prefix of `@CommandNotFound`). The params on the `@Command` will override those of `@Discord`.  
The `@Command` decorator also have a `description` parameter and an `infos` one. The description one is useful if you have an help command to display the command description. The `infos` one can store anything you want.

**If you use different prefixes or case sensitivity, I recommend implementing multiple classes decorated by the `@Discord` parameter using different prefixes/case sensitivity, like the [multipleDiscordInstances example](https://github.com/OwenCalvin/discord.ts/tree/master/examples/multipleDiscordInstances).**  

But here is an example for the different params:
```typescript
import {
  Discord,
  On,
  Client,
  Command,
  CommandMessage
} from "@typeit/discord";

@Discord({ prefix: "!", commandCaseSensitive: true })
abstract class AppDiscord {
  // Executed using the !HELLO command
  @Command("HELLO")
  private hello(message: CommandMessage) {
    // ...
  }

  // Executed if a command with the prefix "!" isn't found
  @CommandNotFound()
  private notFound(message: CommandMessage) {
    // ...
  }

  // Executed using the .helloDot command
  @Command("helloDot", { prefix: "." }) 
  private helloDot(message: CommandMessage) {
    // ...
  }

  // Executed if a command with the prefix "." isn't found
  @CommandNotFound({ prefix: "." })
  private notFoundDot(message: CommandMessage) {
    // ...
  }

  // Executed using 0ab, 0Ab, 0aB or 0AB
  @Command("ab", { prefix: "0" }) 
  private ab(message: CommandMessage) {
    // ...
  }
}
```

### Dynamic prefix
If you have different prefix for different servers you can use dynamic prefixes using functions like this:
```typescript
// If the message has been sent in the guild with the name MyGuildName the prefix "." will be considered otherwise the prefix "$" will trigger the action.
async function prefixBehaviour(message: Message, client: Client) {
  if (message.guild.name === "MyGuildName") {
    return ".";
  }
  return "$";
}

@Discord({ prefix: prefixBehaviour })
abstract class AppDiscord {
  @Command("HELLO")
  private hello(message: CommandMessage) {
    // ...
  }
}
```


### Retrieve the commands
You can simply get all the commands and their details using `Client.getCommands<InfoType>(forPrefix?: string): ICommandInfos[]`.
> If you specify no prefix for the `forPrefix` parameter, you will receive the details of all the commands.

```typescript
import {
  Discord,
  On,
  Client,
  Command,
  CommandMessage
} from "@typeit/discord";

interface Infos {
  requiredRole: string;
}

@Discord({ prefix: "!" })
abstract class AppDiscord {
  @Command("hello", {
    description: "The command simply say hello",
    infos: { requiredRole: "master" }
  })
  private hello(
    message: CommandMessage,
    client: Client
  ) {
    // ...
  }

  @Command("help")
  private hello(
    message: CommandMessage,
    client: Client
  ) {
    // You receive all the commands prefixed by "!"
    const commands = Client.getCommands<Infos>("!");

    // ...
  }

  @CommandNotFound()
  private notFound(
    message: CommandMessage,
    client: Client
  ) {
    // ...
  }
}
```

### Command directory pattern
> [Example](https://github.com/OwenCalvin/discord.ts/tree/master/examples/commandsDir)
If you have a directory pattern that looks like this:
```shell
Main.ts
DiscordApp.ts
commands
- Ping.ts
- Hello.ts
- Blabla.ts
```
You should use the `importCommands` parameter for the `@Discord` decorator.
Here, all of the commands will be injected into this Discord class instance.
```typescript
import {
  Discord,
  CommandNotFound
} from "@typeit/discord";

@Discord({
  prefix: "!", // The Discord parameters will be applied to the imported commands
  importCommands: [
    Path.join(__dirname,  "commands", "*.ts")
    // You can also specify the class directly here if you don't want to use a glob
  ]
})
export abstract class DiscordApp {
  @CommandNotFound({ prefix: "!" })
  notFoundA(commad: CommandMessage) {
    commad.reply("Command not found");
  }
}
```

Here is an example of what your command file should look like:
```typescript
import { ClassCommand, Command, CommandMessage } from "@typeit/discord";

export abstract class Bye implements ClassCommand {
  @Command("bye")
  async execute(command: CommandMessage) {
    command.reply("Bye!");
  }
}
```

### Set commands paramaters programmaticaly
If you are forced to change the prefix during the execution or if it's loaded from a file when your app start, you can use two methods (it returns `true` if the params changed):
- `Client.setDiscordParams(discordInstance: InstanceType<any>, params: IDiscordParams): boolean`  
- `Client.setCommandParams(discordInstance InstanceType<any>, method: Function, params: ICommandParams): boolean`  
>I recommend not specifying the prefix inside the decorator if you use one of these two methods because it wouldn't be consistent

```typescript
import {
  Discord,
  On,
  Client,
  Command,
  CommandMessage
} from "@typeit/discord";

@Discord({ prefix: "!" })
abstract class AppDiscord {
  @Command("prefix")
  private prefix(message: CommandMessage) {
    // Will change the prefix of all the @Command methods of this @Discord instance
    Client.setDiscordParams(this, {
      prefix: message.params[0]
    });
  }

  @Command("changeMyPrefix")
  private changeMyPrefix(message: CommandMessage) {
    // Will change the prefix of the changeMyPrefix method
    Client.setCommandParams(this, this.changeMyPrefix, {
      prefix: message.params[0]
    });
  }
}
```

The command class should look like that:
```typescript
import {
  ClassCommand,
  Command,
  CommandMessage
} from "@typeit/discord";

export abstract class Bye implements ClassCommand {
  @Command()
  async execute(command: CommandMessage) {
    command.reply("Bye!");
  }
}
```

## ⚔️ Guards
> Guards works also with `@Command` and `@CommandNotFound`   

You can use functions that are executed before your event to determine if it's executed. For example, if you want to apply a prefix to the messages, you can simply use the `@Guard` decorator:
(The `Prefix` function is provided by the `@typeit/discord` package, where you can import it)
```typescript
import {
  Discord,
  On,
  Client,
  Guard,
  Prefix
} from "@typeit/discord";
import {
  Message
} from "discord.js";
import {
  NotBot
} from "./NotBot";

@Discord()
abstract class AppDiscord {
  @On("message")
  @Guard(
    NotBot, // You can use multiple guard functions, they are excuted in the same order!
    Prefix("!")
  )
  async onMessage(message: Message) {
    switch (message.content.toLowerCase()) {
      case "hello":
        message.reply("Hello!");
        break;
      default:
        message.reply("Command not found");
        break;
    }
  }
}
```

### The guard functions
Here is a simple example of a guard function (the payload and the client instance are injected like for events)
- If the function returns `false`: the next guards and the event function aren't executed  
- If the function returns `true`: it continues the executions of the next guards  
```typescript
import { Client } from "typeit/discord";
import { Message } from "discord.js";

export function NotBot(message: Message, client: Client) {
  return client.user.id !== message.author.id;
}
```

If you have to indicate parameters for a guard function (like for the `Prefix` guard) you can simple use the "function that returns a function" pattern like this:
```typescript
import { Client } from "typeit/discord";
import { Message } from "discord.js";

export function Prefix(text: string, replace: boolean = true) {
  return (message: Message, client: Client) => {
    const startWith = message.content.startsWith(text);
    if (replace) {
      message.content = message.content.replace(text, "");
    }
    return startWith;
  };
}
```

## 💡 Events and payload
Here you have the details about the payloads that are injected into the method related to a specific event.
Be aware that the types must be imported from discord.js (except for `Client`).
In this example of the event `"channelUpdate"`, we receive two payloads from the event :
> it works for `@Once(event: DiscordEvent)` too
```typescript
@Discord()
abstract class AppDiscord {
  @On("channelUpdate")
  private onChannelUpdate(
    oldChannel: Channel,  // first one
    newChannel: Channel   // second one
  ) {
    // ...
  }
}
```

Here is all the `DiscordEvents` and their parameters (`discord.js` version 12.2.0)
> Example for the first one:  
> `@On("`**channelCreate**`")`  
> `onChannelCreate(`**channel: Channel**`) { }`

- **channelCreate**: `(Channel)`
- **channelDelete**: `(Channel | PartialDMChannel)`
- **channelPinsUpdate**: `(Channel | PartialDMChannel, Date)`
- **channelUpdate**: `(Channel, Channel)`
- **debug**: `(string)`
- **warn**: `(string)`
- **disconnect**: `(any, number)`
- **emojiCreate**: `(GuildEmoji)`
- **emojiDelete**: `(GuildEmoji)`
- **emojiUpdate**: `(GuildEmoji, GuildEmoji)`
- **error**: `(Error)`
- **guildBanAdd**: `(Guild, User | PartialUser)`
- **guildBanRemove**: `(Guild, User | PartialUser)`
- **guildCreate**: `(Guild)`
- **guildDelete**: `(Guild)`
- **guildUnavailable**: `(Guild)`
- **guildIntegrationsUpdate**: `(Guild)`
- **guildMemberAdd**: `(GuildMember | PartialGuildMember)`
- **guildMemberAvailable**: `(GuildMember | PartialGuildMember)`
- **guildMemberRemove**: `(GuildMember | PartialGuildMember)`
- **guildMembersChunk**: `(Collection<Snowflake, GuildMember | PartialGuildMember>, Guild)`
- **guildMemberSpeaking**: `(GuildMember | PartialGuildMember, Readonly<Speaking>)`
- **guildMemberUpdate**: `(GuildMember | PartialGuildMember, GuildMember | PartialGuildMember)`
- **guildUpdate**: `(Guild, Guild)`
- **inviteCreate**: `(Invite)`
- **inviteDelete**: `(Invite)`
- **message**: `(Message)`
- **messageDelete**: `(Message | PartialMessage)`
- **messageReactionRemoveAll**: `(Message | PartialMessage)`
- **messageReactionRemoveEmoji**: `(MessageReaction)`
- **messageDeleteBulk**: `(Collection<Snowflake, Message | PartialMessage>)`
- **messageReactionAdd**: `(MessageReaction, User | PartialUser)`
- **messageReactionRemove**: `(MessageReaction, User | PartialUser)`
- **messageUpdate**: `(Message | PartialMessage, Message | PartialMessage)`
- **presenceUpdate**: `(Presence | undefined, Presence)`
- **rateLimit**: `(RateLimitData)`
- **ready**: `()`
- **invalidated**: `()`
- **roleCreate**: `(Role)`
- **roleDelete**: `(Role)`
- **roleUpdate**: `(Role, Role)`
- **typingStart**: `(Channel | PartialDMChannel, User | PartialUser)`
- **userUpdate**: `(User | PartialUser, User | PartialUser)`
- **voiceStateUpdate**: `(VoiceState, VoiceState)`
- **webhookUpdate**: `(TextChannel)`
- **shardDisconnect**: `(CloseEvent, number)`
- **shardError**: `(Error, number)`
- **shardReady**: `(number)`
- **shardReconnecting**: `(number)`
- **shardResume**: `(number, number)`

## Examples
An example is provided in the [`/examples` folder](https://github.com/OwenCalvin/DiscordTS/tree/master/examples) !

## Migration v1 to v2
You should just add parenthesis after the `@Discord` decorator, everywhere in your app.  
`@Discord class X` should now be `@Discord() class X`.

## See also
- [discord.js](https://discord.js.org/#/)
<file_sep>import { PrefixType } from ".";

export interface ICommandNotFoundParams {
  prefix?: PrefixType;
}
<file_sep>import { Client } from "..";
import { Message } from "discord.js";

export type PrefixType = string | ((params: Message, client: Client) => Promise<string> | string);
<file_sep>import { PrefixType } from ".";

export interface ICommandInfos<InfoType = any> {
  prefix: PrefixType;
  commandName: string;
  description: string;
  infos: InfoType;
}
<file_sep>import { IDiscordParams, PrefixType } from "..";

export interface IInstance extends IDiscordParams {
  instance?: Function;
  prefix: PrefixType;
}
<file_sep>import {
  MetadataStorage,
  ICommandNotFoundParams
} from "..";

export function CommandNotFound();
export function CommandNotFound(params: ICommandNotFoundParams);
export function CommandNotFound(params?: ICommandNotFoundParams) {
  const definedParams = params || {};
  return (target: Object, key: string, descriptor: PropertyDescriptor): void => {
    MetadataStorage.Instance.AddOn({
      class: target.constructor,
      key,
      params: {
        from: target.constructor,
        commandName: "",
        prefix: definedParams.prefix,
        guards: [],
        event: "message",
        once: false,
        method: descriptor.value,
        originalParams: definedParams
      }
    });
  };
}
<file_sep>import {
  MetadataStorage,
  ICommandParams
} from "..";

export function Command();
export function Command(commandName: string);
export function Command(params: ICommandParams);
export function Command(commandName: string, params: ICommandParams);
export function Command(commandNameOrParams?: string | ICommandParams, params?: ICommandParams) {
  const isCommandName = typeof commandNameOrParams === "string";
  let definedParams = params || {};
  let definedCommandName: string;

  if (!isCommandName) {
    if (commandNameOrParams) {
      definedParams = commandNameOrParams as ICommandParams;
    } else {
      definedParams = {};
    }
  } else {
    definedCommandName = commandNameOrParams as string;
  }

  return (target: Object, key: string, descriptor: PropertyDescriptor): void => {
    const method = descriptor.value;

    MetadataStorage.Instance.AddOn({
      class: target.constructor,
      key,
      params: {
        from: target.constructor,
        commandName: definedCommandName || key,
        guards: [],
        event: "message",
        once: false,
        method,
        ...definedParams,
        commandCaseSensitive: definedParams.commandCaseSensitive || false,
        prefix: definedParams.prefix,
        originalParams: {
          ...definedParams,
          commandName: definedCommandName
        }
      }
    });
  };
}
<file_sep>import { GuardFunction } from ".";

export interface IGuard {
  fn: GuardFunction;
  method: Function;
}
<file_sep>import { ICommandNotFoundParams } from "./ICommandNotFoundParams";

export interface ICommandParams extends ICommandNotFoundParams {
  commandCaseSensitive?: boolean;
  description?: string;
  infos?: any;
}
<file_sep>import { CommandNotFound, Discord, CommandMessage } from "../../../src";
import * as Path from "path";
import { Bye } from "../commands/Bye";

@Discord({
  prefix: "!",
  importCommands: [
    Path.join(__dirname, "..", "commands", "*.ts"),
    Bye
  ]
})
export class DiscordApp {
  @CommandNotFound({ prefix: "!" })
  notFoundA(commad: CommandMessage) {
    commad.reply("Command not found");
  }
}
<file_sep>export interface IAppConfig {
  classes?: Function[];
}
<file_sep>import { ClassCommand, Command, CommandMessage } from "../../../src";

export abstract class Hello implements ClassCommand {
  @Command("hello")
  async execute(command: CommandMessage) {
    command.reply("Hello!");
  }
}
<file_sep>import {
  MetadataStorage,
  GuardFunction
} from "..";

export function Guard(...fns: GuardFunction[]) {
  return (target: Object, key: string, descriptor: PropertyDescriptor): void => {
    fns.reverse().map((fn) => {
      MetadataStorage.Instance.AddGuard({
        class: target.constructor,
        key,
        params: {
          fn,
          method: descriptor.value
        }
      });
    });
  };
}
<file_sep>import {
  MetadataStorage,
  IDiscordParams
} from "..";
import * as Glob from "glob";

function importCommand(classType: Function, target: Object) {
  const ons = MetadataStorage.Instance.Ons.filter((on) => {
    return on.class === classType;
  });
  ons.map((on) => {
    on.class = target;
    on.params.commandName = on.params.originalParams.commandName || classType.name;
    on.params.from = classType;
  });
}

export function Discord();
export function Discord(params: IDiscordParams);
export function Discord(params?: IDiscordParams) {
  const definedParams = params || {};
  return (target: Object) => {
    if (definedParams.importCommands) {
      definedParams.importCommands.map((cmd) => {
        if (typeof cmd === "string") {
          const files = Glob.sync(cmd);
          files.map((file) => {
            let classType;
            const classImport = require(file);
            if (classImport.default) {
              classType = classImport.default;
            } else {
              const key = Object.keys(classImport)[0];
              classType = classImport[key];
            }
            importCommand(classType, target);
          });
        } else {
          importCommand((cmd as any).execute, target);
        }
      });
    }

    MetadataStorage.Instance.AddInstance({
      class: target,
      key: target.constructor.name,
      params: {
        prefix: definedParams.prefix || "",
        commandCaseSensitive: definedParams.commandCaseSensitive || false
      }
    });
  };
}
<file_sep>import {
  IOn,
  IDecorator,
  IInstance,
  IGuard,
  Client,
  Prefix,
  CommandMessage,
  IDiscordParams,
  ICommandParams,
  ICommandInfos
} from "..";

export class MetadataStorage {
  private static _instance: MetadataStorage;
  private _ons: IDecorator<IOn>[] = [];
  private _guards: IDecorator<IGuard>[] = [];
  private _instances: IDecorator<IInstance>[] = [];

  static get Instance() {
    if (!this._instance) {
      this._instance = new MetadataStorage();
    }
    return this._instance;
  }

  get Ons() {
    return this.getReadonlyArray(this._ons);
  }

  AddOn(on: IDecorator<IOn>) {
    this._ons.push(on);
  }

  AddGuard(guard: IDecorator<IGuard>) {
    this._guards.push(guard);
  }

  AddInstance(classType: IDecorator<IInstance>) {
    this._instances.push({
      ...classType,
      params: {
        instance: new classType.class(),
        ...classType.params
      }
    });
  }

  Build(client: Client) {
    const commands = this._ons.reduce<string[]>((prev, on) => {
      if (on.params.commandName) {
        prev.push(on.params.commandName);
      }
      return prev;
    }, []);

    this._ons.map((on) => {
      on.params.guards = this._guards.reverse().filter((guard) => {
        return (
          guard.class === on.params.from &&
          guard.params.method === on.params.method
        );
      }, []);

      on.params.guardFn = async (client: Client, ...params: any) => {
        let res = true;
        for (const fn of on.params.guards) {
          if (res) {
            res = await fn.params.fn(...params, client);
          } else {
            break;
          }
        }
        return res;
      };

      const instance = this._instances.find((instance) => instance.class === on.class);
      if (instance) {
        on.params.linkedInstance = instance;
      }

      on.params.compiledMethod = async (...params: any[]) => {
        let command: IDecorator<IOn> = on;
        let execute = true;

        if (on.params.event === "message") {
          if (on.params.commandName !== undefined) {
            execute = false;
            const message = params[0] as CommandMessage;
            let prefix = on.params.prefix || on.params.linkedInstance.params.prefix;
            if (typeof prefix === "function") {
              prefix = await prefix(message, client);
            }
            if (Prefix(prefix)(message)) {
              if (message.author.id !== client.user.id) {
                const params = message.content.split(" ");

                let testedCommand = params[0].replace(prefix, "");
                let commandName = on.params.commandName;
                let allCommands = commands;
                const lowerCommands = allCommands.map((command) => command.toLowerCase());
                const notFoundFn = this._ons.find((cmd) => {
                  return (
                    (cmd.params.prefix || cmd.params.linkedInstance.params.prefix) === prefix &&
                    cmd.params.commandName === ""
                  );
                });

                if (testedCommand.toLowerCase() === commandName.toLowerCase()) {
                  const originalCommand = testedCommand;

                  message.prefix = prefix;
                  message.command = testedCommand;
                  message.commandWithPrefix = prefix + testedCommand;
                  message.originalCommand = originalCommand;
                  message.originalCommandWithPrefix = prefix + originalCommand;
                  message.params = params;
                  message.params.splice(0, 1);

                  if (
                    !on.params.linkedInstance.params.commandCaseSensitive &&
                    !on.params.commandCaseSensitive &&
                    on.params.commandCaseSensitive !== undefined
                  ) {
                    testedCommand = testedCommand.toLowerCase();
                    commandName = commandName.toLowerCase();
                    allCommands = lowerCommands;
                  }

                  if (allCommands.indexOf(testedCommand) === -1) {
                    if (notFoundFn) {
                      command = notFoundFn;
                      execute = true;
                    }
                  }
                } else {
                  if (lowerCommands.indexOf(testedCommand.toLowerCase()) === -1) {
                    testedCommand = "";
                  }
                }

                if (testedCommand === commandName) {
                  execute = true;
                  command = on;
                }
              }
            }
          }
        }

        if (execute) {
          const executeMain = await command.params.guardFn(client, ...params);
          if (executeMain) {
            return await this.executeBindedOn(command, params, client);
          }
        }
      };
    });
  }

  setDiscordParams(discordInstance: InstanceType<any>, params: IDiscordParams): boolean {
    const discord = this._instances.find((instance) => instance.params.instance === discordInstance);

    if (discord) {
      discord.params = {
        ...discord.params,
        ...params
      };
      return true;
    }

    return false;
  }

  getPrefix(command: IOn) {
    if (command.prefix || command.linkedInstance) {
      return command.prefix || command.linkedInstance.params.prefix;
    }
  }

  getCommands<InfoType = any>(forPrefix?: string) {
    return this.getCommandsIntrospection(forPrefix).map<ICommandInfos<InfoType>>((command) => {
      const prefix = this.getPrefix(command);
      if (prefix) {
        return {
          prefix,
          commandName: command.commandName,
          description: command.description,
          infos: command.infos
        };
      }
    });
  }

  getCommandsIntrospection(forPrefix?: string) {
    return this._ons.reduce<IOn[]>((prev, on) => {
      if (on.params.commandName) {
        if (forPrefix) {
          const prefix = on.params.prefix || on.params.linkedInstance.params.prefix;
          if (forPrefix === prefix) {
            prev.push(on.params);
          }
        } else {
          prev.push(on.params);
        }
      }
      return prev;
    }, []);
  }

  setCommandParams(discordInstance: InstanceType<any>, instanceMethod: Function, params: ICommandParams): boolean {
    const on = this._ons.find((on) => {
      let cond = on.class === discordInstance;

      if (on.params.linkedInstance) {
        cond = on.params.linkedInstance.params.instance === discordInstance;
      }

      return (
        cond &&
        on.params.method === instanceMethod
      );
    });

    if (on) {
      on.params = {
        ...on.params,
        ...params
      };
      return true;
    }

    return false;
  }

  compileOnForEvent(
    event: string,
    client: Client,
    once: boolean = false
  ) {
    const ons = this._ons.filter(on => (
      on.params.event === event &&
      on.params.once === once
    ));

    return async (...params: any[]) => {
      for (const on of ons) {
        await on.params.compiledMethod(...params, client);
      }
    };
  }

  private executeBindedOn(on: IDecorator<IOn>, params: any[], client: Client) {
    if (on.params.linkedInstance && on.params.linkedInstance.params.instance) {
      return on.params.method.bind(on.params.linkedInstance.params.instance)(...params, client);
    } else {
      return on.params.method(...params, client);
    }
  }

  private getReadonlyArray<Type>(array: Type[]) {
    return array as ReadonlyArray<Type>;
  }
}
<file_sep>import { ClientOptions } from "discord.js";

export interface IClientOptions extends ClientOptions {
  silent?: boolean;
}
<file_sep>export type GuardFunction =
  ((...params: any[]) => Promise<boolean> | boolean | Promise<undefined> | undefined);

<file_sep>export interface IDecorator<Type> {
  class: any;
  key: string;
  params: Type;
}
<file_sep>import { Client } from "../../../src";
import { Message } from "discord.js";

export function NotBot(message: Message, client: Client) {
  return client.user.id !== message.author.id;
}
<file_sep>export * from "./Prefix";
<file_sep>export * from "./On";
export * from "./Once";
export * from "./Guard";
export * from "./Discord";
export * from "./Command";
export * from "./CommandNotFound";
<file_sep>import { ClassCommand, Command, CommandMessage } from "../../../src";

export abstract class Bye implements ClassCommand {
  @Command("bye")
  async execute(command: CommandMessage) {
    command.reply("Bye!");
  }
}
<file_sep>import { Client as ClientJS } from "discord.js";
import * as Glob from "glob";
import {
  MetadataStorage,
  LoadClass,
  IDiscordParams,
  ICommandParams
} from ".";
import { IClientOptions } from "./Types/ClientOptions";

export class Client extends ClientJS {
  private _silent: boolean;
  private _loadClasses: LoadClass[] = [];
  private _loadedOnEvents: string[] = [];
  private _loadedOnceEvents: string[] = [];

  get silent() {
    return this._silent;
  }
  set silent(value: boolean) {
    this._silent = value;
  }

  constructor(options?: IClientOptions) {
    super(options);

    if (options) {
      this.silent = options.silent;
    }
  }

  static setDiscordParams(discordInstance: InstanceType<any>, params: IDiscordParams): boolean {
    return MetadataStorage.Instance.setDiscordParams(discordInstance, params);
  }

  static setCommandParams(discordInstance: InstanceType<any>, instanceMethod: Function, params: ICommandParams): boolean {
    return MetadataStorage.Instance.setCommandParams(discordInstance, instanceMethod, params);
  }

  static getCommandsIntrospection(forPrefix?: string) {
    return MetadataStorage.Instance.getCommandsIntrospection(forPrefix);
  }

  static getCommands<InfoType = any>(forPrefix?: string) {
    return MetadataStorage.Instance.getCommands<InfoType>(forPrefix);
  }

  /**
   * Start your bot
   * @param token The bot token
   * @param loadClasses A list of glob path or classes
   */
  login(token: string, ...loadClasses: LoadClass[]) {
    this._loadClasses = loadClasses;
    this.loadClasses();

    MetadataStorage.Instance.Build(this);
    MetadataStorage.Instance.Ons.map(async (on) => {
      if (
        on.params.once &&
        this._loadedOnceEvents.indexOf(on.params.event) === -1
      ) {
        this.once(
          "warn",
          MetadataStorage.Instance.compileOnForEvent(
            on.params.event,
            this,
            true
          )
        );
        this._loadedOnceEvents.push(on.params.event);
      } else if (this._loadedOnEvents.indexOf(on.params.event) === -1) {
        this.on(
          on.params.event,
          MetadataStorage.Instance.compileOnForEvent(
            on.params.event,
            this
          )
        );
        this._loadedOnEvents.push(on.params.event);
      }

      if (!this.silent) {
        let eventName = on.params.event;
        if (on.params.commandName !== undefined) {
          const prefix = MetadataStorage.Instance.getPrefix(on.params);
          if (prefix) {
            let commandName = on.params.commandName;
            if (!on.params.commandCaseSensitive && !on.params.linkedInstance.params.commandCaseSensitive) {
              commandName = commandName.toLowerCase();
            }
            if (on.params.commandName === "") {
              eventName += ` (Command not found "${prefix}")`;
            } else {
              eventName += ` (Command "${prefix}${on.params.commandName}")`;
            }
          }
        }
        console.log(`${eventName}: ${on.params.from.name}.${on.key}`);
      }
    });

    return super.login(token);
  }

  private loadClasses() {
    if (this._loadClasses) {
      this._loadClasses.map((file) => {
        if (typeof file === "string") {
          const files = Glob.sync(file);
          files.map((file) => {
            require(file);
          });
        }
      });
    }
  }
}
<file_sep>import { ClassCommand, Command, CommandMessage, Guard } from "../../../src";
import { Say } from "../guards/Say";

export default abstract class Ping implements ClassCommand {
  @Guard(Say("Pong"))
  @Command({ description: "Ping pong", commandCaseSensitive: true })
  async execute(command: CommandMessage) {
    command.reply("Pong");
  }
}

<file_sep>import {
  DiscordEvent,
  IInstance,
  IDecorator,
  IGuard,
  ICommandParams
} from ".";

export interface IOn extends ICommandParams {
  commandName?: string;
  event: DiscordEvent;
  method: (...params: any[]) => void;
  compiledMethod?: (...params: any[]) => void;
  linkedInstance?: IDecorator<IInstance>;
  once: boolean;
  guards: IDecorator<IGuard>[];
  guardFn?: (...params: any[]) => Promise<any>;
  from: Function;
  originalParams: Partial<IOn>;
}
<file_sep>import { Message } from "discord.js";

export function Prefix(text: string, replace: boolean = false) {
  return (message: Message) => {
    const startWith = message.content.startsWith(text);
    if (replace) {
      message.content = message.content.replace(text, "");
    }
    return startWith;
  };
}
<file_sep>import {
  Discord,
  On,
  Client, // Use the Client that is provided by @typeit/discord NOT discord.js
  Guard,
  Prefix,
  Command,
  CommandNotFound,
  CommandMessage
} from "../../src";
// You must import the types from discord.js
import {
  Message
} from "discord.js";
import { NotBot } from "./guards/NotBot";
import { Say } from "./guards/Say";

enum Answers {
  hello = "Hello",
  notFound = "Command not found...",
  prefix = "Prefix changed"
}

// Decorate the class with the @Discord() decorator
// You can specify the prefix for the @Command() decorator
@Discord({ prefix: "!" })
export class AppDiscord {
  private static _client: Client;

  static start() {
    this._client = new Client();
    // In the login method, you must specify the glob string to load your classes (for the framework).
    // In this case that's not necessary because the entry point of your application is this file.
    this._client.login(
      "YOUR_TOKEN",
      `${__dirname}/*Discord.ts` // glob string to load the classes
    );

    console.log(Client.getCommands());
  }

  // When the "message" event is triggered, this method is called with a specific payload (related to the event)
  @On("message")
  @Guard(
    NotBot,
    Prefix(".", true)
  )
  async onMessage(message: Message) {
    switch (message.content) {
      case "hello":
        message.reply(Answers.hello);
        break;
      case "hello2":
        message.reply(Answers.hello + "2");
        break;
      default:
        message.reply(Answers.notFound);
        break;
    }
  }

  // The onMessage method but with @Command() decorator

  @Guard(Say("hello"))
  @Command("hello", {
    description: "asdkjad",
    commandCaseSensitive: true,
    infos: { infoA: "my info" }
  })
  hello(command: CommandMessage) {
    command.reply(Answers.hello);
  }

  @Guard(Say("set prefix"))
  @Command("setPrefix", { commandCaseSensitive: true })
  changePrefix(command: CommandMessage) {
    Client.setDiscordParams(this, {
      prefix: command.params[0]
    });
    command.reply(Answers.prefix);
  }

  @Guard(Say("command not found"))
  @CommandNotFound()
  notFound(command: CommandMessage) {
    command.reply(Answers.notFound + ".");
  }

  @Guard(Say("hello comma"))
  @Command("heLLo", {
    prefix: async (message: Message, client: Client) => {
      if (message.guild.name === "dev") {
        return ",";
      }
      return "-";
    }
  })
  helloComma(command: CommandMessage) {
    command.reply(Answers.hello + " comma");
  }

  @Guard(Say("command not found comma"))
  @CommandNotFound({ prefix: "," })
  notFoundComma(command: CommandMessage) {
    command.reply(Answers.notFound + " comma");
  }

  @Guard(Say("change me"))
  @Command("changeMe", { prefix: "$", commandCaseSensitive: true })
  changeMyPrefix(command: CommandMessage) {
    Client.setCommandParams(this, this.changeMyPrefix, {
      prefix: command.params[0]
    });
    command.reply(Answers.prefix);
  }
}

// Start your app
AppDiscord.start();
<file_sep>import { Message } from "discord.js";
import { PrefixType } from ".";

export interface CommandMessage extends Message {
  params: string[];
  command: string;
  commandWithPrefix: string;
  originalCommand: string;
  originalCommandWithPrefix: string;
  prefix: PrefixType;
}
