From ac6c1342c21367dbc9f8d74a345492cc968bbffe Mon Sep 17 00:00:00 2001 From: fjodor-rybakov Date: Sun, 27 Sep 2020 19:44:06 +0300 Subject: [PATCH] add interceptor --- README.md | 32 +++++++++++++++++++ lib/constant/discord.constant.ts | 1 + lib/decorator/interface/middleware-options.ts | 3 +- lib/decorator/middleware.decorator.ts | 2 +- lib/decorator/use-interceptor.decorator.ts | 16 ++++++++++ lib/discord.module.ts | 7 ++-- lib/index.ts | 2 ++ lib/interface/discord-interceptor.ts | 11 +++++++ lib/resolver/on-command.resolver.ts | 15 +++++++-- lib/resolver/on.resolver.ts | 15 +++++++-- lib/resolver/once.resolver.ts | 16 +++++++--- lib/service/discord-interceptor.service.ts | 22 +++++++++++++ .../discord-middleware.service.ts | 4 +-- lib/{ => service}/discord.service.ts | 12 +++---- lib/utils/is-class-decorator.ts | 3 ++ package.json | 2 +- 16 files changed, 140 insertions(+), 23 deletions(-) create mode 100644 lib/decorator/use-interceptor.decorator.ts create mode 100644 lib/interface/discord-interceptor.ts create mode 100644 lib/service/discord-interceptor.service.ts rename lib/{ => service}/discord-middleware.service.ts (89%) rename lib/{ => service}/discord.service.ts (83%) create mode 100644 lib/utils/is-class-decorator.ts diff --git a/README.md b/README.md index 083af274..ce8b47a6 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ Configuration commandPrefix: '!', allowGuilds: ['Some guild id'], // Optional denyGuilds: ['Some guild id'] // Optional + // and other discord options })], providers: [BotGateway] }) @@ -59,6 +60,7 @@ Or async commandPrefix: '!', allowGuilds: ['Some guild id'], // Optional denyGuilds: ['Some guild id'] // Optional + // and other discord options }) })], providers: [BotGateway] @@ -190,6 +192,36 @@ export interface OnDecoratorOptions { } ``` +### Decorator @UseInterceptor (Test feature) + +You must implement `DiscordInterceptor` interface +```typescript +/*bot.interceptor.ts*/ + +import { DiscordInterceptor } from 'discord-nestjs'; +import { ClientEvents } from 'discord.js'; + +export class BotInterceptor implements DiscordInterceptor { + intercept(event: keyof ClientEvents, context: any): any { + return 'Some custom value'; + } +} +``` + +```typescript +/*bot.gateway.ts*/ +import { On, UseInterceptors } from 'discord-nestjs'; + +@Injectable() +export class BotGateway { + @UseInterceptors(BotInterceptor) + @On({event: 'message'}) + async onSomeEvent(context: string): Promise { + // to do something + } +} +``` + ### Decorator @Middleware (Test feature) You must implement `DiscordMiddleware` interface diff --git a/lib/constant/discord.constant.ts b/lib/constant/discord.constant.ts index 2c416a3f..efdf70b3 100644 --- a/lib/constant/discord.constant.ts +++ b/lib/constant/discord.constant.ts @@ -5,3 +5,4 @@ export const ON_DECORATOR = "ON_DECORATOR"; export const ONCE_DECORATOR = "ONCE_DECORATOR"; export const MIDDLEWARE_DECORATOR = "MIDDLEWARE_DECORATOR"; +export const USE_INTERCEPTORS_DECORATOR = "USE_INTERCEPTORS_DECORATOR"; diff --git a/lib/decorator/interface/middleware-options.ts b/lib/decorator/interface/middleware-options.ts index 57e4481b..ca211fbd 100644 --- a/lib/decorator/interface/middleware-options.ts +++ b/lib/decorator/interface/middleware-options.ts @@ -1,9 +1,10 @@ import { ClientEvents } from 'discord.js'; +import { InjectableOptions } from '@nestjs/common/decorators/core/injectable.decorator'; /** * Middleware options */ -export interface MiddlewareOptions { +export interface MiddlewareOptions extends InjectableOptions { /** * Take events */ diff --git a/lib/decorator/middleware.decorator.ts b/lib/decorator/middleware.decorator.ts index 0893ce78..85923ca2 100644 --- a/lib/decorator/middleware.decorator.ts +++ b/lib/decorator/middleware.decorator.ts @@ -9,7 +9,7 @@ export const Middleware = (options: MiddlewareOptions = {}): ClassDecorator => { return ( target: TFunction ): TFunction | void => { - applyDecorators(Injectable); + applyDecorators(Injectable(options)); Reflect.defineMetadata(MIDDLEWARE_DECORATOR, options, target.prototype); return target; }; diff --git a/lib/decorator/use-interceptor.decorator.ts b/lib/decorator/use-interceptor.decorator.ts new file mode 100644 index 00000000..b71c0f6f --- /dev/null +++ b/lib/decorator/use-interceptor.decorator.ts @@ -0,0 +1,16 @@ +import { USE_INTERCEPTORS_DECORATOR } from '../constant/discord.constant'; +import { DiscordInterceptor } from '..'; + +/** + * UseInterceptor decorator + */ +export const UseInterceptors = (...interceptors: (DiscordInterceptor | Function)[]): MethodDecorator => { + return ( + target: Record, + propertyKey: string | symbol, + descriptor: PropertyDescriptor, + ): PropertyDescriptor => { + Reflect.defineMetadata(USE_INTERCEPTORS_DECORATOR, interceptors, target, propertyKey); + return descriptor; + }; +}; diff --git a/lib/discord.module.ts b/lib/discord.module.ts index 80409706..48f1da5f 100644 --- a/lib/discord.module.ts +++ b/lib/discord.module.ts @@ -2,14 +2,15 @@ import { DynamicModule, Module, Provider } from '@nestjs/common'; import { DiscoveryModule } from '@nestjs/core'; import { DiscordModuleAsyncOptions } from './interface/discord-module-async-options'; import { DiscordOptionsFactory } from './interface/discord-options-factory'; -import { DiscordService } from './discord.service'; +import { DiscordService } from './service/discord.service'; import { DiscordModuleOption } from './interface/discord-module-option'; import { DISCORD_MODULE_OPTIONS } from './constant/discord.constant'; import { DiscordClient } from './discord-client'; -import { DiscordMiddlewareService } from './discord-middleware.service'; +import { DiscordMiddlewareService } from './service/discord-middleware.service'; import { OnResolver } from './resolver/on.resolver'; import { OnCommandResolver } from './resolver/on-command.resolver'; import { OnceResolver } from './resolver/once.resolver'; +import { DiscordInterceptorService } from './service/discord-interceptor.service'; @Module({ imports: [DiscoveryModule] @@ -20,6 +21,7 @@ export class DiscordModule { module: DiscordModule, providers: [ DiscordMiddlewareService, + DiscordInterceptorService, OnResolver, OnCommandResolver, OnceResolver, @@ -45,6 +47,7 @@ export class DiscordModule { imports: options.imports || [], providers: [ DiscordMiddlewareService, + DiscordInterceptorService, OnResolver, OnCommandResolver, OnceResolver, diff --git a/lib/index.ts b/lib/index.ts index ac9625d6..1b52f46c 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,10 +1,12 @@ export * from './discord.module'; export * from './interface/discord-module-option'; export * from './interface/discord-middleware'; +export * from './interface/discord-interceptor'; export * from './decorator/on.decorator'; export * from './decorator/once.decorator'; export * from './decorator/on-command.decorator'; export * from './decorator/middleware.decorator'; +export * from './decorator/use-interceptor.decorator'; export * from './decorator/interface/on-command-decorator-options'; export * from './decorator/interface/on-decorator-options'; export * from './decorator/interface/middleware-options'; diff --git a/lib/interface/discord-interceptor.ts b/lib/interface/discord-interceptor.ts new file mode 100644 index 00000000..23750bf2 --- /dev/null +++ b/lib/interface/discord-interceptor.ts @@ -0,0 +1,11 @@ +import { ClientEvents } from "discord.js"; + +/** + * Base interceptor interface + */ +export interface DiscordInterceptor { + intercept( + event: keyof ClientEvents, + context: T + ): any | Promise; +} diff --git a/lib/resolver/on-command.resolver.ts b/lib/resolver/on-command.resolver.ts index 88d6eec2..0ebb5934 100644 --- a/lib/resolver/on-command.resolver.ts +++ b/lib/resolver/on-command.resolver.ts @@ -1,19 +1,25 @@ import { DiscordResolve } from '../interface/discord-resolve'; import { Message } from 'discord.js'; -import { ON_MESSAGE_DECORATOR } from '../constant/discord.constant'; +import { ON_MESSAGE_DECORATOR, USE_INTERCEPTORS_DECORATOR } from '../constant/discord.constant'; import { DiscordClient, OnCommandDecoratorOptions } from '..'; import { DiscordResolveOptions } from '../interface/discord-resolve-options'; -import { DiscordMiddlewareService } from '../discord-middleware.service'; +import { DiscordMiddlewareService } from '../service/discord-middleware.service'; import { Injectable } from '@nestjs/common'; +import { DiscordInterceptor } from '..'; +import { DiscordInterceptorService } from '../service/discord-interceptor.service'; @Injectable() export class OnCommandResolver implements DiscordResolve { - constructor(private readonly discordMiddlewareService: DiscordMiddlewareService) { + constructor( + private readonly discordMiddlewareService: DiscordMiddlewareService, + private readonly discordInterceptorService: DiscordInterceptorService + ) { } resolve(options: DiscordResolveOptions): void { const { discordClient, instance, methodName, middlewareList } = options; const metadata: OnCommandDecoratorOptions = Reflect.getMetadata(ON_MESSAGE_DECORATOR, instance, methodName); + const interceptors: (DiscordInterceptor | Function)[] = Reflect.getMetadata(USE_INTERCEPTORS_DECORATOR, instance, methodName); if (metadata) { const { name, @@ -54,6 +60,9 @@ export class OnCommandResolver implements DiscordResolve { } message.content = message.content.trim(); await this.discordMiddlewareService.applyMiddleware(middlewareList, 'message', [message]); + if (interceptors && interceptors.length !== 0) { + message = await this.discordInterceptorService.applyInterceptors(interceptors, 'message', message); + } instance[methodName](message); } }); diff --git a/lib/resolver/on.resolver.ts b/lib/resolver/on.resolver.ts index 529b139c..ccc108e5 100644 --- a/lib/resolver/on.resolver.ts +++ b/lib/resolver/on.resolver.ts @@ -1,19 +1,25 @@ import { DiscordResolve } from '../interface/discord-resolve'; import { ClientEvents } from 'discord.js'; -import { ON_DECORATOR } from '../constant/discord.constant'; +import { ON_DECORATOR, USE_INTERCEPTORS_DECORATOR } from '../constant/discord.constant'; import { DiscordClient, OnDecoratorOptions } from '..'; import { DiscordResolveOptions } from '../interface/discord-resolve-options'; -import { DiscordMiddlewareService } from '../discord-middleware.service'; +import { DiscordMiddlewareService } from '../service/discord-middleware.service'; import { Injectable } from '@nestjs/common'; +import { DiscordInterceptor } from '..'; +import { DiscordInterceptorService } from '../service/discord-interceptor.service'; @Injectable() export class OnResolver implements DiscordResolve { - constructor(private readonly discordMiddlewareService: DiscordMiddlewareService) { + constructor( + private readonly discordMiddlewareService: DiscordMiddlewareService, + private readonly discordInterceptorService: DiscordInterceptorService + ) { } resolve(options: DiscordResolveOptions): void { const { discordClient, instance, methodName, middlewareList } = options; const metadata: OnDecoratorOptions = Reflect.getMetadata(ON_DECORATOR, instance, methodName); + const interceptors: (DiscordInterceptor | Function)[] = Reflect.getMetadata(USE_INTERCEPTORS_DECORATOR, instance, methodName); if (metadata) { discordClient.on(metadata.event, async (...data: ClientEvents[keyof ClientEvents]) => { if (!this.isAllowGuild(discordClient, data)) { @@ -23,6 +29,9 @@ export class OnResolver implements DiscordResolve { return; } await this.discordMiddlewareService.applyMiddleware(middlewareList, metadata.event, data); + if (interceptors && interceptors.length !== 0) { + data = await this.discordInterceptorService.applyInterceptors(interceptors, metadata.event, data); + } instance[methodName](...data); }); } diff --git a/lib/resolver/once.resolver.ts b/lib/resolver/once.resolver.ts index edb602bd..af5b770a 100644 --- a/lib/resolver/once.resolver.ts +++ b/lib/resolver/once.resolver.ts @@ -1,19 +1,25 @@ import { DiscordResolve } from '../interface/discord-resolve'; import { ClientEvents } from 'discord.js'; import { DiscordClient, OnDecoratorOptions } from '..'; -import { ONCE_DECORATOR } from '../constant/discord.constant'; +import { ONCE_DECORATOR, USE_INTERCEPTORS_DECORATOR } from '../constant/discord.constant'; import { DiscordResolveOptions } from '../interface/discord-resolve-options'; -import { DiscordMiddlewareService } from '../discord-middleware.service'; +import { DiscordMiddlewareService } from '../service/discord-middleware.service'; import { Injectable } from '@nestjs/common'; +import { DiscordInterceptor } from '..'; +import { DiscordInterceptorService } from '../service/discord-interceptor.service'; @Injectable() export class OnceResolver implements DiscordResolve { - constructor(private readonly discordMiddlewareService: DiscordMiddlewareService) { + constructor( + private readonly discordMiddlewareService: DiscordMiddlewareService, + private readonly discordInterceptorService: DiscordInterceptorService + ) { } resolve(options: DiscordResolveOptions): void { const {discordClient, instance, methodName, middlewareList} = options; const metadata: OnDecoratorOptions = Reflect.getMetadata(ONCE_DECORATOR, instance, methodName); + const interceptors: (DiscordInterceptor | Function)[] = Reflect.getMetadata(USE_INTERCEPTORS_DECORATOR, instance, methodName); if (metadata) { discordClient.once(metadata.event, async (...data: ClientEvents[keyof ClientEvents]) => { if (!this.isAllowGuild(discordClient, data)) { @@ -23,12 +29,14 @@ export class OnceResolver implements DiscordResolve { return; } await this.discordMiddlewareService.applyMiddleware(middlewareList, metadata.event, data); + if (interceptors && interceptors.length !== 0) { + data = await this.discordInterceptorService.applyInterceptors(interceptors, metadata.event, data); + } instance[methodName](...data); }); } } - private isAllowGuild(discordClient: DiscordClient, data: any[] = []): boolean { const guild = data.find((item) => !!item && !!item.guild); const guildId = !!guild && guild.guild.id; diff --git a/lib/service/discord-interceptor.service.ts b/lib/service/discord-interceptor.service.ts new file mode 100644 index 00000000..098e530b --- /dev/null +++ b/lib/service/discord-interceptor.service.ts @@ -0,0 +1,22 @@ +import { DiscordInterceptor } from '..'; +import { ClientEvents, Message } from 'discord.js'; +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class DiscordInterceptorService { + async applyInterceptors( + interceptors: (DiscordInterceptor | Function)[], + event: keyof ClientEvents, + context: any + ): Promise { + return interceptors.reduce(async (prev: Promise, curr: DiscordInterceptor | Function) => { + let interceptorInstance: DiscordInterceptor; + if (typeof curr === 'function') { + // @ts-ignore + interceptorInstance = new curr(); + } + const prevData = await prev; + return interceptorInstance.intercept(event, prevData); + }, context); + } +} diff --git a/lib/discord-middleware.service.ts b/lib/service/discord-middleware.service.ts similarity index 89% rename from lib/discord-middleware.service.ts rename to lib/service/discord-middleware.service.ts index 62891c7d..77c4f3a2 100644 --- a/lib/discord-middleware.service.ts +++ b/lib/service/discord-middleware.service.ts @@ -1,8 +1,8 @@ -import { DiscordMiddlewareInstance } from './interface/discord-middleware-instance'; +import { DiscordMiddlewareInstance } from '../interface/discord-middleware-instance'; import { ClientEvents } from "discord.js"; import { Injectable } from '@nestjs/common'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; -import { MIDDLEWARE_DECORATOR } from './constant/discord.constant'; +import { MIDDLEWARE_DECORATOR } from '../constant/discord.constant'; @Injectable() export class DiscordMiddlewareService { diff --git a/lib/discord.service.ts b/lib/service/discord.service.ts similarity index 83% rename from lib/discord.service.ts rename to lib/service/discord.service.ts index 9eb41e94..c76a3e53 100644 --- a/lib/discord.service.ts +++ b/lib/service/discord.service.ts @@ -1,12 +1,12 @@ import { Injectable, OnApplicationBootstrap } from '@nestjs/common'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { DiscoveryService, MetadataScanner } from '@nestjs/core'; -import { DiscordResolve } from './interface/discord-resolve'; -import { OnCommandResolver } from './resolver/on-command.resolver'; -import { OnResolver } from './resolver/on.resolver'; -import { DiscordClient } from './discord-client'; -import { OnceResolver } from './resolver/once.resolver'; -import { DiscordMiddlewareInstance } from './interface/discord-middleware-instance'; +import { DiscordResolve } from '../interface/discord-resolve'; +import { OnCommandResolver } from '../resolver/on-command.resolver'; +import { OnResolver } from '../resolver/on.resolver'; +import { DiscordClient } from '../discord-client'; +import { OnceResolver } from '../resolver/once.resolver'; +import { DiscordMiddlewareInstance } from '../interface/discord-middleware-instance'; import { DiscordMiddlewareService } from './discord-middleware.service'; @Injectable() diff --git a/lib/utils/is-class-decorator.ts b/lib/utils/is-class-decorator.ts new file mode 100644 index 00000000..612d285e --- /dev/null +++ b/lib/utils/is-class-decorator.ts @@ -0,0 +1,3 @@ +export const isClassDecorator = (target: any): boolean => { + return typeof target === 'function'; +} diff --git a/package.json b/package.json index d6ae1552..95196e68 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "discord-nestjs", - "version": "0.2.3", + "version": "0.2.4", "description": "NestJS package for discord.js", "author": "fjodor-rybakov", "license": "MIT",