mirror of
				https://github.com/Maks1mS/nestjs-telegraf.git
				synced 2025-10-30 13:37:14 +03:00 
			
		
		
		
	feat: complete rewrite
This commit is contained in:
		| @@ -1,4 +1,4 @@ | ||||
| { | ||||
|   "trailingComma": "none", | ||||
|   "trailingComma": "all", | ||||
|   "singleQuote": true | ||||
| } | ||||
|   | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -1,18 +0,0 @@ | ||||
| import { Type } from '@nestjs/common' | ||||
| import { ContextTransformer } from '../interfaces' | ||||
| import { addHandlerToStore } from './' | ||||
|  | ||||
| export const PipeContext = <T>(transform: Type<ContextTransformer<T>>) => ( | ||||
|   target: Object, | ||||
|   propertyKey: string, | ||||
|   parameterIndex: number, | ||||
| ) => { | ||||
|   addHandlerToStore(target, propertyKey, { | ||||
|     transformations: [ | ||||
|       { | ||||
|         index: parameterIndex, | ||||
|         transform, | ||||
|       }, | ||||
|     ], | ||||
|   }) | ||||
| } | ||||
							
								
								
									
										13
									
								
								lib/decorators/telegraf-hears.decorator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								lib/decorators/telegraf-hears.decorator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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 }); | ||||
| } | ||||
							
								
								
									
										15
									
								
								lib/decorators/telegraf-on.decorator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/decorators/telegraf-on.decorator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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 }); | ||||
| } | ||||
							
								
								
									
										11
									
								
								lib/decorators/telegraf-start.decorator.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								lib/decorators/telegraf-start.decorator.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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, {}); | ||||
| } | ||||
| @@ -1,44 +0,0 @@ | ||||
| import { HandleParameters } from '../interfaces' | ||||
|  | ||||
| type Decorator = ( | ||||
|   params: HandleParameters, | ||||
| ) => (target: any, propertyKey: string, descriptor: PropertyDescriptor) => void | ||||
|  | ||||
| type HandlerDecorator = Decorator & { | ||||
|   handlers?: Map<any, Map<string, HandleParameters>> | ||||
| } | ||||
|  | ||||
| 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 || []), | ||||
|     ], | ||||
|   }) | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| import { Type } from '@nestjs/common' | ||||
| import { TelegramErrorHandler } from '../interfaces' | ||||
|  | ||||
| type Decorator = (error: any) => ClassDecorator | ||||
|  | ||||
| type HandlerDecorator = Decorator & { | ||||
|   handlers?: Map<Error, Type<TelegramErrorHandler>> | ||||
| } | ||||
|  | ||||
| export const TelegramCatch: HandlerDecorator = error => target => { | ||||
|   if (!TelegramCatch.handlers) { | ||||
|     TelegramCatch.handlers = new Map() | ||||
|   } | ||||
|  | ||||
|   TelegramCatch.handlers.set(error, target as any) | ||||
|  | ||||
|   return target | ||||
| } | ||||
| @@ -1 +0,0 @@ | ||||
| export * from './invalid-configuration.exeption' | ||||
| @@ -1,10 +0,0 @@ | ||||
| export class InvalidConfigurationException extends Error { | ||||
|   public constructor( | ||||
|     public readonly invalidField, | ||||
|     public readonly invalidCause, | ||||
|   ) { | ||||
|     super( | ||||
|       `Options validation failed, "${invalidField}" invalid — ${invalidCause}`, | ||||
|     ) | ||||
|   } | ||||
| } | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -1,5 +0,0 @@ | ||||
| import { ContextMessageUpdate } from 'telegraf' | ||||
|  | ||||
| export interface ContextTransformer<T = any> { | ||||
|   transform: (ctx: ContextMessageUpdate) => Promise<T> | ||||
| } | ||||
| @@ -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<ContextTransformer> | ||||
| } | ||||
|  | ||||
| export interface HandleParameters { | ||||
|   onStart?: boolean | ||||
|   on?: UpdateType | UpdateType[] | MessageSubTypes | MessageSubTypes[] | ||||
|   command?: string | ||||
|   message?: string | RegExp | ||||
|   action?: HearsTriggers | ||||
|   transformations?: ArgumentTransformation[] | ||||
| } | ||||
| @@ -1,6 +0,0 @@ | ||||
| import { HandleParameters } from './' | ||||
|  | ||||
| export interface Handler { | ||||
|   handle: (...args: any[]) => Promise<void> | ||||
|   config: HandleParameters | ||||
| } | ||||
| @@ -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'; | ||||
|   | ||||
| @@ -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<ModuleMetadata, 'imports'> { | ||||
|   useExisting?: Type<TelegrafOptionsFactory> | ||||
|   useClass?: Type<TelegrafOptionsFactory> | ||||
|   useExisting?: Type<TelegrafOptionsFactory>; | ||||
|   useClass?: Type<TelegrafOptionsFactory>; | ||||
|   useFactory?: ( | ||||
|     ...args: any[] | ||||
|   ) => Promise<TelegrafModuleOptions> | TelegrafModuleOptions | ||||
|   inject?: any[] | ||||
|   ) => Promise<TelegrafModuleOptions> | TelegrafModuleOptions; | ||||
|   inject?: any[]; | ||||
| } | ||||
|   | ||||
| @@ -1,5 +0,0 @@ | ||||
| import { ContextMessageUpdate } from 'telegraf' | ||||
|  | ||||
| export interface TelegramErrorHandler<E = any> { | ||||
|   catch(ctx: ContextMessageUpdate, error: E): Promise<void> | ||||
| } | ||||
							
								
								
									
										83
									
								
								lib/telegraf-core.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										83
									
								
								lib/telegraf-core.module.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<TelegrafOptionsFactory>; | ||||
