merge refactor/v2

This commit is contained in:
Morb0
2021-01-05 00:32:31 +03:00
81 changed files with 34665 additions and 4110 deletions

View 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
View File

@@ -0,0 +1,3 @@
export * from './listeners-explorer.service';
export * from './metadata-accessor.service';
export * from './listeners-explorer.service';

View 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);
});
}
}

View File

@@ -0,0 +1,36 @@
import { Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import {
SCENE_METADATA,
UPDATE_LISTENER_METADATA,
UPDATE_METADATA,
} from '../telegraf.constants';
import { ListenerMetadata } from '../interfaces';
@Injectable()
export class 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);
}
getListenerMetadata(target: Function): ListenerMetadata | undefined {
return this.reflector.get(UPDATE_LISTENER_METADATA, target);
}
getSceneMetadata(target: Function): string | undefined {
return this.reflector.get(SCENE_METADATA, target);
}
}