2.0.3 • Published 3 years ago

ledis v2.0.3

Weekly downloads
-
License
ISC
Repository
github
Last release
3 years ago

Ledis

It's a typescript framework for building discord bots by decorators, based on discord.js, like discord.ts or nest.js

Powered by discord.js Inspired by discord.ts, nest.js and typedi

Index

Installation

Install ledis, discord.js and reflect-metadata to your project

npm i ledis discord.js reflect-metadata

Import reflect-metadata on top of your root file

import 'reflect-metadata';
// ...code

Activate experimental decorators in your tsconfig.json

  {
    "compilerOptions": {
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true
    }
  }

Usage

import { 
  bootstrap,
  App,
  Command,
  CommandError,
  CommandNotFound,
  ErrorArg,
  Once,
  Module,
  MatchGroup
} from 'ledis';
import { Client } from 'discord.js';

const token = 'Your token';

@Module()
class HiModule {
    @Command('hi :id')
    public hi(@MatchGroup('id') id: string){
     return `hi ${id}`;
    }
}

@App({
  prefix: '!',
  modules: [HiModule]
})
class Application {
  constructor(private client: Client){}

  @Once('ready')
  public readyHandler() {
    console.log(`Logged in as ${this.client?.user?.tag}!`);
  }

  @CommandError()
  public commandError(@ErrorArg() error: Error) {
    console.error(error);
  }

  @CommandNotFound()
  public commandNotFound() {
    return 'command not found!';
  }
}

const client = new Client();

const main = async () => {
  await bootstrap(client, Application);
  await client.login(token);
};

main();

Example

Repository with example ledis discord bot

Dependency Injection

You can inject your dependencies to ledis App and Modules. For this define dependencies in @App/@Module dependencies field. Ledis have module scope support. If your local dependency can't find self dependencies in module scope then it will search this dependency in global scope, if global scope doesn't have this dependency or ledis di finds circular dependency then ledis di throws error. For add dependencies to global scope just add it to @App dependencies field.

import { Service, Module, App, Inject, Command, bootstrap } from 'ledis';
import { Client } from 'discord.js';

type LoggerData = 'loggerData';
const loggerData: LoggerData = 'loggerData';

const createLogger = (loggerData: LoggerData) => {
  console.log(loggerData);
  
  return console.log;
};

type TestValue = 'test';
const testValue: TestValue = 'test';

@Service()
class HiService {
  public hi(){ return 'hi' }
}

@Service()
class TestService {
  constructor( 
    @Inject('testValue')
    private testValue: TestValue, // will be injected from global

    @Inject('logger')
    private logger: (...args: any[]) => void
  ){}
  
  public getTestValue(){
    this.logger('test value getted!');

    return this.testValue
  }
}

@Module({
  dependencies: [HiService, TestService] // define module scoped HiService
})
class HiModule {
  constructor( 
    private hiService: HiService, // will be injected from local
    private testService: TestService  // will be injected from local
   ){}
  
  @Command('hi')
  public hi(){ return this.hiService.hi() }

  @Command('test')
  public test(){ return this.testService.getTestValue() }
}

@App({
  modules: [HiModule],
  dependencies: [
    {
      token: 'testValue',
      useValue: testValue,
    },
    {
      token: 'loggerData',
      useFactory: () => loggerData
    },
    {
      token: 'logger',
      useFactory: (loggerData: LoggerData) => createLogger(loggerData),
      inject: ['loggerData']
    }
  ]
})
class Application {}

const client = new Client();

const main = async () => {
  await bootstrap(client, Application);
  await client.login('token')
}

main();

Api

Decorators

@App(AppMetadata)

Define class as root application.

type AppMetadata = {
  prefix?: string | ({ message: DiscordMessage }) => Promise<string> // prefix for commands
  modules?: Class[] // classes with @Module() decorator
  dependencies?: Dependencies // array with global scoped dependencies
}

@App({}: AppMetadata)
class Application {}

@Module(ModuleMetadata)

Define class as module with commands. @Module class creates module scoped dependencies container for this module and services inside dependencies array.

type ModuleMetadata = {
  dependencies?: Dependencies // array with module scoped dependencies
}

const logger = console.log;
const loggerData = '123';

type Logger = typeof logger;
type LoggerData = typeof loggerData;

@Service()
class HiService {
  constructor(
    // for non class dependencies need @Inject(token)
    @Inject('logger')
    private logger: Logger
  ){}
  
  public hi(){
    this.logger('run hi');
  }
}

@Module({
  // set locale dependencies
  dependencies: [
    // HiService will init as module scoped dependencion with another module/global scoped dependencies
    HiService,
    {
      token: 'loggerData',
      useValue: loggerData
    },
    {
      token: 'logger',
      // can be async
      useFactory: (loggerData: LoggerData) => logger,
      inject: ['loggerData']
    }
  ]
})
class HiModule {
  constructor(private hiService: HiService){}
  
  @Command('hi')
  public hi(){
    return this.hiService.hi();
  }
}

@Service()

Define class as ledis service. In @Service class ledis can insert dependencies.

@Service()
class HiService {
  public hi() {
    return 'hi';
  }
}

@Service()
class HiUserService {
  constructor(private hiService: HiService){}
  
  public hi(){
    return `${this.hiService.hi()} user!`;
  }
}

@App({
  dependencies: [HiService, HiUserService]
})
class Application {
  constructor(private hiUserService: HiUserService){}
  
