ledis v2.0.3
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();