|     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<TelegrafOptionsFactory>` is a workaround for microsoft/TypeScript#31603 | ||||
|     const inject = [ | ||||
|       (options.useClass || options.useExisting) as Type<TelegrafOptionsFactory>, | ||||
|     ]; | ||||
|     return { | ||||
|       provide: TELEGRAF_MODULE_OPTIONS, | ||||
|       useFactory: async (optionsFactory: TelegrafOptionsFactory) => | ||||
|         await optionsFactory.createTelegrafOptions(), | ||||
|       inject, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
							
								
								
									
										38
									
								
								lib/telegraf-metadata.accessor.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								lib/telegraf-metadata.accessor.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<any> | Function): boolean { | ||||
|     if (!target) { | ||||
|       return false; | ||||
|     } | ||||
|     return !!this.reflector.get(DECORATORS.START, target); | ||||
|   } | ||||
|  | ||||
|   isTelegrafOn(target: Type<any> | Function): boolean { | ||||
|     if (!target) { | ||||
|       return false; | ||||
|     } | ||||
|     return !!this.reflector.get(DECORATORS.ON, target); | ||||
|   } | ||||
|  | ||||
|   getTelegrafOnMetadata(target: Type<any> | Function) { | ||||
|     return this.reflector.get(DECORATORS.ON, target); | ||||
|   } | ||||
|  | ||||
|   isTelegrafHears(target: Type<any> | Function): boolean { | ||||
|     if (!target) { | ||||
|       return false; | ||||
|     } | ||||
|     return !!this.reflector.get(DECORATORS.HEARS, target); | ||||
|   } | ||||
|  | ||||
|   getTelegrafHearsMetadata(target: Type<any> | Function) { | ||||
|     return this.reflector.get(DECORATORS.HEARS, target); | ||||
|   } | ||||
| } | ||||
| @@ -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, {}); | ||||
|   } | ||||
| } | ||||
| @@ -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`, | ||||
| }; | ||||
|   | ||||
							
								
								
									
										85
									
								
								lib/telegraf.explorer.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								lib/telegraf.explorer.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<TelegrafProvider<any>>( | ||||
|         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<ContextMessageUpdate>, | ||||
|     metadata: any, | ||||
|   ) { | ||||
|     telegraf.on(metadata.updateTypes, instance[key].bind(instance)); | ||||
|   } | ||||
|  | ||||
|   handleTelegrafStart( | ||||
|     instance: object, | ||||
|     key: string, | ||||
|     telegraf: Telegraf<ContextMessageUpdate>, | ||||
|   ) { | ||||
|     telegraf.start(instance[key].bind(instance)); | ||||
|   } | ||||
|  | ||||
|   handleTelegrafHears( | ||||
|     instance: object, | ||||
|     key: string, | ||||
|     telegraf: Telegraf<ContextMessageUpdate>, | ||||
|     metadata: any, | ||||
|   ) { | ||||
|     telegraf.hears(metadata.triggers, instance[key].bind(instance)); | ||||
|   } | ||||
| } | ||||
| @@ -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)], | ||||
|     }; | ||||
|   } | ||||
| } | ||||
|   | ||||
							
								
								
									
										28
									
								
								lib/telegraf.provider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								lib/telegraf.provider.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -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<TContext extends ContextMessageUpdate> | ||||
|   extends Telegraf<TContext> | ||||
|   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(); | ||||
|   } | ||||
| } | ||||
| @@ -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<ContextMessageUpdate> | ||||
|   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<ContextTransformer>(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<TelegramErrorHandler>(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 | ||||
|     } | ||||
|   } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user