mirror of
https://github.com/Maks1mS/nestjs-telegraf.git
synced 2024-12-23 22:52:59 +03:00
feat: complete rewrite
This commit is contained in:
parent
5cf452784f
commit
9fedcb964f
@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"trailingComma": "none",
|
"trailingComma": "all",
|
||||||
"singleQuote": true
|
"singleQuote": true
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
export * from './pipe-context.decorator'
|
export * from './telegraf-on.decorator';
|
||||||
export * from './telegram-action-handler.decorator'
|
export * from './telegraf-hears.decorator';
|
||||||
export * from './telegram-catch.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 './telegraf.module';
|
||||||
export * from './interfaces'
|
export * from './interfaces';
|
||||||
export * from './decorators'
|
export * from './decorators';
|
||||||
export * from './telegraf.service'
|
|
||||||
export * from './telegraf-telegram.service'
|
|
||||||
|
@ -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 './telegraf-options.interface';
|
||||||
export * from './handler.interface'
|
|
||||||
export * from './handle-parameters.interface'
|
|
||||||
export * from './telegram-error-handler.interface'
|
|
||||||
export * from './context-transformer.interface'
|
|
||||||
|
@ -1,22 +1,21 @@
|
|||||||
import { ModuleMetadata, Type } from '@nestjs/common/interfaces'
|
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
|
||||||
import { TelegrafOptions } from 'telegraf'
|
import { TelegrafOptions } from 'telegraf';
|
||||||
|
|
||||||
export interface TelegrafModuleOptions {
|
export interface TelegrafModuleOptions {
|
||||||
token: string
|
token: string;
|
||||||
sitePublicUrl?: string
|
options?: TelegrafOptions;
|
||||||
telegrafOptions?: TelegrafOptions
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TelegrafOptionsFactory {
|
export interface TelegrafOptionsFactory {
|
||||||
createTelegrafOptions(): TelegrafModuleOptions
|
createTelegrafOptions(): TelegrafModuleOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TelegrafModuleAsyncOptions
|
export interface TelegrafModuleAsyncOptions
|
||||||
extends Pick<ModuleMetadata, 'imports'> {
|
extends Pick<ModuleMetadata, 'imports'> {
|
||||||
useExisting?: Type<TelegrafOptionsFactory>
|
useExisting?: Type<TelegrafOptionsFactory>;
|
||||||
useClass?: Type<TelegrafOptionsFactory>
|
useClass?: Type<TelegrafOptionsFactory>;
|
||||||
useFactory?: (
|
useFactory?: (
|
||||||
...args: any[]
|
...args: any[]
|
||||||
) => Promise<TelegrafModuleOptions> | TelegrafModuleOptions
|
) => Promise<TelegrafModuleOptions> | TelegrafModuleOptions;
|
||||||
inject?: any[]
|
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 TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS';
|
||||||
export const TokenInjectionToken = Symbol('TokenInjectionToken')
|
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 {
|
import {
|
||||||
|
TelegrafModuleOptions,
|
||||||
TelegrafModuleAsyncOptions,
|
TelegrafModuleAsyncOptions,
|
||||||
TelegrafOptionsFactory
|
|
||||||
} from './interfaces';
|
} from './interfaces';
|
||||||
import {
|
|
||||||
TELEGRAF_MODULE_OPTIONS,
|
|
||||||
TokenInjectionToken
|
|
||||||
} from './telegraf.constants';
|
|
||||||
import { TelegrafService, TelegrafTelegramService } from './';
|
|
||||||
|
|
||||||
@Module({})
|
@Module({})
|
||||||
export class TelegrafModule {
|
export class TelegrafModule {
|
||||||
static fromFactory(options: TelegrafModuleAsyncOptions): DynamicModule {
|
public static forRoot(options?: TelegrafModuleOptions): DynamicModule {
|
||||||
return {
|
return {
|
||||||
module: TelegrafModule,
|
module: TelegrafModule,
|
||||||
imports: options.imports || [],
|
imports: [TelegrafCoreModule.forRoot(options)],
|
||||||
providers: [
|
|
||||||
...this.createAsyncProviders(options),
|
|
||||||
TelegrafService,
|
|
||||||
TelegrafTelegramService,
|
|
||||||
{
|
|
||||||
provide: TokenInjectionToken,
|
|
||||||
useClass: options.useClass
|
|
||||||
}
|
|
||||||
],
|
|
||||||
exports: [TelegrafService, TelegrafTelegramService]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static createAsyncProviders(
|
public static forRootAsync(
|
||||||
options: TelegrafModuleAsyncOptions
|
options: TelegrafModuleAsyncOptions,
|
||||||
): Provider[] {
|
): DynamicModule {
|
||||||
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 {
|
return {
|
||||||
provide: TELEGRAF_MODULE_OPTIONS,
|
module: TelegrafModule,
|
||||||
useFactory: options.useFactory,
|
imports: [TelegrafCoreModule.forRootAsync(options)],
|
||||||
inject: options.inject || []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
provide: TELEGRAF_MODULE_OPTIONS,
|
|
||||||
useFactory: async (optionsFactory: TelegrafOptionsFactory) =>
|
|
||||||
await optionsFactory.createTelegrafOptions(),
|
|
||||||
inject: [options.useExisting || options.useClass]
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user