  @Command('hi-user-command')
  public hiUserCommand(){
    return this.hiUserService.hi();
  }
}

@Command(patternOrCommandMeta: string | RegExp | CommandMeta)

Define method as command handler. If return not undefined, send this value to channel (message.channel.send(returnedValue))

@App()
class Application {
  @Command('hi')
  public hi(){
    return `hi!`;
  }
  
  @Command(/hi/su)
  public hiRegExp(){
    return `hi!`;
  }

  @Command({
    pattern: 'hi :id', // pattern
    description: 'hi command' // command description,
    data: {} // custom data
  })
  public hiId(
    @MatchGroup('id') id: string;
  ){
    return `hi ${id}!`;
  }
}

@ComandError()

Define method as command error handler If return not undefined, send this value to channel (message.channel.send(returnedValue))

@App()
class Application {
  @CommandError()
  public commandErrorHandler(
    @ErrorArg() error: Error
  ){
    console.log(error);
  }
}

@CommandNotFound()

Define method as command not found handler If return not undefined, send this value to channel (message.channel.send(returnedValue))

@App()
class Application {
  @CommandNotFound()
  public commandNotFoundHandler(
    @Message() message: DiscordMessage
  ){
    console.log(message.content);
  }
}

@Middleware(handler: (ctx, next) => void)

Define a middleware for command/commandError/commandNotFound/on/once handler

@App()
class Application {
  @Middleware(async (ctx, next) => next())
  @Middleware((ctx, next) => {
    if (context.message.content !== '!hi') return;
    next();
  })
  @Command('hi')
  public hi(){
    return 'hi!'
  }
}

@On(eventName: string)

Define on discord.js handler

@App()
class Application {
  @On('message')
  public messageHandler(
    @Arguments() [message]: ArgumentsOf<"message">
  ){
    console.log(message);
  }
}

@Once(eventName: string)

Define once discord.js handler

import { Client } from 'discord.js';

@App()
class Application {
  constructor(private client: Client){}

  @Once('ready')
  public readyHandler(){
    console.log(`Logged in as ${this.client?.user?.tag}!`);
  }
}

@Inject(token: string)

Define constructor parameter as injected token dependency

const logger = console.log;
type Logger = typeof logger;

@App({
  dependencies: [{
    token: 'logger',
    useValue: logger
  }]
})
class Application {
  constructor(
    @Inject('logger') logger: Logger
  ){}
  
  @On('ready')
  public readyHandler(){
    this.logger('ready');
  }
}

@Arg(argIndex: number)

Define parameter as command argument (prefixcommand arg1 arg2 arg3).

@App()
class Application {
  @Command(/hi/su)
  public hi(
    @Arg(0) id: string
  ){
    return `hi ${id}!`;
  }
}

@MatchGroup(groupKey: string)

Define parameter as regexp match group.

@App()
class Application {
  @Command('command :id')
  public command(@MatchGroup('id') id: string) {
    return id;
  }

  @Command('command2 ...ids')
  public command2(@MatchGroup('ids') ids: string) {
    return ids;
  }

  @Command(/command3 (?<value>.+)/su)
  public command3(@MatchGroup('value') value: string) {
    return value;
  }
}

@Arguments()

Define parameter as discord.js on/once handler arguments

@App()
class Application {
  @On('message')
  public messageHandler(
    @Arguments() [message]: ArgumentsOf<"message">
  ){
    console.log(message);
  }
}

@Commands()

Define parameter as LedisCommands

@App()
class Application {
  @Command('help')
  public help(
    @Commands() commands: LedisCommands
   ){
    const helpMessage = commands.map(...);

    return helpMessage;
  }
}

@ErrorArg()

Define parameter as command error

@App()
class Application {
  @CommandError()
  public commandErrorHandler(
    @ErrorArg() error: Error
  ){
    console.log(error);
  }
}

@Message()

Define parameter as discord.js message

@App()
class Application {
  @Command('hi :id')
  public hi(
    @Arg('id') id: string
    @Message() message: DiscordMessage
  ){
    message.reply(`hi ${id}!`);
  }
}

Customize

Custom parameter decorator

You can create custom parameter decorator by createParamDecorator.

import { createParamDecorator, CommandParameterContext } from 'ledis';

const supportedTypes = [
  'ledis:command-not-found',
  'ledis:command-error',
  'ledis:command',
] as const;

const MessageAuthor = (): ParameterDecorator => createParamDecorator(ctx => {
    // if ctx doesn't have message object
    if (!supportedTypes.some((event) => event === ctx.event)) return;
    
    return (ctx as CommandParameterContext).message.author;
})

Bootstrap

bootstrap(client: Client, App: Class) => Promise

Function for bootstrap your Application

import { Client } from 'discord.js';
import { bootstrap, App } from 'ledis';
  
@App()
class Application {}
  
const main = async () => {
  const client = new Client();  
  
  await bootstrap(client, Application); // bootstrap also inject Config and Client to global di scope
  await client.login('your token');
};

main();
2.0.3

3 years ago

2.0.2

3 years ago

2.0.1

3 years ago

2.0.0

3 years ago

1.0.11

3 years ago

1.0.10

3 years ago

1.0.9

3 years ago

1.0.8

3 years ago

1.0.7

3 years ago

1.0.6

3 years ago

1.0.5

3 years ago

1.0.4

3 years ago

1.0.3

3 years ago

1.0.2

3 years ago

1.0.1

3 years ago

1.0.0

3 years ago