mirror of
https://github.com/Maks1mS/nestjs-telegraf.git
synced 2025-09-23 09:49:06 +03:00
merge refactor/v2
This commit is contained in:
@@ -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));
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling callback_data actions with regular expressions.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=action
|
||||
*/
|
||||
export const Action = createUpdateListenerDecorator('action');
|
||||
export const Action = createListenerDecorator('action');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Cashtag handling.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=cashtag
|
||||
*/
|
||||
export const Cashtag = createUpdateListenerDecorator('cashtag');
|
||||
export const Cashtag = createMissedListenerDecorator<[string | string[]]>(
|
||||
'cashtag',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Command handling.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=command
|
||||
*/
|
||||
export const Command = createUpdateListenerDecorator('command');
|
||||
export const Command = createListenerDecorator('command');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling messages with email entity.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=telegraf-email
|
||||
*/
|
||||
export const Email = createUpdateListenerDecorator('email');
|
||||
export const Email = createMissedListenerDecorator<[string | string[]]>(
|
||||
'email',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling callback_data actions with game query.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=inlinequery
|
||||
*/
|
||||
export const GameQuery = createUpdateListenerDecorator('gameQuery');
|
||||
export const GameQuery = createListenerDecorator('gameQuery');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Hashtag handling.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=hashtag
|
||||
*/
|
||||
export const Hashtag = createUpdateListenerDecorator('hashtag');
|
||||
export const Hashtag = createMissedListenerDecorator<[string | string[]]>(
|
||||
'hashtag',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling text messages.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=hears
|
||||
*/
|
||||
export const Hears = createUpdateListenerDecorator('hears');
|
||||
export const Hears = createListenerDecorator('hears');
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Handler for /help command.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=help
|
||||
*/
|
||||
export const Help = createUpdateListenerDecorator('help');
|
||||
export const Help = createListenerDecorator('help');
|
||||
|
@@ -1,8 +1,11 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
import { HearsTriggers } from 'telegraf/typings/composer';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling inline_query actions with regular expressions.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=inlinequery
|
||||
*/
|
||||
export const InlineQuery = createUpdateListenerDecorator('inlineQuery');
|
||||
export const InlineQuery = createMissedListenerDecorator<
|
||||
[HearsTriggers<unknown>]
|
||||
>('inlineQuery');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Mention handling.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=mention
|
||||
*/
|
||||
export const Mention = createUpdateListenerDecorator('mention');
|
||||
export const Mention = createMissedListenerDecorator<[string | string[]]>(
|
||||
'mention',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for provided update type.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=on
|
||||
*/
|
||||
export const On = createUpdateListenerDecorator('on');
|
||||
export const On = createListenerDecorator('on');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Phone number handling.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=phone
|
||||
*/
|
||||
export const Phone = createUpdateListenerDecorator('phone');
|
||||
export const Phone = createMissedListenerDecorator<[string | string[]]>(
|
||||
'phone',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Handler for /settings command.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=settings
|
||||
*/
|
||||
export const Settings = createUpdateListenerDecorator('settings');
|
||||
export const Settings = createMissedListenerDecorator<[]>('settings');
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Handler for /start command.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=start
|
||||
*/
|
||||
export const Start = createUpdateListenerDecorator('start');
|
||||
export const Start = createListenerDecorator('start');
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling messages with text_link entity.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=telegraf-textlink
|
||||
*/
|
||||
export const TextLink = createUpdateListenerDecorator('textLink');
|
||||
export const TextLink = createMissedListenerDecorator<[string | string[]]>(
|
||||
'textLink',
|
||||
);
|
||||
|
@@ -1,8 +1,10 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling messages with text_mention entity.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=telegraf-textlink
|
||||
*/
|
||||
export const TextMention = createUpdateListenerDecorator('textMention');
|
||||
export const TextMention = createMissedListenerDecorator<[string | string[]]>(
|
||||
'textMention',
|
||||
);
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createMissedListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers middleware for handling messages with url entity.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=telegraf-url
|
||||
*/
|
||||
export const Url = createUpdateListenerDecorator('url');
|
||||
export const Url = createMissedListenerDecorator<[string | string[]]>('url');
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { createUpdateListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
/**
|
||||
* Registers a middleware.
|
||||
*
|
||||
* @see https://telegraf.js.org/#/?id=use
|
||||
*/
|
||||
export const Use = createUpdateListenerDecorator('use');
|
||||
export const Use = createListenerDecorator('use');
|
||||
|
@@ -1,3 +1,3 @@
|
||||
import { createSceneListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
export const SceneEnter = createSceneListenerDecorator('enter');
|
||||
export const SceneEnter = createListenerDecorator('enter');
|
||||
|
@@ -1,3 +1,3 @@
|
||||
import { createSceneListenerDecorator } from '../../utils';
|
||||
import { createListenerDecorator } from '../../utils';
|
||||
|
||||
export const SceneLeave = createSceneListenerDecorator('leave');
|
||||
export const SceneLeave = createListenerDecorator('leave');
|
||||
|
@@ -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<never>,
|
||||
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<never>,
|
||||
instance: Record<string, Function>,
|
||||
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);
|
||||
}
|
||||
}
|
@@ -1,7 +1,6 @@
|
||||
export * from './decorators';
|
||||
export * from './interfaces';
|
||||
export * from './utils';
|
||||
export * from './errors';
|
||||
export * from './execution-context';
|
||||
export * from './types';
|
||||
export * from './telegraf.constants';
|
||||
export * from './telegraf.module';
|
||||
export * from './telegraf.types';
|
||||
|
@@ -1,19 +1,21 @@
|
||||
import { ModuleMetadata, Type } from '@nestjs/common/interfaces';
|
||||
import { Middleware, Context } from 'telegraf';
|
||||
import {
|
||||
TelegrafOptions,
|
||||
LaunchPollingOptions,
|
||||
LaunchWebhookOptions,
|
||||
TelegrafOptions,
|
||||
} from 'telegraf/typings/telegraf';
|
||||
|
||||
export interface TelegrafModuleOptions<C extends Context = Context> {
|
||||
token: string;
|
||||
name?: string;
|
||||
options?: TelegrafOptions;
|
||||
launchOptions?: {
|
||||
polling?: LaunchPollingOptions;
|
||||
webhook?: LaunchWebhookOptions;
|
||||
};
|
||||
middlewares?: Middleware<C>[];
|
||||
include?: Function[];
|
||||
middlewares?: ReadonlyArray<Middleware<C>>;
|
||||
}
|
||||
|
||||
export interface TelegrafOptionsFactory {
|
||||
@@ -22,6 +24,7 @@ export interface TelegrafOptionsFactory {
|
||||
|
||||
export interface TelegrafModuleAsyncOptions
|
||||
extends Pick<ModuleMetadata, 'imports'> {
|
||||
botName?: string;
|
||||
useExisting?: Type<TelegrafOptionsFactory>;
|
||||
useClass?: Type<TelegrafOptionsFactory>;
|
||||
useFactory?: (
|
||||
|
37
lib/services/base-explorer.service.ts
Normal file
37
lib/services/base-explorer.service.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper';
|
||||
import { Module } from '@nestjs/core/injector/module';
|
||||
import { flattenDeep, identity, isEmpty } from 'lodash';
|
||||
|
||||
export class BaseExplorerService {
|
||||
getModules(
|
||||
modulesContainer: Map<string, Module>,
|
||||
include: Function[],
|
||||
): Module[] {
|
||||
if (!include || isEmpty(include)) {
|
||||
return [...modulesContainer.values()];
|
||||
}
|
||||
const whitelisted = this.includeWhitelisted(modulesContainer, include);
|
||||
return whitelisted;
|
||||
}
|
||||
|
||||
includeWhitelisted(
|
||||
modulesContainer: Map<string, Module>,
|
||||
include: Function[],
|
||||
): Module[] {
|
||||
const modules = [...modulesContainer.values()];
|
||||
return modules.filter(({ metatype }) => include.includes(metatype));
|
||||
}
|
||||
|
||||
flatMap<T>(
|
||||
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);
|
||||
}
|
||||
}
|
3
lib/services/index.ts
Normal file
3
lib/services/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export * from './listeners-explorer.service';
|
||||
export * from './metadata-accessor.service';
|
||||
export * from './listeners-explorer.service';
|
111
lib/services/listeners-explorer.service.ts
Normal file
111
lib/services/listeners-explorer.service.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
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';
|
||||
import { Module } from '@nestjs/core/injector/module';
|
||||
import { BaseScene, Composer, Stage, Telegraf } from 'telegraf';
|
||||
|
||||
import { MetadataAccessorService } from './metadata-accessor.service';
|
||||
import { TELEGRAF_MODULE_OPTIONS } from '../telegraf.constants';
|
||||
import { TelegrafModuleOptions } from '../interfaces';
|
||||
import { BaseExplorerService } from './base-explorer.service';
|
||||
import { getBotToken } from '../utils';
|
||||
|
||||
@Injectable()
|
||||
export class ListenersExplorerService
|
||||
extends BaseExplorerService
|
||||
implements OnModuleInit {
|
||||
private readonly stage = new Stage([]);
|
||||
private bot: Telegraf<any>;
|
||||
|
||||
constructor(
|
||||
@Inject(TELEGRAF_MODULE_OPTIONS)
|
||||
private readonly telegrafOptions: TelegrafModuleOptions,
|
||||
private readonly moduleRef: ModuleRef,
|
||||
private readonly discoveryService: DiscoveryService,
|
||||
private readonly metadataAccessor: MetadataAccessorService,
|
||||
private readonly metadataScanner: MetadataScanner,
|
||||
private readonly modulesContainer: ModulesContainer,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
onModuleInit(): void {
|
||||
const botToken = getBotToken(this.telegrafOptions.name);
|
||||
this.bot = this.moduleRef.get<Telegraf<never>>(botToken);
|
||||
this.bot.use(this.stage.middleware());
|
||||
|
||||
this.explore();
|
||||
}
|
||||
|
||||
explore(): void {
|
||||
const modules = this.getModules(
|
||||
this.modulesContainer,
|
||||
this.telegrafOptions.include || [],
|
||||
);
|
||||
|
||||
this.registerUpdates(modules);
|
||||
this.registerScenes(modules);
|
||||
}
|
||||
|
||||
private registerUpdates(modules: Module[]): void {
|
||||
const updates = this.flatMap<InstanceWrapper>(modules, (instance) =>
|
||||
this.filterUpdates(instance),
|
||||
);
|
||||
updates.forEach(({ instance }) =>
|
||||
this.registerInstanceMethodListeners(this.bot, instance),
|
||||
);
|
||||
}
|
||||
|
||||
private registerScenes(modules: Module[]): void {
|
||||
const scenes = this.flatMap<InstanceWrapper>(modules, (instance) =>
|
||||
this.filterScenes(instance),
|
||||
);
|
||||
scenes.forEach(({ instance }) => {
|
||||
const sceneId = this.metadataAccessor.getSceneMetadata(
|
||||
instance.constructor,
|
||||
);
|
||||
const scene = new BaseScene(sceneId);
|
||||
this.stage.register(scene);
|
||||
|
||||
this.registerInstanceMethodListeners(scene, instance);
|
||||
});
|
||||
}
|
||||
|
||||
private filterUpdates(wrapper: InstanceWrapper): InstanceWrapper<unknown> {
|
||||
const { instance } = wrapper;
|
||||
if (!instance) return undefined;
|
||||
|
||||
const isUpdate = this.metadataAccessor.isUpdate(wrapper.metatype);
|
||||
if (!isUpdate) return undefined;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private filterScenes(wrapper: InstanceWrapper): InstanceWrapper<unknown> {
|
||||
const { instance } = wrapper;
|
||||
if (!instance) return undefined;
|
||||
|
||||
const isScene = this.metadataAccessor.isScene(wrapper.metatype);
|
||||
if (!isScene) return undefined;
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private registerInstanceMethodListeners(
|
||||
composer: Composer<never>,
|
||||
instance: Record<string, Function>,
|
||||
): void {
|
||||
const prototype = Object.getPrototypeOf(instance);
|
||||
this.metadataScanner.scanFromPrototype(instance, prototype, (name) => {
|
||||
const methodRef = instance[name];
|
||||
|
||||
const metadata = this.metadataAccessor.getListenerMetadata(methodRef);
|
||||
if (!metadata) return;
|
||||
|
||||
const middlewareFn = methodRef.bind(instance);
|
||||
const { method, args } = metadata;
|
||||
composer[method](...args, middlewareFn);
|
||||
});
|
||||
}
|
||||
}
|
@@ -4,18 +4,25 @@ import {
|
||||
SCENE_METADATA,
|
||||
UPDATE_LISTENER_METADATA,
|
||||
UPDATE_METADATA,
|
||||
} from './telegraf.constants';
|
||||
import { ListenerMetadata } from './interfaces';
|
||||
} from '../telegraf.constants';
|
||||
import { ListenerMetadata } from '../interfaces';
|
||||
|
||||
@Injectable()
|
||||
export class TelegrafMetadataAccessor {
|
||||
export class MetadataAccessorService {
|
||||
constructor(private readonly reflector: Reflector) {}
|
||||
|
||||
isUpdate(target: Function): boolean {
|
||||
// TODO: We really need this check?
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
return !!this.reflector.get(UPDATE_METADATA, target);
|
||||
}
|
||||
|
||||
isScene(target: Function): boolean {
|
||||
if (!target) {
|
||||
return false;
|
||||
}
|
||||
return !!this.reflector.get(SCENE_METADATA, target);
|
||||
}
|
||||
|
115
lib/telegraf-core.module.ts
Normal file
115
lib/telegraf-core.module.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { DiscoveryModule, ModuleRef } from '@nestjs/core';
|
||||
import {
|
||||
Module,
|
||||
DynamicModule,
|
||||
Provider,
|
||||
Type,
|
||||
Global,
|
||||
Inject,
|
||||
OnApplicationShutdown,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
TelegrafModuleOptions,
|
||||
TelegrafModuleAsyncOptions,
|
||||
TelegrafOptionsFactory,
|
||||
} from './interfaces';
|
||||
import { TELEGRAF_MODULE_OPTIONS } from './telegraf.constants';
|
||||
import { MetadataAccessorService, ListenersExplorerService } from './services';
|
||||
import { getBotToken, createBotFactory } from './utils';
|
||||
|
||||
@Global()
|
||||
@Module({
|
||||
imports: [DiscoveryModule],
|
||||
providers: [ListenersExplorerService, MetadataAccessorService],
|
||||
})
|
||||
export class TelegrafCoreModule implements OnApplicationShutdown {
|
||||
constructor(
|
||||
@Inject(TELEGRAF_MODULE_OPTIONS)
|
||||
private readonly options: TelegrafModuleOptions,
|
||||
private readonly moduleRef: ModuleRef,
|
||||
) {}
|
||||
|
||||
public static forRoot(options: TelegrafModuleOptions): DynamicModule {
|
||||
const telegrafBotProvider: Provider = {
|
||||
provide: getBotToken(options.name),
|
||||
useFactory: async () => await createBotFactory(options),
|
||||
};
|
||||
|
||||
return {
|
||||
module: TelegrafCoreModule,
|
||||
providers: [
|
||||
{
|
||||
provide: TELEGRAF_MODULE_OPTIONS,
|
||||
useValue: options,
|
||||
},
|
||||
telegrafBotProvider,
|
||||
],
|
||||
exports: [telegrafBotProvider],
|
||||
};
|
||||
}
|
||||
|
||||
public static forRootAsync(
|
||||
options: TelegrafModuleAsyncOptions,
|
||||
): DynamicModule {
|
||||
const telegrafBotName = getBotToken(options.botName);
|
||||
|
||||
const telegrafBotProvider: Provider = {
|
||||
provide: telegrafBotName,
|
||||
useFactory: async (options: TelegrafModuleOptions) =>
|
||||
await createBotFactory(options),
|
||||
inject: [TELEGRAF_MODULE_OPTIONS],
|
||||
};
|
||||
|
||||
const asyncProviders = this.createAsyncProviders(options);
|
||||
return {
|
||||
module: TelegrafCoreModule,
|
||||
imports: options.imports,
|
||||
providers: [...asyncProviders, telegrafBotProvider],
|
||||
exports: [telegrafBotProvider],
|
||||
};
|
||||
}
|
||||
|
||||
async onApplicationShutdown(): Promise<void> {
|
||||
const botName = getBotToken(this.options.name);
|
||||
const bot = this.moduleRef.get<any>(botName);
|
||||
bot && (await bot.stop());
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import { ROUTE_ARGS_METADATA } from '@nestjs/common/constants';
|
||||
|
||||
export const TELEGRAF_MODULE_OPTIONS = 'TELEGRAF_MODULE_OPTIONS';
|
||||
export const DEFAULT_BOT_NAME = 'DEFAULT_BOT_NAME';
|
||||
|
||||
export const UPDATE_METADATA = 'UPDATE_METADATA';
|
||||
export const UPDATE_LISTENER_METADATA = 'UPDATE_LISTENER_METADATA';
|
||||
|
@@ -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<void> {
|
||||
const { launchOptions } = this.options;
|
||||
const telegraf = this.moduleRef.get(Telegraf);
|
||||
await telegraf.launch(launchOptions);
|
||||
}
|
||||
|
||||
async onApplicationShutdown(): Promise<void> {
|
||||
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],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +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);
|
||||
telegraf.use(...options.middlewares);
|
||||
return telegraf;
|
||||
},
|
||||
inject: [TELEGRAF_MODULE_OPTIONS],
|
||||
};
|
||||
|
||||
export function createProviders(options: TelegrafModuleOptions): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: TELEGRAF_MODULE_OPTIONS,
|
||||
useValue: options,
|
||||
},
|
||||
];
|
||||
}
|
@@ -17,5 +17,5 @@ export type ComposerMethodArgs<
|
||||
U extends OnlyFunctionPropertyNames<T> = OnlyFunctionPropertyNames<T>
|
||||
> = Filter<Parameters<T[U]>, Middleware<never>>;
|
||||
|
||||
export type UpdateMethods = OnlyFunctionPropertyNames<Composer<never>>;
|
||||
export type ComposerMethods = OnlyFunctionPropertyNames<Composer<never>>;
|
||||
export type SceneMethods = OnlyFunctionPropertyNames<BaseScene<never>>;
|
13
lib/utils/create-bot-factory.util.ts
Normal file
13
lib/utils/create-bot-factory.util.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Telegraf } from 'telegraf';
|
||||
import { TelegrafModuleOptions } from '../interfaces';
|
||||
|
||||
export async function createBotFactory(
|
||||
options: TelegrafModuleOptions,
|
||||
): Promise<Telegraf<never>> {
|
||||
const bot = new Telegraf<never>(options.token, options.options);
|
||||
|
||||
bot.use(...(options.middlewares ?? []));
|
||||
await bot.launch(options.launchOptions);
|
||||
|
||||
return bot;
|
||||
}
|
29
lib/utils/create-listener-decorator.util.ts
Normal file
29
lib/utils/create-listener-decorator.util.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { BaseScene as Scene } from 'telegraf';
|
||||
import { ComposerMethodArgs, SceneMethods } from '../types';
|
||||
import { UPDATE_LISTENER_METADATA } from '../telegraf.constants';
|
||||
import { ListenerMetadata } from '../interfaces';
|
||||
|
||||
export function createListenerDecorator<TMethod extends SceneMethods>(
|
||||
method: TMethod,
|
||||
) {
|
||||
return (
|
||||
...args: ComposerMethodArgs<Scene<never>, TMethod>
|
||||
): MethodDecorator => {
|
||||
return SetMetadata(UPDATE_LISTENER_METADATA, {
|
||||
method,
|
||||
args,
|
||||
} as ListenerMetadata);
|
||||
};
|
||||
}
|
||||
|
||||
export function createMissedListenerDecorator<TArgs extends any[]>(
|
||||
method: string,
|
||||
) {
|
||||
return (...args: TArgs): MethodDecorator => {
|
||||
return SetMetadata(UPDATE_LISTENER_METADATA, {
|
||||
method,
|
||||
args,
|
||||
} as ListenerMetadata);
|
||||
};
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { BaseScene as Scene } from 'telegraf';
|
||||
import { ComposerMethodArgs, SceneMethods } from '../telegraf.types';
|
||||
import { UPDATE_LISTENER_METADATA } from '../telegraf.constants';
|
||||
import { ListenerMetadata } from '../interfaces';
|
||||
|
||||
export function createSceneListenerDecorator<Method extends SceneMethods>(
|
||||
method: Method,
|
||||
) {
|
||||
return (
|
||||
...args: ComposerMethodArgs<Scene<never>, Method>
|
||||
): MethodDecorator => {
|
||||
return SetMetadata(UPDATE_LISTENER_METADATA, {
|
||||
method,
|
||||
args,
|
||||
} as ListenerMetadata);
|
||||
};
|
||||
}
|
@@ -1,18 +0,0 @@
|
||||
import { SetMetadata } from '@nestjs/common';
|
||||
import { Composer } from 'telegraf';
|
||||
import { ComposerMethodArgs, UpdateMethods } from '../telegraf.types';
|
||||
import { UPDATE_LISTENER_METADATA } from '../telegraf.constants';
|
||||
import { ListenerMetadata } from '../interfaces';
|
||||
|
||||
export function createUpdateListenerDecorator<Method extends UpdateMethods>(
|
||||
method: Method,
|
||||
) {
|
||||
return (
|
||||
...args: ComposerMethodArgs<Composer<never>, Method>
|
||||
): MethodDecorator => {
|
||||
return SetMetadata(UPDATE_LISTENER_METADATA, {
|
||||
method,
|
||||
args,
|
||||
} as ListenerMetadata);
|
||||
};
|
||||
}
|
5
lib/utils/get-bot-token.util.ts
Normal file
5
lib/utils/get-bot-token.util.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import { DEFAULT_BOT_NAME } from '../telegraf.constants';
|
||||
|
||||
export function getBotToken(name?: string): string {
|
||||
return name && name !== DEFAULT_BOT_NAME ? `${name}Bot` : DEFAULT_BOT_NAME;
|
||||
}
|
@@ -1,2 +1,3 @@
|
||||
export * from './create-update-listener-decorator.util';
|
||||
export * from './create-scene-listener-decorator.util';
|
||||
export * from './get-bot-token.util';
|
||||
export * from './create-bot-factory.util';
|
||||
export * from './create-listener-decorator.util';
|
||||
|
Reference in New Issue
Block a user