From ccb2db01065256376848f4cc4a56cda6d2f61d6d Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 01:27:01 +0300 Subject: [PATCH 1/6] feat(): multiple instances support --- lib/decorators/inject-bot.decorator.ts | 5 +- lib/index.ts | 6 +- lib/interfaces/index.ts | 1 + lib/interfaces/telegraf-options.interface.ts | 5 + lib/interfaces/update-metadata.interface.ts | 6 + lib/services/base-explorer.service.ts | 59 ++++++++ lib/services/index.ts | 2 + .../metadata-accessor.service.ts} | 6 +- .../updates-explorer.service.ts} | 138 ++++++++++-------- lib/telegraf-core.module.ts | 109 +++++++++++--- lib/telegraf.constants.ts | 3 +- lib/telegraf.provider.ts | 37 ----- lib/utils/get-bot-token.util.ts | 7 + lib/utils/index.ts | 1 + 14 files changed, 263 insertions(+), 122 deletions(-) create mode 100644 lib/interfaces/update-metadata.interface.ts create mode 100644 lib/services/base-explorer.service.ts create mode 100644 lib/services/index.ts rename lib/{telegraf-metadata.accessor.ts => services/metadata-accessor.service.ts} (97%) rename lib/{telegraf.explorer.ts => services/updates-explorer.service.ts} (64%) delete mode 100644 lib/telegraf.provider.ts create mode 100644 lib/utils/get-bot-token.util.ts create mode 100644 lib/utils/index.ts diff --git a/lib/decorators/inject-bot.decorator.ts b/lib/decorators/inject-bot.decorator.ts index 84f1c50..47808e3 100644 --- a/lib/decorators/inject-bot.decorator.ts +++ b/lib/decorators/inject-bot.decorator.ts @@ -1,4 +1,5 @@ import { Inject } from '@nestjs/common'; -import { TELEGRAF_PROVIDER } from '../telegraf.constants'; +import { getBotToken } from '../utils'; -export const InjectBot = (): ParameterDecorator => Inject(TELEGRAF_PROVIDER); +export const InjectBot = (name?: string): ParameterDecorator => + Inject(getBotToken(name)); diff --git a/lib/index.ts b/lib/index.ts index 39b657d..819279a 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -9,4 +9,8 @@ export * as Extra from 'telegraf/extra'; export * from './decorators'; export * from './interfaces'; export * from './telegraf.module'; -export * from './telegraf.provider'; + +/** + * Backward compatibility with versions < 1.4.0 + */ +export { Telegraf as TelegrafProvider } from 'telegraf'; diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts index 3b25926..a403036 100644 --- a/lib/interfaces/index.ts +++ b/lib/interfaces/index.ts @@ -1,2 +1,3 @@ export * from './context.interface'; export * from './telegraf-options.interface'; +export * from './update-metadata.interface'; diff --git a/lib/interfaces/telegraf-options.interface.ts b/lib/interfaces/telegraf-options.interface.ts index 13c729d..60e96f2 100644 --- a/lib/interfaces/telegraf-options.interface.ts +++ b/lib/interfaces/telegraf-options.interface.ts @@ -4,6 +4,7 @@ import { LaunchPollingOptions, LaunchWebhookOptions, } from 'telegraf/typings/telegraf'; +import { Middleware } from 'telegraf/typings/composer'; export interface TelegrafModuleOptions { token: string; @@ -12,6 +13,9 @@ export interface TelegrafModuleOptions { polling?: LaunchPollingOptions; webhook?: LaunchWebhookOptions; }; + botName?: string; + include?: Function[]; + middlewares?: ReadonlyArray>; } export interface TelegrafOptionsFactory { @@ -20,6 +24,7 @@ export interface TelegrafOptionsFactory { export interface TelegrafModuleAsyncOptions extends Pick { + botName?: string; useExisting?: Type; useClass?: Type; useFactory?: ( diff --git a/lib/interfaces/update-metadata.interface.ts b/lib/interfaces/update-metadata.interface.ts new file mode 100644 index 0000000..4c2ba0e --- /dev/null +++ b/lib/interfaces/update-metadata.interface.ts @@ -0,0 +1,6 @@ +export interface UpdateMetadata { + name: string; + type: string; + methodName: string; + callback?: Function | Record; +} diff --git a/lib/services/base-explorer.service.ts b/lib/services/base-explorer.service.ts new file mode 100644 index 0000000..eff7c82 --- /dev/null +++ b/lib/services/base-explorer.service.ts @@ -0,0 +1,59 @@ +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { Module } from '@nestjs/core/injector/module'; +import { flattenDeep, groupBy, identity, isEmpty, mapValues } from 'lodash'; +import { UpdateMetadata } from '../interfaces'; + +export class BaseExplorerService { + getModules( + modulesContainer: Map, + include: Function[], + ): Module[] { + if (!include || isEmpty(include)) { + return [...modulesContainer.values()]; + } + const whitelisted = this.includeWhitelisted(modulesContainer, include); + return whitelisted; + } + + includeWhitelisted( + modulesContainer: Map, + include: Function[], + ): Module[] { + const modules = [...modulesContainer.values()]; + return modules.filter(({ metatype }) => + include.some((item) => item === metatype), + ); + } + + flatMap( + modules: Module[], + callback: (instance: InstanceWrapper, moduleRef: Module) => T | T[], + ): T[] { + const invokeMap = () => { + return modules.map((moduleRef) => { + const providers = [...moduleRef.providers.values()]; + return providers.map((wrapper) => callback(wrapper, moduleRef)); + }); + }; + return flattenDeep(invokeMap()).filter(identity); + } + + groupMetadata(resolvers: UpdateMetadata[]) { + const groupByType = groupBy( + resolvers, + (metadata: UpdateMetadata) => metadata.type, + ); + const groupedMetadata = mapValues( + groupByType, + (resolversArr: UpdateMetadata[]) => + resolversArr.reduce( + (prev, curr) => ({ + ...prev, + [curr.name]: curr.callback, + }), + {}, + ), + ); + return groupedMetadata; + } +} diff --git a/lib/services/index.ts b/lib/services/index.ts new file mode 100644 index 0000000..9514e05 --- /dev/null +++ b/lib/services/index.ts @@ -0,0 +1,2 @@ +export * from './updates-explorer.service'; +export * from './metadata-accessor.service'; diff --git a/lib/telegraf-metadata.accessor.ts b/lib/services/metadata-accessor.service.ts similarity index 97% rename from lib/telegraf-metadata.accessor.ts rename to lib/services/metadata-accessor.service.ts index b204d60..a4c0ac7 100644 --- a/lib/telegraf-metadata.accessor.ts +++ b/lib/services/metadata-accessor.service.ts @@ -12,11 +12,11 @@ import { OnOptions, PhoneOptions, UpdateHookOptions, -} from './decorators'; -import { DECORATORS } from './telegraf.constants'; +} from '../decorators'; +import { DECORATORS } from '../telegraf.constants'; @Injectable() -export class TelegrafMetadataAccessor { +export class MetadataAccessorService { constructor(private readonly reflector: Reflector) {} isUpdate(target: Type | Function): boolean { diff --git a/lib/telegraf.explorer.ts b/lib/services/updates-explorer.service.ts similarity index 64% rename from lib/telegraf.explorer.ts rename to lib/services/updates-explorer.service.ts index 4ddefd6..31d4325 100644 --- a/lib/telegraf.explorer.ts +++ b/lib/services/updates-explorer.service.ts @@ -1,10 +1,12 @@ -import { Injectable, OnModuleInit } from '@nestjs/common'; -import { DiscoveryService, ModuleRef } from '@nestjs/core'; +import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService, ModuleRef, ModulesContainer } from '@nestjs/core'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; -import { TelegrafProvider } from './telegraf.provider'; -import { TELEGRAF_PROVIDER } from './telegraf.constants'; +import { MetadataAccessorService } from './metadata-accessor.service'; +import { + TELEGRAF_BOT_NAME, + TELEGRAF_MODULE_OPTIONS, +} from '../telegraf.constants'; import { ActionOptions, CashtagOptions, @@ -17,65 +19,88 @@ import { OnOptions, PhoneOptions, UpdateHookOptions, -} from './decorators'; +} from '../decorators'; +import { Telegraf } from 'telegraf'; +import { TelegrafModuleOptions } from '../interfaces'; +import { BaseExplorerService } from './base-explorer.service'; +import { Module } from '@nestjs/core/injector/module'; @Injectable() -export class TelegrafExplorer implements OnModuleInit { +export class UpdatesExplorerService + extends BaseExplorerService + implements OnModuleInit { + private readonly logger = new Logger(UpdatesExplorerService.name); + constructor( + @Inject(TELEGRAF_BOT_NAME) + private readonly botName: string, + @Inject(TELEGRAF_MODULE_OPTIONS) + private readonly telegrafModuleOptions: TelegrafModuleOptions, private readonly moduleRef: ModuleRef, private readonly discoveryService: DiscoveryService, - private readonly metadataAccessor: TelegrafMetadataAccessor, + private readonly metadataAccessor: MetadataAccessorService, private readonly metadataScanner: MetadataScanner, - ) {} + private readonly modulesContainer: ModulesContainer, + ) { + super(); + } - private telegraf: TelegrafProvider; + private bot: Telegraf; - onModuleInit() { - this.telegraf = this.moduleRef.get(TELEGRAF_PROVIDER, { + onModuleInit(): void { + this.logger.debug(this.botName); + this.bot = this.moduleRef.get>(this.botName, { strict: false, }); this.explore(); } explore() { - /** - * Update providers section is only for decorators under Update decorator - */ - const updateProviders: InstanceWrapper[] = this.discoveryService - .getProviders() - .filter((wrapper: InstanceWrapper) => + const modules = this.getModules( + this.modulesContainer, + this.telegrafModuleOptions.include || [], + ); + const updates = this.flatMap(modules, (instance, moduleRef) => + this.applyUpdates(instance, moduleRef), + ); + } + + private applyUpdates(wrapper: InstanceWrapper, moduleRef: Module) { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + const prototype = Object.getPrototypeOf(instance); + + const providers: InstanceWrapper[] = this.discoveryService.getProviders(); + const updateProviders: InstanceWrapper[] = providers.filter( + (wrapper: InstanceWrapper) => this.metadataAccessor.isUpdate(wrapper.metatype), - ); + ); updateProviders.forEach((wrapper: InstanceWrapper) => { const { instance } = wrapper; - - this.metadataScanner.scanFromPrototype( - instance, - Object.getPrototypeOf(instance), - (key: string) => { - if (this.metadataAccessor.isUpdateHook(instance[key])) { - const metadata = this.metadataAccessor.getUpdateHookMetadata( - instance[key], - ); - this.handleUpdateHook(instance, key, metadata); - } - }, - ); + if (!instance) { + return undefined; + } + this.metadataScanner.scanFromPrototype(instance, prototype, (name) => { + if (this.metadataAccessor.isUpdateHook(instance[name])) { + const metadata = this.metadataAccessor.getUpdateHookMetadata( + instance[name], + ); + this.handleUpdateHook(instance, name, metadata); + } + }); }); - const providers: InstanceWrapper[] = this.discoveryService.getProviders(); - providers.forEach((wrapper: InstanceWrapper) => { const { instance } = wrapper; - if (!instance) { - return; + return undefined; } - this.metadataScanner.scanFromPrototype( instance, - Object.getPrototypeOf(instance), + prototype, (key: string) => { if (this.metadataAccessor.isTelegrafUse(instance[key])) { this.handleTelegrafUse(instance, key); @@ -146,19 +171,19 @@ export class TelegrafExplorer implements OnModuleInit { } handleUpdateHook(instance: object, key: string, metadata: UpdateHookOptions) { - this.telegraf.on(metadata.updateType, instance[key].bind(instance)); + this.bot.on(metadata.updateType, instance[key].bind(instance)); } handleTelegrafUse(instance: object, key: string) { - this.telegraf.use(instance[key].bind(instance)); + this.bot.use(instance[key].bind(instance)); } handleTelegrafOn(instance: object, key: string, metadata: OnOptions) { - this.telegraf.on(metadata.updateTypes, instance[key].bind(instance)); + this.bot.on(metadata.updateTypes, instance[key].bind(instance)); } handleTelegrafHears(instance: object, key: string, metadata: HearsOptions) { - this.telegraf.hears(metadata.triggers, instance[key].bind(instance)); + this.bot.hears(metadata.triggers, instance[key].bind(instance)); } handleTelegrafCommand( @@ -166,25 +191,25 @@ export class TelegrafExplorer implements OnModuleInit { key: string, metadata: CommandOptions, ) { - this.telegraf.command(metadata.commands, instance[key].bind(instance)); + this.bot.command(metadata.commands, instance[key].bind(instance)); } handleTelegrafStart(instance: object, key: string) { - this.telegraf.start(instance[key].bind(instance)); + this.bot.start(instance[key].bind(instance)); } handleTelegrafHelp(instance: object, key: string) { - this.telegraf.help(instance[key].bind(instance)); + this.bot.help(instance[key].bind(instance)); } handleTelegrafSettings(instance: object, key: string) { // @ts-ignore - this.telegraf.settings(instance[key].bind(instance)); + this.bot.settings(instance[key].bind(instance)); } handleTelegrafEntity(instance: object, key: string, metadata: EntityOptions) { // @ts-ignore - this.telegraf.entity(metadata.entity, instance[key].bind(instance)); + this.bot.entity(metadata.entity, instance[key].bind(instance)); } handleTelegrafMention( @@ -193,12 +218,12 @@ export class TelegrafExplorer implements OnModuleInit { metadata: MentionOptions, ) { // @ts-ignore - this.telegraf.mention(metadata.username, instance[key].bind(instance)); + this.bot.mention(metadata.username, instance[key].bind(instance)); } handleTelegrafPhone(instance: object, key: string, metadata: PhoneOptions) { // @ts-ignore - this.telegraf.phone(metadata.phone, instance[key].bind(instance)); + this.bot.phone(metadata.phone, instance[key].bind(instance)); } handleTelegrafHashtag( @@ -207,7 +232,7 @@ export class TelegrafExplorer implements OnModuleInit { metadata: HashtagOptions, ) { // @ts-ignore - this.telegraf.hashtag(metadata.hashtag, instance[key].bind(instance)); + this.bot.hashtag(metadata.hashtag, instance[key].bind(instance)); } handleTelegrafCashtag( @@ -216,11 +241,11 @@ export class TelegrafExplorer implements OnModuleInit { metadata: CashtagOptions, ) { // @ts-ignore - this.telegraf.cashtag(metadata.cashtag, instance[key].bind(instance)); + this.bot.cashtag(metadata.cashtag, instance[key].bind(instance)); } handleTelegrafAction(instance: object, key: string, metadata: ActionOptions) { - this.telegraf.action(metadata.triggers, instance[key].bind(instance)); + this.bot.action(metadata.triggers, instance[key].bind(instance)); } handleTelegrafInlineQuery( @@ -230,16 +255,13 @@ export class TelegrafExplorer implements OnModuleInit { ) { if (metadata.triggers) { // @ts-ignore - this.telegraf.inlineQuery( - metadata.triggers, - instance[key].bind(instance), - ); + this.bot.inlineQuery(metadata.triggers, instance[key].bind(instance)); } else { - this.telegraf.on(metadata.updateType, instance[key].bind(instance)); + this.bot.on(metadata.updateType, instance[key].bind(instance)); } } handleTelegrafGameQuery(instance: object, key: string) { - this.telegraf.gameQuery(instance[key].bind(instance)); + this.bot.gameQuery(instance[key].bind(instance)); } } diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts index 9be69ed..7cf3703 100644 --- a/lib/telegraf-core.module.ts +++ b/lib/telegraf-core.module.ts @@ -1,56 +1,120 @@ -import { DiscoveryModule } from '@nestjs/core'; -import { Module, DynamicModule, Provider, Type } from '@nestjs/common'; +import { DiscoveryModule, ModuleRef } from '@nestjs/core'; +import { + Module, + DynamicModule, + Provider, + Type, + Global, + Inject, + OnApplicationShutdown, + Logger, +} from '@nestjs/common'; import { TelegrafModuleOptions, TelegrafModuleAsyncOptions, TelegrafOptionsFactory, } from './interfaces'; import { + TELEGRAF_BOT_NAME, TELEGRAF_MODULE_OPTIONS, - TELEGRAF_PROVIDER, } from './telegraf.constants'; -import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; -import { TelegrafExplorer } from './telegraf.explorer'; -import { TelegrafProvider } from './telegraf.provider'; +import { MetadataAccessorService, UpdatesExplorerService } from './services'; +import { getBotToken } from './utils'; +import { Telegraf } from 'telegraf'; +import { defer } from 'rxjs'; +@Global() @Module({ imports: [DiscoveryModule], - providers: [TelegrafMetadataAccessor, TelegrafExplorer], + providers: [UpdatesExplorerService, MetadataAccessorService], }) -export class TelegrafCoreModule { +export class TelegrafCoreModule implements OnApplicationShutdown { + private readonly logger = new Logger(TelegrafCoreModule.name); + + constructor( + @Inject(TELEGRAF_BOT_NAME) private readonly botName: string, + private readonly moduleRef: ModuleRef, + ) { + this.logger.debug(botName); + } + public static forRoot(options: TelegrafModuleOptions): DynamicModule { - const telegrafProvider = { - provide: TELEGRAF_PROVIDER, - useClass: TelegrafProvider, - inject: [TELEGRAF_MODULE_OPTIONS], + const telegrafBotName = getBotToken(options.botName); + + const telegrafBotProvider = { + provide: telegrafBotName, + useFactory: async (): Promise => + await defer(async () => { + const bot = new Telegraf(options.token); + this.applyBotMiddlewares(bot, options.middlewares); + await bot.launch(options.launchOptions); + return bot; + }).toPromise(), }; + return { module: TelegrafCoreModule, providers: [ - { provide: TELEGRAF_MODULE_OPTIONS, useValue: options }, - telegrafProvider, + { + provide: TELEGRAF_MODULE_OPTIONS, + useValue: options, + }, + { + provide: TELEGRAF_BOT_NAME, + useValue: telegrafBotName, + }, + telegrafBotProvider, ], - exports: [telegrafProvider], + exports: [telegrafBotProvider], }; } public static forRootAsync( options: TelegrafModuleAsyncOptions, ): DynamicModule { - const telegrafProvider = { - provide: TELEGRAF_PROVIDER, - useClass: TelegrafProvider, + const telegrafBotName = getBotToken(options.botName); + + const telegrafBotProvider = { + provide: telegrafBotName, + useFactory: async ( + telegrafModuleOptions: TelegrafModuleOptions, + ): Promise => { + const { botName, ...telegrafOptions } = telegrafModuleOptions; + + return await defer(async () => { + const bot = new Telegraf(telegrafOptions.token); + this.applyBotMiddlewares(bot, telegrafOptions.middlewares); + await bot.launch(telegrafOptions.launchOptions); + return bot; + }).toPromise(); + }, inject: [TELEGRAF_MODULE_OPTIONS], }; + const asyncProviders = this.createAsyncProviders(options); return { module: TelegrafCoreModule, imports: options.imports, - providers: [...asyncProviders, telegrafProvider], - exports: [telegrafProvider], + providers: [ + ...asyncProviders, + { + provide: TELEGRAF_BOT_NAME, + useValue: telegrafBotName, + }, + telegrafBotProvider, + ], + exports: [telegrafBotProvider], }; } + private static applyBotMiddlewares(bot, middlewares) { + if (middlewares) { + middlewares.forEach((middleware) => { + bot.use(middleware); + }); + } + } + private static createAsyncProviders( options: TelegrafModuleAsyncOptions, ): Provider[] { @@ -88,4 +152,9 @@ export class TelegrafCoreModule { inject, }; } + + async onApplicationShutdown(): Promise { + const bot = this.moduleRef.get(this.botName); + bot && (await bot.stop()); + } } diff --git a/lib/telegraf.constants.ts b/lib/telegraf.constants.ts index 1affe84..54270ef 100644 --- a/lib/telegraf.constants.ts +++ b/lib/telegraf.constants.ts @@ -1,5 +1,6 @@ export const TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS'; -export const TELEGRAF_PROVIDER = 'TelegrafProvider'; +export const TELEGRAF_BOT_NAME = 'TELEGRAF_BOT_NAME'; +export const DEFAULT_BOT_NAME = 'DEFAULT_BOT_NAME'; export const DECORATORS_PREFIX = 'TELEGRAF'; export const DECORATORS = { diff --git a/lib/telegraf.provider.ts b/lib/telegraf.provider.ts deleted file mode 100644 index 58be5b1..0000000 --- a/lib/telegraf.provider.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - Injectable, - Inject, - OnApplicationBootstrap, - Logger, - OnApplicationShutdown, -} from '@nestjs/common'; -import { Telegraf } from 'telegraf'; -import { Context, TelegrafModuleOptions } from './interfaces'; -import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; - -@Injectable() -export class TelegrafProvider extends Telegraf - implements OnApplicationBootstrap, OnApplicationShutdown { - private logger = new Logger('Telegraf'); - private readonly launchOptions; - - constructor(@Inject(TELEGRAF_MODULE_OPTIONS) options: TelegrafModuleOptions) { - super(options.token, options.options); - this.launchOptions = options.launchOptions; - } - - async onApplicationBootstrap() { - this.catch((err, ctx: Context) => { - this.logger.error( - `Encountered an error for ${ctx.updateType} update type`, - err, - ); - }); - - await this.launch(this.launchOptions); - } - - async onApplicationShutdown() { - await this.stop(); - } -} diff --git a/lib/utils/get-bot-token.util.ts b/lib/utils/get-bot-token.util.ts new file mode 100644 index 0000000..a8458e8 --- /dev/null +++ b/lib/utils/get-bot-token.util.ts @@ -0,0 +1,7 @@ +import { DEFAULT_BOT_NAME } from '../telegraf.constants'; + +export function getBotToken(name?: string) { + return name && name !== DEFAULT_BOT_NAME + ? `${name}_BOT_NAME` + : DEFAULT_BOT_NAME; +} diff --git a/lib/utils/index.ts b/lib/utils/index.ts new file mode 100644 index 0000000..9dc7d1d --- /dev/null +++ b/lib/utils/index.ts @@ -0,0 +1 @@ +export * from './get-bot-token.util'; From 841ba6e6a07695b78f7cc2db0bf8f85acda1c692 Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 01:30:28 +0300 Subject: [PATCH 2/6] chore(deps): add missing dependencies --- package-lock.json | 16 +++++++++++++++- package.json | 2 ++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index aeba571..c7dca95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,7 @@ "requires": true, "packages": { "": { - "version": "1.3.0", + "version": "1.3.1", "license": "MIT", "dependencies": { "telegraf": "3.38.0" @@ -13,6 +13,7 @@ "devDependencies": { "@nestjs/common": "7.6.5", "@nestjs/core": "7.6.5", + "@types/lodash": "^4.14.167", "@typescript-eslint/eslint-plugin": "4.11.1", "@typescript-eslint/parser": "4.11.1", "eslint": "7.16.0", @@ -20,6 +21,7 @@ "eslint-plugin-import": "2.22.1", "husky": "4.3.6", "lint-staged": "10.5.3", + "lodash": "^4.17.20", "prettier": "2.2.1", "reflect-metadata": "0.1.13", "rxjs": "6.6.3", @@ -292,6 +294,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3880,6 +3888,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", diff --git a/package.json b/package.json index abaa52c..b783949 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "devDependencies": { "@nestjs/common": "7.6.5", "@nestjs/core": "7.6.5", + "@types/lodash": "^4.14.167", "@typescript-eslint/eslint-plugin": "4.11.1", "@typescript-eslint/parser": "4.11.1", "eslint": "7.16.0", @@ -44,6 +45,7 @@ "eslint-plugin-import": "2.22.1", "husky": "4.3.6", "lint-staged": "10.5.3", + "lodash": "^4.17.20", "prettier": "2.2.1", "reflect-metadata": "0.1.13", "rxjs": "6.6.3", From 3d7ecbd7f2e9c9faba607816a5ff1df6c3994111 Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 01:32:49 +0300 Subject: [PATCH 3/6] chore(): cleanup debug code --- lib/services/updates-explorer.service.ts | 5 +---- lib/telegraf-core.module.ts | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/services/updates-explorer.service.ts b/lib/services/updates-explorer.service.ts index 31d4325..f1d785c 100644 --- a/lib/services/updates-explorer.service.ts +++ b/lib/services/updates-explorer.service.ts @@ -1,4 +1,4 @@ -import { Inject, Injectable, Logger, OnModuleInit } from '@nestjs/common'; +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; import { DiscoveryService, ModuleRef, ModulesContainer } from '@nestjs/core'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; import { MetadataScanner } from '@nestjs/core/metadata-scanner'; @@ -29,8 +29,6 @@ import { Module } from '@nestjs/core/injector/module'; export class UpdatesExplorerService extends BaseExplorerService implements OnModuleInit { - private readonly logger = new Logger(UpdatesExplorerService.name); - constructor( @Inject(TELEGRAF_BOT_NAME) private readonly botName: string, @@ -48,7 +46,6 @@ export class UpdatesExplorerService private bot: Telegraf; onModuleInit(): void { - this.logger.debug(this.botName); this.bot = this.moduleRef.get>(this.botName, { strict: false, }); diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts index 7cf3703..25f0e8f 100644 --- a/lib/telegraf-core.module.ts +++ b/lib/telegraf-core.module.ts @@ -7,7 +7,6 @@ import { Global, Inject, OnApplicationShutdown, - Logger, } from '@nestjs/common'; import { TelegrafModuleOptions, @@ -29,14 +28,10 @@ import { defer } from 'rxjs'; providers: [UpdatesExplorerService, MetadataAccessorService], }) export class TelegrafCoreModule implements OnApplicationShutdown { - private readonly logger = new Logger(TelegrafCoreModule.name); - constructor( @Inject(TELEGRAF_BOT_NAME) private readonly botName: string, private readonly moduleRef: ModuleRef, - ) { - this.logger.debug(botName); - } + ) {} public static forRoot(options: TelegrafModuleOptions): DynamicModule { const telegrafBotName = getBotToken(options.botName); From 03b17a0cdb911b3f3e4ed0892e47e757f4b9ebff Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 02:00:10 +0300 Subject: [PATCH 4/6] feat(): disabling global catch support --- lib/index.ts | 4 ++- lib/interfaces/context.interface.ts | 2 +- lib/interfaces/telegraf-options.interface.ts | 4 ++- lib/telegraf-core.module.ts | 36 ++++++++++++++++++-- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/lib/index.ts b/lib/index.ts index 819279a..c4e9f7f 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -11,6 +11,8 @@ export * from './interfaces'; export * from './telegraf.module'; /** - * Backward compatibility with versions < 1.4.0 + * Backward compatibility with versions < 1.4.0, + * after removing TelegrafProvider service + * TODO: remove that on next major release */ export { Telegraf as TelegrafProvider } from 'telegraf'; diff --git a/lib/interfaces/context.interface.ts b/lib/interfaces/context.interface.ts index 418bbac..169e5fb 100644 --- a/lib/interfaces/context.interface.ts +++ b/lib/interfaces/context.interface.ts @@ -1,7 +1,7 @@ import { TelegrafContext } from 'telegraf/typings/context'; export interface Context extends TelegrafContext { - [key: string]: any; + [key: string]: any; // TBD } /** diff --git a/lib/interfaces/telegraf-options.interface.ts b/lib/interfaces/telegraf-options.interface.ts index 60e96f2..6de8013 100644 --- a/lib/interfaces/telegraf-options.interface.ts +++ b/lib/interfaces/telegraf-options.interface.ts @@ -5,6 +5,7 @@ import { LaunchWebhookOptions, } from 'telegraf/typings/telegraf'; import { Middleware } from 'telegraf/typings/composer'; +import { Context } from './context.interface'; export interface TelegrafModuleOptions { token: string; @@ -15,7 +16,8 @@ export interface TelegrafModuleOptions { }; botName?: string; include?: Function[]; - middlewares?: ReadonlyArray>; + middlewares?: ReadonlyArray>; + disableGlobalCatch?: boolean; } export interface TelegrafOptionsFactory { diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts index 25f0e8f..66566f2 100644 --- a/lib/telegraf-core.module.ts +++ b/lib/telegraf-core.module.ts @@ -7,11 +7,13 @@ import { Global, Inject, OnApplicationShutdown, + Logger, } from '@nestjs/common'; import { TelegrafModuleOptions, TelegrafModuleAsyncOptions, TelegrafOptionsFactory, + Context, } from './interfaces'; import { TELEGRAF_BOT_NAME, @@ -28,6 +30,8 @@ import { defer } from 'rxjs'; providers: [UpdatesExplorerService, MetadataAccessorService], }) export class TelegrafCoreModule implements OnApplicationShutdown { + private static logger = new Logger(TelegrafCoreModule.name); + constructor( @Inject(TELEGRAF_BOT_NAME) private readonly botName: string, private readonly moduleRef: ModuleRef, @@ -40,8 +44,22 @@ export class TelegrafCoreModule implements OnApplicationShutdown { provide: telegrafBotName, useFactory: async (): Promise => await defer(async () => { - const bot = new Telegraf(options.token); + const bot = new Telegraf(options.token); this.applyBotMiddlewares(bot, options.middlewares); + + /** + * Backward compatibility with versions < 1.4.0, + * TODO: remove that on next major release, + * after exception filters has been added + */ + if (!options.disableGlobalCatch) { + bot.catch((err, ctx: Context) => { + this.logger.error( + `Encountered an error for ${ctx.updateType} update type`, + err, + ); + }); + } await bot.launch(options.launchOptions); return bot; }).toPromise(), @@ -77,8 +95,22 @@ export class TelegrafCoreModule implements OnApplicationShutdown { const { botName, ...telegrafOptions } = telegrafModuleOptions; return await defer(async () => { - const bot = new Telegraf(telegrafOptions.token); + const bot = new Telegraf(telegrafOptions.token); this.applyBotMiddlewares(bot, telegrafOptions.middlewares); + + /** + * Backward compatibility with versions < 1.4.0, + * TODO: remove that on next major release, + * after exception filters has been added + */ + if (!telegrafOptions.disableGlobalCatch) { + bot.catch((err, ctx: Context) => { + this.logger.error( + `Encountered an error for ${ctx.updateType} update type`, + err, + ); + }); + } await bot.launch(telegrafOptions.launchOptions); return bot; }).toPromise(); From a6ddfb7d0268b866b9350b69d1c2b740d259fc87 Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 03:10:25 +0300 Subject: [PATCH 5/6] docs(): update docs --- lib/index.ts | 2 + website/docs/bot-injection.md | 4 +- website/docs/error-handling.md | 31 ++++++++++++++ website/docs/getting-updates.md | 13 +++--- website/docs/middlewares.md | 13 ++++++ website/docs/multiple-bots.md | 74 +++++++++++++++++++++++++++++++++ website/sidebars.js | 3 ++ 7 files changed, 133 insertions(+), 7 deletions(-) create mode 100644 website/docs/error-handling.md create mode 100644 website/docs/middlewares.md create mode 100644 website/docs/multiple-bots.md diff --git a/lib/index.ts b/lib/index.ts index c4e9f7f..fb91cba 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -8,7 +8,9 @@ export * as Extra from 'telegraf/extra'; export * from './decorators'; export * from './interfaces'; +export * from './utils'; export * from './telegraf.module'; +export { Telegraf } from 'telegraf'; /** * Backward compatibility with versions < 1.4.0, diff --git a/website/docs/bot-injection.md b/website/docs/bot-injection.md index 8aa67df..316b1e9 100644 --- a/website/docs/bot-injection.md +++ b/website/docs/bot-injection.md @@ -8,10 +8,10 @@ slug: /bot-injection At times you may need to access the native `Telegraf` instance. For example, you may want to connect stage middleware. You can inject the Telegraf by using the `@InjectBot()` decorator as follows: ```typescript import { Injectable } from '@nestjs/common'; -import { InjectBot, TelegrafProvider } from 'nestjs-telegraf'; +import { InjectBot, TelegrafProvider, Context } from 'nestjs-telegraf'; @Injectable() export class BotSettingsService { - constructor(@InjectBot() private bot: TelegrafProvider) {} + constructor(@InjectBot() private bot: TelegrafProvider) {} } ``` diff --git a/website/docs/error-handling.md b/website/docs/error-handling.md new file mode 100644 index 0000000..a684e81 --- /dev/null +++ b/website/docs/error-handling.md @@ -0,0 +1,31 @@ +--- +id: error-handling +title: Error handling +sidebar_label: Error handling +slug: /error-handling +--- + +By default, `nestjs-telegraf` catches all errors using the `Logger` built into NestJS. + +Use can disable global errors catch with `disableGlobalCatch`: +```typescript +TelegrafModule.forRoot({ + disableGlobalCatch: true, +}), +``` + +After that you can override errors handling with bot instance `catch` function. + +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectBot, TelegrafProvider, Context } from 'nestjs-telegraf'; + +@Injectable() +export class BotSettingsService { + constructor(@InjectBot() private bot: TelegrafProvider) { + this.bot.catch((err, ctx) => { + console.log(`Ooops, encountered an error for ${ctx.updateType}`, err); + }); + } +} +``` diff --git a/website/docs/getting-updates.md b/website/docs/getting-updates.md index df6e31a..a42d3fe 100644 --- a/website/docs/getting-updates.md +++ b/website/docs/getting-updates.md @@ -11,23 +11,26 @@ By default, the bot receives updates using long-polling and requires no addition ## Webhooks -If you want to configure a telegram bot webhook, you need to get a middleware from `TelegrafProvider` for connect it in your `main.ts` file. +If you want to configure a telegram bot webhook, you need to get a middleware via `getBotToken` helper in your `main.ts` file. To access it, you must use the `app.get()` method, followed by the provider reference: ```typescript -const telegrafProvider = app.get('TelegrafProvider'); +import { getBotToken } from 'nestjs-telegraf'; + +// ... +const bot = app.get(getBotToken()); ``` Now you can connect middleware: ```typescript -app.use(telegrafProvider.webhookCallback('/secret-path')); +app.use(bot.webhookCallback('/secret-path')); ``` The last step is to specify launchOptions in `forRoot` method: ```typescript TelegrafModule.forRootAsync({ - imports: [ConfigModule.forFeature(telegrafModuleConfig)], - useFactory: async (configService: ConfigService) => ({ + imports: [ConfigModule], + useFactory: (configService: ConfigService) => ({ token: configService.get('TELEGRAM_BOT_TOKEN'), launchOptions: { webhook: { diff --git a/website/docs/middlewares.md b/website/docs/middlewares.md new file mode 100644 index 0000000..b16387a --- /dev/null +++ b/website/docs/middlewares.md @@ -0,0 +1,13 @@ +--- +id: middlewares +title: Middlewares +sidebar_label: Middlewares +slug: /middlewares +--- + +`nestjs-telegraf` has support of the Telegraf middleware packages. To use an existing middleware package, simply import it and add it to the middlewares array: +```typescript +TelegrafModule.forRoot({ + middlewares: [session()], +}), +``` diff --git a/website/docs/multiple-bots.md b/website/docs/multiple-bots.md new file mode 100644 index 0000000..7500a6f --- /dev/null +++ b/website/docs/multiple-bots.md @@ -0,0 +1,74 @@ +--- +id: multiple-bots +title: Multiple bots +sidebar_label: Multiple bots +slug: /multiple-bots +--- + +In some cases, you may need to run multiple bots at the same time. This can also be achieved with this module. To work with multiple bots, first create the bots. In this case, bot naming becomes mandatory. +```typescript +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { TelegrafModule } from 'nestjs-telegraf'; + +@Module({ + imports: [ + ConfigModule.forRoot(), + TelegrafModule.forRootAsync({ + imports: [ConfigModule], + botName: 'cat', + useFactory: (configService: ConfigService) => ({ + token: configService.get('CAT_BOT_TOKEN'), + }), + inject: [ConfigService], + }), + TelegrafModule.forRootAsync({ + imports: [ConfigModule.forFeature(telegrafModuleConfig)], + botName: 'dog', + useFactory: async (configService: ConfigService) => ({ + token: configService.get('DOG_BOT_TOKEN'), + }), + inject: [ConfigService], + }), + ], +}) +export class AppModule {} +``` + +:::caution +Please note that you shouldn't have multiple bots without a name, or with the same name, otherwise they will get overridden. +::: + +You can also inject the `Bot` for a given bot: +```typescript +import { Injectable } from '@nestjs/common'; +import { InjectBot, Telegraf, Context } from 'nestjs-telegraf'; + +@Injectable() +export class EchoService { + constructor(@InjectBot('cat') private catBot: Telegraf) {} +} +``` +To inject a given `Bot` to a custom provider (for example, factory provider), use the `getBotToken()` function passing the name of the bot as an argument. +```typescript +{ + provide: CatsService, + useFactory: (catBot: Telegraf) => { + return new CatsService(catBot); + }, + inject: [getBotToken('cat')], +} +``` +Another useful feature of the `nestjs-telegraf` module is the ability to choose which modules should handle updates for each launched bot. By default, module searches for handlers throughout the whole app. To limit this scan to only a subset of modules, use the include property. + +```typescript +TelegrafModule.forRootAsync({ + imports: [ConfigModule], + botName: 'cat', + useFactory: (configService: ConfigService) => ({ + token: configService.get('CAT_BOT_TOKEN'), + include: [CatsModule], + }), + inject: [ConfigService], +}), +``` diff --git a/website/sidebars.js b/website/sidebars.js index 9912645..ea88714 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -6,6 +6,9 @@ module.exports = { 'telegraf-methods', 'bot-injection', 'async-configuration', + 'multiple-bots', + 'middlewares', + 'error-handling', ], Extras: ['extras/standalone-applications'], 'API Reference': ['api-reference/decorators'], From 832ddaf15c70d56a6aec297a829d8d1ee0e206d8 Mon Sep 17 00:00:00 2001 From: Alexander Bukhalo Date: Sat, 2 Jan 2021 21:40:13 +0300 Subject: [PATCH 6/6] fix(): working after merge --- .npmignore | 1 + lib/decorators/core/inject-bot.decorator.ts | 5 +- lib/decorators/inject-bot.decorator.ts | 5 - lib/explorers/telegraf-scene.explorer.ts | 73 ------- lib/explorers/telegraf-update.explorer.ts | 63 ------ .../create-scene-listener-decorator.helper.ts | 2 +- ...create-update-listener-decorator.helper.ts | 2 +- lib/index.ts | 2 +- lib/interfaces/context.interface.ts | 11 - lib/interfaces/index.ts | 1 - lib/interfaces/telegraf-options.interface.ts | 7 +- lib/services/index.ts | 2 + lib/services/metadata-accessor.service.ts | 194 ++--------------- lib/services/scenes-explorer.service.ts | 98 +++++++++ lib/services/updates-explorer.service.ts | 197 +----------------- lib/telegraf-core.module.ts | 45 ++-- lib/telegraf.metadata-accessor.ts | 29 --- lib/telegraf.module.ts | 97 +-------- lib/telegraf.providers.ts | 25 --- lib/{telegraf.types.ts => types/index.ts} | 0 package-lock.json | 14 ++ 21 files changed, 167 insertions(+), 706 deletions(-) delete mode 100644 lib/decorators/inject-bot.decorator.ts delete mode 100644 lib/explorers/telegraf-scene.explorer.ts delete mode 100644 lib/explorers/telegraf-update.explorer.ts delete mode 100644 lib/interfaces/context.interface.ts create mode 100644 lib/services/scenes-explorer.service.ts delete mode 100644 lib/telegraf.metadata-accessor.ts delete mode 100644 lib/telegraf.providers.ts rename lib/{telegraf.types.ts => types/index.ts} (100%) diff --git a/.npmignore b/.npmignore index 267876c..130fdb1 100644 --- a/.npmignore +++ b/.npmignore @@ -1,5 +1,6 @@ # source lib +index.ts package-lock.json tsconfig.json .prettierrc diff --git a/lib/decorators/core/inject-bot.decorator.ts b/lib/decorators/core/inject-bot.decorator.ts index d010fbe..73299e8 100644 --- a/lib/decorators/core/inject-bot.decorator.ts +++ b/lib/decorators/core/inject-bot.decorator.ts @@ -1,4 +1,5 @@ import { Inject } from '@nestjs/common'; -import { Telegraf } from 'telegraf'; +import { getBotToken } from '../../utils'; -export const InjectBot = (): ParameterDecorator => Inject(Telegraf); +export const InjectBot = (name?: string): ParameterDecorator => + Inject(getBotToken(name)); diff --git a/lib/decorators/inject-bot.decorator.ts b/lib/decorators/inject-bot.decorator.ts deleted file mode 100644 index 47808e3..0000000 --- a/lib/decorators/inject-bot.decorator.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { Inject } from '@nestjs/common'; -import { getBotToken } from '../utils'; - -export const InjectBot = (name?: string): ParameterDecorator => - Inject(getBotToken(name)); diff --git a/lib/explorers/telegraf-scene.explorer.ts b/lib/explorers/telegraf-scene.explorer.ts deleted file mode 100644 index 2ab2c49..0000000 --- a/lib/explorers/telegraf-scene.explorer.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { DiscoveryService } from '@nestjs/core'; -import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; -import { BaseScene as Scene, Stage, Telegraf } from 'telegraf'; -import { TelegrafMetadataAccessor } from '../telegraf.metadata-accessor'; - -@Injectable() -export class TelegrafSceneExplorer implements OnModuleInit { - private readonly stage = new Stage([]); - - constructor( - @Inject(Telegraf) - private readonly telegraf: Telegraf, - private readonly discoveryService: DiscoveryService, - private readonly metadataAccessor: TelegrafMetadataAccessor, - private readonly metadataScanner: MetadataScanner, - ) { - this.telegraf.use(this.stage.middleware()); - } - - onModuleInit(): void { - this.explore(); - } - - private explore(): void { - const sceneClasses = this.filterSceneClasses(); - - sceneClasses.forEach((wrapper) => { - const { instance } = wrapper; - - const sceneId = this.metadataAccessor.getSceneMetadata( - instance.constructor, - ); - const scene = new Scene(sceneId); - this.stage.register(scene); - - const prototype = Object.getPrototypeOf(instance); - this.metadataScanner.scanFromPrototype( - instance, - prototype, - (methodKey: string) => - this.registerIfListener(scene, instance, methodKey), - ); - }); - } - - private filterSceneClasses(): InstanceWrapper[] { - return this.discoveryService - .getProviders() - .filter((wrapper) => wrapper.instance) - .filter((wrapper) => - this.metadataAccessor.isScene(wrapper.instance.constructor), - ); - } - - private registerIfListener( - scene: Scene, - instance: Record, - methodKey: string, - ): void { - const methodRef = instance[methodKey]; - const middlewareFn = methodRef.bind(instance); - - const listenerMetadata = this.metadataAccessor.getListenerMetadata( - methodRef, - ); - if (!listenerMetadata) return; - - const { method, args } = listenerMetadata; - (scene[method] as any)(...args, middlewareFn); - } -} diff --git a/lib/explorers/telegraf-update.explorer.ts b/lib/explorers/telegraf-update.explorer.ts deleted file mode 100644 index a848806..0000000 --- a/lib/explorers/telegraf-update.explorer.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; -import { DiscoveryService } from '@nestjs/core'; -import { MetadataScanner } from '@nestjs/core/metadata-scanner'; -import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; -import { Telegraf } from 'telegraf'; -import { TelegrafMetadataAccessor } from '../telegraf.metadata-accessor'; - -@Injectable() -export class TelegrafUpdateExplorer implements OnModuleInit { - constructor( - @Inject(Telegraf) - private readonly telegraf: Telegraf, - private readonly discoveryService: DiscoveryService, - private readonly metadataAccessor: TelegrafMetadataAccessor, - private readonly metadataScanner: MetadataScanner, - ) {} - - onModuleInit(): void { - this.explore(); - } - - private explore(): void { - const updateClasses = this.filterUpdateClasses(); - - updateClasses.forEach((wrapper) => { - const { instance } = wrapper; - - const prototype = Object.getPrototypeOf(instance); - this.metadataScanner.scanFromPrototype( - instance, - prototype, - (methodKey: string) => this.registerIfListener(instance, methodKey), - ); - }); - } - - private filterUpdateClasses(): InstanceWrapper[] { - return this.discoveryService - .getProviders() - .filter((wrapper) => wrapper.instance) - .filter((wrapper) => - this.metadataAccessor.isUpdate(wrapper.instance.constructor), - ); - } - - private registerIfListener( - instance: Record, - methodKey: string, - ): void { - const methodRef = instance[methodKey]; - const middlewareFn = methodRef.bind(instance); - - const listenerMetadata = this.metadataAccessor.getListenerMetadata( - methodRef, - ); - if (!listenerMetadata) return; - - const { method, args } = listenerMetadata; - // NOTE: Use "any" to disable "Expected at least 1 arguments, but got 1 or more." error. - // Use telegraf instance for non-scene listeners - (this.telegraf[method] as any)(...args, middlewareFn); - } -} diff --git a/lib/helpers/create-scene-listener-decorator.helper.ts b/lib/helpers/create-scene-listener-decorator.helper.ts index c997ffc..b089ebf 100644 --- a/lib/helpers/create-scene-listener-decorator.helper.ts +++ b/lib/helpers/create-scene-listener-decorator.helper.ts @@ -1,6 +1,6 @@ import { SetMetadata } from '@nestjs/common'; import { BaseScene as Scene } from 'telegraf'; -import { ComposerMethodArgs, SceneMethods } from '../telegraf.types'; +import { ComposerMethodArgs, SceneMethods } from '../types'; import { UPDATE_LISTENER_METADATA } from '../telegraf.constants'; import { ListenerMetadata } from '../interfaces'; diff --git a/lib/helpers/create-update-listener-decorator.helper.ts b/lib/helpers/create-update-listener-decorator.helper.ts index a27f85b..28343e2 100644 --- a/lib/helpers/create-update-listener-decorator.helper.ts +++ b/lib/helpers/create-update-listener-decorator.helper.ts @@ -1,6 +1,6 @@ import { SetMetadata } from '@nestjs/common'; import { Composer } from 'telegraf'; -import { ComposerMethodArgs, UpdateMethods } from '../telegraf.types'; +import { ComposerMethodArgs, UpdateMethods } from '../types'; import { UPDATE_LISTENER_METADATA } from '../telegraf.constants'; import { ListenerMetadata } from '../interfaces'; diff --git a/lib/index.ts b/lib/index.ts index e45daa6..ace41d2 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -11,5 +11,5 @@ export * from './interfaces'; export * from './helpers'; export * from './utils'; export * from './telegraf.module'; -export * from './telegraf.types'; +export * from './types'; export { Telegraf } from 'telegraf'; diff --git a/lib/interfaces/context.interface.ts b/lib/interfaces/context.interface.ts deleted file mode 100644 index 169e5fb..0000000 --- a/lib/interfaces/context.interface.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { TelegrafContext } from 'telegraf/typings/context'; - -export interface Context extends TelegrafContext { - [key: string]: any; // TBD -} - -/** - * Removed type from Telegraf v3.38.0, added for backward compatibility. - * TODO: remove on next major release - */ -export interface ContextMessageUpdate extends Context {} diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts index 5d0c244..6be0df9 100644 --- a/lib/interfaces/index.ts +++ b/lib/interfaces/index.ts @@ -1,4 +1,3 @@ -export * from './context.interface'; export * from './telegraf-options.interface'; export * from './listener-metadata.interface'; export * from './update-metadata.interface'; diff --git a/lib/interfaces/telegraf-options.interface.ts b/lib/interfaces/telegraf-options.interface.ts index 6ac30d1..cfdb594 100644 --- a/lib/interfaces/telegraf-options.interface.ts +++ b/lib/interfaces/telegraf-options.interface.ts @@ -4,10 +4,7 @@ import { TelegrafOptions, LaunchPollingOptions, LaunchWebhookOptions, - TelegrafOptions, } from 'telegraf/typings/telegraf'; -import { Middleware } from 'telegraf/typings/composer'; -import { Context } from './context.interface'; export interface TelegrafModuleOptions { token: string; @@ -18,9 +15,7 @@ export interface TelegrafModuleOptions { }; botName?: string; include?: Function[]; - middlewares?: ReadonlyArray>; - disableGlobalCatch?: boolean; - middlewares?: Middleware[]; + middlewares?: ReadonlyArray>; } export interface TelegrafOptionsFactory { diff --git a/lib/services/index.ts b/lib/services/index.ts index 9514e05..9859475 100644 --- a/lib/services/index.ts +++ b/lib/services/index.ts @@ -1,2 +1,4 @@ export * from './updates-explorer.service'; export * from './metadata-accessor.service'; +export * from './scenes-explorer.service'; +export * from './updates-explorer.service'; diff --git a/lib/services/metadata-accessor.service.ts b/lib/services/metadata-accessor.service.ts index a4c0ac7..3e7e044 100644 --- a/lib/services/metadata-accessor.service.ts +++ b/lib/services/metadata-accessor.service.ts @@ -1,204 +1,42 @@ -import { Injectable, Type } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { - ActionOptions, - CashtagOptions, - CommandOptions, - EntityOptions, - HashtagOptions, - HearsOptions, - InlineQueryOptions, - MentionOptions, - OnOptions, - PhoneOptions, - UpdateHookOptions, -} from '../decorators'; -import { DECORATORS } from '../telegraf.constants'; + SCENE_METADATA, + UPDATE_LISTENER_METADATA, + UPDATE_METADATA, +} from '../telegraf.constants'; +import { ListenerMetadata } from '../interfaces'; @Injectable() export class MetadataAccessorService { constructor(private readonly reflector: Reflector) {} - isUpdate(target: Type | Function): boolean { + isUpdate(target: Function): boolean { if (!target) { return false; } - return !!this.reflector.get(DECORATORS.UPDATE, target); + return !!this.reflector.get(UPDATE_METADATA, target); } - isUpdateHook(target: Type | Function): boolean { + isUpdateListener(target: Function) { if (!target) { return false; } - return !!this.reflector.get(DECORATORS.UPDATE_HOOK, target); + return !!this.reflector.get(UPDATE_LISTENER_METADATA, target); } - getUpdateHookMetadata( - target: Type | Function, - ): UpdateHookOptions | undefined { - return this.reflector.get(DECORATORS.UPDATE_HOOK, target); - } - - isTelegrafUse(target: Type | Function): boolean { + isScene(target: Function): boolean { if (!target) { return false; } - return !!this.reflector.get(DECORATORS.USE, target); + return !!this.reflector.get(SCENE_METADATA, target); } - isTelegrafOn(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ON, target); + getListenerMetadata(target: Function): ListenerMetadata | undefined { + return this.reflector.get(UPDATE_LISTENER_METADATA, target); } - getTelegrafOnMetadata(target: Type | Function): OnOptions | undefined { - return this.reflector.get(DECORATORS.ON, target); - } - - isTelegrafHears(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HEARS, target); - } - - getTelegrafHearsMetadata( - target: Type | Function, - ): HearsOptions | undefined { - return this.reflector.get(DECORATORS.HEARS, target); - } - - isTelegrafCommand(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.COMMAND, target); - } - - getTelegrafCommandMetadata( - target: Type | Function, - ): CommandOptions | undefined { - return this.reflector.get(DECORATORS.COMMAND, target); - } - - isTelegrafStart(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.START, target); - } - - isTelegrafHelp(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HELP, target); - } - - isTelegrafSettings(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.SETTINGS, target); - } - - isTelegrafEntity(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ENTITY, target); - } - - getTelegrafEntityMetadata( - target: Type | Function, - ): EntityOptions | undefined { - return this.reflector.get(DECORATORS.ENTITY, target); - } - - isTelegrafMention(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.MENTION, target); - } - - getTelegrafMentionMetadata( - target: Type | Function, - ): MentionOptions | undefined { - return this.reflector.get(DECORATORS.MENTION, target); - } - - isTelegrafPhone(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.PHONE, target); - } - - getTelegrafPhoneMetadata( - target: Type | Function, - ): PhoneOptions | undefined { - return this.reflector.get(DECORATORS.PHONE, target); - } - - isTelegrafHashtag(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.HASHTAG, target); - } - - getTelegrafHashtagMetadata( - target: Type | Function, - ): HashtagOptions | undefined { - return this.reflector.get(DECORATORS.HASHTAG, target); - } - - isTelegrafCashtag(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.CASHTAG, target); - } - - getTelegrafCashtagMetadata( - target: Type | Function, - ): CashtagOptions | undefined { - return this.reflector.get(DECORATORS.CASHTAG, target); - } - - isTelegrafAction(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.ACTION, target); - } - - getTelegrafActionMetadata( - target: Type | Function, - ): ActionOptions | undefined { - return this.reflector.get(DECORATORS.ACTION, target); - } - - isTelegrafInlineQuery(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.INLINE_QUERY, target); - } - - getTelegrafInlineQueryMetadata( - target: Type | Function, - ): InlineQueryOptions | undefined { - return this.reflector.get(DECORATORS.INLINE_QUERY, target); - } - - isTelegrafGameQuery(target: Type | Function): boolean { - if (!target) { - return false; - } - return !!this.reflector.get(DECORATORS.GAME_QUERY, target); + getSceneMetadata(target: Function): string | undefined { + return this.reflector.get(SCENE_METADATA, target); } } diff --git a/lib/services/scenes-explorer.service.ts b/lib/services/scenes-explorer.service.ts new file mode 100644 index 0000000..be3d47d --- /dev/null +++ b/lib/services/scenes-explorer.service.ts @@ -0,0 +1,98 @@ +import { Inject, Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService, ModuleRef, ModulesContainer } from '@nestjs/core'; +import { MetadataScanner } from '@nestjs/core/metadata-scanner'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { BaseScene as Scene, Stage, Telegraf } from 'telegraf'; +import { MetadataAccessorService } from './metadata-accessor.service'; +import { BaseExplorerService } from './base-explorer.service'; +import { + TELEGRAF_BOT_NAME, + TELEGRAF_MODULE_OPTIONS, +} from '../telegraf.constants'; +import { TelegrafModuleOptions } from '../interfaces'; +import { Module } from '@nestjs/core/injector/module'; + +@Injectable() +export class ScenesExplorerService + extends BaseExplorerService + implements OnModuleInit { + private readonly stage = new Stage([]); + + constructor( + @Inject(TELEGRAF_BOT_NAME) + private readonly botName: string, + @Inject(TELEGRAF_MODULE_OPTIONS) + private readonly telegrafModuleOptions: TelegrafModuleOptions, + private readonly moduleRef: ModuleRef, + private readonly discoveryService: DiscoveryService, + private readonly metadataAccessor: MetadataAccessorService, + private readonly metadataScanner: MetadataScanner, + private readonly modulesContainer: ModulesContainer, + ) { + super(); + } + + private bot: Telegraf; + + onModuleInit(): void { + this.bot = this.moduleRef.get>(this.botName, { + strict: false, + }); + this.bot.use(this.stage.middleware()); + this.explore(); + } + + private explore(): void { + const modules = this.getModules( + this.modulesContainer, + this.telegrafModuleOptions.include || [], + ); + const scenes = this.flatMap(modules, (instance, moduleRef) => + this.applyScenes(instance, moduleRef), + ); + } + + private applyScenes(wrapper: InstanceWrapper, moduleRef: Module) { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + const prototype = Object.getPrototypeOf(instance); + + const providers: InstanceWrapper[] = this.discoveryService.getProviders(); + const sceneProviders: InstanceWrapper[] = providers.filter( + (wrapper: InstanceWrapper) => + this.metadataAccessor.isScene(wrapper.metatype), + ); + + sceneProviders.forEach((wrapper) => { + const { instance } = wrapper; + if (!instance) { + return undefined; + } + + const sceneId = this.metadataAccessor.getSceneMetadata( + instance.constructor, + ); + + const scene = new Scene(sceneId); + this.stage.register(scene); + + this.metadataScanner.scanFromPrototype( + instance, + prototype, + (methodKey: string) => { + const methodRef = instance[methodKey]; + if (this.metadataAccessor.isUpdateListener(methodRef)) { + const metadata = this.metadataAccessor.getListenerMetadata( + methodRef, + ); + const middlewareFn = methodRef.bind(instance); + const { method, args } = metadata; + (scene[method] as any)(...args, middlewareFn); + } + }, + ); + }); + } +} diff --git a/lib/services/updates-explorer.service.ts b/lib/services/updates-explorer.service.ts index f1d785c..09fa250 100644 --- a/lib/services/updates-explorer.service.ts +++ b/lib/services/updates-explorer.service.ts @@ -7,19 +7,6 @@ import { TELEGRAF_BOT_NAME, TELEGRAF_MODULE_OPTIONS, } from '../telegraf.constants'; -import { - ActionOptions, - CashtagOptions, - CommandOptions, - EntityOptions, - HashtagOptions, - HearsOptions, - InlineQueryOptions, - MentionOptions, - OnOptions, - PhoneOptions, - UpdateHookOptions, -} from '../decorators'; import { Telegraf } from 'telegraf'; import { TelegrafModuleOptions } from '../interfaces'; import { BaseExplorerService } from './base-explorer.service'; @@ -81,184 +68,16 @@ export class UpdatesExplorerService return undefined; } this.metadataScanner.scanFromPrototype(instance, prototype, (name) => { - if (this.metadataAccessor.isUpdateHook(instance[name])) { - const metadata = this.metadataAccessor.getUpdateHookMetadata( - instance[name], - ); - this.handleUpdateHook(instance, name, metadata); + const methodRef = instance[name]; + if (this.metadataAccessor.isUpdateListener(methodRef)) { + const metadata = this.metadataAccessor.getListenerMetadata(methodRef); + const middlewareFn = methodRef.bind(instance); + const { method, args } = metadata; + // NOTE: Use "any" to disable "Expected at least 1 arguments, but got 1 or more." error. + // Use telegraf instance for non-scene listeners + (this.bot[method] as any)(...args, middlewareFn); } }); }); - - providers.forEach((wrapper: InstanceWrapper) => { - const { instance } = wrapper; - if (!instance) { - return undefined; - } - this.metadataScanner.scanFromPrototype( - instance, - prototype, - (key: string) => { - if (this.metadataAccessor.isTelegrafUse(instance[key])) { - this.handleTelegrafUse(instance, key); - } else if (this.metadataAccessor.isTelegrafOn(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafOnMetadata( - instance[key], - ); - this.handleTelegrafOn(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafHears(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafHearsMetadata( - instance[key], - ); - this.handleTelegrafHears(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafCommand(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafCommandMetadata( - instance[key], - ); - this.handleTelegrafCommand(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafStart(instance[key])) { - this.handleTelegrafStart(instance, key); - } else if (this.metadataAccessor.isTelegrafHelp(instance[key])) { - this.handleTelegrafHelp(instance, key); - } else if (this.metadataAccessor.isTelegrafSettings(instance[key])) { - this.handleTelegrafSettings(instance, key); - } else if (this.metadataAccessor.isTelegrafEntity(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafEntityMetadata( - instance[key], - ); - this.handleTelegrafEntity(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafMention(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafMentionMetadata( - instance[key], - ); - this.handleTelegrafMention(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafPhone(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafPhoneMetadata( - instance[key], - ); - this.handleTelegrafPhone(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafHashtag(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafHashtagMetadata( - instance[key], - ); - this.handleTelegrafHashtag(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafCashtag(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafCashtagMetadata( - instance[key], - ); - this.handleTelegrafCashtag(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafAction(instance[key])) { - const metadata = this.metadataAccessor.getTelegrafActionMetadata( - instance[key], - ); - this.handleTelegrafAction(instance, key, metadata); - } else if ( - this.metadataAccessor.isTelegrafInlineQuery(instance[key]) - ) { - const metadata = this.metadataAccessor.getTelegrafInlineQueryMetadata( - instance[key], - ); - this.handleTelegrafInlineQuery(instance, key, metadata); - } else if (this.metadataAccessor.isTelegrafGameQuery(instance[key])) { - this.handleTelegrafGameQuery(instance, key); - } - }, - ); - }); - } - - handleUpdateHook(instance: object, key: string, metadata: UpdateHookOptions) { - this.bot.on(metadata.updateType, instance[key].bind(instance)); - } - - handleTelegrafUse(instance: object, key: string) { - this.bot.use(instance[key].bind(instance)); - } - - handleTelegrafOn(instance: object, key: string, metadata: OnOptions) { - this.bot.on(metadata.updateTypes, instance[key].bind(instance)); - } - - handleTelegrafHears(instance: object, key: string, metadata: HearsOptions) { - this.bot.hears(metadata.triggers, instance[key].bind(instance)); - } - - handleTelegrafCommand( - instance: object, - key: string, - metadata: CommandOptions, - ) { - this.bot.command(metadata.commands, instance[key].bind(instance)); - } - - handleTelegrafStart(instance: object, key: string) { - this.bot.start(instance[key].bind(instance)); - } - - handleTelegrafHelp(instance: object, key: string) { - this.bot.help(instance[key].bind(instance)); - } - - handleTelegrafSettings(instance: object, key: string) { - // @ts-ignore - this.bot.settings(instance[key].bind(instance)); - } - - handleTelegrafEntity(instance: object, key: string, metadata: EntityOptions) { - // @ts-ignore - this.bot.entity(metadata.entity, instance[key].bind(instance)); - } - - handleTelegrafMention( - instance: object, - key: string, - metadata: MentionOptions, - ) { - // @ts-ignore - this.bot.mention(metadata.username, instance[key].bind(instance)); - } - - handleTelegrafPhone(instance: object, key: string, metadata: PhoneOptions) { - // @ts-ignore - this.bot.phone(metadata.phone, instance[key].bind(instance)); - } - - handleTelegrafHashtag( - instance: object, - key: string, - metadata: HashtagOptions, - ) { - // @ts-ignore - this.bot.hashtag(metadata.hashtag, instance[key].bind(instance)); - } - - handleTelegrafCashtag( - instance: object, - key: string, - metadata: CashtagOptions, - ) { - // @ts-ignore - this.bot.cashtag(metadata.cashtag, instance[key].bind(instance)); - } - - handleTelegrafAction(instance: object, key: string, metadata: ActionOptions) { - this.bot.action(metadata.triggers, instance[key].bind(instance)); - } - - handleTelegrafInlineQuery( - instance: object, - key: string, - metadata: InlineQueryOptions, - ) { - if (metadata.triggers) { - // @ts-ignore - this.bot.inlineQuery(metadata.triggers, instance[key].bind(instance)); - } else { - this.bot.on(metadata.updateType, instance[key].bind(instance)); - } - } - - handleTelegrafGameQuery(instance: object, key: string) { - this.bot.gameQuery(instance[key].bind(instance)); } } diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts index 66566f2..f33d5cc 100644 --- a/lib/telegraf-core.module.ts +++ b/lib/telegraf-core.module.ts @@ -13,13 +13,16 @@ import { TelegrafModuleOptions, TelegrafModuleAsyncOptions, TelegrafOptionsFactory, - Context, } from './interfaces'; import { TELEGRAF_BOT_NAME, TELEGRAF_MODULE_OPTIONS, } from './telegraf.constants'; -import { MetadataAccessorService, UpdatesExplorerService } from './services'; +import { + MetadataAccessorService, + ScenesExplorerService, + UpdatesExplorerService, +} from './services'; import { getBotToken } from './utils'; import { Telegraf } from 'telegraf'; import { defer } from 'rxjs'; @@ -27,7 +30,11 @@ import { defer } from 'rxjs'; @Global() @Module({ imports: [DiscoveryModule], - providers: [UpdatesExplorerService, MetadataAccessorService], + providers: [ + UpdatesExplorerService, + ScenesExplorerService, + MetadataAccessorService, + ], }) export class TelegrafCoreModule implements OnApplicationShutdown { private static logger = new Logger(TelegrafCoreModule.name); @@ -44,22 +51,8 @@ export class TelegrafCoreModule implements OnApplicationShutdown { provide: telegrafBotName, useFactory: async (): Promise => await defer(async () => { - const bot = new Telegraf(options.token); + const bot = new Telegraf(options.token); this.applyBotMiddlewares(bot, options.middlewares); - - /** - * Backward compatibility with versions < 1.4.0, - * TODO: remove that on next major release, - * after exception filters has been added - */ - if (!options.disableGlobalCatch) { - bot.catch((err, ctx: Context) => { - this.logger.error( - `Encountered an error for ${ctx.updateType} update type`, - err, - ); - }); - } await bot.launch(options.launchOptions); return bot; }).toPromise(), @@ -95,22 +88,8 @@ export class TelegrafCoreModule implements OnApplicationShutdown { const { botName, ...telegrafOptions } = telegrafModuleOptions; return await defer(async () => { - const bot = new Telegraf(telegrafOptions.token); + const bot = new Telegraf(telegrafOptions.token); this.applyBotMiddlewares(bot, telegrafOptions.middlewares); - - /** - * Backward compatibility with versions < 1.4.0, - * TODO: remove that on next major release, - * after exception filters has been added - */ - if (!telegrafOptions.disableGlobalCatch) { - bot.catch((err, ctx: Context) => { - this.logger.error( - `Encountered an error for ${ctx.updateType} update type`, - err, - ); - }); - } await bot.launch(telegrafOptions.launchOptions); return bot; }).toPromise(); diff --git a/lib/telegraf.metadata-accessor.ts b/lib/telegraf.metadata-accessor.ts deleted file mode 100644 index 5c4394d..0000000 --- a/lib/telegraf.metadata-accessor.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Reflector } from '@nestjs/core'; -import { - SCENE_METADATA, - UPDATE_LISTENER_METADATA, - UPDATE_METADATA, -} from './telegraf.constants'; -import { ListenerMetadata } from './interfaces'; - -@Injectable() -export class TelegrafMetadataAccessor { - constructor(private readonly reflector: Reflector) {} - - isUpdate(target: Function): boolean { - return !!this.reflector.get(UPDATE_METADATA, target); - } - - isScene(target: Function): boolean { - return !!this.reflector.get(SCENE_METADATA, target); - } - - getListenerMetadata(target: Function): ListenerMetadata | undefined { - return this.reflector.get(UPDATE_LISTENER_METADATA, target); - } - - getSceneMetadata(target: Function): string | undefined { - return this.reflector.get(SCENE_METADATA, target); - } -} diff --git a/lib/telegraf.module.ts b/lib/telegraf.module.ts index 4e9f958..fb3cdeb 100644 --- a/lib/telegraf.module.ts +++ b/lib/telegraf.module.ts @@ -1,106 +1,27 @@ -import { DiscoveryModule, ModuleRef } from '@nestjs/core'; +import { Module, DynamicModule } from '@nestjs/common'; +import { TelegrafCoreModule } from './telegraf-core.module'; import { - DynamicModule, - Inject, - Module, - OnApplicationBootstrap, - OnApplicationShutdown, - Provider, -} from '@nestjs/common'; -import { Telegraf } from 'telegraf'; -import { - TelegrafModuleAsyncOptions, TelegrafModuleOptions, - TelegrafOptionsFactory, + TelegrafModuleAsyncOptions, } from './interfaces'; -import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; -import { TelegrafMetadataAccessor } from './telegraf.metadata-accessor'; -import { TelegrafUpdateExplorer } from './explorers/telegraf-update.explorer'; -import { TelegrafSceneExplorer } from './explorers/telegraf-scene.explorer'; -import { createProviders, TelegrafProvider } from './telegraf.providers'; - -@Module({ - imports: [DiscoveryModule], - providers: [ - TelegrafMetadataAccessor, - TelegrafSceneExplorer, - TelegrafUpdateExplorer, - ], -}) -export class TelegrafModule - implements OnApplicationBootstrap, OnApplicationShutdown { - constructor( - @Inject(TELEGRAF_MODULE_OPTIONS) - private readonly options: TelegrafModuleOptions, - private readonly moduleRef: ModuleRef, - ) {} - - async onApplicationBootstrap(): Promise { - const { launchOptions } = this.options; - const telegraf = this.moduleRef.get(Telegraf); - await telegraf.launch(launchOptions); - } - - async onApplicationShutdown(): Promise { - const telegraf = this.moduleRef.get(Telegraf); - await telegraf.stop(); - } +@Module({}) +export class TelegrafModule { public static forRoot(options: TelegrafModuleOptions): DynamicModule { - const providers = [...createProviders(options), TelegrafProvider]; - return { module: TelegrafModule, - providers, - exports: providers, + imports: [TelegrafCoreModule.forRoot(options)], + exports: [TelegrafCoreModule], }; } public static forRootAsync( options: TelegrafModuleAsyncOptions, ): DynamicModule { - const providers = [...this.createAsyncProviders(options), TelegrafProvider]; - return { module: TelegrafModule, - imports: options.imports || [], - providers, - exports: providers, - }; - } - - private static createAsyncProviders( - options: TelegrafModuleAsyncOptions, - ): Provider[] { - if (options.useExisting || options.useFactory) { - return [this.createAsyncOptionsProvider(options)]; - } - - return [ - this.createAsyncOptionsProvider(options), - { - provide: options.useClass, - useClass: options.useClass, - }, - ]; - } - - private static createAsyncOptionsProvider( - options: TelegrafModuleAsyncOptions, - ): Provider { - if (options.useFactory) { - return { - provide: TELEGRAF_MODULE_OPTIONS, - useFactory: options.useFactory, - inject: options.inject || [], - }; - } - - return { - provide: TELEGRAF_MODULE_OPTIONS, - useFactory: async (optionsFactory: TelegrafOptionsFactory) => - await optionsFactory.createTelegrafOptions(), - inject: [options.useExisting || options.useClass], + imports: [TelegrafCoreModule.forRootAsync(options)], + exports: [TelegrafCoreModule], }; } } diff --git a/lib/telegraf.providers.ts b/lib/telegraf.providers.ts deleted file mode 100644 index b13de54..0000000 --- a/lib/telegraf.providers.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { Provider } from '@nestjs/common'; -import { Telegraf } from 'telegraf'; -import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; -import { TelegrafModuleOptions } from './interfaces'; - -export const TelegrafProvider = { - provide: Telegraf, - useFactory: (options: TelegrafModuleOptions) => { - const telegraf = new Telegraf(options.token, options.options); - if (options.middlewares?.length > 0) { - telegraf.use(...options.middlewares); - } - return telegraf; - }, - inject: [TELEGRAF_MODULE_OPTIONS], -}; - -export function createProviders(options: TelegrafModuleOptions): Provider[] { - return [ - { - provide: TELEGRAF_MODULE_OPTIONS, - useValue: options, - }, - ]; -} diff --git a/lib/telegraf.types.ts b/lib/types/index.ts similarity index 100% rename from lib/telegraf.types.ts rename to lib/types/index.ts diff --git a/package-lock.json b/package-lock.json index 7490ee2..acbfe97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "devDependencies": { "@nestjs/common": "7.6.5", "@nestjs/core": "7.6.5", + "@types/lodash": "^4.14.167", "@typescript-eslint/eslint-plugin": "4.11.1", "@typescript-eslint/parser": "4.11.1", "eslint": "7.17.0", @@ -20,6 +21,7 @@ "eslint-plugin-import": "2.22.1", "husky": "4.3.6", "lint-staged": "10.5.3", + "lodash": "^4.17.20", "prettier": "2.2.1", "reflect-metadata": "0.1.13", "rxjs": "6.6.3", @@ -292,6 +294,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "node_modules/@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "node_modules/@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", @@ -3880,6 +3888,12 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.167", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.167.tgz", + "integrity": "sha512-w7tQPjARrvdeBkX/Rwg95S592JwxqOjmms3zWQ0XZgSyxSLdzWaYH3vErBhdVS/lRBX7F8aBYcYJYTr5TMGOzw==", + "dev": true + }, "@types/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",