diff --git a/.prettierrc b/.prettierrc index e9c0f50..6e778b4 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,4 +1,4 @@ { - "trailingComma": "none", + "trailingComma": "all", "singleQuote": true } diff --git a/lib/decorators/index.ts b/lib/decorators/index.ts index 246b998..d001649 100644 --- a/lib/decorators/index.ts +++ b/lib/decorators/index.ts @@ -1,3 +1,3 @@ -export * from './pipe-context.decorator' -export * from './telegram-action-handler.decorator' -export * from './telegram-catch.decorator' +export * from './telegraf-on.decorator'; +export * from './telegraf-hears.decorator'; +export * from './telegraf-start.decorator'; diff --git a/lib/decorators/pipe-context.decorator.ts b/lib/decorators/pipe-context.decorator.ts deleted file mode 100644 index 01aeec0..0000000 --- a/lib/decorators/pipe-context.decorator.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Type } from '@nestjs/common' -import { ContextTransformer } from '../interfaces' -import { addHandlerToStore } from './' - -export const PipeContext = (transform: Type>) => ( - target: Object, - propertyKey: string, - parameterIndex: number, -) => { - addHandlerToStore(target, propertyKey, { - transformations: [ - { - index: parameterIndex, - transform, - }, - ], - }) -} diff --git a/lib/decorators/telegraf-hears.decorator.ts b/lib/decorators/telegraf-hears.decorator.ts new file mode 100644 index 0000000..b9c44fb --- /dev/null +++ b/lib/decorators/telegraf-hears.decorator.ts @@ -0,0 +1,13 @@ +import { SetMetadata } from '@nestjs/common'; +import { DECORATORS } from '../telegraf.constants'; +import { HearsTriggers } from 'telegraf'; + +/** + * Registers middleware for handling text messages. + * @param triggers Triggers + * + * https://telegraf.js.org/#/?id=hears + */ +export function TelegrafHears(triggers: HearsTriggers): MethodDecorator { + return SetMetadata(DECORATORS.HEARS, { triggers: triggers }); +} diff --git a/lib/decorators/telegraf-on.decorator.ts b/lib/decorators/telegraf-on.decorator.ts new file mode 100644 index 0000000..b6a2a60 --- /dev/null +++ b/lib/decorators/telegraf-on.decorator.ts @@ -0,0 +1,15 @@ +import { SetMetadata } from '@nestjs/common'; +import { DECORATORS } from '../telegraf.constants'; +import { UpdateType, MessageSubTypes } from 'telegraf/typings/telegram-types'; + +/** + * Registers middleware for provided update type. + * @param updateTypes Update type + * + * https://telegraf.js.org/#/?id=on + */ +export function TelegrafOn( + updateTypes: UpdateType | UpdateType[] | MessageSubTypes | MessageSubTypes[], +): MethodDecorator { + return SetMetadata(DECORATORS.ON, { updateTypes: updateTypes }); +} diff --git a/lib/decorators/telegraf-start.decorator.ts b/lib/decorators/telegraf-start.decorator.ts new file mode 100644 index 0000000..3e0c820 --- /dev/null +++ b/lib/decorators/telegraf-start.decorator.ts @@ -0,0 +1,11 @@ +import { SetMetadata } from '@nestjs/common'; +import { DECORATORS } from '../telegraf.constants'; + +/** + * Handler for /start command. + * + * https://telegraf.js.org/#/?id=start + */ +export function TelegrafStart(): MethodDecorator { + return SetMetadata(DECORATORS.START, {}); +} diff --git a/lib/decorators/telegram-action-handler.decorator.ts b/lib/decorators/telegram-action-handler.decorator.ts deleted file mode 100644 index b19a619..0000000 --- a/lib/decorators/telegram-action-handler.decorator.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { HandleParameters } from '../interfaces' - -type Decorator = ( - params: HandleParameters, -) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void - -type HandlerDecorator = Decorator & { - handlers?: Map> -} - -export const TelegramActionHandler: HandlerDecorator = ( - parameters: HandleParameters, -) => (target: any, propertyKey: string) => { - // eslint-disable-next-line no-use-before-define - addHandlerToStore(target, propertyKey, parameters) -} - -export const addHandlerToStore = ( - instance: Object, - name: string, - config: HandleParameters, -) => { - const handlerClass = instance.constructor - - if (!TelegramActionHandler.handlers) { - TelegramActionHandler.handlers = new Map() - } - - if (!TelegramActionHandler.handlers.get(handlerClass)) { - TelegramActionHandler.handlers.set(handlerClass, new Map()) - } - - const oldParameters = - TelegramActionHandler.handlers.get(handlerClass).get(name) || {} - - TelegramActionHandler.handlers.get(handlerClass).set(name, { - ...oldParameters, - ...config, - transformations: [ - ...(oldParameters.transformations || []), - ...(config.transformations || []), - ], - }) -} diff --git a/lib/decorators/telegram-catch.decorator.ts b/lib/decorators/telegram-catch.decorator.ts deleted file mode 100644 index f924ea9..0000000 --- a/lib/decorators/telegram-catch.decorator.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Type } from '@nestjs/common' -import { TelegramErrorHandler } from '../interfaces' - -type Decorator = (error: any) => ClassDecorator - -type HandlerDecorator = Decorator & { - handlers?: Map> -} - -export const TelegramCatch: HandlerDecorator = error => target => { - if (!TelegramCatch.handlers) { - TelegramCatch.handlers = new Map() - } - - TelegramCatch.handlers.set(error, target as any) - - return target -} diff --git a/lib/exeptions/index.ts b/lib/exeptions/index.ts deleted file mode 100644 index 40f3902..0000000 --- a/lib/exeptions/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './invalid-configuration.exeption' diff --git a/lib/exeptions/invalid-configuration.exeption.ts b/lib/exeptions/invalid-configuration.exeption.ts deleted file mode 100644 index deddd73..0000000 --- a/lib/exeptions/invalid-configuration.exeption.ts +++ /dev/null @@ -1,10 +0,0 @@ -export class InvalidConfigurationException extends Error { - public constructor( - public readonly invalidField, - public readonly invalidCause, - ) { - super( - `Options validation failed, "${invalidField}" invalid — ${invalidCause}`, - ) - } -} diff --git a/lib/index.ts b/lib/index.ts index d7b43b6..6ca2523 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -1,5 +1,3 @@ -export * from './telegraf.module' -export * from './interfaces' -export * from './decorators' -export * from './telegraf.service' -export * from './telegraf-telegram.service' +export * from './telegraf.module'; +export * from './interfaces'; +export * from './decorators'; diff --git a/lib/interfaces/context-transformer.interface.ts b/lib/interfaces/context-transformer.interface.ts deleted file mode 100644 index 61422ac..0000000 --- a/lib/interfaces/context-transformer.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContextMessageUpdate } from 'telegraf' - -export interface ContextTransformer { - transform: (ctx: ContextMessageUpdate) => Promise -} diff --git a/lib/interfaces/handle-parameters.interface.ts b/lib/interfaces/handle-parameters.interface.ts deleted file mode 100644 index 561953c..0000000 --- a/lib/interfaces/handle-parameters.interface.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { ContextTransformer } from './' -import { HearsTriggers } from 'telegraf' -import { UpdateType, MessageSubTypes } from 'telegraf/typings/telegram-types' -import { Type } from '@nestjs/common' - -interface ArgumentTransformation { - index: number - transform: Type -} - -export interface HandleParameters { - onStart?: boolean - on?: UpdateType | UpdateType[] | MessageSubTypes | MessageSubTypes[] - command?: string - message?: string | RegExp - action?: HearsTriggers - transformations?: ArgumentTransformation[] -} diff --git a/lib/interfaces/handler.interface.ts b/lib/interfaces/handler.interface.ts deleted file mode 100644 index ac61f9b..0000000 --- a/lib/interfaces/handler.interface.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { HandleParameters } from './' - -export interface Handler { - handle: (...args: any[]) => Promise - config: HandleParameters -} diff --git a/lib/interfaces/index.ts b/lib/interfaces/index.ts index 4f31766..23e7d9f 100644 --- a/lib/interfaces/index.ts +++ b/lib/interfaces/index.ts @@ -1,5 +1 @@ -export * from './telegraf-options.interface' -export * from './handler.interface' -export * from './handle-parameters.interface' -export * from './telegram-error-handler.interface' -export * from './context-transformer.interface' +export * from './telegraf-options.interface'; diff --git a/lib/interfaces/telegraf-options.interface.ts b/lib/interfaces/telegraf-options.interface.ts index 64b70a6..e3b1969 100644 --- a/lib/interfaces/telegraf-options.interface.ts +++ b/lib/interfaces/telegraf-options.interface.ts @@ -1,22 +1,21 @@ -import { ModuleMetadata, Type } from '@nestjs/common/interfaces' -import { TelegrafOptions } from 'telegraf' +import { ModuleMetadata, Type } from '@nestjs/common/interfaces'; +import { TelegrafOptions } from 'telegraf'; export interface TelegrafModuleOptions { - token: string - sitePublicUrl?: string - telegrafOptions?: TelegrafOptions + token: string; + options?: TelegrafOptions; } export interface TelegrafOptionsFactory { - createTelegrafOptions(): TelegrafModuleOptions + createTelegrafOptions(): TelegrafModuleOptions; } export interface TelegrafModuleAsyncOptions extends Pick { - useExisting?: Type - useClass?: Type + useExisting?: Type; + useClass?: Type; useFactory?: ( ...args: any[] - ) => Promise | TelegrafModuleOptions - inject?: any[] + ) => Promise | TelegrafModuleOptions; + inject?: any[]; } diff --git a/lib/interfaces/telegram-error-handler.interface.ts b/lib/interfaces/telegram-error-handler.interface.ts deleted file mode 100644 index 893f309..0000000 --- a/lib/interfaces/telegram-error-handler.interface.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ContextMessageUpdate } from 'telegraf' - -export interface TelegramErrorHandler { - catch(ctx: ContextMessageUpdate, error: E): Promise -} diff --git a/lib/telegraf-core.module.ts b/lib/telegraf-core.module.ts new file mode 100644 index 0000000..0e45913 --- /dev/null +++ b/lib/telegraf-core.module.ts @@ -0,0 +1,83 @@ +import { Module, DynamicModule, Provider, Type } from '@nestjs/common'; +import { + TelegrafModuleOptions, + TelegrafModuleAsyncOptions, + TelegrafOptionsFactory, +} from './interfaces'; +import { + TELEGRAF_MODULE_OPTIONS, + TELEGRAF_PROVIDER, +} from './telegraf.constants'; +import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; +import { TelegrafExplorer } from './telegraf.explorer'; +import { DiscoveryModule } from '@nestjs/core'; +import { TelegrafProvider } from './telegraf.provider'; + +@Module({ + imports: [DiscoveryModule], + providers: [TelegrafMetadataAccessor, TelegrafExplorer], +}) +export class TelegrafCoreModule { + public static forRoot(options: TelegrafModuleOptions): DynamicModule { + return { + module: TelegrafCoreModule, + providers: [], + exports: [], + }; + } + + public static forRootAsync( + options: TelegrafModuleAsyncOptions, + ): DynamicModule { + const telegrafProvider = { + provide: TELEGRAF_PROVIDER, + useClass: TelegrafProvider, + inject: [TELEGRAF_MODULE_OPTIONS], + }; + const asyncProviders = this.createAsyncProviders(options); + return { + module: TelegrafCoreModule, + imports: options.imports, + providers: [...asyncProviders, telegrafProvider], + exports: [telegrafProvider], + }; + } + + private static createAsyncProviders( + options: TelegrafModuleAsyncOptions, + ): Provider[] { + if (options.useExisting || options.useFactory) { + return [this.createAsyncOptionsProvider(options)]; + } + const useClass = options.useClass as Type; + return [ + this.createAsyncOptionsProvider(options), + { + provide: useClass, + useClass, + }, + ]; + } + + private static createAsyncOptionsProvider( + options: TelegrafModuleAsyncOptions, + ): Provider { + if (options.useFactory) { + return { + provide: TELEGRAF_MODULE_OPTIONS, + useFactory: options.useFactory, + inject: options.inject || [], + }; + } + // `as Type` is a workaround for microsoft/TypeScript#31603 + const inject = [ + (options.useClass || options.useExisting) as Type, + ]; + return { + provide: TELEGRAF_MODULE_OPTIONS, + useFactory: async (optionsFactory: TelegrafOptionsFactory) => + await optionsFactory.createTelegrafOptions(), + inject, + }; + } +} diff --git a/lib/telegraf-metadata.accessor.ts b/lib/telegraf-metadata.accessor.ts new file mode 100644 index 0000000..d4bc776 --- /dev/null +++ b/lib/telegraf-metadata.accessor.ts @@ -0,0 +1,38 @@ +import { Injectable, Type } from '@nestjs/common'; +import { Reflector } from '@nestjs/core'; +import { TelegrafStart } from './decorators'; +import { DECORATORS } from './telegraf.constants'; + +@Injectable() +export class TelegrafMetadataAccessor { + constructor(private readonly reflector: Reflector) {} + + isTelegrafStart(target: Type | Function): boolean { + if (!target) { + return false; + } + return !!this.reflector.get(DECORATORS.START, target); + } + + isTelegrafOn(target: Type | Function): boolean { + if (!target) { + return false; + } + return !!this.reflector.get(DECORATORS.ON, target); + } + + getTelegrafOnMetadata(target: Type | Function) { + 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) { + return this.reflector.get(DECORATORS.HEARS, target); + } +} diff --git a/lib/telegraf-telegram.service.ts b/lib/telegraf-telegram.service.ts deleted file mode 100644 index 8b262b7..0000000 --- a/lib/telegraf-telegram.service.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Injectable, Inject } from '@nestjs/common'; -const Telegram = require('telegraf/telegram'); -import { Telegram as TelegramClient } from 'telegraf'; - -import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; -import { TelegrafModuleOptions } from './interfaces'; - -@Injectable() -export class TelegrafTelegramService extends TelegramClient { - constructor(@Inject(TELEGRAF_MODULE_OPTIONS) options: TelegrafModuleOptions) { - super(options.token, {}); - } -} diff --git a/lib/telegraf.constants.ts b/lib/telegraf.constants.ts index b62570c..edfedfd 100644 --- a/lib/telegraf.constants.ts +++ b/lib/telegraf.constants.ts @@ -1,2 +1,9 @@ -export const TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS' -export const TokenInjectionToken = Symbol('TokenInjectionToken') +export const TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS'; +export const TELEGRAF_PROVIDER = 'TELEGRAF_PROVIDER'; + +export const DECORATORS_PREFIX = 'TELEGRAF'; +export const DECORATORS = { + ON: `${DECORATORS_PREFIX}/ON`, + HEARS: `${DECORATORS_PREFIX}/HEARS`, + START: `${DECORATORS_PREFIX}/START`, +}; diff --git a/lib/telegraf.explorer.ts b/lib/telegraf.explorer.ts new file mode 100644 index 0000000..ebe0405 --- /dev/null +++ b/lib/telegraf.explorer.ts @@ -0,0 +1,85 @@ +import { Injectable, OnModuleInit } from '@nestjs/common'; +import { DiscoveryService, ModuleRef } from '@nestjs/core'; +import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { MetadataScanner } from '@nestjs/core/metadata-scanner'; +import Telegraf from 'telegraf'; +import { TelegrafMetadataAccessor } from './telegraf-metadata.accessor'; +import { TelegrafProvider } from './telegraf.provider'; +import { TELEGRAF_PROVIDER } from './telegraf.constants'; +import { ContextMessageUpdate } from 'telegraf'; + +@Injectable() +export class TelegrafExplorer implements OnModuleInit { + constructor( + private readonly moduleRef: ModuleRef, + private readonly discoveryService: DiscoveryService, + private readonly metadataAccessor: TelegrafMetadataAccessor, + private readonly metadataScanner: MetadataScanner, + ) {} + + onModuleInit() { + this.explore(); + } + + explore() { + const providers: InstanceWrapper[] = this.discoveryService.getProviders(); + providers.forEach((wrapper: InstanceWrapper) => { + const { instance } = wrapper; + + if (!instance) { + return; + } + + const telegraf = this.moduleRef.get>( + TELEGRAF_PROVIDER, + { strict: false }, + ); + + this.metadataScanner.scanFromPrototype( + instance, + Object.getPrototypeOf(instance), + (key: string) => { + if (this.metadataAccessor.isTelegrafStart(instance[key])) { + this.handleTelegrafStart(instance, key, telegraf); + } else if (this.metadataAccessor.isTelegrafOn(instance[key])) { + const metadata = this.metadataAccessor.getTelegrafOnMetadata( + instance[key], + ); + this.handleTelegrafOn(instance, key, telegraf, metadata); + } else if (this.metadataAccessor.isTelegrafHears(instance[key])) { + const metadata = this.metadataAccessor.getTelegrafHearsMetadata( + instance[key], + ); + this.handleTelegrafHears(instance, key, telegraf, metadata); + } + }, + ); + }); + } + + handleTelegrafOn( + instance: object, + key: string, + telegraf: Telegraf, + metadata: any, + ) { + telegraf.on(metadata.updateTypes, instance[key].bind(instance)); + } + + handleTelegrafStart( + instance: object, + key: string, + telegraf: Telegraf, + ) { + telegraf.start(instance[key].bind(instance)); + } + + handleTelegrafHears( + instance: object, + key: string, + telegraf: Telegraf, + metadata: any, + ) { + telegraf.hears(metadata.triggers, instance[key].bind(instance)); + } +} diff --git a/lib/telegraf.module.ts b/lib/telegraf.module.ts index 9bf8840..a0c34de 100644 --- a/lib/telegraf.module.ts +++ b/lib/telegraf.module.ts @@ -1,63 +1,25 @@ -import { Module, DynamicModule, Provider } from '@nestjs/common'; +import { Module, DynamicModule } from '@nestjs/common'; +import { TelegrafCoreModule } from './telegraf-core.module'; import { + TelegrafModuleOptions, TelegrafModuleAsyncOptions, - TelegrafOptionsFactory } from './interfaces'; -import { - TELEGRAF_MODULE_OPTIONS, - TokenInjectionToken -} from './telegraf.constants'; -import { TelegrafService, TelegrafTelegramService } from './'; @Module({}) export class TelegrafModule { - static fromFactory(options: TelegrafModuleAsyncOptions): DynamicModule { + public static forRoot(options?: TelegrafModuleOptions): DynamicModule { return { module: TelegrafModule, - imports: options.imports || [], - providers: [ - ...this.createAsyncProviders(options), - TelegrafService, - TelegrafTelegramService, - { - provide: TokenInjectionToken, - useClass: options.useClass - } - ], - exports: [TelegrafService, TelegrafTelegramService] + imports: [TelegrafCoreModule.forRoot(options)], }; } - 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 || [] - }; - } + public static forRootAsync( + options: TelegrafModuleAsyncOptions, + ): DynamicModule { return { - provide: TELEGRAF_MODULE_OPTIONS, - useFactory: async (optionsFactory: TelegrafOptionsFactory) => - await optionsFactory.createTelegrafOptions(), - inject: [options.useExisting || options.useClass] + module: TelegrafModule, + imports: [TelegrafCoreModule.forRootAsync(options)], }; } } diff --git a/lib/telegraf.provider.ts b/lib/telegraf.provider.ts new file mode 100644 index 0000000..4f16c60 --- /dev/null +++ b/lib/telegraf.provider.ts @@ -0,0 +1,28 @@ +import { + Injectable, + Inject, + OnApplicationBootstrap, + Logger, +} from '@nestjs/common'; +import Telegraf, { ContextMessageUpdate } from 'telegraf'; +import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants'; +import { TelegrafModuleOptions } from './interfaces'; + +@Injectable() +// @ts-ignore +export class TelegrafProvider + extends Telegraf + implements OnApplicationBootstrap { + private logger = new Logger('Telegraf'); + + constructor(@Inject(TELEGRAF_MODULE_OPTIONS) options: TelegrafModuleOptions) { + super(options.token, options.options); + } + + onApplicationBootstrap() { + this.catch(e => { + this.logger.error(e); + }); + this.startPolling(); + } +} diff --git a/lib/telegraf.service.ts b/lib/telegraf.service.ts deleted file mode 100644 index 5ba6e8a..0000000 --- a/lib/telegraf.service.ts +++ /dev/null @@ -1,183 +0,0 @@ -import { Inject, Injectable, Logger } from '@nestjs/common' -import { ModuleRef } from '@nestjs/core' -import { flatten, head } from 'lodash' -import Telegraf, { ContextMessageUpdate } from 'telegraf' -import { TelegramActionHandler, TelegramCatch } from './decorators' -import { InvalidConfigurationException } from './exeptions' -import { - ContextTransformer, - Handler, - TelegrafOptionsFactory, - TelegramErrorHandler, -} from './interfaces' -import { TokenInjectionToken } from './telegraf.constants' - -@Injectable() -export class TelegrafService { - private readonly logger = new Logger(TelegrafService.name, true) - private readonly sitePublicUrl?: string - public readonly bot: Telegraf - private ref: ModuleRef - - public constructor( - @Inject(TokenInjectionToken) options: TelegrafOptionsFactory - ) { - const { - token, - sitePublicUrl, - telegrafOptions, - } = options.createTelegrafOptions() - this.sitePublicUrl = sitePublicUrl - this.bot = new Telegraf(token, telegrafOptions) - } - - public init(ref: ModuleRef, devMode: boolean = false) { - this.ref = ref - - const handlers = this.createHandlers() - - this.setupOnStart(handlers) - this.setupOn(handlers) - this.setupOnMessage(handlers) - this.setupOnCommand(handlers) - this.setupActions(handlers) - - if (devMode) { - this.startPolling() - } - } - - public getMiddleware(path: string) { - if (!this.sitePublicUrl) { - throw new InvalidConfigurationException( - 'sitePublicUrl', - 'does not exist, but webook used' - ) - } - - const url = `${this.sitePublicUrl}/${path}` - - this.bot.telegram - .setWebhook(url) - .then(() => this.logger.log(`Webhook set success @ ${url}`)) - - return this.bot.webhookCallback(`/${path}`) - } - - public startPolling() { - this.bot.telegram.deleteWebhook().then( - () => this.bot.startPolling(), - () => { - // okay, never mind - } - ) - } - - private createHandlers(): Handler[] { - return flatten( - Array.from((TelegramActionHandler.handlers || new Map()).entries()).map( - ([handlerClass, classConfig]) => { - const handlerInstance = this.ref.get(handlerClass, { strict: false }) - - return Array.from(classConfig.entries()).map( - ([methodName, methodCondig]) => ({ - handle: handlerInstance[methodName].bind(handlerInstance), - config: methodCondig, - }) - ) - } - ) - ) - } - - private setupOnStart(handlers: Handler[]): void { - const onStart = handlers.filter(({ config }) => config.onStart) - - if (onStart.length !== 1) { - throw new Error() - } - - this.bot.start(this.adoptHandle(head(onStart))) - } - - private setupOn(handlers: Handler[]): void { - const onHandlers = handlers.filter(({ config }) => config.on) - - onHandlers.forEach(handler => { - this.bot.on(handler.config.on, this.adoptHandle(handler)) - }) - } - - private setupOnMessage(handlers: Handler[]): void { - const onMessageHandlers = handlers.filter( - ({ config }) => config.message !== undefined - ) - - onMessageHandlers.forEach(handler => { - if (handler.config.message) { - this.bot.hears(handler.config.message, this.adoptHandle(handler)) - } else { - this.bot.on('message', this.adoptHandle(handler)) - } - }) - } - - private setupOnCommand(handlers: Handler[]): void { - const commandHandlers = handlers.filter(({ config }) => config.command) - - commandHandlers.forEach(handler => { - this.bot.command(handler.config.command, this.adoptHandle(handler)) - }) - } - - private setupActions(handlers: Handler[]): void { - const commandHandlers = handlers.filter(({ config }) => config.action) - - commandHandlers.forEach(handler => { - this.bot.action(handler.config.action, this.adoptHandle(handler)) - }) - } - - private adoptHandle({ handle, config }: Handler) { - const errorHandler = this.createCatch() - - return async (ctx: ContextMessageUpdate) => { - const args = await Promise.all( - (config.transformations || []) - .sort((a, b) => a.index - b.index) - .map(({ transform }) => - this.ref - .get(transform, { strict: false }) - .transform(ctx) - ) - ) - - return handle(ctx, ...args).catch(errorHandler(ctx)) - } - } - - private createCatch() { - const handlers = Array.from( - (TelegramCatch.handlers || new Map()).entries() - ).map(([errorType, handlerType]) => { - const handler = this.ref.get(handlerType, { - strict: false, - }) - - return { - errorType, - handler, - } - }) - - return (ctx: ContextMessageUpdate) => (e: any) => { - for (const { errorType, handler } of handlers) { - if (e instanceof (errorType as any)) { - return handler.catch(ctx, e) - } - } - - throw e - } - } -}